diff options
Diffstat (limited to 'nbxmpp/modules/bookmarks')
-rw-r--r-- | nbxmpp/modules/bookmarks/__init__.py | 0 | ||||
-rw-r--r-- | nbxmpp/modules/bookmarks/native_bookmarks.py | 129 | ||||
-rw-r--r-- | nbxmpp/modules/bookmarks/pep_bookmarks.py | 109 | ||||
-rw-r--r-- | nbxmpp/modules/bookmarks/private_bookmarks.py | 59 | ||||
-rw-r--r-- | nbxmpp/modules/bookmarks/util.py | 152 |
5 files changed, 449 insertions, 0 deletions
diff --git a/nbxmpp/modules/bookmarks/__init__.py b/nbxmpp/modules/bookmarks/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/nbxmpp/modules/bookmarks/__init__.py diff --git a/nbxmpp/modules/bookmarks/native_bookmarks.py b/nbxmpp/modules/bookmarks/native_bookmarks.py new file mode 100644 index 0000000..16da9e9 --- /dev/null +++ b/nbxmpp/modules/bookmarks/native_bookmarks.py @@ -0,0 +1,129 @@ +# Copyright (C) 2018 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.protocol import NodeProcessed +from nbxmpp.structs import StanzaHandler +from nbxmpp.task import iq_request_task +from nbxmpp.errors import MalformedStanzaError +from nbxmpp.modules.base import BaseModule +from nbxmpp.modules.util import raise_if_error +from nbxmpp.modules.util import finalize +from nbxmpp.modules.bookmarks.util import parse_bookmark +from nbxmpp.modules.bookmarks.util import build_conference_node + + +BOOKMARK_OPTIONS = { + 'pubsub#notify_delete': 'true', + 'pubsub#notify_retract': 'true', + 'pubsub#persist_items': 'true', + 'pubsub#max_items': 'max', + 'pubsub#access_model': 'whitelist', + 'pubsub#send_last_published_item': 'never', +} + + +class NativeBookmarks(BaseModule): + + _depends = { + 'retract': 'PubSub', + 'publish': 'PubSub', + 'request_items': 'PubSub', + } + + def __init__(self, client): + BaseModule.__init__(self, client) + + self._client = client + self.handlers = [ + StanzaHandler(name='message', + callback=self._process_pubsub_bookmarks, + ns=Namespace.PUBSUB_EVENT, + priority=16), + ] + + def _process_pubsub_bookmarks(self, _client, _stanza, properties): + if not properties.is_pubsub_event: + return + + if properties.pubsub_event.node != Namespace.BOOKMARKS_1: + return + + item = properties.pubsub_event.item + if item is None: + # Retract, Deleted or Purged + return + + try: + bookmark_item = parse_bookmark(item) + except MalformedStanzaError as error: + self._log.warning(error) + self._log.warning(error.stanza) + raise NodeProcessed + + pubsub_event = properties.pubsub_event._replace(data=bookmark_item) + self._log.info('Received bookmark item from: %s', properties.jid) + self._log.info(bookmark_item) + + properties.pubsub_event = pubsub_event + + @iq_request_task + def request_bookmarks(self): + _task = yield + + items = yield self.request_items(Namespace.BOOKMARKS_1) + raise_if_error(items) + + bookmarks = [] + for item in items: + try: + bookmark_item = self.parse_bookmark(item) + except MalformedStanzaError as error: + self._log.warning(error) + self._log.warning(error.stanza) + continue + + bookmarks.append(bookmark_item) + + for bookmark in bookmarks: + self._log.info(bookmark) + + yield bookmarks + + @iq_request_task + def retract_bookmark(self, bookmark_jid): + task = yield + + self._log.info('Retract Bookmark: %s', bookmark_jid) + + result = yield self.retract(Namespace.BOOKMARKS_1, str(bookmark_jid)) + yield finalize(task, result) + + @iq_request_task + def store_bookmarks(self, bookmarks): + _task = yield + + self._log.info('Store Bookmarks') + + for bookmark in bookmarks: + self.publish(Namespace.BOOKMARKS_1, + build_conference_node(bookmark), + id_=str(bookmark.jid), + options=BOOKMARK_OPTIONS, + force_node_options=True) + + yield True diff --git a/nbxmpp/modules/bookmarks/pep_bookmarks.py b/nbxmpp/modules/bookmarks/pep_bookmarks.py new file mode 100644 index 0000000..508843d --- /dev/null +++ b/nbxmpp/modules/bookmarks/pep_bookmarks.py @@ -0,0 +1,109 @@ +# Copyright (C) 2018 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.protocol import NodeProcessed +from nbxmpp.structs import StanzaHandler +from nbxmpp.task import iq_request_task +from nbxmpp.errors import MalformedStanzaError +from nbxmpp.modules.base import BaseModule +from nbxmpp.modules.util import raise_if_error +from nbxmpp.modules.bookmarks.util import parse_bookmarks +from nbxmpp.modules.bookmarks.util import build_storage_node + + +BOOKMARK_OPTIONS = { + 'pubsub#persist_items': 'true', + 'pubsub#access_model': 'whitelist', +} + + +class PEPBookmarks(BaseModule): + + _depends = { + 'publish': 'PubSub', + 'request_items': 'PubSub', + } + + def __init__(self, client): + BaseModule.__init__(self, client) + + self._client = client + self.handlers = [ + StanzaHandler(name='message', + callback=self._process_pubsub_bookmarks, + ns=Namespace.PUBSUB_EVENT, + priority=16), + ] + + def _process_pubsub_bookmarks(self, _client, stanza, properties): + if not properties.is_pubsub_event: + return + + if properties.pubsub_event.node != Namespace.BOOKMARKS: + return + + item = properties.pubsub_event.item + if item is None: + # Retract, Deleted or Purged + return + + try: + bookmarks = parse_bookmarks(item, self._log) + except MalformedStanzaError as error: + self._log.warning(error) + self._log.warning(stanza) + raise NodeProcessed + + if not bookmarks: + self._log.info('Bookmarks removed') + return + + pubsub_event = properties.pubsub_event._replace(data=bookmarks) + self._log.info('Received bookmarks from: %s', properties.jid) + for bookmark in bookmarks: + self._log.info(bookmark) + + properties.pubsub_event = pubsub_event + + @iq_request_task + def request_bookmarks(self): + _task = yield + + items = yield self.request_items(Namespace.BOOKMARKS, max_items=1) + raise_if_error(items) + + if not items: + yield [] + + bookmarks = parse_bookmarks(items[0], self._log) + for bookmark in bookmarks: + self._log.info(bookmark) + + yield bookmarks + + @iq_request_task + def store_bookmarks(self, bookmarks): + _task = yield + + self._log.info('Store Bookmarks') + + self.publish(Namespace.BOOKMARKS, + build_storage_node(bookmarks), + id_='current', + options=BOOKMARK_OPTIONS, + force_node_options=True) diff --git a/nbxmpp/modules/bookmarks/private_bookmarks.py b/nbxmpp/modules/bookmarks/private_bookmarks.py new file mode 100644 index 0000000..88d8a62 --- /dev/null +++ b/nbxmpp/modules/bookmarks/private_bookmarks.py @@ -0,0 +1,59 @@ +# Copyright (C) 2018 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.protocol import Iq +from nbxmpp.task import iq_request_task +from nbxmpp.modules.util import raise_if_error +from nbxmpp.modules.util import finalize +from nbxmpp.modules.base import BaseModule +from nbxmpp.modules.bookmarks.util import build_storage_node +from nbxmpp.modules.bookmarks.util import get_private_request +from nbxmpp.modules.bookmarks.util import parse_private_bookmarks + + + +class PrivateBookmarks(BaseModule): + def __init__(self, client): + BaseModule.__init__(self, client) + + self._client = client + self.handlers = [] + + @iq_request_task + def request_bookmarks(self): + _task = yield + + response = yield get_private_request() + raise_if_error(response) + + + bookmarks = parse_private_bookmarks(response, self._log) + for bookmark in bookmarks: + self._log.info(bookmark) + + yield bookmarks + + @iq_request_task + def store_bookmarks(self, bookmarks): + task = yield + + self._log.info('Store Bookmarks') + + storage_node = build_storage_node(bookmarks) + result = yield Iq('set', Namespace.PRIVATE, payload=storage_node) + yield finalize(task, result) diff --git a/nbxmpp/modules/bookmarks/util.py b/nbxmpp/modules/bookmarks/util.py new file mode 100644 index 0000000..2672131 --- /dev/null +++ b/nbxmpp/modules/bookmarks/util.py @@ -0,0 +1,152 @@ +# Copyright (C) 2020 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.protocol import Node +from nbxmpp.protocol import validate_resourcepart +from nbxmpp.protocol import JID +from nbxmpp.protocol import Iq +from nbxmpp.util import from_xs_boolean +from nbxmpp.util import to_xs_boolean +from nbxmpp.namespaces import Namespace +from nbxmpp.errors import MalformedStanzaError +from nbxmpp.structs import BookmarkData + + +def parse_nickname(nick): + if nick is None: + return None + + try: + return validate_resourcepart(nick) + except Exception: + return None + + +def parse_autojoin(autojoin): + if autojoin is None: + return False + + try: + return from_xs_boolean(autojoin) + except ValueError: + return False + + +def parse_bookmark(item): + conference = item.getTag('conference', namespace=Namespace.BOOKMARKS_1) + if conference is None: + raise MalformedStanzaError('conference node missing', item) + + try: + jid = JID.from_string(item.getAttr('id')) + except Exception as error: + raise MalformedStanzaError('invalid jid: %s' % error, item) + + if jid.localpart is None or jid.resource is not None: + raise MalformedStanzaError('invalid jid', item) + + autojoin = parse_autojoin(conference.getAttr('autojoin')) + nick = parse_nickname(conference.getTagData('nick')) + name = conference.getAttr('name') or None + password = conference.getTagData('password') or None + + return BookmarkData(jid=jid, + name=name, + autojoin=autojoin, + password=password, + nick=nick) + + +def parse_bookmarks(item, log): + storage_node = item.getTag('storage', namespace=Namespace.BOOKMARKS) + if storage_node is None: + raise MalformedStanzaError('storage node missing', item) + + return parse_storage_node(storage_node, log) + + +def parse_private_bookmarks(response, log): + query = response.getQuery() + storage_node = query.getTag('storage', namespace=Namespace.BOOKMARKS) + if storage_node is None: + raise MalformedStanzaError('storage node missing', response) + + return parse_storage_node(storage_node, log) + + +def parse_storage_node(storage, log): + bookmarks = [] + confs = storage.getTags('conference') + for conf in confs: + try: + jid = JID.from_string(conf.getAttr('jid')) + except Exception: + log.warning('invalid jid: %s', conf) + continue + + if jid.localpart is None or jid.resource is not None: + log.warning('invalid jid: %s', conf) + continue + + autojoin = parse_autojoin(conf.getAttr('autojoin')) + nick = parse_nickname(conf.getTagData('nick')) + name = conf.getAttr('name') or None + password = conf.getTagData('password') or None + + bookmark = BookmarkData( + jid=jid, + name=name, + autojoin=autojoin, + password=password, + nick=nick) + bookmarks.append(bookmark) + + return bookmarks + + +def build_conference_node(bookmark): + attrs = {'xmlns': Namespace.BOOKMARKS_1} + if bookmark.autojoin: + attrs['autojoin'] = 'true' + if bookmark.name: + attrs['name'] = bookmark.name + conference = Node(tag='conference', attrs=attrs) + if bookmark.nick: + conference.setTagData('nick', bookmark.nick) + return conference + + +def build_storage_node(bookmarks): + storage_node = Node(tag='storage', attrs={'xmlns': Namespace.BOOKMARKS}) + for bookmark in bookmarks: + conf_node = storage_node.addChild(name="conference") + conf_node.setAttr('jid', bookmark.jid) + conf_node.setAttr('autojoin', to_xs_boolean(bookmark.autojoin)) + if bookmark.name: + conf_node.setAttr('name', bookmark.name) + if bookmark.nick: + conf_node.setTagData('nick', bookmark.nick) + if bookmark.password: + conf_node.setTagData('password', bookmark.password) + return storage_node + + +def get_private_request(): + iq = Iq(typ='get') + query = iq.addChild(name='query', namespace=Namespace.PRIVATE) + query.addChild(name='storage', namespace=Namespace.BOOKMARKS) + return iq |