]> gitweb.pimeys.fr Git - bots/historien.git/blob - historien.py
66a4ff6a9b604b2d7d79793c1da72445a6e0d32d
[bots/historien.git] / historien.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3
4 # Codé par 20-100 le 25/05/12
5
6 # Un bot IRC qui pose des questions d'histoire
7
8 import threading
9 import random
10 import time
11 import pickle
12 import re
13 import signal
14 import sys
15 import os
16 from cast_as_date import *
17
18 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
19 sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
20 import irclib
21 import ircbot
22
23 # Fichier de conf
24 import config
25
26 def get_config_played_file(serveur):
27 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
28 return config.played_file_template%(serveurs[serveur])
29
30 def get_config_logfile(serveur):
31 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
32 return config.logfile_template%(serveurs[serveur])
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(serveur,channel,auteur=None,message=None):
43 f=open(get_config_logfile(serveur),"a")
44 if auteur==message==None:
45 # alors c'est que c'est pas un channel mais juste une ligne de log
46 chain="%s %s"%(time.strftime("%F %T"),channel)
47 else:
48 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
49 f.write(chain+"\n")
50 if config.debug_stdout:
51 print chain
52 f.close()
53
54 def ignore_event(serv, ev):
55 """Retourne ``True`` si il faut ignorer cet évènement."""
56 for (blackmask, exceptmask) in config.blacklisted_masks:
57 usermask = ev.source()
58 if exceptmask is None:
59 exceptit = False
60 else:
61 exceptit = bool(irclib.mask_matches(usermask, exceptmask))
62 blackit = bool(irclib.mask_matches(usermask, blackmask))
63 if blackit and not exceptit:
64 return True
65
66 class GoodCentury(Exception):
67 pass
68
69 class GoodDeceny(Exception):
70 pass
71
72 def reussi(message,answer,auteur):
73 if auteur in config.level3:
74 return answer in message
75 if auteur in config.level2:
76 return answer in message
77 else:
78 try:
79 date=cast_as_date(message.lower().strip())
80 except ThisIsNotADate:
81 return False
82 realdate=map(lambda x:int(x), answer.split('/'))
83 realdate.reverse()
84 score=0
85 if date[0]==realdate[0]:
86 score=config.score_annee
87 if date[1]==realdate[1]:
88 score+=config.score_mois
89 if date[2]==realdate[2]:
90 score+=config.score_jour
91 elif date[0]/10 == realdate[0]/10:
92 raise GoodDeceny
93 elif date[0]/100 == realdate[0]/100:
94 raise GoodCentury
95 return score
96
97 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
98 if case_sensitive:
99 chain=unicode(chain,"utf8")
100 else:
101 chain=unicode(chain,"utf8").lower()
102 allmatches="("+"|".join(matches)+")"
103 reg=(avant+allmatches+apres).lower()
104 o=re.match(reg,chain)
105 return o
106
107 def is_tag(chain):
108 return is_something(chain,config.tag_triggers)
109
110 class RefuseError(Exception):
111 pass
112
113 class Historien(ircbot.SingleServerIRCBot):
114 def __init__(self,serveur,debug=False):
115 temporary_pseudo=config.pseudo+str(random.randrange(10000,100000))
116 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
117 temporary_pseudo,"Un bot irc qui a au moins l'agreg d'histoire", 10)
118 self.debug=debug
119 self.serveur=serveur
120 self.overops=config.overops
121 self.ops=self.overops+config.ops
122 self.chanlist=config.chanlist
123 self.stay_channels=config.stay_channels
124 self.play_channels=config.play_channels
125 self.play_status={i:[0] for i in self.play_channels}
126 self.last_activity={}
127 self.quiet_channels=[]
128
129 def give_me_my_pseudo(self,serv):
130 serv.privmsg("NickServ","RECOVER %s %s"%(config.pseudo,config.password))
131 serv.privmsg("NickServ","RELEASE %s %s"%(config.pseudo,config.password))
132 time.sleep(0.3)
133 serv.nick(config.pseudo)
134
135 def on_welcome(self, serv, ev):
136 self.serv=serv # ça serv ira :)
137 self.give_me_my_pseudo(serv)
138 serv.privmsg("NickServ","identify %s"%(config.password))
139 log(self.serveur,"Connected")
140 if self.debug:
141 self.chanlist=["#bot"]
142 self.play_channels=["#bot"]
143 for c in self.chanlist:
144 log(self.serveur,"JOIN %s"%(c))
145 serv.join(c)
146 for c in self.play_channels:
147 token=time.time()-3600
148 self.play_status[c]=[0,token]
149 if config.auto_trigger:
150 serv.execute_delayed(random.randrange(config.ttrig),self.start_enigme,(serv,c,token))
151
152 def start_enigme(self,serv,channel,token=None):
153 # On reste silencieux si lechan n'est pas actif
154 if not self.is_active(channel):
155 if config.auto_trigger:
156 serv.execute_delayed(config.ttrig*5,self.start_enigme,(serv,channel,token))
157 return
158 if self.play_status[channel][0]==0 and channel in self.play_channels:
159 ok="skip"
160 if token==self.play_status[channel][-1]:
161 ok="do_it"
162 if token==None:
163 if time.time() > self.play_status[channel][-1]+config.time_incompressible:
164 ok="do_it"
165 else:
166 ok="refuse"
167 if ok=="do_it":
168 date,evenement=self.get_enigme()
169 log(self.serveur,channel,u"$Date$".encode("utf8"),("%s : %s"%(date, evenement)).encode("utf8"))
170 serv.privmsg(channel,evenement.encode("utf8"))
171 token=time.time()
172 # le 0 est le flag "bon siècle" n'a pas encore été dit
173 self.play_status[channel]=[1,date,evenement,0,token]
174 serv.execute_delayed(random.randrange(config.ttrig*3,config.ttrig*5),self.give_indice,(serv,channel,token))
175 elif ok=="refuse":
176 raise RefuseError
177 def give_indice(self,serv,channel,token):
178 if self.play_status[channel][0]==1:
179 if token==None:
180 # c'est donc que l'indice a été demandé
181 if self.play_status[channel][-1]+config.time_incompressible_clue<time.time():
182 token=self.play_status[channel][-1]
183 if self.play_status[channel][-1]==token:
184 date=self.play_status[channel][1]
185 indice=date[:5]
186 serv.privmsg(channel,"indice : %s"%(indice).encode("utf8"))
187 self.play_status[channel][0]=2
188 serv.execute_delayed(random.randrange(config.ttrig*1,config.ttrig*3),self.give_answer,(serv,channel,token))
189 def give_answer(self,serv,channel,token):
190 if self.play_status[channel][0]==2 and self.play_status[channel][-1]==token:
191 date=self.play_status[channel][1]
192 serv.privmsg(channel,"C'était le %s"%(date).encode("utf8"))
193 token=time.time()
194 self.play_status[channel]=[0,token]
195 if config.auto_trigger:
196 serv.execute_delayed(random.randrange(config.Ttrig*5,config.Ttrig*10),self.start_enigme,(serv,channel,token))
197
198 def get_enigme(self):
199 # on récupère les dates
200 f=open(config.source_file)
201 l=f.readlines()
202 f.close()
203 l=[i.split(" : ",2) for i in l]
204 dates={int(i[0]):i[1:] for i in l}
205 # on va chercher combien de fois elles ont été jouées
206 played_file=get_config_played_file(self.serveur)
207 f=open(played_file)
208 t=f.read()
209 f.close()
210 l=re.findall("(.*):(.*)",t)
211 played={int(i[0]):int(i[1]) for i in l}
212 # on récupère le nombre d'occurrences le plus faible
213 mini=min(played.values())
214 # on choisit un id dans ceux qui ont ce nombre d'occurences
215 id_choisi=random.choice([k for k,v in played.items() if v==mini])
216 date,evenement=dates[id_choisi]
217 evenement=evenement.replace("\n","")
218 # on incrémente la choisie
219 played[id_choisi]+=1
220 # on enregistre le played_file
221 f=open(played_file,"w")
222 f.write("\n".join(["%-4s : %s"%(k,v) for k,v in played.items()]))
223 f.close()
224 return map(lambda x:x.decode("utf8"), [date,evenement])
225
226 def pourmoi(self, serv, message):
227 pseudo=self.nick
228 size=len(pseudo)
229 if message[:size]==pseudo and len(message)>size and message[size]==":":
230 return (True,message[size+1:].strip(" "))
231 else:
232 return (False,message)
233
234 def on_privmsg(self, serv, ev):
235 if ignore_event(serv, ev):
236 return
237 message=ev.arguments()[0]
238 auteur = irclib.nm_to_n(ev.source())
239 try:
240 test=bot_unicode(message)
241 except UnicodeBotError:
242 if config.utf8_trigger:
243 serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8"))
244 return
245 message=message.split()
246 cmd=message[0].lower()
247 notunderstood=False
248 if cmd=="help":
249 helpmsg_default="""Liste des commandes :
250 HELP Affiche ce message d'aide
251 SCORE Affiche ton score (SCORE TRANSFERT <pseudo> [<n>] pour transférer des points)
252 SCORES Affiche les scores"""
253 helpmsg_ops="""
254 JOIN Faire rejoindre un channel (sans paramètres, donne la liste des chans actuels)
255 LEAVE Faire quitter un channel
256 PLAY Passe un channel en mode "jouer"
257 NOPLAY Passe un channel en mode "ne pas jouer"
258 QUIET Se taire sur un channel
259 NOQUIET Opposé de QUIET
260 RELOAD Me fait recharger la conf"""
261 helpmsg_overops="""
262 SCORES {DEL|ADD|SUB} Tu veux un dessin ?
263 SAY Fais envoyer un message sur un chan ou à une personne
264 STAY Ignorera les prochains LEAVE pour un chan
265 NOSTAY Opposé de STAY
266 STATUS Montre l'état courant
267 DIE Mourir"""
268 helpmsg=helpmsg_default
269 if auteur in self.ops:
270 helpmsg+=helpmsg_ops
271 if auteur in self.overops:
272 helpmsg+=helpmsg_overops
273 for ligne in helpmsg.split("\n"):
274 serv.privmsg(auteur,ligne)
275 elif cmd=="join":
276 if auteur in self.ops:
277 if len(message)>1:
278 if message[1] in self.chanlist:
279 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
280 else:
281 serv.join(message[1])
282 self.chanlist.append(message[1])
283 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
284 log(self.serveur,"priv",auteur," ".join(message))
285 else:
286 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
287 else:
288 notunderstood=True
289 elif cmd=="leave":
290 if auteur in self.ops and len(message)>1:
291 if message[1] in self.chanlist:
292 if not (message[1] in self.stay_channels) or auteur in self.overops:
293 self.quitter(message[1]," ".join(message[2:]))
294 self.chanlist.remove(message[1])
295 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
296 else:
297 serv.privmsg(auteur,"Non, je reste !")
298 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
299 else:
300 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
301 else:
302 notunderstood=True
303 elif cmd=="stay":
304 if auteur in self.overops:
305 if len(message)>1:
306 if message[1] in self.stay_channels:
307 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
308 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
309 else:
310 self.stay_channels.append(message[1])
311 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
312 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
313 else:
314 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
315 else:
316 notunderstood=True
317 elif cmd=="nostay":
318 if auteur in self.overops:
319 if len(message)>1:
320 if message[1] in self.stay_channels:
321 self.stay_channels.remove(message[1])
322 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
323 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
324 else:
325 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
326 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
327 else:
328 notunderstood=True
329 elif cmd=="play":
330 if auteur in self.ops:
331 if len(message)>1:
332 if message[1] in self.play_channels:
333 serv.privmsg(auteur,"Je play déjà sur %s."%(message[1]))
334 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
335 else:
336 self.play_channels.append(message[1])
337 self.play_status[message[1]]=[0,time.time()-3600]
338 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
339 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
340 else:
341 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
342 else:
343 notunderstood=True
344 elif cmd=="noplay":
345 if auteur in self.ops:
346 if len(message)>1:
347 if message[1] in self.play_channels:
348 self.play_channels.remove(message[1])
349 serv.privmsg(auteur,"Play channels : "+" ".join(self.play_channels))
350 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
351 else:
352 serv.privmsg(auteur,"Je ne play pas sur %s."%(message[1]))
353 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
354 else:
355 notunderstood=True
356 elif cmd=="quiet":
357 if auteur in self.ops:
358 if len(message)>1:
359 if message[1] in self.quiet_channels:
360 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
361 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
362 else:
363 self.quiet_channels.append(message[1])
364 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
365 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
366 else:
367 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
368 else:
369 notunderstood=True
370 elif cmd=="noquiet":
371 if auteur in self.ops:
372 if len(message)>1:
373 if message[1] in self.quiet_channels:
374 self.quiet_channels.remove(message[1])
375 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
376 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
377 else:
378 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
379 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
380 else:
381 notunderstood=True
382 elif cmd=="reload":
383 if auteur in self.ops:
384 self.reload(auteur)
385 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
386 else:
387 notunderstood=True
388 elif cmd in ["states","status"]:
389 if auteur in self.overops:
390 for k in self.play_status.keys():
391 serv.privmsg(auteur,(u"%s : %s"%(k," | ".join([unicode(i) for i in self.play_status[k]]))).encode("utf8") )
392 elif cmd=="say":
393 if auteur in self.overops and len(message)>2:
394 serv.privmsg(message[1]," ".join(message[2:]))
395 log(self.serveur,"priv",auteur," ".join(message))
396 elif len(message)<=2:
397 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
398 else:
399 notunderstood=True
400 elif cmd=="die":
401 if auteur in self.overops:
402 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
403 self.mourir()
404 elif cmd=="score":
405 if len(message)>1:
406 if len(message) in [3,4] and message[1].lower()=="transfert":
407 scores=self.get_scores()
408 de,to=auteur,message[2]
409 value=scores.get(de,0)
410 if len(message)==4:
411 try:
412 asked=int(message[3])
413 except ValueError:
414 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
415 return
416 else:
417 asked=value
418 if value==0:
419 serv.privmsg(auteur,"Vous n'avez pas de points")
420 return
421 elif asked<=0:
422 serv.privmsg(auteur,"Bien tenté…")
423 return
424 elif asked>value:
425 serv.privmsg(auteur,"Vous n'avez que %s points"%(value))
426 return
427 else:
428 self.add_score(de,-asked)
429 self.add_score(to,asked)
430 serv.privmsg(auteur,"Transfert de %s points de %s à %s"%(asked,de,to))
431 else:
432 serv.privmsg(auteur,"Syntaxe : SCORE TRANSFERT <pseudo> [<n>]")
433 else:
434 serv.privmsg(auteur,"Votre score : %s"%(self.get_scores().get(auteur,0)) )
435 elif cmd=="scores":
436 if len(message)==1:
437 scores=self.get_scores().items()
438 # trie par score
439 scores.sort(lambda x,y:cmp(x[1],y[1]))
440 scores.reverse()
441 serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
442 # trie par pseudo
443 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
444 serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
445 elif auteur in self.overops:
446 souscmd=message[1].lower()
447 if souscmd=="del":
448 if len(message)==3:
449 todelete=message[2]
450 scores=self.get_scores()
451 if scores.has_key(todelete):
452 del scores[todelete]
453 self.save_scores(scores)
454 serv.privmsg(auteur,"Score de %s supprimé"%(todelete))
455 else:
456 serv.privmsg(auteur,"Ce score n'existe pas : %s"%(todelete))
457 else:
458 serv.privmsg(auteur,"Syntaxe : SCORES DEL <pseudo>")
459 elif souscmd in ["add","sub"]:
460 if len(message)==4:
461 toadd,val=message[2],message[3]
462 try:
463 val=int(val)
464 except ValueError:
465 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
466 return
467 if souscmd=="sub":
468 val=-val
469 self.add_score(toadd,val)
470 serv.privmsg(auteur,"Done")
471 else:
472 serv.privmsg(auteur,"Syntaxe : SCORES {ADD|SUB} <pseudo> <n>")
473 else:
474 serv.privmsg(auteur,"Syntaxe : SCORES {DEL|ADD|SUB} <pseudo> [<n>]")
475 else:
476 notunderstood=True
477 else:
478 notunderstood=True
479 if notunderstood:
480 serv.privmsg(auteur,"Je n'ai pas compris. Essaye HELP…")
481
482 def on_pubmsg(self, serv, ev):
483 if ignore_event(serv, ev):
484 return
485 auteur = irclib.nm_to_n(ev.source())
486 canal = ev.target()
487 message = ev.arguments()[0]
488 self.update_activity(canal,auteur,message)
489 try:
490 test=bot_unicode(message)
491 except UnicodeBotError:
492 if config.utf8_trigger and not canal in self.quiet_channels:
493 serv.privmsg(canal, (u"%s: %s"%(auteur, random.choice(config.utf8_fail_answers))).encode("utf8"))
494 return
495 tryother=False
496 pour_moi,message=self.pourmoi(serv,message)
497 if pour_moi and message.split()!=[]:
498 cmd=message.split()[0].lower()
499 try:
500 args=" ".join(message.split()[1:])
501 except:
502 args=""
503 if cmd in ["meurs","die","crève"]:
504 if auteur in self.overops:
505 self.mourir()
506 log(self.serveur,canal,auteur,message+"[successful]")
507 else:
508 serv.privmsg(canal,"%s: crève !"%(auteur))
509 log(self.serveur,canal,auteur,message+"[failed]")
510 elif cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
511 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
512 elif cmd == "reload":
513 if auteur in self.ops:
514 log(self.serveur, canal, auteur, message+"[successful]")
515 self.reload(canal)
516 elif cmd in ["part","leave","dégage"]:
517 if auteur in self.ops and (not (canal in self.stay_channels)
518 or auteur in self.overops):
519 self.quitter(canal)
520 log(self.serveur,canal,auteur,message+"[successful]")
521 self.chanlist.remove(canal)
522 else:
523 serv.privmsg(canal,"%s: Non, je reste !"%(auteur))
524 log(self.serveur,canal,auteur,message+"[failed]")
525
526 elif cmd in ["deviens","pseudo"]:
527 if auteur in self.ops:
528 become=args
529 serv.nick(become)
530 log(self.serveur,canal,auteur,message+"[successful]")
531 elif cmd in ["coucou"] and not canal in self.quiet_channels:
532 serv.privmsg(canal,"%s: coucou"%(auteur))
533 elif cmd in ["ping"] and not canal in self.quiet_channels:
534 serv.privmsg(canal,"%s: pong"%(auteur))
535 elif cmd in ["date","dates","histoire","énigme","enigme","encore"]:
536 if canal in self.play_channels:
537 if self.play_status.get(canal,[-1])[0]==0:
538 try:
539 self.start_enigme(serv,canal)
540 except RefuseError:
541 serv.privmsg(canal,"%s: Je peux souffler une minute ?"%(auteur))
542 else:
543 serv.privmsg(canal,("%s: Rappel : %s"%(auteur,self.play_status[canal][2])).encode("utf8") )
544 else:
545 serv.privmsg(canal,"%s: pas ici…"%(auteur))
546 elif cmd in ["score","!score"]:
547 serv.privmsg(auteur,"Votre score : %s"%(self.get_scores().get(auteur,0)) )
548 elif cmd in ["scores","!scores"]:
549 scores=self.get_scores().items()
550 # trie par score
551 scores.sort(lambda x,y:cmp(x[1],y[1]))
552 scores.reverse()
553 serv.privmsg(auteur,"Scores by score : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
554 # trie par pseudo
555 scores.sort(lambda x,y:cmp(x[0].lower(),y[0].lower()))
556 serv.privmsg(auteur,"Scores by pseudo : "+" ; ".join(["%s %s"%(i[0],i[1]) for i in scores]))
557 elif cmd == "indice" and canal in self.play_channels:
558 self.give_indice(serv,canal,None)
559 elif is_tag(message) and not canal in self.quiet_channels:
560 if auteur in self.ops:
561 action=random.choice(config.tag_actions)
562 serv.action(canal,action.encode("utf8"))
563 self.quiet_channels.append(canal)
564 else:
565 answer=random.choice(config.tag_answers)
566 for ligne in answer.split("\n"):
567 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
568 else:
569 tryother=True
570 else:
571 tryother=True
572 if tryother:
573 if self.play_status.get(canal,[-1])[0] in [1,2]:
574 answer=self.play_status[canal][1]
575 flag_century=self.play_status[canal][3]
576 try:
577 score_obtenu=reussi(message.decode("utf8"),answer,auteur)
578 except GoodCentury:
579 if not flag_century:
580 serv.privmsg(canal,"%s: C'est le bon siècle, mais pas la bonne année, cherche encore ;)"%(auteur))
581 self.play_status[canal][3]=1
582 return
583 except GoodDeceny:
584 if flag_century in [0,1]:
585 serv.privmsg(canal,"%s: C'est la bonne décennie, mais pas la bonne année, encore un effort ;)"%(auteur))
586 self.play_status[canal][3]=2
587 return
588 if score_obtenu:
589 if self.play_status[canal][0]==1:
590 bonusmsg=u" [+bonus_mois"*(score_obtenu>config.score_annee)+u"+bonus_jour"*(score_obtenu>config.score_annee+config.score_mois)
591 else:
592 bonusmsg=""
593 score_obtenu=1
594 if bonusmsg:
595 bonusmsg+=u"]"
596 serv.privmsg(canal,(u"%s: bravo ! (C'était le %s)%s"%(auteur,answer,bonusmsg)).encode("utf8"))
597 log(self.serveur,canal,auteur+"$win",message)
598 if auteur in config.noscore:
599 score_obtenu=0
600 self.add_score(auteur,score_obtenu)
601 token=time.time()
602 self.play_status[canal]=[0,token]
603 if config.auto_trigger:
604 serv.execute_delayed(random.randrange(config.Ttrig*5,config.Ttrig*10),self.start_enigme,(serv,canal,token))
605
606 def on_kick(self,serv,ev):
607 auteur = irclib.nm_to_n(ev.source())
608 channel = ev.target()
609 victime = ev.arguments()[0]
610 raison = ev.arguments()[1]
611 if victime==self.nick:
612 log(self.serveur,"%s kické de %s par %s (raison : %s)" %(victime,channel,auteur,raison))
613 time.sleep(5)
614 serv.join(channel)
615 self.update_activity(channel,force=True)
616 # on ne dit rien au rejoin
617 #l1,l2=config.kick_answers,config.kick_actions
618 #n1,n2=len(l1),len(l2)
619 #i=random.randrange(n1+n2)
620 #if i>=n1:
621 # serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
622 #else:
623 # serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
624
625 def quitter(self,chan,leave_message=None):
626 if leave_message==None:
627 leave_message=random.choice(config.leave_messages)
628 try:
629 leave_message=leave_message%(time.strftime("le %d/%m/%Y à %T").decode("utf8"),self.nick)
630 except:
631 pass
632 self.serv.part(chan,message=leave_message.encode("utf8"))
633
634 def mourir(self):
635 quit_message=random.choice(config.quit_messages)
636 try:
637 quit_message=quit_message%(time.strftime("le %d/%m/%Y à %T").decode("utf8"),self.nick)
638 except:
639 pass
640 self.die(msg=quit_message.encode("utf8"))
641
642 def get_scores(self):
643 f=open(config.score_file)
644 scores=pickle.load(f)
645 f.close()
646 return scores
647
648 def add_score(self,pseudo,value):
649 scores=self.get_scores()
650 if scores.has_key(pseudo):
651 scores[pseudo]+=value
652 else:
653 scores[pseudo]=value
654 self.save_scores(scores)
655
656 def save_scores(self,scores):
657 f=open(config.score_file,"w")
658 pickle.dump(scores,f)
659 f.close()
660
661 def _getnick(self):
662 return self.serv.get_nickname()
663 nick = property(_getnick)
664
665 def update_activity(self,canal="",pseudo="",message="",force=False):
666 if force or (not pseudo in config.idle_bots and all([not re.match(ignore, message) for ignore in config.idle_messages])):
667 self.last_activity[canal]=time.time()
668 def is_active(self,canal):
669 # Si on n'a pas d'info sur le chan, il est inactif
670 return time.time()-self.last_activity.get(canal, config.idle_time)<config.idle_time
671
672 def reload(self, auteur=None):
673 reload(config)
674 if auteur in [None, "SIGHUP"]:
675 towrite = "Config reloaded" + " (SIGHUP received)"*(auteur == "SIGHUP")
676 for to in config.report_bugs_to:
677 self.serv.privmsg(to, towrite)
678 log(self.serveur, towrite)
679 else:
680 self.serv.privmsg(auteur,"Config reloaded")
681
682 def start_as_daemon(self, outfile):
683 sys.stderr = Logger(outfile)
684 self.start()
685
686
687 class Logger(object):
688 """Pour écrire ailleurs que sur stdout"""
689 def __init__(self, filename="historien.full.log"):
690 self.filename = filename
691
692 def write(self, message):
693 f = open(self.filename, "a")
694 f.write(message)
695 f.close()
696
697
698 if __name__=="__main__":
699 import sys
700 if len(sys.argv)==1:
701 print "Usage : historien.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
702 print " --outfile sans --no-output ni --daemon n'a aucun effet"
703 exit(1)
704 serveur=sys.argv[1]
705 if "--daemon" in sys.argv:
706 thisfile = os.path.realpath(__file__)
707 thisdirectory = thisfile.rsplit("/", 1)[0]
708 os.chdir(thisdirectory)
709 daemon = True
710 else:
711 daemon = False
712 if "debug" in sys.argv or "--debug" in sys.argv:
713 debug=True
714 else:
715 debug=False
716 if "--no-output" in sys.argv or "--daemon" in sys.argv:
717 outfile = "/var/log/bots/historien.full.log"
718 for arg in sys.argv:
719 arg = arg.split("=")
720 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
721 outfile = arg[1]
722 sys.stdout = Logger(outfile)
723 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
724 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
725 try:
726 serveur=serveurs[serveur]
727 except KeyError:
728 print "Server Unknown : %s"%(serveur)
729 exit(404)
730 historien=Historien(serveur,debug)
731 # Si on reçoit un SIGHUP, on reload la config
732 def sighup_handler(signum, frame):
733 historien.reload("SIGHUP")
734 signal.signal(signal.SIGHUP, sighup_handler)
735 if daemon:
736 child_pid = os.fork()
737 if child_pid == 0:
738 os.setsid()
739 historien.start_as_daemon(outfile)
740 else:
741 # on enregistre le pid de historien
742 pidfile = "/var/run/bots/historien.pid"
743 for arg in sys.argv:
744 arg = arg.split("=")
745 if arg[0].strip('-') in ["pidfile"]:
746 pidfile = arg[1]
747 f = open(pidfile, "w")
748 f.write("%s\n" % child_pid)
749 f.close()
750 else:
751 historien.start()
752