4 # Codé par 20-100 (commencé le 23/04/12)
6 # Un bot IRC qui, un jour, s'interfacera avec la Note Kfet 2015
11 import socket
, ssl
, json
18 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
19 sys
.path
.insert(0, "/home/vincent/scripts/python-myirclib")
23 from commands
import getstatusoutput
as ex
25 # on récupère la config
28 # la partie qui réfère au fichier lui-même est mieux ici
29 # sinon on réfère la config et pas le fichier lui-même
31 config
.thisfile
= os
.path
.realpath( __file__
)
33 def get_config_logfile(serveur
):
34 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
35 return config
.logfile_template
%(serveurs
[serveur
])
38 return ex("ls -s %s"%(config
.thisfile
))[1].split()[0]
40 class NKError(Exception):
41 def __init__(self
,msg
):
42 Exception.__init
__(self
)
46 def __unicode__(self
):
47 return unicode(self
.msg
)
49 class NKRefused(NKError
):
52 class NKHelloFailed(NKError
):
55 class NKUnknownError(NKError
):
58 def log(serveur
,channel
,auteur
=None,message
=None):
59 f
=open(get_config_logfile(serveur
),"a")
60 if auteur
==message
==None:
61 # alors c'est que c'est pas un channel mais juste une ligne de log
62 chain
="%s %s"%(time
.strftime("%F %T"),channel
)
64 chain
="%s [%s:%s] %s"%(time
.strftime("%F %T"),channel
,auteur
,message
)
66 if config
.debug_stdout
:
73 # On établit la connexion sur port 4242
74 sock
.connect(("127.0.0.1",4242))
76 sock
=ssl
.wrap_socket(sock
,ca_certs
='../keys/ca_.crt')
78 sock
.write('hello "Basile"')
79 # On récupère la réponse du hello
82 except Exception as exc
:
83 # Si on a foiré quelque part, c'est que le serveur est down
84 raise NKRefused(str(exc
))
87 elif out
["retcode"]==11:
88 raise NKHelloFailed(out
["errmsg"])
90 raise NKUnknownError(out
["errmsg"])
92 def login_NK(username
,password
,typ
="bdd"):
94 if typ
=="special": # ça c'est pour Basile lui-même
97 masque
='[[], [], true]'
99 # Basile a un compte special user
100 commande
='login [%s,%s,"%s",%s]'%(json
.dumps(username
),json
.dumps(password
),typ
,masque
)
103 except Exception as exc
:
104 # Si on a foiré quelque part, c'est que le serveur est down
105 raise NKRefused(str(exc
))
106 # On vérifie ensuite que le login
107 return json
.loads(out
),sock
110 def is_something(chain
,matches
,avant
=u
".*(?:^| )",apres
=u
"(?:$|\.| |,|;).*",case_sensitive
=False,debug
=False):
112 chain
=unicode(chain
,"utf8")
114 chain
=unicode(chain
,"utf8").lower()
115 allmatches
="("+"|".join(matches
)+")"
116 reg
=(avant
+allmatches
+apres
).lower()
117 o
=re
.match(reg
,chain
)
120 def is_insult(chain
,debug
=True):
121 return is_something(chain
,config
.insultes
,avant
=".*(?:^| |')")
122 def is_not_insult(chain
):
123 chain
=unicode(chain
,"utf8")
124 insult_regexp
=u
"("+u
"|".join(config
.insultes
)+u
")"
125 middle_regexp
=u
"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
126 reg
=".*pas %s%s.*"%(middle_regexp
,insult_regexp
)
127 if re
.match(reg
,chain
):
131 def is_compliment(chain
,debug
=True):
132 return is_something(chain
,config
.compliment_triggers
,avant
=".*(?:^| |')")
134 return is_something(chain
,config
.perdu
)
136 return is_something(chain
,config
.tag_triggers
)
138 return is_something(chain
,config
.gros
)
140 return is_something(chain
,config
.tesla_triggers
,avant
=u
"^",apres
=u
"$",debug
=True)
142 return is_something(chain
,config
.merci_triggers
)
143 def is_tamere(chain
):
144 return is_something(chain
,config
.tamere_triggers
)
145 def is_bad_action_trigger(chain
,pseudo
):
146 return is_something(chain
,config
.bad_action_triggers
,avant
=u
"^",
147 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
148 def is_good_action_trigger(chain
,pseudo
):
149 return is_something(chain
,config
.good_action_triggers
,avant
=u
"^",
150 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
151 def is_bonjour(chain
):
152 return is_something(chain
,config
.bonjour_triggers
,avant
=u
"^")
153 def is_bonne_nuit(chain
):
154 return is_something(chain
,config
.bonne_nuit_triggers
,avant
=u
"^")
156 return re
.match(u
"^(pan|bim|bang)( .*)?$",unicode(chain
,"utf8").lower().strip())
159 _
,_
,_
,h
,m
,s
,_
,_
,_
=time
.localtime()
160 return (conf
[0],0,0)<(h
,m
,s
)<(conf
[1],0,0)
162 return is_time(config
.daytime
)
164 return is_time(config
.nighttime
)
167 class UnicodeBotError(Exception):
170 class CrashError(Exception):
171 """Pour pouvoir faire crasher Basile, parce que ça a l'air drôle"""
174 def bot_unicode(chain
):
176 unicode(chain
,"utf8")
177 except UnicodeDecodeError as exc
:
178 raise UnicodeBotError
181 class Basile(ircbot
.SingleServerIRCBot
):
182 def __init__(self
,serveur
,debug
=False):
183 temporary_pseudo
=config
.irc_pseudo
+str(random
.randrange(10000,100000))
184 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
185 temporary_pseudo
,"Basile, le bot irc.[Codé par 20-100, fouettez-le]", 10)
188 self
.overops
=config
.overops
189 self
.ops
=self
.overops
+config
.ops
190 self
.report_bugs_to
=config
.report_bugs_to
191 self
.chanlist
=config
.chanlist
192 self
.identities
=pickle
.load(open("identities.pickle","r"))
193 self
.stay_channels
=config
.stay_channels
194 self
.quiet_channels
=config
.quiet_channels
198 def new_connection_NK(self
,serv
,username
,password
,typ
="bdd"):
200 login_result
,sock
=login_NK(username
,password
,typ
)
201 droits
,retcode
,errmsg
=login_result
["msg"],login_result
["retcode"],login_result
["errmsg"]
202 except NKRefused
as exc
:
203 for report
in self
.report_bugs_to
:
204 serv
.privmsg(report
,"Le Serveur NK2015 est down.")
206 except NKHelloFailed
as exc
:
207 for report
in self
.report_bugs_to
:
209 "La version du site utilisée n'est pas supportée par le serveur NK2015.")
211 except NKUnknownError
as exc
:
212 erreurs
=["Une fucking erreur inconnue s'est produite"]
213 erreurs
+=str(exc
).split("\n")
214 for report
in self
.report_bugs_to
:
216 serv
.privmsg(report
,err
)
218 except Exception as exc
:
219 # Exception qui ne vient pas de la communication avec le serveur NK2015
220 log(self
.serveur
,"Erreur dans new_connection_NK\n"+str(exc
))
227 def give_me_my_pseudo(self
,serv
):
228 serv
.privmsg("NickServ","RECOVER %s %s"%(config
.irc_pseudo
,config
.irc_password
))
229 serv
.privmsg("NickServ","RELEASE %s %s"%(config
.irc_pseudo
,config
.irc_password
))
231 serv
.nick(config
.irc_pseudo
)
233 def on_welcome(self
, serv
, ev
):
234 self
.serv
=serv
# ça serv ira :)
235 self
.give_me_my_pseudo(serv
)
236 serv
.privmsg("NickServ","identify %s"%(config
.irc_password
))
237 log(self
.serveur
,"Connected")
239 self
.chanlist
=["#bot"]
240 for c
in self
.chanlist
:
241 log(self
.serveur
,"JOIN %s"%(c))
243 # on ouvre la connexion note de Basile, special user
244 self
.nk
=self
.new_connection_NK(serv
,config
.note_pseudo
,config
.note_password
,"special")[1]
246 for report
in self
.report_bugs_to
:
247 serv
.privmsg(report
,"Connection to NK2015 failed, invalid password ?")
249 def lost(self
,serv
,channel
,forced
=False):
250 if self
.last_perdu
+config
.time_between_perdu
<time
.time() or forced
:
251 if not channel
in self
.quiet_channels
or forced
:
252 serv
.privmsg(channel
,"J'ai perdu !")
253 self
.last_perdu
=time
.time()
254 delay
=config
.time_between_perdu_trigger
255 delta
=config
.time_between_perdu_trigger_delta
256 serv
.execute_delayed(random
.randrange(delay
-delta
,delay
+delta
),self
.lost
,(serv
,channel
))
258 def pourmoi(self
, serv
, message
):
259 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
262 if message
[:size
]==pseudo
and len(message
)>size
and message
[size
]==":":
263 return (True,message
[size
+1:].lstrip(" "))
265 return (False,message
)
267 def on_privmsg(self
, serv
, ev
):
268 message
=ev
.arguments()[0]
269 auteur
= irclib
.nm_to_n(ev
.source())
271 test
=bot_unicode(message
)
272 except UnicodeBotError
:
273 if config
.utf8_trigger
:
274 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
276 message
=message
.split()
277 cmd
=message
[0].lower()
280 helpdico
={"help":["""HELP <commande>
281 Affiche de l'aide sur la commande""",None,None],
282 "identify": ["""IDENTIFY <username> <password>
283 Vérifie le mot de passe et me permet de savoir à l'avenir quel est votre pseudo note kfet.
284 Sans paramètre, je vous précise sous quel pseudo je vous connais.""",None,None],
285 "drop":["""DROP <password>
286 Vérifie le mot de passe et me fait d'oublier votre pseudo note kfet.""",None,None],
288 Affiche votre solde, si je connais votre pseudo note kfet.""",
290 Affiche le solde de la personne désignée (par son pseudo note).""",None],
291 "join": [None, """JOIN <channel>
292 Me fait rejoindre le channel""",None],
293 "leave": [None,"""LEAVE <channel>
294 Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
295 "quiet": [None,"""QUIET <channel>
296 Me rend silencieux sur le channel.""",None],
297 "noquiet": [None,"""NOQUIET <channel>
298 Me rend la parole sur le channel.""",None],
299 "lost": [None,"""LOST <channel>
300 Me fait perdre sur le channel.""",None],
301 "reconnect": [None,"""RECONNECT
302 Établit à nouveau la connexion avec le serveur NK2015""",None],
303 "reload": [None,"""RELOAD
304 Recharge la configuration.""",None],
305 "say": [None,None,"""SAY <channel> <message>
306 Me fait parler sur le channel."""],
307 "do": [None,None,"""DO <channel> <action>
308 Me fait faitre une action (/me) sur le channel."""],
309 "stay": [None,None,"""STAY <channel>
310 Ajoute le channel à ma stay_list."""],
311 "nostay": [None,None,"""NOSTAY <channel>
312 Retire le channel de ma stay_list."""],
313 "ops": [None,None,"""OPS
314 Affiche la liste des ops."""],
315 "overops": [None,None,"""OVEROPS
316 Affiche la liste des overops."""],
317 "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
318 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
319 "die": [None,None,"""DIE
320 Me déconnecte du serveur IRC."""],
321 "crash": [None,None,"""CRASH
324 helpmsg_default
="Liste des commandes disponibles :\nHELP IDENTIFY DROP SOLDE"
325 helpmsg_ops
=" JOIN LEAVE QUIET NOQUIET LOST RECONNECT RELOAD"
326 helpmsg_overops
=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE CRASH"
327 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
329 helpmsg
=helpmsg_default
333 helpmsg
+=helpmsg_overops
335 helpmsgs
=helpdico
.get(message
[1].lower(),["Commande inconnue.",None,None])
337 if op
and helpmsgs
[1]:
339 helpmsg
+="\n"+helpmsgs
[1]
342 if overop
and helpmsgs
[2]:
344 helpmsg
+="\n"+helpmsgs
[2]
347 for ligne
in helpmsg
.split("\n"):
348 serv
.privmsg(auteur
,ligne
)
349 elif cmd
=="identify":
351 if self
.identities
.has_key(auteur
):
352 serv
.privmsg(auteur
,"Je vous connais sous le pseudo note %s."%(
353 self
.identities
[auteur
].encode("utf8")))
355 serv
.privmsg(auteur
,"Je ne connais pas votre pseudo note.")
356 elif len(message
)>=3:
357 username
,password
=message
[1],unicode(" ".join(message
[2:]),"utf8")
358 success
,_
=self
.new_connection_NK(serv
,username
,password
)
360 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
361 serv
.privmsg(auteur
,"Identité enregistrée.")
362 self
.identities
[auteur
]=username
363 pickle
.dump(Xself
.identities
,open("identities.pickle","w"))
365 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
366 serv
.privmsg(auteur
,"Mot de passe invalide. (ou serveur down)")
368 serv
.privmsg(auteur
,u
"Syntaxe : IDENTIFY [<username> <password>]")
371 if self
.identities
.has_key(auteur
):
372 password
=" ".join(message
[1:])
373 success
,_
=self
.new_connection_NK(serv
,self
.identities
[auteur
],password
)
375 del self
.identities
[auteur
]
376 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
377 pickle
.dump(self
.identities
,open("identities.pickle","w"))
378 serv
.privmsg(auteur
,"Identité oubliée.")
380 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
381 serv
.privmsg(auteur
,"Mot de passe invalide. (ou serveur down)")
383 serv
.privmsg(auteur
,"Je ne connais pas ton pseudo note.")
385 serv
.privmsg(auteur
,"Syntaxe : DROP <password>")
387 if auteur
in self
.ops
:
389 if message
[1] in self
.chanlist
:
390 serv
.privmsg(auteur
,"Je suis déjà sur %s"%(message
[1]))
392 serv
.join(message
[1])
393 self
.chanlist
.append(message
[1])
394 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
395 log(self
.serveur
,"priv",auteur
," ".join(message
))
397 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
401 if auteur
in self
.ops
and len(message
)>1:
402 if message
[1] in self
.chanlist
:
403 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
404 self
.quitter(message
[1]," ".join(message
[2:]))
405 self
.chanlist
.remove(message
[1])
406 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
408 serv
.privmsg(auteur
,"Non, je reste !")
409 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
411 serv
.privmsg(auteur
,"Je ne suis pas sur %s"%(message
[1]))
415 if auteur
in self
.overops
:
417 if message
[1] in self
.stay_channels
:
418 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
419 serv
.privmsg(auteur
,"Je stay déjà sur %s."%(message
[1]))
421 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
422 self
.stay_channels
.append(message
[1])
423 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
425 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
429 if auteur
in self
.overops
:
431 if message
[1] in self
.stay_channels
:
432 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
433 self
.stay_channels
.remove(message
[1])
434 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
436 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
437 serv
.privmsg(auteur
,"Je ne stay pas sur %s."%(message
[1]))
442 if auteur
in self
.overops
:
443 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
448 if auteur
in self
.overops
:
449 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
454 if auteur
in self
.ops
:
456 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
459 elif cmd
=="reconnect":
460 if auteur
in self
.ops
:
462 self
.nk
=self
.new_connection_NK(serv
,config
.note_pseudo
,
463 config
.note_password
,"special")[1]
464 except Exception as exc
:
466 log(self
.serveur
,"""Erreur dans on_pubmsg/"cmd in ["reconnect"]\n"""+str(exc
))
468 serv
.privmsg(auteur
,"%s: done"%(auteur))
469 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
471 serv
.privmsg(auteur
,"%s: failed"%(auteur))
472 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
473 for report
in self
.report_bugs_to
:
474 serv
.privmsg(report
,"Connection to NK2015 failed, invalid password ? Server dead ?")
478 if auteur
in self
.ops
:
480 if message
[1] in self
.quiet_channels
:
481 serv
.privmsg(auteur
,"Je me la ferme déjà sur %s"%(message
[1]))
482 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
484 self
.quiet_channels
.append(message
[1])
485 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
486 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
488 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
492 if auteur
in self
.ops
:
494 if message
[1] in self
.quiet_channels
:
495 self
.quiet_channels
.remove(message
[1])
496 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
497 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
499 serv
.privmsg(auteur
,"Je ne me la ferme pas sur %s."%(message
[1]))
500 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
504 if auteur
in self
.overops
and len(message
)>2:
505 serv
.privmsg(message
[1]," ".join(message
[2:]))
506 log(self
.serveur
,"priv",auteur
," ".join(message
))
507 elif len(message
)<=2:
508 serv
.privmsg(auteur
,"Syntaxe : SAY <channel> <message>")
512 if auteur
in self
.overops
and len(message
)>2:
513 serv
.action(message
[1]," ".join(message
[2:]))
514 log(self
.serveur
,"priv",auteur
," ".join(message
))
515 elif len(message
)<=2:
516 serv
.privmsg(auteur
,"Syntaxe : DO <channel> <action>")
520 if auteur
in self
.overops
and len(message
)>2:
521 serv
.kick(message
[1],message
[2]," ".join(message
[3:]))
522 log(self
.serveur
,"priv",auteur
," ".join(message
))
523 elif len(message
)<=2:
524 serv
.privmsg(auteur
,"Syntaxe : KICK <channel> <pseudo> [<raison>]")
528 if auteur
in self
.ops
and len(message
)>1:
529 serv
.privmsg(message
[1],"J'ai perdu !")
530 log(self
.serveur
,"priv",auteur
," ".join(message
))
531 elif len(message
)<=1:
532 serv
.privmsg(auteur
,"Syntaxe : LOST <channel>")
537 if self
.identities
.has_key(auteur
):
539 self
.nk
.write('search ["x",["pseudo"],%s]'%(json
.dumps(self
.identities
[auteur
])))
540 ret
=json
.loads(self
.nk
.read())
541 solde
=ret
["msg"][0]["solde"]
542 pseudo
=ret
["msg"][0]["pseudo"]
543 except Exception as exc
:
545 serv
.privmsg(auteur
,"failed")
546 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
548 serv
.privmsg(auteur
,"%s (%s)"%(float(solde
)/100,pseudo
.encode("utf8")))
549 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
551 serv
.privmsg(canal
,"Je ne connais pas ton pseudo note.")
552 elif auteur
in self
.ops
:
554 self
.nk
.write('search ["x",["pseudo"],%s]'%(json
.dumps(message
[1])))
555 ret
=json
.loads(self
.nk
.read())
556 solde
=ret
["msg"][0]["solde"]
557 pseudo
=ret
["msg"][0]["pseudo"]
558 except Exception as exc
:
559 serv
.privmsg(auteur
,"failed")
560 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
562 serv
.privmsg(auteur
,"%s (%s)"%(float(solde
)/100,pseudo
.encode("utf8")))
564 if auteur
in self
.overops
:
565 serv
.privmsg(auteur
," ".join(self
.ops
))
569 if auteur
in self
.overops
:
570 serv
.privmsg(auteur
," ".join(self
.overops
))
576 serv
.privmsg(auteur
,"Je n'ai pas compris. Essayez HELP…")
578 def on_pubmsg(self
, serv
, ev
):
579 auteur
= irclib
.nm_to_n(ev
.source())
581 message
= ev
.arguments()[0]
583 test
=bot_unicode(message
)
584 except UnicodeBotError
:
585 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
586 serv
.privmsg(canal
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
588 pour_moi
,message
=self
.pourmoi(serv
,message
)
589 if pour_moi
and message
.split()!=[]:
590 cmd
=message
.split()[0].lower()
592 args
=" ".join(message
.split()[1:])
595 if cmd
in ["meurs","die","crève"]:
596 if auteur
in self
.overops
:
597 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
600 serv
.privmsg(canal
,("%s: %s"%(auteur
,random
.choice(config
.quit_fail_messages
))).encode("utf8"))
601 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
602 elif cmd
== "reload":
603 if auteur
in self
.ops
:
604 log(self
.serveur
, canal
, auteur
, message
+"[successful]")
607 if auteur
in self
.overops
:
609 elif cmd
in ["part","leave","dégage","va-t-en","tut'tiresailleurs,c'estmesgalets"]:
610 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
611 or auteur
in self
.overops
):
613 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
614 if canal
in self
.chanlist
:
615 self
.chanlist
.remove(canal
)
617 serv
.privmsg(canal
,("%s: %s"%(auteur
,random
.choice(config
.leave_fail_messages
))).encode("utf8"))
618 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
620 elif cmd
in ["reconnect"]:
621 if auteur
in self
.ops
:
623 self
.nk
=self
.new_connection_NK(serv
,config
.note_pseudo
,
624 config
.note_password
,"special")[1]
625 except Exception as exc
:
627 log(self
.serveur
,"""Erreur dans on_pubmsg/"cmd in ["reconnect"]\n"""+str(exc
))
629 serv
.privmsg(canal
,"%s: done"%(auteur))
630 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
632 serv
.privmsg(canal
,"%s: failed"%(auteur))
633 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
634 for report
in self
.report_bugs_to
:
635 serv
.privmsg(report
,"Connection to NK2015 failed, invalid password ? Server dead ?")
637 serv
.privmsg(canal
,"%s: %s"%(auteur
,random
.choice(config
.pas_programme_pour_tobeir
).encode("utf8")))
638 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
640 elif cmd
in ["deviens","pseudo"]:
641 if auteur
in self
.ops
:
644 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
646 if cmd
in ["meur", "meurt","meurre","meurres"] and not canal
in self
.quiet_channels
:
647 serv
.privmsg(canal
,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
648 elif cmd
in ["ping"] and not canal
in self
.quiet_channels
:
649 serv
.privmsg(canal
,"%s: pong"%(auteur))
651 elif cmd
in ["solde","!solde"]:
652 if self
.identities
.has_key(auteur
):
653 pseudo
=self
.identities
[auteur
]
655 self
.nk
.write('search ["x",["pseudo"],%s]'%(json
.dumps(pseudo
)))
656 ret
=json
.loads(self
.nk
.read())
657 solde
=ret
["msg"][0]["solde"]
658 pseudo
=ret
["msg"][0]["pseudo"]
659 except Exception as exc
:
660 serv
.privmsg(canal
,"%s: failed"%(auteur))
661 log(self
.serveur
,canal
,auteur
,message
+"[failed]")
663 serv
.privmsg(canal
,"%s: %s (%s)"%(auteur
,float(solde
)/100,pseudo
.encode("utf8")))
664 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
666 serv
.privmsg(canal
,"%s: Je ne connais pas votre pseudo note."%(auteur))
667 log(self
.serveur
,canal
,auteur
,message
+"[unknown]")
668 elif (re
.match("!?(pain au chocolat|chocolatine)",message
.lower())
669 and not canal
in self
.quiet_channels
):
670 serv
.action(canal
,"sert un pain au chocolat à %s"%(auteur))
671 elif re
.match("!?manzana",message
.lower()) and not canal
in self
.quiet_channels
:
672 if auteur
in config
.manzana
:
673 serv
.action(canal
,"sert une bouteille de manzana à %s"%(auteur))
674 elif auteur
in config
.manzana_bis
:
675 serv
.action(canal
,"sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas."%(auteur))
677 serv
.action(canal
,"sert un verre de manzana à %s"%(auteur))
678 if is_insult(message
) and not canal
in self
.quiet_channels
:
679 if is_not_insult(message
):
680 answer
=random
.choice(config
.compliment_answers
)
681 for ligne
in answer
.split("\n"):
682 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
684 answer
=random
.choice(config
.insultes_answers
)
685 for ligne
in answer
.split("\n"):
686 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
687 elif is_compliment(message
) and not canal
in self
.quiet_channels
:
688 answer
=random
.choice(config
.compliment_answers
)
689 for ligne
in answer
.split("\n"):
690 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
691 gros_match
=is_gros(message
)
692 if gros_match
and not canal
in self
.quiet_channels
:
693 taille
=get_filesize()
694 answer
=u
"Mais non, je ne suis pas %s, %sKo tout au plus…"%(gros_match
.groups()[0],taille
)
695 serv
.privmsg(canal
,"%s: %s"%(auteur
,answer
.encode("utf8")))
696 if is_tesla(message
) and not canal
in self
.quiet_channels
:
697 l1
,l2
=config
.tesla_answers
,config
.tesla_actions
698 n1
,n2
=len(l1
),len(l2
)
699 i
=random
.randrange(n1
+n2
)
701 serv
.action(canal
,l2
[i
-n1
].encode("utf8"))
703 serv
.privmsg(canal
,"%s: %s"%(auteur
,l1
[i
].encode("utf8")))
704 if is_tamere(message
) and not canal
in self
.quiet_channels
:
705 answer
=random
.choice(config
.tamere_answers
)
706 for ligne
in answer
.split("\n"):
707 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
708 if is_tag(message
) and not canal
in self
.quiet_channels
:
709 if auteur
in self
.ops
:
710 action
=random
.choice(config
.tag_actions
)
711 serv
.action(canal
,action
.encode("utf8"))
712 self
.quiet_channels
.append(canal
)
714 answer
=random
.choice(config
.tag_answers
)
715 for ligne
in answer
.split("\n"):
716 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
717 if is_merci(message
):
718 answer
=random
.choice(config
.merci_answers
)
719 for ligne
in answer
.split("\n"):
720 serv
.privmsg(canal
,"%s: %s"%(auteur
,ligne
.encode("utf8")))
721 out
=re
.match(ur
"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$",
722 unicode(message
.upper(),"utf8"))
723 if re
.match("ma bite dans ton oreille",message
) and not canal
in self
.quiet_channels
:
724 serv
.privmsg(canal
,"%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !"%(auteur))
725 if out
and not canal
in self
.quiet_channels
:
729 serv
.privmsg(canal
,"%s: %s !"%(auteur
,out
+1))
731 serv
.privmsg(canal
,"%s: Ciel, un maxint ! Heureusement que je suis en python…"%(auteur))
733 if out
+1>1000 and random
.randrange(4)==0:
734 serv
.privmsg(canal
,"%s: Vous savez, moi et les chiffres…"%(auteur))
736 except Exception as exc
:
738 if re
.match("[A-Y]",out
):
739 alphabet
="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
740 serv
.privmsg(canal
,"%s: %s !"%(auteur
,alphabet
[alphabet
.index(out
)+1]))
742 serv
.privmsg(canal
,"%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?"%(auteur))
744 serv
.privmsg(canal
,"%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…"%(auteur))
745 elif re
.match(ur
"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+",out
):
747 return "".join([{u
"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i
]:u
"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i
]
748 for i
in range(20)}[j
]
750 out
=int(translate(out
))
751 serv
.privmsg(canal
,"%s: %s !"%(auteur
,translate(str(out
+1)).encode("utf8")))
752 if is_bonjour(message
) and not canal
in self
.quiet_channels
:
754 answer
=random
.choice(config
.night_answers
)
756 answer
=random
.choice(config
.bonjour_answers
)
758 answer
=random
.choice(config
.bonsoir_answers
)
759 serv
.privmsg(canal
,answer
.format(auteur
).encode("utf8"))
760 if is_bonne_nuit(message
) and not canal
in self
.quiet_channels
:
761 answer
=random
.choice(config
.bonne_nuit_answers
)
762 serv
.privmsg(canal
,answer
.format(auteur
).encode("utf8"))
763 if is_pan(message
) and not canal
in self
.quiet_channels
:
764 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))
766 if message
in ["!pain au chocolat","!chocolatine"] and not canal
in self
.quiet_channels
:
767 serv
.action(canal
,"sert un pain au chocolat à %s"%(auteur))
768 if message
in ["!manzana"] and not canal
in self
.quiet_channels
:
769 if auteur
in config
.manzana
:
770 serv
.action(canal
,"sert une bouteille de manzana à %s"%(auteur))
771 elif auteur
in config
.manzana_bis
:
772 serv
.action(canal
,"sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas."%(auteur))
774 serv
.action(canal
,"sert un verre de manzana à %s"%(auteur))
775 if re
.match(u
'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$',message
.decode("utf8")) and not canal
in self
.quiet_channels
:
776 failanswers
=config
.buffer_fail_answers
777 answer
=random
.choice(failanswers
)
778 serv
.privmsg(canal
,("%s: %s"%(auteur
,answer
)).encode("utf8"))
779 if not canal
in self
.quiet_channels
:
781 if re
.match((u
"^("+u
"|".join(config
.bonjour_triggers
)
782 +ur
")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
783 ).format(mypseudo
).lower(), message
.decode("utf8").strip().lower()):
784 answer
=random
.choice(config
.bonjour_answers
)
785 serv
.privmsg(canal
,answer
.format(auteur
).encode("utf8"))
786 if (is_perdu(message
) and not canal
in self
.quiet_channels
):
787 # proba de perdre sur trigger :
788 # avant 30min (enfin, config) : 0
789 # ensuite, +25%/30min, linéairement
790 deltat
=time
.time()-self
.last_perdu
791 barre
=(deltat
-config
.time_between_perdu
)/(2*3600.0)
792 if random
.uniform(0,1)<barre
:
793 serv
.privmsg(canal
,"%s: J'ai perdu !"%(auteur))
794 self
.last_perdu
=time
.time()
796 def on_action(self
, serv
, ev
):
797 action
= ev
.arguments()[0]
798 auteur
= irclib
.nm_to_n(ev
.source())
799 channel
= ev
.target()
801 test
=bot_unicode(action
)
802 except UnicodeBotError
:
803 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
804 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
808 if is_bad_action_trigger(action
,mypseudo
) and not channel
in self
.quiet_channels
:
809 l1
,l2
=config
.bad_action_answers
,config
.bad_action_actions
810 n1
,n2
=len(l1
),len(l2
)
811 i
=random
.randrange(n1
+n2
)
813 serv
.action(channel
,l2
[i
-n1
].format(auteur
).encode("utf8"))
815 serv
.privmsg(channel
,l1
[i
].format(auteur
).format(auteur
).encode("utf8"))
816 if is_good_action_trigger(action
,mypseudo
) and not channel
in self
.quiet_channels
:
817 l1
,l2
=config
.good_action_answers
,config
.good_action_actions
818 n1
,n2
=len(l1
),len(l2
)
819 i
=random
.randrange(n1
+n2
)
821 serv
.action(channel
,l2
[i
-n1
].format(auteur
).format(auteur
).encode("utf8"))
823 serv
.privmsg(channel
,l1
[i
].format(auteur
).format(auteur
).encode("utf8"))
825 def on_kick(self
,serv
,ev
):
826 auteur
= irclib
.nm_to_n(ev
.source())
827 channel
= ev
.target()
828 victime
= ev
.arguments()[0]
829 raison
= ev
.arguments()[1]
830 if victime
==self
.nick
:
831 log(self
.serveur
,"%s kické de %s par %s (raison : %s)" %(victime
,channel
,auteur
,raison
))
834 l1
,l2
=config
.kick_answers
,config
.kick_actions
835 n1
,n2
=len(l1
),len(l2
)
836 i
=random
.randrange(n1
+n2
)
838 serv
.action(channel
,l2
[i
-n1
].format(auteur
).encode("utf8"))
840 serv
.privmsg(channel
,l1
[i
].format(auteur
).encode("utf8"))
842 def quitter(self
,chan
,leave_message
=None):
843 if leave_message
==None:
844 leave_message
=random
.choice(config
.leave_messages
)
845 self
.serv
.part(chan
,message
=leave_message
.encode("utf8"))
848 quit_message
=random
.choice(config
.quit_messages
)
849 self
.die(msg
=quit_message
.encode("utf8"))
852 return self
.serv
.get_nickname()
853 nick
=property(_getnick
)
855 def reload(self
, auteur
=None):
857 if auteur
in [None, "SIGHUP"]:
858 towrite
= "Config reloaded" + " (SIGHUP received)"*(auteur
== "SIGHUP")
859 for to
in config
.report_bugs_to
:
860 self
.serv
.privmsg(to
, towrite
)
861 log(self
.serveur
, towrite
)
863 self
.serv
.privmsg(auteur
,"Config reloaded")
868 def start_as_daemon(self
, outfile
):
869 sys
.stderr
= Logger(outfile
)
873 class Logger(object):
874 """Pour écrire ailleurs que sur stdout"""
875 def __init__(self
, filename
="basile.full.log"):
876 self
.filename
= filename
878 def write(self
, message
):
879 f
= open(self
.filename
, "a")
885 print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
886 print " --outfile sans --no-output ni --daemon n'a aucun effet"
889 if "--daemon" in sys
.argv
:
890 thisfile
= os
.path
.realpath(__file__
)
891 thisdirectory
= thisfile
.rsplit("/", 1)[0]
892 os
.chdir(thisdirectory
)
896 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
900 if "--quiet" in sys
.argv
:
901 config
.debug_stdout
=False
902 serveurs
={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
903 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
904 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
905 outfile
= "/var/log/bots/basile.full.log"
908 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
910 sys
.stdout
= Logger(outfile
)
912 serveur
=serveurs
[serveur
]
914 print "Server Unknown : %s"%(serveur)
916 basile
=Basile(serveur
,debug
)
917 # Si on reçoit un SIGHUP, on reload la config
918 def sighup_handler(signum
, frame
):
919 basile
.reload("SIGHUP")
920 signal
.signal(signal
.SIGHUP
, sighup_handler
)
922 child_pid
= os
.fork()
925 basile
.start_as_daemon(outfile
)
927 # on enregistre le pid de basile
928 pidfile
= "/var/run/bots/basile.pid"
931 if arg
[0].strip('-') in ["pidfile"]:
933 f
= open(pidfile
, "w")
934 f
.write("%s\n" % child_pid
)
939 if __name__
== "__main__":