Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits: b5f7007a by lovetox at 2021-02-08T22:51:02+01:00 Presence: Add methods for sending presence
- - - - - 4aa0825d by lovetox at 2021-02-08T23:02:14+01:00 Add Roster module
- - - - -
3 changed files:
- nbxmpp/modules/presence.py - + nbxmpp/modules/roster.py - nbxmpp/structs.py
Changes:
===================================== nbxmpp/modules/presence.py ===================================== @@ -18,6 +18,8 @@ from nbxmpp.protocol import Error as ErrorStanza from nbxmpp.protocol import ERR_BAD_REQUEST from nbxmpp.protocol import NodeProcessed +from nbxmpp.protocol import Presence +from nbxmpp.namespaces import Namespace from nbxmpp.structs import StanzaHandler from nbxmpp.util import error_factory from nbxmpp.const import PresenceType @@ -90,3 +92,62 @@ class BasePresence(BaseModule): self._log.warning('Presence with invalid show') self._log.warning(stanza) return PresenceShow.ONLINE + + def unsubscribe(self, jid): + self.send(jid=jid, typ='unsubscribe') + + def unsubscribed(self, jid): + self.send(jid=jid, typ='unsubscribed') + + def subscribed(self, jid): + self.send(jid=jid, typ='subscribed') + + def subscribe(self, jid, status=None, nick=None): + self.send(jid=jid, typ='subscribe', status=status, nick=nick) + + def send(self, + jid=None, + typ=None, + priority=None, + show=None, + status=None, + nick=None, + caps=None, + idle_time=None, + signed=None, + muc=False, + muc_history=None, + muc_password=None, + extend=None): + + if show is not None and show not in ('chat', 'away', 'xa', 'dnd'): + raise ValueError('Invalid show value: %s' % show) + + presence = Presence(jid, typ, priority, show, status) + if nick is not None: + nick_tag = presence.setTag('nick', namespace=Namespace.NICK) + nick_tag.setData(nick) + + if idle_time is not None: + idle_node = presence.setTag('idle', namespace=Namespace.IDLE) + idle_node.setAttr('since', idle_time) + + if caps is not None and typ != 'unavailable': + presence.setTag('c', namespace=Namespace.CAPS, attrs=caps) + + if signed is not None: + presence.setTag(Namespace.SIGNED + ' x').setData(signed) + + if muc or muc_history is not None or muc_password is not None: + muc_x = presence.setTag(Namespace.MUC + ' x') + if muc_history is not None: + muc_x.setTag('history', muc_history) + + if muc_password is not None: + muc_x.setTagData('password', muc_password) + + if extend is not None: + for node in extend: + presence.addChild(node=node) + + self._con.send_stanza(presence)
===================================== nbxmpp/modules/roster.py ===================================== @@ -0,0 +1,147 @@ +# Copyright (C) 2021 Philipp Hörist <philipp AT hoerist.com> +# +# This file is part of nbxmpp. +# +# 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 nbxmpp.namespaces import Namespace +from nbxmpp.simplexml import Node +from nbxmpp.protocol import Iq +from nbxmpp.protocol import JID +from nbxmpp.protocol import NodeProcessed +from nbxmpp.structs import RosterData +from nbxmpp.structs import RosterItem +from nbxmpp.structs import StanzaHandler +from nbxmpp.errors import StanzaError +from nbxmpp.task import iq_request_task +from nbxmpp.modules.base import BaseModule +from nbxmpp.modules.util import process_response + + +class Roster(BaseModule): + def __init__(self, client): + BaseModule.__init__(self, client) + + self._client = client + self.handlers = [ + # StanzaHandler(name='iq', + # callback=self._process_roster_push, + # typ='set', + # priority=15, + # ns=Namespace.ROSTER), + ] + + @iq_request_task + def request_roster(self, version=None): + _task = yield + + self._log.info('Request Roster, version: %s', version) + response = yield _make_request(version) + if response.isError(): + raise StanzaError(response) + + yield _parse_push(response) + + def _process_roster_push(self, _client, stanza, properties): + from_ = stanza.getFrom() + if from_ is not None: + if not self._con.get_bound_jid().bare == from_: + self._log.warning('Malicious Roster Push from %s', from_) + raise NodeProcessed + + properties.roster = _parse_push(stanza) + + self._log.info('Roster Push, version: %s', properties.roster.version) + for item in properties.roster.items: + self._log.info(item) + + self._ack_roster_push(stanza) + + def _ack_roster_push(self, stanza): + iq = Iq('result', + to=stanza.getFrom(), + frm=stanza.getTo(), + attrs={'id': stanza.getID()}) + self._con.send_stanza(iq) + + @iq_request_task + def delete_item(self, jid): + _task = yield + + response = yield _make_delete(jid) + yield process_response(response) + + @iq_request_task + def set_item(self, jid, name, groups=None): + _task = yield + + response = yield _make_set(jid, name, groups) + yield process_response(response) + + +def _make_delete(jid): + return Iq('set', + Namespace.ROSTER, + payload=[Node('item', {'jid': jid, 'subscription': 'remove'})]) + + +def _make_set(jid, name, groups=None): + if groups is None: + groups = [] + + infos = {'jid': jid} + if name: + infos['name'] = name + iq = Iq('set', Namespace.ROSTER) + query = iq.setQuery() + item = query.addChild('item', attrs=infos) + for group in groups: + item.addChild('group').setData(group) + return iq + + +def _make_request(version): + iq = Iq('get', Namespace.ROSTER) + if version is not None: + iq.setTagAttr('query', 'ver', version) + return iq + + +def _get_item_attrs(item): + default_attrs = {'name': None, + 'ask': None, + 'subscription': None, + 'groups': []} + + attrs = item.getAttrs() + del attrs['jid'] + groups = {group.getData() for group in item.getTags('group')} + attrs['groups'] = list(groups) + + default_attrs.update(attrs) + return default_attrs + + +def _parse_push(stanza): + query = stanza.getTag('query', namespace=Namespace.ROSTER) + version = query.getAttr('ver') + pushed_items = [] + + for item in query.getTags('item'): + jid = JID.from_string(item.getAttr('jid')) + attrs = _get_item_attrs(item) + pushed_items.append(RosterItem(attrs, jid)) + + return RosterData(pushed_items, version)
===================================== nbxmpp/structs.py ===================================== @@ -126,6 +126,9 @@ MAMQueryData = namedtuple('MAMQueryData', 'jid rsm complete')
MAMPreferencesData = namedtuple('MAMPreferencesData', 'default always never')
+RosterData = namedtuple('RosterData', 'items version') +RosterItem = namedtuple('RosterItem', 'data jid') +
class DiscoInfo(namedtuple('DiscoInfo', 'stanza identities features dataforms timestamp')):
@@ -833,6 +836,7 @@ class IqProperties: self.http_auth = None self.ibb = None self.blocking = None + self.roster = None
@property def is_http_auth(self): @@ -846,6 +850,10 @@ class IqProperties: def is_blocking(self): return self.blocking is not None
+ @property + def is_roster(self): + return self.roster is not None +
class PresenceProperties: def __init__(self, own_jid):
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/compare/e280f973cb81d5da8b176db9...