]> gitweb.pimeys.fr Git - bots/deconnaisseur.git/blob - deconnaisseur.py
Création des modes level 2 et 3 (avec respectivement Petite-Peste et [20-100])
[bots/deconnaisseur.git] / deconnaisseur.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3
4 # Codé par 20-100 le 23/04/12
5
6 # Un bot IRC qui sort des déconnaissances
7
8 import irclib
9 import ircbot
10 import threading
11 import random
12 import time
13 import pickle
14 import re
15 from remplace_accents import remplace_accents
16
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"]
24
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
34
35 config_score_file="scores.pickle"
36
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 :°",
41 u"Héhé, try again",
42 u"Non, j'ai pas envie",
43 u"Peut-être quand toi tu la fermeras, et encore…"]
44
45 config_level2=["Petite-Peste"]
46 config_level3=["[20-100]"]
47
48 class UnicodeBotError(Exception):
49 pass
50 def bot_unicode(chain):
51 try:
52 unicode(chain,"utf8")
53 except UnicodeDecodeError:
54 raise UnicodeBotError
55
56 def log(serveur,channel="prout",auteur=None,message=None):
57 #f=open(config_logfile,"a")
58 #if auteur==message==None:
59 # chain=channel
60 #else:
61 # chain="%s [%s:%s] %s"%(time.strftime("%T"),channel,auteur,message)
62 #f.write(chain+"\n")
63 #print chain
64 #f.close()
65 a=0 # does nothing
66
67
68 def reussi(message,answer,answer_regexp,auteur):
69 if auteur in config_level3:
70 return answer in message
71 if auteur in config_level2:
72 return remplace_accents(answer) in message
73 else:
74 return re.match(answer_regexp.lower(),remplace_accents(message).lower())
75
76 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
77 if case_sensitive:
78 chain=unicode(chain,"utf8")
79 else:
80 chain=unicode(chain,"utf8").lower()
81 allmatches="("+"|".join(matches)+")"
82 reg=(avant+allmatches+apres).lower()
83 o=re.match(reg,chain)
84 return o
85
86 def is_tag(chain):
87 return is_something(chain,config_tag_triggers)
88
89 class RefuseError(Exception):
90 pass
91
92 class Deconnaisseur(ircbot.SingleServerIRCBot):
93 def __init__(self,serveur,debug=False):
94 temporary_pseudo=config_pseudo+str(random.randrange(10000,100000))
95 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
96 temporary_pseudo,"Un bot irc.[flagellez 20-100, il le mérite]", 10)
97 self.debug=debug
98 self.serveur=serveur
99 self.overops=config_overops
100 self.ops=self.overops+config_ops
101 self.chanlist=config_chanlist
102 self.stay_channels=config_stay_channels
103 self.play_channels=config_play_channels
104 self.play_status={i:[0] for i in self.play_channels}
105 self.quiet_channels=[]
106
107 def give_me_my_pseudo(self,serv):
108 serv.privmsg("NickServ","RECOVER %s %s"%(config_pseudo,config_password))
109 serv.privmsg("NickServ","RELEASE %s %s"%(config_pseudo,config_password))
110 time.sleep(0.3)
111 serv.nick(config_pseudo)
112
113 def on_welcome(self, serv, ev):
114 self.give_me_my_pseudo(serv)
115 serv.privmsg("NickServ","identify %s"%(config_password))
116 log("Connected")
117 if self.debug:
118 self.chanlist=["#bot"]
119 self.play_channels=["#bot"]
120 for c in self.chanlist:
121 log("JOIN %s"%(c))
122 serv.join(c)
123 for c in self.play_channels:
124 token=time.time()-3600
125 self.play_status[c]=[0,token]
126 serv.execute_delayed(random.randrange(ttrig),self.start_enigme,(serv,c,token))
127
128 def start_enigme(self,serv,channel,token=None):
129 if self.play_status[channel][0]==0 and channel in self.play_channels:
130 ok="skip"
131 if token==self.play_status[channel][-1]:
132 ok="do_it"
133 if token==None:
134 if time.time() > self.play_status[channel][-1]+config_time_incompressible:
135 ok="do_it"
136 else:
137 ok="refuse"
138 if ok=="do_it":
139 enigme,indice,answer_reg,answer=self.get_enigme()
140 print "%s; %s; %s; %s"%(enigme, indice, answer_reg, answer)
141 serv.privmsg(channel,enigme.encode("utf8"))
142 token=time.time()
143 self.play_status[channel]=[1,enigme,indice,answer_reg,answer,token]
144 serv.execute_delayed(random.randrange(ttrig*3,ttrig*5),self.give_indice,(serv,channel,token))
145 elif ok=="refuse":
146 raise RefuseError
147 def give_indice(self,serv,channel,token):
148 if self.play_status[channel][0]==1:
149 if token==None:
150 # c'est donc que l'indice a été demandé
151 if self.play_status[channel][-1]+config_time_incompressible_clue<time.time():
152 token=self.play_status[channel][-1]
153 if self.play_status[channel][-1]==token:
154 indice=self.play_status[channel][2]
155 serv.privmsg(channel,"indice : %s"%(indice).encode("utf8"))
156 self.play_status[channel][0]=2
157 serv.execute_delayed(random.randrange(ttrig*1,ttrig*3),self.give_answer,(serv,channel,token))
158 def give_answer(self,serv,channel,token):
159 if self.play_status[channel][0]==2 and self.play_status[channel][-1]==token:
160 answer=self.play_status[channel][4]
161 serv.privmsg(channel,"C'était : %s"%(answer).encode("utf8"))
162 token=time.time()
163 self.play_status[channel]=[0,token]
164 serv.execute_delayed(random.randrange(Ttrig*5,Ttrig*10),self.start_enigme,(serv,channel,token))
165
166 def get_enigme(self):
167 # on récupère les déconnaissances
168 f=open(config_source_file)
169 t=f.read()
170 f.close()
171 l=re.findall("%\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n",t)
172 dec={int(i[0]):list(i[1:]) for i in l if len(i)==5}
173 # on va chercher combien de fois elles ont été jouées
174 played_file=get_config_played_file(self.serveur)
175 f=open(played_file)
176 t=f.read()
177 f.close()
178 l=re.findall("(.*):(.*)",t)
179 played={int(i[0]):int(i[1]) for i in l}
180 # on récupère le nombre d'occurrences le plus faible
181 mini=min(played.values())
182 # on choisit un id dans ceux qui ont ce nombre d'occurences
183 id_choisi=random.choice([k for k,v in played.items() if v==mini])
184 enigme,indice,answer_reg,answer=dec[id_choisi]
185 # on incrémente la choisie
186 played[id_choisi]+=1
187 # on enregistre le played_file
188 f=open(played_file,"w")
189 f.write("\n".join(["%-3s : %s"%(k,v) for k,v in played.items()]))
190 f.close()
191 return enigme.decode("utf8"),indice.decode("utf8"),answer_reg.decode("utf8"),answer.decode("utf8")
192
193 def pourmoi(self, serv, message):
194 pseudo=serv.get_nickname()
195 size=len(pseudo)
196 if message[:size]==pseudo and len(message)>size and message[size]==":":
197 return (True,message[size+1:].strip(" "))
198 else:
199 return (False,message)
200
201 def on_privmsg(self, serv, ev):
202 message=ev.arguments()[0]
203 auteur = irclib.nm_to_n(ev.source())
204 try:
205 test=bot_unicode(message)
206 except UnicodeBotError:
207 serv.privmsg(auteur,
208 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
209 return
210 message=message.split()
211 cmd=message[0].lower()
212 notunderstood=False
213 if cmd=="help":
214 helpmsg_default="""Liste des commandes :
215 HELP Affiche ce message d'aide
216 SCORE Affiche ton score (SCORE TRANSFERT <pseudo> [<n>] pour transférer des points)
217 SCORES Affiche les scores"""
218 helpmsg_ops="""
219 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
220 LEAVE Faire quitter un channel
221 PLAY Passe un channel en mode "jouer"
222 NOPLAY Passe un channel en mode "ne pas jouer"
223 QUIET Se taire sur un channel
224 NOQUIET Opposé de QUIET"""
225 helpmsg_overops="""
226 SCORES {DEL|ADD|SUB} Tu veux un dessin ?
227 SAY Fais envoyer un message sur un chan ou à une personne
228 STAY Ignorera les prochains LEAVE pour un chan
229 NOSTAY Opposé de STAY
230 STATUS Montre l'état courant
231 DIE Mourir"""
232 helpmsg=helpmsg_default
233 if auteur in self.ops:
234 helpmsg+=helpmsg_ops
235 if auteur in self.overops:
236 helpmsg+=helpmsg_overops
237 for ligne in helpmsg.split("\n"):
238 serv.privmsg(auteur,ligne)
239 elif cmd=="join":
240 if auteur in self.ops:
241 if len(message)>1:
242 if message[1] in self.chanlist:
243 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
244 else:
245 serv.join(message[1])
246 self.chanlist.append(message[1])
247 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
248 log("priv",auteur," ".join(message))
249 else:
250 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
251 else:
252 notunderstood=True
253 elif cmd=="leave":
254 if auteur in self.ops and len(message)>1:
255 if message[1] in self.chanlist:
256 if not (message[1] in self.stay_channels) or auteur in self.overops:
257 serv.part(message[1])
258 self.chanlist.remove(message[1])
259 log("priv",auteur," ".join(message)+"[successful]")
260 else:
261 serv.privmsg(auteur,"Non, je reste !")
262 log("priv",auteur," ".join(message)+"[failed]")
263 else:
264 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
265 else:
266 notunderstood=True
267 elif cmd=="stay":
268 if auteur in self.overops:
269 if len(message)>1:
270 if message[1] in self.stay_channels:
271 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
272 log("priv",auteur," ".join(message)+"[failed]")
273 else:
274 self.stay_channels.append(message[1])
275 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
276 log("priv",auteur," ".join(message)+"[successful]")
277 else:
278 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
279 else:
280 notunderstood=True
281 elif cmd=="nostay":
282 if auteur in self.overops:
283 if len(message)>1:
284 if message[1] in self.stay_channels:
285 self.stay_channels.remove(message[1])
286 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
287 log("priv",auteur," ".join(message)+"[successful]")
288 else:
289 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
290 log("priv",auteur," ".join(message)+"[failed]")
291 else:
292 notunderstood=True
293 elif cmd=="play":
294 if auteur in self.ops:
295 if len(message)>1:
296 if message[1] in self.play_channels:
297 serv.privmsg(auteur,"Je play déjà sur %s."%(message[1]))
298 log("priv",auteur," ".join(message)+"[failed]")
299 else:
300 self.play_channels.append(message[1])
301 self.play_status[message[1]]=[0,time.time()-3600]
302 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
303 log("priv",auteur," ".join(message)+"[successful]")
304 else:
305 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
306 else:
307 notunderstood=True
308 elif cmd=="noplay":
309 if auteur in self.ops:
310 if len(message)>1:
311 if message[1] in self.play_channels:
312 self.play_channels.remove(message[1])
313 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
314 log("priv",auteur," ".join(message)+"[successful]")
315 else:
316 serv.privmsg(auteur,"Je ne play pas sur %s."%(message[1]))
317 log("priv",auteur," ".join(message)+"[failed]")
318 else:
319 notunderstood=True
320 elif cmd=="quiet":
321 if auteur in self.ops:
322 if len(message)>1:
323 if message[1] in self.quiet_channels:
324 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
325 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
326 else:
327 self.quiet_channels.append(message[1])
328 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
329 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
330 else:
331 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
332 else:
333 notunderstood=True
334 elif cmd=="noquiet":
335 if auteur in self.ops:
336 if len(message)>1:
337 if message[1] in self.quiet_channels:
338 self.quiet_channels.remove(message[1])
339 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
340 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
341 else:
342 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
343 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
344 else:
345 notunderstood=True
346 elif cmd in ["states","status"]:
347 if auteur in self.overops:
348 for k in self.play_status.keys():
349 serv.privmsg(auteur,(u"%s : %s"%(k,"; ".join([str(i) for i in self.play_status[k]]))).encode("utf8") )
350 elif cmd=="say":
351 if auteur in self.overops and len(message)>2:
352 serv.privmsg(message[1]," ".join(message[2:]))
353 log("priv",auteur," ".join(message))
354 elif len(message)<=2:
355 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
356 else:
357 notunderstood=True
358 elif cmd=="die":
359 if auteur in self.overops:
360 self.die()
361 elif cmd=="score":
362 if len(message)>1:
363 if len(message) in [3,4] and message[1].lower()=="transfert":
364 scores=self.get_scores()
365 de,to=auteur,message[2]
366 value=scores.get(de,0)
367 if len(message)==4:
368 try:
369 asked=int(message[3])
370 except ValueError:
371 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
372 return
373 else:
374 asked=value
375 if value==0:
376 serv.privmsg(auteur,"Vous n'avez pas de points")
377 return
378 elif asked<=0:
379 serv.privmsg(auteur,"Bien tenté…")
380 return
381 elif asked>value:
382 serv.privmsg(auteur,"Vous n'avez que %s points"%(value))
383 return
384 else:
385 self.add_score(de,-asked)
386 self.add_score(to,asked)
387 serv.privmsg(auteur,"Transfert de %s points de %s à %s"%(asked,de,to))
388 else:
389 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
390 else:
391 serv.privmsg(auteur,"Votre score : %s"%(self.get_scores().get(auteur,0)) )
392 elif cmd=="scores":
393 if len(message)==1:
394 scores=self.get_scores().items()
395 # trie par score
396 scores.sort(lambda x,y:cmp(x[1],y[1]))
397 scores.reverse()
398 serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
399 # trie par pseudo
400 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
401 serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
402 elif auteur in self.overops:
403 souscmd=message[1].lower()
404 if souscmd=="del":
405 if len(message)==3:
406 todelete=message[2]
407 scores=self.get_scores()
408 if scores.has_key(todelete):
409 del scores[todelete]
410 self.save_scores(scores)
411 serv.privmsg(auteur,"Score de %s supprimé"%(todelete))
412 else:
413 serv.privmsg(auteur,"Ce score n'existe pas : %s"%(todelete))
414 else:
415 serv.privmsg(auteur,"Syntaxe : SCORES DEL <pseudo>")
416 elif souscmd in ["add","sub"]:
417 if len(message)==4:
418 toadd,val=message[2],message[3]
419 try:
420 val=int(val)
421 except ValueError:
422 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
423 return
424 if souscmd=="sub":
425 val=-val
426 self.add_score(toadd,val)
427 serv.privmsg(auteur,"Done")
428 else:
429 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
430 else:
431 serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
432 else:
433 notunderstood=True
434 else:
435 notunderstood=True
436 if notunderstood:
437 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
438
439 def on_pubmsg(self, serv, ev):
440 auteur = irclib.nm_to_n(ev.source())
441 canal = ev.target()
442 message = ev.arguments()[0]
443 try:
444 test=bot_unicode(message)
445 except UnicodeBotError:
446 if not canal in self.quiet_channels:
447 serv.privmsg(canal,
448 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
449 return
450 tryother=False
451 pour_moi,message=self.pourmoi(serv,message)
452 if pour_moi and message.split()!=[]:
453 cmd=message.split()[0].lower()
454 try:
455 args=" ".join(message.split()[1:])
456 except:
457 args=""
458 if cmd in ["meurs","die","crève"]:
459 if auteur in self.overops:
460 self.die()
461 log(canal,auteur,message+"[successful]")
462 else:
463 serv.privmsg(canal,"%s: crève !"%(auteur))
464 log(canal,auteur,message+"[failed]")
465 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
466 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
467 if cmd in ["part","leave","dégage"]:
468 if auteur in self.ops and (not (canal in self.stay_channels)
469 or auteur in self.overops):
470 serv.part(canal,message="Éjecté par %s"%(auteur))
471 log(canal,auteur,message+"[successful]")
472 self.chanlist.remove(canal)
473 else:
474 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
475 log(canal,auteur,message+"[failed]")
476
477 if cmd in ["deviens","pseudo"]:
478 if auteur in self.ops:
479 become=args
480 serv.nick(become)
481 log(canal,auteur,message+"[successful]")
482
483 if cmd in ["coucou"] and not canal in self.quiet_channels:
484 serv.privmsg(canal,"%s: coucou"%(auteur))
485 if cmd in ["ping"] and not canal in self.quiet_channels:
486 serv.privmsg(canal,"%s: pong"%(auteur))
487 if cmd in ["déconnaissance","deconnaissance","énigme","enigme","encore"]:
488 if canal in self.play_channels:
489 if self.play_status.get(canal,[-1])[0]==0:
490 try:
491 self.start_enigme(serv,canal)
492 except RefuseError:
493 serv.privmsg(canal,"%s: Je peux souffler une minute ?"%(auteur))
494 else:
495 serv.privmsg(canal,"%s: Rappel : %s"%(auteur,self.play_status[canal][1]))
496 else:
497 serv.privmsg(canal,"%s: pas ici…"%(auteur))
498 if cmd=="indice" and canal in self.play_channels:
499 self.give_indice(serv,canal,None)
500 if is_tag(message) and not canal in self.quiet_channels:
501 if auteur in self.ops:
502 action=random.choice(config_tag_actions)
503 serv.action(canal,action.encode("utf8"))
504 self.quiet_channels.append(canal)
505 else:
506 answer=random.choice(config_tag_answers)
507 for ligne in answer.split("\n"):
508 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
509 else:
510 tryother=True
511 else:
512 tryother=True
513 if tryother:
514 if self.play_status.get(canal,[-1])[0] in [1,2]:
515 answer_regexp=self.play_status[canal][3]
516 answer=self.play_status[canal][4]
517 if reussi(message.decode("utf8"),answer,answer_regexp,auteur):
518 serv.privmsg(canal,(u"%s: bravo ! (C'était %s)"%(auteur,answer)).encode("utf8"))
519 self.add_score(auteur,1)
520 token=time.time()
521 self.play_status[canal]=[0,token]
522 serv.execute_delayed(random.randrange(Ttrig*5,Ttrig*10),self.start_enigme,(serv,canal,token))
523 def get_scores(self):
524 f=open(config_score_file)
525 scores=pickle.load(f)
526 f.close()
527 return scores
528
529 def add_score(self,pseudo,value):
530 scores=self.get_scores()
531 if scores.has_key(pseudo):
532 scores[pseudo]+=value
533 else:
534 scores[pseudo]=value
535 self.save_scores(scores)
536
537 def save_scores(self,scores):
538 f=open(config_score_file,"w")
539 pickle.dump(scores,f)
540 f.close()
541
542 if __name__=="__main__":
543 import sys
544 if len(sys.argv)==1:
545 print "Usage : deconnaisseur.py <serveur> [--debug]"
546 exit(1)
547 serveur=sys.argv[1]
548 if "debug" in sys.argv or "--debug" in sys.argv:
549 debug=True
550 else:
551 debug=False
552 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
553 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
554 try:
555 serveur=serveurs[serveur]
556 except KeyError:
557 print "Server Unknown : %s"%(serveur)
558 exit(404)
559 deco=Deconnaisseur(serveur,debug)
560 deco.start()