]> gitweb.pimeys.fr Git - bots/basile.git/blob - basile.py
Meteo : bot mort et enterré depuis longtemps
[bots/basile.git] / basile.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3
4 # Codé par 20-100 (commencé le 23/04/12)
5
6 """ Un bot IRC destiné à s'interfacer avec la Note Kfet 2015 """
7
8 import threading
9 import random
10 import time
11 import json
12 import re
13 import os
14 import signal
15 import sys
16
17 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
18 sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
19 import irclib
20 # On veut réagir sur la partie du whois qui dit qu'un nick est registered
21 irclib.numeric_events['307'] = "whoisregnick"
22 import ircbot
23
24 from commands import getstatusoutput as ex
25
26 #: Config de basile
27 import config
28 #: Module responsable du dialogue avec la NoteKfet2015
29 import nk
30 #: Module de réponse aux questions de base
31 import isit
32 #: Module définissant les erreurs
33 import errors
34 #: Module de gestion des utilisateurs
35 import users
36
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__)
40
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],)
46
47 def get_filesize():
48 """Récupère la taille de ce fichier."""
49 return ex("ls -s %s" % (config.thisfile))[1].split()[0]
50
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)
56 else:
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"))
60 f.close()
61 if config.debug_stdout:
62 print chain.encode("utf-8")
63
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é
71 return False
72 else:
73 if blackit: # Il n'est pas exempté et matche la blacklist
74 return True
75
76
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."""
80 try:
81 return chain.decode("utf8")
82 except UnicodeDecodeError as exc:
83 raise errors.UnicodeBotError
84
85
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)
92 self.debug = debug
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
100 self.last_perdu = 0
101 # On charge la base de données d'utilisateurs
102 self.users = users.UserDB()
103 self.users.load()
104
105 ### Communication NK
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)"""
109 try:
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:
118 serv.privmsg(report,
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:
125 for err in erreurs:
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)
132 if retcode == 0:
133 return (True, info, sock)
134 else:
135 return (False, None, None)
136
137 ### Utilitaires
138 def _getnick(self):
139 """Récuère le nick effectif du bot sur le serveur."""
140 return self.serv.get_nickname()
141 nick = property(_getnick)
142
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))
147 time.sleep(0.3)
148 serv.nick(config.irc_pseudo)
149
150 def pourmoi(self, serv, message):
151 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
152 pseudo = self.nick
153 pseudo = pseudo.decode("utf-8")
154 size = len(pseudo)
155 if message[:size] == pseudo and len(message) > size and message[size] == ":":
156 return (True, message[size+1:].lstrip(" "))
157 else:
158 return (False, message)
159
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))
171
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"))
177
178 def mourir(self):
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"))
182
183 def execute_reload(self, auteur=None):
184 """Recharge la config."""
185 reload(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)
192 return True, None
193 else:
194 return True, u"Config reloaded"
195
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"))
200
201 ACTIONS = {
202 "reload" : execute_reload,
203 }
204
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)
210 if message:
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]"))
215
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])
220
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")
228 if self.debug:
229 self.chanlist = ["#bot"]
230 for c in self.chanlist:
231 log(self.serveur, "JOIN %s" % (c))
232 serv.join(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]
235 if self.nk == None:
236 for report in self.report_bugs_to:
237 serv.privmsg(report, "Connection to NK2015 failed, invalid password ?")
238
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.
242
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,))
254
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,))
261
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,))
274
275 def on_privmsg(self, serv, ev):
276 """À la réception d'un message en privé."""
277 if ignore_event(serv, ev):
278 return
279 message = ev.arguments()[0]
280 auteur = irclib.nm_to_n(ev.source())
281 try:
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"))
286 return
287 message = message.split()
288 cmd = message[0].lower()
289 notunderstood = False
290 if cmd == u"help":
291 op,overop=auteur in self.ops, auteur in self.overops
292 if len(message)==1:
293 helpmsg = config.helpmsg_default
294 if op:
295 helpmsg += config.helpmsg_ops
296 if overop:
297 helpmsg += config.helpmsg_overops
298 else:
299 helpmsgs = config.helpdico.get(message[1].lower(), ["Commande inconnue.", None, None])
300 helpmsg = helpmsgs[0]
301 if op and helpmsgs[1]:
302 if helpmsg:
303 helpmsg += "\n" + helpmsgs[1]
304 else:
305 helpmsg = helpmsgs[1]
306 if overop and helpmsgs[2]:
307 if helpmsg:
308 helpmsg += "\n" + helpmsgs[2]
309 else:
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
320 ).encode("utf8"))
321 else:
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)
326 if success:
327 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
328 self.users.add(auteur, info["idbde"])
329 serv.privmsg(auteur, "Pseudo enregistré.")
330 else:
331 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
332 serv.privmsg(auteur, "Mot de passe invalide. (ou serveur down)")
333 else:
334 serv.privmsg(auteur, "Syntaxe : IDENTIFY [<username> <password>]")
335 elif cmd == u"drop":
336 if len(message) > 1:
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)
341 if success:
342 self.users.drop(idbde)
343 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
344 serv.privmsg(auteur, "Pseudo oublié.")
345 else:
346 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
347 serv.privmsg(auteur, "Mot de passe invalide. (ou serveur down)")
348 else:
349 serv.privmsg(auteur, "Je ne connais pas votre note.")
350 else:
351 serv.privmsg(auteur, "Syntaxe : DROP <password>")
352 elif cmd == u"join":
353 if auteur in self.ops:
354 if len(message) > 1:
355 if message[1] in self.chanlist:
356 serv.privmsg(auteur, (u"Je suis déjà sur %s" % (message[1])).encode("utf-8"))
357 else:
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))
362 else:
363 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
364 else:
365 notunderstood = True
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]")
373 else:
374 serv.privmsg(auteur, "Non, je reste !")
375 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
376 else:
377 serv.privmsg(auteur, "Je ne suis pas sur %s" % (message[1]))
378 else:
379 notunderstood = True
380 elif cmd == u"stay":
381 if auteur in self.overops:
382 if len(message) > 1:
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]))
386 else:
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))
390 else:
391 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
392 else:
393 notunderstood = True
394 elif cmd == u"nostay":
395 if auteur in self.overops:
396 if len(message) > 1:
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))
401 else:
402 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
403 serv.privmsg(auteur, "Je ne stay pas sur %s." % (message[1]))
404 else:
405 notunderstood = True
406 elif cmd == u"die":
407 if auteur in self.overops:
408 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
409 self.mourir()
410 else:
411 notunderstood = True
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")
416 else:
417 notunderstood = True
418 elif cmd == u"reload":
419 if auteur in self.ops:
420 self.execute_something("reload", {"auteur" : auteur}, place=auteur, auteur=auteur)
421 else:
422 notunderstood = True
423 elif cmd == u"reconnect":
424 if auteur in self.ops:
425 try:
426 self.nk = self.new_connection_NK(serv, config.note_pseudo,
427 config.note_password, "special")[2]
428 except Exception as exc:
429 self.nk = None
430 log(self.serveur, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc))
431 if self.nk != None:
432 serv.privmsg(auteur, "%s: done" % (auteur))
433 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
434 else:
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 ?")
439 else:
440 notunderstood = True
441 elif cmd == u"quiet":
442 if auteur in self.ops:
443 if len(message) > 1:
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]")
447 else:
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]")
451 else:
452 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
453 else:
454 notunderstood = True
455 elif cmd == u"noquiet":
456 if auteur in self.ops:
457 if len(message) > 1:
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]")
462 else:
463 serv.privmsg(auteur, "Je ne me la ferme pas sur %s." % (message[1]))
464 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
465 else:
466 notunderstood = True
467 elif cmd == u"say":
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>")
473 else:
474 notunderstood = True
475 elif cmd == u"do":
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>")
481 else:
482 notunderstood = True
483 elif cmd == u"kick":
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>]")
489 else:
490 notunderstood = True
491 elif cmd == u"lost":
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>")
497 else:
498 notunderstood = True
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)
503 if success:
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]"))
506 else:
507 serv.privmsg(auteur, "Je ne connais pas votre note.")
508 elif cmd == u"ops":
509 if auteur in self.overops:
510 serv.privmsg(auteur, " ".join(self.ops))
511 else:
512 notunderstood = True
513 elif cmd == u"overops":
514 if auteur in self.overops:
515 serv.privmsg(auteur, " ".join(self.overops))
516 else:
517 notunderstood = True
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)
521 else:
522 notunderstood = True
523 else:
524 notunderstood = True
525 if notunderstood:
526 serv.privmsg(auteur, "Je n'ai pas compris. Essayez HELP…")
527
528 def on_pubmsg(self, serv, ev):
529 """À la réception d'un message sur un channel."""
530 if ignore_event(serv, ev):
531 return
532 auteur = irclib.nm_to_n(ev.source())
533 canal = ev.target()
534 message = ev.arguments()[0]
535 try:
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"))
540 return
541 pour_moi, message = self.pourmoi(serv, message)
542 if pour_moi and message.split()!=[]:
543 cmd = message.split()[0].lower()
544 try:
545 args = " ".join(message.split()[1:])
546 except:
547 args = ""
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]")
551 self.mourir()
552 else:
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):
564 self.quitter(canal)
565 log(self.serveur, canal, auteur, message + "[successful]")
566 if canal in self.chanlist:
567 self.chanlist.remove(canal)
568 else:
569 serv.privmsg(canal,(u"%s: %s" % (auteur, random.choice(config.leave_fail_messages))).encode("utf8"))
570 log(self.serveur, canal, auteur, message + "[failed]")
571
572 elif cmd == u"reconnect":
573 if auteur in self.ops:
574 try:
575 self.nk = self.new_connection_NK(serv, config.note_pseudo,
576 config.note_password, "special")[2]
577 except Exception as exc:
578 self.nk = None
579 log(self.serveur, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc))
580 if self.nk != None:
581 serv.privmsg(canal, "%s: done" % (auteur))
582 log(self.serveur, canal, auteur, message + "[successful]")
583 else:
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 ?")
588 else:
589 serv.privmsg(canal, "%s: %s" % (auteur, random.choice(config.pas_programme_pour_tobeir).encode("utf8")))
590 log(self.serveur, canal, auteur, message + "[failed]")
591
592 elif cmd in [u"deviens", u"pseudo"]:
593 if auteur in self.ops:
594 become = args
595 serv.nick(become)
596 log(self.serveur, canal, auteur, message + "[successful]")
597
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))
602
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)
608 if success:
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]"))
613 else:
614 serv.privmsg(canal, "%s: Je ne connais pas votre pseudo note." % (auteur))
615 log(self.serveur, canal, auteur, message + "[unknown]")
616 elif (re.match("(pain au chocolat|chocolatine)", message.lower())
617 and not canal in self.quiet_channels):
618 serv.action(canal, "sert un pain au chocolat à %s" % (auteur))
619 elif re.match("manzana",message.lower()) and not canal in self.quiet_channels:
620 if auteur in config.manzana:
621 serv.action(canal, "sert une bouteille de manzana à %s" % (auteur))
622 elif auteur in config.manzana_bis:
623 serv.action(canal, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur))
624 else:
625 serv.action(canal, "sert un verre de manzana à %s" % (auteur))
626 if 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")))
631 else:
632 answer = random.choice(config.insultes_answers)
633 for ligne in answer.split("\n"):
634 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
635 elif 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)
648 if i >= n1:
649 serv.action(canal, l2[i - n1].encode("utf8"))
650 else:
651 serv.privmsg(canal, "%s: %s" % (auteur, l1[i].encode("utf8")))
652 if 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)
661 else:
662 answer = random.choice(config.tag_answers)
663 for ligne in answer.split("\n"):
664 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
665 if 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]
674 try:
675 iout = int(out)
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))
679 return
680 if iout + 1 > 1000 and random.randrange(4) == 0:
681 serv.privmsg(canal, "%s: Vous savez, moi et les chiffres…" % (auteur))
682 return
683 except Exception as exc:
684 pass
685 if re.match("[A-Y]", out):
686 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
687 serv.privmsg(canal, "%s: %s !"%(auteur, alphabet[alphabet.index(out) + 1]))
688 elif out == "Z":
689 serv.privmsg(canal, "%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?" % (auteur))
690 elif out in "[\\":
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):
693 def translate(mess):
694 return "".join([{u"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i]:u"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i]
695 for i in range(20)}[j]
696 for j in mess])
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:
700 if isit.is_night():
701 answer = random.choice(config.night_answers)
702 elif isit.is_day():
703 answer = random.choice(config.bonjour_answers)
704 else:
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))
712 else:
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))
720 else:
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:
727 mypseudo = self.nick
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()
742
743 def on_action(self, serv, ev):
744 """À la réception d'une action."""
745 if ignore_event(serv, ev):
746 return
747 action = ev.arguments()[0]
748 auteur = irclib.nm_to_n(ev.source())
749 channel = ev.target()
750 try:
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"))
755 return
756 mypseudo = self.nick
757
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)
762 if i >= n1:
763 serv.action(channel, l2[i - n1].format(auteur).encode("utf8"))
764 else:
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)
770 if i >= n1:
771 serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
772 else:
773 serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
774
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"))
783 time.sleep(2)
784 serv.join(channel)
785 l1, l2 = config.kick_answers, config.kick_actions
786 n1, n2 = len(l1), len(l2)
787 i = random.randrange(n1 + n2)
788 if i >= n1:
789 serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
790 else:
791 serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
792
793 ### .fork trick
794 def start_as_daemon(self, outfile):
795 sys.stderr = Logger(outfile)
796 self.start()
797
798
799 class Logger(object):
800 """Pour écrire ailleurs que sur stdout"""
801 def __init__(self, filename="basile.full.log"):
802 self.filename = filename
803
804 def write(self, message):
805 f = open(self.filename, "a")
806 f.write(message)
807 f.close()
808
809 def main():
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"
814 exit(1)
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)
820 daemon = True
821 else:
822 daemon = False
823 if "debug" in sys.argv or "--debug" in sys.argv:
824 debug = True
825 else:
826 debug = False
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"
837 for arg in sys.argv:
838 arg = arg.split("=")
839 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
840 outfile = arg[1]
841 sys.stdout = Logger(outfile)
842 try:
843 serveur = serveurs[serveur]
844 except KeyError:
845 print "Server Unknown : %s" % (serveur)
846 exit(404)
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)
852 # Daemonization
853 if daemon:
854 child_pid = os.fork()
855 if child_pid == 0:
856 os.setsid()
857 basile.start_as_daemon(outfile)
858 else:
859 # on enregistre le pid de basile
860 pidfile = "/var/run/bots/basile.pid"
861 for arg in sys.argv:
862 arg = arg.split("=")
863 if arg[0].strip('-') in ["pidfile"]:
864 pidfile = arg[1]
865 f = open(pidfile, "w")
866 f.write("%s\n" % child_pid)
867 f.close()
868 else:
869 basile.start()
870
871 if __name__ == "__main__":
872 main()