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