Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
-
22aaefb2
by wurstsalat at 2021-11-10T21:54:31+01:00
6 changed files:
- nbxmpp/dispatcher.py
- + nbxmpp/modules/muc/moderation.py
- nbxmpp/modules/muc/muc.py
- nbxmpp/namespaces.py
- nbxmpp/structs.py
- python-nbxmpp.doap
Changes:
| ... | ... | @@ -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)
|
| 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
|
| ... | ... | @@ -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)
|
| ... | ... | @@ -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'
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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>
|