I wanted a handy way to convert a feed url to the redirected url. For example, Reeder puts this url on the clipboard:
“http://feeds.harvardbusiness.org/~r/harvardbusiness/~3/_folqII9-_8/beware-of-the-short-term-opera.html”
But when I make a post, I want this url:
“http://blogs.hbr.org/hbsfaculty/2011/12/beware-of-the-short-term-opera.html”
There are a number of reasonable ways to do this, but I like Python so that’s what I went with.
It’s a Hack!
I’m an inquisitive hack. I know just enough of these technologies to be dangerous. I accept that risk. Anyone else running these scripts should accept the same risks. I’ve omitted all of the standard try blocks from the scripts for brevity. They really should have more error handling. I’ve hung many apps with poorly crafted scripts.
I like to build my own solutions when I can. It provides an opportunity to learn something new, but it also forces me examine my workflows and motivations. For example, I’ve abandoned several “tools” in favor of consolidating into more simple options. The script in this post will make it’s way into Keyboard Maestro.
Getting The URL
Get the redirect URL is fairly trivial in Python
feedUrl = 'http://feeds.harvardbusiness.org/~r/harvardbusiness/~3/_folqII9-_8/beware-of-the-short-term-opera.html'
f = urllib2.urlopen(feedUrl)
source = f.read()
f.close()
newurl = f.geturl()
newurl = newurl.split('?')[0]
print newurl
The “f.geturl()” function grabs the redirected url from the feed. But that url also has some garbage at the end indicating that it also came from a feed:
“http://blogs.hbr.org/hbsfaculty/2011/12/beware-of-the-short-term-opera.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+harvardbusiness+%28HBR.org%29”
The second to last line of the Python script splits the url at the “?” and grabs the first bit of the string.
All well and good. This does extract the base url I wanted. But now I need a better way to set the feed url. I don’t want to open the script every time and edit the feedUrl variable. I could do this Keyboard Maestro but I wanted a more general solution. That means Python needs access to the OS X clipboard.
Clipboard and Python
There are a number of ways I could do this. I could first grab the clipboard in the shell environment then pass in the result to the python script when it is run. That’s fine, but I wanted something a little more direct. I wanted Python to get the clipboard from within the script.
Subprocess
I found this very thorough and very interesting tutorial for making a cross OS clipboard sharing script. In it, there are two nice methods that I’ve lifted.
def getClipboardData():
p = subprocess.Popen(['pbpaste'], stdout=subprocess.PIPE)
retcode = p.wait()
data = p.stdout.read()
return data
def setClipboardData(data):
p = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
p.stdin.write(data)
p.stdin.close()
retcode = p.wait()
These are pretty clever tricks (to me) and something I never thought of. Subprocess allows Python to spawn a shell command for pbpaste and pipe the results to a variable. How cool is that. One method gets the clipboard contents and the other puts content back on the clipboard.
Putting it all together and I get this script for converting the current clipboard url to a normal address.
import subprocess
def getClipboardData():
p = subprocess.Popen(['pbpaste'], stdout=subprocess.PIPE)
retcode = p.wait()
data = p.stdout.read()
return data
def setClipboardData(data):
p = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
p.stdin.write(data)
p.stdin.close()
retcode = p.wait()
f = urllib2.urlopen(getClipboardData())
source = f.read()
f.close()
newurl = f.geturl()
newurl = newurl.split('?')[0]
setClipboardData(newurl)
PyObjC
This process seemed a bit indirect. It means I run a shell command to run a python script that then spawns a shell command to get the clipboard. There’s nothing wrong with that as far as I can see, but I wanted to learn more about Python on the Mac. So, I kept reading.
PyObjC is a Python library for accessing the Objective C frameworks in Python. It should provide a native mechanism for accessing the OS X clipboard and provide additional features not available through the subprocess methods above.
After some reading and fiddling[1], I ended up with a version of the above script that uses the NSPasteboard class to get and set the clipboard instead of a using the shell command.
from Foundation import *
from AppKit import
# Sets the clipboard to a string
def pbcopy(s):
pb = NSPasteboard.generalPasteboard()
pb.declareTypes_owner_([NSStringPboardType], None)
newStr = NSString.stringWithString_(s)
newData = newStr.nsstring().dataUsingEncoding_(NSUTF8StringEncoding)
pb.setData_forType_(newData, NSStringPboardType)
# Gets the clipboard contents
def pbpaste():
pb = NSPasteboard.generalPasteboard()
content = pb.stringForType_(NSStringPboardType)
return content
f = urllib2.urlopen(pbpaste())
source = f.read()
f.close()
newurl = f.geturl()
newurl = newurl.split('?')[0]
pbcopy(newurl)
Conclusion
I found this whole process interesting and educational. At the very least I have a better appreciation for the clipboard and how complex a clipboard item can be. But it also gave me a way to make more generalized macros. Applications like TextExpander and Keyboard Maestro can access the clipboard through token replacements, but if I’m embedding a script, I might as well handle the clipboard myself. That means that the same script can be used in multiple application, including Launchbar and the terminal.

Hi, I followed the link here from Brett Terpstra. That was my pastebin, actually. Glad to hear that it was helpful! It’s a bit out of date now though. I got the code for using AppKit from elsewhere and never really understood it or got it to work with Python 3, so I switched to using subprocess, more or less the same as you have above.
Here’s what’s current: http://pastebin.com/gmwa603R
As for my convenience functions, they’ve grown knottier and weirder, so I’m a bit embarrassed to show them in public. Suffice it to say, I have it set to where typing pb.whatever (to get stuff out of the clipboard and into Python) or cb.whatever (to copy the results out of Python and into the clipboard) in an interactive Python shell will do a lot of juggling for me from titlecasing to reencoding.
Sounds very cool. Thanks for making it available. I’d love to see the crazier stuff if you ever get up the nerve. I know my stuff is ugly but what the hell, very little I come across is much worse that what I Duct-tape together.
Why did you drop the PyObjC clipboard handling in favor of subprocess? It seems like AppKit is more direct.
Like I said, I can’t figure out how to get PyObjC/AppKit to work with Python 3. I switched over to using Python 3 for my current projects a couple months back.
As far as the convenience module goes, I guess I should just go ahead and start a github site at some point, maybe over Christmas break.
Sorry, I read that but didn’t absorb it. Send me the link when you have a GitHub repo for it. I look forward to checking it out.
This reminded me that I had done something similar (cleaning up URLs) in a shell script that I use with Keyboard Maestro.
I wrote it up at http://luo.ma/locurl.