]> gitweb.pimeys.fr Git - bots/saturnin.git/commitdiff
Version 0.1
authorVincent Le Gallic <legallic@crans.org>
Fri, 13 Jul 2012 19:06:11 +0000 (21:06 +0200)
committerVincent Le Gallic <legallic@crans.org>
Fri, 13 Jul 2012 19:06:11 +0000 (21:06 +0200)
.gitignore
config.py [new file with mode: 0644]
saturnin.py [new file with mode: 0755]

index a57fd2e1371d220e6a03263011d32a51bebfb738..c92b851d6d0e9a8a592ba4abb55a7ba352a32c3b 100644 (file)
@@ -1,6 +1,7 @@
 # Custom #
 ###################
 *~
+scores.pickle
 
 # Compiled source #
 ###################
diff --git a/config.py b/config.py
new file mode 100644 (file)
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 (executable)
index 0000000..931ecd7
--- /dev/null
@@ -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 <commande>
+ 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 <channel>
+ Me fait rejoindre le channel""",None],
+ "leave": [None,"""LEAVE <channel>
+ Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
+ "quiet": [None,"""QUIET <channel>
+ Me rend silencieux sur le channel.""",None],
+ "noquiet": [None,"""NOQUIET <channel>
+ Me rend la parole sur le channel.""",None],
+ "say": [None,None,"""SAY <channel> <message>
+ Me fait parler sur le channel."""],
+ "do": [None,None,"""DO <channel> <action>
+ Me fait faitre une action (/me) sur le channel."""],
+ "stay": [None,None,"""STAY <channel>
+ Ajoute le channel à ma stay_list."""],
+ "nostay": [None,None,"""NOSTAY <channel>
+ 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 <channel> <pseudo> [<raison>]
+ Kicke <pseudo> 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 <channel> <message>")
+            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 <channel> <action>")
+            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 <channel> <pseudo> [<raison>]")
+            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 <pseudo> [<n>]")
+                            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 <pseudo> [<n>]")
+            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 <pseudo>")
+                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} <pseudo> <n>")
+                            return
+                        if souscmd=="sub":
+                            val=-val
+                        self.add_score(toadd,val)
+                        serv.privmsg(auteur,"Done")
+                    else:
+                        serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
+                else:
+                    serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
+            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 <serveur> [--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()