Skip to content

Instantly share code, notes, and snippets.

@wwalker
Forked from eugeneius/xmarks2git.py
Created February 2, 2018 00:52
Show Gist options
  • Save wwalker/3f4d971dd95100633355f5c751547396 to your computer and use it in GitHub Desktop.
Save wwalker/3f4d971dd95100633355f5c751547396 to your computer and use it in GitHub Desktop.
Downloads every revision of your Xmarks bookmarks and saves them in a git repository.
#!/usr/bin/env python
import argparse
import getpass
import json
import re
import requests
import subprocess
parser = argparse.ArgumentParser()
parser.add_argument('--username', required=True)
parser.add_argument('--password')
args = parser.parse_args()
username = args.username
password = args.password
if password is None:
password = getpass.getpass()
login_url = 'https://login.xmarks.com/login/login'
r = requests.get(login_url, data=dict(username=username, password=password))
r.raise_for_status()
token = re.search('<input type="hidden" name="token" value="([^"]*)" />', r.text).group(1)
r = requests.post(login_url, data=dict(username=username, password=password, token=token))
r.raise_for_status()
session_cookie = r.cookies['XMARKS']
print("Getting list of revisions from Xmarks...")
revisions_url = 'https://my.xmarks.com/bookmarks/syncd_get_revisions'
headers = { 'content-type': 'application/json' }
r = requests.post(revisions_url, cookies=dict(XMARKS=session_cookie), data=json.dumps({}), headers=headers)
r.raise_for_status()
response = r.text
revision_hashes = response.split('[')[1].split(']')[0]
revisions = json.loads('[{0}]'.format(revision_hashes))
try:
process = subprocess.Popen(['git', 'log', '--max-count=1', '--format=%B'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = process.communicate()
last_revision = int(out)
except ValueError:
last_revision = 0
revisions = filter(lambda revision: int(revision['revision']) > last_revision, revisions)
revisions = sorted(revisions, key=lambda revision: revision['revision'])
if not revisions:
print("No new revisions since last sync.")
raise SystemExit
print("Latest local revision: {0}".format(last_revision))
print("Latest remote revision: {0}".format(revisions[-1]['revision']))
for revision in revisions:
for attempt in range(3):
try:
print("Backup {0} downloading".format(revision['revision']))
bookmarks_url = 'https://my.xmarks.com/bookmarks/view/{0}/bookmarks.html'.format(revision['revision'])
r = requests.get(bookmarks_url, cookies=dict(XMARKS=session_cookie))
r.raise_for_status()
if '<H1>Bookmarks</H1>' not in r.text:
raise Exception('Backup appears to be corrupted!')
break
except Exception as e:
if attempt == 3:
raise e
with open('bookmarks.html', 'wb') as f:
f.write(r.text.encode('utf-8'))
subprocess.call(['git', 'add', 'bookmarks.html'])
subprocess.call(['git', 'commit', '--message={0}'.format(revision['revision'])])
print("Backup {0} downloaded".format(revision['revision']))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment