Friday, May 16, 2014

I use Safari's Reading List to transfer pages from my phone to my computer. But I don't really want to read these in Safari on my desktop. I'd rather read them in Firefox, maybe through Pinboard, which has an API to write items and mark them as "to read". This blog post documents what I learned.

Update: [2014-06-07] Deleting items out of the reading list does not work properly. It's ok to read everything out of there but I haven't figured out how to properly modify it. So I guess I need to delete things manually after I read them out.

Update: [2014-09-18] With iOS 8, I'm switching from Reading List to something else, maybe Pinner, to make this process easier.

I searched the web for some resources, and learned that the reading list, as well as other Safari bookmarks, are stored in ~/Library/Safari/Bookmarks.plist. I can use plutil and PlistBuddy to read and edit that file.

Here's the simplest way for me to get the data in json format (unfortunately plutil can't directly convert it to json because it won't convert dates to strings):

plutil -convert xml1 ~/Library/Safari/Bookmarks.plist -o - \
  | sed -e 's/date>/string>/g' -e 's/data>/string>/g' \
  | plutil -convert json -r - -o - >bookmarks.json

Ideally I'd also be able to delete the reading list from Safari when I transfer it elsewhere. To do that, I need to figure out which element(s) to delete.

Looking at the structure of the bookmarks file, Children is an array of bookmarks, and one of those is the Reading List folder. How do I find the index into the array? If it's a bookmark it will contain URIDictionary; if it's a folder it will contain Title. The Title of the folder will be com.apple.ReadingList. I needed to figure out how to specify an array element. With PlistBuddy, the man page tells you what the commands are but not how to access elements. I eventually figured out that I can use Children:3 to access element 3 of the Children array. Here's a loop to go through the titles (assuming at most 10 bookmarks; you may need to increase this):

bookmarks_file="$HOME/Library/Safari/Bookmarks.plist"
i=0
while [ $i -lt 10 ]; do
    title=$(/usr/libexec/PlistBuddy -c "print Children:$i:Title" $bookmarks_file 2>/dev/null)
    echo $i $title
    i=$[$i+1]
done

The next step was to delete the folder that contains the Reading List. For some reason I can't remember, I used plutil -remove instead of PlistBuddy -c delete to delete the entry. But plutil wants it written Children.3 instead of Children:3. Why? I don't know. The same loop as above, except when I find com.apple.ReadingList I delete it:

bookmarks_file="$HOME/Library/Safari/Bookmarks.plist"

i=0
while [ $i -lt 10 ]; do 
    title=$(/usr/libexec/PlistBuddy -c "print Children:$i:Title" $bookmarks_file 2>/dev/null)
    if [ "$title" = "com.apple.ReadingList" ]; then
        plutil -remove "Children.$i" $bookmarks_file
        break
    fi
    i=$[$i+1]
done

So now I can take the Reading List out of Safari and into JSON format. What to do from there? Pinboard is an obvious choice. I instead write the items out in a text file which I check periodically from Emacs.

Labels: ,

4 comments:

shokk wrote at Tuesday, August 19, 2014 at 6:09:00 PM PDT

How well does an "add" to the list work?

Amit wrote at Wednesday, August 20, 2014 at 9:31:00 AM PDT

Hi shokk, I haven't tried that. I've put this whole project on hold because it's not been as easy as I had hoped. It's too bad. There could be a nice ecosystem of tools if the reading list were programmable.

Anonymous wrote at Friday, August 29, 2014 at 6:45:00 AM PDT

Adding works well, and has been done. If we can figure out how to delete, we’ll have all the pieces.

shokk wrote at Friday, August 29, 2014 at 6:53:00 AM PDT

The folks on the MACENTERPRISE@LISTS.PSU.EDU list might know more.