4 # Codé par 20-100 le 23/04/12
6 # Un bot IRC qui sort des déconnaissances
15 from remplace_accents
import remplace_accents
17 config_password
="PatrickSébastien"
18 config_pseudo
="deconnaisseur"
19 config_chanlist
=["#bot","#flood"]
20 config_play_channels
=["#flood"]
21 config_stay_channels
=["#flood","#bot"]
22 config_overops
=["[20-100]","[20-100]_","PEB"]
23 config_ops
=["Nit","Eguel","Harry"]
25 config_source_file
="deconnaissances.txt"
26 config_played_file_template
="played.%s.txt" #il faut rajouter le nom du serveur
27 def get_config_played_file(serveur
):
28 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
29 return config_played_file_template
%(serveurs
[serveur
])
30 ttrig
=120 #time trigger (normalement 120, mais diminué pour les tests)
31 Ttrig
=600 #between two enigms
32 config_time_incompressible
=15 #on peut pas retrigger en dessous de ce temps (60)
33 config_time_incompressible_clue
=60 #on peut pas forcer la demande d'indice en dessous
35 config_score_file
="scores.pickle"
37 config_tag_triggers
=[u
"t(|a)g",u
"ta gueule",u
"la ferme",u
"ferme( |-)la",u
"tais-toi",u
"chut"]
38 config_tag_actions
=[u
"se tait",u
"ferme sa gueule",u
"se la ferme",u
"la ferme"]
39 config_tag_answers
=[u
"J'me tais si j'veux !",
40 u
"Je t'entends pas :°",
42 u
"Non, j'ai pas envie",
43 u
"Peut-être quand toi tu la fermeras, et encore…"]
46 config_level3
=["[20-100]","Petite-Peste","PEB"]
48 config_debug_stdout
= True
49 config_logfile_template
="deconnaisseur.%s.log"
50 def get_config_logfile(serveur
):
51 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
52 return config_logfile_template
%(serveurs
[serveur
])
54 class UnicodeBotError(Exception):
56 def bot_unicode(chain
):
59 except UnicodeDecodeError:
62 def log(serveur
,channel
,auteur
=None,message
=None):
63 f
=open(get_config_logfile(serveur
),"a")
64 if auteur
==message
==None:
65 # alors c'est que c'est pas un channel mais juste une ligne de log
66 chain
="%s %s"%(time
.strftime("%F %T"),channel
)
68 chain
="%s [%s:%s] %s"%(time
.strftime("%F %T"),channel
,auteur
,message
)
70 if config_debug_stdout
:
75 def reussi(message
,answer
,answer_regexp
,auteur
):
76 if auteur
in config_level3
:
77 return answer
in message
78 if auteur
in config_level2
:
79 return remplace_accents(answer
) in message
81 if re
.match(remplace_accents(answer_regexp
).lower(),remplace_accents(message
).lower()):
84 def is_something(chain
,matches
,avant
=u
".*(?:^| )",apres
=u
"(?:$|\.| |,|;).*",case_sensitive
=False,debug
=False):
86 chain
=unicode(chain
,"utf8")
88 chain
=unicode(chain
,"utf8").lower()
89 allmatches
="("+"|".join(matches
)+")"
90 reg
=(avant
+allmatches
+apres
).lower()
95 return is_something(chain
,config_tag_triggers
)
97 class RefuseError(Exception):
100 class Deconnaisseur(ircbot
.SingleServerIRCBot
):
101 def __init__(self
,serveur
,debug
=False):
102 temporary_pseudo
=config_pseudo
+str(random
.randrange(10000,100000))
103 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
104 temporary_pseudo
,"Un bot irc.[flagellez 20-100, il le mérite]", 10)
107 self
.overops
=config_overops
108 self
.ops
=self
.overops
+config_ops
109 self
.chanlist
=config_chanlist
110 self
.stay_channels
=config_stay_channels
111 self
.play_channels
=config_play_channels
112 self
.play_status
={i
:[0] for i
in self
.play_channels
}
113 self
.quiet_channels
=[]
115 def give_me_my_pseudo(self
,serv
):
116 serv
.privmsg("NickServ","RECOVER %s %s"%(config_pseudo
,config_password
))
117 serv
.privmsg("NickServ","RELEASE %s %s"%(config_pseudo
,config_password
))
119 serv
.nick(config_pseudo
)
121 def on_welcome(self
, serv
, ev
):
122 self
.serv
=serv
# ça serv ira :)
123 self
.give_me_my_pseudo(serv
)
124 serv
.privmsg("NickServ","identify %s"%(config_password))
125 log(self
.serveur
,"Connected")
127 self
.chanlist
=["#bot"]
128 self
.play_channels
=["#bot"]
129 for c
in self
.chanlist
:
130 log(self
.serveur
,"JOIN %s"%(c))
132 for c
in self
.play_channels
:
133 token
=time
.time()-3600
134 self
.play_status
[c
]=[0,token
]
135 serv
.execute_delayed(random
.randrange(ttrig
),self
.start_enigme
,(serv
,c
,token
))
137 def start_enigme(self
,serv
,channel
,token
=None):
138 if self
.play_status
[channel
][0]==0 and channel
in self
.play_channels
:
140 if token
==self
.play_status
[channel
][-1]:
143 if time
.time() > self
.play_status
[channel
][-1]+config_time_incompressible
:
148 enigme
,indice
,answer_reg
,answer
=self
.get_enigme()
149 log(self
.serveur
,channel
,u
"$Énigme$".encode("utf8"),("%s; %s; %s; %s"%(enigme
, indice
, answer_reg
, answer
)).encode("utf8"))
150 serv
.privmsg(channel
,enigme
.encode("utf8"))
152 self
.play_status
[channel
]=[1,enigme
,indice
,answer_reg
,answer
,token
]
153 serv
.execute_delayed(random
.randrange(ttrig
*3,ttrig
*5),self
.give_indice
,(serv
,channel
,token
))
156 def give_indice(self
,serv
,channel
,token
):
157 if self
.play_status
[channel
][0]==1:
159 # c'est donc que l'indice a été demandé
160 if self
.play_status
[channel
][-1]+config_time_incompressible_clue
<time
.time():
161 token
=self
.play_status
[channel
][-1]
162 if self
.play_status
[channel
][-1]==token
:
163 indice
=self
.play_status
[channel
][2]
164 serv
.privmsg(channel
,"indice : %s"%(indice).encode("utf8"))
165 self
.play_status
[channel
][0]=2
166 serv
.execute_delayed(random
.randrange(ttrig
*1,ttrig
*3),self
.give_answer
,(serv
,channel
,token
))
167 def give_answer(self
,serv
,channel
,token
):
168 if self
.play_status
[channel
][0]==2 and self
.play_status
[channel
][-1]==token
:
169 answer
=self
.play_status
[channel
][4]
170 serv
.privmsg(channel
,"C'était : %s"%(answer).encode("utf8"))
172 self
.play_status
[channel
]=[0,token
]
173 serv
.execute_delayed(random
.randrange(Ttrig
*5,Ttrig
*10),self
.start_enigme
,(serv
,channel
,token
))
175 def get_enigme(self
):
176 # on récupère les déconnaissances
177 f
=open(config_source_file
)
180 l
=re
.findall("%\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n",t
)
181 dec
={int(i
[0]):list(i
[1:]) for i
in l
if len(i
)==5}
182 # on va chercher combien de fois elles ont été jouées
183 played_file
=get_config_played_file(self
.serveur
)
187 l
=re
.findall("(.*):(.*)",t
)
188 played
={int(i
[0]):int(i
[1]) for i
in l
}
189 # on récupère le nombre d'occurrences le plus faible
190 mini
=min(played
.values())
191 # on choisit un id dans ceux qui ont ce nombre d'occurences
192 id_choisi
=random
.choice([k
for k
,v
in played
.items() if v
==mini
])
193 enigme
,indice
,answer_reg
,answer
=dec
[id_choisi
]
194 # on incrémente la choisie
196 # on enregistre le played_file
197 f
=open(played_file
,"w")
198 f
.write("\n".join(["%-3s : %s"%(k
,v
) for k
,v
in played
.items()]))
200 return enigme
.decode("utf8"),indice
.decode("utf8"),answer_reg
.decode("utf8"),answer
.decode("utf8")
202 def pourmoi(self
, serv
, message
):
205 if message
[:size
]==pseudo
and len(message
)>size
and message
[size
]==":":
206 return (True,message
[size
+1:].strip(" "))
208 return (False,message
)
210 def on_privmsg(self
, serv
, ev
):
211 message
=ev
.arguments()[0]
212 auteur
= irclib
.nm_to_n(ev
.source())
214 test
=bot_unicode(message
)
215 except UnicodeBotError
:
217 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
219 message
=message
.split()
220 cmd
=message
[0].lower()
223 helpmsg_default
="""Liste des commandes :
224 HELP Affiche ce message d'aide
225 SCORE Affiche ton score (SCORE TRANSFERT <pseudo> [<n>] pour transférer des points)
226 SCORES Affiche les scores"""
228 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
229 LEAVE Faire quitter un channel
230 PLAY Passe un channel en mode "jouer"
231 NOPLAY Passe un channel en mode "ne pas jouer"
232 QUIET Se taire sur un channel
233 NOQUIET Opposé de QUIET"""
235 SCORES {DEL|ADD|SUB} Tu veux un dessin ?
236 SAY Fais envoyer un message sur un chan ou à une personne
237 STAY Ignorera les prochains LEAVE pour un chan
238 NOSTAY Opposé de STAY
239 STATUS Montre l'état courant
241 helpmsg
=helpmsg_default
242 if auteur
in self
.ops
:
244 if auteur
in self
.overops
:
245 helpmsg
+=helpmsg_overops
246 for ligne
in helpmsg
.split("\n"):
247 serv
.privmsg(auteur
,ligne
)
249 if auteur
in self
.ops
:
251 if message
[1] in self
.chanlist
:
252 serv
.privmsg(auteur
,"Je suis déjà sur %s"%(message
[1]))
254 serv
.join(message
[1])
255 self
.chanlist
.append(message
[1])
256 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
257 log(self
.serveur
,"priv",auteur
," ".join(message
))
259 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
263 if auteur
in self
.ops
and len(message
)>1:
264 if message
[1] in self
.chanlist
:
265 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
266 serv
.part(message
[1])
267 self
.chanlist
.remove(message
[1])
268 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
270 serv
.privmsg(auteur
,"Non, je reste !")
271 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
273 serv
.privmsg(auteur
,"Je ne suis pas sur %s"%(message
[1]))
277 if auteur
in self
.overops
:
279 if message
[1] in self
.stay_channels
:
280 serv
.privmsg(auteur
,"Je stay déjà sur %s."%(message
[1]))
281 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
283 self
.stay_channels
.append(message
[1])
284 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
285 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
287 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
291 if auteur
in self
.overops
:
293 if message
[1] in self
.stay_channels
:
294 self
.stay_channels
.remove(message
[1])
295 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
296 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
298 serv
.privmsg(auteur
,"Je ne stay pas sur %s."%(message
[1]))
299 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
303 if auteur
in self
.ops
:
305 if message
[1] in self
.play_channels
:
306 serv
.privmsg(auteur
,"Je play déjà sur %s."%(message
[1]))
307 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
309 self
.play_channels
.append(message
[1])
310 self
.play_status
[message
[1]]=[0,time
.time()-3600]
311 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
312 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
314 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
318 if auteur
in self
.ops
:
320 if message
[1] in self
.play_channels
:
321 self
.play_channels
.remove(message
[1])
322 serv
.privmsg(auteur
,"Play channels : "+" ".join(self
.play_channels
))
323 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
325 serv
.privmsg(auteur
,"Je ne play pas sur %s."%(message
[1]))
326 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
330 if auteur
in self
.ops
:
332 if message
[1] in self
.quiet_channels
:
333 serv
.privmsg(auteur
,"Je me la ferme déjà sur %s"%(message
[1]))
334 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
336 self
.quiet_channels
.append(message
[1])
337 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
338 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
340 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
344 if auteur
in self
.ops
:
346 if message
[1] in self
.quiet_channels
:
347 self
.quiet_channels
.remove(message
[1])
348 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
349 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
351 serv
.privmsg(auteur
,"Je ne me la ferme pas sur %s."%(message
[1]))
352 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
355 elif cmd
in ["states","status"]:
356 if auteur
in self
.overops
:
357 for k
in self
.play_status
.keys():
358 serv
.privmsg(auteur
,(u
"%s : %s"%(k
,"; ".join([unicode(i
) for i
in self
.play_status
[k
]]))).encode("utf8") )
360 if auteur
in self
.overops
and len(message
)>2:
361 serv
.privmsg(message
[1]," ".join(message
[2:]))
362 log(self
.serveur
,"priv",auteur
," ".join(message
))
363 elif len(message
)<=2:
364 serv
.privmsg(auteur
,"Syntaxe : SAY <channel> <message>")
368 if auteur
in self
.overops
:
372 if len(message
) in [3,4] and message
[1].lower()=="transfert":
373 scores
=self
.get_scores()
374 de
,to
=auteur
,message
[2]
375 value
=scores
.get(de
,0)
378 asked
=int(message
[3])
380 serv
.privmsg(auteur
,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
385 serv
.privmsg(auteur
,"Vous n'avez pas de points")
388 serv
.privmsg(auteur
,"Bien tenté…")
391 serv
.privmsg(auteur
,"Vous n'avez que %s points"%(value))
394 self
.add_score(de
,-asked
)
395 self
.add_score(to
,asked
)
396 serv
.privmsg(auteur
,"Transfert de %s points de %s à %s"%(asked
,de
,to
))
398 serv
.privmsg(auteur
,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
400 serv
.privmsg(auteur
,"Votre score : %s"%(self
.get_scores().get(auteur
,0)) )
403 scores
=self
.get_scores().items()
405 scores
.sort(lambda x
,y
:cmp(x
[1],y
[1]))
407 serv
.privmsg(auteur
,"Scores by score : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
409 scores
.sort(lambda x
,y
:cmp(x
[0].lower(),y
[0].lower()))
410 serv
.privmsg(auteur
,"Scores by pseudo : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
411 elif auteur
in self
.overops
:
412 souscmd
=message
[1].lower()
416 scores
=self
.get_scores()
417 if scores
.has_key(todelete
):
419 self
.save_scores(scores
)
420 serv
.privmsg(auteur
,"Score de %s supprimé"%(todelete))
422 serv
.privmsg(auteur
,"Ce score n'existe pas : %s"%(todelete))
424 serv
.privmsg(auteur
,"Syntaxe : SCORES DEL <pseudo>")
425 elif souscmd
in ["add","sub"]:
427 toadd
,val
=message
[2],message
[3]
431 serv
.privmsg(auteur
,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
435 self
.add_score(toadd
,val
)
436 serv
.privmsg(auteur
,"Done")
438 serv
.privmsg(auteur
,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
440 serv
.privmsg(auteur
,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
446 serv
.privmsg(auteur
,"Je n'ai pas compris. Essaye HELP…")
448 def on_pubmsg(self
, serv
, ev
):
449 auteur
= irclib
.nm_to_n(ev
.source())
451 message
= ev
.arguments()[0]
453 test
=bot_unicode(message
)
454 except UnicodeBotError
:
455 if not canal
in self
.quiet_channels
:
457 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
460 pour_moi
,message
=self
.pourmoi(serv
,message
)
461 if pour_moi
and message
.split()!=[]:
462 cmd
=message
.split()[0].lower()
464 args
=" ".join(message
.split()[1:])
467 if cmd
in ["meurs","die","crève"]:
468 if auteur
in self
.overops
:
470 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
472 serv
.privmsg(canal
,"%s: crève !"%(auteur))
473 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
474 if cmd
in ["meur", "meurt","meurre","meurres"] and not canal
in self
.quiet_channels
:
475 serv
.privmsg(canal
,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
476 if cmd
in ["part","leave","dégage"]:
477 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
478 or auteur
in self
.overops
):
479 serv
.part(canal
,message
="Éjecté par %s"%(auteur))
480 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
481 self
.chanlist
.remove(canal
)
483 serv
.privmsg(canal
,"%s: Non, je reste !"%(auteur))
484 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
486 if cmd
in ["deviens","pseudo"]:
487 if auteur
in self
.ops
:
490 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
491 if cmd
in ["coucou"] and not canal
in self
.quiet_channels
:
492 serv
.privmsg(canal
,"%s: coucou"%(auteur))
493 if cmd
in ["ping"] and not canal
in self
.quiet_channels
:
494 serv
.privmsg(canal
,"%s: pong"%(auteur))
495 if cmd
in ["déconnaissance","deconnaissance","énigme","enigme","encore"]:
496 if canal
in self
.play_channels
:
497 if self
.play_status
.get(canal
,[-1])[0]==0:
499 self
.start_enigme(serv
,canal
)
501 serv
.privmsg(canal
,"%s: Je peux souffler une minute ?"%(auteur))
503 serv
.privmsg(canal
,("%s: Rappel : %s"%(auteur
,self
.play_status
[canal
][1])).encode("utf8") )
505 serv
.privmsg(canal
,"%s: pas ici…"%(auteur))
506 if cmd
in ["score","!score"]:
507 serv
.privmsg(auteur
,"Votre score : %s"%(self
.get_scores().get(auteur
,0)) )
508 if cmd
in ["scores","!scores"]:
509 scores
=self
.get_scores().items()
511 scores
.sort(lambda x
,y
:cmp(x
[1],y
[1]))
513 serv
.privmsg(auteur
,"Scores by score : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
515 scores
.sort(lambda x
,y
:cmp(x
[0].lower(),y
[0].lower()))
516 serv
.privmsg(auteur
,"Scores by pseudo : "+" ; ".join(["%s %s"%(i
[0],i
[1]) for i
in scores
]))
517 if cmd
=="indice" and canal
in self
.play_channels
:
518 self
.give_indice(serv
,canal
,None)
519 if is_tag(message
) and not canal
in self
.quiet_channels
:
520 if auteur
in self
.ops
:
521 action
=random
.choice(config_tag_actions
)
522 serv
.action(canal
,action
.encode("utf8"))
523 self
.quiet_channels
.append(canal
)
525 answer
=random
.choice(config_tag_answers
)
526 for ligne
in answer
.split("\n"):
527 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
533 if self
.play_status
.get(canal
,[-1])[0] in [1,2]:
534 answer_regexp
=self
.play_status
[canal
][3]
535 answer
=self
.play_status
[canal
][4]
536 if reussi(message
.decode("utf8"),answer
,answer_regexp
,auteur
):
537 serv
.privmsg(canal
,(u
"%s: bravo ! (C'était %s)"%(auteur
,answer
)).encode("utf8"))
538 log(self
.serveur
,canal
,auteur
+"$win",message
)
539 self
.add_score(auteur
,1)
541 self
.play_status
[canal
]=[0,token
]
542 serv
.execute_delayed(random
.randrange(Ttrig
*5,Ttrig
*10),self
.start_enigme
,(serv
,canal
,token
))
544 def on_kick(self
,serv
,ev
):
545 auteur
= irclib
.nm_to_n(ev
.source())
546 channel
= ev
.target()
547 victime
= ev
.arguments()[0]
548 raison
= ev
.arguments()[1]
549 if victime
==self
.nick
:
550 log(self
.serveur
,"%s kické par %s (raison : %s)" %(victime
,auteur
,raison
))
553 # on ne dit rien au rejoin
554 #l1,l2=config_kick_answers,config_kick_actions
555 #n1,n2=len(l1),len(l2)
556 #i=random.randrange(n1+n2)
558 # serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
560 # serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
562 def get_scores(self
):
563 f
=open(config_score_file
)
564 scores
=pickle
.load(f
)
568 def add_score(self
,pseudo
,value
):
569 scores
=self
.get_scores()
570 if scores
.has_key(pseudo
):
571 scores
[pseudo
]+=value
574 self
.save_scores(scores
)
576 def save_scores(self
,scores
):
577 f
=open(config_score_file
,"w")
578 pickle
.dump(scores
,f
)
582 return self
.serv
.get_nickname()
583 nick
= property(_getnick
)
585 if __name__
=="__main__":
588 print "Usage : deconnaisseur.py <serveur> [--debug]"
591 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
595 serveurs
={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
596 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
598 serveur
=serveurs
[serveur
]
600 print "Server Unknown : %s"%(serveur)
602 deco
=Deconnaisseur(serveur
,debug
)