4 # Codé par 20-100 (commencé le 23/04/12)
6 """ Un bot IRC destiné à s'interfacer avec la Note Kfet 2015 """
17 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
18 sys
.path
.insert(0, "/home/vincent/scripts/python-myirclib")
22 from commands
import getstatusoutput
as ex
26 #: Module responsable du dialogue avec la NoteKfet2015
28 #: Module de réponse aux questions de base
31 # la partie qui réfère au fichier lui-même est mieux ici
32 # sinon on réfère la config et pas le fichier lui-même
34 config
.thisfile
= os
.path
.realpath(__file__
)
36 def get_config_logfile(serveur
):
37 """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
38 serveurs
= {"acoeur.crans.org" : "acoeur",
39 "irc.crans.org" : "crans"}
40 return config
.logfile_template
% (serveurs
[serveur
],)
43 """Récupère la taille de ce fichier."""
44 return ex("ls -s %s" % (config
.thisfile
))[1].split()[0]
46 def log(serveur
, channel
, auteur
=None, message
=None):
47 """Enregistre une ligne de log."""
48 if auteur
== message
== None:
49 # alors c'est que c'est pas un channel mais juste une ligne de log
50 chain
= u
"%s %s" % (time
.strftime("%F %T"), channel
)
52 chain
= u
"%s [%s:%s] %s" % (time
.strftime("%F %T"), channel
, auteur
, message
)
53 f
= open(get_config_logfile(serveur
), "a")
54 f
.write((chain
+ u
"\n").encode("utf-8"))
56 if config
.debug_stdout
:
57 print chain
.encode("utf-8")
59 def ignore_event(serv
, ev
):
60 """Retourne ``True`` si il faut ignorer cet évènement."""
61 for (blackmask
, exceptlist
) in config
.blacklisted_masks
:
62 usermask
= ev
.source()
63 blackit
= bool(irclib
.mask_matches(usermask
, blackmask
))
64 exceptit
= any([bool(irclib
.mask_matches(usermask
, exceptmask
)) for exceptmask
in exceptlist
])
65 if exceptit
: # Il est exempté
68 if blackit
: # Il n'est pas exempté et matche la blacklist
74 class UnicodeBotError(Exception):
75 """Erreur levée si quelqu'un fait du caca avec son encodage."""
78 class CrashError(Exception):
79 """Pour pouvoir faire crasher Basile, parce que ça a l'air drôle."""
80 def __init__(self
, msg
=""):
81 Exception.__init
__(self
, msg
)
83 def bot_unicode(chain
):
84 """Essaye de décoder ``chain`` en UTF-8.
85 Lève une py:class:`UnicodeBotError` en cas d'échec."""
87 return chain
.decode("utf8")
88 except UnicodeDecodeError as exc
:
92 class Basile(ircbot
.SingleServerIRCBot
):
93 """Classe principale : définition du bot Basile."""
94 def __init__(self
, serveur
, debug
=False):
95 temporary_pseudo
= config
.irc_pseudo
+ str(random
.randrange(10000,100000))
96 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
97 temporary_pseudo
, "Basile, le bot irc. [Codé par 20-100]", 10)
99 self
.serveur
= serveur
100 self
.overops
= config
.overops
101 self
.ops
= self
.overops
+ config
.ops
102 self
.report_bugs_to
= config
.report_bugs_to
103 self
.chanlist
= config
.chanlist
104 self
.identities
= json
.load(open(config
.identities_file
, "r"))
105 self
.stay_channels
= config
.stay_channels
106 self
.quiet_channels
= config
.quiet_channels
110 def new_connection_NK(self
, serv
, username
, password
, typ
="bdd"):
111 """Renvoie (``True``, <une socket ouverte et authentifiée sur la NoteKfet2015>)
112 ou bien (``False``, None)"""
114 login_result
, sock
= nk
.login(username
, password
, typ
)
115 info
, retcode
, errmsg
= login_result
["msg"], login_result
["retcode"], login_result
["errmsg"]
116 except nk
.NKRefused
as exc
:
117 for report
in self
.report_bugs_to
:
118 serv
.privmsg(report
, "Le Serveur NK2015 est down.")
119 return (False, None, None)
120 except nk
.NKHelloFailed
as exc
:
121 for report
in self
.report_bugs_to
:
123 "La version du protocole utilisée n'est pas supportée par le serveur NK2015.")
124 return (False, None, None)
125 except nk
.NKUnknownError
as exc
:
126 erreurs
= ["Une fucking erreur inconnue s'est produite"]
127 erreurs
+= str(exc
).split("\n")
128 for report
in self
.report_bugs_to
:
130 serv
.privmsg(report
, err
)
131 return (False, None, None)
132 except Exception as exc
:
133 # Exception qui ne vient pas de la communication avec le serveur NK2015
134 log(self
.serveur
, "Erreur dans new_connection_NK\n" + str(exc
))
135 return (False, None, None)
137 return (True, info
, sock
)
139 return (False, None, None)
143 """Récuère le nick effectif du bot sur le serveur."""
144 return self
.serv
.get_nickname()
145 nick
= property(_getnick
)
147 def give_me_my_pseudo(self
, serv
):
148 """Récupère le pseudo auprès de NickServ."""
149 serv
.privmsg("NickServ", "RECOVER %s %s" % (config
.irc_pseudo
, config
.irc_password
))
150 serv
.privmsg("NickServ", "RELEASE %s %s" % (config
.irc_pseudo
, config
.irc_password
))
152 serv
.nick(config
.irc_pseudo
)
154 def pourmoi(self
, serv
, message
):
155 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
157 pseudo
= pseudo
.decode("utf-8")
159 if message
[:size
] == pseudo
and len(message
) > size
and message
[size
] == ":":
160 return (True, message
[size
+1:].lstrip(" "))
162 return (False, message
)
164 ### Exécution d'actions
165 def lost(self
, serv
, channel
, forced
=False):
166 """Réaction à un trigger de perdu.
167 Annonce "J'ai perdu" sur le channel si on n'a pas perdu depuis un certain temps."""
168 if self
.last_perdu
+ config
.time_between_perdu
< time
.time() or forced
:
169 if not channel
in self
.quiet_channels
or forced
:
170 serv
.privmsg(channel
, "J'ai perdu !")
171 self
.last_perdu
=time
.time()
172 delay
=config
.time_between_perdu_trigger
173 delta
=config
.time_between_perdu_trigger_delta
174 serv
.execute_delayed(random
.randrange(delay
-delta
,delay
+delta
),self
.lost
,(serv
,channel
))
176 def quitter(self
, chan
, leave_message
=None):
177 """Quitter un channel avec un message customisable."""
178 if leave_message
== None:
179 leave_message
= random
.choice(config
.leave_messages
)
180 self
.serv
.part(chan
, message
=leave_message
.encode("utf8"))
183 """Se déconnecter du serveur IRC avec un message customisable."""
184 quit_message
= random
.choice(config
.quit_messages
)
185 self
.die(msg
=quit_message
.encode("utf8"))
187 def execute_reload(self
, auteur
=None):
188 """Recharge la config."""
190 isit
.regexp_compile()
191 if auteur
in [None, "SIGHUP"]:
192 towrite
= "Config reloaded" + " (SIGHUP received)" * (auteur
== "SIGHUP")
193 for to
in config
.report_bugs_to
:
194 self
.serv
.privmsg(to
, towrite
)
195 log(self
.serveur
, towrite
)
198 return True, u
"Config reloaded"
200 def crash(self
, who
="nobody", chan
="nowhere"):
201 """Fait crasher le bot."""
202 where
= "en privé" if chan
== "priv" else "sur le chan %s" % chan
203 raise CrashError((u
"Crash demandé par %s %s" % (who
, where
)).encode("utf-8"))
206 "reload" : execute_reload
,
209 def execute_something(self
, something
, params
, place
=None, auteur
=None):
210 """Exécute une action et répond son résultat à ``auteur``
211 sur un chan ou en privé en fonction de ``place``"""
212 action
= self
.ACTIONS
[something
]
213 success
, message
= action(self
, **params
)
215 if irclib
.is_channel(place
):
216 message
= "%s: %s" % (auteur
, message
.encode("utf-8"))
217 self
.serv
.privmsg(place
, message
)
218 log(self
.serveur
, place
, auteur
, something
+ "%r" % params
+ ("[successful]" if success
else "[failed]"))
220 ### Surcharge des events du Bot
221 def on_welcome(self
, serv
, ev
):
222 """À l'arrivée sur le serveur."""
223 self
.serv
= serv
# ça serv ira :)
224 self
.give_me_my_pseudo(serv
)
225 serv
.privmsg("NickServ", "IDENTIFY %s" % (config
.irc_password
))
226 log(self
.serveur
, "Connected")
228 self
.chanlist
= ["#bot"]
229 for c
in self
.chanlist
:
230 log(self
.serveur
, "JOIN %s" % (c
))
232 # on ouvre la connexion note de Basile, special user
233 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
, config
.note_password
, "special")[2]
235 for report
in self
.report_bugs_to
:
236 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ?")
238 def on_privmsg(self
, serv
, ev
):
239 """À la réception d'un message en privé."""
240 if ignore_event(serv
, ev
):
242 message
= ev
.arguments()[0]
243 auteur
= irclib
.nm_to_n(ev
.source())
245 message
= bot_unicode(message
)
246 except UnicodeBotError
:
247 if config
.utf8_trigger
:
248 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
250 message
= message
.split()
251 cmd
= message
[0].lower()
252 notunderstood
= False
254 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
256 helpmsg
= config
.helpmsg_default
258 helpmsg
+= config
.helpmsg_ops
260 helpmsg
+= config
.helpmsg_overops
262 helpmsgs
= config
.helpdico
.get(message
[1].lower(), ["Commande inconnue.", None, None])
263 helpmsg
= helpmsgs
[0]
264 if op
and helpmsgs
[1]:
266 helpmsg
+= "\n" + helpmsgs
[1]
268 helpmsg
= helpmsgs
[1]
269 if overop
and helpmsgs
[2]:
271 helpmsg
+= "\n" + helpmsgs
[2]
273 helpmsg
= helpmsgs
[2]
274 for ligne
in helpmsg
.split("\n"):
275 serv
.privmsg(auteur
, ligne
.encode("utf-8"))
276 elif cmd
== u
"identify":
277 if len(message
) == 1:
278 if self
.identities
.has_key(auteur
):
279 serv
.privmsg(auteur
, "Je vous connais sous le pseudo note %s." % (
280 self
.identities
[auteur
]["pseudo"].encode("utf8")))
282 serv
.privmsg(auteur
, "Je ne connais pas votre pseudo note.")
283 elif len(message
) >= 3:
284 username
, password
= message
[1], " ".join(message
[2:])
285 success
, info
, _
= self
.new_connection_NK(serv
, username
, password
)
287 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
288 serv
.privmsg(auteur
, "Identité enregistrée.")
289 self
.identities
[auteur
] = info
290 json
.dump(self
.identities
, open(config
.identities_file
,"w"))
292 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
293 serv
.privmsg(auteur
, "Mot de passe invalide. (ou serveur down)")
295 serv
.privmsg(auteur
, "Syntaxe : IDENTIFY [<username> <password>]")
298 if self
.identities
.has_key(auteur
):
299 password
= " ".join(message
[1:])
300 success
, _
, _
= self
.new_connection_NK(serv
, self
.identities
[auteur
], password
)
302 del self
.identities
[auteur
]
303 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
304 json
.dump(self
.identities
, open(config
.identities_file
, "w"))
305 serv
.privmsg(auteur
, "Identité oubliée.")
307 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
308 serv
.privmsg(auteur
, "Mot de passe invalide. (ou serveur down)")
310 serv
.privmsg(auteur
, "Je ne connais pas ton pseudo note.")
312 serv
.privmsg(auteur
, "Syntaxe : DROP <password>")
314 if auteur
in self
.ops
:
316 if message
[1] in self
.chanlist
:
317 serv
.privmsg(auteur
, "Je suis déjà sur %s" % (message
[1]))
319 serv
.join(message
[1])
320 self
.chanlist
.append(message
[1])
321 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
322 log(self
.serveur
, "priv", auteur
, " ".join(message
))
324 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
327 elif cmd
== u
"leave":
328 if auteur
in self
.ops
and len(message
) > 1:
329 if message
[1] in self
.chanlist
:
330 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
331 self
.quitter(message
[1].encode("utf-8"), " ".join(message
[2:]))
332 self
.chanlist
.remove(message
[1])
333 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
335 serv
.privmsg(auteur
, "Non, je reste !")
336 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
338 serv
.privmsg(auteur
, "Je ne suis pas sur %s" % (message
[1]))
342 if auteur
in self
.overops
:
344 if message
[1] in self
.stay_channels
:
345 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
346 serv
.privmsg(auteur
, "Je stay déjà sur %s." % (message
[1]))
348 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
349 self
.stay_channels
.append(message
[1])
350 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
352 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
355 elif cmd
== u
"nostay":
356 if auteur
in self
.overops
:
358 if message
[1] in self
.stay_channels
:
359 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
360 self
.stay_channels
.remove(message
[1])
361 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
363 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
364 serv
.privmsg(auteur
, "Je ne stay pas sur %s." % (message
[1]))
368 if auteur
in self
.overops
:
369 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
373 elif cmd
== u
"crash":
374 if auteur
in self
.overops
:
375 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
376 self
.crash(auteur
, "priv")
379 elif cmd
== u
"reload":
380 if auteur
in self
.ops
:
381 self
.execute_something("reload", {"auteur" : auteur
}, place
=auteur
, auteur
=auteur
)
384 elif cmd
== u
"reconnect":
385 if auteur
in self
.ops
:
387 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
,
388 config
.note_password
, "special")[2]
389 except Exception as exc
:
391 log(self
.serveur
, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc
))
393 serv
.privmsg(auteur
, "%s: done" % (auteur
))
394 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
396 serv
.privmsg(auteur
, "%s: failed" % (auteur
))
397 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
398 for report
in self
.report_bugs_to
:
399 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ? Server dead ?")
402 elif cmd
== u
"quiet":
403 if auteur
in self
.ops
:
405 if message
[1] in self
.quiet_channels
:
406 serv
.privmsg(auteur
, "Je me la ferme déjà sur %s" % (message
[1]))
407 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
409 self
.quiet_channels
.append(message
[1])
410 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
411 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
413 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
416 elif cmd
== u
"noquiet":
417 if auteur
in self
.ops
:
419 if message
[1] in self
.quiet_channels
:
420 self
.quiet_channels
.remove(message
[1])
421 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
422 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
424 serv
.privmsg(auteur
, "Je ne me la ferme pas sur %s." % (message
[1]))
425 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
429 if auteur
in self
.overops
and len(message
) > 2:
430 serv
.privmsg(message
[1].encode("utf-8"), (u
" ".join(message
[2:])).encode("utf-8"))
431 log(self
.serveur
, "priv", auteur
, " ".join(message
))
432 elif len(message
) <= 2:
433 serv
.privmsg(auteur
, "Syntaxe : SAY <channel> <message>")
437 if auteur
in self
.overops
and len(message
) > 2:
438 serv
.action(message
[1], " ".join(message
[2:]))
439 log(self
.serveur
, "priv", auteur
, " ".join(message
))
440 elif len(message
) <= 2:
441 serv
.privmsg(auteur
, "Syntaxe : DO <channel> <action>")
445 if auteur
in self
.overops
and len(message
) > 2:
446 serv
.kick(message
[1].encode("utf-8"), message
[2].encode("utf-8"), " ".join(message
[3:]).encode("utf-8"))
447 log(self
.serveur
, "priv", auteur
, " ".join(message
))
448 elif len(message
) <= 2:
449 serv
.privmsg(auteur
, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
453 if auteur
in self
.ops
and len(message
) > 1:
454 serv
.privmsg(message
[1], "J'ai perdu !")
455 log(self
.serveur
, "priv", auteur
, " ".join(message
))
456 elif len(message
) <= 1:
457 serv
.privmsg(auteur
, "Syntaxe : LOST <channel>")
460 elif cmd
== u
"solde":
461 if len(message
) == 1:
462 if self
.identities
.has_key(auteur
):
463 success
, solde
, pseudo
= nk
.get_solde(self
.nk
, self
.identities
[auteur
]["idbde"], serv
, auteur
)
465 serv
.privmsg(auteur
, "%.2f (%s)" % (solde
/100.0, pseudo
.encode("utf8")))
466 log(self
.serveur
, "priv", auteur
, " ".join(message
) + ("[successful]" if success
else "[failed]"))
468 serv
.privmsg(canal
, "Je ne connais pas ton pseudo note.")
470 if auteur
in self
.overops
:
471 serv
.privmsg(auteur
, " ".join(self
.ops
))
474 elif cmd
== u
"overops":
475 if auteur
in self
.overops
:
476 serv
.privmsg(auteur
, " ".join(self
.overops
))
482 serv
.privmsg(auteur
, "Je n'ai pas compris. Essayez HELP…")
484 def on_pubmsg(self
, serv
, ev
):
485 """À la réception d'un message sur un channel."""
486 if ignore_event(serv
, ev
):
488 auteur
= irclib
.nm_to_n(ev
.source())
490 message
= ev
.arguments()[0]
492 message
= bot_unicode(message
)
493 except UnicodeBotError
:
494 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
495 serv
.privmsg(canal
, (u
"%s: %s"% ( auteur
, random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
497 pour_moi
, message
= self
.pourmoi(serv
, message
)
498 if pour_moi
and message
.split()!=[]:
499 cmd
= message
.split()[0].lower()
501 args
= " ".join(message
.split()[1:])
504 if cmd
in [u
"meurs", u
"die", u
"crève"]:
505 if auteur
in self
.overops
:
506 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
509 serv
.privmsg(canal
,(u
"%s: %s"%(auteur
, random
.choice(config
.quit_fail_messages
))).encode("utf8"))
510 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
511 elif cmd
== u
"reload":
512 if auteur
in self
.ops
:
513 self
.execute_something("reload", {"auteur" : auteur
}, place
=canal
, auteur
=auteur
)
514 elif cmd
== u
"crash":
515 if auteur
in self
.overops
:
516 self
.crash(auteur
, canal
)
517 elif cmd
in [u
"part", u
"leave", u
"dégage", u
"va-t-en", u
"tut'tiresailleurs,c'estmesgalets"]:
518 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
519 or auteur
in self
.overops
):
521 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
522 if canal
in self
.chanlist
:
523 self
.chanlist
.remove(canal
)
525 serv
.privmsg(canal
,(u
"%s: %s" % (auteur
, random
.choice(config
.leave_fail_messages
))).encode("utf8"))
526 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
528 elif cmd
== u
"reconnect":
529 if auteur
in self
.ops
:
531 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
,
532 config
.note_password
, "special")[2]
533 except Exception as exc
:
535 log(self
.serveur
, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc
))
537 serv
.privmsg(canal
, "%s: done" % (auteur
))
538 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
540 serv
.privmsg(canal
, "%s: failed" % (auteur
))
541 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
542 for report
in self
.report_bugs_to
:
543 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ? Server dead ?")
545 serv
.privmsg(canal
, "%s: %s" % (auteur
, random
.choice(config
.pas_programme_pour_tobeir
).encode("utf8")))
546 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
548 elif cmd
in [u
"deviens", u
"pseudo"]:
549 if auteur
in self
.ops
:
552 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
554 if cmd
in [u
"meur", u
"meurt", u
"meurre", u
"meurres"] and not canal
in self
.quiet_channels
:
555 serv
.privmsg(canal
, '%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)' % (auteur
))
556 elif cmd
in [u
"ping"] and not canal
in self
.quiet_channels
:
557 serv
.privmsg(canal
, "%s: pong" % (auteur
))
559 elif cmd
in [u
"solde", u
"!solde", u
"!coca"] or cmd
.startswith("!"):
560 if self
.identities
.has_key(auteur
):
561 idbde
= self
.identities
[auteur
]["idbde"]
562 if cmd
in [u
"solde", u
"!solde"]:
563 success
, solde
, pseudo
= nk
.get_solde(self
.nk
, self
.identities
[auteur
]["idbde"], serv
, canal
)
565 serv
.privmsg(canal
, "%s: %s (%s)" % (auteur
, float(solde
)/100, pseudo
.encode("utf8")))
566 elif cmd
in [u
"!coca"] or cmd
.startswith("!"):
567 success
= nk
.consomme(self
.nk
, self
.identities
[auteur
]["idbde"], message
[1:], serv
, canal
)
568 log(self
.serveur
, canal
, auteur
, message
+ ("[successful]" if success
else "[failed]"))
570 serv
.privmsg(canal
, "%s: Je ne connais pas votre pseudo note." % (auteur
))
571 log(self
.serveur
, canal
, auteur
, message
+ "[unknown]")
572 elif (re
.match("(pain au chocolat|chocolatine)", message
.lower())
573 and not canal
in self
.quiet_channels
):
574 serv
.action(canal
, "sert un pain au chocolat à %s" % (auteur
))
575 elif re
.match("manzana",message
.lower()) and not canal
in self
.quiet_channels
:
576 if auteur
in config
.manzana
:
577 serv
.action(canal
, "sert une bouteille de manzana à %s" % (auteur
))
578 elif auteur
in config
.manzana_bis
:
579 serv
.action(canal
, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur
))
581 serv
.action(canal
, "sert un verre de manzana à %s" % (auteur
))
582 if isit
.is_insult(message
) and not canal
in self
.quiet_channels
:
583 if isit
.is_not_insult(message
):
584 answer
= random
.choice(config
.compliment_answers
)
585 for ligne
in answer
.split("\n"):
586 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
588 answer
= random
.choice(config
.insultes_answers
)
589 for ligne
in answer
.split("\n"):
590 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
591 elif isit
.is_compliment(message
) and not canal
in self
.quiet_channels
:
592 answer
= random
.choice(config
.compliment_answers
)
593 for ligne
in answer
.split("\n"):
594 serv
.privmsg(canal
, "%s: %s" % (auteur
,ligne
.encode("utf8")))
595 gros_match
= isit
.is_gros(message
)
596 if gros_match
and not canal
in self
.quiet_channels
:
597 taille
= get_filesize()
598 answer
= u
"Mais non, je ne suis pas %s, %sKo tout au plus…" % (gros_match
.groups()[0], taille
)
599 serv
.privmsg(canal
, "%s: %s"%(auteur
, answer
.encode("utf8")))
600 if isit
.is_tesla(message
) and not canal
in self
.quiet_channels
:
601 l1
, l2
= config
.tesla_answers
, config
.tesla_actions
602 n1
, n2
= len(l1
), len(l2
)
603 i
= random
.randrange(n1
+ n2
)
605 serv
.action(canal
, l2
[i
- n1
].encode("utf8"))
607 serv
.privmsg(canal
, "%s: %s" % (auteur
, l1
[i
].encode("utf8")))
608 if isit
.is_tamere(message
) and not canal
in self
.quiet_channels
:
609 answer
= random
.choice(config
.tamere_answers
)
610 for ligne
in answer
.split("\n"):
611 serv
.privmsg(canal
, "%s: %s"%(auteur
, ligne
.encode("utf8")))
612 if isit
.is_tag(message
) and not canal
in self
.quiet_channels
:
613 if auteur
in self
.ops
:
614 action
= random
.choice(config
.tag_actions
)
615 serv
.action(canal
, action
.encode("utf8"))
616 self
.quiet_channels
.append(canal
)
618 answer
= random
.choice(config
.tag_answers
)
619 for ligne
in answer
.split("\n"):
620 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
621 if isit
.is_merci(message
):
622 answer
= random
.choice(config
.merci_answers
)
623 for ligne
in answer
.split("\n"):
624 serv
.privmsg(canal
, "%s: %s"%(auteur
, ligne
.encode("utf8")))
625 out
= re
.match(ur
"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$", message
.upper())
626 if re
.match("ma bite dans ton oreille", message
) and not canal
in self
.quiet_channels
:
627 serv
.privmsg(canal
, "%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !" % (auteur
))
628 if out
and not canal
in self
.quiet_channels
:
629 out
= out
.groups()[0]
632 serv
.privmsg(canal
, "%s: %s !" % (auteur
, iout
+ 1))
633 if iout
== 2147483647:
634 serv
.privmsg(canal
, "%s: Ciel, un maxint ! Heureusement que je suis en python…" % (auteur
))
636 if iout
+ 1 > 1000 and random
.randrange(4) == 0:
637 serv
.privmsg(canal
, "%s: Vous savez, moi et les chiffres…" % (auteur
))
639 except Exception as exc
:
641 if re
.match("[A-Y]", out
):
642 alphabet
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
643 serv
.privmsg(canal
, "%s: %s !"%(auteur
, alphabet
[alphabet
.index(out
) + 1]))
645 serv
.privmsg(canal
, "%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?" % (auteur
))
647 serv
.privmsg(canal
, "%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…" % (auteur
))
648 elif re
.match(ur
"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+", out
):
650 return "".join([{u
"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i
]:u
"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i
]
651 for i
in range(20)}[j
]
653 out
= int(translate(out
))
654 serv
.privmsg(canal
,"%s: %s !" % (auteur
, translate(str(out
+ 1)).encode("utf8")))
655 if isit
.is_bonjour(message
) and not canal
in self
.quiet_channels
:
657 answer
= random
.choice(config
.night_answers
)
659 answer
= random
.choice(config
.bonjour_answers
)
661 answer
= random
.choice(config
.bonsoir_answers
)
662 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
663 if isit
.is_bonne_nuit(message
) and not canal
in self
.quiet_channels
:
664 answer
= random
.choice(config
.bonne_nuit_answers
)
665 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
666 if isit
.is_pan(message
) and not canal
in self
.quiet_channels
:
667 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
))
669 if message
in [u
"!pain au chocolat", u
"!chocolatine"] and not canal
in self
.quiet_channels
:
670 serv
.action(canal
, "sert un pain au chocolat à %s" % (auteur
))
671 if message
in [u
"!manzana"] 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 re
.match(u
'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$', message
) and not canal
in self
.quiet_channels
:
679 failanswers
= config
.buffer_fail_answers
680 answer
= random
.choice(failanswers
)
681 serv
.privmsg(canal
, ("%s: %s"%(auteur
,answer
)).encode("utf8"))
682 if not canal
in self
.quiet_channels
:
684 if re
.match((u
"^(" + u
"|".join(config
.bonjour_triggers
)
685 + ur
")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
686 ).format(mypseudo
).lower(), message
.strip().lower()):
687 answer
= random
.choice(config
.bonjour_answers
)
688 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
689 if (isit
.is_perdu(message
) and not canal
in self
.quiet_channels
):
690 # proba de perdre sur trigger :
691 # avant 30min (enfin, config) : 0
692 # ensuite, +25%/30min, linéairement
693 deltat
= time
.time() - self
.last_perdu
694 barre
= (deltat
- config
.time_between_perdu
)/(2*3600.0)
695 if random
.uniform(0, 1) < barre
:
696 serv
.privmsg(canal
, "%s: J'ai perdu !" % (auteur
))
697 self
.last_perdu
= time
.time()
699 def on_action(self
, serv
, ev
):
700 """À la réception d'une action."""
701 if ignore_event(serv
, ev
):
703 action
= ev
.arguments()[0]
704 auteur
= irclib
.nm_to_n(ev
.source())
705 channel
= ev
.target()
707 action
= bot_unicode(action
)
708 except UnicodeBotError
:
709 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
710 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
714 if isit
.is_bad_action_trigger(action
, mypseudo
) and not channel
in self
.quiet_channels
:
715 l1
, l2
= config
.bad_action_answers
, config
.bad_action_actions
716 n1
, n2
= len(l1
), len(l2
)
717 i
= random
.randrange(n1
+ n2
)
719 serv
.action(channel
, l2
[i
- n1
].format(auteur
).encode("utf8"))
721 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
722 if isit
.is_good_action_trigger(action
, mypseudo
) and not channel
in self
.quiet_channels
:
723 l1
, l2
= config
.good_action_answers
, config
.good_action_actions
724 n1
, n2
= len(l1
), len(l2
)
725 i
= random
.randrange(n1
+ n2
)
727 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
729 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
731 def on_kick(self
, serv
, ev
):
732 """À la réception d'une action."""
733 auteur
= irclib
.nm_to_n(ev
.source())
734 channel
= ev
.target()
735 victime
= ev
.arguments()[0]
736 raison
= ev
.arguments()[1]
737 if victime
== self
.nick
:
738 log(self
.serveur
, u
"%s kické de %s par %s (raison : %s)" % (victime
, channel
.decode("utf-8"), auteur
, raison
))
741 l1
, l2
= config
.kick_answers
, config
.kick_actions
742 n1
, n2
= len(l1
), len(l2
)
743 i
= random
.randrange(n1
+ n2
)
745 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
747 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
750 def start_as_daemon(self
, outfile
):
751 sys
.stderr
= Logger(outfile
)
755 class Logger(object):
756 """Pour écrire ailleurs que sur stdout"""
757 def __init__(self
, filename
="basile.full.log"):
758 self
.filename
= filename
760 def write(self
, message
):
761 f
= open(self
.filename
, "a")
766 """Exécution principal : lecture des paramètres et lancement du bot."""
767 if len(sys
.argv
) == 1:
768 print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
769 print " --outfile sans --no-output ni --daemon n'a aucun effet"
771 serveur
= sys
.argv
[1]
772 if "--daemon" in sys
.argv
:
773 thisfile
= os
.path
.realpath(__file__
)
774 thisdirectory
= thisfile
.rsplit("/", 1)[0]
775 os
.chdir(thisdirectory
)
779 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
783 if "--quiet" in sys
.argv
:
784 config
.debug_stdout
= False
785 serveurs
= {"a♡" : "acoeur.crans.org",
786 "acoeur" : "acoeur.crans.org",
787 "acoeur.crans.org" : "acoeur.crans.org",
788 "irc" : "irc.crans.org",
789 "crans" : "irc.crans.org",
790 "irc.crans.org" : "irc.crans.org"}
791 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
792 outfile
= "/var/log/bots/basile.full.log"
795 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
797 sys
.stdout
= Logger(outfile
)
799 serveur
= serveurs
[serveur
]
801 print "Server Unknown : %s" % (serveur
)
803 basile
= Basile(serveur
,debug
)
804 # Si on reçoit un SIGHUP, on reload la config
805 def sighup_handler(signum
, frame
):
806 basile
.execute_reload(auteur
="SIGHUP")
807 signal
.signal(signal
.SIGHUP
, sighup_handler
)
810 child_pid
= os
.fork()
813 basile
.start_as_daemon(outfile
)
815 # on enregistre le pid de basile
816 pidfile
= "/var/run/bots/basile.pid"
819 if arg
[0].strip('-') in ["pidfile"]:
821 f
= open(pidfile
, "w")
822 f
.write("%s\n" % child_pid
)
827 if __name__
== "__main__":