#!/usr/bin/python # -*- coding: utf-8 -*- """ SAT: a jabber client Copyright (C) 2009 Jérôme Poisson (goffi@goffi.org) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from twisted.application import internet, service from twisted.internet import glib2reactor, protocol, task glib2reactor.install() from twisted.words.protocols.jabber import client, jid, xmlstream, error from twisted.words.xish import domish from twisted.internet import reactor import pdb from sat_bridge.DBus import DBusBridge import logging from logging import debug, info, error import signal, sys import os.path from tools.memory import Memory from glob import glob ### logging configuration FIXME: put this elsewhere ### logging.basicConfig(level=logging.DEBUG, format='%(message)s') ### class SAT: def __init__(self): #self.reactor=reactor self.memory=Memory() self.server_features=[] #XXX: temp dic, need to be transfered into self.memory in the future self.connected=False #FIXME: use twisted var instead self._iq_cb_map = {} #callback called when ns is found on IQ self._waiting_conf = {} #callback called when a confirmation is received self._progress_cb_map = {} #callback called when a progress is requested (key = progress id) self.plugins = {} self.bridge=DBusBridge() self.bridge.register("connect", self.connect) self.bridge.register("disconnect", self.disconnect) self.bridge.register("getContacts", self.memory.getContacts) self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus) self.bridge.register("sendMessage", self.sendMessage) self.bridge.register("setParam", self.setParam) self.bridge.register("getParam", self.memory.getParam) self.bridge.register("getParams", self.memory.getParams) self.bridge.register("getParamsCategories", self.memory.getParamsCategories) self.bridge.register("getHistory", self.memory.getHistory) self.bridge.register("setPresence", self.setPresence) self.bridge.register("addContact", self.addContact) self.bridge.register("delContact", self.delContact) self.bridge.register("isConnected", self.isConnected) self.bridge.register("confirmationAnswer", self.confirmationAnswer) self.bridge.register("getProgress", self.getProgress) self._import_plugins() #self.connect() def _import_plugins(self): """Import all plugins found in plugins directory""" #TODO: manage dependencies plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename,glob ("plugins/plugin*.py"))] for plug in plug_lst: plug_path = 'plugins.'+plug __import__(plug_path) mod = sys.modules[plug_path] plug_info = mod.PLUGIN_INFO info ("importing plugin: %s", plug_info['name']) self.plugins[plug_info['import_name']] = getattr(mod, plug_info['main'])(self) def connect(self): if (self.connected): info("already connected !") return print "connecting..." reactor.connectTCP(self.memory.getParamV("Server", "Connection"), 5222, self.factory) def disconnect(self): if (not self.connected): info("not connected !") return info("Disconnecting...") self.factory.stopTrying() if self.xmlstream: self.xmlstream.sendFooter() def getService(self): print "GetService !" """if (self.connected): info("already connected !") return""" info("Getting service...") self.me = jid.JID(self.memory.getParamV("JabberID", "Connection")) self.factory = client.XMPPClientFactory(self.me, self.memory.getParamV("Password", "Connection")) self.factory.clientConnectionLost = self.connectionLost self.factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT,self.authd) self.factory.addBootstrap(xmlstream.INIT_FAILED_EVENT,self.failed) return internet.TCPClient(self.memory.getParamV("Server", "Connection"), 5222, self.factory) def run(self): debug("running app") reactor.run() def stop(self): debug("stopping app") reactor.stop() def authd(self,xmlstream): self.xmlstream=xmlstream roster=client.IQ(xmlstream,'get') roster.addElement(('jabber:iq:roster', 'query')) roster.addCallback(self.rosterCb) roster.send() debug("server = %s",self.memory.getParamV("Server", "Connection")) ###FIXME: tmp disco ### self.memory.registerFeature("http://jabber.org/protocol/disco#info") self.disco(self.memory.getParamV("Server", "Connection"), self.serverDisco) #we now send our presence status self.setPresence(status="Online") # add a callback for the messages xmlstream.addObserver('/message', self.gotMessage) xmlstream.addObserver('/presence', self.presenceCb) xmlstream.addObserver("/iq[@type='set' or @type='get']", self.iqCb) print "********** CONNECTED **********" self.connected=True self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire) self.keep_alife.start(180) #reactor.callLater(2,self.sendFile,"goffi2@jabber.goffi.int/Psi", "/tmp/fakefile") def connectionLost(self, connector, unused_reason): print "********** DISCONNECTED **********" if self.keep_alife: self.keep_alife.stop() self.connected=False def sendMessage(self,to,msg,type='chat'): #FIXME: check validity of recipient debug("Sending jabber message to %s...", to) message = domish.Element(('jabber:client','message')) message["to"] = jid.JID(to).full() message["from"] = self.me.full() message["type"] = type message.addElement("body", "jabber:client", msg) self.xmlstream.send(message) self.memory.addToHistory(self.me, self.me, jid.JID(to), message["type"], unicode(msg)) self.bridge.newMessage(message['from'], unicode(msg), to=message['to']) #We send back the message, so all clients are aware of it def setParam(self, name, value, namespace): """set wanted paramater and notice observers""" info ("setting param: %s=%s in namespace %s", name, value, namespace) self.memory.setParam(name, value, namespace) self.bridge.paramUpdate(name, value, namespace) def setRoster(self, to): """Add a contact to roster list""" to_jid=jid.JID(to) roster=client.IQ(self.xmlstream,'set') query=roster.addElement(('jabber:iq:roster', 'query')) item=query.addElement("item") item.attributes["jid"]=to_jid.userhost() roster.send() #TODO: check IQ result def delRoster(self, to): """Remove a contact from roster list""" to_jid=jid.JID(to) roster=client.IQ(self.xmlstream,'set') query=roster.addElement(('jabber:iq:roster', 'query')) item=query.addElement("item") item.attributes["jid"]=to_jid.userhost() item.attributes["subscription"]="remove" roster.send() #TODO: check IQ result def failed(self,xmlstream): debug("failed: %s", xmlstream.getErrorMessage()) debug("failed: %s", dir(xmlstream)) def isConnected(self): return self.connected ## jabber methods ## def disco (self, item, callback, node=None): """XEP-0030 Service discovery Feature.""" disco=client.IQ(self.xmlstream,'get') disco["from"]=self.me.full() disco["to"]=item disco.addElement(('http://jabber.org/protocol/disco#info', 'query')) disco.addCallback(callback) disco.send() def setPresence(self, to="", type="", show="", status="", priority=0): """Send our presence information""" presence = domish.Element(('jabber:client', 'presence')) if not type in ["", "unavailable", "subscribed", "subscribe", "unsubscribe", "unsubscribed", "prob", "error"]: error("Type error !") #TODO: throw an error return if to: presence.attributes["to"]=to if type: presence.attributes["type"]=type for element in ["show", "status", "priority"]: if locals()[element]: presence.addElement(element).addContent(unicode(locals()[element])) self.xmlstream.send(presence) def addContact(self, to): """Add a contact in roster list""" to_jid=jid.JID(to) self.setRoster(to_jid.userhost()) self.setPresence(to_jid.userhost(), "subscribe") def delContact(self, to): """Remove contact from roster list""" to_jid=jid.JID(to) self.delRoster(to_jid.userhost()) self.bridge.contactDeleted(to) def gotMessage(self,message): debug (u"got_message from: %s", message["from"]) for e in message.elements(): if e.name == "body": self.bridge.newMessage(message["from"], e.children[0]) self.memory.addToHistory(self.me, jid.JID(message["from"]), self.me, "chat", e.children[0]) break ## callbacks ## def add_IQ_cb(self, ns, cb): """Add an IQ callback on namespace ns""" debug ("Registered callback for namespace %s", ns) self._iq_cb_map[ns]=cb def iqCb(self, stanza): info ("iqCb") debug ("="*20) debug ("DEBUG:\n") debug (stanza.toXml().encode('utf-8')) debug ("="*20) #FIXME: temporary ugly code uri = stanza.firstChildElement().uri if self._iq_cb_map.has_key(uri): self._iq_cb_map[uri](stanza) #TODO: manage errors stanza def presenceCb(self, elem): info ("presence update for [%s]", elem.getAttribute("from")) debug("\n\nXML=\n%s\n\n", elem.toXml()) presence={} presence["jid"]=elem.getAttribute("from") presence["type"]=elem.getAttribute("type") or "" presence["show"]="" presence["status"]="" presence["priority"]=0 for item in elem.elements(): if presence.has_key(item.name): presence[item.name]=item.children[0] ### we check if the status is not about subscription ### #TODO: check that from jid is one we wants to subscribe (ie: check a recent subscription asking) if jid.JID(presence["jid"]).userhost()!=self.me.userhost(): if presence["type"]=="subscribed": debug ("subscription answer") elif presence["type"]=="unsubscribed": debug ("unsubscription answer") elif presence["type"]=="subscribe": #FIXME: auto answer for subscribe request, must be checked ! debug ("subscription request") self.setPresence(to=presence["jid"], type="subscribed") else: #We keep presence information only if it is not for subscription self.memory.addPresenceStatus(presence["jid"], presence["type"], presence["show"], presence["status"], int(presence["priority"])) #now it's time to notify frontends self.bridge.presenceUpdate(presence["jid"], presence["type"], presence["show"], presence["status"], int(presence["priority"])) def rosterCb(self,roster): for contact in roster.firstChildElement().elements(): info ("new contact in roster list: %s", contact['jid']) #and now the groups groups=[] for group in contact.elements(): if group.name!="group": error("Unexpected element !") break groups.append(str(group)) self.memory.addContact(contact['jid'], contact.attributes, groups) self.bridge.newContact(contact['jid'], contact.attributes, groups) def serverDisco(self, disco): """xep-0030 Discovery Protocol.""" for element in disco.firstChildElement().elements(): if element.name=="feature": debug ("Feature dectetee: %s",element["var"]) self.server_features.append(element["var"]) elif element.name=="identity": debug ("categorie= %s",element["category"]) debug ("features= %s",self.server_features) ## Generic HMI ## def askConfirmation(self, id, type, data, cb): """Add a confirmation callback""" if self._waiting_conf.has_key(id): error ("Attempt to register two callbacks for the same confirmation") else: self._waiting_conf[id] = cb self.bridge.askConfirmation(type, id, data) def confirmationAnswer(self, id, accepted, data): """Called by frontends to answer confirmation requests""" debug ("Received confirmation answer for id [%s]: %s", id, "accepted" if accepted else "refused") if not self._waiting_conf.has_key(id): error ("Received an unknown confirmation") else: cb = self._waiting_conf[id] del self._waiting_conf[id] cb(id, accepted, data) def registerProgressCB(self, id, CB): """Register a callback called when progress is requested for id""" self._progress_cb_map[id] = CB def removeProgressCB(self, id): """Remove a progress callback""" if not self._progress_cb_map.has_key(id): error ("Trying to remove an unknow progress callback") else: del self._progress_cb_map[id] def getProgress(self, id): """Return a dict with progress information data['position'] : current possition data['size'] : end_position """ data = {} try: self._progress_cb_map[id](data) except KeyError: pass #debug("Requested progress for unknown id") return data application = service.Application('SàT') sat = SAT() service = sat.getService() service.setServiceParent(application) #app.memory.save() #FIXME: not the best place #debug("Good Bye")