#!/usr/bin/env python # Copyright (c) 2007, Tim Goh # All rights reserved. # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * The name of Tim Goh may not be used to endorse or promote products derived # from this software without specific prior written permission. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Attributions # ============ # Code contains portions of Natalie Downe's Snafflr script # Snafflr: # Description: http://notes.natbat.net/2007/03/06/delicioussnaflr/ # Code: http://natbat.net/code/python/snafflr/sync.txt import sys, urllib2, urllib, time, datetime from xml.dom import minidom from xml.parsers.expat import ExpatError def halt(msg, error_code=1): if isinstance(msg, list): msg = "\n".join(msg) print msg sys.exit(error_code) def urlencode(d): for key, value in d.items(): if isinstance(value, unicode): d[key] = value.encode('utf-8') return urllib.urlencode(d) def domify(url_str): try: element = minidom.parseString(urllib2.urlopen(url_str).read()) except ExpatError, e: halt("Error parsing %s : %s" % (url_str, e)) return element def login(delicious_user, delicious_pass): """Takes a username and password and logs into delicious""" password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() password_manager.add_password( None, 'https://api.del.icio.us/', delicious_user, delicious_pass ) auth_handler = urllib2.HTTPBasicAuthHandler(password_manager) opener = urllib2.build_opener(auth_handler) try: # check if login works opener.open('https://api.del.icio.us/v1/posts/update') except IOError, e: halt(["Error logging in", str(e)]) urllib2.install_opener(opener) def post_item(url, title, notes, tags): """function takes a title, url, description and tags and posts to delicious""" postUrl = urlencode({ 'url': url, 'description': title, 'extended': notes, 'tags': tags, 'replace': 'no' }) return domify('https://api.del.icio.us/v1/posts/add?%s' % postUrl).getElementsByTagName('result')[0].getAttribute('code') def get_data(item, tag_name, value_if_none=''): element = item.getElementsByTagName(tag_name) if len(element) and element[0].firstChild: return element[0].firstChild.data return value_if_none def get_for_links(account): el_for = domify(account['feed_url']) # check if this is a valid url el_title = get_data(el_for, 'title') if not len(el_title) or \ el_title != 'del.icio.us/for/%s' % account['user']: halt(["Invalid feed url", "The feed url should look like", "'http://del.icio.us/rss/for/%s?private='" % account['user'] ]) el_links = el_for.getElementsByTagName('item') if len(el_links) and get_data(el_links[0], 'title') == 'del.icio.us error': halt("Invalid private key in feed url %s" % account['feed_url']) return el_links def parse_item(item): result = post_item(get_data(item, 'link'), get_data(item, 'title'), get_data(item, 'description'), get_data(item, 'dc:subject')) return result == 'done' def sync(account): print "Sync starting." login(account['user'], account['password']) el_links = get_for_links(account) el_links.reverse() # maintain posting order success = 0 for link in el_links: print "Copying %s ... " % get_data(link, 'link'), if parse_item(link): print "success" success += 1 else: print "fail" time.sleep(2) print "Sync complete." print "%s out of %s links synchronized." % (success, len(el_links)) def parse_args(): usage = "Usage: %s username password feed_url" % sys.argv[0] if len(sys.argv) < 4: halt(["Not enough arguments", usage], 2) return {'user': sys.argv[1], 'password': sys.argv[2], 'feed_url': sys.argv[3]} if __name__ == '__main__': account = parse_args() sync(account)