#!/usr/bin/python

# Plurk interface module
#
# usage:  ./plurk.py <twitter username> <twitter password> <plurk username> <plurk password> [-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 = "</a>"
	while "<a " in text:
		start = text.find('"', text.find('a href')) + 1
		end = text.find('" class', start)
		link = text[start:end]
		start = text.find("<a href")
		end = text.find(end_tag, start) + len(end_tag) + 1
		text = text[:start] + link + text[end:]
	return text
	

def _update_handler2(text):
	# turn @user links into plain text @user
	# turn other links into url of the link if the link itself is in the url
	# turn words in a link into words + url
	if "@<a href" in text:
		start = text.find("@<a href") + 1
		end = text.find(">", start) + 1
		start2 = text.find("</a>", 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 <twitter user> <twitter password> <plurk user> <plurk passwd> [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])
