diff options
author | lovetox <philipp@hoerist.com> | 2020-10-17 17:17:37 +0300 |
---|---|---|
committer | lovetox <philipp@hoerist.com> | 2020-10-17 21:25:40 +0300 |
commit | 8956f272e065154a2e0a0dc98399ccfa13c42601 (patch) | |
tree | 4db40a16e8978fa8b44d2c3a4f38f58a5c5a64ef /nbxmpp | |
parent | 53bfd2b1f64e3d8ac91710fab87a7f0074e2a151 (diff) |
Bookmarks: Refactor module
- Split into multiple modules
Diffstat (limited to 'nbxmpp')
-rw-r--r-- | nbxmpp/const.py | 6 | ||||
-rw-r--r-- | nbxmpp/dispatcher.py | 8 | ||||
-rw-r--r-- | nbxmpp/modules/bookmarks.py | 380 | ||||
-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 | ||||
-rw-r--r-- | nbxmpp/namespaces.py | 3 | ||||
-rw-r--r-- | nbxmpp/old_dispatcher.py | 1 |
10 files changed, 457 insertions, 390 deletions
diff --git a/nbxmpp/const.py b/nbxmpp/const.py index d37ba64..a2cdfd3 100644 --- a/nbxmpp/const.py +++ b/nbxmpp/const.py @@ -287,12 +287,6 @@ Role._WEIGHTS = { } -class BookmarkStoreType(Enum): - PUBSUB_BOOKMARK_2 = 'pubsub bookmark 2' - PUBSUB_BOOKMARK_1 = 'pubsub bookmark 1' - PRIVATE = 'private' - - class AnonymityMode(Enum): UNKNOWN = None SEMI = 'semi' diff --git a/nbxmpp/dispatcher.py b/nbxmpp/dispatcher.py index f2dc86a..1682980 100644 --- a/nbxmpp/dispatcher.py +++ b/nbxmpp/dispatcher.py @@ -56,7 +56,9 @@ from nbxmpp.modules.tune import Tune from nbxmpp.modules.mood import Mood from nbxmpp.modules.location import Location from nbxmpp.modules.user_avatar import UserAvatar -from nbxmpp.modules.bookmarks import Bookmarks +from nbxmpp.modules.bookmarks.private_bookmarks import PrivateBookmarks +from nbxmpp.modules.bookmarks.pep_bookmarks import PEPBookmarks +from nbxmpp.modules.bookmarks.native_bookmarks import NativeBookmarks from nbxmpp.modules.openpgp import OpenPGP from nbxmpp.modules.omemo import OMEMO from nbxmpp.modules.annotations import Annotations @@ -163,7 +165,9 @@ class StanzaDispatcher(Observable): self._modules['Tune'] = Tune(self._client) self._modules['Location'] = Location(self._client) self._modules['UserAvatar'] = UserAvatar(self._client) - self._modules['Bookmarks'] = Bookmarks(self._client) + self._modules['PrivateBookmarks'] = PrivateBookmarks(self._client) + self._modules['PEPBookmarks'] = PEPBookmarks(self._client) + self._modules['NativeBookmarks'] = NativeBookmarks(self._client) self._modules['OpenPGP'] = OpenPGP(self._client) self._modules['OMEMO'] = OMEMO(self._client) self._modules['Annotations'] = Annotations(self._client) diff --git a/nbxmpp/modules/bookmarks.py b/nbxmpp/modules/bookmarks.py deleted file mode 100644 index fa32b28..0000000 --- a/nbxmpp/modules/bookmarks.py +++ /dev/null @@ -1,380 +0,0 @@ -# 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 isResultNode -from nbxmpp.protocol import Node -from nbxmpp.protocol import Iq -from nbxmpp.protocol import JID -from nbxmpp.protocol import NodeProcessed -from nbxmpp.protocol import validate_resourcepart -from nbxmpp.structs import StanzaHandler -from nbxmpp.structs import BookmarkData -from nbxmpp.const import BookmarkStoreType -from nbxmpp.util import from_xs_boolean -from nbxmpp.util import to_xs_boolean -from nbxmpp.util import call_on_response -from nbxmpp.util import callback -from nbxmpp.util import raise_error -from nbxmpp.modules.pubsub import get_pubsub_item -from nbxmpp.modules.pubsub import get_pubsub_items -from nbxmpp.modules.pubsub import get_pubsub_request -from nbxmpp.modules.base import BaseModule - - -BOOKMARK_1_OPTIONS = { - 'pubsub#persist_items': 'true', - 'pubsub#access_model': 'whitelist', -} - -BOOKMARK_2_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 Bookmarks(BaseModule): - - _depends = { - 'retract': 'PubSub', - 'publish': '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), - StanzaHandler(name='message', - callback=self._process_pubsub_bookmarks2, - ns=Namespace.PUBSUB_EVENT, - priority=16), - ] - - self._bookmark_2_queue = {} - self._bookmark_1_queue = [] - - 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 - - storage_node = item.getTag('storage', namespace=Namespace.BOOKMARKS) - if storage_node is None: - self._log.warning('No storage node found') - self._log.warning(stanza) - raise NodeProcessed - - bookmarks = self._parse_bookmarks(storage_node) - 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 - - def _process_pubsub_bookmarks2(self, _client, _stanza, properties): - if not properties.is_pubsub_event: - return - - if properties.pubsub_event.node != Namespace.BOOKMARKS_2: - return - - item = properties.pubsub_event.item - if item is None: - # Retract, Deleted or Purged - return - - bookmark_item = self._parse_bookmarks2(item) - if bookmark_item is None: - 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 - - def _parse_bookmarks(self, storage): - bookmarks = [] - confs = storage.getTags('conference') - for conf in confs: - try: - jid = JID.from_string(conf.getAttr('jid')) - except Exception as error: - self._log.warning('Invalid JID: "%s", %s', - conf.getAttr('jid'), - error) - continue - - if jid.localpart is None or jid.resource is not None: - self._log.warning('Invalid JID: "%s", %s', - conf.getAttr('jid'), - error) - continue - - autojoin = self._parse_autojoin(conf.getAttr('autojoin')) - nick = self._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 _parse_bookmarks2(self, item): - conference = item.getTag('conference', namespace=Namespace.BOOKMARKS_2) - if conference is None: - self._log.warning('No conference node found') - self._log.warning(item) - return None - - try: - jid = JID.from_string(item.getAttr('id')) - except Exception as error: - self._log.warning('Invalid JID: "%s", %s', - item.getAttr('id'), - error) - return None - - if jid.localpart is None or jid.resource is not None: - self._log.warning('Invalid JID: "%s", %s', - item.getAttr('id'), - error) - return None - - autojoin = self._parse_autojoin(conference.getAttr('autojoin')) - nick = self._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_nickname(self, nick): - if nick is None: - return None - - try: - return validate_resourcepart(nick) - except Exception as error: - self._log.warning('Invalid nick: %s, %s', nick, error) - return None - - def _parse_autojoin(self, autojoin): - if autojoin is None: - return False - - try: - return from_xs_boolean(autojoin) - except ValueError as error: - self._log.warning('Invalid autojoin attribute: (%s) %s', - autojoin, error) - return False - - @staticmethod - 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 - - @call_on_response('_bookmarks_received') - def request_bookmarks(self, type_): - jid = self._client.get_bound_jid().bare - if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2: - self._log.info('Request bookmarks 2 (PubSub)') - request = get_pubsub_request(jid, Namespace.BOOKMARKS_2) - return request, {'type_': type_} - - if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_1: - self._log.info('Request bookmarks (PubSub)') - request = get_pubsub_request(jid, Namespace.BOOKMARKS, max_items=1) - return request, {'type_': type_} - - if type_ == BookmarkStoreType.PRIVATE: - self._log.info('Request bookmarks (Private Storage)') - return self.get_private_request(), {'type_': type_} - return None - - @callback - def _bookmarks_received(self, stanza, type_): - if not isResultNode(stanza): - return raise_error(self._log.info, stanza) - - bookmarks = [] - if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2: - items = get_pubsub_items(stanza, Namespace.BOOKMARKS_2) - if items is None: - return raise_error(self._log.warning, - stanza, - 'stanza-malformed') - - for item in items: - bookmark_item = self._parse_bookmarks2(item) - if bookmark_item is not None: - bookmarks.append(bookmark_item) - - elif type_ == BookmarkStoreType.PUBSUB_BOOKMARK_1: - item = get_pubsub_item(stanza) - if item is not None: - storage_node = item.getTag('storage', - namespace=Namespace.BOOKMARKS) - if storage_node is None: - return raise_error(self._log.warning, - stanza, - 'stanza-malformed', - 'No storage node') - - if storage_node.getChildren(): - bookmarks = self._parse_bookmarks(storage_node) - - elif type_ == BookmarkStoreType.PRIVATE: - query = stanza.getQuery() - storage_node = query.getTag('storage', - namespace=Namespace.BOOKMARKS) - if storage_node is None: - return raise_error(self._log.warning, - stanza, - 'stanza-malformed', - 'No storage node') - - if storage_node.getChildren(): - bookmarks = self._parse_bookmarks(storage_node) - - from_ = stanza.getFrom() - if from_ is None: - from_ = self._client.get_bound_jid().bare - self._log.info('Received bookmarks from: %s', from_) - for bookmark in bookmarks: - self._log.info(bookmark) - return bookmarks - - @staticmethod - 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 - - @staticmethod - def _build_conference_node(bookmark): - attrs = {'xmlns': Namespace.BOOKMARKS_2} - 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 store_bookmarks(self, bookmarks, type_): - if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2: - self._store_bookmark_2(bookmarks) - elif type_ == BookmarkStoreType.PUBSUB_BOOKMARK_1: - self._store_bookmark_1(bookmarks) - elif type_ == BookmarkStoreType.PRIVATE: - self._store_with_private(bookmarks) - - def retract_bookmark(self, bookmark_jid): - self._log.info('Retract Bookmark: %s', bookmark_jid) - - self.retract(Namespace.BOOKMARKS_2, str(bookmark_jid)) - - def _store_bookmark_1(self, bookmarks): - self._log.info('Store Bookmarks 1 (PubSub)') - - self._bookmark_1_queue = bookmarks - item = self._build_storage_node(bookmarks) - self.publish(Namespace.BOOKMARKS, - item, - id_='current', - options=BOOKMARK_1_OPTIONS, - force_node_options=True, - callback=self._on_store_bookmark_result, - user_data=Namespace.BOOKMARKS) - - def _store_bookmark_2(self, bookmarks): - self._log.info('Store Bookmarks 2 (PubSub)') - - for bookmark in bookmarks: - self._bookmark_2_queue[bookmark.jid] = bookmark - item = self._build_conference_node(bookmark) - self.publish(Namespace.BOOKMARKS_2, - item, - id_=str(bookmark.jid), - options=BOOKMARK_2_OPTIONS, - force_node_options=True, - callback=self._on_store_bookmark_result, - user_data=Namespace.BOOKMARKS_2) - - def _on_store_bookmark_result(self, task): - try: - result = task.finish() - except Exception as error: - self._log.warning(error) - - self._bookmark_1_queue = [] - self._bookmark_2_queue.pop(result.id, None) - - @call_on_response('_on_private_store_result') - def _store_with_private(self, bookmarks): - self._log.info('Store Bookmarks (Private Storage)') - storage_node = self._build_storage_node(bookmarks) - return Iq('set', Namespace.PRIVATE, payload=storage_node) - - def _on_private_store_result(self, _client, stanza): - if not isResultNode(stanza): - return raise_error(self._log.info, stanza) - return None 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 diff --git a/nbxmpp/namespaces.py b/nbxmpp/namespaces.py index bdd6262..d874822 100644 --- a/nbxmpp/namespaces.py +++ b/nbxmpp/namespaces.py @@ -34,8 +34,9 @@ class _Namespaces: BLOCKING: str = 'urn:xmpp:blocking' BOB: str = 'urn:xmpp:bob' BOOKMARKS: str = 'storage:bookmarks' - BOOKMARKS_2: str = 'urn:xmpp:bookmarks:0' + BOOKMARKS_1: str = 'urn:xmpp:bookmarks:1' BOOKMARKS_COMPAT: str = 'urn:xmpp:bookmarks:0#compat' + BOOKMARKS_COMPAT_PEP: str = 'urn:xmpp:bookmarks:1#compat-pep' BOOKMARK_CONVERSION: str = 'urn:xmpp:bookmarks-conversion:0' BROWSE: str = 'jabber:iq:browse' BYTESTREAM: str = 'http://jabber.org/protocol/bytestreams' diff --git a/nbxmpp/old_dispatcher.py b/nbxmpp/old_dispatcher.py index 5036052..2c7c2c1 100644 --- a/nbxmpp/old_dispatcher.py +++ b/nbxmpp/old_dispatcher.py @@ -60,7 +60,6 @@ from nbxmpp.modules.tune import Tune from nbxmpp.modules.mood import Mood from nbxmpp.modules.location import Location from nbxmpp.modules.user_avatar import UserAvatar -from nbxmpp.modules.bookmarks import Bookmarks from nbxmpp.modules.openpgp import OpenPGP from nbxmpp.modules.omemo import OMEMO from nbxmpp.modules.annotations import Annotations |