wantzel.py 10.5 KB
Newer Older
1
# encoding: utf-8
2
"""
Mindiell's avatar
Mindiell committed
3
Bot Wantzel from La Quadrature du Net.
4

Mindiell's avatar
Mindiell committed
5
6
License : AGPLv3
Doc     : https://wiki.laquadrature.net/Wantzel
7
8
9
"""

import re
10
11

from irc import IrcClientFactory
12
from twisted.internet import reactor, task
13
14

import config
15
from logs import Log
16

17
18
19
20
21
22
# bot modules
from admin import Admin
from fun import fun
from monitor import Monitor
from op import Op
from rp import Rp
23

24
25
26
27
28
29
class Wantzel(object):
    """
    Wantzel bot.
    """
    def __init__(self):
        """
30
        Initialization of all utility objects and bot over IRC.
31
        """
32
33
34
35
        self.admin = Admin()
        self.monitor = Monitor()
        self.op = Op()
        self.rp = Rp()
36
        # Connection to IRC
37
38
39
        self.irc = IrcClientFactory(config)
        self.irc.set_privmsg = self.set_privmsg
        reactor.connectTCP(config.server, config.port, self.irc)
40
        self.test_timer = False
41
        # Loop call
Mindiell's avatar
Mindiell committed
42
        self.loop = task.LoopingCall(self.timer)
43
        self.loop.start(config.timer)
Mindiell's avatar
Mindiell committed
44
45
46
47
48

    def timer(self):
        """
        This method launches function regularly (see config.timer).
        """
49
        Log.debug("Timer called")
50
51
52
53
        # Testing timer ?
        if self.test_timer:
            self.send_message("#lqdn-travail", "Timer fonctionnel!")        
            self.test_timer = False
54
55
56
57
58
59
60
61
62
63
64
        # Tweeting Press reviews f necessary
        self.rp.tweet()
        # Bot tries to list RP channel users, if it can't, then it is no more an op and shall
        # complain
        self.irc.client.sendLine("NAMES %s" % config.RP_CHANNEL)
        # Update topic based on number of articles waiting in queue 
        topic = self.rp.count_articles()
        if topic != "":
            self.irc.client.topic(config.RP_CHANNEL, topic)
        # Tell on channel if a wiki was modified since last time
        self.send_message("#lqdn-travail", self.monitor.wiki_updates())
65
        # Cleaning points of mastering rp
66
        self.rp.clean_master_rp()
67
68
69
70
71

    def set_privmsg(self):
        """
        This method set the methods to call for each callback received from IRC.
        """
72
        # When receiving a message
73
        self.irc.client.privmsg = self.on_privmsg
74
75
76
        # When the bot discover it is no more op
        self.irc.client.irc_unknown = self.irc_unknown
        # When bot mode is changed
77
78
        self.irc.client.modeChanged = self.mode_changed

79
    def mode_changed(self, user, channel, flag_set, modes, args):
80
        """
81
        Callback called whenever bot mode is changed.
82
        """
83
        Log.debug("Mode changed : %s %s %s %s %s" % (user, channel, flag_set, modes, args))
Mindiell's avatar
Mindiell committed
84
        if "o" in modes and self.irc.client.nickname in args:
85
            self.send_message(channel, self.op.get_op_mode(user, flag_set))
86
87
88
89

    def irc_unknown(self, prefix, command, params):
        """
        This Callback is called whenever the bot tries to perform the command "NAMES" on the
Mindiell's avatar
Mindiell committed
90
        RP_CHANNEL channel. If it occurs, then the bot knows that it is no more op and beg for
91
92
        a mode change to actual operators on the channel.
        """
93
        Log.debug("UNKNOWN %s %s %s" % (prefix, command, params))
94
        if command=="RPL_NAMREPLY":
95
96
            # Beg for operator mode ?
            self.send_message(params[2], self.op.need_op_mode("@"+self.irc.client.nickname, params))
97

98
    def send_message(self, channel, multiline_message=""):
99
100
101
        """
        Sends a message on specified channel, cutting each line in a new message
        """
102
103
        if isinstance(multiline_message, list):
            multiline_message = "\n".join(multiline_message)
104
        for message in multiline_message.splitlines():
105
106
107
108
            self.irc.client.msg(channel, message)

    def on_privmsg(self, user, channel, msg):
        """
Mindiell's avatar
Mindiell committed
109
110
        Wantzel can understand a lot of commands. Commands followed by a (*)
        are accessible only to moderators:
111
112
113
        - help
            Returns a message about how to use the bot.
            If a command is passed after help, the message explains how to use
114
            the command.
115
            Bot answers in private in order not to spam channel
116
        - rp(acp) <url>
117
            Add an article in the database with a specific flag
118
119
        - status <url>
            Retrieve some informations about an article in the database
120
        - stats
Mindiell's avatar
Mindiell committed
121
            Show some statistics about the RP
Mindiell's avatar
Mindiell committed
122
        - kill (*)
Mindiell's avatar
Mindiell committed
123
            Kill an article by giving it a score of -100
124
        - admin list (*)
Mindiell's avatar
Mindiell committed
125
            List rights in private
126
        - admin add (*)
127
128
129
            Add one or more new moderator to list
        - admin del (*)
            Delete one or more moderator from list
130
        - admin timer
131
            Relaunch a timer
132
133
        """
        # Cleaning user name
Mindiell's avatar
Mindiell committed
134
        user = re.search("([^!]*)!", user).group(1)
135
        Log.debug("Message received: %s %s %s" % (user, channel, msg))
Mindiell's avatar
Mindiell committed
136
        # Never answer to botself
137
        if user!=config.nickname:
Mindiell's avatar
Mindiell committed
138
139
140
141
142
            # If it's a query, bot should answer to the user as the channel
            if "#" not in channel:
                channel = user
            # Help command, specific
            if "wantzel" in msg and ("help" in msg or "aide" in msg):
143
                self.help(user, channel, msg)
Mindiell's avatar
Mindiell committed
144
            # Find known command
145
146
            command = re.search("[!~](rp[acp]*|status|kill|help|stats|admin)", msg)
            Log.debug("Command: %s" % command)
Mindiell's avatar
Mindiell committed
147
            if command:
148
                Log.debug("group(0): %s" % command.group(0))
Mindiell's avatar
Mindiell committed
149
                command = command.group(1)
150
                Log.debug("Command: %s" % command)
Mindiell's avatar
Mindiell committed
151
                if command.startswith("rp"):
152
153
                    Log.debug("Calling self.rp")
                    self.send_message(channel, self.rp.rp(command, user, channel, msg))
154
                if command.startswith("status"):
155
156
                    Log.debug("Calling self.status")
                    self.send_message(channel, self.rp.status(user, msg))
157
                elif command == "help":
158
                    Log.debug("Calling self.help")
159
                    self.help(user, channel, msg)
160
                elif command == "kill":
161
162
                    Log.debug("Calling self.kill")
                    self.send_message(channel, self.rp.kill(user, msg))
163
                elif command == "stats":
164
165
                    Log.debug("Calling self.stats")
                    self.send_message(channel, self.rp.stats())
166
                elif command == "admin":
167
168
169
170
                    Log.debug("Calling self.admin")
                    result = self.admin.admin(user, msg)
                    if result=="REACTOR":
                        try:
171
172
                            # Re-creating timer
                            self.loop = task.LoopingCall(self.timer)
173
                            self.loop.start(config.timer)
174
175
                        except Exception:
                            pass
176
177
178
                    elif result=="REACTOR":
                        self.test_timer = True
                        self.send_message(channel, "Test du timer en cours...")
179
180
181
                    else:
                        self.send_message(channel, result)
            self.send_message(channel, fun(user, channel, msg))
182

183
    def help(self, user, channel, msg):
184
185
        """
        Show global help.
186
        If a known command is behind the ~!help command, an adequate message is
187
188
        returned.
        """
189
        Log.debug("help command")
Mindiell's avatar
Mindiell committed
190
        # Searching for a command after help keyword
191
        command = re.search("[!~]help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
192
193
        if command:
            command = command.group(1)
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
            if command=="help":
                self.send_message(
                    user,
                    """Bravo!
                    Tu viens d'entrer dans le monde récursif où l'aide sert à expliciter l'aide."""
                )
            elif command=="rp":
                self.send_message(
                    user,
                    """Cette commande sert à ajouter un article à la Revue de Presse (https://wiki.laquadrature.net/Revue_de_presse)
                    L'utilisation se fait sous la forme: ~rp(cpa) <url de l'article à ajouter>"""
                )
            elif command=="status":
                self.send_message(
                    user,
                    """Cette commande sert à retrouver les informations concernant un article ajouté à la Revue de Presse (https://wiki.laquadrature.net/Revue_de_presse)
                    L'utilisation se fait sous la forme: ~status <url de l'article>"""
                )
            elif command=="stats":
                self.send_message(
                    user,
                    """Cette commande permet de fournir quelques statistiques sur la Revue de Presse (https://wiki.laquadrature.net/Revue_de_presse)
                    Les statistiques sont calculées sur des notes supérieurs ou égales à 0, 3, et 4. Et sur les 1, 3, 7, et 15 derniers jours."""
                )
            elif command=="kill":
                self.send_message(
                    user,
                    """*Attention* seuls les vrais rp-jedis ont accès à cette commande <3
                    Fixe la note de l'article donné en paramètre à -100.
                    Utile en cas d'erreur ou pour s'assurer que l'article ne sera pas publié dans la RP
                    Utilisation: ~kill <url de l'article>"""
                )
            elif command=="admin":
                self.send_message(
                    user,
                    """*Attention* seuls les vrais rp-jedis ont accès à cette commande <3
                    Permet de gérer la liste des utilisateurs ayant un accès privilégié. Il n'y a qu'un seul niveau de privilège.
                    Utilisations:
                    ~admin list => Fournit la liste des utilisateurs privilégiés
                    ~admin add user[, user]> => Ajoute un ou plusieurs utilisateurs à la liste
                    ~admin del user[, user] => Supprime un ou plusieurs utilisateurs de la liste
                    ~admin timer => Relance un timer pour gérer le topic et les tweets"""
                )
            else:
                self.send_message(
                    user,
                    """Désolé, je ne connais pas cette commande."""
                )
Mindiell's avatar
Mindiell committed
242
        else:
243
244
245
246
247
248
            self.send_message(
                user,
                """Mes commandes sont : ~help ~rp(cpa) ~status ~kill ~stats et ~admin.
                Pour plus d'informations, voir ici: https://wiki.laquadrature.net/Wantzel
                Pour obtenir de l'aide sur une commande en particulier, il suffit de taper ~help <commande>"""
            )
249
250
251


if __name__ == '__main__':
252
    Wantzel()
253
    reactor.run()