Shortened URLs with Quicksilver

Update The changes to this post and to the script at the heart of it have gotten out of hand. The text below that describes the motivation for the script is still valid, but the script itself has been “improved” to the point where it’s ugly, unreadable, and invalid for the most recent version of Tiger. I’ve rewritten it in AppleScript, and the new version is much cleaner and should run on any version of 10.4 or 10.5 without alteration. You can find the new version here.

John Gruber has been touting the Metamark URL shortening service as a substitute for TinyURL in Twitter posts. Metamark does have a pretty simple API, and the API page shows how to call it from Perl, Python, and TCL. Like TinyURL, there’s also a bookmarklet that you can drag into your toolbar to shorten the URL of the current page.

What I don’t like about these bookmarklets is that they take me away from the page I’m looking at to the shortening service page. I then have to select and copy the shortened URL and return to the original page. It’s not exactly hard labor, but it’s more steps than I want to perform. More important than the number of steps, it forces me to switch context. When I want a shortened URL, I’m usually writing in some window (TextMate, Twitterrific, whatever) with the web page of interest open in Safari in the background and off to the side for reference. I’d like to be able to simply paste the shortened URL of that page without switching out of whatever window I’m writing in.

Now I can. The short Python program below runs an AppleScript that gets the (long) URL from the frontmost Safari window/tab, passes that through the Metamark shortening service, and puts the shortened URL onto the clipboard.

 1:  #!/usr/bin/env python
 2:  
 3:  usage = '''
 4:  Takes the URL of the frontmost Safari window/tab and
 5:  shortens using the service at metamark.net. The shortened
 6:  URL, which starts with "http://xrl.us/", is put on the
 7:  clipboard, ready for pasting.
 8:  '''
 9:  
10:  from urllib import urlopen, urlencode
11:  from os import popen
12:  
13:  # Two functions for getting and setting the beep sound.
14:  def getBeep():
15:    # Next line is for Tiger. Comment it out if you're running Leopard.
16:    # cmd = "defaults -currentHost read -g com.apple.sound.beep.sound"
17:    
18:    # Next line is for Leopard. Comment it out if you're running Tiger.
19:    cmd = "defaults read ~/Library/Preferences/com.apple.systemsound com.apple.sound.beep.sound"
20:    
21:    return popen(cmd).read()
22:  
23:  def setBeep(beepFile):
24:    # Next line is for Tiger. Comment it out if you're running Leopard.
25:    # cmd = "defaults -currentHost write -g com.apple.sound.beep.sound "
26:    
27:    # Next line is for Leopard. Comment it out if you're running Tiger.
28:    cmd = "defaults write ~/Library/Preferences/com.apple.systemsound com.apple.sound.beep.sound "
29:    
30:    return popen(cmd + beepFile).read()
31:  
32:  # Get the URl of the frontmost Safari window/tab though AppleScript.
33:  applescript = '''tell application "Safari"
34:    URL of front document
35:  end tell'''
36:  
37:  url = popen("osascript -e '" + applescript + "'").read().strip()
38:  
39:  # Get the shortened URL from Metamark.
40:  shortURL = urlopen("http://metamark.net/api/rest/simple", 
41:               urlencode({'long_url':url})).read()
42:  
43:  # Put the shortened URL on the clipboard.
44:  popen('pbcopy', 'w').write(shortURL)
45:  
46:  # Sound the Ping alert to tell the user the shortened URL is ready.
47:  # Reset the alert sound after beeping.
48:  oldBeep = getBeep()
49:  print oldBeep
50:  setBeep("/System/Library/Sounds/Ping.aiff")
51:  popen("osascript -e 'beep 1'").read()
52:  setBeep(oldBeep)

I suppose I could have used appscript to get the URL from Safari instead of forking out to AppleScript, but doing it this way means there’s no dependency on the nonstandard appscript module. The strip() at the end of line 37 is needed because the AppleScript call puts a linefeed at the end of the original URL.

Lines 40 and 41 are pretty close to a cut-and-paste from the Metamark API page. Line 44 is another fork, this time to the very handy pbcopy command that Apple provides for command line control of the clipboard.

The file is saved with the name surl.command in my home folder. I can run it by activating Quicksilver, typing “surl” and hitting the Return key. No need to switch out of the window/program I’m typing in; just do those steps and the shortened URL is in my clipboard, ready to paste. If I wanted to cut out a few keystrokes, I could turn it into a Quicksilver trigger. In fact, let me do that… OK, now this sequence

is bound to Control-Option-Command-U. The final, empty text field tells Quicksilver to run surl.command with no options or arguments (because it doesn’t take any).

Update
This script can be modified to work with other URL shortening services. For example, you can get it to use TinyURL by changing lines 40 and 41 to the single line

shortURL = urlopen('http://tinyurl.com/api-create.php?url=' + url).read()

I first thought I should run the original url through urllib’s quote or quote_plus filters, but that just seemed to just screw up TinyURL—it seems to be a little more touchy than the Metamark API.

You may not be able to modify the script to work with Firefox. I’ve looked into the AppleScript dictionary for Firefox (version 2.0.0.4), and I couldn’t find a way to get the URL of the frontmost window/tab.

Later update
John Gruber tells me he wrote a similar script in Perl. His system requires a bit more interaction with Quicksilver, which adds some steps but gives him more flexibility. I’m willing to forego the flexibility to keep the number of keystrokes down, but an important aspect of his system that mine was missing was feedback to the user telling him when the shortened URL is ready. So I’ve added some lines to the script that play the system beep sound when it’s done.

My beep sound is set to Funk, and I wanted the script to play a different sound—I chose Ping. AppleScript’s beep command doesn’t let you choose the sound, so I had to:

  1. Save the current beep sound.
  2. Set the beep to Ping.
  3. Beep.
  4. Sat the beep back to the original sound.

That’s what lines 48-52 do. To make these lines a bit easier to read, and avoid some repetition, I also added the getBeep and setBeep functions in lines 14-30. This discussion on Apple’s AppleScript mailing list (especially Randal Schwartz’s contributions to the thread) showed me how to write those functions for Tiger (see below for Leopard update).

Even later update
Bob Rudis has some changes to the script to make it work with Firefox and to give feedback through Growl rather than beeping.

And later still The beeping part was added while working on an iMac that’s still running Tiger. Tonight, while working on a Leopardized iBook, I found that the feedback sound wasn’t working. I had to change the beep to 'beep 1' in line 51 (which should work in Tiger, too, but I won’t be able to check it for a few days) and I also had to change the getBeep and setBeep functions. Apple has apparently changed where the system beep sound preference is kept. In Leopard, it’s in the new plist file ~/Library/Preferences/com.apple.systemsound.plist. So now the getBeep and setBeep functions each have a line that works for Tiger and a line that works for Leopard, and you’ll have to comment out the one that doesn’t apply. As shown above, the script will work for Leopard. For Tiger, uncomment lines 16 and 25 and comment out lines 19 and 28.

If I need to update the script further—and that wouldn’t be too surprising—I’ll do it in another post, and I’ll put a link to that post at the top of this one.

Tags: