wantzel.py 10.1 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
        # Loop call
Mindiell's avatar
Mindiell committed
41
        self.loop = task.LoopingCall(self.timer)
42
        self.loop.start(config.timer)
Mindiell's avatar
Mindiell committed
43
44
45
46
47

    def timer(self):
        """
        This method launches function regularly (see config.timer).
        """
48
49
50
51
52
53
54
55
56
57
58
59
        Log.debug("Timer called")
        # 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())
60
        # Cleaning points of mastering rp
61
        self.rp.clean_master_rp()
62
63
64
65
66

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

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

    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
85
        RP_CHANNEL channel. If it occurs, then the bot knows that it is no more op and beg for
86
87
        a mode change to actual operators on the channel.
        """
88
        Log.debug("UNKNOWN %s %s %s" % (prefix, command, params))
89
        if command=="RPL_NAMREPLY":
90
91
            # Beg for operator mode ?
            self.send_message(params[2], self.op.need_op_mode("@"+self.irc.client.nickname, params))
92

93
    def send_message(self, channel, multiline_message=""):
94
95
96
        """
        Sends a message on specified channel, cutting each line in a new message
        """
97
98
        if isinstance(multiline_message, list):
            multiline_message = "\n".join(multiline_message)
99
        for message in multiline_message.splitlines():
100
101
102
103
            self.irc.client.msg(channel, message)

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

174
    def help(self, user, channel, msg):
175
176
        """
        Show global help.
177
        If a known command is behind the ~!help command, an adequate message is
178
179
        returned.
        """
180
        Log.debug("help command")
Mindiell's avatar
Mindiell committed
181
        # Searching for a command after help keyword
182
        command = re.search("[!~]help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
183
184
        if command:
            command = command.group(1)
185
186
187
188
189
190
191
192
193
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
            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
233
        else:
234
235
236
237
238
239
            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>"""
            )
240
241
242


if __name__ == '__main__':
243
    Wantzel()
244
    reactor.run()