#encoding: utf-8 """ Bot Wantzel from La Quadrature du Net. License : AGPLv3 Doc : https://wiki.laquadrature.net/Wantzel TODO: - Ajouter la gestion des droits pour certaines commandes - Mettre une valeur par défaut pour les champs concernés - Afficher les titres des urls fournies sur le canal """ from irc import IrcClientFactory import MySQLdb import re from twisted.internet import reactor import urllib import config from messages import messages def get_cursor(): """ This function connects to a database and returns a usable cursor. """ db = MySQLdb.connect( host=config.dbserver, user=config.dbuser, passwd=config.dbpassword, db=config.dbname ) if db: return db.cursor() return None def get_url(message, command=""): """ Retrieve the url behind the command. """ # Let's get what is behind the command result = re.search("!%s ([^ ]*)" % command, message) if not result: return "" url = result.group(1) # Verify the presence of http result = re.search("^(https?://)(.+)$", url) if not result: return "http" # Removing anchor if needed result = re.search("^([^#]*)", url) if result: url = result.group(1) # Removing trackers url = re.sub("[?&](utm_medium|utm_source|utm_campaign|xtor)=[^&]*", "", url) return url class Wantzel(object): """ Wantzel bot. """ def __init__(self): """ Initialization of bot over IRC. """ self.irc = IrcClientFactory(config) self.irc.set_privmsg = self.set_privmsg reactor.connectTCP(config.server, config.port, self.irc) def set_privmsg(self): """ This method set the methods to call for each callback received from IRC. """ self.irc.client.privmsg = self.on_privmsg self.irc.client.joined = self.on_joined def send_message(self, channel, messages): """ Sends a message on specified channel, cutting each line in a new message """ for message in messages.splitlines(): self.irc.client.msg(channel, message) def on_privmsg(self, user, channel, msg): """ Wantzel can understand some commands : - help Returns a message about how to use the bot. If a command is passed after help, the message explains how to use the command. - rp(acp) Add an article in the database - stats Show some statistics about the RP - kill Kill an article by giving it a score of -100 """ # Cleaning user name user = re.search("([^!]*)!", user).group(1) print("Message received: %s %s %s" % (user, channel, msg)) # Never answer to botself if user!=config.nickname: # 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): self.help(user, channel, msg) # Find known command command = re.search("!(rp[acp]*|kill|help|stats)", msg) if command: command = command.group(1) print("Command: %s" % command) if command.startswith("rp"): self.rp(command, user, channel, msg) elif command=="help": self.help(user, channel, msg) elif command=="kill": self.kill(user, channel, msg) elif command=="stats": self.stats(user, channel, msg) def on_joined(self, channel): """ Say hello to everyone. """ print("Joined channel %s" % channel) # Specific message for specific channel if "hello_"+channel[1:] in messages: self.send_message(channel, messages["hello_"+channel[1:]]) else: self.send_message(channel, messages["hello"]) def help(self, user, channel, msg): """ Show global help. If a known command is behind the !help command, an adequate message is returned. """ print("help command") # Searching for a command after help keyword command = re.search("!help (stats|rp|help|kill)", msg) if command: command = command.group(1) self.send_message(user, messages["help_"+command]) else: self.send_message(channel, messages["help"]) def rp(self, command, user, channel, msg): """ Adding the article in rp database. """ print("rp command %s" % command) cite = 0 note = 0 url = get_url(msg, command) print("url: %s" % url) if url=="": return elif url=="http": self.send_message(channel, messages["rp_http"] % user) return # Looking for such an article in database cursor = get_cursor() cursor.execute("SELECT id, note, provenance FROM presse WHERE url = %s", (url, )) rows = cursor.fetchall() if not rows: # LQdN is quoted if "c" in command: cite += 2 # the article speak about LQdN if command.count("p")>1: cite += 2 # Archive this article if "a" in command: note -= 2 #TODO: Gérer les autres champs qui n'ont pas de valeur par défaut # lang, published, nid, screenshot, title, fetched, seemscite print("Adding an article by %s: %s" % (user, url)) result = cursor.execute( "INSERT INTO presse SET url=%s, provenance=%s, cite=%s, note=%s, datec=NOW()", (url, user, cite, note) ) self.send_message(channel, messages["rp_new_article"] % user) else: if rows[0][2]!=user: print("Adding a point by %s on %s" % (user, rows[0][0])) result = cursor.execute( "UPDATE presse SET note=note+1 WHERE id=%s", (rows[0][0], ) ) if (rows[0][1]+1)<3: self.send_message(channel, messages["rp_known_article"] % user) else: self.send_message(channel, messages["rp_taken_article"] % user) def kill(self, user, channel, msg): """ Kill an article by setting its score to -100. """ #TODO: Gérer les droits de cette commande print("kill command") url = get_url(msg, "kill") print("url: %s" % url) if url=="": return elif url=="http": self.send_message(channel, messages["rp_http"] % user) return # Looking for such an article in database cursor = get_cursor() cursor.execute("SELECT id, note FROM presse WHERE url=%s", (url, )) rows = cursor.fetchall() if not rows: self.send_message(channel, messages["kill_none"] % url) else: cursor.execute("UPDATE presse SET note=-100 WHERE id=%s", (rows[0][0], )) self.send_message(channel, messages["kill_done"] % url) def stats(self, user, channel, msg): """ Returns stats on articles in press review. """ print("stats command") db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="site") cursor = db.cursor() periods = [1, 3, 7, 15] notes = [0, 3 ,4] notnull = 0 somethingatall = 0 result = "" for note in notes: notnull = 0 result = result + "note>=%s: " % note for period in periods: cursor.execute( """SELECT COUNT(id) AS cid FROM presse WHERE nid=0 AND datec>(NOW()-INTERVAL %s DAY) AND note>=%s""", (period, note) ) rows = cursor.fetchall() if rows[0][0]>0: result = result + "%sj:%s, " % (period, rows[0][0]) notnull = 1 somethingatall = 1 if notnull: result = result[:-2] + "\n" if somethingatall==0: result = messages["stats_bravo"] % periods[-1] self.send_message(channel, result) if __name__ == '__main__': wantzel = Wantzel() reactor.run()