#!/usr/bin/python # http://del.icio.us/doc/api # serve up a delicious proxy # # side-step anything with certain tags - and if "selection" is listed, grab the tag? # and then augment the tags as needed... # xx = xml.dom.minidom.parse("tagsample") parent_url = "http://del.icio.us/api/" # check for 503 errors, and at least pass them back # email joshua about it if released # set user-agent (wrap the caller's?) # all of these are base urls off of parent_url # that take cgi arguments # posts/dates # tag= optional, filter # returns a list of dates with the number of posts at each date. # tags/get # returns all tags, as XML # <?xml version='1.0' standalone='yes'?> # <tags> # <tag count="2" tag="bluetooth" /> # <tag count="1" tag="gaim" /> # etc. # posts/get # tag= optional, filter # dt= filter by date (default most recent) # returns list of posts # posts/recent # tag= optional, filter # count= optional, number to get, default=15, max=100 # list of posts # posts/all # all posts. don't use. # posts/add # url= # description= # extended= # tags= (space delimited) # dt=CCYY-MM-DDThh:mm:ssZ (ie. "1984-09-01T14:21:31Z") # posts/delete # url= # tags/rename # old= # new= import BaseHTTPServer import urllib import urlparse urllib.URLopener.version = "thok.org-delicious-proxy/0.0" def construct_parent_call(noun, verb, args): argstring = "?" if args: ## this probably needs a level of argquoting too argstring = "?" + "&".join(["%s=%s" % (k,v) for k,v in args.items()]) return urllib.basejoin(parent_url, "".join([noun, "/", verb, argstring])) class URLrepeater(urllib.URLopener): def http_error_default(self, url, fp, errcode, errmsg, headers): """Default error handler: close the connection and raise IOError.""" void = fp.read() fp.close() raise IOError, ('http error', errcode, errmsg, headers) class DeliciousRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): # print "Got me!", self.__dict__.items() print "Got headers!", self.headers if "?" not in self.path: self.send_error(404, "Arguments not found") return # <scheme>://<netloc>/<path>?<query>#<fragment> type, host, upath, argset, fragment = urlparse.urlsplit("http://localhost" + self.path) while upath.startswith("/"): upath = upath.replace("/","",1) noun, verb = upath.split("/") ## path, argset = self.path.split("?",1) arglist = filter(None,argset.split("&")) ## this probably needs a level of argquoting too argdict = dict([arg.split("=",1) for arg in arglist]) ## print "PATH:", path # may need to just strip all of the leading slashes... ## noun, verb = path.replace("/","",1).split("/") # strip leading slash print "NOUN", noun, "VERB", verb print "args", argdict # uo = urllib.URLopener() uo = URLrepeater() for h in self.headers.headers: k,v = h.strip().split(": ",1) if k == "User-Agent": continue if k == "Host": continue if k == "Connection" and v == "keep-alive": v = "close" print "adding", k, "value", v uo.addheader(k,v) try: newurl = construct_parent_call(noun, verb, argdict) print "NEW:", newurl u = uo.open(newurl) except IOError, http_error_info: # raise IOError, ('http error', errcode, errmsg, headers) kind, errcode, errmsg, headers = http_error_info print "Got error:", errcode, errmsg self.send_response(errcode) if 'www-authenticate' in headers: print "AUTH HEADER:", headers['www-authenticate'] self.send_header('www-authenticate', headers['www-authenticate']) self.send_header("Content-Type", "text/html") self.send_header('Connection', 'close') self.end_headers() print "DONE rejecting", noun, verb, errcode return # print "UO:", uo.__dict__.items() # print "U:", u.__dict__.items() print "UH:", u.headers content = u.read() self.send_response(200) # for now, let it be readonly # let it modify headers and return content later self.server.lookaside.handle(noun, verb, content, argdict, u.headers) for h in u.headers.headers: k,v = h.strip().split(": ",1) print "passing back", k, "as", v self.send_header(k, v) #self.send_header("Content-type", ctype) # self.send_header("Content-Length", len(content)) self.end_headers() self.wfile.write(content) print "DONE with", noun, verb return import gzip import xml.dom.minidom import StringIO import os import time class LookAside: def __init__(self): self.delibase = os.path.expanduser("~/.delicious.stuff") if not os.path.isdir(self.delibase): os.mkdir(self.delibase) def handle(self, noun, verb, content, args, headers): name = 'handle_%s_%s' % (noun, verb) if not hasattr(self, name): print "UNKNOWN:", noun, verb return handler = getattr(self, name) if "Content-Encoding" in headers and headers["Content-Encoding"] == "gzip": # from feedparser.parse: content = gzip.GzipFile(fileobj=StringIO.StringIO(content)).read() fname = "_".join([noun, verb, "_".join(["%s=%s" % (k,v) for k,v in args.items()])])[:225] fname = fname + "_" + str(time.time()) # should save the full tags and such too, though, later print >> open(os.path.join(self.delibase, fname),"w"), content handler(content, args) # posts/dates # tag= optional, filter # returns a list of dates with the number of posts at each date. def handle_posts_dates(self, content, args): print "dates posts(tag):", args print "got dates posts:", content # tags/get # returns all tags, as XML # <?xml version='1.0' standalone='yes'?> # <tags> # <tag count="2" tag="bluetooth" /> # <tag count="1" tag="gaim" /> # etc. def handle_tags_get(self, content, args): print "Tags:" xx = xml.dom.minidom.parseString(content) count = 0 for tag in xx.getElementsByTagName("tag"): print "TAG:", tag.attributes["tag"].value, "COUNT:", tag.attributes["count"].value count += 1 print count, "tags total." # posts/get # tag= optional, filter # dt= filter by date (default most recent) # returns list of posts def handle_posts_get(self, content, args): print "get posts(tag,dt):", args print "got posts:", content # posts/recent # tag= optional, filter # count= optional, number to get, default=15, max=100 # list of posts def handle_posts_recent(self, content, args): print "recent posts(tag,count):", args print "got recent posts:", content # posts/all # all posts. don't use. cocoalicious does, though. def handle_posts_all(self, content, args): print "all posts:", args # print "got all posts:", content print "Posts:" xx = xml.dom.minidom.parseString(content) # <posts update="2004-12-09T13:01:18Z" user="eichin"> count = 0 for post in xx.getElementsByTagName("post"): # <post href="http://hem.fyristorg.com/matben/" # description="The Coccinella" # extended="jabber client that does sasl?" # hash="ff54b80de7acd94f069bf0563d1a9c54" # tag="jabber sasl xmpp zephyr-vs-jabber" # time="2004-12-05T11:27:22Z" /> print "URL:", post.attributes["href"].value print " desc:", post.attributes["description"].value print " keys:", post.attributes["tag"].value count += 1 print count, "posts total." # posts/add # url= # description= # extended= # tags= (space delimited) # dt=CCYY-MM-DDThh:mm:ssZ (ie. "1984-09-01T14:21:31Z") def handle_posts_add(self, content, args): print "add posts(url,desc,etc):", args print "added posts:", content # posts/delete # url= def handle_posts_delete(self, content, args): print "delete posts(url):", args print "deleted posts:", content # tags/rename # old= # new= def handle_tags_rename(self, content, args): print "rename tags(old, new):", args print "renamed tags:", content # change this to bind to localhost, once testing is done server_address = ('', 8989) httpd = BaseHTTPServer.HTTPServer(server_address, DeliciousRequestHandler) httpd.lookaside = LookAside() httpd.serve_forever()