| ... |
... |
@@ -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
|