--- /dev/null
+#!/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()