# 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 . # Presence handler import logging import time import nbxmpp from nbxmpp.structs import StanzaHandler from nbxmpp.const import PresenceType from gajim.common import app from gajim.common.i18n import _ from gajim.common.nec import NetworkEvent from gajim.common.const import KindConstant from gajim.common.helpers import prepare_and_validate_gpg_keyID log = logging.getLogger('gajim.c.m.presence') class Presence: def __init__(self, con): self._con = con self._account = con.name self.handlers = [ StanzaHandler(name='presence', callback=self._presence_received, priority=50), StanzaHandler(name='presence', callback=self._subscribe_received, typ='subscribe', priority=49), StanzaHandler(name='presence', callback=self._subscribed_received, typ='subscribed', priority=49), StanzaHandler(name='presence', callback=self._unsubscribe_received, typ='unsubscribe', priority=49), StanzaHandler(name='presence', callback=self._unsubscribed_received, typ='unsubscribed', priority=49), ] # keep the jids we auto added (transports contacts) to not send the # SUBSCRIBED event to GUI self.automatically_added = [] # list of jid to auto-authorize self.jids_for_auto_auth = [] def _presence_received(self, _con, stanza, properties): if properties.from_muc: # Already handled in MUC module return log.info('Received from %s', properties.jid) if properties.type == PresenceType.ERROR: log.info('Error: %s %s', properties.jid, properties.error) return if self._account == 'Local': app.nec.push_incoming_event( NetworkEvent('raw-pres-received', conn=self._con, stanza=stanza)) return if properties.is_self_presence: app.nec.push_incoming_event( NetworkEvent('our-show', conn=self._con, show=properties.show.value)) return contacts = app.contacts.get_jid_list(self._account) if properties.jid.getBare() not in contacts and not properties.is_self_bare: # Handle only presence from roster contacts log.warning('Unknown presence received') log.warning(stanza) return key_id = '' if properties.signed is not None and self._con.USE_GPG: key_id = self._con.gpg.verify(properties.status, properties.signed) key_id = prepare_and_validate_gpg_keyID( self._account, properties.jid.getBare(), key_id) show = properties.show.value if properties.type.is_unavailable: show = 'offline' event_attrs = { 'conn': self._con, 'stanza': stanza, 'keyID': key_id, 'prio': properties.priority, 'need_add_in_roster': False, 'popup': False, 'ptype': properties.type.value, 'jid': properties.jid.getBare(), 'resource': properties.jid.getResource(), 'id_': properties.id, 'fjid': str(properties.jid), 'timestamp': properties.timestamp, 'avatar_sha': properties.avatar_sha, 'user_nick': properties.nickname, 'idle_time': properties.idle_timestamp, 'show': show, 'new_show': show, 'old_show': 0, 'status': properties.status, 'contact_list': [], 'contact': None, } event_ = NetworkEvent('presence-received', **event_attrs) # TODO: Refactor self._update_contact(event_, properties) app.nec.push_incoming_event(event_) def _update_contact(self, event, properties): # Note: A similar method also exists in connection_zeroconf jid = properties.jid.getBare() resource = properties.jid.getResource() status_strings = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', 'invisible'] event.new_show = status_strings.index(event.show) # Update contact contact_list = app.contacts.get_contacts(self._account, jid) if not contact_list: log.warning('No contact found') return event.contact_list = contact_list contact = app.contacts.get_contact_strict(self._account, properties.jid.getBare(), properties.jid.getResource()) if contact is None: contact = app.contacts.get_first_contact_from_jid(self._account, jid) if contact is None: log.warning('First contact not found') return if self._is_resource_known(contact_list) and not app.jid_is_transport(jid): # Another resource of an existing contact connected # Add new contact event.old_show = 0 contact = app.contacts.copy_contact(contact) contact.resource = resource app.contacts.add_contact(self._account, contact) else: # Convert the inital roster contact to a contact with resource contact.resource = resource event.old_show = status_strings.index(contact.show) event.need_add_in_roster = True elif contact.show in status_strings: event.old_show = status_strings.index(contact.show) # Update contact with presence data contact.show = event.show contact.status = properties.status contact.priority = properties.priority attached_keys = app.config.get_per('accounts', self._account, 'attached_gpg_keys').split() if jid in attached_keys: contact.keyID = attached_keys[attached_keys.index(jid) + 1] else: # Do not override assigned key contact.keyID = event.keyID contact.idle_time = properties.idle_timestamp event.contact = contact if not app.jid_is_transport(jid) and len(contact_list) == 1: # It's not an agent if event.old_show == 0 and event.new_show > 1: if not jid in app.newly_added[self._account]: app.newly_added[self._account].append(jid) if jid in app.to_be_removed[self._account]: app.to_be_removed[self._account].remove(jid) elif event.old_show > 1 and event.new_show == 0 and \ self._con.connected > 1: if not jid in app.to_be_removed[self._account]: app.to_be_removed[self._account].append(jid) if jid in app.newly_added[self._account]: app.newly_added[self._account].remove(jid) if app.jid_is_transport(jid): return if properties.type.is_unavailable: # TODO: This causes problems when another # resource signs off! self._con.stop_all_active_file_transfers(contact) self._log_presence(properties) @staticmethod def _is_resource_known(contact_list): if len(contact_list) > 1: return True if contact_list[0].resource == '': return False return contact_list[0].show not in ('not in roster', 'offline') def _log_presence(self, properties): if not app.config.get('log_contact_status_changes'): return if not app.config.should_log(self._account, properties.jid.getBare()): return # TODO: Refactor if properties.type.is_unavailable: show = 'offline' else: show = properties.show.value app.logger.insert_into_logs(self._account, properties.jid.getBare(), time.time(), KindConstant.STATUS, message=properties.status, show=show) def _subscribe_received(self, _con, _stanza, properties): jid = properties.jid.getBare() fjid = str(properties.jid) is_transport = app.jid_is_transport(fjid) auto_auth = app.config.get_per('accounts', self._account, 'autoauth') log.info('Received Subscribe: %s, transport: %s, ' 'auto_auth: %s, user_nick: %s', properties.jid, is_transport, auto_auth, properties.nickname) if is_transport and fjid in self._con.agent_registrations: self._con.agent_registrations[fjid]['sub_received'] = True if not self._con.agent_registrations[fjid]['roster_push']: # We'll reply after roster push result raise nbxmpp.NodeProcessed if auto_auth or is_transport or jid in self.jids_for_auto_auth: self.send_presence(fjid, 'subscribed') status = (properties.status or _('I would like to add you to my roster.')) app.nec.push_incoming_event(NetworkEvent( 'subscribe-presence-received', conn=self._con, jid=jid, fjid=fjid, status=status, user_nick=properties.nickname, is_transport=is_transport)) raise nbxmpp.NodeProcessed def _subscribed_received(self, _con, _stanza, properties): jid = properties.jid.getBare() resource = properties.jid.getResource() log.info('Received Subscribed: %s', properties.jid) if jid in self.automatically_added: self.automatically_added.remove(jid) raise nbxmpp.NodeProcessed app.nec.push_incoming_event(NetworkEvent( 'subscribed-presence-received', conn=self._con, jid=jid, resource=resource)) raise nbxmpp.NodeProcessed @staticmethod def _unsubscribe_received(_con, _stanza, properties): log.info('Received Unsubscribe: %s', properties.jid) raise nbxmpp.NodeProcessed def _unsubscribed_received(self, _con, _stanza, properties): log.info('Received Unsubscribed: %s', properties.jid) app.nec.push_incoming_event(NetworkEvent( 'unsubscribed-presence-received', conn=self._con, jid=properties.jid.getBare())) raise nbxmpp.NodeProcessed def subscribed(self, jid): if not app.account_is_connected(self._account): return log.info('Subscribed: %s', jid) self.send_presence(jid, 'subscribed') def unsubscribed(self, jid): if not app.account_is_connected(self._account): return log.info('Unsubscribed: %s', jid) self.send_presence(jid, 'unsubscribed') def unsubscribe(self, jid, remove_auth=True): if not app.account_is_connected(self._account): return if remove_auth: self._con.getRoster().del_item(jid) jid_list = app.config.get_per('contacts') for j in jid_list: if j.startswith(jid): app.config.del_per('contacts', j) else: log.info('Unsubscribe from %s', jid) self._con.getRoster().unsubscribe(jid) self._con.getRoster().set_item(jid) def subscribe(self, jid, msg=None, name='', groups=None, auto_auth=False): if not app.account_is_connected(self._account): return if groups is None: groups = [] log.info('Request Subscription to %s', jid) if auto_auth: self.jids_for_auto_auth.append(jid) infos = {'jid': jid} if name: infos['name'] = name iq = nbxmpp.Iq('set', nbxmpp.NS_ROSTER) query = iq.setQuery() item = query.addChild('item', attrs=infos) for group in groups: item.addChild('group').setData(group) self._con.connection.send(iq) self.send_presence(jid, 'subscribe', status=msg) def get_presence(self, to=None, typ=None, priority=None, show=None, status=None, nick=None, caps=True, sign=None, idle_time=None): presence = nbxmpp.Presence(to, typ, priority, show, status) if nick is not None: nick_tag = presence.setTag('nick', namespace=nbxmpp.NS_NICK) nick_tag.setData(nick) if sign: presence.setTag(nbxmpp.NS_SIGNED + ' x').setData(sign) if idle_time is not None: idle_node = presence.setTag('idle', namespace=nbxmpp.NS_IDLE) idle_node.setAttr('since', idle_time) if not self._con.avatar_conversion: # XEP-0398 not supported by server so # we add the avatar sha to our presence self._con.get_module('VCardAvatars').add_update_node(presence) if caps: attrs = {'hash': 'sha-1', 'node': 'http://gajim.org', 'ver': app.caps_hash[self._account]} presence.setTag('c', namespace=nbxmpp.NS_CAPS, attrs=attrs) return presence def send_presence(self, *args, **kwargs): if not app.account_is_connected(self._account): return presence = self.get_presence(*args, **kwargs) log.debug('Send presence:\n%s', presence) self._con.connection.send(presence) def get_instance(*args, **kwargs): return Presence(*args, **kwargs), 'Presence'