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