wantzel.py 7.28 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
from messages import messages

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

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

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

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

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

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

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

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

177
    def help(self, user, channel, msg):
178 179
        """
        Show global help.
180
        If a known command is behind the ~!help command, an adequate message is
181 182
        returned.
        """
183
        Log.debug("help command")
Mindiell's avatar
Mindiell committed
184
        # Searching for a command after help keyword
185
        command = re.search("[!~]help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
186 187
        if command:
            command = command.group(1)
188
            self.send_message(user, messages["help_"+command])
Mindiell's avatar
Mindiell committed
189
        else:
190
            self.send_message(user, messages["help"])
191 192 193


if __name__ == '__main__':
194
    Wantzel()
195
    reactor.run()