From Instapaper to Reading List to Pinboard

Betaworks has been doing an outstanding job with Instapaper lately, yet as days went by, I felt that how I've read articles online departed from what the service offered me. The web has changed and you find interactive articles all the time. Content that relies on the design around it, something no read later service can comply. How the web changed along with my needs as a web designer led me to a search for a new way to store the articles I'm yet to read.

The Reading List

I left Instapaper in the lookout for a place to store content I want to read at a given time, served on its actual design. Reading List, the service that comes with Safari felt like a strong candidate to take the place, until I realized I couldn't bear with its inconsistencies.

Even Apple apps support to send links to the Reading List, therefore, in terms of integration across the system, it is the best all-around. But when it comes to use the Reading List as your read later service, the merge with the Safari interface confuses me.

On the Mac you can open the Reading List with ⌘ + ⌃ + 2, a shortcut I never memorized and often relied on the sidebar at the Favorites tab. Then I realized how I enjoyed a dedicated environment to decide what I'd read next; think about it for a minute: if you use a read later service, you're probably short on time; when you get some minutes to spare and clean up some articles from your queue, picking the lucky ones is a sacred moment.

Safari wasn't built to take decisions like these, when you open the Reading List, it lies confined in a panel on the left, sharing space with either your Favorites, Top Sites or another web page. On the iPad it is stranger, in landscape, the panel pushes the current content, while, in portrait it covers the screen with a dark backdrop.

You can add an Add to Reading List button in the Mac's toolbar, but my favorite method to add links is through a hidden plus sign in the left of the address bar, it may have flashed unnoticed to you. Then there's the cue: the link flies to the left of the screen, where the Reading List panel is supposed to spawn. There's no such a thing on iOS, the share sheet disappears and you have no confirmation if you succeeded or failed to add the link. You can contest that with the Copy action, which reacts likewise, but I wonder why Apple felt the need for a cue on the Mac and not on iOS.

I concluded the Reading List wasn't for me and the next option was a service I've always used not yet to its full potential: Pinboard.


Moving to Pinboard meant abdicating offline access to my unread bookmarks, however, I was reminded by [Seth]( "Seth Clifford no Twitter: "Check out Pinner." .") and [Pedro]( "Pedro Lobo no Twitter: "Pgruneich I think pinner_app does that, and I’ve asked dwlz to add it to Pushpin_app too" .") that Pinner caches content. Pushpin is adding offline caching in an upcoming update. I felt safe in that matter and, thinking about it, if I'm offline I could use that time to read some of the books I've been neglecting for so long.

Pinboard clients for iOS were the first to massively adopt extensions, therefore, adding new items is as easy as it gets. On the Mac, I get most of the work done with the official bookmarklet, but I don't refuse the idea to create something dedicated for unread items one of these days. I browse through my unread items using Launchbar and an action I designed specifically for this, which we'll visit now.

Back in 2012, Brett Terpstra created an action to download all of your bookmarks from Pinboard and store them in a HTML file you could search through Launchbar. I created my action based on that with a couple of quirks of its own.


import requests
import json

# User Configuration
PB_USERNAME = "your Pinboard username"
PB_PASSWORD = "your Pinboard password"
PB_PATH = "/absolute/path/to/file/PinboardUnread.json"
# End Configuration

PB_URL = ""

r = requests.get(PB_URL, auth=((PB_USERNAME, PB_PASSWORD)))
data = json.loads(r.text)
toread = [{"title": i["description"], "subtitle": i["extended"], "url": i["href"], "actionArgument": {"shared": i["shared"], "tags": i["tags"]}, "action": "", "actionRunsInBackground": bool("true")} for i in data if i["toread"] == "yes"]

with open(PB_PATH, "w+") as the_file:
    print(json.dumps(toread), file=the_file)

You must change the PB_USERNAME, PB_PASSWORD and PB_PATH variables. The latter requires an absolute path to the location where you want to store the JSON output file. Notice that I run this script with Python 3 installed with Homebrew, if you have Python 2 on your Mac, you may have to tweak this script altogether, else if you run Python 3 from a different path, change the first line. You can find out the path for Python 3 in the Terminal using which python3. You find this script on Github here.

There's a service on your Mac called launchd, you can assign scripts and run them on the background as soon as you boot the machine or within time intervals. I trigger a Python script every 15 minutes to fetch my unread bookmarks from Pinboard and store them in a JSON file. I use Lingon X to create the plist file required to make this work. If you have one of these scripts you must run all the time to update data, I suggest you set it up on launchd to never worry about it again.

Lingon X eases the creation of launchd services.

I chose to retrieve the bookmarks separately to avoid the JSON request on Launchbar, which would hang until the script is processed. Splitting these scripts makes the action instantaneous. Everything could be improved with a more granular approach, requesting solely updated items and only if there's any instead of every 15 minutes. Something for a future version.

Moving to the Launchbar action, there's stuff for you to tweak there as well if you plan to use this. Right click on the action, Show Package Contents, then Contents and Scripts, where you'll find two Python scripts. The has the PB_PATH variable, where you should put the path to the JSON file, just paste the same from the earlier script.


import json

PB_PATH = "/absolute/path/to/file/PinboardUnread.json"

PB_FILE = open(PB_PATH, 'r+')
PB_JSON = json.loads(PB_READ)


The other script, requires username and password, so you should set it up as well. This script marks the article you open through the action as read on Pinboard:


import requests
import json
import sys
import webbrowser

# User Configuration
PB_USERNAME = "your Pinboard username"
PB_PASSWORD = "your Pinboard password"
# End Configuration

PB_INPUT = sys.argv[1]

PB_JSON = json.loads(PB_INPUT)['url'])

PB_URL = "{0}&description={1}&extended={2}&tags={3}&shared={4}replace=yes&toread=no".format(PB_JSON['url'], PB_JSON['title'], PB_JSON['subtitle'], PB_JSON['actionArgument']['tags'], PB_JSON['actionArgument']['shared'])


You can download the Launchbar action here.

There's no Pinboard request for only the unread bookmarks, therefore, you have to request all of them and filter the results. Unless your Mac is on 24/7 or you have a MacMini working in the sidelines, this may be an issue while porting this action to iOS.

You're good to go if you use Pythonista, however, Workflow seems to crash while processing large JSON files. You could use Pythonista to update the JSON file and store it in Dropbox; and Workflow would probably be able to handle it1. You could also split your server request into multiple calls, yet you'd take a performance hit I consider unworthy when you have such great apps on iOS to read Pinboard bookmarks like Pinner, Pinswift and Pushpin.


Although I won't miss the clean markup Instapaper provides, it still has great features I'm yet to reproduce, specially the reading time shown as tiny circles on every entry. When it comes to the sacred time where you select what to read, sorting articles by reading time is a welcome option. Instapaper is a remarkable service and we may be together again in the future, but at the moment I believe this change has come for good.

If you still don't have a Pinboard account, I suggest you create one right away since, beginning January 1st, 2015, the pricing model is about to change from a one-time fee to a yearly subscription model. If that's not a good enough reason, Pinboard will donate all its revenue for the remainder of the year to the World Food Program.

  1. Unless your queue is ridiculously long.