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")
20 # On veut réagir sur la partie du whois qui dit qu'un nick est registered
21 irclib
.numeric_events
['307'] = "whoisregnick"
24 from commands
import getstatusoutput
as ex
28 #: Module responsable du dialogue avec la NoteKfet2015
30 #: Module de réponse aux questions de base
32 #: Module définissant les erreurs
34 #: Module de gestion des utilisateurs
37 # la partie qui réfère au fichier lui-même est mieux ici
38 # sinon on réfère la config et pas le fichier lui-même
39 config
.thisfile
= os
.path
.realpath(__file__
)
41 def get_config_logfile(serveur
):
42 """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
43 serveurs
= {"acoeur.crans.org" : "acoeur",
44 "irc.crans.org" : "crans"}
45 return config
.logfile_template
% (serveurs
[serveur
],)
48 """Récupère la taille de ce fichier."""
49 return ex("ls -s %s" % (config
.thisfile
))[1].split()[0]
51 def log(serveur
, channel
, auteur
=None, message
=None):
52 """Enregistre une ligne de log."""
53 if auteur
== message
== None:
54 # alors c'est que c'est pas un channel mais juste une ligne de log
55 chain
= u
"%s %s" % (time
.strftime("%F %T"), channel
)
57 chain
= u
"%s [%s:%s] %s" % (time
.strftime("%F %T"), channel
, auteur
, message
)
58 f
= open(get_config_logfile(serveur
), "a")
59 f
.write((chain
+ u
"\n").encode("utf-8"))
61 if config
.debug_stdout
:
62 print chain
.encode("utf-8")
64 def ignore_event(serv
, ev
):
65 """Retourne ``True`` si il faut ignorer cet évènement."""
66 for (blackmask
, exceptlist
) in config
.blacklisted_masks
:
67 usermask
= ev
.source()
68 blackit
= bool(irclib
.mask_matches(usermask
, blackmask
))
69 exceptit
= any([bool(irclib
.mask_matches(usermask
, exceptmask
)) for exceptmask
in exceptlist
])
70 if exceptit
: # Il est exempté
73 if blackit
: # Il n'est pas exempté et matche la blacklist
77 def bot_unicode(chain
):
78 """Essaye de décoder ``chain`` en UTF-8.
79 Lève une py:class:`errors.UnicodeBotError` en cas d'échec."""
81 return chain
.decode("utf8")
82 except UnicodeDecodeError as exc
:
83 raise errors
.UnicodeBotError
86 class Basile(ircbot
.SingleServerIRCBot
):
87 """Classe principale : définition du bot Basile."""
88 def __init__(self
, serveur
, debug
=False):
89 temporary_pseudo
= config
.irc_pseudo
+ str(random
.randrange(10000,100000))
90 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
91 temporary_pseudo
, "Basile, le bot irc. [Codé par 20-100]", 10)
93 self
.serveur
= serveur
94 self
.overops
= config
.overops
95 self
.ops
= self
.overops
+ config
.ops
96 self
.report_bugs_to
= config
.report_bugs_to
97 self
.chanlist
= config
.chanlist
98 self
.stay_channels
= config
.stay_channels
99 self
.quiet_channels
= config
.quiet_channels
101 # On charge la base de données d'utilisateurs
102 self
.users
= users
.UserDB()
106 def new_connection_NK(self
, serv
, username
, password
, typ
="bdd"):
107 """Renvoie (``True``, <une socket ouverte et authentifiée sur la NoteKfet2015>)
108 ou bien (``False``, None)"""
110 login_result
, sock
= nk
.login(username
, password
, typ
)
111 info
, retcode
, errmsg
= login_result
["msg"], login_result
["retcode"], login_result
["errmsg"]
112 except nk
.NKRefused
as exc
:
113 for report
in self
.report_bugs_to
:
114 serv
.privmsg(report
, "Le Serveur NK2015 est down.")
115 return (False, None, None)
116 except nk
.NKHelloFailed
as exc
:
117 for report
in self
.report_bugs_to
:
119 "La version du protocole utilisée n'est pas supportée par le serveur NK2015.")
120 return (False, None, None)
121 except nk
.NKUnknownError
as exc
:
122 erreurs
= ["Une fucking erreur inconnue s'est produite"]
123 erreurs
+= str(exc
).split("\n")
124 for report
in self
.report_bugs_to
:
126 serv
.privmsg(report
, err
)
127 return (False, None, None)
128 except Exception as exc
:
129 # Exception qui ne vient pas de la communication avec le serveur NK2015
130 log(self
.serveur
, "Erreur dans new_connection_NK\n" + str(exc
))
131 return (False, None, None)
133 return (True, info
, sock
)
135 return (False, None, None)
139 """Récuère le nick effectif du bot sur le serveur."""
140 return self
.serv
.get_nickname()
141 nick
= property(_getnick
)
143 def give_me_my_pseudo(self
, serv
):
144 """Récupère le pseudo auprès de NickServ."""
145 serv
.privmsg("NickServ", "RECOVER %s %s" % (config
.irc_pseudo
, config
.irc_password
))
146 serv
.privmsg("NickServ", "RELEASE %s %s" % (config
.irc_pseudo
, config
.irc_password
))
148 serv
.nick(config
.irc_pseudo
)
150 def pourmoi(self
, serv
, message
):
151 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
153 pseudo
= pseudo
.decode("utf-8")
155 if message
[:size
] == pseudo
and len(message
) > size
and message
[size
] == ":":
156 return (True, message
[size
+1:].lstrip(" "))
158 return (False, message
)
160 ### Exécution d'actions
161 def lost(self
, serv
, channel
, forced
=False):
162 """Réaction à un trigger de perdu.
163 Annonce "J'ai perdu" sur le channel si on n'a pas perdu depuis un certain temps."""
164 if self
.last_perdu
+ config
.time_between_perdu
< time
.time() or forced
:
165 if not channel
in self
.quiet_channels
or forced
:
166 serv
.privmsg(channel
, "J'ai perdu !")
167 self
.last_perdu
= time
.time()
168 delay
= config
.time_between_perdu_trigger
169 delta
= config
.time_between_perdu_trigger_delta
170 serv
.execute_delayed(random
.randrange(delay
- delta
, delay
+ delta
), self
.lost
, (serv
, channel
))
172 def quitter(self
, chan
, leave_message
=None):
173 """Quitter un channel avec un message customisable."""
174 if leave_message
== None:
175 leave_message
= random
.choice(config
.leave_messages
)
176 self
.serv
.part(chan
, message
=leave_message
.encode("utf8"))
179 """Se déconnecter du serveur IRC avec un message customisable."""
180 quit_message
= random
.choice(config
.quit_messages
)
181 self
.die(msg
=quit_message
.encode("utf8"))
183 def execute_reload(self
, auteur
=None):
184 """Recharge la config."""
186 isit
.regexp_compile()
187 if auteur
in [None, "SIGHUP"]:
188 towrite
= "Config reloaded" + " (SIGHUP received)" * (auteur
== "SIGHUP")
189 for to
in config
.report_bugs_to
:
190 self
.serv
.privmsg(to
, towrite
)
191 log(self
.serveur
, towrite
)
194 return True, u
"Config reloaded"
196 def crash(self
, who
="nobody", chan
="nowhere"):
197 """Fait crasher le bot."""
198 where
= "en privé" if chan
== "priv" else "sur le chan %s" % chan
199 raise errors
.CrashError((u
"Crash demandé par %s %s" % (who
, where
)).encode("utf-8"))
202 "reload" : execute_reload
,
205 def execute_something(self
, something
, params
, place
=None, auteur
=None):
206 """Exécute une action et répond son résultat à ``auteur``
207 sur un chan ou en privé en fonction de ``place``"""
208 action
= self
.ACTIONS
[something
]
209 success
, message
= action(self
, **params
)
211 if irclib
.is_channel(place
):
212 message
= "%s: %s" % (auteur
, message
.encode("utf-8"))
213 self
.serv
.privmsg(place
, message
)
214 log(self
.serveur
, place
, auteur
, something
+ "%r" % params
+ ("[successful]" if success
else "[failed]"))
216 def whois(self
, pseudo
, askedwhy
, askedby
):
217 """Demande un whois sur ``pseudo``. La réponse sera handled par une autre fonction."""
218 self
.users
.pending_whois
[pseudo
] = ["pending", askedwhy
, askedby
, None]
219 self
.serv
.whois([pseudo
])
221 ### Surcharge des events du Bot
222 def on_welcome(self
, serv
, ev
):
223 """À l'arrivée sur le serveur."""
224 self
.serv
= serv
# ça serv ira :)
225 self
.give_me_my_pseudo(serv
)
226 serv
.privmsg("NickServ", "IDENTIFY %s" % (config
.irc_password
))
227 log(self
.serveur
, "Connected")
229 self
.chanlist
= ["#bot"]
230 for c
in self
.chanlist
:
231 log(self
.serveur
, "JOIN %s" % (c
))
233 # on ouvre la connexion note de Basile, special user
234 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
, config
.note_password
, "special")[2]
236 for report
in self
.report_bugs_to
:
237 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ?")
239 def on_whoisregnick(self
, serv
, ev
):
240 """Appelée sur une réponse à un whois au moment où ça dit "is a registered nick".
241 J'ai vérifié, "is a registered nick" ça inclu le fiat qu'il est identified correctement.
243 On stocke l'information comme quoi cette personne est registered, et quand c'était."""
244 pseudo
, phrase
= ev
.arguments()
245 if phrase
== 'is a registered nick':
246 # Le whois n'est plus "pending"
247 if self
.users
.pending_whois
.has_key(pseudo
):
248 self
.users
.pending_whois
[pseudo
][0] = "registered"
249 self
.users
.pending_whois
[pseudo
][3] = time
.time()
250 _
, askedwhy
, askedby
, _
= self
.users
.pending_whois
[pseudo
]
251 if askedwhy
== "cmd WHOIS":
252 # Ce whois a été demandé par quelqu'un, on lui répond
253 self
.serv
.privmsg(askedby
, "%s is a registered nick" % (pseudo
,))
255 def on_endofwhois(self
, serv
, ev
):
256 """Si on arrive à la fin du whois, on va voir si on n'a pas reçu "is a registered nick"
257 c'est que le pseudo n'est pas identifié. """
258 pseudo
= ev
.arguments()[0]
259 # On laisse le temps au bot de souffler un coup
260 serv
.execute_delayed(config
.whois_timeout
, self
.fail_whoisregnick
, (pseudo
,))
262 def fail_whoisregnick(self
, pseudo
):
263 """Maintenant qu'on a laissé quelques secondes au bot pour gérer les affaires courantes,
264 on considère que le pseudo n'est pas registered. """
265 # Attention, parce qu'il se pourrait qu'on n'ait pas sollicité ce whois
266 # et que donc pending_whois n'ai pas été peuplé en conséquence
267 if self
.users
.pending_whois
.has_key(pseudo
):
268 status
, askedwhy
, askedby
, _
= self
.users
.pending_whois
[pseudo
]
269 if status
== "pending":
270 # Si le status est encore pending, on n'a pas eu de réponse positive, donc elle est négative
271 self
.users
.pending_whois
[pseudo
] = "notregistered"
272 if askedwhy
== "cmd WHOIS":
273 self
.serv
.privmsg(askedby
, "%s is NOT a registered nick" % (pseudo
,))
275 def on_privmsg(self
, serv
, ev
):
276 """À la réception d'un message en privé."""
277 if ignore_event(serv
, ev
):
279 message
= ev
.arguments()[0]
280 auteur
= irclib
.nm_to_n(ev
.source())
282 message
= bot_unicode(message
)
283 except errors
.UnicodeBotError
:
284 if config
.utf8_trigger
:
285 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
287 message
= message
.split()
288 cmd
= message
[0].lower()
289 notunderstood
= False
291 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
293 helpmsg
= config
.helpmsg_default
295 helpmsg
+= config
.helpmsg_ops
297 helpmsg
+= config
.helpmsg_overops
299 helpmsgs
= config
.helpdico
.get(message
[1].lower(), ["Commande inconnue.", None, None])
300 helpmsg
= helpmsgs
[0]
301 if op
and helpmsgs
[1]:
303 helpmsg
+= "\n" + helpmsgs
[1]
305 helpmsg
= helpmsgs
[1]
306 if overop
and helpmsgs
[2]:
308 helpmsg
+= "\n" + helpmsgs
[2]
310 helpmsg
= helpmsgs
[2]
311 if not helpmsg
: # Un non-op a demandé de l'aide sur une commande dont il n'est pas censé connaître l'existence
312 helpmsg
= "Commande inacessible."
313 for ligne
in helpmsg
.split("\n"):
314 serv
.privmsg(auteur
, ligne
.encode("utf-8"))
315 elif cmd
== u
"identify":
316 if len(message
) == 1:
317 if self
.users
.has(auteur
):
318 infos
= self
.users
[auteur
].get_infos(self
.nk
, serv
, auteur
)
319 serv
.privmsg(auteur
, (u
"Vous avez le compte note n°%(idbde)s, pseudo : %(pseudo)s." % infos
322 serv
.privmsg(auteur
, "Je ne connais pas votre note.")
323 elif len(message
) >= 3:
324 username
, password
= message
[1], " ".join(message
[2:])
325 success
, info
, _
= self
.new_connection_NK(serv
, username
, password
)
327 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
328 self
.users
.add(auteur
, info
["idbde"])
329 serv
.privmsg(auteur
, "Pseudo enregistré.")
331 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
332 serv
.privmsg(auteur
, "Mot de passe invalide. (ou serveur down)")
334 serv
.privmsg(auteur
, "Syntaxe : IDENTIFY [<username> <password>]")
337 if self
.users
.has(auteur
):
338 idbde
= self
.users
[auteur
].idbde
339 password
= " ".join(message
[1:])
340 success
, _
, _
= self
.new_connection_NK(serv
, "#%s" % idbde
, password
)
342 self
.users
.drop(idbde
)
343 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
344 serv
.privmsg(auteur
, "Pseudo oublié.")
346 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
347 serv
.privmsg(auteur
, "Mot de passe invalide. (ou serveur down)")
349 serv
.privmsg(auteur
, "Je ne connais pas votre note.")
351 serv
.privmsg(auteur
, "Syntaxe : DROP <password>")
353 if auteur
in self
.ops
:
355 if message
[1] in self
.chanlist
:
356 serv
.privmsg(auteur
, (u
"Je suis déjà sur %s" % (message
[1])).encode("utf-8"))
358 serv
.join(message
[1])
359 self
.chanlist
.append(message
[1])
360 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
361 log(self
.serveur
, "priv", auteur
, " ".join(message
))
363 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
366 elif cmd
== u
"leave":
367 if auteur
in self
.ops
and len(message
) > 1:
368 if message
[1] in self
.chanlist
:
369 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
370 self
.quitter(message
[1].encode("utf-8"), " ".join(message
[2:]))
371 self
.chanlist
.remove(message
[1])
372 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
374 serv
.privmsg(auteur
, "Non, je reste !")
375 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
377 serv
.privmsg(auteur
, "Je ne suis pas sur %s" % (message
[1]))
381 if auteur
in self
.overops
:
383 if message
[1] in self
.stay_channels
:
384 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
385 serv
.privmsg(auteur
, "Je stay déjà sur %s." % (message
[1]))
387 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
388 self
.stay_channels
.append(message
[1])
389 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
391 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
394 elif cmd
== u
"nostay":
395 if auteur
in self
.overops
:
397 if message
[1] in self
.stay_channels
:
398 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
399 self
.stay_channels
.remove(message
[1])
400 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
402 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
403 serv
.privmsg(auteur
, "Je ne stay pas sur %s." % (message
[1]))
407 if auteur
in self
.overops
:
408 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
412 elif cmd
== u
"crash":
413 if auteur
in self
.overops
:
414 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
415 self
.crash(auteur
, "priv")
418 elif cmd
== u
"reload":
419 if auteur
in self
.ops
:
420 self
.execute_something("reload", {"auteur" : auteur
}, place
=auteur
, auteur
=auteur
)
423 elif cmd
== u
"reconnect":
424 if auteur
in self
.ops
:
426 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
,
427 config
.note_password
, "special")[2]
428 except Exception as exc
:
430 log(self
.serveur
, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc
))
432 serv
.privmsg(auteur
, "%s: done" % (auteur
))
433 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
435 serv
.privmsg(auteur
, "%s: failed" % (auteur
))
436 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
437 for report
in self
.report_bugs_to
:
438 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ? Server dead ?")
441 elif cmd
== u
"quiet":
442 if auteur
in self
.ops
:
444 if message
[1] in self
.quiet_channels
:
445 serv
.privmsg(auteur
, "Je me la ferme déjà sur %s" % (message
[1]))
446 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
448 self
.quiet_channels
.append(message
[1])
449 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
450 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
452 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
455 elif cmd
== u
"noquiet":
456 if auteur
in self
.ops
:
458 if message
[1] in self
.quiet_channels
:
459 self
.quiet_channels
.remove(message
[1])
460 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
461 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
463 serv
.privmsg(auteur
, "Je ne me la ferme pas sur %s." % (message
[1]))
464 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
468 if auteur
in self
.overops
and len(message
) > 2:
469 serv
.privmsg(message
[1].encode("utf-8"), (u
" ".join(message
[2:])).encode("utf-8"))
470 log(self
.serveur
, "priv", auteur
, " ".join(message
))
471 elif len(message
) <= 2:
472 serv
.privmsg(auteur
, "Syntaxe : SAY <channel> <message>")
476 if auteur
in self
.overops
and len(message
) > 2:
477 serv
.action(message
[1], " ".join(message
[2:]))
478 log(self
.serveur
, "priv", auteur
, " ".join(message
))
479 elif len(message
) <= 2:
480 serv
.privmsg(auteur
, "Syntaxe : DO <channel> <action>")
484 if auteur
in self
.overops
and len(message
) > 2:
485 serv
.kick(message
[1].encode("utf-8"), message
[2].encode("utf-8"), " ".join(message
[3:]).encode("utf-8"))
486 log(self
.serveur
, "priv", auteur
, " ".join(message
))
487 elif len(message
) <= 2:
488 serv
.privmsg(auteur
, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
492 if auteur
in self
.ops
and len(message
) > 1:
493 serv
.privmsg(message
[1], "J'ai perdu !")
494 log(self
.serveur
, "priv", auteur
, " ".join(message
))
495 elif len(message
) <= 1:
496 serv
.privmsg(auteur
, "Syntaxe : LOST <channel>")
499 elif cmd
== u
"solde":
500 if len(message
) == 1:
501 if self
.users
.has(auteur
):
502 success
, solde
, pseudo
= nk
.get_solde(self
.nk
, self
.users
[auteur
].idbde
, serv
, auteur
)
504 serv
.privmsg(auteur
, "%.2f (%s)" % (solde
/100.0, pseudo
.encode("utf8")))
505 log(self
.serveur
, "priv", auteur
, " ".join(message
) + ("[successful]" if success
else "[failed]"))
507 serv
.privmsg(auteur
, "Je ne connais pas votre note.")
509 if auteur
in self
.overops
:
510 serv
.privmsg(auteur
, " ".join(self
.ops
))
513 elif cmd
== u
"overops":
514 if auteur
in self
.overops
:
515 serv
.privmsg(auteur
, " ".join(self
.overops
))
518 elif cmd
== u
"whois":
519 if auteur
in self
.ops
and len(message
) > 1:
520 self
.whois(message
[1], askedwhy
="cmd WHOIS", askedby
=auteur
)
526 serv
.privmsg(auteur
, "Je n'ai pas compris. Essayez HELP…")
528 def on_pubmsg(self
, serv
, ev
):
529 """À la réception d'un message sur un channel."""
530 if ignore_event(serv
, ev
):
532 auteur
= irclib
.nm_to_n(ev
.source())
534 message
= ev
.arguments()[0]
536 message
= bot_unicode(message
)
537 except errors
.UnicodeBotError
:
538 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
539 serv
.privmsg(canal
, (u
"%s: %s"% ( auteur
, random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
541 pour_moi
, message
= self
.pourmoi(serv
, message
)
542 if pour_moi
and message
.split()!=[]:
543 cmd
= message
.split()[0].lower()
545 args
= " ".join(message
.split()[1:])
548 if cmd
in [u
"meurs", u
"die", u
"crève"]:
549 if auteur
in self
.overops
:
550 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
553 serv
.privmsg(canal
,(u
"%s: %s"%(auteur
, random
.choice(config
.quit_fail_messages
))).encode("utf8"))
554 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
555 elif cmd
== u
"reload":
556 if auteur
in self
.ops
:
557 self
.execute_something("reload", {"auteur" : auteur
}, place
=canal
, auteur
=auteur
)
558 elif cmd
== u
"crash":
559 if auteur
in self
.overops
:
560 self
.crash(auteur
, canal
)
561 elif cmd
in [u
"part", u
"leave", u
"dégage", u
"va-t-en", u
"tut'tiresailleurs,c'estmesgalets"]:
562 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
563 or auteur
in self
.overops
):
565 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
566 if canal
in self
.chanlist
:
567 self
.chanlist
.remove(canal
)
569 serv
.privmsg(canal
,(u
"%s: %s" % (auteur
, random
.choice(config
.leave_fail_messages
))).encode("utf8"))
570 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
572 elif cmd
== u
"reconnect":
573 if auteur
in self
.ops
:
575 self
.nk
= self
.new_connection_NK(serv
, config
.note_pseudo
,
576 config
.note_password
, "special")[2]
577 except Exception as exc
:
579 log(self
.serveur
, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc
))
581 serv
.privmsg(canal
, "%s: done" % (auteur
))
582 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
584 serv
.privmsg(canal
, "%s: failed" % (auteur
))
585 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
586 for report
in self
.report_bugs_to
:
587 serv
.privmsg(report
, "Connection to NK2015 failed, invalid password ? Server dead ?")
589 serv
.privmsg(canal
, "%s: %s" % (auteur
, random
.choice(config
.pas_programme_pour_tobeir
).encode("utf8")))
590 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
592 elif cmd
in [u
"deviens", u
"pseudo"]:
593 if auteur
in self
.ops
:
596 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
598 if cmd
in [u
"meur", u
"meurt", u
"meurre", u
"meurres"] and not canal
in self
.quiet_channels
:
599 serv
.privmsg(canal
, '%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)' % (auteur
))
600 elif cmd
in [u
"ping"] and not canal
in self
.quiet_channels
:
601 serv
.privmsg(canal
, "%s: pong" % (auteur
))
603 elif cmd
in [u
"solde", u
"!solde", u
"!coca"] or cmd
.startswith("!"):
604 if self
.users
.has(auteur
):
605 idbde
= self
.users
[auteur
].idbde
606 if cmd
in [u
"solde", u
"!solde"]:
607 success
, solde
, pseudo
= nk
.get_solde(self
.nk
, idbde
, serv
, canal
)
609 serv
.privmsg(canal
, "%s: %s (%s)" % (auteur
, float(solde
)/100, pseudo
.encode("utf8")))
610 elif cmd
in [u
"!coca"] or cmd
.startswith("!"):
611 success
= nk
.consomme(self
.nk
, idbde
, message
[1:], serv
, canal
)
612 log(self
.serveur
, canal
, auteur
, message
+ ("[successful]" if success
else "[failed]"))
614 serv
.privmsg(canal
, "%s: Je ne connais pas votre pseudo note." % (auteur
))
615 log(self
.serveur
, canal
, auteur
, message
+ "[unknown]")
616 elif (re
.match("(pain au chocolat|chocolatine)", message
.lower())
617 and not canal
in self
.quiet_channels
):
618 serv
.action(canal
, "sert un pain au chocolat à %s" % (auteur
))
619 elif re
.match("manzana",message
.lower()) and not canal
in self
.quiet_channels
:
620 if auteur
in config
.manzana
:
621 serv
.action(canal
, "sert une bouteille de manzana à %s" % (auteur
))
622 elif auteur
in config
.manzana_bis
:
623 serv
.action(canal
, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur
))
625 serv
.action(canal
, "sert un verre de manzana à %s" % (auteur
))
626 if isit
.is_insult(message
) and not canal
in self
.quiet_channels
:
627 if isit
.is_not_insult(message
):
628 answer
= random
.choice(config
.compliment_answers
)
629 for ligne
in answer
.split("\n"):
630 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
632 answer
= random
.choice(config
.insultes_answers
)
633 for ligne
in answer
.split("\n"):
634 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
635 elif isit
.is_compliment(message
) and not canal
in self
.quiet_channels
:
636 answer
= random
.choice(config
.compliment_answers
)
637 for ligne
in answer
.split("\n"):
638 serv
.privmsg(canal
, "%s: %s" % (auteur
,ligne
.encode("utf8")))
639 gros_match
= isit
.is_gros(message
)
640 if gros_match
and not canal
in self
.quiet_channels
:
641 taille
= get_filesize()
642 answer
= u
"Mais non, je ne suis pas %s, %sKo tout au plus…" % (gros_match
.groups()[0], taille
)
643 serv
.privmsg(canal
, "%s: %s"%(auteur
, answer
.encode("utf8")))
644 if isit
.is_tesla(message
) and not canal
in self
.quiet_channels
:
645 l1
, l2
= config
.tesla_answers
, config
.tesla_actions
646 n1
, n2
= len(l1
), len(l2
)
647 i
= random
.randrange(n1
+ n2
)
649 serv
.action(canal
, l2
[i
- n1
].encode("utf8"))
651 serv
.privmsg(canal
, "%s: %s" % (auteur
, l1
[i
].encode("utf8")))
652 if isit
.is_tamere(message
) and not canal
in self
.quiet_channels
:
653 answer
= random
.choice(config
.tamere_answers
)
654 for ligne
in answer
.split("\n"):
655 serv
.privmsg(canal
, "%s: %s"%(auteur
, ligne
.encode("utf8")))
656 if isit
.is_tag(message
) and not canal
in self
.quiet_channels
:
657 if auteur
in self
.ops
:
658 action
= random
.choice(config
.tag_actions
)
659 serv
.action(canal
, action
.encode("utf8"))
660 self
.quiet_channels
.append(canal
)
662 answer
= random
.choice(config
.tag_answers
)
663 for ligne
in answer
.split("\n"):
664 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
665 if isit
.is_merci(message
):
666 answer
= random
.choice(config
.merci_answers
)
667 for ligne
in answer
.split("\n"):
668 serv
.privmsg(canal
, "%s: %s"%(auteur
, ligne
.encode("utf8")))
669 out
= re
.match(ur
"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$", message
.upper())
670 if re
.match("ma bite dans ton oreille", message
) and not canal
in self
.quiet_channels
:
671 serv
.privmsg(canal
, "%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !" % (auteur
))
672 if out
and not canal
in self
.quiet_channels
:
673 out
= out
.groups()[0]
676 serv
.privmsg(canal
, "%s: %s !" % (auteur
, iout
+ 1))
677 if iout
== 2147483647:
678 serv
.privmsg(canal
, "%s: Ciel, un maxint ! Heureusement que je suis en python…" % (auteur
))
680 if iout
+ 1 > 1000 and random
.randrange(4) == 0:
681 serv
.privmsg(canal
, "%s: Vous savez, moi et les chiffres…" % (auteur
))
683 except Exception as exc
:
685 if re
.match("[A-Y]", out
):
686 alphabet
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
687 serv
.privmsg(canal
, "%s: %s !"%(auteur
, alphabet
[alphabet
.index(out
) + 1]))
689 serv
.privmsg(canal
, "%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?" % (auteur
))
691 serv
.privmsg(canal
, "%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…" % (auteur
))
692 elif re
.match(ur
"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+", out
):
694 return "".join([{u
"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i
]:u
"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i
]
695 for i
in range(20)}[j
]
697 out
= int(translate(out
))
698 serv
.privmsg(canal
,"%s: %s !" % (auteur
, translate(str(out
+ 1)).encode("utf8")))
699 if isit
.is_bonjour(message
) and not canal
in self
.quiet_channels
:
701 answer
= random
.choice(config
.night_answers
)
703 answer
= random
.choice(config
.bonjour_answers
)
705 answer
= random
.choice(config
.bonsoir_answers
)
706 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
707 if isit
.is_bonne_nuit(message
) and not canal
in self
.quiet_channels
:
708 answer
= random
.choice(config
.bonne_nuit_answers
)
709 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
710 if isit
.is_pan(message
) and not canal
in self
.quiet_channels
:
711 serv
.privmsg(canal
, "%s: ce n'est pas sur moi qu'il faut tirer, même si je sais que j'attire l'œil !" % (auteur
))
713 if message
in [u
"!pain au chocolat", u
"!chocolatine"] and not canal
in self
.quiet_channels
:
714 serv
.action(canal
, "sert un pain au chocolat à %s" % (auteur
))
715 if message
in [u
"!manzana"] and not canal
in self
.quiet_channels
:
716 if auteur
in config
.manzana
:
717 serv
.action(canal
, "sert une bouteille de manzana à %s" % (auteur
))
718 elif auteur
in config
.manzana_bis
:
719 serv
.action(canal
, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur
))
721 serv
.action(canal
, "sert un verre de manzana à %s" % (auteur
))
722 if re
.match(config
.buffer_fail_regexp
, message
, flags
=re
.UNICODE
) and not canal
in self
.quiet_channels
:
723 failanswers
= config
.buffer_fail_answers
724 answer
= random
.choice(failanswers
)
725 serv
.privmsg(canal
, ("%s: %s"%(auteur
,answer
)).encode("utf8"))
726 if not canal
in self
.quiet_channels
:
728 if re
.match((u
"^(" + u
"|".join(config
.bonjour_triggers
)
729 + ur
")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
730 ).format(mypseudo
).lower(), message
.strip().lower()):
731 answer
= random
.choice(config
.bonjour_answers
)
732 serv
.privmsg(canal
, answer
.format(auteur
).encode("utf8"))
733 if (isit
.is_perdu(message
) and not canal
in self
.quiet_channels
):
734 # proba de perdre sur trigger :
735 # avant 30min (enfin, config) : 0
736 # ensuite, +25%/30min, linéairement
737 deltat
= time
.time() - self
.last_perdu
738 barre
= (deltat
- config
.time_between_perdu
)/(2*3600.0)
739 if random
.uniform(0, 1) < barre
:
740 serv
.privmsg(canal
, "%s: J'ai perdu !" % (auteur
))
741 self
.last_perdu
= time
.time()
743 def on_action(self
, serv
, ev
):
744 """À la réception d'une action."""
745 if ignore_event(serv
, ev
):
747 action
= ev
.arguments()[0]
748 auteur
= irclib
.nm_to_n(ev
.source())
749 channel
= ev
.target()
751 action
= bot_unicode(action
)
752 except errors
.UnicodeBotError
:
753 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
754 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
758 if isit
.is_bad_action_trigger(action
, mypseudo
) and not channel
in self
.quiet_channels
:
759 l1
, l2
= config
.bad_action_answers
, config
.bad_action_actions
760 n1
, n2
= len(l1
), len(l2
)
761 i
= random
.randrange(n1
+ n2
)
763 serv
.action(channel
, l2
[i
- n1
].format(auteur
).encode("utf8"))
765 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
766 if isit
.is_good_action_trigger(action
, mypseudo
) and not channel
in self
.quiet_channels
:
767 l1
, l2
= config
.good_action_answers
, config
.good_action_actions
768 n1
, n2
= len(l1
), len(l2
)
769 i
= random
.randrange(n1
+ n2
)
771 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
773 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
775 def on_kick(self
, serv
, ev
):
776 """À la réception d'une action."""
777 auteur
= irclib
.nm_to_n(ev
.source())
778 channel
= ev
.target()
779 victime
= ev
.arguments()[0]
780 raison
= ev
.arguments()[1]
781 if victime
== self
.nick
:
782 log(self
.serveur
, ("%s kické de %s par %s (raison : %s)" % (victime
, channel
, auteur
, raison
)).decode("utf-8"))
785 l1
, l2
= config
.kick_answers
, config
.kick_actions
786 n1
, n2
= len(l1
), len(l2
)
787 i
= random
.randrange(n1
+ n2
)
789 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
791 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
794 def start_as_daemon(self
, outfile
):
795 sys
.stderr
= Logger(outfile
)
799 class Logger(object):
800 """Pour écrire ailleurs que sur stdout"""
801 def __init__(self
, filename
="basile.full.log"):
802 self
.filename
= filename
804 def write(self
, message
):
805 f
= open(self
.filename
, "a")
810 """Exécution principale : lecture des paramètres et lancement du bot."""
811 if len(sys
.argv
) == 1:
812 print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
813 print " --outfile sans --no-output ni --daemon n'a aucun effet"
815 serveur
= sys
.argv
[1]
816 if "--daemon" in sys
.argv
:
817 thisfile
= os
.path
.realpath(__file__
)
818 thisdirectory
= thisfile
.rsplit("/", 1)[0]
819 os
.chdir(thisdirectory
)
823 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
827 if "--quiet" in sys
.argv
:
828 config
.debug_stdout
= False
829 serveurs
= {"a♡" : "acoeur.crans.org",
830 "acoeur" : "acoeur.crans.org",
831 "acoeur.crans.org" : "acoeur.crans.org",
832 "irc" : "irc.crans.org",
833 "crans" : "irc.crans.org",
834 "irc.crans.org" : "irc.crans.org"}
835 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
836 outfile
= "/var/log/bots/basile.full.log"
839 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
841 sys
.stdout
= Logger(outfile
)
843 serveur
= serveurs
[serveur
]
845 print "Server Unknown : %s" % (serveur
)
847 basile
= Basile(serveur
,debug
)
848 # Si on reçoit un SIGHUP, on reload la config
849 def sighup_handler(signum
, frame
):
850 basile
.execute_reload(auteur
="SIGHUP")
851 signal
.signal(signal
.SIGHUP
, sighup_handler
)
854 child_pid
= os
.fork()
857 basile
.start_as_daemon(outfile
)
859 # on enregistre le pid de basile
860 pidfile
= "/var/run/bots/basile.pid"
863 if arg
[0].strip('-') in ["pidfile"]:
865 f
= open(pidfile
, "w")
866 f
.write("%s\n" % child_pid
)
871 if __name__
== "__main__":