Mercurial > libervia-backend
view src/core/xmpp.py @ 384:785420cd63f7
plugins: In-Band Bytestreams (XEP-0047) implementation
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 29 Sep 2011 12:05:45 +0200 |
parents | adcc41e4d6ea |
children | 32dc8b18c2ae |
line wrap: on
line source
#!/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() self.conn_deferred = defer.Deferred() def getConnectionDeferred(self): """Return a deferred which fire when the client is connected""" return self.conn_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) self.conn_deferred.callback(None) 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 self.conn_deferred.errback() 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 updateItem(self, roster_item): """ Update an item of the contact list. @param roster_item: item to update """ iq = compat.IQ(self.xmlstream, 'set') iq.addElement((xmppim.NS_ROSTER, 'query')) item = iq.query.addElement('item') item['jid'] = roster_item.jid.userhost() if roster_item.name: item['name'] = roster_item.name for group in roster_item.groups: item.addElement('group', content=group) return iq.send() def onRosterSet(self, item): """Called when a new/update roster item is received""" #TODO: send a signal to frontends if not item.subscriptionTo and not item.subscriptionFrom and not item.ask: #XXX: current behaviour: we don't want contact in our roster list #if there is no presence subscription #may change in the future self.removeItem(item.jid) return 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""" print _("removing %s from roster list") % entity.full() self.host.memory.delContact(entity, self.parent.profile) self.host.bridge.contactDeleted(entity.userhost(), 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 not statuses: statuses = {} 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 not statuses: statuses = {} if 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 not statuses: statuses = {} # default for us is None for wokkel # so we must temporarily switch to wokkel's convention... if 'default' in statuses: statuses[None] = statuses['default'] xmppim.PresenceClientProtocol.available(self, entity, show, statuses, priority) # ... before switching back if None in statuses: del statuses[None] def subscribed(self, entity): xmppim.PresenceClientProtocol.subscribed(self, entity) self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) contact = self.host.memory.getContact(entity, self.parent.profile) if not contact or contact[0]['to'] == 'False': #we automatically subscribe to 'to' presence debug(_('sending automatic "from" subscription request')) self.subscribe(entity) def unsubscribed(self, entity): xmppim.PresenceClientProtocol.unsubscribed(self, entity) self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) def subscribedReceived(self, entity): debug (_("subscription approved for [%s]") % entity.userhost()) self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile) def unsubscribedReceived(self, entity): debug (_("unsubscription confirmed for [%s]") % entity.userhost()) self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile) def subscribeReceived(self, entity): debug (_("subscription request from [%s]") % entity.userhost()) contact = self.host.memory.getContact(entity, self.parent.profile) if contact and contact[0]['to'] == 'True': #We automatically accept subscription if we are already subscribed to contact presence debug(_('sending automatic subscription acceptance')) self.subscribed(entity) else: 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()) contact = self.host.memory.getContact(entity, self.parent.profile) if contact and contact[0]['from'] == 'True': #we automatically remove contact debug(_('automatic contact deletion')) self.host.delContact(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, email, 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.user_email = email 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) if self.user_email: _email = query.addElement('email') _email.addContent(self.user_email) 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)