wantzel.py 10 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
Mindiell's avatar
Mindiell committed
12
from twisted.internet import reactor, task, ssl
13
14

import config
15
from logs import Log
16

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

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

    def timer(self):
        """
        This method launches function regularly (see config.timer).
        """
48
        Log.debug("Timer called")
49
        # Testing timer ?
50
51
52
        if self.test_timer!="":
            self.send_message(self.test_timer, "Timer fonctionnel!")        
            self.test_timer = ""
53
54
55
56
57
        # 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)
58
        # Update topic based on number of articles waiting in queue if necessary
59
        topic = self.rp.count_articles()
60
        if topic != self.topic:
61
62
63
            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())
64
        # Cleaning points of mastering rp
65
        self.rp.clean_master_rp()
66

67
    def set_callbacks(self):
68
69
70
        """
        This method set the methods to call for each callback received from IRC.
        """
71
        # When receiving a message
72
        self.irc.client.privmsg = self.on_privmsg
73
74
        # When topic is modified
        self.irc.client.topicUpdated = self.topic_updated
75

76
    def send_message(self, channel, multiline_message=""):
77
78
79
        """
        Sends a message on specified channel, cutting each line in a new message
        """
80
81
        if isinstance(multiline_message, list):
            multiline_message = "\n".join(multiline_message)
82
        for message in multiline_message.splitlines():
83
84
            self.irc.client.msg(channel, message)

85
86
87
88
89
90
91
92
93
94
95
    def topic_updated(self, user, channel, newTopic):
        """
        Topic has been modified, or bot is coming in the channel.
        If the bot is coming in, we can start its timer's loop
        """
        self.topic = newTopic
        # Can I start the loop ?
        if not self.loop_started:
            self.loop_started = True
            self.loop.start(config.timer)

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

172
    def help(self, user, channel, msg):
173
174
        """
        Show global help.
175
        If a known command is behind the ~!help command, an adequate message is
176
177
        returned.
        """
178
        Log.debug("help command")
Mindiell's avatar
Mindiell committed
179
        # Searching for a command after help keyword
180
        command = re.search("[!~]help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
181
182
        if command:
            command = command.group(1)
183
            print(command)
184
185
            if command=="help":
                self.send_message(
186
187
188
                    channel,
                    """Bravo %s!
                    Tu viens d'entrer dans le monde récursif où l'aide sert à expliciter l'aide.""" % user
189
190
191
                )
            elif command=="rp":
                self.send_message(
192
193
194
                    channel,
                    """%s, 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>""" % user
195
196
197
                )
            elif command=="status":
                self.send_message(
198
199
200
                    channel,
                    """%s, 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>""" % user
201
202
203
                )
            elif command=="stats":
                self.send_message(
204
205
206
                    channel,
                    """%s, 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.""" % user
207
208
209
                )
            elif command=="kill":
                self.send_message(
210
211
                    channel,
                    """*Attention %s* seuls les vrais rp-jedis ont accès à cette commande <3
212
213
                    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
214
                    Utilisation: ~kill <url de l'article>""" % user
215
216
217
                )
            elif command=="admin":
                self.send_message(
218
219
                    channel,
                    """*Attention %s* seuls les vrais rp-jedis ont accès à cette commande <3
220
221
222
223
224
                    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
225
                    ~admin timer => Relance un timer pour gérer le topic et les tweets""" % user
226
227
228
                )
            else:
                self.send_message(
229
230
                    channel,
                    """Désolé %s, je ne connais pas cette commande.""" % user
231
                )
Mindiell's avatar
Mindiell committed
232
        else:
233
            self.send_message(
234
235
                channel,
                """%s Mes commandes sont : ~help ~rp(cpa) ~status ~kill ~stats et ~admin.
236
                Pour plus d'informations, voir ici: https://wiki.laquadrature.net/Wantzel
237
                Pour obtenir de l'aide sur une commande en particulier, il suffit de taper ~help <commande>""" % user
238
            )
239
240

if __name__ == '__main__':
241
    Wantzel()
242
    reactor.run()