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