DropBox as a No Paste server

In this blog post I'm going to talk about my own custom "no paste" solution that I've developed over the years. How I started out using a web page as a service, moved to scripting this from the command line, and how I finally ended up subverting DropBox to my ends. Skip to the end to get to the code. So, what's "no paste" I hear you ask? "No paste" servers allow you to go to a webpage and submit a bit of text and get a unique url back where that text can be viewed. This means that you don't have to paste a whole bunch of text into IRC or an IM conversation, you can just upload your text and and copy and paste only the link the "no paste" server sent back into your IM. If you haven't seen one before check out textmate's nopaste server. This has several advantages. Firstly and foremostly, it doesn't "SPAM" the conversation you're having. Pasting a whole bunch of code into a IRC channel where people are having a conversation causing that conversation to scroll off the screen before they can read it isn't polite. Secondly it makes the text easier to read and easier to copy and paste into an editor (for example, most IRC an IM clients will prepend each line someone says with a datestamp when you copy and paste from them.) Excellent. A useful idea. Now how can we make it better? As a Perl programmer I tend to automate a heck of a lot of what I do with my computer. Filing in a form on a webpage is easy to do, but it's more hassle than hitting a key combination that pastes whatever text is highlighted in your editor. If we do it a lot we should make the computer do all the work! For a long time I used the App::Nopaste module on CPAN which installs a command line utility called nopaste which integrates with a vast range of "no paste" servers. This utility can take input on the command line and automatically fill in the forms on those websites for you. This means that it's trivial to execute from your editor - in textmate it's just a simple "Bundle" command. In the end I stopped using nopaste not because I had a problem with the script, but because I had a problem with the nopaste servers, in particular the lack of privacy. Now, I'm a great believer in simply not putting anything on the internet that is truly private (face it, it's going to get out!) but there exists a bunch of "semi-private" stuff (closed source code, contact information, private correspondence) that shouldn't be put on a totally public paste service. Often it's just a case of editing the URL that the "no paste" server returns by increasing or decreasing the number at the end to see the thing the next or previous person pasted! So in the end I decided it might be a good idea to run my own custom "No Paste" solution with semi-secure (non-guessable) URLs. One problem with that: I couldn't justify the infrastructure - I'm currently trying to reduce the amount of stuff I have to maintain, not increase it. So I started looking at what infrastructure I'm already using and seeing how I can better use that. Enter DropBox. DropBox is a service that syncs a directory on your various computers with each other and the DropBox server. And one of the things it does is publish files in a certain directory as accessible from the web. This simplifies my problem a lot: All I need to do to have my own "No Paste" solution is simply have an easy way of getting text into a file on my hard drive and let the DropBox program automatically handle the "uploading" to a hosted service. So, below is the script I wrote to do that. Features include:
  • Using a web-safe version of the "MD5 hex" one way hash of the file contents as the filename. This means that it's both unguessable unless you know what the text contains and reasonably guaranteed to be unique
  • Taking input from STDIN or the system clipboard
  • Printing out the URL that the text will be available at, and/or copying it to the clipboard, and/or displaying it in a Growl message
#!/usr/bin/perl

use strict;
use warnings;

use 5.010;
use autodie;
use Path::Class qw(file dir);
use Digest::MD5 qw(md5_base64);
use Net::Growl qw(register notify);
use Getopt::Std qw(getopt);

########################################################################
# config

my $DROPBOX_ID = "301667";
my $GROWL_PASSWORD = "shoutout";

########################################################################

# get the config options
my %opt;
getopt("",\%opt);

# read the entire of STDIN / the files passed on the command line
my $data = $opt{c}
  ? read_clipboard()
  : do { local $/; scalar <> };

# work out the digest for it.  Covert the non url safe characters
# to url safe characters
my $uuid = md5_base64($data);
$uuid =~ s{/}{-}g;
$uuid =~ s{\+}{_}g;

# copy the data to the new file
open my $fh, ">:bytes",
  file($ENV{HOME},"Dropbox","Public","nopaste","$uuid.txt");
print {$fh} $data;
close $fh;

# output the url that dropbox will make that file avalible at
my $url = "http://dl.getdropbox.com/u/$DROPBOX_ID/nopaste/$uuid.txt";
say $url unless $opt{q};
write_clipboard($url) if $opt{p};
if ($opt{g}) {
  my $message = "shortly at $url";
  $message .= " (copied to clipboard)" if $opt{p};
  growl("Text Dropped", $message);
}

########################################################################

# this is mac os X depenent.  I'd use the Clipboard module from CPAN
# to make this system independent, but it fails tests.

sub read_clipboard {
  open my $pfh, "-|", "pbpaste";
  local $/;
  return scalar <$pfh>;
}

sub write_clipboard {
  my $data = shift;
  
  open my $pfh, "|-", "pbcopy"; 
  print {$pfh} $data;
  close $pfh;
}

sub growl {
  my $title = shift;
  my $description = shift;
  
  register(
    application => "droptxt",
    password => $GROWL_PASSWORD,
  );

  notify(
    application => "droptxt",
    password => $GROWL_PASSWORD,
    title => $title,
    description => $description,
  );
  
}

########################################################################

__END__

=head1 NAME

droptxt - easily write text to a file in your public dropbox

=head1 SYNOPSIS

  # read from stdin
  bash$ droptxt
  this is some text
  ^D
  http://dl.getdropbox.com/u/301667/nopaste/4ZwSg8klsyBmhf9SKs-j5g.txt
  
  # read from a file
  bash$ droptxt some_text.txt
  http://dl.getdropbox.com/u/301667/nopaste/asdSDsq_asdQsasdw12s3d.txt
  
  # read from the clipboard
  bash$ droptxt -c
  http://dl.getdropbox.com/u/301667/nopaste/cssj12-22WWdsqQfxjpDDe.txt
  
  # also paste the url to the clipboard
  bash droptxt -p some_text.txt
  http://dl.getdropbox.com/u/301667/nopaste/asdSDsq_asdQsasdw12s3d.txt

=head1 DESCRIPTION

This is a command line utility that is designed to be used as an
alternative to "no paste" utilities.  Instead of sending the input to a
webserver it simply writes it to a location on your hard drive where the
DropBox utility will syncronize it with the Dropox webservers.

=head2 Options

=over

=item -c

Copy the input from the system clipboard rather than from the usual
location.

=item -p

Paste the url to the system clipboard.

=item -g

Announce the url via Growl

=item -q

Do not print the url to STDOUT

=back

=head1 AUTHOR

Copyright Mark Fowler E<lt>mark@twoshortplanks.comE<gt> 2009. All rights reserved.

This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.

=head1 BUGS

Doesn't wait for DropBox to sync the file.  The URL this creates may not be
usable straight away!

=head1 SEE ALSO

L<http://www.getdropbox.com> for details on the service.

L<App::Nopaste> for a utility that uses public "no paste" servers instead.

=cut

- to blog -

blog built using the cayman-theme by Jason Long. LICENSE