# This file is part of Gajim. # # Gajim 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; version 3 only. # # Gajim 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 Gajim. If not, see . # XEP-0030: Service Discovery import nbxmpp from nbxmpp.namespaces import Namespace from nbxmpp.structs import StanzaHandler from nbxmpp.errors import StanzaError from nbxmpp.errors import is_error from gajim.common import app from gajim.common.nec import NetworkIncomingEvent from gajim.common.nec import NetworkEvent from gajim.common.modules.util import as_task from gajim.common.modules.base import BaseModule class Discovery(BaseModule): _nbxmpp_extends = 'Discovery' _nbxmpp_methods = [ 'disco_info', 'disco_items', ] def __init__(self, con): BaseModule.__init__(self, con) self.handlers = [ StanzaHandler(name='iq', callback=self._answer_disco_info, typ='get', ns=Namespace.DISCO_INFO), StanzaHandler(name='iq', callback=self._answer_disco_items, typ='get', ns=Namespace.DISCO_ITEMS), ] self._account_info = None self._server_info = None @property def account_info(self): return self._account_info @property def server_info(self): return self._server_info def discover_server_items(self): server = self._con.get_own_jid().domain self.disco_items(server, callback=self._server_items_received) def _server_items_received(self, task): try: result = task.finish() except StanzaError as error: self._log.warning('Server disco failed') self._log.error(error) return self._log.info('Server items received') self._log.debug(result) for item in result.items: if item.node is not None: # Only disco components continue self.disco_info(item.jid, callback=self._server_items_info_received) def _server_items_info_received(self, task): try: result = task.finish() except StanzaError as error: self._log.warning('Server item disco info failed') self._log.warning(error) return self._log.info('Server item info received: %s', result.jid) self._parse_transports(result) try: self._con.get_module('MUC').pass_disco(result) self._con.get_module('HTTPUpload').pass_disco(result) self._con.get_module('Bytestream').pass_disco(result) except nbxmpp.NodeProcessed: pass app.nec.push_incoming_event( NetworkIncomingEvent('server-disco-received')) def discover_account_info(self): own_jid = self._con.get_own_jid().bare self.disco_info(own_jid, callback=self._account_info_received) def _account_info_received(self, task): try: result = task.finish() except StanzaError as error: self._log.warning('Account disco info failed') self._log.warning(error) return self._log.info('Account info received: %s', result.jid) self._account_info = result self._con.get_module('MAM').pass_disco(result) self._con.get_module('PEP').pass_disco(result) self._con.get_module('PubSub').pass_disco(result) self._con.get_module('Bookmarks').pass_disco(result) self._con.get_module('VCardAvatars').pass_disco(result) self._con.get_module('Caps').update_caps() def discover_server_info(self): # Calling this method starts the connect_maschine() server = self._con.get_own_jid().domain self.disco_info(server, callback=self._server_info_received) def _server_info_received(self, task): try: result = task.finish() except StanzaError as error: self._log.error('Server disco info failed') self._log.error(error) return self._log.info('Server info received: %s', result.jid) self._server_info = result self._con.get_module('SecLabels').pass_disco(result) self._con.get_module('Blocking').pass_disco(result) self._con.get_module('VCardTemp').pass_disco(result) self._con.get_module('Carbons').pass_disco(result) self._con.get_module('HTTPUpload').pass_disco(result) self._con.get_module('Register').pass_disco(result) self._con.connect_machine(restart=True) def _parse_transports(self, info): for identity in info.identities: if identity.category not in ('gateway', 'headline'): continue self._log.info('Found transport: %s %s %s', info.jid, identity.category, identity.type) jid = str(info.jid) if jid not in app.transport_type: app.transport_type[jid] = identity.type if identity.type in self._con.available_transports: self._con.available_transports[identity.type].append(jid) else: self._con.available_transports[identity.type] = [jid] def _answer_disco_items(self, _con, stanza, _properties): from_ = stanza.getFrom() self._log.info('Answer disco items to %s', from_) if self._con.get_module('AdHocCommands').command_items_query(stanza): raise nbxmpp.NodeProcessed node = stanza.getTagAttr('query', 'node') if node is None: result = stanza.buildReply('result') self._con.connection.send(result) raise nbxmpp.NodeProcessed if node == Namespace.COMMANDS: self._con.get_module('AdHocCommands').command_list_query(stanza) raise nbxmpp.NodeProcessed def _answer_disco_info(self, _con, stanza, _properties): from_ = stanza.getFrom() self._log.info('Answer disco info %s', from_) if str(from_).startswith('echo.'): # Service that echos all stanzas, ignore it raise nbxmpp.NodeProcessed if self._con.get_module('AdHocCommands').command_info_query(stanza): raise nbxmpp.NodeProcessed @as_task def disco_muc(self, jid, request_vcard=False, allow_redirect=False): _task = yield self._log.info('Request MUC info for %s', jid) result = yield self._nbxmpp('MUC').request_info( jid, request_vcard=request_vcard, allow_redirect=allow_redirect) if is_error(result): raise result if result.redirected: self._log.info('MUC info received after redirect: %s -> %s', jid, result.info.jid) else: self._log.info('MUC info received: %s', result.info.jid) app.storage.cache.set_last_disco_info(result.info.jid, result.info) if result.vcard is not None: avatar, avatar_sha = result.vcard.get_avatar() if avatar is not None: if not app.interface.avatar_exists(avatar_sha): app.interface.save_avatar(avatar) app.storage.cache.set_muc_avatar_sha(result.info.jid, avatar_sha) app.interface.avatar_storage.invalidate_cache(result.info.jid) self._con.get_module('VCardAvatars').muc_disco_info_update(result.info) app.nec.push_incoming_event(NetworkEvent( 'muc-disco-update', account=self._account, room_jid=result.info.jid)) yield result @as_task def disco_contact(self, contact): _task = yield fjid = contact.get_full_jid() result = yield self.disco_info(fjid) if is_error(result): raise result self._log.info('Disco Info received: %s', fjid) app.storage.cache.set_last_disco_info(result.jid, result, cache_only=True) app.nec.push_incoming_event( NetworkEvent('caps-update', account=self._account, fjid=fjid, jid=contact.jid)) def get_instance(*args, **kwargs): return Discovery(*args, **kwargs), 'Discovery'