From e5a491afbd015fa9a95a2fef3199fb8852ee93e2 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Fri, 13 Jul 2012 21:06:11 +0200 Subject: [PATCH] Version 0.1 --- .gitignore | 1 + config.py | 39 ++++ saturnin.py | 505 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 545 insertions(+) create mode 100644 config.py create mode 100755 saturnin.py diff --git a/.gitignore b/.gitignore index a57fd2e..c92b851 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Custom # ################### *~ +scores.pickle # Compiled source # ################### diff --git a/config.py b/config.py new file mode 100644 index 0000000..7f34924 --- /dev/null +++ b/config.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# -*- coding:utf8 -*- + +# Configuration d'un bot IRC (squelette de base vide) + +debug_stdout=True + +# la config irc-related +irc_password="96ac0342a3ccf9553e3d4c9da9b821b0" +irc_pseudo="Saturnin" +chanlist=["#flood", "#bot"] # liste des chans à rejoindre +stay_channels=["#flood", "#bot"] # liste des chans que le bot ne quitte pas, même sur demande d'un leave de la part d'un OP +quiet_channels=[] # liste des chans sur lesquels le bot ne parle pas +spawn_channels = ["#flood"] + +# les logs +logfile_template="saturnin.%s.log" # fichier de log (%s est remplacé par le nom du serveur) + +# les ops +overops=["[20-100]"] # liste des OVEROPs (meilleurs que les OP ^^) +ops=[] # liste des OPs + +# config UTF8-fail +utf8_fail_answers = [u"Couac !"] +utf8_trigger = True # râlé-je en cas de non-utf8 ? + +# config on m'a demandé de mourir/partir +quit_messages=[u"For a man who has done his natural duty, death is as natural as sleep."] +leave_messages=quit_messages + +quit_fail_messages = [u"Do they teach you that in the CIA?"] +leave_fail_messages = quit_fail_messages + +# config spécial canard +canards = [ur"\_%s<" % (tete) for tete in [u"0óøoOØ"]] + [""] + +killwords = [u"pan", u"bim", u"bang"] + +score_file="scores.pickle" \ No newline at end of file diff --git a/saturnin.py b/saturnin.py new file mode 100755 index 0000000..931ecd7 --- /dev/null +++ b/saturnin.py @@ -0,0 +1,505 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +# Codé par 20-100 + +# Un bot IRC pour remplacer le canard. +# parce que le canard, c'est le bien et que braice ne pong pas + +import irclib +import ircbot +import threading +import random +import time +import socket, ssl, json +import pickle +import re +import os +from commands import getstatusoutput as ex + +# on récupère la config +import config + + + +def get_config_logfile(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 + +regexp_pan = re.compile(u".*(" + "|".join(config.killwords) + u").*") +def is_pan(chain): + return regexp_pan.match(unicode(chain,"utf8").lower()) + +class UnicodeBotError(Exception): + pass +def bot_unicode(chain): + try: + unicode(chain,"utf8") + except UnicodeDecodeError as exc: + raise UnicodeBotError + +class Saturnin(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,"Coin ? ©braice [mais 'faut frapper 20-100]", 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 + self.spawn_channels=config.spawn_channels + self.status = { chan : False for chan in self.spawn_channels } + self.last_perdu=0 + + 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 = self.spawn_channels = ["#bot"] + self.status = { chan : False for chan in self.spawn_channels } + 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: + test=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], + "score":["""SCORE + Affiche votre score""", None, None], + "scores":["""SCORES + Afficher tous les scores""", 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 SCORE SCORES" + helpmsg_ops=" JOIN LEAVE QUIET NOQUIET LOST" + 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 + elif cmd=="score": + if len(message)>1: + if len(message) in [3,4] and message[1].lower()=="transfert": + scores=self.get_scores() + de,to=auteur,message[2] + value=scores.get(de,0) + if len(message)==4: + try: + asked=int(message[3]) + except ValueError: + serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT []") + return + else: + asked=value + if value==0: + serv.privmsg(auteur,"Vous n'avez pas de points") + return + elif asked<=0: + serv.privmsg(auteur,"Bien tenté…") + return + elif asked>value: + serv.privmsg(auteur,"Vous n'avez que %s points"%(value)) + return + else: + self.add_score(de,-asked) + self.add_score(to,asked) + serv.privmsg(auteur,"Transfert de %s points de %s à %s"%(asked,de,to)) + else: + serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT []") + else: + self.sendscore(auteur) + elif cmd=="scores": + if len(message)==1: + self.sendscores(auteur) + elif auteur in self.overops: + souscmd=message[1].lower() + if souscmd=="del": + if len(message)==3: + todelete=message[2] + scores=self.get_scores() + if scores.has_key(todelete): + del scores[todelete] + self.save_scores(scores) + serv.privmsg(auteur,"Score de %s supprimé"%(todelete)) + else: + serv.privmsg(auteur,"Ce score n'existe pas : %s"%(todelete)) + else: + serv.privmsg(auteur,"Syntaxe : SCORES DEL ") + elif souscmd in ["add","sub"]: + if len(message)==4: + toadd,val=message[2],message[3] + try: + val=int(val) + except ValueError: + serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} ") + return + if souscmd=="sub": + val=-val + self.add_score(toadd,val) + serv.privmsg(auteur,"Done") + else: + serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} ") + else: + serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} []") + else: + notunderstood=True + else: + notunderstood=True + if notunderstood: + serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…") + + def sendscore(self, to): + self.serv.privmsg(to, "Votre score : %s"%(self.get_scores().get(to,0)) ) + + def sendscores(self, to): + scores=self.get_scores().items() + # trie par score + scores.sort(lambda x,y:cmp(x[1],y[1])) + scores.reverse() + self.serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores])) + # trie par pseudo + scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower())) + self.serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores])) + + def on_pubmsg(self, serv, ev): + auteur = irclib.nm_to_n(ev.source()) + canal = ev.target() + message = ev.arguments()[0] + try: + test=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 == "score": + self.sendscore(auteur) + elif cmd == "scores": + self.sendscores(auteur) + else: + if is_pan(message): + self.shot(auteur) + + def on_action(self, serv, ev): + action = ev.arguments()[0] + auteur = irclib.nm_to_n(ev.source()) + channel = ev.target() + #~ try: + #~ test=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) + #~ 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 get_scores(self): + f=open(config.score_file) + scores=pickle.load(f) + f.close() + return scores + + def add_score(self,pseudo,value): + scores=self.get_scores() + if scores.has_key(pseudo): + scores[pseudo]+=value + else: + scores[pseudo]=value + self.save_scores(scores) + + def save_scores(self,scores): + f=open(config.score_file,"w") + pickle.dump(scores,f) + f.close() + + def _getnick(self): + return self.serv.get_nickname() + nick=property(_getnick) + + +if __name__=="__main__": + import sys + if len(sys.argv)==1: + print "Usage : saturnin.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 = Saturnin(serveur,debug) + bot.start() -- 2.39.2