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
13
from twitter import Twitter, OAuth
14 15

import config
16
from logs import Log
17

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

25 26 27 28 29 30
class Wantzel(object):
    """
    Wantzel bot.
    """
    def __init__(self):
        """
31
        Initialization of all utility objects and bot over IRC.
32
        """
33 34 35 36
        self.admin = Admin()
        self.monitor = Monitor()
        self.op = Op()
        self.rp = Rp()
37
        # Connection to IRC
38 39 40
        self.irc = IrcClientFactory(config)
        self.irc.set_privmsg = self.set_privmsg
        reactor.connectTCP(config.server, config.port, self.irc)
41 42 43 44 45 46 47
        # Prepare timer
        reactor.callLater(config.timer, self.timer)

    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
        # Recalling the timer
        reactor.callLater(config.timer, self.timer)
64 65 66 67 68

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

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

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

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

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

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


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