]> gitweb.pimeys.fr Git - bots/deconnaisseur.git/blob - deconnaisseur.py
Gestion améliorée des scores (SCORE TRANSFERT & SCORES {DEL|ADD|SUB})
[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[channe][-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 scores.reverse()
344 serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
345 elif auteur in self.overops:
346 souscmd=message[1].lower()
347 if souscmd=="del":
348 if len(message)==3:
349 todelete=message[2]
350 scores=self.get_scores()
351 if scores.has_key(todelete):
352 del scores[todelete]
353 self.save_scores(scores)
354 serv.privmsg(auteur,"Score de %s supprimé"%(todelete))
355 else:
356 serv.privmsg(auteur,"Ce score n'existe pas : %s"%(todelete))
357 else:
358 serv.privmsg(auteur,"Syntaxe : SCORES DEL <pseudo>")
359 elif souscmd in ["add","sub"]:
360 if len(message)==4:
361 toadd,val=message[2],message[3]
362 try:
363 val=int(val)
364 except ValueError:
365 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
366 return
367 if souscmd=="sub":
368 val=-val
369 self.add_score(toadd,val)
370 serv.privmsg(auteur,"Done")
371 else:
372 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
373 else:
374 serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
375 else:
376 notunderstood=True
377 else:
378 notunderstood=True
379 if notunderstood:
380 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
381
382 def on_pubmsg(self, serv, ev):
383 auteur = irclib.nm_to_n(ev.source())
384 canal = ev.target()
385 message = ev.arguments()[0]
386 try:
387 test=bot_unicode(message)
388 except UnicodeBotError:
389 if not canal in self.quiet_channels:
390 serv.privmsg(canal,
391 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
392 return
393 tryother=False
394 pour_moi,message=self.pourmoi(serv,message)
395 if pour_moi and message.split()!=[]:
396 cmd=message.split()[0].lower()
397 try:
398 args=" ".join(message.split()[1:])
399 except:
400 args=""
401 if cmd in ["meurs","die","crève"]:
402 if auteur in self.overops:
403 self.die()
404 log(canal,auteur,message+"[successful]")
405 else:
406 serv.privmsg(canal,"%s: crève !"%(auteur))
407 log(canal,auteur,message+"[failed]")
408 if cmd in ["meur", "meurt","meurre","meurres"]:
409 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du pluriel : "meurs" (de rien)'%(auteur))
410 if cmd in ["part","leave","dégage"]:
411 if auteur in self.ops and (not (canal in self.stay_channels)
412 or auteur in self.overops):
413 serv.part(canal,message="Éjecté par %s"%(auteur))
414 log(canal,auteur,message+"[successful]")
415 self.chanlist.remove(canal)
416 else:
417 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
418 log(canal,auteur,message+"[failed]")
419
420 if cmd in ["deviens","pseudo"]:
421 if auteur in self.ops:
422 become=args
423 serv.nick(become)
424 log(canal,auteur,message+"[successful]")
425
426 if cmd in ["coucou"]:
427 serv.privmsg(canal,"%s: coucou"%(auteur))
428 if cmd in ["ping"]:
429 serv.privmsg(canal,"%s: pong"%(auteur))
430 if cmd in ["déconnaissance","deconnaissance","énigme","enigme","encore"]:
431 if canal in self.play_channels:
432 if self.play_status.get(canal,[-1])[0]==0:
433 try:
434 self.start_enigme(serv,canal)
435 except RefuseError:
436 serv.privmsg(canal,"%s: Je peux souffler une minute ?"%(auteur))
437 else:
438 serv.privmsg(canal,"%s: Rappel : %s"%(auteur,self.play_status[canal][1]))
439 else:
440 serv.privmsg(canal,"%s: pas ici…"%(auteur))
441 else:
442 tryother=True
443 else:
444 tryother=True
445 if tryother:
446 if self.play_status.get(canal,[-1])[0] in [1,2]:
447 answer_regexp=self.play_status[canal][3]
448 if re.match(tolere(answer_regexp),unicode(message,"utf8").lower()):
449 answer=self.play_status[canal][4]
450 serv.privmsg(canal,"%s: bravo ! (C'était %s)"%(auteur,answer))
451 self.add_score(auteur,1)
452 token=time.time()
453 self.play_status[canal]=[0,token]
454 serv.execute_delayed(random.randrange(Ttrig*5,Ttrig*10),self.start_enigme,(serv,canal,token))
455 def get_scores(self):
456 f=open(config_score_file)
457 scores=pickle.load(f)
458 f.close()
459 return scores
460
461 def add_score(self,pseudo,value):
462 scores=self.get_scores()
463 if scores.has_key(pseudo):
464 scores[pseudo]+=value
465 else:
466 scores[pseudo]=value
467 self.save_scores(scores)
468
469 def save_scores(self,scores):
470 f=open(config_score_file,"w")
471 pickle.dump(scores,f)
472 f.close()
473
474 if __name__=="__main__":
475 import sys
476 if len(sys.argv)==1:
477 print "Usage : deconnaisseur.py <serveur> [--debug]"
478 exit(1)
479 serveur=sys.argv[1]
480 if "debug" in sys.argv or "--debug" in sys.argv:
481 debug=True
482 else:
483 debug=False
484 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
485 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
486 try:
487 serveur=serveurs[serveur]
488 except KeyError:
489 print "Server Unknown : %s"%(serveur)
490 exit(404)
491 deco=Deconnaisseur(serveur,debug)
492 deco.start()