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
Mindiell's avatar
Mindiell committed
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)
Mindiell's avatar
Mindiell committed
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()
Mindiell's avatar
Mindiell committed
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):
        """
Mindiell's avatar
Mindiell committed
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
Mindiell's avatar
Mindiell committed
120
        - kill (*)
Mindiell's avatar
Mindiell committed
121
            Kill an article by giving it a score of -100
122
        - admin list (*)
Mindiell's avatar
Mindiell committed
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()