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