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