Mercurial > libervia-backend
diff src/core/xmpp.py @ 330:608a4a2ba94e
Core: created a new core module where xmpp classes are put
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 23 May 2011 21:18:58 +0200 |
parents | src/sat.tac@b069055320b1 |
children | 4c835d614bdb |
line wrap: on
line diff
--- /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 <http://www.gnu.org/licenses/>. +""" + +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) +