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
29 # la partie qui réfère au fichier lui-même est mieux ici
30 # sinon on réfère la config et pas le fichier lui-même
32 config
.thisfile
= os
.path
.realpath(__file__
)
34 def get_config_logfile(serveur
):
35 """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
36 serveurs
= {"acoeur.crans.org" : "acoeur",
37 "irc.crans.org" : "crans"}
38 return config
.logfile_template
% (serveurs
[serveur
],)
41 """Récupère la taille de ce fichier."""
42 return ex("ls -s %s" % (config
.thisfile
))[1].split()[0]
44 def log(serveur
, channel
, auteur
=None, message
=None):
45 """Enregistre une ligne de log."""
46 if auteur
== message
== None:
47 # alors c'est que c'est pas un channel mais juste une ligne de log
48 chain
= u
"%s %s" % (time
.strftime("%F %T"), channel
)
50 chain
= u
"%s [%s:%s] %s" % (time
.strftime("%F %T"), channel
, auteur
, message
)
51 f
= open(get_config_logfile(serveur
), "a")
52 f
.write((chain
+ u
"\n").encode("utf-8"))
54 if config
.debug_stdout
:
55 print chain
.encode("utf-8")
57 def ignore_event(serv
, ev
):
58 """Retourne ``True`` si il faut ignorer cet évènement."""
59 for (blackmask
, exceptlist
) in config
.blacklisted_masks
:
60 usermask
= ev
.source()
61 blackit
= bool(irclib
.mask_matches(usermask
, blackmask
))
62 exceptit
= any([bool(irclib
.mask_matches(usermask
, exceptmask
)) for exceptmask
in exceptlist
])
63 if exceptit
: # Il est exempté
66 if blackit
: # Il n'est pas exempté et matche la blacklist
69 def regex_join(liste
, avant
=u
".*(?:^| )", apres
=u
"(?:$|\.| |,|;).*"):
70 """Fabrique une regexp à partir d'une liste d'éléments à matcher."""
71 return avant
+ u
"(" + u
"|".join(liste
) + u
")" + apres
73 def is_something(chain
, regexp
=None, matches
=[], avant
=u
".*(?:^| )", apres
=u
"(?:$|\.| |,|;).*",
74 case_sensitive
=False):
75 """Vérifie si chain contient un des éléments de ``matches``.
76 Si ``regexp`` est fournie, c'est simplement elle qui est testée"""
77 if not case_sensitive
:
82 regexp
= regex_join(matches
, avant
, apres
)
83 regexp
= re
.compile(regexp
)
84 o
= regexp
.match(chain
)
88 """Compilation des regexp à partir de la conf.
89 Place les résultats dans le namespace de ``config``"""
90 config
.insult_regexp
= regex_join(config
.insultes
, avant
=u
".*(?:^| |')")
91 config
.insult_regexp_compiled
= re
.compile(config
.insult_regexp
)
93 config
.not_insult_regexp
= u
".*pas %s%s" % (config
.amplifier_regexp
, config
.insult_regexp
)
94 config
.not_insult_regexp_compiled
= re
.compile(config
.not_insult_regexp
)
96 config
.compliment_regexp
= regex_join(config
.compliment_triggers
, avant
=u
".*(?:^| |')")
97 config
.compliment_regexp_compiled
= re
.compile(config
.compliment_regexp
)
99 config
.perdu_regexp
= regex_join(config
.perdu
)
100 config
.perdu_regexp_compiled
= re
.compile(config
.perdu_regexp
)
102 config
.tag_regexp
= regex_join(config
.tag_triggers
)
103 config
.tag_regexp_compiled
= re
.compile(config
.tag_regexp
)
105 config
.gros_regexp
= regex_join(config
.gros
)
106 config
.gros_regexp_compiled
= re
.compile(config
.gros_regexp
)
108 config
.tesla_regexp
= regex_join(config
.tesla_triggers
, avant
=u
"^", apres
="$")
109 config
.tesla_regexp_compiled
= re
.compile(config
.tesla_regexp
)
111 config
.merci_regexp
= regex_join(config
.merci_triggers
)
112 config
.merci_regexp_compiled
= re
.compile(config
.merci_regexp
)
114 config
.tamere_regexp
= regex_join(config
.tamere_triggers
)
115 config
.tamere_regexp_compiled
= re
.compile(config
.tamere_regexp
)
117 config
.bonjour_regexp
= regex_join(config
.bonjour_triggers
, avant
=u
"^")
118 config
.bonjour_regexp_compiled
= re
.compile(config
.bonjour_regexp
)
120 config
.bonne_nuit_regexp
= regex_join(config
.bonne_nuit_triggers
, avant
=u
"^")
121 config
.bonne_nuit_regexp_compiled
= re
.compile(config
.bonne_nuit_regexp
)
123 config
.pan_regexp
= regex_join(config
.pan_triggers
, avant
=".*", apres
=".*")
124 config
.pan_regexp_compiled
= re
.compile(config
.pan_regexp
)
128 def is_insult(chain
, debug
=True):
129 """Vérifie si ``chain`` contient une insulte."""
130 return is_something(chain
, config
.insult_regexp_compiled
)
131 def is_not_insult(chain
):
132 """Vérifie si ``chain`` contient une insulte à la forme négative."""
133 return is_something(chain
, config
.not_insult_regexp_compiled
)
134 def is_compliment(chain
, debug
=True):
135 """Vérifie si ``chain`` contient un compliment."""
136 return is_something(chain
, config
.compliment_regexp_compiled
)
138 """Vérifie si ``chain`` contient une raison de perdre."""
139 return is_something(chain
, config
.perdu_regexp_compiled
)
141 """Vérifie si ``chain`` demande de fermer sa gueule."""
142 return is_something(chain
, config
.tag_regexp_compiled
)
144 """Vérifie si ``chain`` traite de gros."""
145 return is_something(chain
, config
.gros_regexp_compiled
)
147 """Vérifie si ``chain`` est un ping."""
148 return is_something(chain
, config
.tesla_regexp_compiled
)
150 """Vérifie si ``chain`` contient un remerciement."""
151 return is_something(chain
, config
.merci_regexp_compiled
)
152 def is_tamere(chain
):
153 """Vérifie si ``chain`` traite ma mère."""
154 return is_something(chain
, config
.tamere_regexp_compiled
)
155 def is_bad_action_trigger(chain
,pseudo
):
156 """Vérifie si ``chain`` est une action méchante.
157 On a besoin d'une regexp dynamique à cause du pseudo qui peut changer."""
158 return is_something(chain
, matches
=config
.bad_action_triggers
, avant
=u
"^",
159 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*" % (pseudo
))
160 def is_good_action_trigger(chain
,pseudo
):
161 """Vérifie si ``chain`` est une action gentille.
162 On a besoin d'une regexp dynamique à cause du pseudo qui peut changer."""
163 return is_something(chain
, matches
=config
.good_action_triggers
, avant
=u
"^",
164 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*" % (pseudo
))
165 def is_bonjour(chain
):
166 """Vérifie si ``chain`` contient un bonjour."""
167 return is_something(chain
, config
.bonjour_regexp_compiled
)
168 def is_bonne_nuit(chain
):
169 """Vérifie si ``chain`` contient un bonne nuit."""
170 return is_something(chain
, config
.bonne_nuit_regexp_compiled
)
172 """Vérifie si ``chain`` contient un pan."""
173 return is_something(chain
, config
.pan_regexp_compiled
)
176 """Vérifie si l'heure actuelle est entre les deux heures ``conf[0]`` et ``conf[1]``"""
177 _
, _
, _
, h
, m
, s
, _
, _
, _
= time
.localtime()
178 return (conf
[0], 0, 0) < (h
, m
, s
) < (conf
[1], 0, 0)
180 """Vérifie si on est le jour."""
181 return is_time(config
.daytime
)
183 """Vérifie si on est la nuit."""
184 return is_time(config
.nighttime
)
187 class UnicodeBotError(Exception):
188 """Erreur levée si quelqu'un fait du caca avec son encodage."""
191 class CrashError(Exception):
192 """Pour pouvoir faire crasher Basile, parce que ça a l'air drôle."""
193 def __init__(self
, msg
=""):
194 Exception.__init
__(self
, msg
)
196 def bot_unicode(chain
):
197 """Essaye de décoder ``chain`` en UTF-8.
198 Lève une py:class:`UnicodeBotError` en cas d'échec."""
200 return chain
.decode("utf8")
201 except UnicodeDecodeError as exc
:
202 raise UnicodeBotError
205 class Basile(ircbot
.SingleServerIRCBot
):
206 """Classe principale : définition du bot Basile."""
207 def __init__(self
, serveur
, debug
=False):
208 temporary_pseudo
= config
.irc_pseudo
+ str(random
.randrange(10000,100000))
209 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
210 temporary_pseudo
, "Basile, le bot irc. [Codé par 20-100]", 10)
212 self
.serveur
= serveur
213 self
.overops
= config
.overops
214 self
.ops
= self
.overops
+ config
.ops
215 self
.report_bugs_to
= config
.report_bugs_to
216 self
.chanlist
= config
.chanlist
217 self
.identities
= json
.load(open(config
.identities_file
, "r"))
218 self
.stay_channels
= config
.stay_channels
219 self
.quiet_channels
= config
.quiet_channels
223 def new_connection_NK(self
, serv
, username
, password
, typ
="bdd"):
224 """Renvoie (``True``, <une socket ouverte et authentifiée sur la NoteKfet2015>)
225 ou bien (``False``, None)"""
227 login_result
, sock
= nk
.login(username
, password
, typ
)
228 info
, retcode
, errmsg
= login_result
["msg"], login_result
["retcode"], login_result
["errmsg"]
229 except nk
.NKRefused
as exc
:
230 for report
in self
.report_bugs_to
:
231 serv
.privmsg(report
, "Le Serveur NK2015 est down.")
232 return (False, None, None)
233 except nk
.NKHelloFailed
as exc
:
234 for report
in self
.report_bugs_to
:
236 "La version du protocole utilisée n'est pas supportée par le serveur NK2015.")
237 return (False, None, None)
238 except nk
.NKUnknownError
as exc
:
239 erreurs
= ["Une fucking erreur inconnue s'est produite"]
240 erreurs
+= str(exc
).split("\n")
241 for report
in self
.report_bugs_to
:
243 serv
.privmsg(report
, err
)
244 return (False, None, None)
245 except Exception as exc
:
246 # Exception qui ne vient pas de la communication avec le serveur NK2015
247 log(self
.serveur
, "Erreur dans new_connection_NK\n" + str(exc
))
248 return (False, None, None)
250 return (True, info
, sock
)
252 return (False, None, None)
256 """Récuère le nick effectif du bot sur le serveur."""
257 return self
.serv
.get_nickname()
258 nick
= property(_getnick
)
260 def give_me_my_pseudo(self
, serv
):
261 """Récupère le pseudo auprès de NickServ."""
262 serv
.privmsg("NickServ", "RECOVER %s %s" % (config
.irc_pseudo
, config
.irc_password
))
263 serv
.privmsg("NickServ", "RELEASE %s %s" % (config
.irc_pseudo
, config
.irc_password
))
265 serv
.nick(config
.irc_pseudo
)
267 def pourmoi(self
, serv
, message
):
268 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
270 pseudo
= pseudo
.decode("utf-8")
272 if message
[:size
] == pseudo
and len(message
) > size
and message
[size
] == ":":
273 return (True, message
[size
+1:].lstrip(" "))
275 return (False, message
)
277 ### Exécution d'actions
278 def lost(self
, serv
, channel
, forced
=False):
279 """Réaction à un trigger de perdu.
280 Annonce "J'ai perdu" sur le channel si on n'a pas perdu depuis un certain temps."""
281 if self
.last_perdu
+ config
.time_between_perdu
< time
.time() or forced
:
282 if not channel
in self
.quiet_channels
or forced
:
283 serv
.privmsg(channel
, "J'ai perdu !")
284 self
.last_perdu
=time
.time()
285 delay
=config
.time_between_perdu_trigger
286 delta
=config
.time_between_perdu_trigger_delta
287 serv
.execute_delayed(random
.randrange(delay
-delta
,delay
+delta
),self
.lost
,(serv
,channel
))
289 def quitter(self
, chan
, leave_message
=None):
290 """Quitter un channel avec un message customisable."""
291 if leave_message
== None:
292 leave_message
= random
.choice(config
.leave_messages
)
293 self
.serv
.part(chan
, message
=leave_message
.encode("utf8"))
296 """Se déconnecter du serveur IRC avec un message customisable."""
297 quit_message
= random
.choice(config
.quit_messages
)
298 self
.die(msg
=quit_message
.encode("utf8"))
300 def execute_reload(self
, auteur
=None):
301 """Recharge la config."""
304 if auteur
in [None, "SIGHUP"]:
305 towrite
= "Config reloaded" + " (SIGHUP received)" * (auteur
== "SIGHUP")
306 for to
in config
.report_bugs_to
:
307 self
.serv
.privmsg(to
, towrite
)
308 log(self
.serveur
, towrite
)
311 return True, u
"Config reloaded"
313 def crash(self
, chan
="nowhere", who
="nobody"):
314 """Fait crasher le bot."""
315 where
= "en privé" if chan
== "priv" else "sur le chan %s" % chan
316 raise CrashError((u
"Crash demandé par %s %s" % (who
, where
)).encode("utf-8"))
319 "reload" : execute_reload
,
322 def execute_something(self
, something
, params
, place
=None, auteur
=None):
323 """Exécute une action et répond son résultat à ``auteur``
324 sur un chan ou en privé en fonction de ``place``"""
325 action
= self
.ACTIONS
[something
]
326 success
, message
= action(self
, **params
)
328 if irclib
.is_channel(place
):
329 message
= "%s: %s" % (auteur
, message
.encode("utf-8"))
330 self
.serv
.privmsg(place
, message
)
331 log(self
.serveur
, place
, auteur
, something
+ "%r" % params
+ ("[successful]" if success
else "[failed]"))
333 ### Surcharge des events du Bot
334 def on_welcome(self
, serv
, ev
):
335 """À l'arrivée sur le serveur."""
336 self
.serv
= serv
# ça serv ira :)
337 self
.give_me_my_pseudo(serv
)
338 serv
.privmsg("NickServ", "IDENTIFY %s" % (config
.irc_password
))
339 log(self
.serveur
, "Connected")
341 self
.chanlist
= ["#bot"]
342 for c
in self
.chanlist
:
343 log(self
.serveur
, "JOIN %s" % (c
))
345 # on ouvre la connexion note de Basile, special user
346 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
, config
.note_password
, "special")[2]
348 for report
in self
.report_bugs_to
:
349 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ?")
351 def on_privmsg(self
, serv
, ev
):
352 """À la réception d'un message en privé."""
353 if ignore_event(serv
, ev
):
355 message
= ev
.arguments()[0]
356 auteur
= irclib
.nm_to_n(ev
.source())
358 message
= bot_unicode(message
)
359 except UnicodeBotError
:
360 if config
.utf8_trigger
:
361 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
363 message
= message
.split()
364 cmd
= message
[0].lower()
365 notunderstood
= False
367 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
369 helpmsg
= config
.helpmsg_default
371 helpmsg
+= config
.helpmsg_ops
373 helpmsg
+= config
.helpmsg_overops
375 helpmsgs
= config
.helpdico
.get(message
[1].lower(), ["Commande inconnue.", None, None])
376 helpmsg
= helpmsgs
[0]
377 if op
and helpmsgs
[1]:
379 helpmsg
+= "\n" + helpmsgs
[1]
381 helpmsg
= helpmsgs
[1]
382 if overop
and helpmsgs
[2]:
384 helpmsg
+= "\n" + helpmsgs
[2]
386 helpmsg
= helpmsgs
[2]
387 for ligne
in helpmsg
.split("\n"):
388 serv
.privmsg(auteur
, ligne
.encode("utf-8"))
389 elif cmd
== u
"identify":
390 if len(message
) == 1:
391 if self
.identities
.has_key(auteur
):
392 serv
.privmsg(auteur
, "Je vous connais sous le pseudo note %s." % (
393 self
.identities
[auteur
]["pseudo"].encode("utf8")))
395 serv
.privmsg(auteur
, "Je ne connais pas votre pseudo note.")
396 elif len(message
) >= 3:
397 username
, password
= message
[1], " ".join(message
[2:])
398 success
, info
, _
= self
.new_connection_NK(serv
, username
, password
)
400 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
401 serv
.privmsg(auteur
, "Identité enregistrée.")
402 self
.identities
[auteur
] = info
403 json
.dump(self
.identities
, open(config
.identities_file
,"w"))
405 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
406 serv
.privmsg(auteur
, "Mot de passe invalide. (ou serveur down)")
408 serv
.privmsg(auteur
, "Syntaxe : IDENTIFY [<username> <password>]")
411 if self
.identities
.has_key(auteur
):
412 password
= " ".join(message
[1:])
413 success
, _
, _
= self
.new_connection_NK(serv
, self
.identities
[auteur
], password
)
415 del self
.identities
[auteur
]
416 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
417 json
.dump(self
.identities
, open(config
.identities_file
, "w"))
418 serv
.privmsg(auteur
, "Identité oubliée.")
420 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
421 serv
.privmsg(auteur
, "Mot de passe invalide. (ou serveur down)")
423 serv
.privmsg(auteur
, "Je ne connais pas ton pseudo note.")
425 serv
.privmsg(auteur
, "Syntaxe : DROP <password>")
427 if auteur
in self
.ops
:
429 if message
[1] in self
.chanlist
:
430 serv
.privmsg(auteur
, "Je suis déjà sur %s" % (message
[1]))
432 serv
.join(message
[1])
433 self
.chanlist
.append(message
[1])
434 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
435 log(self
.serveur
, "priv", auteur
, " ".join(message
))
437 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
440 elif cmd
== u
"leave":
441 if auteur
in self
.ops
and len(message
) > 1:
442 if message
[1] in self
.chanlist
:
443 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
444 self
.quitter(message
[1].encode("utf-8"), " ".join(message
[2:]))
445 self
.chanlist
.remove(message
[1])
446 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
448 serv
.privmsg(auteur
, "Non, je reste !")
449 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
451 serv
.privmsg(auteur
, "Je ne suis pas sur %s" % (message
[1]))
455 if auteur
in self
.overops
:
457 if message
[1] in self
.stay_channels
:
458 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
459 serv
.privmsg(auteur
, "Je stay déjà sur %s." % (message
[1]))
461 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
462 self
.stay_channels
.append(message
[1])
463 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
465 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
468 elif cmd
== u
"nostay":
469 if auteur
in self
.overops
:
471 if message
[1] in self
.stay_channels
:
472 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
473 self
.stay_channels
.remove(message
[1])
474 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
476 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
477 serv
.privmsg(auteur
, "Je ne stay pas sur %s." % (message
[1]))
481 if auteur
in self
.overops
:
482 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
486 elif cmd
== u
"crash":
487 if auteur
in self
.overops
:
488 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
489 self
.crash("priv", auteur
)
492 elif cmd
== u
"reload":
493 if auteur
in self
.ops
:
494 self
.execute_something("reload", {"auteur" : auteur
}, place
=auteur
, auteur
=auteur
)
497 elif cmd
== u
"reconnect":
498 if auteur
in self
.ops
:
500 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
,
501 config
.note_password
, "special")[2]
502 except Exception as exc
:
504 log(self
.serveur
, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc
))
506 serv
.privmsg(auteur
, "%s: done" % (auteur
))
507 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
509 serv
.privmsg(auteur
, "%s: failed" % (auteur
))
510 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
511 for report
in self
.report_bugs_to
:
512 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ? Server dead ?")
515 elif cmd
== u
"quiet":
516 if auteur
in self
.ops
:
518 if message
[1] in self
.quiet_channels
:
519 serv
.privmsg(auteur
, "Je me la ferme déjà sur %s" % (message
[1]))
520 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
522 self
.quiet_channels
.append(message
[1])
523 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
524 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
526 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
529 elif cmd
== u
"noquiet":
530 if auteur
in self
.ops
:
532 if message
[1] in self
.quiet_channels
:
533 self
.quiet_channels
.remove(message
[1])
534 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
535 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
537 serv
.privmsg(auteur
, "Je ne me la ferme pas sur %s." % (message
[1]))
538 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
542 if auteur
in self
.overops
and len(message
) > 2:
543 serv
.privmsg(message
[1].encode("utf-8"), (u
" ".join(message
[2:])).encode("utf-8"))
544 log(self
.serveur
, "priv", auteur
, " ".join(message
))
545 elif len(message
) <= 2:
546 serv
.privmsg(auteur
, "Syntaxe : SAY <channel> <message>")
550 if auteur
in self
.overops
and len(message
) > 2:
551 serv
.action(message
[1], " ".join(message
[2:]))
552 log(self
.serveur
, "priv", auteur
, " ".join(message
))
553 elif len(message
) <= 2:
554 serv
.privmsg(auteur
, "Syntaxe : DO <channel> <action>")
558 if auteur
in self
.overops
and len(message
) > 2:
559 serv
.kick(message
[1].encode("utf-8"), message
[2].encode("utf-8"), " ".join(message
[3:]).encode("utf-8"))
560 log(self
.serveur
, "priv", auteur
, " ".join(message
))
561 elif len(message
) <= 2:
562 serv
.privmsg(auteur
, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
566 if auteur
in self
.ops
and len(message
) > 1:
567 serv
.privmsg(message
[1], "J'ai perdu !")
568 log(self
.serveur
, "priv", auteur
, " ".join(message
))
569 elif len(message
) <= 1:
570 serv
.privmsg(auteur
, "Syntaxe : LOST <channel>")
573 elif cmd
== u
"solde":
574 if len(message
) == 1:
575 if self
.identities
.has_key(auteur
):
576 success
, solde
, pseudo
= nk
.get_solde(self
.nk
, self
.identities
[auteur
]["idbde"], serv
, auteur
)
578 serv
.privmsg(auteur
, "%.2f (%s)" % (solde
/100.0, pseudo
.encode("utf8")))
579 log(self
.serveur
, "priv", auteur
, " ".join(message
) + ("[successful]" if success
else "[failed]"))
581 serv
.privmsg(canal
, "Je ne connais pas ton pseudo note.")
583 if auteur
in self
.overops
:
584 serv
.privmsg(auteur
, " ".join(self
.ops
))
587 elif cmd
== u
"overops":
588 if auteur
in self
.overops
:
589 serv
.privmsg(auteur
, " ".join(self
.overops
))
595 serv
.privmsg(auteur
, "Je n'ai pas compris. Essayez HELP…")
597 def on_pubmsg(self
, serv
, ev
):
598 """À la réception d'un message sur un channel."""
599 if ignore_event(serv
, ev
):
601 auteur
= irclib
.nm_to_n(ev
.source())
603 message
= ev
.arguments()[0]
605 message
= bot_unicode(message
)
606 except UnicodeBotError
:
607 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
608 serv
.privmsg(canal
, (u
"%s: %s"% ( auteur
, random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
610 pour_moi
, message
= self
.pourmoi(serv
, message
)
611 if pour_moi
and message
.split()!=[]:
612 cmd
= message
.split()[0].lower()
614 args
= " ".join(message
.split()[1:])
617 if cmd
in [u
"meurs", u
"die", u
"crève"]:
618 if auteur
in self
.overops
:
619 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
622 serv
.privmsg(canal
,(u
"%s: %s"%(auteur
, random
.choice(config
.quit_fail_messages
))).encode("utf8"))
623 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
624 elif cmd
== u
"reload":
625 if auteur
in self
.ops
:
626 self
.execute_something("reload", {"auteur" : auteur
}, place
=canal
, auteur
=auteur
)
627 elif cmd
== u
"crash":
628 if auteur
in self
.overops
:
629 self
.crash(auteur
, message
)
630 elif cmd
in [u
"part", u
"leave", u
"dégage", u
"va-t-en", u
"tut'tiresailleurs,c'estmesgalets"]:
631 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
632 or auteur
in self
.overops
):
634 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
635 if canal
in self
.chanlist
:
636 self
.chanlist
.remove(canal
)
638 serv
.privmsg(canal
,(u
"%s: %s" % (auteur
, random
.choice(config
.leave_fail_messages
))).encode("utf8"))
639 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
641 elif cmd
== u
"reconnect":
642 if auteur
in self
.ops
:
644 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
,
645 config
.note_password
, "special")[2]
646 except Exception as exc
:
648 log(self
.serveur
, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc
))
650 serv
.privmsg(canal
, "%s: done" % (auteur
))
651 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
653 serv
.privmsg(canal
, "%s: failed" % (auteur
))
654 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
655 for report
in self
.report_bugs_to
:
656 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ? Server dead ?")
658 serv
.privmsg(canal
, "%s: %s" % (auteur
, random
.choice(config
.pas_programme_pour_tobeir
).encode("utf8")))
659 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
661 elif cmd
in [u
"deviens", u
"pseudo"]:
662 if auteur
in self
.ops
:
665 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
667 if cmd
in [u
"meur", u
"meurt", u
"meurre", u
"meurres"] and not canal
in self
.quiet_channels
:
668 serv
.privmsg(canal
, '%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)' % (auteur
))
669 elif cmd
in [u
"ping"] and not canal
in self
.quiet_channels
:
670 serv
.privmsg(canal
, "%s: pong" % (auteur
))
672 elif cmd
in [u
"solde", u
"!solde", u
"!coca"] or cmd
.startswith("!"):
673 if self
.identities
.has_key(auteur
):
674 idbde
= self
.identities
[auteur
]["idbde"]
675 if cmd
in [u
"solde", u
"!solde"]:
676 success
, solde
, pseudo
= nk
.get_solde(self
.nk
, self
.identities
[auteur
]["idbde"], serv
, canal
)
678 serv
.privmsg(canal
, "%s: %s (%s)" % (auteur
, float(solde
)/100, pseudo
.encode("utf8")))
679 elif cmd
in [u
"!coca"] or cmd
.startswith("!"):
680 success
= nk
.consomme(self
.nk
, self
.identities
[auteur
]["idbde"], message
[1:], serv
, canal
)
681 log(self
.serveur
, canal
, auteur
, message
+ ("[successful]" if success
else "[failed]"))
683 serv
.privmsg(canal
, "%s: Je ne connais pas votre pseudo note." % (auteur
))
684 log(self
.serveur
, canal
, auteur
, message
+ "[unknown]")
685 elif (re
.match("(pain au chocolat|chocolatine)", message
.lower())
686 and not canal
in self
.quiet_channels
):
687 serv
.action(canal
, "sert un pain au chocolat à %s" % (auteur
))
688 elif re
.match("manzana",message
.lower()) and not canal
in self
.quiet_channels
:
689 if auteur
in config
.manzana
:
690 serv
.action(canal
, "sert une bouteille de manzana à %s" % (auteur
))
691 elif auteur
in config
.manzana_bis
:
692 serv
.action(canal
, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur
))
694 serv
.action(canal
, "sert un verre de manzana à %s" % (auteur
))
695 if is_insult(message
) and not canal
in self
.quiet_channels
:
696 if is_not_insult(message
):
697 answer
= random
.choice(config
.compliment_answers
)
698 for ligne
in answer
.split("\n"):
699 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
701 answer
= random
.choice(config
.insultes_answers
)
702 for ligne
in answer
.split("\n"):
703 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
704 elif is_compliment(message
) and not canal
in self
.quiet_channels
:
705 answer
= random
.choice(config
.compliment_answers
)
706 for ligne
in answer
.split("\n"):
707 serv
.privmsg(canal
, "%s: %s" % (auteur
,ligne
.encode("utf8")))
708 gros_match
= is_gros(message
)
709 if gros_match
and not canal
in self
.quiet_channels
:
710 taille
= get_filesize()
711 answer
= u
"Mais non, je ne suis pas %s, %sKo tout au plus…" % (gros_match
.groups()[0], taille
)
712 serv
.privmsg(canal
, "%s: %s"%(auteur
, answer
.encode("utf8")))
713 if is_tesla(message
) and not canal
in self
.quiet_channels
:
714 l1
, l2
= config
.tesla_answers
, config
.tesla_actions
715 n1
, n2
= len(l1
), len(l2
)
716 i
= random
.randrange(n1
+ n2
)
718 serv
.action(canal
, l2
[i
- n1
].encode("utf8"))
720 serv
.privmsg(canal
, "%s: %s" % (auteur
, l1
[i
].encode("utf8")))
721 if is_tamere(message
) and not canal
in self
.quiet_channels
:
722 answer
= random
.choice(config
.tamere_answers
)
723 for ligne
in answer
.split("\n"):
724 serv
.privmsg(canal
, "%s: %s"%(auteur
, ligne
.encode("utf8")))
725 if is_tag(message
) and not canal
in self
.quiet_channels
:
726 if auteur
in self
.ops
:
727 action
= random
.choice(config
.tag_actions
)
728 serv
.action(canal
, action
.encode("utf8"))
729 self
.quiet_channels
.append(canal
)
731 answer
= random
.choice(config
.tag_answers
)
732 for ligne
in answer
.split("\n"):
733 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
734 if is_merci(message
):
735 answer
= random
.choice(config
.merci_answers
)
736 for ligne
in answer
.split("\n"):
737 serv
.privmsg(canal
, "%s: %s"%(auteur
, ligne
.encode("utf8")))
738 out
= re
.match(ur
"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$", message
.upper())
739 if re
.match("ma bite dans ton oreille", message
) and not canal
in self
.quiet_channels
:
740 serv
.privmsg(canal
, "%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !" % (auteur
))
741 if out
and not canal
in self
.quiet_channels
:
742 out
= out
.groups()[0]
745 serv
.privmsg(canal
, "%s: %s !" % (auteur
, iout
+ 1))
746 if iout
== 2147483647:
747 serv
.privmsg(canal
, "%s: Ciel, un maxint ! Heureusement que je suis en python…" % (auteur
))
749 if iout
+ 1 > 1000 and random
.randrange(4) == 0:
750 serv
.privmsg(canal
, "%s: Vous savez, moi et les chiffres…" % (auteur
))
752 except Exception as exc
:
754 if re
.match("[A-Y]", out
):
755 alphabet
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
756 serv
.privmsg(canal
, "%s: %s !"%(auteur
, alphabet
[alphabet
.index(out
) + 1]))
758 serv
.privmsg(canal
, "%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?" % (auteur
))
760 serv
.privmsg(canal
, "%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…" % (auteur
))
761 elif re
.match(ur
"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+", out
):
763 return "".join([{u
"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i
]:u
"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i
]
764 for i
in range(20)}[j
]
766 out
= int(translate(out
))
767 serv
.privmsg(canal
,"%s: %s !" % (auteur
, translate(str(out
+ 1)).encode("utf8")))
768 if is_bonjour(message
) and not canal
in self
.quiet_channels
:
770 answer
= random
.choice(config
.night_answers
)
772 answer
= random
.choice(config
.bonjour_answers
)
774 answer
= random
.choice(config
.bonsoir_answers
)
775 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
776 if is_bonne_nuit(message
) and not canal
in self
.quiet_channels
:
777 answer
= random
.choice(config
.bonne_nuit_answers
)
778 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
779 if is_pan(message
) and not canal
in self
.quiet_channels
:
780 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
))
782 if message
in [u
"!pain au chocolat", u
"!chocolatine"] and not canal
in self
.quiet_channels
:
783 serv
.action(canal
, "sert un pain au chocolat à %s" % (auteur
))
784 if message
in [u
"!manzana"] and not canal
in self
.quiet_channels
:
785 if auteur
in config
.manzana
:
786 serv
.action(canal
, "sert une bouteille de manzana à %s" % (auteur
))
787 elif auteur
in config
.manzana_bis
:
788 serv
.action(canal
, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur
))
790 serv
.action(canal
, "sert un verre de manzana à %s" % (auteur
))
791 if re
.match(u
'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$', message
) and not canal
in self
.quiet_channels
:
792 failanswers
= config
.buffer_fail_answers
793 answer
= random
.choice(failanswers
)
794 serv
.privmsg(canal
, ("%s: %s"%(auteur
,answer
)).encode("utf8"))
795 if not canal
in self
.quiet_channels
:
797 if re
.match((u
"^(" + u
"|".join(config
.bonjour_triggers
)
798 + ur
")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
799 ).format(mypseudo
).lower(), message
.strip().lower()):
800 answer
= random
.choice(config
.bonjour_answers
)
801 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
802 if (is_perdu(message
) and not canal
in self
.quiet_channels
):
803 # proba de perdre sur trigger :
804 # avant 30min (enfin, config) : 0
805 # ensuite, +25%/30min, linéairement
806 deltat
= time
.time() - self
.last_perdu
807 barre
= (deltat
- config
.time_between_perdu
)/(2*3600.0)
808 if random
.uniform(0, 1) < barre
:
809 serv
.privmsg(canal
, "%s: J'ai perdu !" % (auteur
))
810 self
.last_perdu
= time
.time()
812 def on_action(self
, serv
, ev
):
813 """À la réception d'une action."""
814 if ignore_event(serv
, ev
):
816 action
= ev
.arguments()[0]
817 auteur
= irclib
.nm_to_n(ev
.source())
818 channel
= ev
.target()
820 action
= bot_unicode(action
)
821 except UnicodeBotError
:
822 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
823 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
827 if is_bad_action_trigger(action
, mypseudo
) and not channel
in self
.quiet_channels
:
828 l1
, l2
= config
.bad_action_answers
, config
.bad_action_actions
829 n1
, n2
= len(l1
), len(l2
)
830 i
= random
.randrange(n1
+ n2
)
832 serv
.action(channel
, l2
[i
- n1
].format(auteur
).encode("utf8"))
834 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
835 if is_good_action_trigger(action
, mypseudo
) and not channel
in self
.quiet_channels
:
836 l1
, l2
= config
.good_action_answers
, config
.good_action_actions
837 n1
, n2
= len(l1
), len(l2
)
838 i
= random
.randrange(n1
+ n2
)
840 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
842 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
844 def on_kick(self
, serv
, ev
):
845 """À la réception d'une action."""
846 auteur
= irclib
.nm_to_n(ev
.source())
847 channel
= ev
.target()
848 victime
= ev
.arguments()[0]
849 raison
= ev
.arguments()[1]
850 if victime
== self
.nick
:
851 log(self
.serveur
, u
"%s kické de %s par %s (raison : %s)" % (victime
, channel
.decode("utf-8"), auteur
, raison
))
854 l1
, l2
= config
.kick_answers
, config
.kick_actions
855 n1
, n2
= len(l1
), len(l2
)
856 i
= random
.randrange(n1
+ n2
)
858 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
860 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
863 def start_as_daemon(self
, outfile
):
864 sys
.stderr
= Logger(outfile
)
868 class Logger(object):
869 """Pour écrire ailleurs que sur stdout"""
870 def __init__(self
, filename
="basile.full.log"):
871 self
.filename
= filename
873 def write(self
, message
):
874 f
= open(self
.filename
, "a")
879 """Exécution principal : lecture des paramètres et lancement du bot."""
880 if len(sys
.argv
) == 1:
881 print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
882 print " --outfile sans --no-output ni --daemon n'a aucun effet"
884 serveur
= sys
.argv
[1]
885 if "--daemon" in sys
.argv
:
886 thisfile
= os
.path
.realpath(__file__
)
887 thisdirectory
= thisfile
.rsplit("/", 1)[0]
888 os
.chdir(thisdirectory
)
892 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
896 if "--quiet" in sys
.argv
:
897 config
.debug_stdout
= False
898 serveurs
= {"a♡" : "acoeur.crans.org",
899 "acoeur" : "acoeur.crans.org",
900 "acoeur.crans.org" : "acoeur.crans.org",
901 "irc" : "irc.crans.org",
902 "crans" : "irc.crans.org",
903 "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
.execute_reload(auteur
="SIGHUP")
920 signal
.signal(signal
.SIGHUP
, sighup_handler
)
923 child_pid
= os
.fork()
926 basile
.start_as_daemon(outfile
)
928 # on enregistre le pid de basile
929 pidfile
= "/var/run/bots/basile.pid"
932 if arg
[0].strip('-') in ["pidfile"]:
934 f
= open(pidfile
, "w")
935 f
.write("%s\n" % child_pid
)
940 if __name__
== "__main__":