Philipp Hörist pushed to branch master at gajim / python-nbxmpp

Commits:

10 changed files:

Changes:

  • nbxmpp/const.py
    ... ... @@ -287,12 +287,6 @@ Role._WEIGHTS = {
    287 287
     }
    
    288 288
     
    
    289 289
     
    
    290
    -class BookmarkStoreType(Enum):
    
    291
    -    PUBSUB_BOOKMARK_2 = 'pubsub bookmark 2'
    
    292
    -    PUBSUB_BOOKMARK_1 = 'pubsub bookmark 1'
    
    293
    -    PRIVATE = 'private'
    
    294
    -
    
    295
    -
    
    296 290
     class AnonymityMode(Enum):
    
    297 291
         UNKNOWN = None
    
    298 292
         SEMI = 'semi'
    

  • nbxmpp/dispatcher.py
    ... ... @@ -56,7 +56,9 @@ from nbxmpp.modules.tune import Tune
    56 56
     from nbxmpp.modules.mood import Mood
    
    57 57
     from nbxmpp.modules.location import Location
    
    58 58
     from nbxmpp.modules.user_avatar import UserAvatar
    
    59
    -from nbxmpp.modules.bookmarks import Bookmarks
    
    59
    +from nbxmpp.modules.bookmarks.private_bookmarks import PrivateBookmarks
    
    60
    +from nbxmpp.modules.bookmarks.pep_bookmarks import PEPBookmarks
    
    61
    +from nbxmpp.modules.bookmarks.native_bookmarks import NativeBookmarks
    
    60 62
     from nbxmpp.modules.openpgp import OpenPGP
    
    61 63
     from nbxmpp.modules.omemo import OMEMO
    
    62 64
     from nbxmpp.modules.annotations import Annotations
    
    ... ... @@ -163,7 +165,9 @@ class StanzaDispatcher(Observable):
    163 165
             self._modules['Tune'] = Tune(self._client)
    
    164 166
             self._modules['Location'] = Location(self._client)
    
    165 167
             self._modules['UserAvatar'] = UserAvatar(self._client)
    
    166
    -        self._modules['Bookmarks'] = Bookmarks(self._client)
    
    168
    +        self._modules['PrivateBookmarks'] = PrivateBookmarks(self._client)
    
    169
    +        self._modules['PEPBookmarks'] = PEPBookmarks(self._client)
    
    170
    +        self._modules['NativeBookmarks'] = NativeBookmarks(self._client)
    
    167 171
             self._modules['OpenPGP'] = OpenPGP(self._client)
    
    168 172
             self._modules['OMEMO'] = OMEMO(self._client)
    
    169 173
             self._modules['Annotations'] = Annotations(self._client)
    

  • nbxmpp/modules/bookmarks.py deleted
    1
    -# Copyright (C) 2018 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
    -
    
    18
    -from nbxmpp.namespaces import Namespace
    
    19
    -from nbxmpp.protocol import isResultNode
    
    20
    -from nbxmpp.protocol import Node
    
    21
    -from nbxmpp.protocol import Iq
    
    22
    -from nbxmpp.protocol import JID
    
    23
    -from nbxmpp.protocol import NodeProcessed
    
    24
    -from nbxmpp.protocol import validate_resourcepart
    
    25
    -from nbxmpp.structs import StanzaHandler
    
    26
    -from nbxmpp.structs import BookmarkData
    
    27
    -from nbxmpp.const import BookmarkStoreType
    
    28
    -from nbxmpp.util import from_xs_boolean
    
    29
    -from nbxmpp.util import to_xs_boolean
    
    30
    -from nbxmpp.util import call_on_response
    
    31
    -from nbxmpp.util import callback
    
    32
    -from nbxmpp.util import raise_error
    
    33
    -from nbxmpp.modules.pubsub import get_pubsub_item
    
    34
    -from nbxmpp.modules.pubsub import get_pubsub_items
    
    35
    -from nbxmpp.modules.pubsub import get_pubsub_request
    
    36
    -from nbxmpp.modules.base import BaseModule
    
    37
    -
    
    38
    -
    
    39
    -BOOKMARK_1_OPTIONS = {
    
    40
    -    'pubsub#persist_items': 'true',
    
    41
    -    'pubsub#access_model': 'whitelist',
    
    42
    -}
    
    43
    -
    
    44
    -BOOKMARK_2_OPTIONS = {
    
    45
    -    'pubsub#notify_delete': 'true',
    
    46
    -    'pubsub#notify_retract': 'true',
    
    47
    -    'pubsub#persist_items': 'true',
    
    48
    -    'pubsub#max_items': 'max',
    
    49
    -    'pubsub#access_model': 'whitelist',
    
    50
    -    'pubsub#send_last_published_item': 'never',
    
    51
    -}
    
    52
    -
    
    53
    -
    
    54
    -class Bookmarks(BaseModule):
    
    55
    -
    
    56
    -    _depends = {
    
    57
    -        'retract': 'PubSub',
    
    58
    -        'publish': 'PubSub',
    
    59
    -    }
    
    60
    -
    
    61
    -    def __init__(self, client):
    
    62
    -        BaseModule.__init__(self, client)
    
    63
    -
    
    64
    -        self._client = client
    
    65
    -        self.handlers = [
    
    66
    -            StanzaHandler(name='message',
    
    67
    -                          callback=self._process_pubsub_bookmarks,
    
    68
    -                          ns=Namespace.PUBSUB_EVENT,
    
    69
    -                          priority=16),
    
    70
    -            StanzaHandler(name='message',
    
    71
    -                          callback=self._process_pubsub_bookmarks2,
    
    72
    -                          ns=Namespace.PUBSUB_EVENT,
    
    73
    -                          priority=16),
    
    74
    -        ]
    
    75
    -
    
    76
    -        self._bookmark_2_queue = {}
    
    77
    -        self._bookmark_1_queue = []
    
    78
    -
    
    79
    -    def _process_pubsub_bookmarks(self, _client, stanza, properties):
    
    80
    -        if not properties.is_pubsub_event:
    
    81
    -            return
    
    82
    -
    
    83
    -        if properties.pubsub_event.node != Namespace.BOOKMARKS:
    
    84
    -            return
    
    85
    -
    
    86
    -        item = properties.pubsub_event.item
    
    87
    -        if item is None:
    
    88
    -            # Retract, Deleted or Purged
    
    89
    -            return
    
    90
    -
    
    91
    -        storage_node = item.getTag('storage', namespace=Namespace.BOOKMARKS)
    
    92
    -        if storage_node is None:
    
    93
    -            self._log.warning('No storage node found')
    
    94
    -            self._log.warning(stanza)
    
    95
    -            raise NodeProcessed
    
    96
    -
    
    97
    -        bookmarks = self._parse_bookmarks(storage_node)
    
    98
    -        if not bookmarks:
    
    99
    -            self._log.info('Bookmarks removed')
    
    100
    -            return
    
    101
    -
    
    102
    -        pubsub_event = properties.pubsub_event._replace(data=bookmarks)
    
    103
    -        self._log.info('Received bookmarks from: %s', properties.jid)
    
    104
    -        for bookmark in bookmarks:
    
    105
    -            self._log.info(bookmark)
    
    106
    -
    
    107
    -        properties.pubsub_event = pubsub_event
    
    108
    -
    
    109
    -    def _process_pubsub_bookmarks2(self, _client, _stanza, properties):
    
    110
    -        if not properties.is_pubsub_event:
    
    111
    -            return
    
    112
    -
    
    113
    -        if properties.pubsub_event.node != Namespace.BOOKMARKS_2:
    
    114
    -            return
    
    115
    -
    
    116
    -        item = properties.pubsub_event.item
    
    117
    -        if item is None:
    
    118
    -            # Retract, Deleted or Purged
    
    119
    -            return
    
    120
    -
    
    121
    -        bookmark_item = self._parse_bookmarks2(item)
    
    122
    -        if bookmark_item is None:
    
    123
    -            raise NodeProcessed
    
    124
    -
    
    125
    -        pubsub_event = properties.pubsub_event._replace(data=bookmark_item)
    
    126
    -        self._log.info('Received bookmark item from: %s', properties.jid)
    
    127
    -        self._log.info(bookmark_item)
    
    128
    -
    
    129
    -        properties.pubsub_event = pubsub_event
    
    130
    -
    
    131
    -    def _parse_bookmarks(self, storage):
    
    132
    -        bookmarks = []
    
    133
    -        confs = storage.getTags('conference')
    
    134
    -        for conf in confs:
    
    135
    -            try:
    
    136
    -                jid = JID.from_string(conf.getAttr('jid'))
    
    137
    -            except Exception as error:
    
    138
    -                self._log.warning('Invalid JID: "%s", %s',
    
    139
    -                                  conf.getAttr('jid'),
    
    140
    -                                  error)
    
    141
    -                continue
    
    142
    -
    
    143
    -            if jid.localpart is None or jid.resource is not None:
    
    144
    -                self._log.warning('Invalid JID: "%s", %s',
    
    145
    -                                  conf.getAttr('jid'),
    
    146
    -                                  error)
    
    147
    -                continue
    
    148
    -
    
    149
    -            autojoin = self._parse_autojoin(conf.getAttr('autojoin'))
    
    150
    -            nick = self._parse_nickname(conf.getTagData('nick'))
    
    151
    -            name = conf.getAttr('name') or None
    
    152
    -            password = conf.getTagData('password') or None
    
    153
    -
    
    154
    -            bookmark = BookmarkData(
    
    155
    -                jid=jid,
    
    156
    -                name=name,
    
    157
    -                autojoin=autojoin,
    
    158
    -                password=password,
    
    159
    -                nick=nick)
    
    160
    -            bookmarks.append(bookmark)
    
    161
    -
    
    162
    -        return bookmarks
    
    163
    -
    
    164
    -    def _parse_bookmarks2(self, item):
    
    165
    -        conference = item.getTag('conference', namespace=Namespace.BOOKMARKS_2)
    
    166
    -        if conference is None:
    
    167
    -            self._log.warning('No conference node found')
    
    168
    -            self._log.warning(item)
    
    169
    -            return None
    
    170
    -
    
    171
    -        try:
    
    172
    -            jid = JID.from_string(item.getAttr('id'))
    
    173
    -        except Exception as error:
    
    174
    -            self._log.warning('Invalid JID: "%s", %s',
    
    175
    -                              item.getAttr('id'),
    
    176
    -                              error)
    
    177
    -            return None
    
    178
    -
    
    179
    -        if jid.localpart is None or jid.resource is not None:
    
    180
    -            self._log.warning('Invalid JID: "%s", %s',
    
    181
    -                              item.getAttr('id'),
    
    182
    -                              error)
    
    183
    -            return None
    
    184
    -
    
    185
    -        autojoin = self._parse_autojoin(conference.getAttr('autojoin'))
    
    186
    -        nick = self._parse_nickname(conference.getTagData('nick'))
    
    187
    -        name = conference.getAttr('name') or None
    
    188
    -        password = conference.getTagData('password') or None
    
    189
    -
    
    190
    -        return BookmarkData(jid=jid,
    
    191
    -                            name=name,
    
    192
    -                            autojoin=autojoin,
    
    193
    -                            password=password,
    
    194
    -                            nick=nick)
    
    195
    -
    
    196
    -    def _parse_nickname(self, nick):
    
    197
    -        if nick is None:
    
    198
    -            return None
    
    199
    -
    
    200
    -        try:
    
    201
    -            return validate_resourcepart(nick)
    
    202
    -        except Exception as error:
    
    203
    -            self._log.warning('Invalid nick: %s, %s', nick, error)
    
    204
    -            return None
    
    205
    -
    
    206
    -    def _parse_autojoin(self, autojoin):
    
    207
    -        if autojoin is None:
    
    208
    -            return False
    
    209
    -
    
    210
    -        try:
    
    211
    -            return from_xs_boolean(autojoin)
    
    212
    -        except ValueError as error:
    
    213
    -            self._log.warning('Invalid autojoin attribute: (%s) %s',
    
    214
    -                              autojoin, error)
    
    215
    -            return False
    
    216
    -
    
    217
    -    @staticmethod
    
    218
    -    def get_private_request():
    
    219
    -        iq = Iq(typ='get')
    
    220
    -        query = iq.addChild(name='query', namespace=Namespace.PRIVATE)
    
    221
    -        query.addChild(name='storage', namespace=Namespace.BOOKMARKS)
    
    222
    -        return iq
    
    223
    -
    
    224
    -    @call_on_response('_bookmarks_received')
    
    225
    -    def request_bookmarks(self, type_):
    
    226
    -        jid = self._client.get_bound_jid().bare
    
    227
    -        if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2:
    
    228
    -            self._log.info('Request bookmarks 2 (PubSub)')
    
    229
    -            request = get_pubsub_request(jid, Namespace.BOOKMARKS_2)
    
    230
    -            return request, {'type_': type_}
    
    231
    -
    
    232
    -        if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_1:
    
    233
    -            self._log.info('Request bookmarks (PubSub)')
    
    234
    -            request = get_pubsub_request(jid, Namespace.BOOKMARKS, max_items=1)
    
    235
    -            return request, {'type_': type_}
    
    236
    -
    
    237
    -        if type_ == BookmarkStoreType.PRIVATE:
    
    238
    -            self._log.info('Request bookmarks (Private Storage)')
    
    239
    -            return self.get_private_request(), {'type_': type_}
    
    240
    -        return None
    
    241
    -
    
    242
    -    @callback
    
    243
    -    def _bookmarks_received(self, stanza, type_):
    
    244
    -        if not isResultNode(stanza):
    
    245
    -            return raise_error(self._log.info, stanza)
    
    246
    -
    
    247
    -        bookmarks = []
    
    248
    -        if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2:
    
    249
    -            items = get_pubsub_items(stanza, Namespace.BOOKMARKS_2)
    
    250
    -            if items is None:
    
    251
    -                return raise_error(self._log.warning,
    
    252
    -                                   stanza,
    
    253
    -                                   'stanza-malformed')
    
    254
    -
    
    255
    -            for item in items:
    
    256
    -                bookmark_item = self._parse_bookmarks2(item)
    
    257
    -                if bookmark_item is not None:
    
    258
    -                    bookmarks.append(bookmark_item)
    
    259
    -
    
    260
    -        elif type_ == BookmarkStoreType.PUBSUB_BOOKMARK_1:
    
    261
    -            item = get_pubsub_item(stanza)
    
    262
    -            if item is not None:
    
    263
    -                storage_node = item.getTag('storage',
    
    264
    -                                           namespace=Namespace.BOOKMARKS)
    
    265
    -                if storage_node is None:
    
    266
    -                    return raise_error(self._log.warning,
    
    267
    -                                       stanza,
    
    268
    -                                       'stanza-malformed',
    
    269
    -                                       'No storage node')
    
    270
    -
    
    271
    -                if storage_node.getChildren():
    
    272
    -                    bookmarks = self._parse_bookmarks(storage_node)
    
    273
    -
    
    274
    -        elif type_ == BookmarkStoreType.PRIVATE:
    
    275
    -            query = stanza.getQuery()
    
    276
    -            storage_node = query.getTag('storage',
    
    277
    -                                        namespace=Namespace.BOOKMARKS)
    
    278
    -            if storage_node is None:
    
    279
    -                return raise_error(self._log.warning,
    
    280
    -                                   stanza,
    
    281
    -                                   'stanza-malformed',
    
    282
    -                                   'No storage node')
    
    283
    -
    
    284
    -            if storage_node.getChildren():
    
    285
    -                bookmarks = self._parse_bookmarks(storage_node)
    
    286
    -
    
    287
    -        from_ = stanza.getFrom()
    
    288
    -        if from_ is None:
    
    289
    -            from_ = self._client.get_bound_jid().bare
    
    290
    -        self._log.info('Received bookmarks from: %s', from_)
    
    291
    -        for bookmark in bookmarks:
    
    292
    -            self._log.info(bookmark)
    
    293
    -        return bookmarks
    
    294
    -
    
    295
    -    @staticmethod
    
    296
    -    def _build_storage_node(bookmarks):
    
    297
    -        storage_node = Node(tag='storage', attrs={'xmlns': Namespace.BOOKMARKS})
    
    298
    -        for bookmark in bookmarks:
    
    299
    -            conf_node = storage_node.addChild(name="conference")
    
    300
    -            conf_node.setAttr('jid', bookmark.jid)
    
    301
    -            conf_node.setAttr('autojoin', to_xs_boolean(bookmark.autojoin))
    
    302
    -            if bookmark.name:
    
    303
    -                conf_node.setAttr('name', bookmark.name)
    
    304
    -            if bookmark.nick:
    
    305
    -                conf_node.setTagData('nick', bookmark.nick)
    
    306
    -            if bookmark.password:
    
    307
    -                conf_node.setTagData('password', bookmark.password)
    
    308
    -        return storage_node
    
    309
    -
    
    310
    -    @staticmethod
    
    311
    -    def _build_conference_node(bookmark):
    
    312
    -        attrs = {'xmlns': Namespace.BOOKMARKS_2}
    
    313
    -        if bookmark.autojoin:
    
    314
    -            attrs['autojoin'] = 'true'
    
    315
    -        if bookmark.name:
    
    316
    -            attrs['name'] = bookmark.name
    
    317
    -        conference = Node(tag='conference', attrs=attrs)
    
    318
    -        if bookmark.nick:
    
    319
    -            conference.setTagData('nick', bookmark.nick)
    
    320
    -        return conference
    
    321
    -
    
    322
    -    def store_bookmarks(self, bookmarks, type_):
    
    323
    -        if type_ == BookmarkStoreType.PUBSUB_BOOKMARK_2:
    
    324
    -            self._store_bookmark_2(bookmarks)
    
    325
    -        elif type_ == BookmarkStoreType.PUBSUB_BOOKMARK_1:
    
    326
    -            self._store_bookmark_1(bookmarks)
    
    327
    -        elif type_ == BookmarkStoreType.PRIVATE:
    
    328
    -            self._store_with_private(bookmarks)
    
    329
    -
    
    330
    -    def retract_bookmark(self, bookmark_jid):
    
    331
    -        self._log.info('Retract Bookmark: %s', bookmark_jid)
    
    332
    -
    
    333
    -        self.retract(Namespace.BOOKMARKS_2, str(bookmark_jid))
    
    334
    -
    
    335
    -    def _store_bookmark_1(self, bookmarks):
    
    336
    -        self._log.info('Store Bookmarks 1 (PubSub)')
    
    337
    -
    
    338
    -        self._bookmark_1_queue = bookmarks
    
    339
    -        item = self._build_storage_node(bookmarks)
    
    340
    -        self.publish(Namespace.BOOKMARKS,
    
    341
    -                     item,
    
    342
    -                     id_='current',
    
    343
    -                     options=BOOKMARK_1_OPTIONS,
    
    344
    -                     force_node_options=True,
    
    345
    -                     callback=self._on_store_bookmark_result,
    
    346
    -                     user_data=Namespace.BOOKMARKS)
    
    347
    -
    
    348
    -    def _store_bookmark_2(self, bookmarks):
    
    349
    -        self._log.info('Store Bookmarks 2 (PubSub)')
    
    350
    -
    
    351
    -        for bookmark in bookmarks:
    
    352
    -            self._bookmark_2_queue[bookmark.jid] = bookmark
    
    353
    -            item = self._build_conference_node(bookmark)
    
    354
    -            self.publish(Namespace.BOOKMARKS_2,
    
    355
    -                         item,
    
    356
    -                         id_=str(bookmark.jid),
    
    357
    -                         options=BOOKMARK_2_OPTIONS,
    
    358
    -                         force_node_options=True,
    
    359
    -                         callback=self._on_store_bookmark_result,
    
    360
    -                         user_data=Namespace.BOOKMARKS_2)
    
    361
    -
    
    362
    -    def _on_store_bookmark_result(self, task):
    
    363
    -        try:
    
    364
    -            result = task.finish()
    
    365
    -        except Exception as error:
    
    366
    -            self._log.warning(error)
    
    367
    -
    
    368
    -        self._bookmark_1_queue = []
    
    369
    -        self._bookmark_2_queue.pop(result.id, None)
    
    370
    -
    
    371
    -    @call_on_response('_on_private_store_result')
    
    372
    -    def _store_with_private(self, bookmarks):
    
    373
    -        self._log.info('Store Bookmarks (Private Storage)')
    
    374
    -        storage_node = self._build_storage_node(bookmarks)
    
    375
    -        return Iq('set', Namespace.PRIVATE, payload=storage_node)
    
    376
    -
    
    377
    -    def _on_private_store_result(self, _client, stanza):
    
    378
    -        if not isResultNode(stanza):
    
    379
    -            return raise_error(self._log.info, stanza)
    
    380
    -        return None

  • nbxmpp/modules/bookmarks/__init__.py

  • nbxmpp/modules/bookmarks/native_bookmarks.py
    1
    +# Copyright (C) 2018 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
    +
    
    18
    +from nbxmpp.namespaces import Namespace
    
    19
    +from nbxmpp.protocol import NodeProcessed
    
    20
    +from nbxmpp.structs import StanzaHandler
    
    21
    +from nbxmpp.task import iq_request_task
    
    22
    +from nbxmpp.errors import MalformedStanzaError
    
    23
    +from nbxmpp.modules.base import BaseModule
    
    24
    +from nbxmpp.modules.util import raise_if_error
    
    25
    +from nbxmpp.modules.util import finalize
    
    26
    +from nbxmpp.modules.bookmarks.util import parse_bookmark
    
    27
    +from nbxmpp.modules.bookmarks.util import build_conference_node
    
    28
    +
    
    29
    +
    
    30
    +BOOKMARK_OPTIONS = {
    
    31
    +    'pubsub#notify_delete': 'true',
    
    32
    +    'pubsub#notify_retract': 'true',
    
    33
    +    'pubsub#persist_items': 'true',
    
    34
    +    'pubsub#max_items': 'max',
    
    35
    +    'pubsub#access_model': 'whitelist',
    
    36
    +    'pubsub#send_last_published_item': 'never',
    
    37
    +}
    
    38
    +
    
    39
    +
    
    40
    +class NativeBookmarks(BaseModule):
    
    41
    +
    
    42
    +    _depends = {
    
    43
    +        'retract': 'PubSub',
    
    44
    +        'publish': 'PubSub',
    
    45
    +        'request_items': 'PubSub',
    
    46
    +    }
    
    47
    +
    
    48
    +    def __init__(self, client):
    
    49
    +        BaseModule.__init__(self, client)
    
    50
    +
    
    51
    +        self._client = client
    
    52
    +        self.handlers = [
    
    53
    +            StanzaHandler(name='message',
    
    54
    +                          callback=self._process_pubsub_bookmarks,
    
    55
    +                          ns=Namespace.PUBSUB_EVENT,
    
    56
    +                          priority=16),
    
    57
    +        ]
    
    58
    +
    
    59
    +    def _process_pubsub_bookmarks(self, _client, _stanza, properties):
    
    60
    +        if not properties.is_pubsub_event:
    
    61
    +            return
    
    62
    +
    
    63
    +        if properties.pubsub_event.node != Namespace.BOOKMARKS_1:
    
    64
    +            return
    
    65
    +
    
    66
    +        item = properties.pubsub_event.item
    
    67
    +        if item is None:
    
    68
    +            # Retract, Deleted or Purged
    
    69
    +            return
    
    70
    +
    
    71
    +        try:
    
    72
    +            bookmark_item = parse_bookmark(item)
    
    73
    +        except MalformedStanzaError as error:
    
    74
    +            self._log.warning(error)
    
    75
    +            self._log.warning(error.stanza)
    
    76
    +            raise NodeProcessed
    
    77
    +
    
    78
    +        pubsub_event = properties.pubsub_event._replace(data=bookmark_item)
    
    79
    +        self._log.info('Received bookmark item from: %s', properties.jid)
    
    80
    +        self._log.info(bookmark_item)
    
    81
    +
    
    82
    +        properties.pubsub_event = pubsub_event
    
    83
    +
    
    84
    +    @iq_request_task
    
    85
    +    def request_bookmarks(self):
    
    86
    +        _task = yield
    
    87
    +
    
    88
    +        items = yield self.request_items(Namespace.BOOKMARKS_1)
    
    89
    +        raise_if_error(items)
    
    90
    +
    
    91
    +        bookmarks = []
    
    92
    +        for item in items:
    
    93
    +            try:
    
    94
    +                bookmark_item = self.parse_bookmark(item)
    
    95
    +            except MalformedStanzaError as error:
    
    96
    +                self._log.warning(error)
    
    97
    +                self._log.warning(error.stanza)
    
    98
    +                continue
    
    99
    +
    
    100
    +            bookmarks.append(bookmark_item)
    
    101
    +
    
    102
    +        for bookmark in bookmarks:
    
    103
    +            self._log.info(bookmark)
    
    104
    +
    
    105
    +        yield bookmarks
    
    106
    +
    
    107
    +    @iq_request_task
    
    108
    +    def retract_bookmark(self, bookmark_jid):
    
    109
    +        task = yield
    
    110
    +
    
    111
    +        self._log.info('Retract Bookmark: %s', bookmark_jid)
    
    112
    +
    
    113
    +        result = yield self.retract(Namespace.BOOKMARKS_1, str(bookmark_jid))
    
    114
    +        yield finalize(task, result)
    
    115
    +
    
    116
    +    @iq_request_task
    
    117
    +    def store_bookmarks(self, bookmarks):
    
    118
    +        _task = yield
    
    119
    +
    
    120
    +        self._log.info('Store Bookmarks')
    
    121
    +
    
    122
    +        for bookmark in bookmarks:
    
    123
    +            self.publish(Namespace.BOOKMARKS_1,
    
    124
    +                         build_conference_node(bookmark),
    
    125
    +                         id_=str(bookmark.jid),
    
    126
    +                         options=BOOKMARK_OPTIONS,
    
    127
    +                         force_node_options=True)
    
    128
    +
    
    129
    +        yield True

  • nbxmpp/modules/bookmarks/pep_bookmarks.py
    1
    +# Copyright (C) 2018 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
    +
    
    18
    +from nbxmpp.namespaces import Namespace
    
    19
    +from nbxmpp.protocol import NodeProcessed
    
    20
    +from nbxmpp.structs import StanzaHandler
    
    21
    +from nbxmpp.task import iq_request_task
    
    22
    +from nbxmpp.errors import MalformedStanzaError
    
    23
    +from nbxmpp.modules.base import BaseModule
    
    24
    +from nbxmpp.modules.util import raise_if_error
    
    25
    +from nbxmpp.modules.bookmarks.util import parse_bookmarks
    
    26
    +from nbxmpp.modules.bookmarks.util import build_storage_node
    
    27
    +
    
    28
    +
    
    29
    +BOOKMARK_OPTIONS = {
    
    30
    +    'pubsub#persist_items': 'true',
    
    31
    +    'pubsub#access_model': 'whitelist',
    
    32
    +}
    
    33
    +
    
    34
    +
    
    35
    +class PEPBookmarks(BaseModule):
    
    36
    +
    
    37
    +    _depends = {
    
    38
    +        'publish': 'PubSub',
    
    39
    +        'request_items': 'PubSub',
    
    40
    +    }
    
    41
    +
    
    42
    +    def __init__(self, client):
    
    43
    +        BaseModule.__init__(self, client)
    
    44
    +
    
    45
    +        self._client = client
    
    46
    +        self.handlers = [
    
    47
    +            StanzaHandler(name='message',
    
    48
    +                          callback=self._process_pubsub_bookmarks,
    
    49
    +                          ns=Namespace.PUBSUB_EVENT,
    
    50
    +                          priority=16),
    
    51
    +        ]
    
    52
    +
    
    53
    +    def _process_pubsub_bookmarks(self, _client, stanza, properties):
    
    54
    +        if not properties.is_pubsub_event:
    
    55
    +            return
    
    56
    +
    
    57
    +        if properties.pubsub_event.node != Namespace.BOOKMARKS:
    
    58
    +            return
    
    59
    +
    
    60
    +        item = properties.pubsub_event.item
    
    61
    +        if item is None:
    
    62
    +            # Retract, Deleted or Purged
    
    63
    +            return
    
    64
    +
    
    65
    +        try:
    
    66
    +            bookmarks = parse_bookmarks(item, self._log)
    
    67
    +        except MalformedStanzaError as error:
    
    68
    +            self._log.warning(error)
    
    69
    +            self._log.warning(stanza)
    
    70
    +            raise NodeProcessed
    
    71
    +
    
    72
    +        if not bookmarks:
    
    73
    +            self._log.info('Bookmarks removed')
    
    74
    +            return
    
    75
    +
    
    76
    +        pubsub_event = properties.pubsub_event._replace(data=bookmarks)
    
    77
    +        self._log.info('Received bookmarks from: %s', properties.jid)
    
    78
    +        for bookmark in bookmarks:
    
    79
    +            self._log.info(bookmark)
    
    80
    +
    
    81
    +        properties.pubsub_event = pubsub_event
    
    82
    +
    
    83
    +    @iq_request_task
    
    84
    +    def request_bookmarks(self):
    
    85
    +        _task = yield
    
    86
    +
    
    87
    +        items = yield self.request_items(Namespace.BOOKMARKS, max_items=1)
    
    88
    +        raise_if_error(items)
    
    89
    +
    
    90
    +        if not items:
    
    91
    +            yield []
    
    92
    +
    
    93
    +        bookmarks = parse_bookmarks(items[0], self._log)
    
    94
    +        for bookmark in bookmarks:
    
    95
    +            self._log.info(bookmark)
    
    96
    +
    
    97
    +        yield bookmarks
    
    98
    +
    
    99
    +    @iq_request_task
    
    100
    +    def store_bookmarks(self, bookmarks):
    
    101
    +        _task = yield
    
    102
    +
    
    103
    +        self._log.info('Store Bookmarks')
    
    104
    +
    
    105
    +        self.publish(Namespace.BOOKMARKS,
    
    106
    +                     build_storage_node(bookmarks),
    
    107
    +                     id_='current',
    
    108
    +                     options=BOOKMARK_OPTIONS,
    
    109
    +                     force_node_options=True)

  • nbxmpp/modules/bookmarks/private_bookmarks.py
    1
    +# Copyright (C) 2018 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
    +
    
    18
    +from nbxmpp.namespaces import Namespace
    
    19
    +from nbxmpp.protocol import Iq
    
    20
    +from nbxmpp.task import iq_request_task
    
    21
    +from nbxmpp.modules.util import raise_if_error
    
    22
    +from nbxmpp.modules.util import finalize
    
    23
    +from nbxmpp.modules.base import BaseModule
    
    24
    +from nbxmpp.modules.bookmarks.util import build_storage_node
    
    25
    +from nbxmpp.modules.bookmarks.util import get_private_request
    
    26
    +from nbxmpp.modules.bookmarks.util import parse_private_bookmarks
    
    27
    +
    
    28
    +
    
    29
    +
    
    30
    +class PrivateBookmarks(BaseModule):
    
    31
    +    def __init__(self, client):
    
    32
    +        BaseModule.__init__(self, client)
    
    33
    +
    
    34
    +        self._client = client
    
    35
    +        self.handlers = []
    
    36
    +
    
    37
    +    @iq_request_task
    
    38
    +    def request_bookmarks(self):
    
    39
    +        _task = yield
    
    40
    +
    
    41
    +        response = yield get_private_request()
    
    42
    +        raise_if_error(response)
    
    43
    +
    
    44
    +
    
    45
    +        bookmarks = parse_private_bookmarks(response, self._log)
    
    46
    +        for bookmark in bookmarks:
    
    47
    +            self._log.info(bookmark)
    
    48
    +
    
    49
    +        yield bookmarks
    
    50
    +
    
    51
    +    @iq_request_task
    
    52
    +    def store_bookmarks(self, bookmarks):
    
    53
    +        task = yield
    
    54
    +
    
    55
    +        self._log.info('Store Bookmarks')
    
    56
    +
    
    57
    +        storage_node = build_storage_node(bookmarks)
    
    58
    +        result = yield Iq('set', Namespace.PRIVATE, payload=storage_node)
    
    59
    +        yield finalize(task, result)

  • nbxmpp/modules/bookmarks/util.py
    1
    +# Copyright (C) 2020 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
    +
    
    18
    +from nbxmpp.protocol import Node
    
    19
    +from nbxmpp.protocol import validate_resourcepart
    
    20
    +from nbxmpp.protocol import JID
    
    21
    +from nbxmpp.protocol import Iq
    
    22
    +from nbxmpp.util import from_xs_boolean
    
    23
    +from nbxmpp.util import to_xs_boolean
    
    24
    +from nbxmpp.namespaces import Namespace
    
    25
    +from nbxmpp.errors import MalformedStanzaError
    
    26
    +from nbxmpp.structs import BookmarkData
    
    27
    +
    
    28
    +
    
    29
    +def parse_nickname(nick):
    
    30
    +    if nick is None:
    
    31
    +        return None
    
    32
    +
    
    33
    +    try:
    
    34
    +        return validate_resourcepart(nick)
    
    35
    +    except Exception:
    
    36
    +        return None
    
    37
    +
    
    38
    +
    
    39
    +def parse_autojoin(autojoin):
    
    40
    +    if autojoin is None:
    
    41
    +        return False
    
    42
    +
    
    43
    +    try:
    
    44
    +        return from_xs_boolean(autojoin)
    
    45
    +    except ValueError:
    
    46
    +        return False
    
    47
    +
    
    48
    +
    
    49
    +def parse_bookmark(item):
    
    50
    +    conference = item.getTag('conference', namespace=Namespace.BOOKMARKS_1)
    
    51
    +    if conference is None:
    
    52
    +        raise MalformedStanzaError('conference node missing', item)
    
    53
    +
    
    54
    +    try:
    
    55
    +        jid = JID.from_string(item.getAttr('id'))
    
    56
    +    except Exception as error:
    
    57
    +        raise MalformedStanzaError('invalid jid: %s' % error, item)
    
    58
    +
    
    59
    +    if jid.localpart is None or jid.resource is not None:
    
    60
    +        raise MalformedStanzaError('invalid jid', item)
    
    61
    +
    
    62
    +    autojoin = parse_autojoin(conference.getAttr('autojoin'))
    
    63
    +    nick = parse_nickname(conference.getTagData('nick'))
    
    64
    +    name = conference.getAttr('name') or None
    
    65
    +    password = conference.getTagData('password') or None
    
    66
    +
    
    67
    +    return BookmarkData(jid=jid,
    
    68
    +                        name=name,
    
    69
    +                        autojoin=autojoin,
    
    70
    +                        password=password,
    
    71
    +                        nick=nick)
    
    72
    +
    
    73
    +
    
    74
    +def parse_bookmarks(item, log):
    
    75
    +    storage_node = item.getTag('storage', namespace=Namespace.BOOKMARKS)
    
    76
    +    if storage_node is None:
    
    77
    +        raise MalformedStanzaError('storage node missing', item)
    
    78
    +
    
    79
    +    return parse_storage_node(storage_node, log)
    
    80
    +
    
    81
    +
    
    82
    +def parse_private_bookmarks(response, log):
    
    83
    +    query = response.getQuery()
    
    84
    +    storage_node = query.getTag('storage', namespace=Namespace.BOOKMARKS)
    
    85
    +    if storage_node is None:
    
    86
    +        raise MalformedStanzaError('storage node missing', response)
    
    87
    +
    
    88
    +    return parse_storage_node(storage_node, log)
    
    89
    +
    
    90
    +
    
    91
    +def parse_storage_node(storage, log):
    
    92
    +    bookmarks = []
    
    93
    +    confs = storage.getTags('conference')
    
    94
    +    for conf in confs:
    
    95
    +        try:
    
    96
    +            jid = JID.from_string(conf.getAttr('jid'))
    
    97
    +        except Exception:
    
    98
    +            log.warning('invalid jid: %s', conf)
    
    99
    +            continue
    
    100
    +
    
    101
    +        if jid.localpart is None or jid.resource is not None:
    
    102
    +            log.warning('invalid jid: %s', conf)
    
    103
    +            continue
    
    104
    +
    
    105
    +        autojoin = parse_autojoin(conf.getAttr('autojoin'))
    
    106
    +        nick = parse_nickname(conf.getTagData('nick'))
    
    107
    +        name = conf.getAttr('name') or None
    
    108
    +        password = conf.getTagData('password') or None
    
    109
    +
    
    110
    +        bookmark = BookmarkData(
    
    111
    +            jid=jid,
    
    112
    +            name=name,
    
    113
    +            autojoin=autojoin,
    
    114
    +            password=password,
    
    115
    +            nick=nick)
    
    116
    +        bookmarks.append(bookmark)
    
    117
    +
    
    118
    +    return bookmarks
    
    119
    +
    
    120
    +
    
    121
    +def build_conference_node(bookmark):
    
    122
    +    attrs = {'xmlns': Namespace.BOOKMARKS_1}
    
    123
    +    if bookmark.autojoin:
    
    124
    +        attrs['autojoin'] = 'true'
    
    125
    +    if bookmark.name:
    
    126
    +        attrs['name'] = bookmark.name
    
    127
    +    conference = Node(tag='conference', attrs=attrs)
    
    128
    +    if bookmark.nick:
    
    129
    +        conference.setTagData('nick', bookmark.nick)
    
    130
    +    return conference
    
    131
    +
    
    132
    +
    
    133
    +def build_storage_node(bookmarks):
    
    134
    +    storage_node = Node(tag='storage', attrs={'xmlns': Namespace.BOOKMARKS})
    
    135
    +    for bookmark in bookmarks:
    
    136
    +        conf_node = storage_node.addChild(name="conference")
    
    137
    +        conf_node.setAttr('jid', bookmark.jid)
    
    138
    +        conf_node.setAttr('autojoin', to_xs_boolean(bookmark.autojoin))
    
    139
    +        if bookmark.name:
    
    140
    +            conf_node.setAttr('name', bookmark.name)
    
    141
    +        if bookmark.nick:
    
    142
    +            conf_node.setTagData('nick', bookmark.nick)
    
    143
    +        if bookmark.password:
    
    144
    +            conf_node.setTagData('password', bookmark.password)
    
    145
    +    return storage_node
    
    146
    +
    
    147
    +
    
    148
    +def get_private_request():
    
    149
    +    iq = Iq(typ='get')
    
    150
    +    query = iq.addChild(name='query', namespace=Namespace.PRIVATE)
    
    151
    +    query.addChild(name='storage', namespace=Namespace.BOOKMARKS)
    
    152
    +    return iq

  • nbxmpp/namespaces.py
    ... ... @@ -34,8 +34,9 @@ class _Namespaces:
    34 34
         BLOCKING: str = 'urn:xmpp:blocking'
    
    35 35
         BOB: str = 'urn:xmpp:bob'
    
    36 36
         BOOKMARKS: str = 'storage:bookmarks'
    
    37
    -    BOOKMARKS_2: str = 'urn:xmpp:bookmarks:0'
    
    37
    +    BOOKMARKS_1: str = 'urn:xmpp:bookmarks:1'
    
    38 38
         BOOKMARKS_COMPAT: str = 'urn:xmpp:bookmarks:0#compat'
    
    39
    +    BOOKMARKS_COMPAT_PEP: str = 'urn:xmpp:bookmarks:1#compat-pep'
    
    39 40
         BOOKMARK_CONVERSION: str = 'urn:xmpp:bookmarks-conversion:0'
    
    40 41
         BROWSE: str = 'jabber:iq:browse'
    
    41 42
         BYTESTREAM: str = 'http://jabber.org/protocol/bytestreams'
    

  • nbxmpp/old_dispatcher.py
    ... ... @@ -60,7 +60,6 @@ from nbxmpp.modules.tune import Tune
    60 60
     from nbxmpp.modules.mood import Mood
    
    61 61
     from nbxmpp.modules.location import Location
    
    62 62
     from nbxmpp.modules.user_avatar import UserAvatar
    
    63
    -from nbxmpp.modules.bookmarks import Bookmarks
    
    64 63
     from nbxmpp.modules.openpgp import OpenPGP
    
    65 64
     from nbxmpp.modules.omemo import OMEMO
    
    66 65
     from nbxmpp.modules.annotations import Annotations