Automatically Send Articles From Reading List to Instapaper

Two days ago, Ben Brooks asked on App.net if anyone had come up with a way to share Safari Reading List items to Instapaper. His question made me realize that it would be a fun project to find out, so in my free time I put together a workflow that runs automatically and in the background on my Mac mini.

Please note, what follows is a raw experiment. I have tested it, and it works, but it’s far from stable. It uses GUI scripting in AppleScript to mark Reading List items as read, and it heavily depends on iCloud, which, unfortunately, is far from reliable when it comes to bookmark syncing. Nothing should happen to your bookmarks (the script simply “reads” them), but backups are recommended, as usual.

What you need

A Mac running Lion or Mountain Lion

An iCloud account with Reading List turned “on”

An Instapaper account

Hazel

ReadingListReader

InstapaperLibrary

In short, the workflow relies on ReadingListReader to parse your unread Reading List items and save them to Instapaper using readinglist2instapaper. To trigger this every time a new item is added to Reading List, you’ll be using Hazel to monitor the Bookmarks.plist file located in ~/Library/Safari/ on your Mac. You’ll then run an AppleScript (also with Hazel) to mark an item as read, so that it won’t be saved multiple times or fetched by the script.

ReadingListReader & InstapaperLibrary

ReadingListReader contains a Python module called readinglistlib that provides a simple way to read Reading List items off your Bookmarks.plist file. With iCloud, Apple decided to store these “read later” articles in the same Bookmarks file that contains your actual bookmarks; for the module, through a simple syntax, it’s possible to “look” at this file and only fetch the URLs that belong to Reading List.

Download ReadingListReader and InstapaperLibrary, which is a Python library to use the Instapaper API. Extract the ReadingListReader archive, and place readinglist2instapaper.py, readinglistreader.py, and readinglistlib in a folder. Download InstapaperLibrary and put instapaperlib in the same folder. I’m choosing the Home folder for convenience, but you could put readinglistreader.py in /usr/local/bin if you want. Just remember to make the file readinglistreader.py executable first.

What you’re doing here is setting up these scripts for the first time: you won’t have to do it again once they’re working. You just need a bit of patience, and, perhaps, a good cup of coffee. I also recommend reading the official ReadingListReader FAQ and installation guide, as it’s very easy to follow.

Next, you’ll have to tell these files how to connect to your Instapaper account. In readinglistreader.py, look for this portion of the file:

The blurred spaces is where you’ll have to put in your Instapaper account and password. Do the same for instapaperlib.py inside the instapaperlib folder.

If you’ve setup everything correctly, you should be able to run python /Users/yourusername/path/to/readinglist2instapaper.py without receiving any errors. It means the file can access your Reading List and Instapaper account. If you have some items in Reading List, they’ll be sent to Instapaper with their title and URL.

Automating the workflow

Next, we want to automate everything so that, no matter the device we’re using, a Mac will run this script and beam items to Instapaper. To accomplish this, obviously, we’ll need iCloud sync on both Mac and iOS, and a computer to do the processing for us. Eventually, you’ll end up with an automated workflow that looks for changes in the Bookmarks.plist file and sends Reading List URLs to Instapaper.

You’ll be able to, say, quickly save a Reading List item from your iPhone and find it after a few seconds on the Instapaper website or app. As long as the iCloud Reading List changes, so will Instapaper.

As usual, I decided to use Hazel to monitor changes to the Bookmarks.plist file. Every time a change is picked up using the good & old “Date Last Matched is before Date Last Modified” filter, the Python script will be executed to take Reading List items and send them off to Instapaper. Reading List items won’t be deleted – the script will just take “unread” ones; that’s why I added an AppleScript to mark them as read afterwards.

In the meantime, to see if the workflow is set up correctly, just create the Hazel rule without the AppleScript. Drop in the ~/Library/Safari/ folder, create the filter, the shell script action, and save the rule. Add a link to Reading List from your iPhone and see what happens. Does Instapaper have that link after a minute? Then the script is working.

GUI scripting

I tend to avoid GUI scripting, as it’s based on visible interface elements rather than actual commands from the AppleScript dictionary. But in Reading List’s case, I don’t think I have a choice. In Lion and Mountain Lion, Safari’s AppleScript dictionary doesn’t have a command to retrieve Reading List items: you can only create new ones using add reading list item.

As I mentioned above, the script defaults to processing unread Reading List items. To avoid an additional processing of the same item, I had to figure out a way to mark it as read. I came up with a quick and dirty hack that, through GUI scripting, shows the Reading List sidebar in Safari, selects the next item (so it is marked as read), closes the sidebar, and closes the blank tab I’m using for this process.

Like I said, it’s a very quick AppleScript put together to see if the selection trick could work. I didn’t even add a check to see if GUI scripting can be used or not (always add one). I am no expert of GUI scripting at all: if you know of a better way to accomplish this – perhaps figure out how to hit “Clear All” – please get in touch.

tell application "Safari"

tell window 1

set current tab to (make new tab)

activate

tell application "System Events"

tell process "Safari"

keystroke "L" using {command down, shift down}

keystroke down using {command down, option down}

click menu item "Select Next Item in Reading List" of menu "Bookmarks" of menu bar 1

delay 3

keystroke "L" using {command down, shift down}

delay 2

click menu item "Close Tab" of menu "File" of menu bar 1

end tell

end tell

end tell

end tell

The current script simply selects the first item – meaning, if you’re adding multiple links at once, those after the first link won’t be selected. If you add a link every few minutes, the script should take care of marking it as read, preventing it from ending up on Instapaper again.

Problems and Reasons

Clearly, this isn’t the best way to process Reading List items and send them to Instapaper, because Apple doesn’t want you to mess with scripting Safari’s bookmarks. The entire workflow may easily fail due to iCloud’s sync (which is surprisingly unstable) and the AppleScript, which may not mark your items as read. In my tests, adding one link at a time, everything went fairly smooth; certainly, the culprit isn’t ReadingListReader, which works as advertised. I wish Reading List had better support for AppleScript.

The interesting fact about this workflow is that, at first, I thought it would be useless: why would I want to do this? As TJ Luoma told me on App.net, though, Reading List is integrated on a system-wide level, while Instapaper isn’t. Sure, most third-party apps offer a “Save to Instapaper” feature nowadays, but it’s still very convenient to be able to add links to Reading List from Mail or Calendar.

In some third-party apps like Downcast and Day One, Reading List is integrated with a single tap & hold, whereas Instapaper is available in a sub-menu of a web view. So, in the end, if you’re into this sort of thing, you’ll also save some taps.

Once again, if you know of a better to achieve the same automated worklfow please let me know on Twitter or App.net (where the original discussion took place).