From: Vincent Le Gallic Date: Sun, 5 Apr 2015 17:35:09 +0000 (+0200) Subject: Initial commit X-Git-Url: http://gitweb.pimeys.fr/?p=bots%2Fhelixbot.git;a=commitdiff_plain;h=863fd80d7b012dd28e1b263fec9f5a06abbd29f0 Initial commit --- 863fd80d7b012dd28e1b263fec9f5a06abbd29f0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbfb78d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.log +*.pyc diff --git a/config.py b/config.py new file mode 100644 index 0000000..3fc5a67 --- /dev/null +++ b/config.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# -*- coding:utf8 -*- + +"""Configuration d'un bot IRC (squelette de base vide)""" + +debug_stdout = True + +### La config irc-related +irc_password = "rSx4YgENAGJYs" +irc_pseudo = "HelixTheFossil" +#: Liste des chans à rejoindre +chanlist = ["#bot", "#flood"] +#: Liste des chans que le bot ne quitte pas, même sur demande d'un leave de la part d'un OP +stay_channels = ["#bot"] +#: Liste des chans sur lesquels le bot ne parle pas +quiet_channels = [] + +### Les logs +#: fichier de log (``%s`` est remplacé par le nom du serveur) +logfile_template = "helixbot.%s.log" + +### Les ops +#: Liste des OVEROPs (meilleurs que les OP ^^) +overops = ["[20-100]"] +#: Liste des OPs (la liste des :py:data:`overops` y est ajoutée) +ops = [] + +# config UTF8-fail +utf8_fail_answers = [u"Thou shalt use UTF-8!"] +#: Le bot râle-t-il en cas de non-utf8 ? +utf8_trigger = True + +### config Helix +#: Quand est-ce que le bot va considérer que c'est une question +fossil_triggers = [".+?\s*$"] +#: Les réponses de Helix the fossil +fossil_answers = [ + u"It is certain", + u"It is decidedly so", + u"Without a doubt", + u"Yes definitely", + u"You may rely on it", + u"As I see it, yes", + u"Most likely", + u"Outlook good", + u"Yes", + u"Signs point to yes", + u"Reply hazy try again", + u"Ask again later", + u"Better not tell you now", + u"Cannot predict now", + u"Concentrate and ask again ", + u"Don't count on it", + u"My reply is no", + u"My sources say no", + u"Outlook not so good", + u"Very doubtful", + u"no.", + u"START", + u"A", + u"B", + u"UP", + u"DOWN", + u"LEFT", + u"RIGHT", + u"SELECT", +] + +#: Réponse envoyée quand t'as pas les droits +no_right = [u"Thou shalt not give me orders."] + +### config "tais-toi" +#: Liste des pattern qui vont faire taire le bot +tag_triggers = [u"t(|a)g", u"ta gueule", u"la ferme", u"ferme( |-)la", u"tais-toi", u"chut", + u"tu fais trop de bruit", u"tu parles trop"] +#: Liste des actions effectuées en réponses à un :py:data:`tag_triggers` si +#: l'utilisateur n'est pas OP +tag_actions = [u"shall not speak its thoughts any more."] +#: Liste des réponses adressée à un utilisateur non-OP +#: suite à un :py:data:`tag_triggers` +tag_answers = no_right + +## config quelqu'un s'est défoulé sur le bot +#kick_answers = [u"Ceci est un message suite à /kick (Perpetré par {})"] +#kick_actions = [u"effectue une action suite à un /kick."] + +# config on m'a demandé de mourir/partir +quit_messages = [u"All hail Helix!"] +leave_messages = quit_messages + +quit_fail_messages = no_right +leave_fail_messages = quit_fail_messages diff --git a/helixbot.py b/helixbot.py new file mode 100755 index 0000000..72e4303 --- /dev/null +++ b/helixbot.py @@ -0,0 +1,416 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +""" Codé par 20-100 + +Un bot IRC qui donne les réponses de Helix the fossil. +""" + +import irclib +import ircbot +import random +import time +import re +from commands import getstatusoutput as ex + +# on récupère la config +import config + + +def get_config_logfile(serveur): + """Renvoie le nom du fichier de log en fonction du serveur.""" + serveurs = {"acoeur.crans.org" : "acoeur", "irc.crans.org" : "crans"} + return config.logfile_template % (serveurs[serveur]) + +def log(serveur, channel, auteur=None, message=None): + f = open(get_config_logfile(serveur), "a") + if auteur == message == None: + # alors c'est que c'est pas un channel mais juste une ligne de log + chain = "%s %s" % (time.strftime("%F %T"), channel) + else: + chain = "%s [%s:%s] %s" % (time.strftime("%F %T"), channel, auteur, message) + f.write(chain+"\n") + if config.debug_stdout: + print chain + f.close() + +def is_something(chain, matches, avant=u".*(?:^| )", apres=u"(?:$|\.| |,|;).*", case_sensitive=False, debug=False): + if case_sensitive: + chain = unicode(chain, "utf8") + else: + chain = unicode(chain, "utf8").lower() + allmatches = "("+"|".join(matches)+")" + reg = (avant+allmatches+apres).lower() + o = re.match(reg, chain) + return o + +def is_tag(chain): + return is_something(chain, config.tag_triggers) +def is_tesla(chain): + return is_something(chain, config.tesla_triggers, avant=u"^", apres=u"$", debug=True) + + +class UnicodeBotError(Exception): + pass +def bot_unicode(chain): + try: + unicode(chain, "utf8") + except UnicodeDecodeError as exc: + raise UnicodeBotError + +class HelixBot(ircbot.SingleServerIRCBot): + def __init__(self, serveur, debug=False): + temporary_pseudo = config.irc_pseudo + str(random.randrange(10000, 100000)) + ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)], + temporary_pseudo, "Ceci est l'ircname du bot", 10) + self.debug = debug + self.serveur = serveur + self.overops = config.overops + self.ops = self.overops+config.ops + self.chanlist = config.chanlist + self.stay_channels = config.stay_channels + self.quiet_channels = config.quiet_channels + + def give_me_my_pseudo(self, serv): + serv.privmsg("NickServ", "RECOVER %s %s" % (config.irc_pseudo, config.irc_password)) + serv.privmsg("NickServ", "RELEASE %s %s" % (config.irc_pseudo, config.irc_password)) + time.sleep(0.3) + serv.nick(config.irc_pseudo) + + def on_welcome(self, serv, ev): + self.serv = serv # ça serv ira :) + self.give_me_my_pseudo(serv) + serv.privmsg("NickServ", "identify %s" % (config.irc_password)) + log(self.serveur, "Connected") + if self.debug: + self.chanlist = ["#bot"] + for c in self.chanlist: + log(self.serveur, "JOIN %s" % (c)) + serv.join(c) + + def pourmoi(self, serv, message): + """renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")""" + pseudo = self.nick + size = len(pseudo) + if message[:size] == pseudo and len(message)>size and message[size] == ":": + return (True, message[size+1:].lstrip(" ")) + else: + return (False, message) + + def on_privmsg(self, serv, ev): + message = ev.arguments()[0] + auteur = irclib.nm_to_n(ev.source()) + try: + bot_unicode(message) + except UnicodeBotError: + if config.utf8_trigger: + serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8")) + return + message = message.split() + cmd = message[0].lower() + notunderstood = False + if cmd == "help": + helpdico = {"help":["""HELP + Affiche de l'aide sur la commande""", None, None], + "join": [None, """JOIN + Me fait rejoindre le channel""", None], + "leave": [None, """LEAVE + Me fait quitter le channel (sauf s'il est dans ma stay_list).""", None], + "quiet": [None, """QUIET + Me rend silencieux sur le channel.""", None], + "noquiet": [None, """NOQUIET + Me rend la parole sur le channel.""", None], + "say": [None, None, """SAY + Me fait parler sur le channel."""], + "do": [None, None, """DO + Me fait faitre une action (/me) sur le channel."""], + "stay": [None, None, """STAY + Ajoute le channel à ma stay_list."""], + "nostay": [None, None, """NOSTAY + Retire le channel de ma stay_list."""], + "ops": [None, None, """OPS + Affiche la liste des ops."""], + "overops": [None, None, """OVEROPS + Affiche la liste des overops."""], + "kick": [None, None, """KICK [] + Kicke du channel (Il faut bien entendu que j'y sois op)."""], + "die": [None, None, """DIE + Me déconnecte du serveur IRC."""] + } + helpmsg_default = "Liste des commandes disponibles :\nHELP" + helpmsg_ops = " JOIN LEAVE QUIET NOQUIET RECONNECT" + helpmsg_overops = " SAY DO STAY NOSTAY OPS OVEROPS KICK DIE" + op, overop = auteur in self.ops, auteur in self.overops + if len(message) == 1: + helpmsg = helpmsg_default + if op: + helpmsg += helpmsg_ops + if overop: + helpmsg += helpmsg_overops + else: + helpmsgs = helpdico.get(message[1].lower(), ["Commande inconnue.", None, None]) + helpmsg = helpmsgs[0] + if op and helpmsgs[1]: + if helpmsg: + helpmsg += "\n"+helpmsgs[1] + else: + helpmsg = helpmsgs[1] + if overop and helpmsgs[2]: + if helpmsg: + helpmsg += "\n"+helpmsgs[2] + else: + helpmsg = helpmsgs[2] + for ligne in helpmsg.split("\n"): + serv.privmsg(auteur, ligne) + elif cmd == "join": + if auteur in self.ops: + if len(message)>1: + if message[1] in self.chanlist: + serv.privmsg(auteur, "Je suis déjà sur %s" % (message[1])) + else: + serv.join(message[1]) + self.chanlist.append(message[1]) + serv.privmsg(auteur, "Channels : "+" ".join(self.chanlist)) + log(self.serveur, "priv", auteur, " ".join(message)) + else: + serv.privmsg(auteur, "Channels : "+" ".join(self.chanlist)) + else: + notunderstood = True + elif cmd == "leave": + if auteur in self.ops and len(message)>1: + if message[1] in self.chanlist: + if not (message[1] in self.stay_channels) or auteur in self.overops: + self.quitter(message[1], " ".join(message[2:])) + self.chanlist.remove(message[1]) + log(self.serveur, "priv", auteur, " ".join(message)+"[successful]") + else: + serv.privmsg(auteur, "Non, je reste !") + log(self.serveur, "priv", auteur, " ".join(message)+"[failed]") + else: + serv.privmsg(auteur, "Je ne suis pas sur %s" % (message[1])) + else: + notunderstood = True + elif cmd == "stay": + if auteur in self.overops: + if len(message)>1: + if message[1] in self.stay_channels: + log(self.serveur, "priv", auteur, " ".join(message)+"[failed]") + serv.privmsg(auteur, "Je stay déjà sur %s." % (message[1])) + else: + log(self.serveur, "priv", auteur, " ".join(message)+"[successful]") + self.stay_channels.append(message[1]) + serv.privmsg(auteur, "Stay channels : "+" ".join(self.stay_channels)) + else: + serv.privmsg(auteur, "Stay channels : "+" ".join(self.stay_channels)) + else: + notunderstood = True + elif cmd == "nostay": + if auteur in self.overops: + if len(message)>1: + if message[1] in self.stay_channels: + log(self.serveur, "priv", auteur, " ".join(message)+"[successful]") + self.stay_channels.remove(message[1]) + serv.privmsg(auteur, "Stay channels : "+" ".join(self.stay_channels)) + else: + log(self.serveur, "priv", auteur, " ".join(message)+"[failed]") + serv.privmsg(auteur, "Je ne stay pas sur %s." % (message[1])) + + else: + notunderstood = True + elif cmd == "die": + if auteur in self.overops: + log(self.serveur, "priv", auteur, " ".join(message)+"[successful]") + self.mourir() + else: + notunderstood = True + elif cmd == "quiet": + if auteur in self.ops: + if len(message)>1: + if message[1] in self.quiet_channels: + serv.privmsg(auteur, "Je me la ferme déjà sur %s" % (message[1])) + log(self.serveur, "priv", auteur, " ".join(message)+"[failed]") + else: + self.quiet_channels.append(message[1]) + serv.privmsg(auteur, "Quiet channels : "+" ".join(self.quiet_channels)) + log(self.serveur, "priv", auteur, " ".join(message)+"[successful]") + else: + serv.privmsg(auteur, "Quiet channels : "+" ".join(self.quiet_channels)) + else: + notunderstood = True + elif cmd == "noquiet": + if auteur in self.ops: + if len(message)>1: + if message[1] in self.quiet_channels: + self.quiet_channels.remove(message[1]) + serv.privmsg(auteur, "Quiet channels : "+" ".join(self.quiet_channels)) + log(self.serveur, "priv", auteur, " ".join(message)+"[successful]") + else: + serv.privmsg(auteur, "Je ne me la ferme pas sur %s." % (message[1])) + log(self.serveur, "priv", auteur, " ".join(message)+"[failed]") + else: + notunderstood = True + elif cmd == "say": + if auteur in self.overops and len(message)>2: + serv.privmsg(message[1], " ".join(message[2:])) + log(self.serveur, "priv", auteur, " ".join(message)) + elif len(message) <= 2: + serv.privmsg(auteur, "Syntaxe : SAY ") + else: + notunderstood = True + elif cmd == "do": + if auteur in self.overops and len(message)>2: + serv.action(message[1], " ".join(message[2:])) + log(self.serveur, "priv", auteur, " ".join(message)) + elif len(message) <= 2: + serv.privmsg(auteur, "Syntaxe : DO ") + else: + notunderstood = True + elif cmd == "kick": + if auteur in self.overops and len(message)>2: + serv.kick(message[1], message[2], " ".join(message[3:])) + log(self.serveur, "priv", auteur, " ".join(message)) + elif len(message) <= 2: + serv.privmsg(auteur, "Syntaxe : KICK []") + else: + notunderstood = True + elif cmd == "ops": + if auteur in self.overops: + serv.privmsg(auteur, " ".join(self.ops)) + else: + notunderstood = True + elif cmd == "overops": + if auteur in self.overops: + serv.privmsg(auteur, " ".join(self.overops)) + else: + notunderstood = True + else: + notunderstood = True + if notunderstood: + serv.privmsg(auteur, "Je n'ai pas compris. Essayez HELP…") + + def on_pubmsg(self, serv, ev): + auteur = irclib.nm_to_n(ev.source()) + canal = ev.target() + message = ev.arguments()[0] + try: + bot_unicode(message) + except UnicodeBotError: + if config.utf8_trigger and not canal in self.quiet_channels: + serv.privmsg(canal, (u"%s: %s" % (auteur, random.choice(config.utf8_fail_answers))).encode("utf8")) + return + pour_moi, message = self.pourmoi(serv, message) + if pour_moi and message.split() != []: + cmd = message.split()[0].lower() + try: + args = " ".join(message.split()[1:]) + except: + args = "" + if cmd in ["meurs", "die", "crève"]: + if auteur in self.overops: + log(self.serveur, canal, auteur, message+"[successful]") + self.mourir() + else: + serv.privmsg(canal, ("%s: %s" % (auteur, random.choice(config.quit_fail_messages))).encode("utf8")) + log(self.serveur, canal, auteur, message+"[failed]") + + elif cmd in ["part", "leave", "dégage", "va-t-en", "tut'tiresailleurs, c'estmesgalets"]: + if auteur in self.ops and (not (canal in self.stay_channels) + or auteur in self.overops): + self.quitter(canal) + log(self.serveur, canal, auteur, message+"[successful]") + if canal in self.chanlist: + self.chanlist.remove(canal) + else: + serv.privmsg(canal, ("%s: %s" % (auteur, random.choice(config.leave_fail_messages))).encode("utf8")) + log(self.serveur, canal, auteur, message+"[failed]") + + elif cmd in ["deviens", "pseudo"]: + if auteur in self.ops: + become = args + serv.nick(become) + log(self.serveur, canal, auteur, message+"[successful]") + + elif cmd in ["ping"] and not canal in self.quiet_channels: + serv.privmsg(canal, "%s: pong" % (auteur)) + if is_tag(message) and not canal in self.quiet_channels: + if auteur in self.ops: + action = random.choice(config.tag_actions) + serv.action(canal, action.encode("utf8")) + self.quiet_channels.append(canal) + else: + answer = random.choice(config.tag_answers) + for ligne in answer.split("\n"): + serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8"))) + elif (any([re.match(trig, message) for trig in config.fossil_triggers]) and + not canal in self.quiet_channels): + answer = random.choice(config.fossil_answers) + serv.privmsg(canal, "%s: %s" % (auteur, answer)) + else: + pass + + def on_action(self, serv, ev): + action = ev.arguments()[0] + auteur = irclib.nm_to_n(ev.source()) + channel = ev.target() + try: + bot_unicode(action) + except UnicodeBotError: + if config.utf8_trigger and not channel in self.quiet_channels: + serv.privmsg(channel, (u"%s: %s" % (auteur, random.choice(config.utf8_fail_answers))).encode("utf8")) + return + mypseudo = self.nick + + + def on_kick(self, serv, ev): + auteur = irclib.nm_to_n(ev.source()) + channel = ev.target() + victime = ev.arguments()[0] + raison = ev.arguments()[1] + if victime == self.nick: + log(self.serveur, "%s kické de %s par %s (raison : %s)" % (victime, channel, auteur, raison)) + time.sleep(2) + serv.join(channel) + return # pas de réaction verbale au kick + l1, l2 = config.kick_answers, config.kick_actions + n1, n2 = len(l1), len(l2) + i = random.randrange(n1+n2) + if i >= n1: + serv.action(channel, l2[i-n1].format(auteur).encode("utf8")) + else: + serv.privmsg(channel, l1[i].format(auteur).encode("utf8")) + + def quitter(self, chan, leave_message=None): + if leave_message == None: + leave_message = random.choice(config.leave_messages) + self.serv.part(chan, message=leave_message.encode("utf8")) + + def mourir(self): + quit_message = random.choice(config.quit_messages) + self.die(msg=quit_message.encode("utf8")) + + def _getnick(self): + return self.serv.get_nickname() + nick = property(_getnick) + + +if __name__ == "__main__": + import sys + if len(sys.argv) == 1: + print "Usage : helixbot.py [--debug]" + exit(1) + serveur = sys.argv[1] + if "debug" in sys.argv or "--debug" in sys.argv: + debug = True + else: + debug = False + if "--quiet" in sys.argv: + config.debug_stdout = False + serveurs = {"a♡":"acoeur.crans.org", "acoeur":"acoeur.crans.org", "acoeur.crans.org":"acoeur.crans.org", + "irc":"irc.crans.org", "crans":"irc.crans.org", "irc.crans.org":"irc.crans.org"} + try: + serveur = serveurs[serveur] + except KeyError: + print "Server Unknown : %s" % (serveur) + exit(404) + bot = HelixBot(serveur, debug) + bot.start()