# HG changeset patch # User Goffi # Date 1306178338 -7200 # Node ID 608a4a2ba94e5412228e9aaca47c34851627a9d0 # Parent be9f682c53a50d7cab775cdd73976e42be5cb338 Core: created a new core module where xmpp classes are put diff -r be9f682c53a5 -r 608a4a2ba94e src/core/__init__.py diff -r be9f682c53a5 -r 608a4a2ba94e src/core/xmpp.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/xmpp.py Mon May 23 21:18:58 2011 +0200 @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +SAT: a jabber client +Copyright (C) 2009, 2010, 2011 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.internet import task, defer +from twisted.words.protocols.jabber import jid, xmlstream +from wokkel import client, disco, xmppim, generic, compat +from logging import debug, info, error + +class SatXMPPClient(client.XMPPClient): + + def __init__(self, host_app, profile, user_jid, password, host=None, port=5222): + client.XMPPClient.__init__(self, user_jid, password, host, port) + self.factory.clientConnectionLost = self.connectionLost + self.__connected=False + self.profile = profile + self.host_app = host_app + self.client_initialized = defer.Deferred() + + def _authd(self, xmlstream): + if not self.host_app.trigger.point("XML Initialized", xmlstream, self.profile): + return + client.XMPPClient._authd(self, xmlstream) + self.__connected=True + info (_("********** [%s] CONNECTED **********") % self.profile) + self.streamInitialized() + self.host_app.bridge.connected(self.profile) #we send the signal to the clients + + + def streamInitialized(self): + """Called after _authd""" + debug (_("XML stream is initialized")) + self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire) + self.keep_alife.start(180) + + self.disco = SatDiscoProtocol(self) + self.disco.setHandlerParent(self) + self.discoHandler = disco.DiscoHandler() + self.discoHandler.setHandlerParent(self) + + if not self.host_app.trigger.point("Disco Handled", self.profile): + return + + self.roster.requestRoster() + + self.presence.available() + + self.disco.requestInfo(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDisco, self.profile) #FIXME: use these informations + self.disco.requestItems(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDiscoItems, self.disco, self.profile, self.client_initialized) + + def initializationFailed(self, reason): + print ("initializationFailed: %s" % reason) + self.host_app.bridge.connectionError("AUTH_ERROR", self.profile) + try: + client.XMPPClient.initializationFailed(self, reason) + except: + #we already send an error signal, no need to raise an exception + pass + + def isConnected(self): + return self.__connected + + def connectionLost(self, connector, unused_reason): + self.__connected=False + info (_("********** [%s] DISCONNECTED **********") % self.profile) + try: + self.keep_alife.stop() + except AttributeError: + debug (_("No keep_alife")) + self.host_app.bridge.disconnected(self.profile) #we send the signal to the clients + + +class SatMessageProtocol(xmppim.MessageProtocol): + + def __init__(self, host): + xmppim.MessageProtocol.__init__(self) + self.host = host + + def onMessage(self, message): + debug (_(u"got message from: %s"), message["from"]) + if not self.host.trigger.point("MessageReceived",message, profile=self.parent.profile): + return + for e in message.elements(): + if e.name == "body": + mess_type = message['type'] if message.hasAttribute('type') else 'normal' + self.host.bridge.newMessage(message["from"], e.children[0], mess_type, message['to'], profile=self.parent.profile) + self.host.memory.addToHistory(self.parent.jid, jid.JID(message["from"]), self.parent.jid, "chat", e.children[0]) + break + +class SatRosterProtocol(xmppim.RosterClientProtocol): + + def __init__(self, host): + xmppim.RosterClientProtocol.__init__(self) + self.host = host + self._groups=set() + + def rosterCb(self, roster): + for raw_jid, item in roster.iteritems(): + self.onRosterSet(item) + + def requestRoster(self): + """ ask the server for Roster list """ + debug("requestRoster") + self.getRoster().addCallback(self.rosterCb) + + def removeItem(self, to): + """Remove a contact from roster list""" + xmppim.RosterClientProtocol.removeItem(self, to) + #TODO: check IQ result + + #XXX: disabled (cf http://wokkel.ik.nu/ticket/56)) + #def addItem(self, to): + #"""Add a contact to roster list""" + #xmppim.RosterClientProtocol.addItem(self, to) + #TODO: check IQ result""" + + def onRosterSet(self, item): + """Called when a new/update roster item is received""" + #TODO: send a signal to frontends + item_attr = {'to': str(item.subscriptionTo), + 'from': str(item.subscriptionFrom), + 'ask': str(item.ask) + } + if item.name: + item_attr['name'] = item.name + info (_("new contact in roster list: %s"), item.jid.full()) + self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile) + self.host.bridge.newContact(item.jid.full(), item_attr, item.groups, self.parent.profile) + self._groups.update(item.groups) + + def onRosterRemove(self, entity): + """Called when a roster removal event is received""" + #TODO: send a signal to frontends + print _("removing %s from roster list") % entity.full() + self.host.memory.delContact(entity, self.parent.profile) + + def getGroups(self): + """Return a set of groups""" + return self._groups + +class SatPresenceProtocol(xmppim.PresenceClientProtocol): + + def __init__(self, host): + xmppim.PresenceClientProtocol.__init__(self) + self.host = host + + def availableReceived(self, entity, show=None, statuses=None, priority=0): + debug (_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity':entity, 'show':show, 'statuses':statuses, 'priority':priority}) + + if statuses.has_key(None): #we only want string keys + statuses["default"] = statuses[None] + del statuses[None] + + self.host.memory.addPresenceStatus(entity, show or "", + int(priority), statuses, self.parent.profile) + + #now it's time to notify frontends + self.host.bridge.presenceUpdate(entity.full(), show or "", + int(priority), statuses, self.parent.profile) + + def unavailableReceived(self, entity, statuses=None): + debug (_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity':entity, 'statuses':statuses}) + if statuses and statuses.has_key(None): #we only want string keys + statuses["default"] = statuses[None] + del statuses[None] + self.host.memory.addPresenceStatus(entity, "unavailable", 0, statuses, self.parent.profile) + + #now it's time to notify frontends + self.host.bridge.presenceUpdate(entity.full(), "unavailable", 0, statuses, self.parent.profile) + + + def available(self, entity=None, show=None, statuses=None, priority=0): + if statuses and statuses.has_key('default'): + statuses[None] = statuses['default'] + del statuses['default'] + xmppim.PresenceClientProtocol.available(self, entity, show, statuses, priority) + + def subscribedReceived(self, entity): + debug (_("subscription approved for [%s]") % entity.userhost()) + self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) + self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile) + + def unsubscribedReceived(self, entity): + debug (_("unsubscription confirmed for [%s]") % entity.userhost()) + self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) + self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile) + + def subscribeReceived(self, entity): + debug (_("subscription request for [%s]") % entity.userhost()) + self.host.memory.addWaitingSub('subscribe', entity.userhost(), self.parent.profile) + self.host.bridge.subscribe('subscribe', entity.userhost(), self.parent.profile) + + def unsubscribeReceived(self, entity): + debug (_("unsubscription asked for [%s]") % entity.userhost()) + self.host.memory.addWaitingSub('unsubscribe', entity.userhost(), self.parent.profile) + self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile) + +class SatDiscoProtocol(disco.DiscoClientProtocol): + def __init__(self, host): + disco.DiscoClientProtocol.__init__(self) + +class SatFallbackHandler(generic.FallbackHandler): + def __init__(self, host): + generic.FallbackHandler.__init__(self) + + def iqFallback(self, iq): + if iq.handled == True: + return + debug (u"iqFallback: xml = [%s]" % (iq.toXml())) + generic.FallbackHandler.iqFallback(self, iq) + +class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): + + def __init__(self, host, jabber_host, user_login, user_pass, answer_id): + xmlstream.ConnectAuthenticator.__init__(self, jabber_host) + self.host = host + self.jabber_host = jabber_host + self.user_login = user_login + self.user_pass = user_pass + self.answer_id = answer_id + print _("Registration asked for"),user_login, user_pass, jabber_host + + def connectionMade(self): + print "connectionMade" + + self.xmlstream.namespace = "jabber:client" + self.xmlstream.sendHeader() + + iq = compat.IQ(self.xmlstream, 'set') + iq["to"] = self.jabber_host + query = iq.addElement(('jabber:iq:register', 'query')) + _user = query.addElement('username') + _user.addContent(self.user_login) + _pass = query.addElement('password') + _pass.addContent(self.user_pass) + reg = iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure) + + def registrationAnswer(self, answer): + debug (_("registration answer: %s") % answer.toXml()) + answer_type = "SUCCESS" + answer_data={"message":_("Registration successfull")} + self.host.bridge.actionResult(answer_type, self.answer_id, answer_data) + self.xmlstream.sendFooter() + + def registrationFailure(self, failure): + info (_("Registration failure: %s") % str(failure.value)) + answer_type = "ERROR" + answer_data = {} + if failure.value.condition == 'conflict': + answer_data['reason'] = 'conflict' + answer_data={"message":_("Username already exists, please choose an other one")} + else: + answer_data['reason'] = 'unknown' + answer_data={"message":_("Registration failed (%s)") % str(failure.value.condition)} + self.host.bridge.actionResult(answer_type, self.answer_id, answer_data) + self.xmlstream.sendFooter() + +class SatVersionHandler(generic.VersionHandler): + + def getDiscoInfo(self, requestor, target, node): + #XXX: We need to work around wokkel's behavious (namespace not added if there is a + # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server + # ask for disco info, and not when we generate the key, so the hash is used with different + # disco features, and when the server (seen on ejabberd) generate its own hash for security check + # it reject our features (resulting in e.g. no notification on PEP) + return generic.VersionHandler.getDiscoInfo(self, requestor, target, None) + diff -r be9f682c53a5 -r 608a4a2ba94e src/sat.tac --- a/src/sat.tac Mon May 23 01:01:44 2011 +0200 +++ b/src/sat.tac Mon May 23 21:18:58 2011 +0200 @@ -48,6 +48,7 @@ import signal, sys import os.path +from sat.core.xmpp import SatXMPPClient, SatMessageProtocol, SatRosterProtocol, SatPresenceProtocol, SatDiscoProtocol, SatFallbackHandler, RegisteringAuthenticator, SatVersionHandler from sat.tools.memory import Memory from sat.tools.xml_tools import tupleList2dataForm from sat.tools.misc import TriggerManager @@ -72,264 +73,6 @@ sat_id+=1 return "sat_id_"+str(sat_id) -class SatXMPPClient(client.XMPPClient): - - def __init__(self, host_app, profile, user_jid, password, host=None, port=5222): - client.XMPPClient.__init__(self, user_jid, password, host, port) - self.factory.clientConnectionLost = self.connectionLost - self.__connected=False - self.profile = profile - self.host_app = host_app - self.client_initialized = defer.Deferred() - - def _authd(self, xmlstream): - if not self.host_app.trigger.point("XML Initialized", xmlstream, self.profile): - return - client.XMPPClient._authd(self, xmlstream) - self.__connected=True - info (_("********** [%s] CONNECTED **********") % self.profile) - self.streamInitialized() - self.host_app.bridge.connected(self.profile) #we send the signal to the clients - - - def streamInitialized(self): - """Called after _authd""" - debug (_("XML stream is initialized")) - self.keep_alife = task.LoopingCall(self.xmlstream.send, " ") #Needed to avoid disconnection (specially with openfire) - self.keep_alife.start(180) - - self.disco = SatDiscoProtocol(self) - self.disco.setHandlerParent(self) - self.discoHandler = disco.DiscoHandler() - self.discoHandler.setHandlerParent(self) - - if not self.host_app.trigger.point("Disco Handled", self.profile): - return - - self.roster.requestRoster() - - self.presence.available() - - self.disco.requestInfo(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDisco, self.profile) #FIXME: use these informations - self.disco.requestItems(jid.JID(self.host_app.memory.getParamA("Server", "Connection", profile_key=self.profile))).addCallback(self.host_app.serverDiscoItems, self.disco, self.profile, self.client_initialized) - - def initializationFailed(self, reason): - print ("initializationFailed: %s" % reason) - self.host_app.bridge.connectionError("AUTH_ERROR", self.profile) - try: - client.XMPPClient.initializationFailed(self, reason) - except: - #we already send an error signal, no need to raise an exception - pass - - def isConnected(self): - return self.__connected - - def connectionLost(self, connector, unused_reason): - self.__connected=False - info (_("********** [%s] DISCONNECTED **********") % self.profile) - try: - self.keep_alife.stop() - except AttributeError: - debug (_("No keep_alife")) - self.host_app.bridge.disconnected(self.profile) #we send the signal to the clients - - -class SatMessageProtocol(xmppim.MessageProtocol): - - def __init__(self, host): - xmppim.MessageProtocol.__init__(self) - self.host = host - - def onMessage(self, message): - debug (_(u"got message from: %s"), message["from"]) - if not self.host.trigger.point("MessageReceived",message, profile=self.parent.profile): - return - for e in message.elements(): - if e.name == "body": - mess_type = message['type'] if message.hasAttribute('type') else 'normal' - self.host.bridge.newMessage(message["from"], e.children[0], mess_type, message['to'], profile=self.parent.profile) - self.host.memory.addToHistory(self.parent.jid, jid.JID(message["from"]), self.parent.jid, "chat", e.children[0]) - break - -class SatRosterProtocol(xmppim.RosterClientProtocol): - - def __init__(self, host): - xmppim.RosterClientProtocol.__init__(self) - self.host = host - self._groups=set() - - def rosterCb(self, roster): - for raw_jid, item in roster.iteritems(): - self.onRosterSet(item) - - def requestRoster(self): - """ ask the server for Roster list """ - debug("requestRoster") - self.getRoster().addCallback(self.rosterCb) - - def removeItem(self, to): - """Remove a contact from roster list""" - xmppim.RosterClientProtocol.removeItem(self, to) - #TODO: check IQ result - - #XXX: disabled (cf http://wokkel.ik.nu/ticket/56)) - #def addItem(self, to): - #"""Add a contact to roster list""" - #xmppim.RosterClientProtocol.addItem(self, to) - #TODO: check IQ result""" - - def onRosterSet(self, item): - """Called when a new/update roster item is received""" - #TODO: send a signal to frontends - item_attr = {'to': str(item.subscriptionTo), - 'from': str(item.subscriptionFrom), - 'ask': str(item.ask) - } - if item.name: - item_attr['name'] = item.name - info (_("new contact in roster list: %s"), item.jid.full()) - self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile) - self.host.bridge.newContact(item.jid.full(), item_attr, item.groups, self.parent.profile) - self._groups.update(item.groups) - - def onRosterRemove(self, entity): - """Called when a roster removal event is received""" - #TODO: send a signal to frontends - print _("removing %s from roster list") % entity.full() - self.host.memory.delContact(entity, self.parent.profile) - - def getGroups(self): - """Return a set of groups""" - return self._groups - -class SatPresenceProtocol(xmppim.PresenceClientProtocol): - - def __init__(self, host): - xmppim.PresenceClientProtocol.__init__(self) - self.host = host - - def availableReceived(self, entity, show=None, statuses=None, priority=0): - debug (_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity':entity, 'show':show, 'statuses':statuses, 'priority':priority}) - - if statuses.has_key(None): #we only want string keys - statuses["default"] = statuses[None] - del statuses[None] - - self.host.memory.addPresenceStatus(entity, show or "", - int(priority), statuses, self.parent.profile) - - #now it's time to notify frontends - self.host.bridge.presenceUpdate(entity.full(), show or "", - int(priority), statuses, self.parent.profile) - - def unavailableReceived(self, entity, statuses=None): - debug (_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity':entity, 'statuses':statuses}) - if statuses and statuses.has_key(None): #we only want string keys - statuses["default"] = statuses[None] - del statuses[None] - self.host.memory.addPresenceStatus(entity, "unavailable", 0, statuses, self.parent.profile) - - #now it's time to notify frontends - self.host.bridge.presenceUpdate(entity.full(), "unavailable", 0, statuses, self.parent.profile) - - - def available(self, entity=None, show=None, statuses=None, priority=0): - if statuses and statuses.has_key('default'): - statuses[None] = statuses['default'] - del statuses['default'] - xmppim.PresenceClientProtocol.available(self, entity, show, statuses, priority) - - def subscribedReceived(self, entity): - debug (_("subscription approved for [%s]") % entity.userhost()) - self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) - self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile) - - def unsubscribedReceived(self, entity): - debug (_("unsubscription confirmed for [%s]") % entity.userhost()) - self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) - self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile) - - def subscribeReceived(self, entity): - debug (_("subscription request for [%s]") % entity.userhost()) - self.host.memory.addWaitingSub('subscribe', entity.userhost(), self.parent.profile) - self.host.bridge.subscribe('subscribe', entity.userhost(), self.parent.profile) - - def unsubscribeReceived(self, entity): - debug (_("unsubscription asked for [%s]") % entity.userhost()) - self.host.memory.addWaitingSub('unsubscribe', entity.userhost(), self.parent.profile) - self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile) - -class SatDiscoProtocol(disco.DiscoClientProtocol): - def __init__(self, host): - disco.DiscoClientProtocol.__init__(self) - -class SatFallbackHandler(generic.FallbackHandler): - def __init__(self, host): - generic.FallbackHandler.__init__(self) - - def iqFallback(self, iq): - if iq.handled == True: - return - debug (u"iqFallback: xml = [%s]" % (iq.toXml())) - generic.FallbackHandler.iqFallback(self, iq) - -class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): - - def __init__(self, host, jabber_host, user_login, user_pass, answer_id): - xmlstream.ConnectAuthenticator.__init__(self, jabber_host) - self.host = host - self.jabber_host = jabber_host - self.user_login = user_login - self.user_pass = user_pass - self.answer_id = answer_id - print _("Registration asked for"),user_login, user_pass, jabber_host - - def connectionMade(self): - print "connectionMade" - - self.xmlstream.namespace = "jabber:client" - self.xmlstream.sendHeader() - - iq = compat.IQ(self.xmlstream, 'set') - iq["to"] = self.jabber_host - query = iq.addElement(('jabber:iq:register', 'query')) - _user = query.addElement('username') - _user.addContent(self.user_login) - _pass = query.addElement('password') - _pass.addContent(self.user_pass) - reg = iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure) - - def registrationAnswer(self, answer): - debug (_("registration answer: %s") % answer.toXml()) - answer_type = "SUCCESS" - answer_data={"message":_("Registration successfull")} - self.host.bridge.actionResult(answer_type, self.answer_id, answer_data) - self.xmlstream.sendFooter() - - def registrationFailure(self, failure): - info (_("Registration failure: %s") % str(failure.value)) - answer_type = "ERROR" - answer_data = {} - if failure.value.condition == 'conflict': - answer_data['reason'] = 'conflict' - answer_data={"message":_("Username already exists, please choose an other one")} - else: - answer_data['reason'] = 'unknown' - answer_data={"message":_("Registration failed (%s)") % str(failure.value.condition)} - self.host.bridge.actionResult(answer_type, self.answer_id, answer_data) - self.xmlstream.sendFooter() - -class SatVersionHandler(generic.VersionHandler): - - def getDiscoInfo(self, requestor, target, node): - #XXX: We need to work around wokkel's behavious (namespace not added if there is a - # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server - # ask for disco info, and not when we generate the key, so the hash is used with different - # disco features, and when the server (seen on ejabberd) generate its own hash for security check - # it reject our features (resulting in e.g. no notification on PEP) - return generic.VersionHandler.getDiscoInfo(self, requestor, target, None) - class SAT(service.Service): def get_next_id(self):