File: moderate.py

package info (click to toggle)
poezio 0.16-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,124 kB
  • sloc: python: 25,093; xml: 995; ansic: 329; makefile: 200; sh: 74; javascript: 2
file content (134 lines) | stat: -rw-r--r-- 4,359 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""
This plugin allows sending message retractions in a room as a moderator.
It requires XEP-0425 to be available on the room.

Command
-------

.. glossary::

    /moderate
        **Usage in a MUC tab:** ``/moderate <message time> [reason]``
"""

from typing import Optional, Union

from slixmpp.exceptions import IqError, IqTimeout
from poezio.decorators import command_args_parser
from poezio.plugin import BasePlugin
from poezio.core.structs import Completion
from poezio.ui.types import Message
from poezio.tabs import MucTab


DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S'


class Plugin(BasePlugin):
    """Message Moderation (XEP-0425) plugin"""

    def init(self):
        self.core.xmpp.register_plugin('xep_0425')

        self.api.add_tab_command(
            MucTab,
            name='moderate',
            handler=self.command_moderate,
            usage='<message time> [reason]',
            help='Moderate a message using its timestamp (using tab complete)',
            short='Moderate a message using its timestamp',
            completion=self.completion_moderate,
        )

    def find_message(self, time: str) -> Optional[Message]:
        """Find a message back from a timestamp"""
        messages = self.api.get_conversation_messages()[:-100:-1]
        if not messages:
            return None
        for message in messages:
            if isinstance(message, Message) and \
               message.time.strftime(DATETIME_FORMAT) == time:
                return message
        return None

    def completion_moderate(self, the_input) -> Union[bool, Completion]:
        """Datetime completion"""
        if the_input.get_argument_position() != 1:
            return False

        all_messages = self.api.get_conversation_messages()[:-100:-1]
        filtered_messages = filter(
            lambda m: isinstance(m, Message) and m.nickname and m.stanza_id,
            all_messages,
        )
        messages = list(map(lambda m: m.time.strftime(DATETIME_FORMAT), filtered_messages))

        if not messages:
            return False

        return Completion(the_input.auto_completion, messages, '')

    @command_args_parser.quoted(1, 1)
    async def command_moderate(self, args) -> None:
        """Moderate a message in a chatroom"""
        if not args:
            return
        time, *reason = args
        reason = ' '.join(reason)

        found_msg = self.find_message(time)
        if not found_msg:
            self.api.information(
                f'Moderated message with timestamp “{time}” not found.',
                'Error',
            )
            return

        id_ = found_msg.stanza_id
        if id_ is None:
            self.api.information(
                f'Moderated message with timestamp “{time}” '
                'found but contained no “{urn:xmpp:sid:0}stanza-id” identifier.',
                'Error',
            )
            return

        # For logging
        nickname = found_msg.nickname
        namespace = self.core.xmpp['xep_0425'].namespace

        tab = self.api.current_tab()
        jid = tab.jid
        try:
            await self.core.xmpp['xep_0425'].moderate(jid, id_, reason)
        except IqError as exn:
            if exn.iq['error']['text']:
                text = f"\n{exn.iq['error']['text']}"
            else:
                text = ''

            if exn.iq['error']['condition'] == 'service-unavailable':
                self.api.information(
                    f'Room “{jid}” doesn\'t support “{namespace}”.{text}',
                    'Error',
                )
            elif exn.iq['error']['condition'] == 'item-not-found':
                self.api.information(
                    f'Room “{jid}”: Message to moderate not found.{text}',
                    'Error',
                )
            elif exn.iq['error']['condition'] == 'forbidden':
                self.api.information(
                    f'Room “{jid}”: Message moderation forbidden.{text}',
                    'Error',
                )

            return
        except IqTimeout:
            self.api.information(f'Received no reply querying “{jid}”…', 'Error')
            return

        self.api.information(
            f'Room “{jid}”: message from: “{nickname}” at “{time}” successfully retracted.',
            'Info',
        )