#!/usr/bin/python # Plurk interface module # # usage: ./plurk.py [-t|-p] # # portions of this script were taken from # http://blog.charlvn.za.net/2008/06/plurk-python-post-script.html # by Neville Newey and based on code by Charl van Niekerk and subject to the following license: # http://creativecommons.org/licenses/by/2.5/za/ # # Please cite my website http://mendicantbug.com if you use this code. __author__ = 'Jason M. Adams (http://mendicantbug.com), Neville Newey' __date__ = '2008-06-05' __license__ = 'http://creativecommons.org/licenses/BSD/, http://creativecommons.org/licenses/by/2.5/za/' __version__ = '0.2.5' from urllib import urlopen, urlencode from xml.parsers import expat from urllib2 import HTTPCookieProcessor, build_opener, Request from cookielib import CookieJar import sys PLURK_LOGIN_URL = 'http://www.plurk.com/Users/login' PLURK_ADD_URL = 'http://www.plurk.com/TimeLine/addPlurk' TWITTER_UPDATE_URL = 'http://%s:%s@twitter.com/statuses/update.xml' PLURK_DB = "plurkdb.dat" ###################################################################### """XML to Tree, from Python Cookbook by O'Reilly. Credit: John Bair, Christoph Dietze """ class Element(object): """A parsed XML element.""" def __init__(self, name, attributes): # Record tagname and attributes dictionary self.name = name self.attributes = attributes # initialize the element's cdata and children to empty self.cdata = '' self.children = list() def addChild(self, element): self.children.append(element) def getAttribute(self, key): return self.attributes.get(key) def getData(self): return self.cdata def __repr__(self): return "Element(%s)" % (self.name) def getElements(self, name=''): if name: return [c for c in self.children if c.name == name] else: return list(self.children) # copy of children class Xml2Obj(object): """XML to object converter.""" def __init__(self): self.root = None self.nodeStack = list() def StartElement(self, name, attributes): """Expat start element event handler.""" # instantiate an element object element = Element(name.encode(), attributes) # push element onto the stack and make it a child of parent if self.nodeStack: parent = self.nodeStack[-1] parent.addChild(element) else: self.root = element self.nodeStack.append(element) def EndElement(self, name): """Expat end element event handler""" self.nodeStack.pop(-1) def CharacterData(self, data): """Expat character data event handler""" if data.strip(): #data = data.encode() element = self.nodeStack[-1] element.cdata += data def Parse(self, text): # create an Expat parser Parser = expat.ParserCreate() # set the Expat event handlers to our methods Parser.StartElementHandler = self.StartElement Parser.EndElementHandler = self.EndElement Parser.CharacterDataHandler = self.CharacterData # parse the XML file ParserStatus = Parser.Parse(text, 1) return self.root ###################################################################### def load_xml(user): # load the plurk xml file corresponding to the given user f = urlopen("http://www.plurk.com/user/%s.xml" % (user)) text = f.read() f.close() return text def parse(text): # parse the xml file and return the root element in the tree p = Xml2Obj() relem = p.Parse(text) return relem def extract_updates(root_elem): # given the root element of an xml tree, return a list of all # the plurks as a tuple: (id, plurk text) outp = list() for child in root_elem.children: if child.name != 'entry': continue id = None content = None for grandchild in child.children: if grandchild.name == 'id': id = str(grandchild.cdata) elif grandchild.name == 'content': content = _update_handler(str(grandchild.cdata)) if id is not None and content is not None: if 'tweets:' in content: continue outp.append((id, content)) outp.reverse() return outp def _update_handler(text): # alias function for dealing with links in a plurk or tweet return _update_handler1(text) def _update_handler1(text): # replace a link with the url of the link end_tag = "" while "", start) + 1 start2 = text.find("", end) end2 = start2 + 4 return _update_handler2(text[:start] + text[end:start2] + text[end2:]) def load_db(): try: f = open(PLURK_DB, 'r') db = [a.replace("\n", "") for a in f.readlines() if len(a)>0] f.close() except: db = list() return db def update_db(old_db, new_ids): db = set(old_db + new_ids) f = open(PLURK_DB, 'w') for dbitem in db: f.write("%s\n" % (dbitem)) f.close() def update_twitter_status(user, passwd, status): tmpstatus = status.replace('"', "") pdict = dict() pdict['status'] = """plurking:%s""" % (status) params = urlencode(pdict) f = urlopen(TWITTER_UPDATE_URL % (user, passwd), params) outp = f.read() f.close() def get_tweets(user): f = urlopen("http://twitter.com/statuses/user_timeline/%s.xml" % (user)) txt = f.read() f.close() x = Xml2Obj() p = x.Parse(txt) statuses = list() ids = list() for child in p.children: foundId = False for grandchild in child.children: if grandchild.name == 'id' and foundId is False: ids.append(str(grandchild.cdata)) foundId = True # don't want to pick up user id if grandchild.name == 'text': if grandchild.cdata.startswith("plurking:") is False: statuses.append(grandchild.cdata) return ids, statuses ############################################# # Based on Charl VN's PHP Code def plurk_message(username, passwd, msg): # Set up jar = CookieJar() handler = HTTPCookieProcessor(jar) opener = build_opener(handler) # Login phase ... url = PLURK_LOGIN_URL values = { 'nick_name': username, 'password': passwd } data = urlencode(values) req = Request(url, data) response = opener.open(req) # Now plurk something! values = {'content': ("tweets: %s" % (msg))[:140], 'lang':'en', 'no_comments':'0'} data = urlencode(values) data+='&qualifier=%3A' # Dont know if this is needed (charl) # it is (jason) url = PLURK_ADD_URL req = Request(url, data) response = opener.open(req) tmp = response.read() ############################################## def main(plurk_user, plurk_passwd, twitter_user, twitter_passwd, twitter_only=False, plurk_only=False): """Usage: main(plurk_user, plurk_passwd, twitter_user, twitter_passwd, twitter_only=False, plurk_only=False) """ assert twitter_only is False or plurk_only is False, "Cannot set both twitter_only and plurk_only to True." db = load_db() upd = list() new_ids = list() # get new plurks text = load_xml(plurk_user) relem = parse(text) raw_upd = extract_updates(relem) for rupd in raw_upd: if rupd[0] in db: continue else: upd.append(rupd[1]) if plurk_only is False: new_ids.append(rupd[0]) # get new tweets tweet_ids, tweets = get_tweets(twitter_user) tw = zip(tweet_ids, tweets) tweetd = list() for twi in tw: if twi[0] in db: continue else: if twitter_only is False: new_ids.append(twi[0]) tweetd.append(twi[1]) # handle updates according to which parameters have been set if plurk_only is False: for update in upd: update_twitter_status(twitter_user, twitter_passwd, update) if twitter_only is False: for tweet in tweetd: plurk_message(plurk_user, plurk_passwd, tweet) update_db(db, new_ids) if __name__ == '__main__': if len(sys.argv) < 5: print "Usage: %s [OPTION]" % (sys.argv[0]) print print "Possible OPTION values:" print "\t-p\tupdate plurk statuses with tweets only (cannot be used with -t option)" print "\t-t\tupdate twitter statuses with plurks only (cannot be used with -p option)" sys.exit(0) if len(sys.argv) > 5: if sys.argv[5] == '-t': main(sys.argv[3], sys.argv[4], sys.argv[1], sys.argv[2], twitter_only=True) elif sys.argv[5] == '-p': main(sys.argv[3], sys.argv[4], sys.argv[1], sys.argv[2], plurk_only=True) else: main(sys.argv[3], sys.argv[4], sys.argv[1], sys.argv[2])