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

Commits:

6 changed files:

Changes:

  • nbxmpp/dispatcher.py
    ... ... @@ -44,6 +44,7 @@ from nbxmpp.modules.iq import BaseIq
    44 44
     from nbxmpp.modules.nickname import Nickname
    
    45 45
     from nbxmpp.modules.delay import Delay
    
    46 46
     from nbxmpp.modules.muc import MUC
    
    47
    +from nbxmpp.modules.muc.moderation import Moderation
    
    47 48
     from nbxmpp.modules.idle import Idle
    
    48 49
     from nbxmpp.modules.pgplegacy import PGPLegacy
    
    49 50
     from nbxmpp.modules.vcard_avatar import VCardAvatar
    
    ... ... @@ -156,6 +157,7 @@ class StanzaDispatcher(Observable):
    156 157
             self._modules['HTTPAuth'] = HTTPAuth(self._client)
    
    157 158
             self._modules['Nickname'] = Nickname(self._client)
    
    158 159
             self._modules['MUC'] = MUC(self._client)
    
    160
    +        self._modules['Moderation'] = Moderation(self._client)
    
    159 161
             self._modules['Delay'] = Delay(self._client)
    
    160 162
             self._modules['Captcha'] = Captcha(self._client)
    
    161 163
             self._modules['Idle'] = Idle(self._client)
    

  • nbxmpp/modules/muc/moderation.py
    1
    +# This file is part of nbxmpp.
    
    2
    +#
    
    3
    +# This program is free software; you can redistribute it and/or
    
    4
    +# modify it under the terms of the GNU General Public License
    
    5
    +# as published by the Free Software Foundation; either version 3
    
    6
    +# of the License, or (at your option) any later version.
    
    7
    +#
    
    8
    +# This program is distributed in the hope that it will be useful,
    
    9
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    10
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    11
    +# GNU General Public License for more details.
    
    12
    +#
    
    13
    +# You should have received a copy of the GNU General Public License
    
    14
    +# along with this program; If not, see <http://www.gnu.org/licenses/>.
    
    15
    +
    
    16
    +# XEP-0425: Message Moderation
    
    17
    +
    
    18
    +from typing import Optional
    
    19
    +
    
    20
    +from nbxmpp.modules.base import BaseModule
    
    21
    +from nbxmpp.modules.util import process_response
    
    22
    +
    
    23
    +from nbxmpp import JID
    
    24
    +from nbxmpp.namespaces import Namespace
    
    25
    +from nbxmpp.protocol import Iq
    
    26
    +from nbxmpp.structs import StanzaHandler
    
    27
    +from nbxmpp.structs import MessageProperties
    
    28
    +from nbxmpp.structs import ModerationData
    
    29
    +from nbxmpp.simplexml import Node
    
    30
    +from nbxmpp.task import iq_request_task
    
    31
    +
    
    32
    +
    
    33
    +class Moderation(BaseModule):
    
    34
    +    def __init__(self, client):
    
    35
    +        BaseModule.__init__(self, client)
    
    36
    +
    
    37
    +        self._client = client
    
    38
    +        self.handlers = [
    
    39
    +            StanzaHandler(name='message',
    
    40
    +                          callback=self._process_message,
    
    41
    +                          typ='groupchat',
    
    42
    +                          ns=Namespace.FASTEN,
    
    43
    +                          priority=20),
    
    44
    +        ]
    
    45
    +
    
    46
    +    @iq_request_task
    
    47
    +    def send_retract_request(self, muc_jid: JID, stanza_id: str,
    
    48
    +                             reason: Optional[str] = None):
    
    49
    +        _task = yield
    
    50
    +
    
    51
    +        response = yield _make_retract_request(muc_jid, stanza_id, reason)
    
    52
    +
    
    53
    +        yield process_response(response)
    
    54
    +
    
    55
    +    @staticmethod
    
    56
    +    def _process_message(_client, stanza: Node,
    
    57
    +                        properties: MessageProperties) -> None:
    
    58
    +        if not properties.jid.is_bare:
    
    59
    +            return
    
    60
    +
    
    61
    +        apply_to = stanza.getTag(
    
    62
    +            'apply-to', namespace=Namespace.FASTEN)
    
    63
    +        if apply_to is None:
    
    64
    +            return
    
    65
    +
    
    66
    +        moderated = apply_to.getTag(
    
    67
    +            'moderated', namespace=Namespace.MESSAGE_MODERATE)
    
    68
    +        if moderated is None:
    
    69
    +            return
    
    70
    +
    
    71
    +        retract = moderated.getTag(
    
    72
    +                'retract', namespace=Namespace.MESSAGE_RETRACT)
    
    73
    +        if retract is None:
    
    74
    +            # Tag can be 'retract' or 'retracted', depending on whether the
    
    75
    +            # server applies a tombstone for MAM messages or not.
    
    76
    +            retract = moderated.getTag(
    
    77
    +                'retracted', namespace=Namespace.MESSAGE_RETRACT)
    
    78
    +        if retract is None:
    
    79
    +            return
    
    80
    +
    
    81
    +        properties.moderation = ModerationData(
    
    82
    +            stanza_id=apply_to.getAttr('id'),
    
    83
    +            moderator_jid=moderated.getAttr('by'),
    
    84
    +            reason=moderated.getTagData('reason'),
    
    85
    +            timestamp=retract.getAttr('stamp'))
    
    86
    +
    
    87
    +
    
    88
    +def _make_retract_request(muc_jid: JID, stanza_id: str,
    
    89
    +                          reason: Optional[str]) -> Iq:
    
    90
    +    iq = Iq('set', Namespace.FASTEN, to=muc_jid)
    
    91
    +    query = iq.setQuery(name='apply-to')
    
    92
    +    query.setAttr('id', stanza_id)
    
    93
    +    moderate = query.addChild(name='moderate',
    
    94
    +                              namespace=Namespace.MESSAGE_MODERATE)
    
    95
    +    moderate.addChild(name='retract', namespace=Namespace.MESSAGE_RETRACT)
    
    96
    +    if reason is not None:
    
    97
    +        moderate.addChild(name='reason', payload=[reason])
    
    98
    +    return iq

  • nbxmpp/modules/muc/muc.py
    ... ... @@ -15,6 +15,7 @@
    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 typing import Optional
    
    18 19
     
    
    19 20
     from nbxmpp.namespaces import Namespace
    
    20 21
     from nbxmpp.protocol import ERR_NOT_ACCEPTABLE
    
    ... ... @@ -61,6 +62,7 @@ class MUC(BaseModule):
    61 62
         _depends = {
    
    62 63
             'disco_info': 'Discovery',
    
    63 64
             'request_vcard': 'VCardTemp',
    
    65
    +        'send_retract_request': 'Moderation',
    
    64 66
         }
    
    65 67
     
    
    66 68
         def __init__(self, client):
    
    ... ... @@ -468,6 +470,10 @@ class MUC(BaseModule):
    468 470
             response = yield make_set_role_request(room_jid, nick, role, reason)
    
    469 471
             yield process_response(response)
    
    470 472
     
    
    473
    +    def retract_message(self, room_jid: JID, stanza_id: str,
    
    474
    +                        reason: Optional[str] = None) -> None:
    
    475
    +        self.send_retract_request(room_jid, stanza_id, reason)
    
    476
    +
    
    471 477
         def set_subject(self, room_jid, subject):
    
    472 478
             message = Message(room_jid, typ='groupchat', subject=subject)
    
    473 479
             self._log.info('Set subject for %s', room_jid)
    

  • nbxmpp/namespaces.py
    ... ... @@ -62,6 +62,7 @@ class _Namespaces:
    62 62
         DOMAIN_BASED_NAME: str = 'urn:xmpp:domain-based-name:1'
    
    63 63
         EME: str = 'urn:xmpp:eme:0'
    
    64 64
         ENCRYPTED: str = 'jabber:x:encrypted'
    
    65
    +    FASTEN: str = 'urn:xmpp:fasten:0'
    
    65 66
         FILE_METADATA: str = 'urn:xmpp:file:metadata:0'
    
    66 67
         FORWARD: str = 'urn:xmpp:forward:0'
    
    67 68
         FRAMING: str = 'urn:ietf:params:xml:ns:xmpp-framing'
    
    ... ... @@ -99,6 +100,8 @@ class _Namespaces:
    99 100
         LOCATION: str = 'http://jabber.org/protocol/geoloc'
    
    100 101
         MAM_1: str = 'urn:xmpp:mam:1'
    
    101 102
         MAM_2: str = 'urn:xmpp:mam:2'
    
    103
    +    MESSAGE_MODERATE: str = 'urn:xmpp:message-moderate:0'
    
    104
    +    MESSAGE_RETRACT: str = 'urn:xmpp:message-retract:0'
    
    102 105
         MOOD: str = 'http://jabber.org/protocol/mood'
    
    103 106
         MSG_HINTS: str = 'urn:xmpp:hints'
    
    104 107
         MUCLUMBUS: str = 'https://xmlns.zombofant.net/muclumbus/search/1.0'
    

  • nbxmpp/structs.py
    ... ... @@ -284,6 +284,13 @@ class CorrectionData(NamedTuple):
    284 284
         id: str
    
    285 285
     
    
    286 286
     
    
    287
    +class ModerationData(NamedTuple):
    
    288
    +    stanza_id: str
    
    289
    +    moderator_jid: str
    
    290
    +    reason: Optional[str] = None
    
    291
    +    timestamp: Optional[str] = None
    
    292
    +
    
    293
    +
    
    287 294
     class DiscoItems(NamedTuple):
    
    288 295
         jid: JID
    
    289 296
         node: str
    
    ... ... @@ -468,6 +475,10 @@ class DiscoInfo(NamedTuple):
    468 475
         def has_httpupload(self) -> bool:
    
    469 476
             return Namespace.HTTPUPLOAD_0 in self.features
    
    470 477
     
    
    478
    +    @property
    
    479
    +    def has_message_moderation(self) -> bool:
    
    480
    +        return Namespace.MESSAGE_MODERATE in self.features
    
    481
    +
    
    471 482
         @property
    
    472 483
         def is_muc(self) -> bool:
    
    473 484
             for identity in self.identities:
    
    ... ... @@ -960,6 +971,7 @@ class MessageProperties:
    960 971
         receipt: Optional[ReceiptData] = None
    
    961 972
         oob: Optional[OOBData] = None
    
    962 973
         correction: Optional[CorrectionData] = None
    
    974
    +    moderation: Optional[ModerationData] = None
    
    963 975
         attention: bool = False
    
    964 976
         forms = None
    
    965 977
         xhtml: Optional[str] = None
    
    ... ... @@ -1069,6 +1081,10 @@ class MessageProperties:
    1069 1081
         def is_correction(self) -> bool:
    
    1070 1082
             return self.correction is not None
    
    1071 1083
     
    
    1084
    +    @property
    
    1085
    +    def is_moderation(self) -> bool:
    
    1086
    +        return self.moderation is not None
    
    1087
    +
    
    1072 1088
         @property
    
    1073 1089
         def has_attention(self) -> bool:
    
    1074 1090
             return self.attention
    

  • python-nbxmpp.doap
    ... ... @@ -392,5 +392,12 @@
    392 392
             <xmpp:version>0.3.0</xmpp:version>
    
    393 393
           </xmpp:SupportedXep>
    
    394 394
         </implements>
    
    395
    +    <implements>
    
    396
    +      <xmpp:SupportedXep>
    
    397
    +        <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0425.html" />
    
    398
    +        <xmpp:status>complete</xmpp:status>
    
    399
    +        <xmpp:version>0.2.1</xmpp:version>
    
    400
    +      </xmpp:SupportedXep>
    
    401
    +    </implements>
    
    395 402
       </Project>
    
    396 403
     </rdf:RDF>