cript to Overwrite a Chrome profile’s bookmarks with your Pinboard bookmarks.
The idea is to overwrite the bookmarks of an unused Chrome profile with your Pinboard bookmarks to get rudimentary integration with apps (such as Alfred) that understand Chrome’s bookmarks, but not Pinboard’s.
Don’t use it with an active profile: the generated file isn’t a valid Chrome bookmarks file, and overwriting apps’ private data is a bad idea in any case.
Usage
The script requires two settings, the name of the Chrome profile to use and your Pinboard API key.
You can either set the PROFILE_NAME
and PB_API_TOKEN
variables in the script itself, or pass them via the PROFILE
and PINBOARD_API_TOKEN
environment variables respectively.
Here’s where you can find your Pinboard API key.
Create a new Chrome profile for use with this script and set the PROFILE
environment variable to its name.
To view your available Chrome profiles, run the script with the -l
option, which will list the names of your profiles and the directories they’re stored in. Take care not to overwrite an important profile.
Run the script every hour or so using cron
or a launch agent. You can create a launch agent online here or use the awesome LaunchControl app.
Script
pb2chrome.py
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright (c) 2019 Dean Jackson <deanishe@deanishe.net>
# MIT Licence. See http://opensource.org/licenses/MIT
# Created on 2019-12-05
"""pb2chrome.py [-h|-l]
Overwrite a Google Chrome profile's bookmarks with your Pinboard bookmarks.
You must set the following environment variables for the script to work:
PROFILE_NAME
The name of the profile whose bookmarks should be overwritten.
You should create a new profile for use exclusively by this script.
PINBOARD_API_TOKEN
Your Pinboard API key. Find it here:
https://pinboard.in/settings/password
Options:
-l list your Chrome profiles
-h show this message and exit
"""
from __future__ import print_function, absolute_import
from collections import namedtuple
from getopt import getopt
import json
import os
import sys
try:
from urllib2 import urlopen, URLError
except ImportError:
from urllib.request import urlopen, URLError
# Name of Chrome profile to write Pinboard bookmarks to
PROFILE = 'Pinboard Sync'
# Pinboard API token from https://pinboard.in/settings/password
# You can set the API token here in the script or via the
# PINBOARD_API_TOKEN environment variable.
PB_API_TOKEN = ''
PROFILE = os.getenv('PROFILE_NAME') or PROFILE
PB_API_TOKEN = os.getenv('PINBOARD_API_TOKEN') or PB_API_TOKEN
# URL to fetch all bookmarks from Pinboard
PB_URL = 'https://api.pinboard.in/v1/posts/all?auth_token={token}&format=json'
Profile = namedtuple('Profile', 'name dir')
def log(s, *args, **kwargs):
"""Log to STDERR."""
if args:
s = s % args
elif kwargs:
s = s % kwargs
print(s, file=sys.stderr)
def get_chrome_profiles():
"""Get names & paths of Chrome profiles."""
profiles = []
dirpath = os.path.expanduser('~/Library/Application Support/Google/Chrome/')
with open(os.path.join(dirpath, 'Local State')) as fp:
state = json.load(fp)
for dirname, data in state['profile']['info_cache'].items():
name = data['name']
p = Profile(data['name'], os.path.join(dirpath, dirname))
profiles.append(p)
profiles.sort(key=lambda p: p.dir)
return profiles
def get_pinboard_bookmarks():
"""Retrieve bookmarks from Pinboard API."""
r = urlopen(PB_URL.format(token=PB_API_TOKEN))
log('[%d] %s', r.getcode(), r.geturl().replace(PB_API_TOKEN, '<secret>'))
if r.getcode() > 200:
raise URLError('bad response: %d' % r.getcode())
return json.load(r)
def convert_bookmark(bookmark):
"""Convert Pinboard bookmarks to Chrome bookmarks."""
return {
'name': bookmark['description'],
'url': bookmark['href'],
'type': 'url',
}
def write_chrome_bookmarks(bookmarks, path):
"""Save Chrome bookmarks to file."""
data = {
'roots': {
'bookmark_bar': {
'children': bookmarks,
'name': 'Pinboard',
'type': 'folder',
},
},
'version': 1,
}
with open(path, 'w') as fp:
json.dump(data, fp, indent=2, separators=(',', ': '))
def main():
"""Run script."""
opts, _ = getopt(sys.argv[1:], 'hl')
for opt, _ in opts:
if opt == '-h':
print(__doc__)
return
if opt == '-l':
for p in get_chrome_profiles():
print('%s -- %s' % (p.name, p.dir))
return
if not PB_API_TOKEN:
raise ValueError(
'Pinboard API token not set. Set the PB_API_TOKEN variable '
'in this script or the environment variable PINBOARD_API_TOKEN.'
)
if not PROFILE:
raise ValueError(
'Chrome profile not set. Set the PROFILE variable in this script '
'or the environment variable PROFILE_NAME.'
)
for profile in get_chrome_profiles():
if profile.name == PROFILE:
break
else:
raise ValueError('Chrome profile "%s" not found' % PROFILE)
path = os.path.join(profile.dir, 'Bookmarks')
pinboard = get_pinboard_bookmarks()
log('%d bookmark(s) loaded from Pinboard', len(pinboard))
chrome = [convert_bookmark(d) for d in pinboard]
write_chrome_bookmarks(chrome, path)
log('Saved bookmarks to Chrome profile "%s" (%s)' % (profile.name, path))
if __name__ == '__main__':
main()