1# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com> 2# 3# This file is part of nbxmpp. 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 3 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; If not, see <http://www.gnu.org/licenses/>. 17 18from nbxmpp.namespaces import Namespace 19from nbxmpp.protocol import Iq 20from nbxmpp.protocol import Node 21from nbxmpp.protocol import JID 22from nbxmpp.structs import AnnotationNote 23from nbxmpp.errors import StanzaError 24from nbxmpp.errors import MalformedStanzaError 25from nbxmpp.task import iq_request_task 26from nbxmpp.modules.base import BaseModule 27from nbxmpp.modules.util import process_response 28from nbxmpp.modules.date_and_time import parse_datetime 29 30 31class Annotations(BaseModule): 32 def __init__(self, client): 33 BaseModule.__init__(self, client) 34 35 self._client = client 36 self.handlers = [] 37 38 @property 39 def domain(self): 40 return self._client.get_bound_jid().domain 41 42 @iq_request_task 43 def request_annotations(self): 44 _task = yield 45 46 response = yield _make_request() 47 if response.isError(): 48 raise StanzaError(response) 49 50 query = response.getQuery() 51 storage = query.getTag('storage', namespace=Namespace.ROSTERNOTES) 52 if storage is None: 53 raise MalformedStanzaError('storage node missing', response) 54 55 notes = [] 56 for note in storage.getTags('note'): 57 try: 58 jid = JID.from_string(note.getAttr('jid')) 59 except Exception as error: 60 self._log.warning('Invalid JID: %s, %s', 61 note.getAttr('jid'), error) 62 continue 63 64 cdate = note.getAttr('cdate') 65 if cdate is not None: 66 cdate = parse_datetime(cdate, epoch=True) 67 68 mdate = note.getAttr('mdate') 69 if mdate is not None: 70 mdate = parse_datetime(mdate, epoch=True) 71 72 data = note.getData() 73 notes.append(AnnotationNote(jid=jid, cdate=cdate, 74 mdate=mdate, data=data)) 75 76 self._log.info('Received annotations from %s:', self.domain) 77 for note in notes: 78 self._log.info(note) 79 yield notes 80 81 @iq_request_task 82 def set_annotations(self, notes): 83 _task = yield 84 85 self._log.info('Set annotations for %s:', self.domain) 86 87 for note in notes: 88 self._log.info(note) 89 90 response = yield _make_set_request(notes) 91 yield process_response(response) 92 93 94def _make_request(): 95 payload = Node('storage', attrs={'xmlns': Namespace.ROSTERNOTES}) 96 return Iq(typ='get', queryNS=Namespace.PRIVATE, payload=payload) 97 98def _make_set_request(notes): 99 storage = Node('storage', attrs={'xmlns': Namespace.ROSTERNOTES}) 100 for note in notes: 101 node = Node('note', attrs={'jid': note.jid}) 102 node.setData(note.data) 103 if note.cdate is not None: 104 node.setAttr('cdate', note.cdate) 105 if note.mdate is not None: 106 node.setAttr('mdate', note.mdate) 107 storage.addChild(node=node) 108 return Iq(typ='set', queryNS=Namespace.PRIVATE, payload=storage) 109