Rescuing Shared Links from Safari 11

Safari for the Mac got the ability to read RSS and Atom feeds way back in 2005 with the release of OS X 10.4 Tiger. It never got much attention from heavy users of feeds because it wasn’t as powerful as NetNewsWire, Google Reader, or any of the readers that cropped up in the wake of Google Reader’s demise. But if you were a casual user of RSS, Safari’s system was probably just fine. At some point, Apple decided to call this feature Shared Links and combine it with recent Twitter and LinkedIn updates under the @ tab in the Safari sidebar.

Sidebar in Safari 10

It did this until Safari 11, which dropped Shared Links and the @ tab.

Sidebar in Safari 11

Safari 11 is the version that will ship with High Sierra, but it appeared in the Mac App Store a few days ago, and you may have already installed it, because it runs on Sierra and El Capitan, too.

So if you were using Shared Links and updated to Safari 11, you’ll have to find a new feed reader. That’s easy; just search on “rss” in the Mac App Store, and you’ll find a bunch. What’s not so easy is exporting your list of subscriptions out of Safari and importing them into your new reader app.

The standard way to pass lists of feed subscriptions around is through an OPML file. You may have run into these if you’ve ever switched your podcast player and needed to export/import subscriptions. Podcasts are basically RSS feeds with links to audio files, so it’s natural to use the same format for sharing both podcast and blog/news subscription lists. Safari has no provision for exporting your Shared Links as an OPML file, but it does keep your subscription list in a similarly formatted plain text file.

Inside your Library folder is a Safari folder that contains a bunch of Safari’s preference and configuration files. One of those is called WebFeedSources.plist, an XML file with all of your Shared Links in Apple’s property list format. Despite Safari 11 having no need for it, Apple doesn’t delete it during the upgrade, so whether you’ve upgraded already or not, you can convert your plist of subscriptions into an OPML. If you have a converter.

How about this one? Download it, unzip it, and launch it. You’ll be presented with this window explaining what the converter does and an opportunity to bail out.

Shared Links converter

If you go ahead with the conversion, you’ll have a new file on your Desktop called converted-subscriptions.opml. You can use that to import your subscriptions into whatever feed reader you decide to use or into one of the online services like FeedBin and Feedly.

The provenance of this converter—an Automator app, as you can tell from its icon—is a little weird. Scholle McFarland, erstwhile editor at Macworld, is writing a Take Control book on High Sierra, and she noticed there was no official way to extract the Shared Links list. She asked Jason Snell, erstwhile editor at Macworld,1 if he knew of a way to do it. Jason, in turn, asked me if it was something I was interested in. I had a pile of undone work sitting on my desk, so I jumped at the chance to avoid it.

I wrote a simple script, which we’ll get to in a minute, and Jason turned it into the Automator app linked above. It hasn’t gotten extensive testing, but it knows how to handle Unicode characters and special XML characters like ampersands. Because the WebFeedSources.plist file is in a known location, it doesn’t have to ask the user where to find it.

Here’s the script:

 1:  #!/usr/bin/python
 3:  from plistlib import readPlist
 4:  from os import environ
 5:  from cgi import escape
 7:  # OPML template with header and footer.
 8:  opml = '''<?xml version="1.0"?>
 9:  <opml version="1.1">
10:    <head>
11:      <title>Feeds Imported from Safari</title>
12:    </head>
13:    <body>
14:  {}
15:    </body>
16:  </opml>
17:  '''
19:  # Outline template for each item.
20:  outline = '    <outline text="{}" htmlUrl="{}" xmlUrl="{}" type="rss" />'
22:  # read and parse the WebFeedSources.plist file from ~/Library/Safari.
23:  plistfile = environ['HOME'] + '/Library/Safari/WebFeedSources.plist'
24:  wfsources = readPlist(plistfile)
26:  # Create the body of the OPML file from WebFeedSources as a list.
27:  # Because WebFeedSources may contain Unicode and XML special characters,
28:  # encode the data and turn the special characters into entities.
29:  body = []
30:  for feed in wfsources:
31:    try:
32:      title = feed['Title'].encode('utf8')
33:    except KeyError:
34:      title = 'Untitled'
35:    try:
36:      htmlURL = feed['SourceURL'].encode('utf8')
37:    except KeyError:
38:      htmlURL = ''
39:    try:
40:      xmlURL = feed['FeedURL'].encode('utf8')
41:    except KeyError:
42:      xmlURL = ''
43:    fields = [ escape(f, True) for f in (title, htmlURL, xmlURL) ]
44:    body.append(outline.format(*fields))
46:  # Output the entire OPML.  
47:  print opml.format('\n'.join(body))

The comments and the code pretty much speak for themselves. Basically, the script parses your WebFeedSources.plist file in Line 24, which creates a list called wfsources. It then loops through that list, starting on Line 30, extracting the Title, SourceURL, and FeedURL fields and creating an OPML <outline> tag using the template defined on Line 20. Finally, on Line 47, it puts all of the <outline>s in the middle of the overall OPML template (defined in Lines 8–17) and prints the result.

Python is good language for this, because it’s preinstalled on every Mac and has a standard library for parsing plist files. It also has libraries for XML, but I chose not to use them because I find them opaque and because the OPML format is easy to write via templates. This is frowned upon by many who say you should always use the purpose-built (and well debugged) libraries to avoid edge cases and silly mistakes. If people use this and errors arise, I will have learned my lesson.

I should mention that you may find the names of some of your feeds ridiculous. The names come from the Title field in the plist file, and I don’t know how Safari chose them. In my Shared Links, there are feeds with absolutely useless names like “ATOM” and “RSS.” The URLs to the feeds, which are the important part, are fine, but don’t be surprised to find yourself tweaking things after importing into your feed reader.

Thanks to Scholle and Jason for giving me an interesting way to avoid work, and to Jason for turning my command-line script into something useable by people who avoid the command line.

Update Sep 22, 2017 11:04 PM  Well, that didn’t take long. Scholle found someone with a set of Shared Links that broke the script before I even posted—I just didn’t learn about it until half an hour after I posted.

Apparently, Safari doesn’t/didn’t always include Title or SourceURL fields. The original version of the script assumed they did. Which was not good defensive coding. Or good coding at all, frankly. The script you see here now, and the linked Automator app based on it, is more robust and will handle feeds with missing Title, SourceURL, and FeedURL fields. (I don’t know how a feed could exist without FeedURL, but the code is there to handle it.)

All the new stuff is in the try/except blocks in Lines 31–42.

  1. There are lots of erstwhile Macworld editors. I hear tell there are feral packs of erstwhile Macworld editors roaming the foothills of the Santa Cruz Mountains, viciously autoplaying videos at unwary hikers.