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

Commits:

3 changed files:

Changes:

  • nbxmpp/modules/muc/__init__.py
    1
    +from .muc import MUC

  • nbxmpp/modules/muc.py → nbxmpp/modules/muc/muc.py
    ... ... @@ -15,43 +15,45 @@
    15 15
     # You should have received a copy of the GNU General Public License
    
    16 16
     # along with this program; If not, see <http://www.gnu.org/licenses/>.
    
    17 17
     
    
    18
    -from dataclasses import dataclass
    
    19 18
     
    
    20 19
     from nbxmpp.namespaces import Namespace
    
    21 20
     from nbxmpp.protocol import ERR_NOT_ACCEPTABLE
    
    22 21
     from nbxmpp.protocol import JID
    
    23
    -from nbxmpp.protocol import Iq
    
    24 22
     from nbxmpp.protocol import Message
    
    25 23
     from nbxmpp.protocol import DataForm
    
    26 24
     from nbxmpp.protocol import DataField
    
    27
    -from nbxmpp.protocol import isResultNode
    
    28 25
     from nbxmpp.protocol import NodeProcessed
    
    29 26
     from nbxmpp.protocol import StanzaMalformed
    
    30
    -from nbxmpp.protocol import InvalidJid
    
    31
    -from nbxmpp.simplexml import Node
    
    32 27
     from nbxmpp.structs import StanzaHandler
    
    33 28
     from nbxmpp.const import InviteType
    
    34 29
     from nbxmpp.const import MessageType
    
    35 30
     from nbxmpp.const import StatusCode
    
    36
    -from nbxmpp.const import Affiliation
    
    37
    -from nbxmpp.const import Role
    
    38 31
     from nbxmpp.structs import DeclineData
    
    39 32
     from nbxmpp.structs import InviteData
    
    40 33
     from nbxmpp.structs import VoiceRequest
    
    41 34
     from nbxmpp.structs import AffiliationResult
    
    42
    -from nbxmpp.structs import CommonResult
    
    43 35
     from nbxmpp.structs import MucConfigResult
    
    44
    -from nbxmpp.structs import MucUserData
    
    45 36
     from nbxmpp.structs import MucDestroyed
    
    46 37
     from nbxmpp.task import iq_request_task
    
    47
    -from nbxmpp.util import call_on_response
    
    48
    -from nbxmpp.util import callback
    
    49
    -from nbxmpp.util import raise_error
    
    50 38
     from nbxmpp.errors import is_error
    
    39
    +from nbxmpp.errors import StanzaError
    
    51 40
     from nbxmpp.modules.util import raise_if_error
    
    52 41
     from nbxmpp.modules.util import parse_xmpp_uri
    
    42
    +from nbxmpp.modules.util import process_response
    
    53 43
     from nbxmpp.modules.dataforms import extend_form
    
    54 44
     from nbxmpp.modules.base import BaseModule
    
    45
    +from nbxmpp.modules.muc.util import MucInfoResult
    
    46
    +from nbxmpp.modules.muc.util import make_affiliation_request
    
    47
    +from nbxmpp.modules.muc.util import make_destroy_request
    
    48
    +from nbxmpp.modules.muc.util import make_set_config_request
    
    49
    +from nbxmpp.modules.muc.util import make_config_request
    
    50
    +from nbxmpp.modules.muc.util import make_cancel_config_request
    
    51
    +from nbxmpp.modules.muc.util import make_set_affiliation_request
    
    52
    +from nbxmpp.modules.muc.util import make_set_role_request
    
    53
    +from nbxmpp.modules.muc.util import make_captcha_request
    
    54
    +from nbxmpp.modules.muc.util import build_direct_invite
    
    55
    +from nbxmpp.modules.muc.util import build_mediated_invite
    
    56
    +from nbxmpp.modules.muc.util import parse_muc_user
    
    55 57
     
    
    56 58
     
    
    57 59
     class MUC(BaseModule):
    
    ... ... @@ -165,7 +167,7 @@ class MUC(BaseModule):
    165 167
                 properties.muc_status_codes = codes
    
    166 168
     
    
    167 169
             try:
    
    168
    -            properties.muc_user = self._parse_muc_user(muc_user)
    
    170
    +            properties.muc_user = parse_muc_user(muc_user)
    
    169 171
             except StanzaMalformed as error:
    
    170 172
                 self._log.warning(error)
    
    171 173
                 self._log.warning(stanza)
    
    ... ... @@ -186,8 +188,8 @@ class MUC(BaseModule):
    186 188
             muc_user = stanza.getTag('x', namespace=Namespace.MUC_USER)
    
    187 189
             if muc_user is not None:
    
    188 190
                 try:
    
    189
    -                properties.muc_user = self._parse_muc_user(muc_user,
    
    190
    -                                                           is_presence=False)
    
    191
    +                properties.muc_user = parse_muc_user(muc_user,
    
    192
    +                                                     is_presence=False)
    
    191 193
                 except StanzaMalformed as error:
    
    192 194
                     self._log.warning(error)
    
    193 195
                     self._log.warning(stanza)
    
    ... ... @@ -342,20 +344,16 @@ class MUC(BaseModule):
    342 344
             form['muc#request_allow'].value = True
    
    343 345
             self._client.send_stanza(Message(to=muc_jid, payload=form))
    
    344 346
     
    
    345
    -    @call_on_response('_affiliation_received')
    
    347
    +    @iq_request_task
    
    346 348
         def get_affiliation(self, jid, affiliation):
    
    347
    -        iq = Iq(typ='get', to=jid, queryNS=Namespace.MUC_ADMIN)
    
    348
    -        item = iq.setQuery().setTag('item')
    
    349
    -        item.setAttr('affiliation', affiliation)
    
    350
    -        return iq
    
    351
    -
    
    352
    -    @callback
    
    353
    -    def _affiliation_received(self, stanza):
    
    354
    -        if not isResultNode(stanza):
    
    355
    -            return raise_error(self._log.info, stanza)
    
    356
    -
    
    357
    -        room_jid = stanza.getFrom()
    
    358
    -        query = stanza.getTag('query', namespace=Namespace.MUC_ADMIN)
    
    349
    +        _task = yield
    
    350
    +
    
    351
    +        response = yield make_affiliation_request(jid, affiliation)
    
    352
    +        if response.isError():
    
    353
    +            raise StanzaError(response)
    
    354
    +
    
    355
    +        room_jid = response.getFrom()
    
    356
    +        query = response.getTag('query', namespace=Namespace.MUC_ADMIN)
    
    359 357
             items = query.getTags('item')
    
    360 358
             users_dict = {}
    
    361 359
             for item in items:
    
    ... ... @@ -378,19 +376,14 @@ class MUC(BaseModule):
    378 376
             self._log.info('Affiliations received from %s: %s',
    
    379 377
                            room_jid, users_dict)
    
    380 378
     
    
    381
    -        return AffiliationResult(jid=room_jid, users=users_dict)
    
    379
    +        yield AffiliationResult(jid=room_jid, users=users_dict)
    
    382 380
     
    
    383
    -    @call_on_response('_default_response')
    
    384
    -    def destroy(self, room_jid, reason='', jid=''):
    
    385
    -        iq = Iq(typ='set', queryNS=Namespace.MUC_OWNER, to=room_jid)
    
    386
    -        destroy = iq.setQuery().setTag('destroy')
    
    387
    -        if reason:
    
    388
    -            destroy.setTagData('reason', reason)
    
    389
    -        if jid:
    
    390
    -            destroy.setAttr('jid', jid)
    
    391
    -        self._log.info('Destroy room: %s, reason: %s, alternate: %s',
    
    392
    -                       room_jid, reason, jid)
    
    393
    -        return iq
    
    381
    +    @iq_request_task
    
    382
    +    def destroy(self, room_jid, reason=None, jid=None):
    
    383
    +        _task = yield
    
    384
    +
    
    385
    +        response = yield make_destroy_request(room_jid, reason, jid)
    
    386
    +        yield process_response(response)
    
    394 387
     
    
    395 388
         @iq_request_task
    
    396 389
         def request_info(self, jid, request_vcard=True, allow_redirect=False):
    
    ... ... @@ -428,79 +421,52 @@ class MUC(BaseModule):
    428 421
                                 vcard=vcard,
    
    429 422
                                 redirected=redirected)
    
    430 423
     
    
    431
    -    @call_on_response('_default_response')
    
    424
    +    @iq_request_task
    
    432 425
         def set_config(self, room_jid, form):
    
    433
    -        iq = Iq(typ='set', to=room_jid, queryNS=Namespace.MUC_OWNER)
    
    434
    -        query = iq.setQuery()
    
    435
    -        form.setAttr('type', 'submit')
    
    436
    -        query.addChild(node=form)
    
    437
    -        self._log.info('Set config for %s', room_jid)
    
    438
    -        return iq
    
    439
    -
    
    440
    -    @call_on_response('_config_received')
    
    426
    +        _task = yield
    
    427
    +
    
    428
    +        response = yield make_set_config_request(room_jid, form)
    
    429
    +        yield process_response(response)
    
    430
    +
    
    431
    +    @iq_request_task
    
    441 432
         def request_config(self, room_jid):
    
    442
    -        iq = Iq(typ='get',
    
    443
    -                queryNS=Namespace.MUC_OWNER,
    
    444
    -                to=room_jid)
    
    445
    -        self._log.info('Request config for %s', room_jid)
    
    446
    -        return iq
    
    433
    +        task = yield
    
    447 434
     
    
    448
    -    @callback
    
    449
    -    def _config_received(self, stanza):
    
    450
    -        if not isResultNode(stanza):
    
    451
    -            return raise_error(self._log.info, stanza)
    
    435
    +        response = yield make_config_request(room_jid)
    
    436
    +        if response.isError():
    
    437
    +            raise StanzaError(response)
    
    452 438
     
    
    453
    -        jid = stanza.getFrom()
    
    454
    -        payload = stanza.getQueryPayload()
    
    439
    +        jid = response.getFrom()
    
    440
    +        payload = response.getQueryPayload()
    
    455 441
     
    
    456 442
             for form in payload:
    
    457 443
                 if form.getNamespace() == Namespace.DATA:
    
    458 444
                     dataform = extend_form(node=form)
    
    459 445
                     self._log.info('Config form received for %s', jid)
    
    460
    -                return MucConfigResult(jid=jid,
    
    461
    -                                       form=dataform)
    
    462
    -        return MucConfigResult(jid=jid)
    
    446
    +                yield MucConfigResult(jid=jid, form=dataform)
    
    463 447
     
    
    464
    -    @call_on_response('_default_response')
    
    448
    +        yield MucConfigResult(jid=jid)
    
    449
    +
    
    450
    +    @iq_request_task
    
    465 451
         def cancel_config(self, room_jid):
    
    466
    -        cancel = Node(tag='x', attrs={'xmlns': Namespace.DATA,
    
    467
    -                                      'type': 'cancel'})
    
    468
    -        iq = Iq(typ='set',
    
    469
    -                queryNS=Namespace.MUC_OWNER,
    
    470
    -                payload=cancel,
    
    471
    -                to=room_jid)
    
    472
    -        self._log.info('Cancel config for %s', room_jid)
    
    473
    -        return iq
    
    474
    -
    
    475
    -    @call_on_response('_default_response')
    
    452
    +        _task = yield
    
    453
    +
    
    454
    +        response = yield make_cancel_config_request(room_jid)
    
    455
    +        yield process_response(response)
    
    456
    +
    
    457
    +    @iq_request_task
    
    476 458
         def set_affiliation(self, room_jid, users_dict):
    
    477
    -        iq = Iq(typ='set', to=room_jid, queryNS=Namespace.MUC_ADMIN)
    
    478
    -        item = iq.setQuery()
    
    479
    -        for jid in users_dict:
    
    480
    -            affiliation = users_dict[jid].get('affiliation')
    
    481
    -            reason = users_dict[jid].get('reason')
    
    482
    -            nick = users_dict[jid].get('nick')
    
    483
    -            item_tag = item.addChild('item', {'jid': jid,
    
    484
    -                                              'affiliation': affiliation})
    
    485
    -            if reason is not None:
    
    486
    -                item_tag.setTagData('reason', reason)
    
    487
    -
    
    488
    -            if nick is not None:
    
    489
    -                item_tag.setAttr('nick', nick)
    
    490
    -        self._log.info('Set affiliation for %s: %s', room_jid, users_dict)
    
    491
    -        return iq
    
    492
    -
    
    493
    -    @call_on_response('_default_response')
    
    494
    -    def set_role(self, room_jid, nick, role, reason=''):
    
    495
    -        iq = Iq(typ='set', to=room_jid, queryNS=Namespace.MUC_ADMIN)
    
    496
    -        item = iq.setQuery().setTag('item')
    
    497
    -        item.setAttr('nick', nick)
    
    498
    -        item.setAttr('role', role)
    
    499
    -        if reason:
    
    500
    -            item.addChild(name='reason', payload=reason)
    
    501
    -        self._log.info('Set role for %s: %s %s %s',
    
    502
    -                       room_jid, nick, role, reason)
    
    503
    -        return iq
    
    459
    +        _task = yield
    
    460
    +
    
    461
    +        response = yield make_set_affiliation_request(room_jid, users_dict)
    
    462
    +        yield process_response(response)
    
    463
    +
    
    464
    +    @iq_request_task
    
    465
    +    def set_role(self, room_jid, nick, role, reason=None):
    
    466
    +        _task = yield
    
    467
    +
    
    468
    +        response = yield make_set_role_request(room_jid, nick, role, reason)
    
    469
    +        yield process_response(response)
    
    504 470
     
    
    505 471
         def set_subject(self, room_jid, subject):
    
    506 472
             message = Message(room_jid, typ='groupchat', subject=subject)
    
    ... ... @@ -529,106 +495,22 @@ class MUC(BaseModule):
    529 495
         def invite(self, room, to, password, reason=None, continue_=False,
    
    530 496
                    type_=InviteType.MEDIATED):
    
    531 497
             if type_ == InviteType.DIRECT:
    
    532
    -            invite = self._build_direct_invite(
    
    498
    +            invite = build_direct_invite(
    
    533 499
                     room, to, reason, password, continue_)
    
    534 500
             else:
    
    535
    -            invite = self._build_mediated_invite(
    
    501
    +            invite = build_mediated_invite(
    
    536 502
                     room, to, reason, password, continue_)
    
    537 503
             return self._client.send_stanza(invite)
    
    538 504
     
    
    539
    -    @staticmethod
    
    540
    -    def _build_direct_invite(room, to, reason, password, continue_):
    
    541
    -        message = Message(to=to)
    
    542
    -        attrs = {'jid': room}
    
    543
    -        if reason:
    
    544
    -            attrs['reason'] = reason
    
    545
    -        if continue_:
    
    546
    -            attrs['continue'] = 'true'
    
    547
    -        if password:
    
    548
    -            attrs['password'] = password
    
    549
    -        message.addChild(name='x', attrs=attrs,
    
    550
    -                         namespace=Namespace.CONFERENCE)
    
    551
    -        return message
    
    552
    -
    
    553
    -    @staticmethod
    
    554
    -    def _build_mediated_invite(room, to, reason, password, continue_):
    
    555
    -        message = Message(to=room)
    
    556
    -        muc_user = message.addChild('x', namespace=Namespace.MUC_USER)
    
    557
    -        invite = muc_user.addChild('invite', attrs={'to': to})
    
    558
    -        if continue_:
    
    559
    -            invite.addChild(name='continue')
    
    560
    -        if reason:
    
    561
    -            invite.setTagData('reason', reason)
    
    562
    -        if password:
    
    563
    -            muc_user.setTagData('password', password)
    
    564
    -        return message
    
    565
    -
    
    566
    -    @call_on_response('_default_response')
    
    505
    +    @iq_request_task
    
    567 506
         def send_captcha(self, room_jid, form_node):
    
    568
    -        iq = Iq(typ='set', to=room_jid)
    
    569
    -        captcha = iq.addChild(name='captcha', namespace=Namespace.CAPTCHA)
    
    570
    -        captcha.addChild(node=form_node)
    
    571
    -        return iq
    
    507
    +        _task = yield
    
    508
    +
    
    509
    +        response = yield make_captcha_request(room_jid, form_node)
    
    510
    +        yield process_response(response)
    
    572 511
     
    
    573 512
         def cancel_captcha(self, room_jid, message_id):
    
    574 513
             message = Message(typ='error', to=room_jid)
    
    575 514
             message.setID(message_id)
    
    576 515
             message.setError(ERR_NOT_ACCEPTABLE)
    
    577 516
             self._client.send_stanza(message)
    578
    -
    
    579
    -    @callback
    
    580
    -    def _default_response(self, stanza):
    
    581
    -        if not isResultNode(stanza):
    
    582
    -            return raise_error(self._log.info, stanza)
    
    583
    -        return CommonResult(jid=stanza.getFrom())
    
    584
    -
    
    585
    -    @staticmethod
    
    586
    -    def _parse_muc_user(muc_user, is_presence=True):
    
    587
    -        item = muc_user.getTag('item')
    
    588
    -        if item is None:
    
    589
    -            return None
    
    590
    -
    
    591
    -        item_dict = item.getAttrs()
    
    592
    -
    
    593
    -        role = item_dict.get('role')
    
    594
    -        if role is not None:
    
    595
    -            try:
    
    596
    -                role = Role(role)
    
    597
    -            except ValueError:
    
    598
    -                raise StanzaMalformed('invalid role %s' % role)
    
    599
    -
    
    600
    -        elif is_presence:
    
    601
    -            # role attr MUST be included in all presence broadcasts
    
    602
    -            raise StanzaMalformed('role attr missing')
    
    603
    -
    
    604
    -        affiliation = item_dict.get('affiliation')
    
    605
    -        if affiliation is not None:
    
    606
    -            try:
    
    607
    -                affiliation = Affiliation(affiliation)
    
    608
    -            except ValueError:
    
    609
    -                raise StanzaMalformed('invalid affiliation %s' % affiliation)
    
    610
    -
    
    611
    -        elif is_presence:
    
    612
    -            # affiliation attr MUST be included in all presence broadcasts
    
    613
    -            raise StanzaMalformed('affiliation attr missing')
    
    614
    -
    
    615
    -        jid = item_dict.get('jid')
    
    616
    -        if jid is not None:
    
    617
    -            try:
    
    618
    -                jid = JID.from_string(jid)
    
    619
    -            except InvalidJid as error:
    
    620
    -                raise StanzaMalformed('invalid jid %s, %s' % (jid, error))
    
    621
    -
    
    622
    -        return MucUserData(affiliation=affiliation,
    
    623
    -                           jid=jid,
    
    624
    -                           nick=item.getAttr('nick'),
    
    625
    -                           role=role,
    
    626
    -                           actor=item.getTagAttr('actor', 'nick'),
    
    627
    -                           reason=item.getTagData('reason'))
    
    628
    -
    
    629
    -
    
    630
    -@dataclass
    
    631
    -class MucInfoResult:
    
    632
    -    info: object
    
    633
    -    vcard: object = None
    
    634
    -    redirected: bool = False

  • nbxmpp/modules/muc/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
    +
    
    19
    +from dataclasses import dataclass
    
    20
    +
    
    21
    +from nbxmpp.namespaces import Namespace
    
    22
    +from nbxmpp.protocol import Iq
    
    23
    +from nbxmpp.protocol import JID
    
    24
    +from nbxmpp.protocol import InvalidJid
    
    25
    +from nbxmpp.protocol import StanzaMalformed
    
    26
    +from nbxmpp.protocol import Message
    
    27
    +from nbxmpp.simplexml import Node
    
    28
    +from nbxmpp.const import Affiliation
    
    29
    +from nbxmpp.const import Role
    
    30
    +from nbxmpp.structs import MucUserData
    
    31
    +
    
    32
    +
    
    33
    +@dataclass
    
    34
    +class MucInfoResult:
    
    35
    +    info: object
    
    36
    +    vcard: object = None
    
    37
    +    redirected: bool = False
    
    38
    +
    
    39
    +
    
    40
    +def make_affiliation_request(jid, affiliation):
    
    41
    +    iq = Iq(typ='get', to=jid, queryNS=Namespace.MUC_ADMIN)
    
    42
    +    item = iq.setQuery().setTag('item')
    
    43
    +    item.setAttr('affiliation', affiliation)
    
    44
    +    return iq
    
    45
    +
    
    46
    +
    
    47
    +def make_set_affiliation_request(room_jid, users_dict):
    
    48
    +    iq = Iq(typ='set', to=room_jid, queryNS=Namespace.MUC_ADMIN)
    
    49
    +    item = iq.setQuery()
    
    50
    +    for jid in users_dict:
    
    51
    +        affiliation = users_dict[jid].get('affiliation')
    
    52
    +        reason = users_dict[jid].get('reason')
    
    53
    +        nick = users_dict[jid].get('nick')
    
    54
    +        item_tag = item.addChild('item', {'jid': jid,
    
    55
    +                                          'affiliation': affiliation})
    
    56
    +        if reason is not None:
    
    57
    +            item_tag.setTagData('reason', reason)
    
    58
    +
    
    59
    +        if nick is not None:
    
    60
    +            item_tag.setAttr('nick', nick)
    
    61
    +
    
    62
    +    return iq
    
    63
    +
    
    64
    +
    
    65
    +def make_destroy_request(room_jid, reason, jid):
    
    66
    +    iq = Iq(typ='set', queryNS=Namespace.MUC_OWNER, to=room_jid)
    
    67
    +    destroy = iq.setQuery().setTag('destroy')
    
    68
    +
    
    69
    +    if reason:
    
    70
    +        destroy.setTagData('reason', reason)
    
    71
    +
    
    72
    +    if jid:
    
    73
    +        destroy.setAttr('jid', jid)
    
    74
    +
    
    75
    +    return iq
    
    76
    +
    
    77
    +
    
    78
    +def make_set_config_request(room_jid, form):
    
    79
    +    iq = Iq(typ='set', to=room_jid, queryNS=Namespace.MUC_OWNER)
    
    80
    +    query = iq.setQuery()
    
    81
    +    form.setAttr('type', 'submit')
    
    82
    +    query.addChild(node=form)
    
    83
    +    return iq
    
    84
    +
    
    85
    +
    
    86
    +def make_config_request(room_jid):
    
    87
    +    iq = Iq(typ='get',
    
    88
    +            queryNS=Namespace.MUC_OWNER,
    
    89
    +            to=room_jid)
    
    90
    +    return iq
    
    91
    +
    
    92
    +
    
    93
    +def make_cancel_config_request(room_jid):
    
    94
    +    cancel = Node(tag='x', attrs={'xmlns': Namespace.DATA,
    
    95
    +                                  'type': 'cancel'})
    
    96
    +    iq = Iq(typ='set',
    
    97
    +            queryNS=Namespace.MUC_OWNER,
    
    98
    +            payload=cancel,
    
    99
    +            to=room_jid)
    
    100
    +    return iq
    
    101
    +
    
    102
    +
    
    103
    +def make_set_role_request(room_jid, nick, role, reason):
    
    104
    +    iq = Iq(typ='set', to=room_jid, queryNS=Namespace.MUC_ADMIN)
    
    105
    +    item = iq.setQuery().setTag('item')
    
    106
    +    item.setAttr('nick', nick)
    
    107
    +    item.setAttr('role', role)
    
    108
    +    if reason:
    
    109
    +        item.addChild(name='reason', payload=reason)
    
    110
    +
    
    111
    +    return iq
    
    112
    +
    
    113
    +
    
    114
    +def make_captcha_request(room_jid, form_node):
    
    115
    +    iq = Iq(typ='set', to=room_jid)
    
    116
    +    captcha = iq.addChild(name='captcha', namespace=Namespace.CAPTCHA)
    
    117
    +    captcha.addChild(node=form_node)
    
    118
    +    return iq
    
    119
    +
    
    120
    +
    
    121
    +def build_direct_invite(room, to, reason, password, continue_):
    
    122
    +    message = Message(to=to)
    
    123
    +    attrs = {'jid': room}
    
    124
    +    if reason:
    
    125
    +        attrs['reason'] = reason
    
    126
    +    if continue_:
    
    127
    +        attrs['continue'] = 'true'
    
    128
    +    if password:
    
    129
    +        attrs['password'] = password
    
    130
    +    message.addChild(name='x', attrs=attrs,
    
    131
    +                     namespace=Namespace.CONFERENCE)
    
    132
    +    return message
    
    133
    +
    
    134
    +
    
    135
    +def build_mediated_invite(room, to, reason, password, continue_):
    
    136
    +    message = Message(to=room)
    
    137
    +    muc_user = message.addChild('x', namespace=Namespace.MUC_USER)
    
    138
    +    invite = muc_user.addChild('invite', attrs={'to': to})
    
    139
    +    if continue_:
    
    140
    +        invite.addChild(name='continue')
    
    141
    +    if reason:
    
    142
    +        invite.setTagData('reason', reason)
    
    143
    +    if password:
    
    144
    +        muc_user.setTagData('password', password)
    
    145
    +    return message
    
    146
    +
    
    147
    +
    
    148
    +def parse_muc_user(muc_user, is_presence=True):
    
    149
    +    item = muc_user.getTag('item')
    
    150
    +    if item is None:
    
    151
    +        return None
    
    152
    +
    
    153
    +    item_dict = item.getAttrs()
    
    154
    +
    
    155
    +    role = item_dict.get('role')
    
    156
    +    if role is not None:
    
    157
    +        try:
    
    158
    +            role = Role(role)
    
    159
    +        except ValueError:
    
    160
    +            raise StanzaMalformed('invalid role %s' % role)
    
    161
    +
    
    162
    +    elif is_presence:
    
    163
    +        # role attr MUST be included in all presence broadcasts
    
    164
    +        raise StanzaMalformed('role attr missing')
    
    165
    +
    
    166
    +    affiliation = item_dict.get('affiliation')
    
    167
    +    if affiliation is not None:
    
    168
    +        try:
    
    169
    +            affiliation = Affiliation(affiliation)
    
    170
    +        except ValueError:
    
    171
    +            raise StanzaMalformed('invalid affiliation %s' % affiliation)
    
    172
    +
    
    173
    +    elif is_presence:
    
    174
    +        # affiliation attr MUST be included in all presence broadcasts
    
    175
    +        raise StanzaMalformed('affiliation attr missing')
    
    176
    +
    
    177
    +    jid = item_dict.get('jid')
    
    178
    +    if jid is not None:
    
    179
    +        try:
    
    180
    +            jid = JID.from_string(jid)
    
    181
    +        except InvalidJid as error:
    
    182
    +            raise StanzaMalformed('invalid jid %s, %s' % (jid, error))
    
    183
    +
    
    184
    +    return MucUserData(affiliation=affiliation,
    
    185
    +                       jid=jid,
    
    186
    +                       nick=item.getAttr('nick'),
    
    187
    +                       role=role,
    
    188
    +                       actor=item.getTagAttr('actor', 'nick'),
    
    189
    +                       reason=item.getTagData('reason'))