]> gitweb.pimeys.fr Git - bots/deconnaisseur.git/blob - deconnaisseur.py
engueulage d'encodage subordonné aux quiet_channels
[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(serveur,channel="prout",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 if not canal in self.quiet_channels:
200 serv.privmsg(auteur,
201 "Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…")
202 return
203 message=message.split()
204 cmd=message[0].lower()
205 notunderstood=False
206 if cmd=="help":
207 helpmsg_default="""Liste des commandes :
208 HELP Affiche ce message d'aide
209 SCORE Affiche ton score (SCORE TRANSFERT <pseudo> [<n>] pour transférer des points)
210 SCORES Affiche les scores"""
211 helpmsg_ops="""
212 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
213 LEAVE Faire quitter un channel
214 PLAY Passe un channel en mode "jouer"
215 NOPLAY Passe un channel en mode "ne pas jouer"
216 QUIET Se taire sur un channel
217 NOQUIET Opposé de QUIET"""
218 helpmsg_overops="""
219 SCORES {DEL|ADD|SUB} Tu veux un dessin ?
220 SAY Fais envoyer un message sur un chan ou à une personne
221 STAY Ignorera les prochains LEAVE pour un chan
222 NOSTAY Opposé de STAY
223 STATUS Montre l'état courant
224 DIE Mourir"""
225 helpmsg=helpmsg_default
226 if auteur in self.ops:
227 helpmsg+=helpmsg_ops
228 if auteur in self.overops:
229 helpmsg+=helpmsg_overops
230 for ligne in helpmsg.split("\n"):
231 serv.privmsg(auteur,ligne)
232 elif cmd=="join":
233 if auteur in self.ops:
234 if len(message)>1:
235 if message[1] in self.chanlist:
236 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
237 else:
238 serv.join(message[1])
239 self.chanlist.append(message[1])
240 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
241 log("priv",auteur," ".join(message))
242 else:
243 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
244 else:
245 notunderstood=True
246 elif cmd=="leave":
247 if auteur in self.ops and len(message)>1:
248 if message[1] in self.chanlist:
249 if not (message[1] in self.stay_channels) or auteur in self.overops:
250 serv.part(message[1])
251 self.chanlist.remove(message[1])
252 log("priv",auteur," ".join(message)+"[successful]")
253 else:
254 serv.privmsg(auteur,"Non, je reste !")
255 log("priv",auteur," ".join(message)+"[failed]")
256 else:
257 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
258 else:
259 notunderstood=True
260 elif cmd=="stay":
261 if auteur in self.overops:
262 if len(message)>1:
263 if message[1] in self.stay_channels:
264 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
265 log("priv",auteur," ".join(message)+"[failed]")
266 else:
267 self.stay_channels.append(message[1])
268 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
269 log("priv",auteur," ".join(message)+"[successful]")
270 else:
271 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
272 else:
273 notunderstood=True
274 elif cmd=="nostay":
275 if auteur in self.overops:
276 if len(message)>1:
277 if message[1] in self.stay_channels:
278 self.stay_channels.remove(message[1])
279 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
280 log("priv",auteur," ".join(message)+"[successful]")
281 else:
282 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
283 log("priv",auteur," ".join(message)+"[failed]")
284 else:
285 notunderstood=True
286 elif cmd=="play":
287 if auteur in self.ops:
288 if len(message)>1:
289 if message[1] in self.play_channels:
290 serv.privmsg(auteur,"Je play déjà sur %s."%(message[1]))
291 log("priv",auteur," ".join(message)+"[failed]")
292 else:
293 self.play_channels.append(message[1])
294 self.play_status[message[1]]=[0,time.time()-3600]
295 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
296 log("priv",auteur," ".join(message)+"[successful]")
297 else:
298 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
299 else:
300 notunderstood=True
301 elif cmd=="noplay":
302 if auteur in self.ops:
303 if len(message)>1:
304 if message[1] in self.play_channels:
305 self.play_channels.remove(message[1])
306 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
307 log("priv",auteur," ".join(message)+"[successful]")
308 else:
309 serv.privmsg(auteur,"Je ne play pas sur %s."%(message[1]))
310 log("priv",auteur," ".join(message)+"[failed]")
311 else:
312 notunderstood=True
313 elif cmd=="quiet":
314 if auteur in self.ops:
315 if len(message)>1:
316 if message[1] in self.quiet_channels:
317 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
318 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
319 else:
320 self.quiet_channels.append(message[1])
321 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
322 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
323 else:
324 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
325 else:
326 notunderstood=True
327 elif cmd=="noquiet":
328 if auteur in self.ops:
329 if len(message)>1:
330 if message[1] in self.quiet_channels:
331 self.quiet_channels.remove(message[1])
332 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
333 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
334 else:
335 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
336 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
337 else:
338 notunderstood=True
339 elif cmd in ["states","status"]:
340 if auteur in self.overops:
341 for k in self.play_status.keys():
342 serv.privmsg(auteur,"%s : %s"%(k,"; ".join([str(i) for i in self.play_status[k]])))
343 elif cmd=="say":
344 if auteur in self.overops and len(message)>2:
345 serv.privmsg(message[1]," ".join(message[2:]))
346 log("priv",auteur," ".join(message))
347 elif len(message)<=2:
348 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
349 else:
350 notunderstood=True
351 elif cmd=="die":
352 if auteur in self.overops:
353 self.die()
354 elif cmd=="score":
355 if len(message)>1:
356 if len(message) in [3,4] and message[1].lower()=="transfert":
357 scores=self.get_scores()
358 de,to=auteur,message[2]
359 value=scores.get(de,0)
360 if len(message)==4:
361 try:
362 asked=int(message[3])
363 except ValueError:
364 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
365 return
366 else:
367 asked=value
368 if value==0:
369 serv.privmsg(auteur,"Vous n'avez pas de points")
370 return
371 elif asked<=0:
372 serv.privmsg(auteur,"Bien tenté…")
373 return
374 elif asked>value:
375 serv.privmsg(auteur,"Vous n'avez que %s points"%(value))
376 return
377 else:
378 self.add_score(de,-asked)
379 self.add_score(to,asked)
380 serv.privmsg(auteur,"Transfert de %s points de %s à %s"%(asked,de,to))
381 else:
382 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
383 else:
384 serv.privmsg(auteur,"Votre score : %s"%(self.get_scores().get(auteur,0)) )
385 elif cmd=="scores":
386 if len(message)==1:
387 scores=self.get_scores().items()
388 # trie par score
389 scores.sort(lambda x,y:cmp(x[1],y[1]))
390 scores.reverse()
391 serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
392 # trie par pseudo
393 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
394 serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
395 elif auteur in self.overops:
396 souscmd=message[1].lower()
397 if souscmd=="del":
398 if len(message)==3:
399 todelete=message[2]
400 scores=self.get_scores()
401 if scores.has_key(todelete):
402 del scores[todelete]
403 self.save_scores(scores)
404 serv.privmsg(auteur,"Score de %s supprimé"%(todelete))
405 else:
406 serv.privmsg(auteur,"Ce score n'existe pas : %s"%(todelete))
407 else:
408 serv.privmsg(auteur,"Syntaxe : SCORES DEL <pseudo>")
409 elif souscmd in ["add","sub"]:
410 if len(message)==4:
411 toadd,val=message[2],message[3]
412 try:
413 val=int(val)
414 except ValueError:
415 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
416 return
417 if souscmd=="sub":
418 val=-val
419 self.add_score(toadd,val)
420 serv.privmsg(auteur,"Done")
421 else:
422 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
423 else:
424 serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
425 else:
426 notunderstood=True
427 else:
428 notunderstood=True
429 if notunderstood:
430 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
431
432 def on_pubmsg(self, serv, ev):
433 auteur = irclib.nm_to_n(ev.source())
434 canal = ev.target()
435 message = ev.arguments()[0]
436 try:
437 test=bot_unicode(message)
438 except UnicodeBotError:
439 if not canal in self.quiet_channels:
440 serv.privmsg(canal,
441 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
442 return
443 tryother=False
444 pour_moi,message=self.pourmoi(serv,message)
445 if pour_moi and message.split()!=[]:
446 cmd=message.split()[0].lower()
447 try:
448 args=" ".join(message.split()[1:])
449 except:
450 args=""
451 if cmd in ["meurs","die","crève"]:
452 if auteur in self.overops:
453 self.die()
454 log(canal,auteur,message+"[successful]")
455 else:
456 serv.privmsg(canal,"%s: crève !"%(auteur))
457 log(canal,auteur,message+"[failed]")
458 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
459 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
460 if cmd in ["part","leave","dégage"]:
461 if auteur in self.ops and (not (canal in self.stay_channels)
462 or auteur in self.overops):
463 serv.part(canal,message="Éjecté par %s"%(auteur))
464 log(canal,auteur,message+"[successful]")
465 self.chanlist.remove(canal)
466 else:
467 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
468 log(canal,auteur,message+"[failed]")
469
470 if cmd in ["deviens","pseudo"]:
471 if auteur in self.ops:
472 become=args
473 serv.nick(become)
474 log(canal,auteur,message+"[successful]")
475
476 if cmd in ["coucou"] and not canal in self.quiet_channels:
477 serv.privmsg(canal,"%s: coucou"%(auteur))
478 if cmd in ["ping"] and not canal in self.quiet_channels:
479 serv.privmsg(canal,"%s: pong"%(auteur))
480 if cmd in ["déconnaissance","deconnaissance","énigme","enigme","encore"]:
481 if canal in self.play_channels:
482 if self.play_status.get(canal,[-1])[0]==0:
483 try:
484 self.start_enigme(serv,canal)
485 except RefuseError:
486 serv.privmsg(canal,"%s: Je peux souffler une minute ?"%(auteur))
487 else:
488 serv.privmsg(canal,"%s: Rappel : %s"%(auteur,self.play_status[canal][1]))
489 else:
490 serv.privmsg(canal,"%s: pas ici…"%(auteur))
491 if cmd=="indice" and canal in self.play_channels:
492 self.give_indice(serv,canal,None)
493 if is_tag(message) and not canal in self.quiet_channels:
494 if auteur in self.ops:
495 action=random.choice(config_tag_actions)
496 serv.action(canal,action.encode("utf8"))
497 self.quiet_channels.append(canal)
498 else:
499 answer=random.choice(config_tag_answers)
500 for ligne in answer.split("\n"):
501 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
502 else:
503 tryother=True
504 else:
505 tryother=True
506 if tryother:
507 if self.play_status.get(canal,[-1])[0] in [1,2]:
508 answer_regexp=self.play_status[canal][3]
509 if re.match(tolere(answer_regexp),unicode(message,"utf8").lower()):
510 answer=self.play_status[canal][4]
511 serv.privmsg(canal,"%s: bravo ! (C'était %s)"%(auteur,answer))
512 self.add_score(auteur,1)
513 token=time.time()
514 self.play_status[canal]=[0,token]
515 serv.execute_delayed(random.randrange(Ttrig*5,Ttrig*10),self.start_enigme,(serv,canal,token))
516 def get_scores(self):
517 f=open(config_score_file)
518 scores=pickle.load(f)
519 f.close()
520 return scores
521
522 def add_score(self,pseudo,value):
523 scores=self.get_scores()
524 if scores.has_key(pseudo):
525 scores[pseudo]+=value
526 else:
527 scores[pseudo]=value
528 self.save_scores(scores)
529
530 def save_scores(self,scores):
531 f=open(config_score_file,"w")
532 pickle.dump(scores,f)
533 f.close()
534
535 if __name__=="__main__":
536 import sys
537 if len(sys.argv)==1:
538 print "Usage : deconnaisseur.py <serveur> [--debug]"
539 exit(1)
540 serveur=sys.argv[1]
541 if "debug" in sys.argv or "--debug" in sys.argv:
542 debug=True
543 else:
544 debug=False
545 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
546 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
547 try:
548 serveur=serveurs[serveur]
549 except KeyError:
550 print "Server Unknown : %s"%(serveur)
551 exit(404)
552 deco=Deconnaisseur(serveur,debug)
553 deco.start()