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 13 14
from twisted.internet import reactor

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 41 42 43 44 45 46
        # Prepare timer
        reactor.callLater(config.timer, self.timer)

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

    def set_privmsg(self):
        """
        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 72
        # When the bot discover it is no more op
        self.irc.client.irc_unknown = self.irc_unknown
        # When bot mode is changed
73 74
        self.irc.client.modeChanged = self.mode_changed

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

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

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

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

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


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