wantzel.py 9.85 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
# bot modules
from admin import Admin
from fun import fun
20
from monitor import Monitoring
21
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
        self.admin = Admin()
32
        self.monitor = Monitoring()
33
        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
        # Tweeting Press reviews f necessary
        self.rp.tweet()
55
        # Update topic based on number of articles waiting in queue if necessary
56
        topic = self.rp.count_articles()
57
        if topic != self.topic:
58
59
            self.irc.client.topic(config.RP_CHANNEL, topic)
        # Tell on channel if a wiki was modified since last time
60
        self.send_message(config.MONITOR_CHANNEL, self.monitor.update())
61
        # Cleaning points of mastering rp
62
        self.rp.clean_master_rp()
63

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

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

82
83
84
85
86
87
88
89
90
91
92
    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)

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

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

if __name__ == '__main__':
238
    Wantzel()
239
    reactor.run()