wantzel.py 7.41 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
# bot modules
18
import helpers
19
from admin import Admin
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
42
        self.loop = task.LoopingCall(self.timer)
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):
        """
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
108
        - kill (*)
Mindiell's avatar
Mindiell committed
109
            Kill an article by giving it a score of -100
110
        - admin list (*)
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
                    else:
                        self.send_message(channel, result)
167

168
    def help(self, user, channel, msg):
169 170
        """
        Show global help.
171
        If a known command is behind the ~!help command, an adequate message is
172 173
        returned.
        """
174
        Log.debug("help command")
Mindiell's avatar
Mindiell committed
175
        # Searching for a command after help keyword
176
        command = re.search("[!~]help (help|rp|status|stats|kill|admin)", msg)
Mindiell's avatar
Mindiell committed
177 178
        if command:
            command = command.group(1)
179
            print(command)
180
            if command=="help":
181
                self.send_message(channel, helpers.help_help % user)
182
            elif command=="rp":
183
                self.send_message(channel, helpers.help_rp % user)
184
            elif command=="status":
185
                self.send_message(channel, helpers.help_status % user)
186
            elif command=="stats":
187
                self.send_message(channel, helpers.help_stats % user)
188
            elif command=="kill":
189
                self.send_message(channel, helpers.help_kill % user)
190
            elif command=="admin":
191
                self.send_message(channel, helpers.help_admin % user)
192
            else:
193
                self.send_message(channel, helpers.help_unknown % user)
Mindiell's avatar
Mindiell committed
194
        else:
195
            self.send_message(channel, helpers.help % user)
196 197

if __name__ == '__main__':
198
    Wantzel()
199
    reactor.run()