]> gitweb.pimeys.fr Git - bots/basile.git/blob - basile.py
96641c120badf7e01959faa6d3351b017c898ebe
[bots/basile.git] / basile.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3
4 # Codé par 20-100 (commencé le 23/04/12)
5
6 """ Un bot IRC destiné à s'interfacer avec la Note Kfet 2015 """
7
8 import threading
9 import random
10 import time
11 import json
12 import pickle
13 import re
14 import os
15 import signal
16 import sys
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 from commands import getstatusoutput as ex
24
25 #: Config de basile
26 import config
27 #: Module responsable du dialogue avec la NoteKfet2015
28 import nk
29
30 # la partie qui réfère au fichier lui-même est mieux ici
31 # sinon on réfère la config et pas le fichier lui-même
32 import os
33 config.thisfile= os.path.realpath( __file__ )
34
35 def get_config_logfile(serveur):
36 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
37 return config.logfile_template%(serveurs[serveur])
38
39 def get_filesize():
40 return ex("ls -s %s"%(config.thisfile))[1].split()[0]
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 is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
55 if case_sensitive:
56 chain=unicode(chain,"utf8")
57 else:
58 chain=unicode(chain,"utf8").lower()
59 allmatches="("+"|".join(matches)+")"
60 reg=(avant+allmatches+apres).lower()
61 o=re.match(reg,chain)
62 return o
63
64 def is_insult(chain,debug=True):
65 return is_something(chain,config.insultes,avant=".*(?:^| |')")
66 def is_not_insult(chain):
67 chain=unicode(chain,"utf8")
68 insult_regexp=u"("+u"|".join(config.insultes)+u")"
69 middle_regexp=u"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
70 reg=".*pas %s%s.*"%(middle_regexp,insult_regexp)
71 if re.match(reg,chain):
72 return True
73 else:
74 return False
75 def is_compliment(chain,debug=True):
76 return is_something(chain,config.compliment_triggers,avant=".*(?:^| |')")
77 def is_perdu(chain):
78 return is_something(chain,config.perdu)
79 def is_tag(chain):
80 return is_something(chain,config.tag_triggers)
81 def is_gros(chain):
82 return is_something(chain,config.gros)
83 def is_tesla(chain):
84 return is_something(chain,config.tesla_triggers,avant=u"^",apres=u"$",debug=True)
85 def is_merci(chain):
86 return is_something(chain,config.merci_triggers)
87 def is_tamere(chain):
88 return is_something(chain,config.tamere_triggers)
89 def is_bad_action_trigger(chain,pseudo):
90 return is_something(chain,config.bad_action_triggers,avant=u"^",
91 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
92 def is_good_action_trigger(chain,pseudo):
93 return is_something(chain,config.good_action_triggers,avant=u"^",
94 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
95 def is_bonjour(chain):
96 return is_something(chain,config.bonjour_triggers,avant=u"^")
97 def is_bonne_nuit(chain):
98 return is_something(chain,config.bonne_nuit_triggers,avant=u"^")
99 def is_pan(chain):
100 return re.match(u"^(pan|bim|bang)( .*)?$",unicode(chain,"utf8").lower().strip())
101
102 def is_time(conf):
103 _,_,_,h,m,s,_,_,_=time.localtime()
104 return (conf[0],0,0)<(h,m,s)<(conf[1],0,0)
105 def is_day():
106 return is_time(config.daytime)
107 def is_night():
108 return is_time(config.nighttime)
109
110
111 class UnicodeBotError(Exception):
112 pass
113
114 class CrashError(Exception):
115 """Pour pouvoir faire crasher Basile, parce que ça a l'air drôle"""
116 def __init__(self, msg=u""):
117 Exception.__init__(self, msg)
118
119 def bot_unicode(chain):
120 try:
121 unicode(chain,"utf8")
122 except UnicodeDecodeError as exc:
123 raise UnicodeBotError
124
125
126 class Basile(ircbot.SingleServerIRCBot):
127 def __init__(self,serveur,debug=False):
128 temporary_pseudo=config.irc_pseudo+str(random.randrange(10000,100000))
129 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
130 temporary_pseudo,"Basile, le bot irc.[Codé par 20-100, fouettez-le]", 10)
131 self.debug=debug
132 self.serveur=serveur
133 self.overops=config.overops
134 self.ops=self.overops+config.ops
135 self.report_bugs_to=config.report_bugs_to
136 self.chanlist=config.chanlist
137 self.identities=pickle.load(open("identities.pickle","r"))
138 self.stay_channels=config.stay_channels
139 self.quiet_channels=config.quiet_channels
140 self.last_perdu=0
141
142
143 def new_connection_NK(self,serv,username,password,typ="bdd"):
144 """Renvoie (``True``, <une socket ouverte et authentifiée sur la NoteKfet2015>)
145 ou bien (``False``, None)"""
146 try:
147 login_result,sock=nk.login(username,password,typ)
148 droits,retcode,errmsg=login_result["msg"],login_result["retcode"],login_result["errmsg"]
149 except nk.NKRefused as exc:
150 for report in self.report_bugs_to:
151 serv.privmsg(report,"Le Serveur NK2015 est down.")
152 return (False,None)
153 except nk.NKHelloFailed as exc:
154 for report in self.report_bugs_to:
155 serv.privmsg(report,
156 "La version du site utilisée n'est pas supportée par le serveur NK2015.")
157 return (False,None)
158 except nk.NKUnknownError as exc:
159 erreurs=["Une fucking erreur inconnue s'est produite"]
160 erreurs+=str(exc).split("\n")
161 for report in self.report_bugs_to:
162 for err in erreurs:
163 serv.privmsg(report,err)
164 return (False,None)
165 except Exception as exc:
166 # Exception qui ne vient pas de la communication avec le serveur NK2015
167 log(self.serveur,"Erreur dans new_connection_NK\n"+str(exc))
168 return (False,None)
169 if retcode==0:
170 return (True,sock)
171 else:
172 return (False,None)
173
174 def give_me_my_pseudo(self,serv):
175 """Récupère le pseudo auprès de NickServ."""
176 serv.privmsg("NickServ","RECOVER %s %s"%(config.irc_pseudo,config.irc_password))
177 serv.privmsg("NickServ","RELEASE %s %s"%(config.irc_pseudo,config.irc_password))
178 time.sleep(0.3)
179 serv.nick(config.irc_pseudo)
180
181 def on_welcome(self, serv, ev):
182 self.serv=serv # ça serv ira :)
183 self.give_me_my_pseudo(serv)
184 serv.privmsg("NickServ","identify %s"%(config.irc_password))
185 log(self.serveur,"Connected")
186 if self.debug:
187 self.chanlist=["#bot"]
188 for c in self.chanlist:
189 log(self.serveur,"JOIN %s"%(c))
190 serv.join(c)
191 # on ouvre la connexion note de Basile, special user
192 self.nk=self.new_connection_NK(serv,config.note_pseudo,config.note_password,"special")[1]
193 if self.nk==None:
194 for report in self.report_bugs_to:
195 serv.privmsg(report,"Connection to NK2015 failed, invalid password ?")
196
197 def lost(self,serv,channel,forced=False):
198 if self.last_perdu+config.time_between_perdu<time.time() or forced:
199 if not channel in self.quiet_channels or forced:
200 serv.privmsg(channel,"J'ai perdu !")
201 self.last_perdu=time.time()
202 delay=config.time_between_perdu_trigger
203 delta=config.time_between_perdu_trigger_delta
204 serv.execute_delayed(random.randrange(delay-delta,delay+delta),self.lost,(serv,channel))
205
206 def pourmoi(self, serv, message):
207 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
208 pseudo=self.nick
209 size=len(pseudo)
210 if message[:size]==pseudo and len(message)>size and message[size]==":":
211 return (True,message[size+1:].lstrip(" "))
212 else:
213 return (False,message)
214
215 def on_privmsg(self, serv, ev):
216 message=ev.arguments()[0]
217 auteur = irclib.nm_to_n(ev.source())
218 try:
219 test=bot_unicode(message)
220 except UnicodeBotError:
221 if config.utf8_trigger:
222 serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8"))
223 return
224 message=message.split()
225 cmd=message[0].lower()
226 notunderstood=False
227 if cmd=="help":
228 helpdico={"help":["""HELP <commande>
229 Affiche de l'aide sur la commande""",None,None],
230 "identify": ["""IDENTIFY <username> <password>
231 Vérifie le mot de passe et me permet de savoir à l'avenir quel est votre pseudo note kfet.
232 Sans paramètre, je vous précise sous quel pseudo je vous connais.""",None,None],
233 "drop":["""DROP <password>
234 Vérifie le mot de passe et me fait d'oublier votre pseudo note kfet.""",None,None],
235 "solde": ["""SOLDE
236 Affiche votre solde, si je connais votre pseudo note kfet.""",
237 """SOLDE <pseudo>
238 Affiche le solde de la personne désignée (par son pseudo note).""",None],
239 "join": [None, """JOIN <channel>
240 Me fait rejoindre le channel""",None],
241 "leave": [None,"""LEAVE <channel>
242 Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
243 "quiet": [None,"""QUIET <channel>
244 Me rend silencieux sur le channel.""",None],
245 "noquiet": [None,"""NOQUIET <channel>
246 Me rend la parole sur le channel.""",None],
247 "lost": [None,"""LOST <channel>
248 Me fait perdre sur le channel.""",None],
249 "reconnect": [None,"""RECONNECT
250 Établit à nouveau la connexion avec le serveur NK2015""",None],
251 "reload": [None,"""RELOAD
252 Recharge la configuration.""",None],
253 "say": [None,None,"""SAY <channel> <message>
254 Me fait parler sur le channel."""],
255 "do": [None,None,"""DO <channel> <action>
256 Me fait faitre une action (/me) sur le channel."""],
257 "stay": [None,None,"""STAY <channel>
258 Ajoute le channel à ma stay_list."""],
259 "nostay": [None,None,"""NOSTAY <channel>
260 Retire le channel de ma stay_list."""],
261 "ops": [None,None,"""OPS
262 Affiche la liste des ops."""],
263 "overops": [None,None,"""OVEROPS
264 Affiche la liste des overops."""],
265 "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
266 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
267 "die": [None,None,"""DIE
268 Me déconnecte du serveur IRC."""],
269 "crash": [None,None,"""CRASH
270 Me fait crasher"""]
271 }
272 helpmsg_default="Liste des commandes disponibles :\nHELP IDENTIFY DROP SOLDE"
273 helpmsg_ops=" JOIN LEAVE QUIET NOQUIET LOST RECONNECT RELOAD"
274 helpmsg_overops=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE CRASH"
275 op,overop=auteur in self.ops, auteur in self.overops
276 if len(message)==1:
277 helpmsg=helpmsg_default
278 if op:
279 helpmsg+=helpmsg_ops
280 if overop:
281 helpmsg+=helpmsg_overops
282 else:
283 helpmsgs=helpdico.get(message[1].lower(),["Commande inconnue.",None,None])
284 helpmsg=helpmsgs[0]
285 if op and helpmsgs[1]:
286 if helpmsg:
287 helpmsg+="\n"+helpmsgs[1]
288 else:
289 helpmsg=helpmsgs[1]
290 if overop and helpmsgs[2]:
291 if helpmsg:
292 helpmsg+="\n"+helpmsgs[2]
293 else:
294 helpmsg=helpmsgs[2]
295 for ligne in helpmsg.split("\n"):
296 serv.privmsg(auteur,ligne)
297 elif cmd=="identify":
298 if len(message)==1:
299 if self.identities.has_key(auteur):
300 serv.privmsg(auteur,"Je vous connais sous le pseudo note %s."%(
301 self.identities[auteur].encode("utf8")))
302 else:
303 serv.privmsg(auteur,"Je ne connais pas votre pseudo note.")
304 elif len(message)>=3:
305 username,password=message[1],unicode(" ".join(message[2:]),"utf8")
306 success,_=self.new_connection_NK(serv,username,password)
307 if success:
308 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
309 serv.privmsg(auteur,"Identité enregistrée.")
310 self.identities[auteur]=username
311 pickle.dump(Xself.identities,open("identities.pickle","w"))
312 else:
313 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
314 serv.privmsg(auteur,"Mot de passe invalide. (ou serveur down)")
315 else:
316 serv.privmsg(auteur,u"Syntaxe : IDENTIFY [<username> <password>]")
317 elif cmd=="drop":
318 if len(message)>1:
319 if self.identities.has_key(auteur):
320 password=" ".join(message[1:])
321 success,_=self.new_connection_NK(serv,self.identities[auteur],password)
322 if success:
323 del self.identities[auteur]
324 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
325 pickle.dump(self.identities,open("identities.pickle","w"))
326 serv.privmsg(auteur,"Identité oubliée.")
327 else:
328 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
329 serv.privmsg(auteur,"Mot de passe invalide. (ou serveur down)")
330 else:
331 serv.privmsg(auteur,"Je ne connais pas ton pseudo note.")
332 else:
333 serv.privmsg(auteur,"Syntaxe : DROP <password>")
334 elif cmd=="join":
335 if auteur in self.ops:
336 if len(message)>1:
337 if message[1] in self.chanlist:
338 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
339 else:
340 serv.join(message[1])
341 self.chanlist.append(message[1])
342 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
343 log(self.serveur,"priv",auteur," ".join(message))
344 else:
345 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
346 else:
347 notunderstood=True
348 elif cmd=="leave":
349 if auteur in self.ops and len(message)>1:
350 if message[1] in self.chanlist:
351 if not (message[1] in self.stay_channels) or auteur in self.overops:
352 self.quitter(message[1]," ".join(message[2:]))
353 self.chanlist.remove(message[1])
354 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
355 else:
356 serv.privmsg(auteur,"Non, je reste !")
357 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
358 else:
359 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
360 else:
361 notunderstood=True
362 elif cmd=="stay":
363 if auteur in self.overops:
364 if len(message)>1:
365 if message[1] in self.stay_channels:
366 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
367 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
368 else:
369 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
370 self.stay_channels.append(message[1])
371 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
372 else:
373 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
374 else:
375 notunderstood=True
376 elif cmd=="nostay":
377 if auteur in self.overops:
378 if len(message)>1:
379 if message[1] in self.stay_channels:
380 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
381 self.stay_channels.remove(message[1])
382 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
383 else:
384 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
385 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
386
387 else:
388 notunderstood=True
389 elif cmd=="die":
390 if auteur in self.overops:
391 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
392 self.mourir()
393 else:
394 notunderstood=True
395 elif cmd=="crash":
396 if auteur in self.overops:
397 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
398 self.crash("priv", auteur)
399 else:
400 notunderstood=True
401 elif cmd=="reload":
402 if auteur in self.ops:
403 self.reload(auteur)
404 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
405 else:
406 notunderstood=True
407 elif cmd=="reconnect":
408 if auteur in self.ops:
409 try:
410 self.nk=self.new_connection_NK(serv,config.note_pseudo,
411 config.note_password,"special")[1]
412 except Exception as exc:
413 self.nk=None
414 log(self.serveur,"""Erreur dans on_pubmsg/"cmd in ["reconnect"]\n"""+str(exc))
415 if self.nk!=None:
416 serv.privmsg(auteur,"%s: done"%(auteur))
417 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
418 else:
419 serv.privmsg(auteur,"%s: failed"%(auteur))
420 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
421 for report in self.report_bugs_to:
422 serv.privmsg(report,"Connection to NK2015 failed, invalid password ? Server dead ?")
423 else:
424 notunderstood=True
425 elif cmd=="quiet":
426 if auteur in self.ops:
427 if len(message)>1:
428 if message[1] in self.quiet_channels:
429 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
430 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
431 else:
432 self.quiet_channels.append(message[1])
433 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
434 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
435 else:
436 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
437 else:
438 notunderstood=True
439 elif cmd=="noquiet":
440 if auteur in self.ops:
441 if len(message)>1:
442 if message[1] in self.quiet_channels:
443 self.quiet_channels.remove(message[1])
444 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
445 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
446 else:
447 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
448 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
449 else:
450 notunderstood=True
451 elif cmd=="say":
452 if auteur in self.overops and len(message)>2:
453 serv.privmsg(message[1]," ".join(message[2:]))
454 log(self.serveur,"priv",auteur," ".join(message))
455 elif len(message)<=2:
456 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
457 else:
458 notunderstood=True
459 elif cmd=="do":
460 if auteur in self.overops and len(message)>2:
461 serv.action(message[1]," ".join(message[2:]))
462 log(self.serveur,"priv",auteur," ".join(message))
463 elif len(message)<=2:
464 serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
465 else:
466 notunderstood=True
467 elif cmd=="kick":
468 if auteur in self.overops and len(message)>2:
469 serv.kick(message[1],message[2]," ".join(message[3:]))
470 log(self.serveur,"priv",auteur," ".join(message))
471 elif len(message)<=2:
472 serv.privmsg(auteur,"Syntaxe : KICK <channel> <pseudo> [<raison>]")
473 else:
474 notunderstood=True
475 elif cmd=="lost":
476 if auteur in self.ops and len(message)>1:
477 serv.privmsg(message[1],"J'ai perdu !")
478 log(self.serveur,"priv",auteur," ".join(message))
479 elif len(message)<=1:
480 serv.privmsg(auteur,"Syntaxe : LOST <channel>")
481 else:
482 notunderstood=True
483 elif cmd=="solde":
484 if len(message)==1:
485 if self.identities.has_key(auteur):
486 try:
487 self.nk.write('["search", ["x",["pseudo"],%s]]'%(json.dumps(self.identities[auteur])))
488 ret=json.loads(self.nk.read())
489 solde=ret["msg"][0]["solde"]
490 pseudo=ret["msg"][0]["pseudo"]
491 except Exception as exc:
492 print exc
493 serv.privmsg(auteur,"failed")
494 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
495 return
496 serv.privmsg(auteur,"%s (%s)"%(float(solde)/100,pseudo.encode("utf8")))
497 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
498 else:
499 serv.privmsg(canal,"Je ne connais pas ton pseudo note.")
500 elif auteur in self.ops:
501 try:
502 self.nk.write('["search", ["x",["pseudo"],%s]]'%(json.dumps(message[1])))
503 ret=json.loads(self.nk.read())
504 solde=ret["msg"][0]["solde"]
505 pseudo=ret["msg"][0]["pseudo"]
506 except Exception as exc:
507 serv.privmsg(auteur,"failed")
508 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
509 return
510 serv.privmsg(auteur,"%s (%s)"%(float(solde)/100,pseudo.encode("utf8")))
511 elif cmd=="ops":
512 if auteur in self.overops:
513 serv.privmsg(auteur," ".join(self.ops))
514 else:
515 notunderstood=True
516 elif cmd=="overops":
517 if auteur in self.overops:
518 serv.privmsg(auteur," ".join(self.overops))
519 else:
520 notunderstood=True
521 else:
522 notunderstood=True
523 if notunderstood:
524 serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…")
525
526 def on_pubmsg(self, serv, ev):
527 auteur = irclib.nm_to_n(ev.source())
528 canal = ev.target()
529 message = ev.arguments()[0]
530 try:
531 test=bot_unicode(message)
532 except UnicodeBotError:
533 if config.utf8_trigger and not canal in self.quiet_channels:
534 serv.privmsg(canal, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf8"))
535 return
536 pour_moi,message=self.pourmoi(serv,message)
537 if pour_moi and message.split()!=[]:
538 cmd=message.split()[0].lower()
539 try:
540 args=" ".join(message.split()[1:])
541 except:
542 args=""
543 if cmd in ["meurs","die","crève"]:
544 if auteur in self.overops:
545 log(self.serveur,canal,auteur,message+"[successful]")
546 self.mourir()
547 else:
548 serv.privmsg(canal,("%s: %s"%(auteur,random.choice(config.quit_fail_messages))).encode("utf8"))
549 log(self.serveur,canal,auteur,message+"[failed]")
550 elif cmd == "reload":
551 if auteur in self.ops:
552 log(self.serveur, canal, auteur, message+"[successful]")
553 self.reload(canal)
554 elif cmd == "crash":
555 if auteur in self.overops:
556 self.crash(auteur, message)
557 elif cmd in ["part","leave","dégage","va-t-en","tut'tiresailleurs,c'estmesgalets"]:
558 if auteur in self.ops and (not (canal in self.stay_channels)
559 or auteur in self.overops):
560 self.quitter(canal)
561 log(self.serveur,canal,auteur,message+"[successful]")
562 if canal in self.chanlist:
563 self.chanlist.remove(canal)
564 else:
565 serv.privmsg(canal,("%s: %s"%(auteur,random.choice(config.leave_fail_messages))).encode("utf8"))
566 log(self.serveur,canal,auteur,message+"[failed]")
567
568 elif cmd in ["reconnect"]:
569 if auteur in self.ops:
570 try:
571 self.nk=self.new_connection_NK(serv,config.note_pseudo,
572 config.note_password,"special")[1]
573 except Exception as exc:
574 self.nk=None
575 log(self.serveur,"""Erreur dans on_pubmsg/"cmd in ["reconnect"]\n"""+str(exc))
576 if self.nk!=None:
577 serv.privmsg(canal,"%s: done"%(auteur))
578 log(self.serveur,canal,auteur,message+"[successful]")
579 else:
580 serv.privmsg(canal,"%s: failed"%(auteur))
581 log(self.serveur,canal,auteur,message+"[failed]")
582 for report in self.report_bugs_to:
583 serv.privmsg(report,"Connection to NK2015 failed, invalid password ? Server dead ?")
584 else:
585 serv.privmsg(canal,"%s: %s"%(auteur,random.choice(config.pas_programme_pour_tobeir).encode("utf8")))
586 log(self.serveur,canal,auteur,message+"[failed]")
587
588 elif cmd in ["deviens","pseudo"]:
589 if auteur in self.ops:
590 become=args
591 serv.nick(become)
592 log(self.serveur,canal,auteur,message+"[successful]")
593
594 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
595 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
596 elif cmd in ["ping"] and not canal in self.quiet_channels:
597 serv.privmsg(canal,"%s: pong"%(auteur))
598
599 elif cmd in ["solde","!solde"]:
600 if self.identities.has_key(auteur):
601 pseudo=self.identities[auteur]
602 try:
603 self.nk.write('["search", ["x",["pseudo"],%s]]'%(json.dumps(pseudo)))
604 ret=json.loads(self.nk.read())
605 solde=ret["msg"][0]["solde"]
606 pseudo=ret["msg"][0]["pseudo"]
607 except Exception as exc:
608 serv.privmsg(canal,"%s: failed"%(auteur))
609 log(self.serveur,canal,auteur,message+"[failed]")
610 else:
611 serv.privmsg(canal,"%s: %s (%s)"%(auteur,float(solde)/100,pseudo.encode("utf8")))
612 log(self.serveur,canal,auteur,message+"[successful]")
613 else:
614 serv.privmsg(canal,"%s: Je ne connais pas votre pseudo note."%(auteur))
615 log(self.serveur,canal,auteur,message+"[unknown]")
616 elif (re.match("!?(pain au chocolat|chocolatine)",message.lower())
617 and not canal in self.quiet_channels):
618 serv.action(canal,"sert un pain au chocolat à %s"%(auteur))
619 elif re.match("!?manzana",message.lower()) and not canal in self.quiet_channels:
620 if auteur in config.manzana:
621 serv.action(canal,"sert une bouteille de manzana à %s"%(auteur))
622 elif auteur in config.manzana_bis:
623 serv.action(canal,"sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas."%(auteur))
624 else:
625 serv.action(canal,"sert un verre de manzana à %s"%(auteur))
626 if is_insult(message) and not canal in self.quiet_channels:
627 if is_not_insult(message):
628 answer=random.choice(config.compliment_answers)
629 for ligne in answer.split("\n"):
630 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
631 else:
632 answer=random.choice(config.insultes_answers)
633 for ligne in answer.split("\n"):
634 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
635 elif is_compliment(message) and not canal in self.quiet_channels:
636 answer=random.choice(config.compliment_answers)
637 for ligne in answer.split("\n"):
638 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
639 gros_match=is_gros(message)
640 if gros_match and not canal in self.quiet_channels:
641 taille=get_filesize()
642 answer=u"Mais non, je ne suis pas %s, %sKo tout au plus…"%(gros_match.groups()[0],taille)
643 serv.privmsg(canal,"%s: %s"%(auteur,answer.encode("utf8")))
644 if is_tesla(message) and not canal in self.quiet_channels:
645 l1,l2=config.tesla_answers,config.tesla_actions
646 n1,n2=len(l1),len(l2)
647 i=random.randrange(n1+n2)
648 if i>=n1:
649 serv.action(canal,l2[i-n1].encode("utf8"))
650 else:
651 serv.privmsg(canal,"%s: %s"%(auteur,l1[i].encode("utf8")))
652 if is_tamere(message) and not canal in self.quiet_channels:
653 answer=random.choice(config.tamere_answers)
654 for ligne in answer.split("\n"):
655 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
656 if is_tag(message) and not canal in self.quiet_channels:
657 if auteur in self.ops:
658 action=random.choice(config.tag_actions)
659 serv.action(canal,action.encode("utf8"))
660 self.quiet_channels.append(canal)
661 else:
662 answer=random.choice(config.tag_answers)
663 for ligne in answer.split("\n"):
664 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
665 if is_merci(message):
666 answer=random.choice(config.merci_answers)
667 for ligne in answer.split("\n"):
668 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
669 out=re.match(ur"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$",
670 unicode(message.upper(),"utf8"))
671 if re.match("ma bite dans ton oreille",message) and not canal in self.quiet_channels:
672 serv.privmsg(canal,"%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !"%(auteur))
673 if out and not canal in self.quiet_channels:
674 out = out.groups()[0]
675 try:
676 iout = int(out)
677 serv.privmsg(canal,"%s: %s !"%(auteur, iout + 1))
678 if iout == 2147483647:
679 serv.privmsg(canal,"%s: Ciel, un maxint ! Heureusement que je suis en python…" % (auteur))
680 return
681 if iout + 1 > 1000 and random.randrange(4)==0:
682 serv.privmsg(canal,"%s: Vous savez, moi et les chiffres…"%(auteur))
683 return
684 except Exception as exc:
685 pass
686 if re.match("[A-Y]",out):
687 alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
688 serv.privmsg(canal,"%s: %s !"%(auteur,alphabet[alphabet.index(out)+1]))
689 elif out=="Z":
690 serv.privmsg(canal,"%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?"%(auteur))
691 elif out in "[\\":
692 serv.privmsg(canal,"%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…"%(auteur))
693 elif re.match(ur"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+",out):
694 def translate(mess):
695 return "".join([{u"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i]:u"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i]
696 for i in range(20)}[j]
697 for j in mess])
698 out=int(translate(out))
699 serv.privmsg(canal,"%s: %s !"%(auteur,translate(str(out+1)).encode("utf8")))
700 if is_bonjour(message) and not canal in self.quiet_channels:
701 if is_night():
702 answer=random.choice(config.night_answers)
703 elif is_day():
704 answer=random.choice(config.bonjour_answers)
705 else:
706 answer=random.choice(config.bonsoir_answers)
707 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
708 if is_bonne_nuit(message) and not canal in self.quiet_channels:
709 answer=random.choice(config.bonne_nuit_answers)
710 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
711 if is_pan(message) and not canal in self.quiet_channels:
712 serv.privmsg(canal,"%s: ce n'est pas sur moi qu'il faut tirer, même si je sais que j'attire l'œil !"%(auteur))
713 else:
714 if message in ["!pain au chocolat","!chocolatine"] and not canal in self.quiet_channels:
715 serv.action(canal,"sert un pain au chocolat à %s"%(auteur))
716 if message in ["!manzana"] and not canal in self.quiet_channels:
717 if auteur in config.manzana:
718 serv.action(canal,"sert une bouteille de manzana à %s"%(auteur))
719 elif auteur in config.manzana_bis:
720 serv.action(canal,"sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas."%(auteur))
721 else:
722 serv.action(canal,"sert un verre de manzana à %s"%(auteur))
723 if re.match(u'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$',message.decode("utf8")) and not canal in self.quiet_channels:
724 failanswers=config.buffer_fail_answers
725 answer=random.choice(failanswers)
726 serv.privmsg(canal,("%s: %s"%(auteur,answer)).encode("utf8"))
727 if not canal in self.quiet_channels:
728 mypseudo=self.nick
729 if re.match((u"^("+u"|".join(config.bonjour_triggers)
730 +ur")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
731 ).format(mypseudo).lower(), message.decode("utf8").strip().lower()):
732 answer=random.choice(config.bonjour_answers)
733 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
734 if (is_perdu(message) and not canal in self.quiet_channels):
735 # proba de perdre sur trigger :
736 # avant 30min (enfin, config) : 0
737 # ensuite, +25%/30min, linéairement
738 deltat=time.time()-self.last_perdu
739 barre=(deltat-config.time_between_perdu)/(2*3600.0)
740 if random.uniform(0,1)<barre:
741 serv.privmsg(canal,"%s: J'ai perdu !"%(auteur))
742 self.last_perdu=time.time()
743
744 def on_action(self, serv, ev):
745 action = ev.arguments()[0]
746 auteur = irclib.nm_to_n(ev.source())
747 channel = ev.target()
748 try:
749 test=bot_unicode(action)
750 except UnicodeBotError:
751 if config.utf8_trigger and not channel in self.quiet_channels:
752 serv.privmsg(channel, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf8"))
753 return
754 mypseudo=self.nick
755
756 if is_bad_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
757 l1,l2=config.bad_action_answers,config.bad_action_actions
758 n1,n2=len(l1),len(l2)
759 i=random.randrange(n1+n2)
760 if i>=n1:
761 serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
762 else:
763 serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
764 if is_good_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
765 l1,l2=config.good_action_answers,config.good_action_actions
766 n1,n2=len(l1),len(l2)
767 i=random.randrange(n1+n2)
768 if i>=n1:
769 serv.action(channel,l2[i-n1].format(auteur).format(auteur).encode("utf8"))
770 else:
771 serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
772
773 def on_kick(self,serv,ev):
774 auteur = irclib.nm_to_n(ev.source())
775 channel = ev.target()
776 victime = ev.arguments()[0]
777 raison = ev.arguments()[1]
778 if victime==self.nick:
779 log(self.serveur,"%s kické de %s par %s (raison : %s)" %(victime,channel,auteur,raison))
780 time.sleep(2)
781 serv.join(channel)
782 l1,l2=config.kick_answers,config.kick_actions
783 n1,n2=len(l1),len(l2)
784 i=random.randrange(n1+n2)
785 if i>=n1:
786 serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
787 else:
788 serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
789
790 def quitter(self,chan,leave_message=None):
791 if leave_message==None:
792 leave_message=random.choice(config.leave_messages)
793 self.serv.part(chan,message=leave_message.encode("utf8"))
794
795 def mourir(self):
796 quit_message=random.choice(config.quit_messages)
797 self.die(msg=quit_message.encode("utf8"))
798
799 def _getnick(self):
800 return self.serv.get_nickname()
801 nick=property(_getnick)
802
803 def reload(self, auteur=None):
804 reload(config)
805 if auteur in [None, "SIGHUP"]:
806 towrite = "Config reloaded" + " (SIGHUP received)"*(auteur == "SIGHUP")
807 for to in config.report_bugs_to:
808 self.serv.privmsg(to, towrite)
809 log(self.serveur, towrite)
810 else:
811 self.serv.privmsg(auteur,"Config reloaded")
812
813 def crash(self, chan="nowhere", who="nobody"):
814 where = u"en privé" if chan == "priv" else u"sur le chan %s" % chan
815 raise CrashError(u"Crash demandé par %s %s" % (who, where))
816
817 def start_as_daemon(self, outfile):
818 sys.stderr = Logger(outfile)
819 self.start()
820
821
822 class Logger(object):
823 """Pour écrire ailleurs que sur stdout"""
824 def __init__(self, filename="basile.full.log"):
825 self.filename = filename
826
827 def write(self, message):
828 f = open(self.filename, "a")
829 f.write(message)
830 f.close()
831
832 def main():
833 if len(sys.argv)==1:
834 print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
835 print " --outfile sans --no-output ni --daemon n'a aucun effet"
836 exit(1)
837 serveur=sys.argv[1]
838 if "--daemon" in sys.argv:
839 thisfile = os.path.realpath(__file__)
840 thisdirectory = thisfile.rsplit("/", 1)[0]
841 os.chdir(thisdirectory)
842 daemon = True
843 else:
844 daemon = False
845 if "debug" in sys.argv or "--debug" in sys.argv:
846 debug=True
847 else:
848 debug=False
849 if "--quiet" in sys.argv:
850 config.debug_stdout=False
851 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
852 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
853 if "--no-output" in sys.argv or "--daemon" in sys.argv:
854 outfile = "/var/log/bots/basile.full.log"
855 for arg in sys.argv:
856 arg = arg.split("=")
857 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
858 outfile = arg[1]
859 sys.stdout = Logger(outfile)
860 try:
861 serveur=serveurs[serveur]
862 except KeyError:
863 print "Server Unknown : %s"%(serveur)
864 exit(404)
865 basile=Basile(serveur,debug)
866 # Si on reçoit un SIGHUP, on reload la config
867 def sighup_handler(signum, frame):
868 basile.reload("SIGHUP")
869 signal.signal(signal.SIGHUP, sighup_handler)
870 if daemon:
871 child_pid = os.fork()
872 if child_pid == 0:
873 os.setsid()
874 basile.start_as_daemon(outfile)
875 else:
876 # on enregistre le pid de basile
877 pidfile = "/var/run/bots/basile.pid"
878 for arg in sys.argv:
879 arg = arg.split("=")
880 if arg[0].strip('-') in ["pidfile"]:
881 pidfile = arg[1]
882 f = open(pidfile, "w")
883 f.write("%s\n" % child_pid)
884 f.close()
885 else:
886 basile.start()
887
888 if __name__ == "__main__":
889 main()