# -*- coding:utf-8 -*- ## src/common/message_archiving.py ## ## Copyright (C) 2009 Anaƫl Verrier ## ## 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 . ## import nbxmpp from common import gajim from common import ged from common import helpers from common.connection_handlers_events import ArchivingReceivedEvent from calendar import timegm from time import localtime import logging log = logging.getLogger('gajim.c.message_archiving') ARCHIVING_COLLECTIONS_ARRIVED = 'archiving_collections_arrived' ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived' ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived' MAM_RESULTS_ARRIVED = 'mam_results_arrived' class ConnectionArchive: def __init__(self): pass class ConnectionArchive313(ConnectionArchive): def __init__(self): ConnectionArchive.__init__(self) self.archiving_313_supported = False self.mam_awaiting_disco_result = {} self.iq_answer = [] gajim.ged.register_event_handler('archiving-finished-legacy', ged.CORE, self._nec_result_finished) gajim.ged.register_event_handler('archiving-finished', ged.CORE, self._nec_result_finished) gajim.ged.register_event_handler('agent-info-error-received', ged.CORE, self._nec_agent_info_error) gajim.ged.register_event_handler('agent-info-received', ged.CORE, self._nec_agent_info) gajim.ged.register_event_handler('mam-decrypted-message-received', ged.CORE, self._nec_mam_decrypted_message_received) gajim.ged.register_event_handler( 'archiving-313-preferences-changed-received', ged.CORE, self._nec_archiving_313_preferences_changed_received) def cleanup(self): gajim.ged.remove_event_handler('archiving-finished-legacy', ged.CORE, self._nec_result_finished) gajim.ged.remove_event_handler('archiving-finished', ged.CORE, self._nec_result_finished) gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE, self._nec_agent_info_error) gajim.ged.remove_event_handler('agent-info-received', ged.CORE, self._nec_agent_info) gajim.ged.remove_event_handler('mam-decrypted-message-received', ged.CORE, self._nec_mam_decrypted_message_received) gajim.ged.remove_event_handler( 'archiving-313-preferences-changed-received', ged.CORE, self._nec_archiving_313_preferences_changed_received) def _nec_archiving_313_preferences_changed_received(self, obj): if obj.id in self.iq_answer: obj.answer = True def _nec_agent_info_error(self, obj): if obj.jid in self.mam_awaiting_disco_result: log.warn('Unable to discover %s, ignoring those logs', obj.jid) del self.mam_awaiting_disco_result[obj.jid] def _nec_agent_info(self, obj): if obj.jid in self.mam_awaiting_disco_result: for identity in obj.identities: if identity['category'] == 'conference': # it's a groupchat for with_, direction, tim, msg_txt, res in \ self.mam_awaiting_disco_result[obj.jid]: gajim.logger.get_jid_id(with_, 'ROOM') gajim.logger.save_if_not_exists(with_, direction, tim, msg=msg_txt, nick=res) del self.mam_awaiting_disco_result[obj.jid] return # it's not a groupchat for with_, direction, tim, msg_txt, res in \ self.mam_awaiting_disco_result[obj.jid]: gajim.logger.get_jid_id(with_) gajim.logger.save_if_not_exists(with_, direction, tim, msg=msg_txt) del self.mam_awaiting_disco_result[obj.jid] def _nec_result_finished(self, obj): if obj.conn.name != self.name: return if obj.queryid not in self.awaiting_answers: return if self.awaiting_answers[obj.queryid][0] == MAM_RESULTS_ARRIVED: set_ = obj.fin.getTag('set', namespace=nbxmpp.NS_RSM) if set_: last = set_.getTagData('last') if last: gajim.config.set_per('accounts', self.name, 'last_mam_id', last) complete = obj.fin.getAttr('complete') if complete != 'true': self.request_archive(after=last) del self.awaiting_answers[obj.queryid] def _nec_mam_decrypted_message_received(self, obj): if obj.conn.name != self.name: return gajim.logger.save_if_not_exists(obj.with_, obj.direction, obj.tim, msg=obj.msgtxt, nick=obj.nick, additional_data=obj.additional_data) def request_archive(self, start=None, end=None, with_=None, after=None, max=30): iq_ = nbxmpp.Iq('set') query = iq_.addChild('query', namespace=self.archiving_namespace) x = query.addChild(node=nbxmpp.DataForm(typ='submit')) x.addChild(node=nbxmpp.DataField(typ='hidden', name='FORM_TYPE', value=self.archiving_namespace)) if start: x.addChild(node=nbxmpp.DataField(typ='text-single', name='start', value=start)) if end: x.addChild(node=nbxmpp.DataField(typ='text-single', name='end', value=end)) if with_: x.addChild(node=nbxmpp.DataField(typ='jid-single', name='with', value=with_)) set_ = query.setTag('set', namespace=nbxmpp.NS_RSM) set_.setTagData('max', max) if after: set_.setTagData('after', after) queryid_ = self.connection.getAnID() query.setAttr('queryid', queryid_) id_ = self.connection.getAnID() iq_.setID(id_) self.awaiting_answers[queryid_] = (MAM_RESULTS_ARRIVED, ) self.connection.send(iq_) def request_archive_preferences(self): if not gajim.account_is_connected(self.name): return iq = nbxmpp.Iq(typ='get') id_ = self.connection.getAnID() iq.setID(id_) iq.addChild(name='prefs', namespace=self.archiving_namespace) self.connection.send(iq) def set_archive_preferences(self, items, default): if not gajim.account_is_connected(self.name): return iq = nbxmpp.Iq(typ='set') id_ = self.connection.getAnID() self.iq_answer.append(id_) iq.setID(id_) prefs = iq.addChild(name='prefs', namespace=self.archiving_namespace, attrs={'default': default}) always = prefs.addChild(name='always') never = prefs.addChild(name='never') for item in items: jid, preference = item if preference == 'always': always.addChild(name='jid').setData(jid) else: never.addChild(name='jid').setData(jid) self.connection.send(iq) class ConnectionArchive136(ConnectionArchive): def __init__(self): ConnectionArchive.__init__(self) self.archiving_136_supported = False self.archive_auto_supported = False self.archive_manage_supported = False self.archive_manual_supported = False self.archive_pref_supported = False self.auto = None self.method_auto = None self.method_local = None self.method_manual = None self.default = None self.items = {} gajim.ged.register_event_handler( 'archiving-preferences-changed-received', ged.CORE, self._nec_archiving_changed_received) gajim.ged.register_event_handler('raw-iq-received', ged.CORE, self._nec_raw_iq_136_received) def cleanup(self): gajim.ged.remove_event_handler( 'archiving-preferences-changed-received', ged.CORE, self._nec_archiving_changed_received) gajim.ged.remove_event_handler('raw-iq-received', ged.CORE, self._nec_raw_iq_136_received) def _nec_raw_iq_136_received(self, obj): if obj.conn.name != self.name: return id_ = obj.stanza.getID() if id_ not in self.awaiting_answers: return if self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED: del self.awaiting_answers[id_] # TODO print('ARCHIVING_COLLECTIONS_ARRIVED') elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED: def save_if_not_exists(with_, nick, direction, tim, payload): assert len(payload) == 1, 'got several archiving messages in' +\ ' the same time %s' % ''.join(payload) if payload[0].getName() == 'body': gajim.logger.save_if_not_exists(with_, direction, tim, msg=payload[0].getData(), nick=nick) elif payload[0].getName() == 'message': print('Not implemented') chat = obj.stanza.getTag('chat') if chat: with_ = chat.getAttr('with') start_ = chat.getAttr('start') tim = helpers.datetime_tuple(start_) tim = timegm(tim) nb = 0 for element in chat.getChildren(): try: secs = int(element.getAttr('secs')) except TypeError: secs = 0 if secs: tim += secs nick = element.getAttr('name') if element.getName() == 'from': save_if_not_exists(with_, nick, 'from', localtime(tim), element.getPayload()) nb += 1 if element.getName() == 'to': save_if_not_exists(with_, nick, 'to', localtime(tim), element.getPayload()) nb += 1 set_ = chat.getTag('set') first = set_.getTag('first') if first: try: index = int(first.getAttr('index')) except TypeError: index = 0 try: count = int(set_.getTagData('count')) except TypeError: count = 0 if count > index + nb: # Request the next page after = element.getTagData('last') self.request_collection_page(with_, start_, after=after) del self.awaiting_answers[id_] elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED: modified = obj.stanza.getTag('modified') if modified: for element in modified.getChildren(): if element.getName() == 'changed': with_ = element.getAttr('with') start_ = element.getAttr('start') self.request_collection_page(with_, start_) #elif element.getName() == 'removed': # do nothing del self.awaiting_answers[id_] def request_message_archiving_preferences(self): iq_ = nbxmpp.Iq('get') iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE) self.connection.send(iq_) def set_pref(self, name, **data): ''' data contains names and values of pref name attributes. ''' iq_ = nbxmpp.Iq('set') pref = iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE) tag = pref.setTag(name) for key, value in data.items(): if value is not None: tag.setAttr(key, value) self.connection.send(iq_) def set_auto(self, save): self.set_pref('auto', save=save) def set_method(self, type, use): self.set_pref('method', type=type, use=use) def set_default(self, otr, save, expire=None): self.set_pref('default', otr=otr, save=save, expire=expire) def append_or_update_item(self, jid, otr, save, expire): self.set_pref('item', jid=jid, otr=otr, save=save) def remove_item(self, jid): iq_ = nbxmpp.Iq('set') itemremove = iq_.setTag('itemremove', namespace=nbxmpp.NS_ARCHIVE) item = itemremove.setTag('item') item.setAttr('jid', jid) self.connection.send(iq_) def stop_archiving_session(self, thread_id): iq_ = nbxmpp.Iq('set') pref = iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE) session = pref.setTag('session', attrs={'thread': thread_id, 'save': 'false', 'otr': 'concede'}) self.connection.send(iq_) def get_item_pref(self, jid): jid = nbxmpp.JID(jid) if str(jid) in self.items: return self.items[jid] if jid.getStripped() in self.items: return self.items[jid.getStripped()] if jid.getDomain() in self.items: return self.items[jid.getDomain()] return self.default def logging_preference(self, jid, initiator_options=None): otr = self.get_item_pref(jid) if not otr: return otr = otr['otr'] if initiator_options: if ((initiator_options == ['mustnot'] and otr == 'forbid') or (initiator_options == ['may'] and otr == 'require')): return None if (initiator_options == ['mustnot'] or (initiator_options[0] == 'mustnot' and otr not in ('opppose', 'forbid')) or (initiator_options == ['may', 'mustnot'] and otr in ('require', 'prefer'))): return 'mustnot' return 'may' if otr == 'require': return ['mustnot'] if otr in ('prefer', 'approve'): return ['mustnot', 'may'] if otr in ('concede', 'oppose'): return ['may', 'mustnot'] # otr == 'forbid' return ['may'] def _ArchiveCB(self, con, iq_obj): log.debug('_ArchiveCB %s' % iq_obj.getType()) gajim.nec.push_incoming_event(ArchivingReceivedEvent(None, conn=self, stanza=iq_obj)) raise nbxmpp.NodeProcessed def _nec_archiving_changed_received(self, obj): if obj.conn.name != self.name: return for key in ('auto', 'default'): if key not in obj.conf: self.archiving_136_supported = False self.archive_auto_supported = False self.archive_manage_supported = False self.archive_manual_supported = False self.archive_pref_supported = False return True for key in ('auto', 'method_auto', 'method_local', 'method_manual', 'default'): if key in obj.conf: self.__dict__[key] = obj.conf[key] for jid, pref in obj.new_items.items(): self.items[jid] = pref for jid in obj.removed_items: del self.items[jid] def request_collections_list_page(self, with_='', start=None, end=None, after=None, max=30, exact_match=False): iq_ = nbxmpp.Iq('get') list_ = iq_.setTag('list', namespace=nbxmpp.NS_ARCHIVE) if with_: list_.setAttr('with', with_) if exact_match: list_.setAttr('exactmatch', 'true') if start: list_.setAttr('start', start) if end: list_.setAttr('end', end) set_ = list_.setTag('set', namespace=nbxmpp.NS_RSM) set_.setTagData('max', max) if after: set_.setTagData('after', after) id_ = self.connection.getAnID() iq_.setID(id_) self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, ) self.connection.send(iq_) def request_collection_page(self, with_, start, end=None, after=None, max=30, exact_match=False): iq_ = nbxmpp.Iq('get') retrieve = iq_.setTag('retrieve', namespace=nbxmpp.NS_ARCHIVE, attrs={'with': with_, 'start': start}) if exact_match: retrieve.setAttr('exactmatch', 'true') set_ = retrieve.setTag('set', namespace=nbxmpp.NS_RSM) set_.setTagData('max', max) if after: set_.setTagData('after', after) id_ = self.connection.getAnID() iq_.setID(id_) self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, ) self.connection.send(iq_) def remove_collection(self, with_='', start=None, end=None, exact_match=False, open=False): iq_ = nbxmpp.Iq('set') remove = iq_.setTag('remove', namespace=nbxmpp.NS_ARCHIVE) if with_: remove.setAttr('with', with_) if exact_match: remove.setAttr('exactmatch', 'true') if start: remove.setAttr('start', start) if end: remove.setAttr('end', end) if open: remove.setAttr('open', 'true') self.connection.send(iq_) def request_modifications_page(self, start, max=30): iq_ = nbxmpp.Iq('get') moified = iq_.setTag('modified', namespace=nbxmpp.NS_ARCHIVE, attrs={'start': start}) set_ = moified.setTag('set', namespace=nbxmpp.NS_RSM) set_.setTagData('max', max) id_ = self.connection.getAnID() iq_.setID(id_) self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, ) self.connection.send(iq_)