4 # Codé par 20-100 le 23/04/12
6 # Un bot IRC qui sort des déconnaissances
16 config_password
="PatrickSébastien"
17 config_pseudo
="deconnaisseur"
18 config_chanlist
=["#bot","#flood"]
19 config_play_channels
=["#flood"]
20 config_stay_channels
=["#flood","#bot"]
21 config_overops
=["[20-100]","[20-100]_","PEB"]
22 config_ops
=["Nit","Eguel","Harry"]
24 config_source_file_template
="deconnaissances.%s.txt" #il faut rajouter le nom du serveur
25 def get_config_source_file(serveur
):
26 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
27 return config_source_file_template
%(serveurs
[serveur
])
28 ttrig
=120 #time trigger (normalement 120, mais diminué pour les tests)
29 Ttrig
=600 #between two enigms
30 config_time_incompressible
=15 #on peut pas retrigger en dessous de ce temps (60)
31 config_time_incompressible_clue
=60 #on peut pas forcer la demande d'indice en dessous
33 config_score_file
="scores.pickle"
35 class UnicodeBotError(Exception):
37 def bot_unicode(chain
):
40 except UnicodeDecodeError:
43 def log(channel
,auteur
=None,message
=None):
44 #f=open(config_logfile,"a")
45 #if auteur==message==None:
48 # chain="%s [%s:%s] %s"%(time.strftime("%T"),channel,auteur,message)
56 """Renvoie une regexp plus tolérante"""
57 reg
=unicode(regexp
,"utf8").lower()
58 reg
=reg
.replace(u
"á",u
"(á|a)").replace(u
"à",u
"(à|a)").replace(u
"â",u
"(â|a)").replace(u
"ä",u
"(ä|a)")
59 reg
=reg
.replace(u
"é",u
"(é|e)").replace(u
"è",u
"(è|e)").replace(u
"ê",u
"(ê|e)").replace(u
"ë",u
"(ë|e)")
60 reg
=reg
.replace(u
"í",u
"(í|i)").replace(u
"ì",u
"(ì|i)").replace(u
"î",u
"(î|i)").replace(u
"ï",u
"(ï|i)")
61 reg
=reg
.replace(u
"ó",u
"(ó|o)").replace(u
"ò",u
"(ò|o)").replace(u
"ô",u
"(ô|o)").replace(u
"ö",u
"(ö|o)")
62 reg
=reg
.replace(u
"ú",u
"(ú|u)").replace(u
"ù",u
"(ù|u)").replace(u
"û",u
"(û|u)").replace(u
"ü",u
"(ü|u)")
63 reg
=reg
.replace(u
"ý",u
"(ý|y)").replace(u
"ỳ",u
"(ỳ|y)").replace(u
"ŷ",u
"(ŷ|y)").replace(u
"ÿ",u
"(ÿ|y)")
64 reg
=reg
.replace(u
"œ",u
"(œ|oe)").replace(u
"æ",u
"(æ|ae)")
67 class RefuseError(Exception):
70 class Deconnaisseur(ircbot
.SingleServerIRCBot
):
71 def __init__(self
,serveur
,debug
=False):
72 temporary_pseudo
=config_pseudo
+str(random
.randrange(10000,100000))
73 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
74 temporary_pseudo
,"Un bot irc.[flagellez 20-100, il le mérite]", 10)
77 self
.overops
=config_overops
78 self
.ops
=self
.overops
+config_ops
79 self
.chanlist
=config_chanlist
80 self
.stay_channels
=config_stay_channels
81 self
.play_channels
=config_play_channels
82 self
.play_status
={i
:[0] for i
in self
.play_channels
}
83 self
.quiet_channels
={}
85 def give_me_my_pseudo(self
,serv
):
86 serv
.privmsg("NickServ","RECOVER %s %s"%(config_pseudo
,config_password
))
87 serv
.privmsg("NickServ","RELEASE %s %s"%(config_pseudo
,config_password
))
89 serv
.nick(config_pseudo
)
91 def on_welcome(self
, serv
, ev
):
92 self
.give_me_my_pseudo(serv
)
93 serv
.privmsg("NickServ","identify %s"%(config_password))
96 self
.chanlist
=["#bot"]
97 self
.play_channels
=["#bot"]
98 for c
in self
.chanlist
:
101 for c
in self
.play_channels
:
102 token
=time
.time()-3600
103 self
.play_status
[c
]=[0,token
]
104 serv
.execute_delayed(random
.randrange(ttrig
),self
.start_enigme
,(serv
,c
,token
))
106 def start_enigme(self
,serv
,channel
,token
=None):
107 if self
.play_status
[channel
][0]==0 and channel
in self
.play_channels
:
109 if token
==self
.play_status
[channel
][-1]:
112 if time
.time() > self
.play_status
[channel
][-1]+config_time_incompressible
:
117 enigme
,indice
,answer_reg
,answer
=self
.get_enigme()
118 print "%s; %s; %s; %s"%(enigme
, indice
, answer_reg
, answer
)
119 serv
.privmsg(channel
,enigme
)
121 self
.play_status
[channel
]=[1,enigme
,indice
,answer_reg
,answer
,token
]
122 serv
.execute_delayed(random
.randrange(ttrig
*3,ttrig
*5),self
.give_indice
,(serv
,channel
,token
))
125 def give_indice(self
,serv
,channel
,token
):
126 if self
.play_status
[channel
][0]==1:
128 # c'est donc que l'indice a été demandé
129 if self
.play_status
[channel
][-1]+config_time_incompressible_clue
<time
.time():
130 token
=self
.play_status
[channel
][-1]
131 if self
.play_status
[channel
][-1]==token
:
132 indice
=self
.play_status
[channel
][2]
133 serv
.privmsg(channel
,"indice : %s"%(indice))
134 self
.play_status
[channel
][0]=2
135 serv
.execute_delayed(random
.randrange(ttrig
*1,ttrig
*3),self
.give_answer
,(serv
,channel
,token
))
136 def give_answer(self
,serv
,channel
,token
):
137 if self
.play_status
[channel
][0]==2 and self
.play_status
[channel
][-1]==token
:
138 answer
=self
.play_status
[channel
][4]
139 serv
.privmsg(channel
,"C'était : %s"%(answer))
141 self
.play_status
[channel
]=[0,token
]
142 serv
.execute_delayed(random
.randrange(Ttrig
*5,Ttrig
*10),self
.start_enigme
,(serv
,channel
,token
))
144 def get_enigme(self
):
145 f
=open(get_config_source_file(self
.serveur
))
147 l
=re
.findall("%\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n",t
)
148 l
=[list(i
) for i
in l
if len(i
)==5]
149 l
.sort(lambda x
,y
: cmp(int(x
[4]),int(y
[4])))
150 # on récupère le nombre d'occurrences le plus faible
152 # on garde que ceux qui ont le même nombre d'occurrences
153 l_mini
=[en
for en
in l
if en
[4]==mini
]
154 # on tire au hasard dedans
155 choisi
=random
.randrange(len(l_mini
))
156 enigme
,indice
,answer_reg
,answer
,_
=l_mini
[choisi
]
157 real_index
=l
.index(l_mini
[choisi
])
158 l
[real_index
][4]=str(int(l
[real_index
][4])+1)
159 f
=open(get_config_source_file(self
.serveur
),"w")
160 f
.write("%\n"+"\n%\n".join(["%s\n%s\n%s\n%s\n%s"%(i
[0],i
[1],i
[2],i
[3],i
[4]) for i
in l
])+"\n%")
162 return enigme
,indice
,answer_reg
,answer
164 def pourmoi(self
, serv
, message
):
165 pseudo
=serv
.get_nickname()
167 if message
[:size
]==pseudo
and len(message
)>size
and message
[size
]==":":
168 return (True,message
[size
+1:].strip(" "))
170 return (False,message
)
172 def on_privmsg(self
, serv
, ev
):
173 message
=ev
.arguments()[0]
174 auteur
= irclib
.nm_to_n(ev
.source())
176 test
=bot_unicode(message
)
177 except UnicodeBotError
:
179 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
181 message
=message
.split()
182 cmd
=message
[0].lower()
185 helpmsg_default
="""Liste des commandes :
186 HELP Affiche ce message d'aide
187 SCORE Affiche ton score (SCORE TRANSFERT <pseudo> [<n>] pour transférer des points)
188 SCORES Affiche les scores"""
190 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
191 LEAVE Faire quitter un channel
192 PLAY Passe un channel en mode "jouer"
193 NOPLAY Passe un channel en mode "ne pas jouer"
194 QUIET Se taire sur un channel
195 NOQUIET Opposé de QUIET"""
197 SCORES {DEL|ADD|SUB} Tu veux un dessin ?
198 SAY Fais envoyer un message sur un chan ou à une personne
199 STAY Ignorera les prochains LEAVE pour un chan
200 NOSTAY Opposé de STAY
201 STATUS Montre l'état courant
203 helpmsg
=helpmsg_default
204 if auteur
in self
.ops
:
206 if auteur
in self
.overops
:
207 helpmsg
+=helpmsg_overops
208 for ligne
in helpmsg
.split("\n"):
209 serv
.privmsg(auteur
,ligne
)
211 if auteur
in self
.ops
:
213 if message
[1] in self
.chanlist
:
214 serv
.privmsg(auteur
,"Je suis déjà sur %s"%(message
[1]))
216 serv
.join(message
[1])
217 self
.chanlist
.append(message
[1])
218 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
219 log("priv",auteur
," ".join(message
))
221 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
225 if auteur
in self
.ops
and len(message
)>1:
226 if message
[1] in self
.chanlist
:
227 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
228 serv
.part(message
[1])
229 self
.chanlist
.remove(message
[1])
230 log("priv",auteur
," ".join(message
)+"[successful]")
232 serv
.privmsg(auteur
,"Non, je reste !")
233 log("priv",auteur
," ".join(message
)+"[failed]")
235 serv
.privmsg(auteur
,"Je ne suis pas sur %s"%(message
[1]))
239 if auteur
in self
.overops
:
241 if message
[1] in self
.stay_channels
:
242 serv
.privmsg(auteur
,"Je stay déjà sur %s."%(message
[1]))
243 log("priv",auteur
," ".join(message
)+"[failed]")
245 self
.stay_channels
.append(message
[1])
246 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
247 log("priv",auteur
," ".join(message
)+"[successful]")
249 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
253 if auteur
in self
.overops
:
255 if message
[1] in self
.stay_channels
:
256 self
.stay_channels
.remove(message
[1])
257 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
258 log("priv",auteur
," ".join(message
)+"[successful]")
260 serv
.privmsg(auteur
,"Je ne stay pas sur %s."%(message
[1]))
261 log("priv",auteur
," ".join(message
)+"[failed]")
265 if auteur
in self
.ops
:
267 if message
[1] in self
.play_channels
:
268 serv
.privmsg(auteur
,"Je play déjà sur %s."%(message
[1]))
269 log("priv",auteur
," ".join(message
)+"[failed]")
271 self
.play_channels
.append(message
[1])
272 self
.play_status
[message
[1]]=[0,time
.time()-3600]
273 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
274 log("priv",auteur
," ".join(message
)+"[successful]")
276 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
280 if auteur
in self
.ops
:
282 if message
[1] in self
.play_channels
:
283 self
.play_channels
.remove(message
[1])
284 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
285 log("priv",auteur
," ".join(message
)+"[successful]")
287 serv
.privmsg(auteur
,"Je ne play pas sur %s."%(message
[1]))
288 log("priv",auteur
," ".join(message
)+"[failed]")
292 if auteur
in self
.ops
:
294 if message
[1] in self
.quiet_channels
:
295 serv
.privmsg(auteur
,"Je me la ferme déjà sur %s"%(message
[1]))
296 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
298 self
.quiet_channels
.append(message
[1])
299 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
300 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
302 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
306 if auteur
in self
.ops
:
308 if message
[1] in self
.quiet_channels
:
309 self
.quiet_channels
.remove(message
[1])
310 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
311 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
313 serv
.privmsg(auteur
,"Je ne me la ferme pas sur %s."%(message
[1]))
314 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
317 elif cmd
in ["states","status"]:
318 if auteur
in self
.overops
:
319 for k
in self
.play_status
.keys():
320 serv
.privmsg(auteur
,"%s : %s"%(k
,"; ".join([str(i
) for i
in self
.play_status
[k
]])))
322 if auteur
in self
.overops
and len(message
)>2:
323 serv
.privmsg(message
[1]," ".join(message
[2:]))
324 log("priv",auteur
," ".join(message
))
325 elif len(message
)<=2:
326 serv
.privmsg(auteur
,"Syntaxe : SAY <channel> <message>")
330 if auteur
in self
.overops
:
334 if len(message
) in [3,4] and message
[1].lower()=="transfert":
335 scores
=self
.get_scores()
336 de
,to
=auteur
,message
[2]
337 value
=scores
.get(de
,0)
340 asked
=int(message
[3])
342 serv
.privmsg(auteur
,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
347 serv
.privmsg(auteur
,"Vous n'avez pas de points")
350 serv
.privmsg(auteur
,"Bien tenté…")
353 serv
.privmsg(auteur
,"Vous n'avez que %s points"%(value))
356 self
.add_score(de
,-asked
)
357 self
.add_score(to
,asked
)
358 serv
.privmsg(auteur
,"Transfert de %s points de %s à %s"%(asked
,de
,to
))
360 serv
.privmsg(auteur
,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
362 serv
.privmsg(auteur
,"Votre score : %s"%(self
.get_scores().get(auteur
,0)) )
365 scores
=self
.get_scores().items()
367 scores
.sort(lambda x
,y
:cmp(x
[1],y
[1]))
369 serv
.privmsg(auteur
,"Scores by score : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
371 scores
.sort(lambda x
,y
:cmp(x
[0].lower(),y
[0].lower()))
372 serv
.privmsg(auteur
,"Scores by pseudo : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
373 elif auteur
in self
.overops
:
374 souscmd
=message
[1].lower()
378 scores
=self
.get_scores()
379 if scores
.has_key(todelete
):
381 self
.save_scores(scores
)
382 serv
.privmsg(auteur
,"Score de %s supprimé"%(todelete))
384 serv
.privmsg(auteur
,"Ce score n'existe pas : %s"%(todelete))
386 serv
.privmsg(auteur
,"Syntaxe : SCORES DEL <pseudo>")
387 elif souscmd
in ["add","sub"]:
389 toadd
,val
=message
[2],message
[3]
393 serv
.privmsg(auteur
,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
397 self
.add_score(toadd
,val
)
398 serv
.privmsg(auteur
,"Done")
400 serv
.privmsg(auteur
,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
402 serv
.privmsg(auteur
,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
408 serv
.privmsg(auteur
,"Je n'ai pas compris. Essaye HELP…")
410 def on_pubmsg(self
, serv
, ev
):
411 auteur
= irclib
.nm_to_n(ev
.source())
413 message
= ev
.arguments()[0]
415 test
=bot_unicode(message
)
416 except UnicodeBotError
:
417 if not canal
in self
.quiet_channels
:
419 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
422 pour_moi
,message
=self
.pourmoi(serv
,message
)
423 if pour_moi
and message
.split()!=[]:
424 cmd
=message
.split()[0].lower()
426 args
=" ".join(message
.split()[1:])
429 if cmd
in ["meurs","die","crève"]:
430 if auteur
in self
.overops
:
432 log(canal
,auteur
,message
+"[successful]")
434 serv
.privmsg(canal
,"%s: crève !"%(auteur))
435 log(canal
,auteur
,message
+"[failed]")
436 if cmd
in ["meur", "meurt","meurre","meurres"] and not canal
in self
.quiet_channels
:
437 serv
.privmsg(canal
,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
438 if cmd
in ["part","leave","dégage"]:
439 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
440 or auteur
in self
.overops
):
441 serv
.part(canal
,message
="Éjecté par %s"%(auteur))
442 log(canal
,auteur
,message
+"[successful]")
443 self
.chanlist
.remove(canal
)
445 serv
.privmsg(canal
,"%s: Non, je reste !"%(auteur))
446 log(canal
,auteur
,message
+"[failed]")
448 if cmd
in ["deviens","pseudo"]:
449 if auteur
in self
.ops
:
452 log(canal
,auteur
,message
+"[successful]")
454 if cmd
in ["coucou"] and not canal
in self
.quiet_channels
:
455 serv
.privmsg(canal
,"%s: coucou"%(auteur))
456 if cmd
in ["ping"] and not canal
in self
.quiet_channels
:
457 serv
.privmsg(canal
,"%s: pong"%(auteur))
458 if cmd
in ["déconnaissance","deconnaissance","énigme","enigme","encore"]:
459 if canal
in self
.play_channels
:
460 if self
.play_status
.get(canal
,[-1])[0]==0:
462 self
.start_enigme(serv
,canal
)
464 serv
.privmsg(canal
,"%s: Je peux souffler une minute ?"%(auteur))
466 serv
.privmsg(canal
,"%s: Rappel : %s"%(auteur
,self
.play_status
[canal
][1]))
468 serv
.privmsg(canal
,"%s: pas ici…"%(auteur))
469 if cmd
=="indice" and canal
in self
.play_channels
:
470 self
.give_indice(serv
,canal
,None)
476 if self
.play_status
.get(canal
,[-1])[0] in [1,2]:
477 answer_regexp
=self
.play_status
[canal
][3]
478 if re
.match(tolere(answer_regexp
),unicode(message
,"utf8").lower()):
479 answer
=self
.play_status
[canal
][4]
480 serv
.privmsg(canal
,"%s: bravo ! (C'était %s)"%(auteur
,answer
))
481 self
.add_score(auteur
,1)
483 self
.play_status
[canal
]=[0,token
]
484 serv
.execute_delayed(random
.randrange(Ttrig
*5,Ttrig
*10),self
.start_enigme
,(serv
,canal
,token
))
485 def get_scores(self
):
486 f
=open(config_score_file
)
487 scores
=pickle
.load(f
)
491 def add_score(self
,pseudo
,value
):
492 scores
=self
.get_scores()
493 if scores
.has_key(pseudo
):
494 scores
[pseudo
]+=value
497 self
.save_scores(scores
)
499 def save_scores(self
,scores
):
500 f
=open(config_score_file
,"w")
501 pickle
.dump(scores
,f
)
504 if __name__
=="__main__":
507 print "Usage : deconnaisseur.py <serveur> [--debug]"
510 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
514 serveurs
={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
515 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
517 serveur
=serveurs
[serveur
]
519 print "Server Unknown : %s"%(serveur)
521 deco
=Deconnaisseur(serveur
,debug
)