Commit 22aaefb2 authored by Daniel Brötzmann's avatar Daniel Brötzmann
Browse files

Add XEP-0425: Message Moderation

parent b948ba2e
Pipeline #8507 failed with stages
in 26 seconds
......@@ -44,6 +44,7 @@ from import BaseIq
from nbxmpp.modules.nickname import Nickname
from nbxmpp.modules.delay import Delay
from nbxmpp.modules.muc import MUC
from nbxmpp.modules.muc.moderation import Moderation
from nbxmpp.modules.idle import Idle
from nbxmpp.modules.pgplegacy import PGPLegacy
from nbxmpp.modules.vcard_avatar import VCardAvatar
......@@ -156,6 +157,7 @@ class StanzaDispatcher(Observable):
self._modules['HTTPAuth'] = HTTPAuth(self._client)
self._modules['Nickname'] = Nickname(self._client)
self._modules['MUC'] = MUC(self._client)
self._modules['Moderation'] = Moderation(self._client)
self._modules['Delay'] = Delay(self._client)
self._modules['Captcha'] = Captcha(self._client)
self._modules['Idle'] = Idle(self._client)
# This file is part of nbxmpp.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; If not, see <>.
# XEP-0425: Message Moderation
from typing import Optional
from nbxmpp.modules.base import BaseModule
from nbxmpp.modules.util import process_response
from nbxmpp import JID
from nbxmpp.namespaces import Namespace
from nbxmpp.protocol import Iq
from nbxmpp.structs import StanzaHandler
from nbxmpp.structs import MessageProperties
from nbxmpp.structs import ModerationData
from nbxmpp.simplexml import Node
from nbxmpp.task import iq_request_task
class Moderation(BaseModule):
def __init__(self, client):
BaseModule.__init__(self, client)
self._client = client
self.handlers = [
def send_retract_request(self, muc_jid: JID, stanza_id: str,
reason: Optional[str] = None):
_task = yield
response = yield _make_retract_request(muc_jid, stanza_id, reason)
yield process_response(response)
def _process_message(_client, stanza: Node,
properties: MessageProperties) -> None:
if not properties.jid.is_bare:
apply_to = stanza.getTag(
'apply-to', namespace=Namespace.FASTEN)
if apply_to is None:
moderated = apply_to.getTag(
'moderated', namespace=Namespace.MESSAGE_MODERATE)
if moderated is None:
retract = moderated.getTag(
'retract', namespace=Namespace.MESSAGE_RETRACT)
if retract is None:
# Tag can be 'retract' or 'retracted', depending on whether the
# server applies a tombstone for MAM messages or not.
retract = moderated.getTag(
'retracted', namespace=Namespace.MESSAGE_RETRACT)
if retract is None:
properties.moderation = ModerationData(
def _make_retract_request(muc_jid: JID, stanza_id: str,
reason: Optional[str]) -> Iq:
iq = Iq('set', Namespace.FASTEN, to=muc_jid)
query = iq.setQuery(name='apply-to')
query.setAttr('id', stanza_id)
moderate = query.addChild(name='moderate',
moderate.addChild(name='retract', namespace=Namespace.MESSAGE_RETRACT)
if reason is not None:
moderate.addChild(name='reason', payload=[reason])
return iq
......@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program; If not, see <>.
from typing import Optional
from nbxmpp.namespaces import Namespace
from nbxmpp.protocol import ERR_NOT_ACCEPTABLE
......@@ -61,6 +62,7 @@ class MUC(BaseModule):
_depends = {
'disco_info': 'Discovery',
'request_vcard': 'VCardTemp',
'send_retract_request': 'Moderation',
def __init__(self, client):
......@@ -468,6 +470,10 @@ class MUC(BaseModule):
response = yield make_set_role_request(room_jid, nick, role, reason)
yield process_response(response)
def retract_message(self, room_jid: JID, stanza_id: str,
reason: Optional[str] = None) -> None:
self.send_retract_request(room_jid, stanza_id, reason)
def set_subject(self, room_jid, subject):
message = Message(room_jid, typ='groupchat', subject=subject)'Set subject for %s', room_jid)
......@@ -62,6 +62,7 @@ class _Namespaces:
DOMAIN_BASED_NAME: str = 'urn:xmpp:domain-based-name:1'
EME: str = 'urn:xmpp:eme:0'
ENCRYPTED: str = 'jabber:x:encrypted'
FASTEN: str = 'urn:xmpp:fasten:0'
FILE_METADATA: str = 'urn:xmpp:file:metadata:0'
FORWARD: str = 'urn:xmpp:forward:0'
FRAMING: str = 'urn:ietf:params:xml:ns:xmpp-framing'
......@@ -99,6 +100,8 @@ class _Namespaces:
LOCATION: str = ''
MAM_1: str = 'urn:xmpp:mam:1'
MAM_2: str = 'urn:xmpp:mam:2'
MESSAGE_MODERATE: str = 'urn:xmpp:message-moderate:0'
MESSAGE_RETRACT: str = 'urn:xmpp:message-retract:0'
MOOD: str = ''
MSG_HINTS: str = 'urn:xmpp:hints'
MUCLUMBUS: str = ''
......@@ -284,6 +284,13 @@ class CorrectionData(NamedTuple):
id: str
class ModerationData(NamedTuple):
stanza_id: str
moderator_jid: str
reason: Optional[str] = None
timestamp: Optional[str] = None
class DiscoItems(NamedTuple):
jid: JID
node: str
......@@ -468,6 +475,10 @@ class DiscoInfo(NamedTuple):
def has_httpupload(self) -> bool:
return Namespace.HTTPUPLOAD_0 in self.features
def has_message_moderation(self) -> bool:
return Namespace.MESSAGE_MODERATE in self.features
def is_muc(self) -> bool:
for identity in self.identities:
......@@ -960,6 +971,7 @@ class MessageProperties:
receipt: Optional[ReceiptData] = None
oob: Optional[OOBData] = None
correction: Optional[CorrectionData] = None
moderation: Optional[ModerationData] = None
attention: bool = False
forms = None
xhtml: Optional[str] = None
......@@ -1069,6 +1081,10 @@ class MessageProperties:
def is_correction(self) -> bool:
return self.correction is not None
def is_moderation(self) -> bool:
return self.moderation is not None
def has_attention(self) -> bool:
return self.attention
......@@ -392,5 +392,12 @@
<xmpp:xep rdf:resource="" />
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment