#!/usr/bin/env python

import dbus
import re
import urllib
import sys

import xml.dom.minidom 

xml.dom.minidom.Element.all   = xml.dom.minidom.Element.getElementsByTagName

obj = None
try:
    obj = dbus.SessionBus().get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
except:
    pass

purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")

class CheckedObject:
    def __init__(self, obj):
        self.obj = obj

    def __getattr__(self, attr):
        return CheckedAttribute(self, attr)

class CheckedAttribute:
    def __init__(self, cobj, attr):
        self.cobj = cobj
        self.attr = attr
        
    def __call__(self, *args):
        result = self.cobj.obj.__getattr__(self.attr)(*args)
        if result == 0:
            raise "Error: " + self.attr + " " + str(args) + " returned " + str(result)
        return result
            
def show_help(requested=False):
    print """This program uses D-Bus to communicate with purple.

Usage:

    %s "command1" "command2" ...

Each command is of one of the three types:

    [protocol:]commandname?param1=value1&param2=value2&...
    FunctionName?param1=value1&param2=value2&...
    FunctionName(value1,value2,...)

The second and third form are provided for completeness but their use
is not recommended; use purple-send or purple-send-async instead.  The
second form uses introspection to find out the parameter names and
their types, therefore it is rather slow.

Examples of commands:

    jabber:goim?screenname=testone@localhost&message=hi
    jabber:gochat?room=TestRoom&server=conference.localhost
    jabber:getinfo?screenname=testone@localhost
    jabber:addbuddy?screenname=my friend

    setstatus?status=away&message=don't disturb
    quit

    PurpleAccountsFindConnected?name=&protocol=prpl-jabber
    PurpleAccountsFindConnected(,prpl-jabber)
""" % sys.argv[0]
    if (requested):
        sys.exit(0)
    else:
        sys.exit(1)

cpurple = CheckedObject(purple)

urlregexp = r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?"

def extendlist(list, length, fill):
    if len(list) < length:
        return list + [fill] * (length - len(list))
    else:
        return list

def convert(value):
    try:
        return int(value)
    except:
        return value

def findaccount(accountname, protocolname):
    try:
        # prefer connected accounts
        account = cpurple.PurpleAccountsFindConnected(accountname, protocolname)
        return account
    except:
        # try to get any account and connect it
        account = cpurple.PurpleAccountsFindAny(accountname, protocolname)
        purple.PurpleAccountSetStatusVargs(account, "online", 1)
        purple.PurpleAccountConnect(account)
        return account
    

def execute(uri):
    match = re.match(urlregexp, uri)
    protocol = match.group(2)
    if protocol == "xmpp":
        protocol = "jabber"
    if protocol is not None:
        protocol = "prpl-" + protocol
    command = match.group(5)
    paramstring = match.group(7)
    params = {}
    if paramstring is not None:
        for param in paramstring.split("&"):
            key, value = extendlist(param.split("=",1), 2, "")
            params[key] = urllib.unquote(value)

    accountname = params.get("account", "")

    if command == "goim":
        account = findaccount(accountname, protocol)
        conversation = cpurple.PurpleConversationNew(1, account, params["screenname"])
        if "message" in params:
            im = cpurple.PurpleConversationGetImData(conversation)
            purple.PurpleConvImSend(im, params["message"])
        return None

    elif command == "gochat":
        account = findaccount(accountname, protocol)
        connection = cpurple.PurpleAccountGetConnection(account)
        return purple.ServJoinChat(connection, params)

    elif command == "addbuddy":
        account = findaccount(accountname, protocol)
        return cpurple.PurpleBlistRequestAddBuddy(account, params["screenname"],
                                              params.get("group", ""), "")

    elif command == "setstatus":
        current = purple.PurpleSavedstatusGetCurrent()

        if "status" in params:
            status_id = params["status"]
            status_type = purple.PurplePrimitiveGetTypeFromId(status_id)
        else:
            status_type = purple.PurpleSavedstatusGetType(current)
            status_id = purple.PurplePrimitiveGetIdFromType(status_type)

        if "message" in params:
            message = params["message"];
        else:
            message = purple.PurpleSavedstatusGetMessage(current)

        if "account" in params:
            accounts = [cpurple.PurpleAccountsFindAny(accountname, protocol)]

            for account in accounts:
                status = purple.PurpleAccountGetStatus(account, status_id)
                type = purple.PurpleStatusGetType(status)
                purple.PurpleSavedstatusSetSubstatus(current, account, type, message)
                purple.PurpleSavedstatusActivateForAccount(current, account)
        else:
            accounts = purple.PurpleAccountsGetAllActive()
            saved = purple.PurpleSavedstatusNew("", status_type)
            purple.PurpleSavedstatusSetMessage(saved, message)
            purple.PurpleSavedstatusActivate(saved)

        return None

    elif command == "getstatus":
        current = purple.PurpleSavedstatusGetCurrent()
        status_type = purple.PurpleSavedstatusGetType(current)
        status_id = purple.PurplePrimitiveGetIdFromType(status_type)
        return status_id

    elif command == "getinfo":
        account = findaccount(accountname, protocol)
        connection = cpurple.PurpleAccountGetConnection(account)
        return purple.ServGetInfo(connection, params["screenname"])

    elif command == "quit":
        return purple.PurpleCoreQuit()

    elif command == "uri":
        return None

    else:
        match = re.match(r"(\w+)\s*\(([^)]*)\)", command)
        if match is not None:
            name = match.group(1)
            argstr = match.group(2)
            if argstr == "":
                args = []
            else:
                args = argstr.split(",")
            fargs = []
            for arg in args:
                fargs.append(convert(arg.strip()))
            return purple.__getattr__(name)(*fargs)
        else:
            # introspect the object to get parameter names and types
            # this is slow because the entire introspection info must be downloaded
            data = dbus.Interface(obj, "org.freedesktop.DBus.Introspectable").\
                   Introspect()
            introspect = xml.dom.minidom.parseString(data).documentElement
            for method in introspect.all("method"):
                if command == method.getAttribute("name"):
                    methodparams = []
                    for arg in method.all("arg"):
                        if arg.getAttribute("direction") == "in":
                            value = params[arg.getAttribute("name")]
                            type = arg.getAttribute("type")
                            if type == "s":
                                methodparams.append(value)
                            elif type == "i":
                                methodparams.append(int(value))
                            else:
                                raise "Don't know how to handle type \"%s\"" % type
                    return purple.__getattr__(command)(*methodparams)
            show_help()

if len(sys.argv) == 1:
    show_help()
elif (sys.argv[1] == "--help" or sys.argv[1] == "-h"):
    show_help(True)
elif (obj == None):
    print "No existing libpurple instance detected."
    sys.exit(1);
    
for arg in sys.argv[1:]:
    output = execute(arg)

    if (output != None):
        print output

