]> gitweb.pimeys.fr Git - bots/deconnaisseur.git/blob - deconnaisseur.py
Gestion des scores, version naïve 1.0
[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
34 class UnicodeBotError(Exception):
35 pass
36 def bot_unicode(chain):
37 try:
38 unicode(chain,"utf8")
39 except UnicodeDecodeError:
40 raise UnicodeBotError
41
42 def log(channel,auteur=None,message=None):
43 #f=open(config_logfile,"a")
44 #if auteur==message==None:
45 # chain=channel
46 #else:
47 # chain="%s [%s:%s] %s"%(time.strftime("%T"),channel,auteur,message)
48 #f.write(chain+"\n")
49 #print chain
50 #f.close()
51 a=0 # does nothing
52
53
54 def tolere(regexp):
55 """Renvoie une regexp plus tolérante"""
56 reg=unicode(regexp,"utf8").lower()
57 reg=reg.replace(u"á",u"(á|a)").replace(u"à",u"(à|a)").replace(u"â",u"(â|a)").replace(u"ä",u"(ä|a)")
58 reg=reg.replace(u"é",u"(é|e)").replace(u"è",u"(è|e)").replace(u"ê",u"(ê|e)").replace(u"ë",u"(ë|e)")
59 reg=reg.replace(u"í",u"(í|i)").replace(u"ì",u"(ì|i)").replace(u"î",u"(î|i)").replace(u"ï",u"(ï|i)")
60 reg=reg.replace(u"ó",u"(ó|o)").replace(u"ò",u"(ò|o)").replace(u"ô",u"(ô|o)").replace(u"ö",u"(ö|o)")
61 reg=reg.replace(u"ú",u"(ú|u)").replace(u"ù",u"(ù|u)").replace(u"û",u"(û|u)").replace(u"ü",u"(ü|u)")
62 reg=reg.replace(u"ý",u"(ý|y)").replace(u"ỳ",u"(ỳ|y)").replace(u"ŷ",u"(ŷ|y)").replace(u"ÿ",u"(ÿ|y)")
63 reg=reg.replace(u"œ",u"(œ|oe)").replace(u"æ",u"(æ|ae)")
64 return reg
65
66 class RefuseError(Exception):
67 pass
68
69 class Deconnaisseur(ircbot.SingleServerIRCBot):
70 def __init__(self,serveur,debug=False):
71 temporary_pseudo=config_pseudo+str(random.randrange(10000,100000))
72 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
73 temporary_pseudo,"Un bot irc.[flagellez 20-100, il le mérite]", 10)
74 self.debug=debug
75 self.serveur=serveur
76 self.overops=config_overops
77 self.ops=self.overops+config_ops
78 self.chanlist=config_chanlist
79 self.stay_channels=config_stay_channels
80 self.play_channels=config_play_channels
81 self.play_status={i:[0] for i in self.play_channels}
82 self.scores={}
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 SCORES Affiche les scores"""
187 helpmsg_ops="""
188 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
189 LEAVE Faire quitter un channel
190 PLAY Passe un channel en mode "jouer"
191 NOPLAY Passe un channel en mode "ne pas jouer" """
192 helpmsg_overops="""
193 SAY Fais envoyer un message sur un chan ou à une personne
194 STAY Ignorera les prochains LEAVE pour un chan
195 NOSTAY Opposé de STAY
196 STATUS Montre l'état courant
197 DIE Mourir"""
198 helpmsg=helpmsg_default
199 if auteur in self.ops:
200 helpmsg+=helpmsg_ops
201 if auteur in self.overops:
202 helpmsg+=helpmsg_overops
203 for ligne in helpmsg.split("\n"):
204 serv.privmsg(auteur,ligne)
205 elif cmd=="join":
206 if auteur in self.ops:
207 if len(message)>1:
208 if message[1] in self.chanlist:
209 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
210 else:
211 serv.join(message[1])
212 self.chanlist.append(message[1])
213 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
214 log("priv",auteur," ".join(message))
215 else:
216 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
217 else:
218 notunderstood=True
219 elif cmd=="leave":
220 if auteur in self.ops and len(message)>1:
221 if message[1] in self.chanlist:
222 if not (message[1] in self.stay_channels) or auteur in self.overops:
223 serv.part(message[1])
224 self.chanlist.remove(message[1])
225 log("priv",auteur," ".join(message)+"[successful]")
226 else:
227 serv.privmsg(auteur,"Non, je reste !")
228 log("priv",auteur," ".join(message)+"[failed]")
229 else:
230 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
231 else:
232 notunderstood=True
233 elif cmd=="stay":
234 if auteur in self.overops:
235 if len(message)>1:
236 if message[1] in self.stay_channels:
237 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
238 log("priv",auteur," ".join(message)+"[failed]")
239 else:
240 self.stay_channels.append(message[1])
241 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
242 log("priv",auteur," ".join(message)+"[successful]")
243 else:
244 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
245 else:
246 notunderstood=True
247 elif cmd=="nostay":
248 if auteur in self.overops:
249 if len(message)>1:
250 if message[1] in self.stay_channels:
251 self.stay_channels.remove(message[1])
252 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
253 log("priv",auteur," ".join(message)+"[successful]")
254 else:
255 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
256 log("priv",auteur," ".join(message)+"[failed]")
257 else:
258 notunderstood=True
259 elif cmd=="play":
260 if auteur in self.ops:
261 if len(message)>1:
262 if message[1] in self.play_channels:
263 serv.privmsg(auteur,"Je play déjà sur %s."%(message[1]))
264 log("priv",auteur," ".join(message)+"[failed]")
265 else:
266 self.play_channels.append(message[1])
267 self.play_status[message[1]]=[0,time.time()-3600]
268 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
269 log("priv",auteur," ".join(message)+"[successful]")
270 else:
271 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
272 else:
273 notunderstood=True
274 elif cmd=="noplay":
275 if auteur in self.ops:
276 if len(message)>1:
277 if message[1] in self.play_channels:
278 self.play_channels.remove(message[1])
279 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
280 log("priv",auteur," ".join(message)+"[successful]")
281 else:
282 serv.privmsg(auteur,"Je ne play pas sur %s."%(message[1]))
283 log("priv",auteur," ".join(message)+"[failed]")
284 else:
285 notunderstood=True
286 elif cmd in ["states","status"]:
287 if auteur in self.overops:
288 for k in self.play_status.keys():
289 serv.privmsg(auteur,"%s : %s"%(k,"; ".join([str(i) for i in self.play_status[k]])))
290 elif cmd=="say":
291 if auteur in self.overops and len(message)>2:
292 serv.privmsg(message[1]," ".join(message[2:]))
293 log("priv",auteur," ".join(message))
294 elif len(message)<=2:
295 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
296 else:
297 notunderstood=True
298 elif cmd=="die":
299 if auteur in self.overops:
300 self.die()
301 elif cmd=="score":
302 serv.privmsg(auteur,"Votre score : %s"%(self.scores.get(auteur,0)) )
303 elif cmd=="scores":
304 scores=self.scores.items()
305 # trie par score
306 scores.sort(lambda x,y:cmp(x[1],y[1]))
307 serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
308 # trie par pseudo
309 scores.sort(lambda x,y:cmp(x[0],y[0]))
310 serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
311 else:
312 notunderstood=True
313 if notunderstood:
314 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
315
316 def on_pubmsg(self, serv, ev):
317 auteur = irclib.nm_to_n(ev.source())
318 canal = ev.target()
319 message = ev.arguments()[0]
320 try:
321 test=bot_unicode(message)
322 except UnicodeBotError:
323 if not canal in self.quiet_channels:
324 serv.privmsg(canal,
325 "%s: Euh, tu fais de la merde avec ton encodage là, j'ai failli crasher…"%(auteur))
326 return
327 tryother=False
328 pour_moi,message=self.pourmoi(serv,message)
329 if pour_moi and message.split()!=[]:
330 cmd=message.split()[0].lower()
331 try:
332 args=" ".join(message.split()[1:])
333 except:
334 args=""
335 if cmd in ["meurs","die","crève"]:
336 if auteur in self.overops:
337 self.die()
338 log(canal,auteur,message+"[successful]")
339 else:
340 serv.privmsg(canal,"%s: crève !"%(auteur))
341 log(canal,auteur,message+"[failed]")
342 if cmd in ["meur", "meurt","meurre","meurres"]:
343 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du pluriel : "meurs" (de rien)'%(auteur))
344 if cmd in ["part","leave","dégage"]:
345 if auteur in self.ops and (not (canal in self.stay_channels)
346 or auteur in self.overops):
347 serv.part(canal,message="Éjecté par %s"%(auteur))
348 log(canal,auteur,message+"[successful]")
349 self.chanlist.remove(canal)
350 else:
351 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
352 log(canal,auteur,message+"[failed]")
353
354 if cmd in ["deviens","pseudo"]:
355 if auteur in self.ops:
356 become=args
357 serv.nick(become)
358 log(canal,auteur,message+"[successful]")
359
360 if cmd in ["coucou"]:
361 serv.privmsg(canal,"%s: coucou"%(auteur))
362 if cmd in ["ping"]:
363 serv.privmsg(canal,"%s: pong"%(auteur))
364 if cmd in ["déconnaissance","deconnaissance","énigme","enigme","encore"]:
365 if canal in self.play_channels:
366 if self.play_status.get(canal,[-1])[0]==0:
367 try:
368 self.start_enigme(serv,canal)
369 except RefuseError:
370 serv.privmsg(canal,"%s: Je peux souffler une minute ?"%(auteur))
371 else:
372 serv.privmsg(canal,"%s: Rappel : %s"%(auteur,self.play_status[canal][1]))
373 else:
374 serv.privmsg(canal,"%s: pas ici…"%(auteur))
375 else:
376 tryother=True
377 else:
378 tryother=True
379 if tryother:
380 if self.play_status.get(canal,[-1])[0] in [1,2]:
381 answer_regexp=self.play_status[canal][3]
382 if re.match(tolere(answer_regexp),unicode(message,"utf8").lower()):
383 answer=self.play_status[canal][4]
384 serv.privmsg(canal,"%s: bravo ! (C'était %s)"%(auteur,answer))
385 self.scoreplus(auteur)
386 token=time.time()
387 self.play_status[canal]=[0,token]
388 serv.execute_delayed(random.randrange(Ttrig*5,Ttrig*10),self.start_enigme,(serv,canal,token))
389
390 def scoreplus(self,pseudo):
391 if self.scores.has_key(pseudo):
392 self.scores[pseudo]+=1
393 else:
394 self.scores[pseudo]=1
395
396 if __name__=="__main__":
397 import sys
398 if len(sys.argv)==1:
399 print "Usage : deconnaisseur.py <serveur> [--debug]"
400 exit(1)
401 serveur=sys.argv[1]
402 if "debug" in sys.argv or "--debug" in sys.argv:
403 debug=True
404 else:
405 debug=False
406 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
407 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
408 try:
409 serveur=serveurs[serveur]
410 except KeyError:
411 print "Server Unknown : %s"%(serveur)
412 exit(404)
413 deco=Deconnaisseur(serveur,debug)
414 deco.start()