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