]> gitweb.pimeys.fr Git - bots/basile.git/blob - basile.py
Modularité : sortie des is_
[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 import ircbot
21
22 from commands import getstatusoutput as ex
23
24 #: Config de basile
25 import config
26 #: Module responsable du dialogue avec la NoteKfet2015
27 import nk
28 #: Module de réponse aux questions de base
29 import isit
30
31 # la partie qui réfère au fichier lui-même est mieux ici
32 # sinon on réfère la config et pas le fichier lui-même
33 import os
34 config.thisfile = os.path.realpath(__file__)
35
36 def get_config_logfile(serveur):
37 """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
38 serveurs = {"acoeur.crans.org" : "acoeur",
39 "irc.crans.org" : "crans"}
40 return config.logfile_template % (serveurs[serveur],)
41
42 def get_filesize():
43 """Récupère la taille de ce fichier."""
44 return ex("ls -s %s" % (config.thisfile))[1].split()[0]
45
46 def log(serveur, channel, auteur=None, message=None):
47 """Enregistre une ligne de log."""
48 if auteur == message == None:
49 # alors c'est que c'est pas un channel mais juste une ligne de log
50 chain = u"%s %s" % (time.strftime("%F %T"), channel)
51 else:
52 chain = u"%s [%s:%s] %s" % (time.strftime("%F %T"), channel, auteur, message)
53 f = open(get_config_logfile(serveur), "a")
54 f.write((chain + u"\n").encode("utf-8"))
55 f.close()
56 if config.debug_stdout:
57 print chain.encode("utf-8")
58
59 def ignore_event(serv, ev):
60 """Retourne ``True`` si il faut ignorer cet évènement."""
61 for (blackmask, exceptlist) in config.blacklisted_masks:
62 usermask = ev.source()
63 blackit = bool(irclib.mask_matches(usermask, blackmask))
64 exceptit = any([bool(irclib.mask_matches(usermask, exceptmask)) for exceptmask in exceptlist])
65 if exceptit: # Il est exempté
66 return False
67 else:
68 if blackit: # Il n'est pas exempté et matche la blacklist
69 return True
70
71
72
73
74 class UnicodeBotError(Exception):
75 """Erreur levée si quelqu'un fait du caca avec son encodage."""
76 pass
77
78 class CrashError(Exception):
79 """Pour pouvoir faire crasher Basile, parce que ça a l'air drôle."""
80 def __init__(self, msg=""):
81 Exception.__init__(self, msg)
82
83 def bot_unicode(chain):
84 """Essaye de décoder ``chain`` en UTF-8.
85 Lève une py:class:`UnicodeBotError` en cas d'échec."""
86 try:
87 return chain.decode("utf8")
88 except UnicodeDecodeError as exc:
89 raise UnicodeBotError
90
91
92 class Basile(ircbot.SingleServerIRCBot):
93 """Classe principale : définition du bot Basile."""
94 def __init__(self, serveur, debug=False):
95 temporary_pseudo = config.irc_pseudo + str(random.randrange(10000,100000))
96 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
97 temporary_pseudo, "Basile, le bot irc. [Codé par 20-100]", 10)
98 self.debug = debug
99 self.serveur = serveur
100 self.overops = config.overops
101 self.ops = self.overops + config.ops
102 self.report_bugs_to = config.report_bugs_to
103 self.chanlist = config.chanlist
104 self.identities = json.load(open(config.identities_file, "r"))
105 self.stay_channels = config.stay_channels
106 self.quiet_channels = config.quiet_channels
107 self.last_perdu = 0
108
109 ### Communication NK
110 def new_connection_NK(self, serv, username, password, typ="bdd"):
111 """Renvoie (``True``, <une socket ouverte et authentifiée sur la NoteKfet2015>)
112 ou bien (``False``, None)"""
113 try:
114 login_result, sock = nk.login(username, password, typ)
115 info, retcode, errmsg = login_result["msg"], login_result["retcode"], login_result["errmsg"]
116 except nk.NKRefused as exc:
117 for report in self.report_bugs_to:
118 serv.privmsg(report, "Le Serveur NK2015 est down.")
119 return (False, None, None)
120 except nk.NKHelloFailed as exc:
121 for report in self.report_bugs_to:
122 serv.privmsg(report,
123 "La version du protocole utilisée n'est pas supportée par le serveur NK2015.")
124 return (False, None, None)
125 except nk.NKUnknownError as exc:
126 erreurs = ["Une fucking erreur inconnue s'est produite"]
127 erreurs += str(exc).split("\n")
128 for report in self.report_bugs_to:
129 for err in erreurs:
130 serv.privmsg(report, err)
131 return (False, None, None)
132 except Exception as exc:
133 # Exception qui ne vient pas de la communication avec le serveur NK2015
134 log(self.serveur, "Erreur dans new_connection_NK\n" + str(exc))
135 return (False, None, None)
136 if retcode == 0:
137 return (True, info, sock)
138 else:
139 return (False, None, None)
140
141 ### Utilitaires
142 def _getnick(self):
143 """Récuère le nick effectif du bot sur le serveur."""
144 return self.serv.get_nickname()
145 nick = property(_getnick)
146
147 def give_me_my_pseudo(self, serv):
148 """Récupère le pseudo auprès de NickServ."""
149 serv.privmsg("NickServ", "RECOVER %s %s" % (config.irc_pseudo, config.irc_password))
150 serv.privmsg("NickServ", "RELEASE %s %s" % (config.irc_pseudo, config.irc_password))
151 time.sleep(0.3)
152 serv.nick(config.irc_pseudo)
153
154 def pourmoi(self, serv, message):
155 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
156 pseudo = self.nick
157 pseudo = pseudo.decode("utf-8")
158 size = len(pseudo)
159 if message[:size] == pseudo and len(message) > size and message[size] == ":":
160 return (True, message[size+1:].lstrip(" "))
161 else:
162 return (False, message)
163
164 ### Exécution d'actions
165 def lost(self, serv, channel, forced=False):
166 """Réaction à un trigger de perdu.
167 Annonce "J'ai perdu" sur le channel si on n'a pas perdu depuis un certain temps."""
168 if self.last_perdu + config.time_between_perdu < time.time() or forced:
169 if not channel in self.quiet_channels or forced:
170 serv.privmsg(channel, "J'ai perdu !")
171 self.last_perdu=time.time()
172 delay=config.time_between_perdu_trigger
173 delta=config.time_between_perdu_trigger_delta
174 serv.execute_delayed(random.randrange(delay-delta,delay+delta),self.lost,(serv,channel))
175
176 def quitter(self, chan, leave_message=None):
177 """Quitter un channel avec un message customisable."""
178 if leave_message == None:
179 leave_message = random.choice(config.leave_messages)
180 self.serv.part(chan, message=leave_message.encode("utf8"))
181
182 def mourir(self):
183 """Se déconnecter du serveur IRC avec un message customisable."""
184 quit_message = random.choice(config.quit_messages)
185 self.die(msg=quit_message.encode("utf8"))
186
187 def execute_reload(self, auteur=None):
188 """Recharge la config."""
189 reload(config)
190 isit.regexp_compile()
191 if auteur in [None, "SIGHUP"]:
192 towrite = "Config reloaded" + " (SIGHUP received)" * (auteur == "SIGHUP")
193 for to in config.report_bugs_to:
194 self.serv.privmsg(to, towrite)
195 log(self.serveur, towrite)
196 return True, None
197 else:
198 return True, u"Config reloaded"
199
200 def crash(self, chan="nowhere", who="nobody"):
201 """Fait crasher le bot."""
202 where = "en privé" if chan == "priv" else "sur le chan %s" % chan
203 raise CrashError((u"Crash demandé par %s %s" % (who, where)).encode("utf-8"))
204
205 ACTIONS = {
206 "reload" : execute_reload,
207 }
208
209 def execute_something(self, something, params, place=None, auteur=None):
210 """Exécute une action et répond son résultat à ``auteur``
211 sur un chan ou en privé en fonction de ``place``"""
212 action = self.ACTIONS[something]
213 success, message = action(self, **params)
214 if message:
215 if irclib.is_channel(place):
216 message = "%s: %s" % (auteur, message.encode("utf-8"))
217 self.serv.privmsg(place, message)
218 log(self.serveur, place, auteur, something + "%r" % params + ("[successful]" if success else "[failed]"))
219
220 ### Surcharge des events du Bot
221 def on_welcome(self, serv, ev):
222 """À l'arrivée sur le serveur."""
223 self.serv = serv # ça serv ira :)
224 self.give_me_my_pseudo(serv)
225 serv.privmsg("NickServ", "IDENTIFY %s" % (config.irc_password))
226 log(self.serveur, "Connected")
227 if self.debug:
228 self.chanlist = ["#bot"]
229 for c in self.chanlist:
230 log(self.serveur, "JOIN %s" % (c))
231 serv.join(c)
232 # on ouvre la connexion note de Basile, special user
233 self.nk = self.new_connection_NK(serv, config.note_pseudo, config.note_password, "special")[2]
234 if self.nk == None:
235 for report in self.report_bugs_to:
236 serv.privmsg(report, "Connection to NK2015 failed, invalid password ?")
237
238 def on_privmsg(self, serv, ev):
239 """À la réception d'un message en privé."""
240 if ignore_event(serv, ev):
241 return
242 message = ev.arguments()[0]
243 auteur = irclib.nm_to_n(ev.source())
244 try:
245 message = bot_unicode(message)
246 except UnicodeBotError:
247 if config.utf8_trigger:
248 serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8"))
249 return
250 message = message.split()
251 cmd = message[0].lower()
252 notunderstood = False
253 if cmd == u"help":
254 op,overop=auteur in self.ops, auteur in self.overops
255 if len(message)==1:
256 helpmsg = config.helpmsg_default
257 if op:
258 helpmsg += config.helpmsg_ops
259 if overop:
260 helpmsg += config.helpmsg_overops
261 else:
262 helpmsgs = config.helpdico.get(message[1].lower(), ["Commande inconnue.", None, None])
263 helpmsg = helpmsgs[0]
264 if op and helpmsgs[1]:
265 if helpmsg:
266 helpmsg += "\n" + helpmsgs[1]
267 else:
268 helpmsg = helpmsgs[1]
269 if overop and helpmsgs[2]:
270 if helpmsg:
271 helpmsg += "\n" + helpmsgs[2]
272 else:
273 helpmsg = helpmsgs[2]
274 for ligne in helpmsg.split("\n"):
275 serv.privmsg(auteur, ligne.encode("utf-8"))
276 elif cmd == u"identify":
277 if len(message) == 1:
278 if self.identities.has_key(auteur):
279 serv.privmsg(auteur, "Je vous connais sous le pseudo note %s." % (
280 self.identities[auteur]["pseudo"].encode("utf8")))
281 else:
282 serv.privmsg(auteur, "Je ne connais pas votre pseudo note.")
283 elif len(message) >= 3:
284 username, password = message[1], " ".join(message[2:])
285 success, info, _ = self.new_connection_NK(serv, username, password)
286 if success:
287 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
288 serv.privmsg(auteur, "Identité enregistrée.")
289 self.identities[auteur] = info
290 json.dump(self.identities, open(config.identities_file,"w"))
291 else:
292 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
293 serv.privmsg(auteur, "Mot de passe invalide. (ou serveur down)")
294 else:
295 serv.privmsg(auteur, "Syntaxe : IDENTIFY [<username> <password>]")
296 elif cmd == u"drop":
297 if len(message) > 1:
298 if self.identities.has_key(auteur):
299 password = " ".join(message[1:])
300 success, _, _ = self.new_connection_NK(serv, self.identities[auteur], password)
301 if success:
302 del self.identities[auteur]
303 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
304 json.dump(self.identities, open(config.identities_file, "w"))
305 serv.privmsg(auteur, "Identité oubliée.")
306 else:
307 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
308 serv.privmsg(auteur, "Mot de passe invalide. (ou serveur down)")
309 else:
310 serv.privmsg(auteur, "Je ne connais pas ton pseudo note.")
311 else:
312 serv.privmsg(auteur, "Syntaxe : DROP <password>")
313 elif cmd == u"join":
314 if auteur in self.ops:
315 if len(message) > 1:
316 if message[1] in self.chanlist:
317 serv.privmsg(auteur, "Je suis déjà sur %s" % (message[1]))
318 else:
319 serv.join(message[1])
320 self.chanlist.append(message[1])
321 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
322 log(self.serveur, "priv", auteur, " ".join(message))
323 else:
324 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
325 else:
326 notunderstood = True
327 elif cmd == u"leave":
328 if auteur in self.ops and len(message) > 1:
329 if message[1] in self.chanlist:
330 if not (message[1] in self.stay_channels) or auteur in self.overops:
331 self.quitter(message[1].encode("utf-8"), " ".join(message[2:]))
332 self.chanlist.remove(message[1])
333 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
334 else:
335 serv.privmsg(auteur, "Non, je reste !")
336 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
337 else:
338 serv.privmsg(auteur, "Je ne suis pas sur %s" % (message[1]))
339 else:
340 notunderstood = True
341 elif cmd == u"stay":
342 if auteur in self.overops:
343 if len(message) > 1:
344 if message[1] in self.stay_channels:
345 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
346 serv.privmsg(auteur, "Je stay déjà sur %s." % (message[1]))
347 else:
348 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
349 self.stay_channels.append(message[1])
350 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
351 else:
352 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
353 else:
354 notunderstood = True
355 elif cmd == u"nostay":
356 if auteur in self.overops:
357 if len(message) > 1:
358 if message[1] in self.stay_channels:
359 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
360 self.stay_channels.remove(message[1])
361 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
362 else:
363 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
364 serv.privmsg(auteur, "Je ne stay pas sur %s." % (message[1]))
365 else:
366 notunderstood = True
367 elif cmd == u"die":
368 if auteur in self.overops:
369 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
370 self.mourir()
371 else:
372 notunderstood = True
373 elif cmd == u"crash":
374 if auteur in self.overops:
375 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
376 self.crash("priv", auteur)
377 else:
378 notunderstood = True
379 elif cmd == u"reload":
380 if auteur in self.ops:
381 self.execute_something("reload", {"auteur" : auteur}, place=auteur, auteur=auteur)
382 else:
383 notunderstood = True
384 elif cmd == u"reconnect":
385 if auteur in self.ops:
386 try:
387 self.nk = self.new_connection_NK(serv, config.note_pseudo,
388 config.note_password, "special")[2]
389 except Exception as exc:
390 self.nk = None
391 log(self.serveur, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc))
392 if self.nk != None:
393 serv.privmsg(auteur, "%s: done" % (auteur))
394 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
395 else:
396 serv.privmsg(auteur, "%s: failed" % (auteur))
397 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
398 for report in self.report_bugs_to:
399 serv.privmsg(report, "Connection to NK2015 failed, invalid password ? Server dead ?")
400 else:
401 notunderstood = True
402 elif cmd == u"quiet":
403 if auteur in self.ops:
404 if len(message) > 1:
405 if message[1] in self.quiet_channels:
406 serv.privmsg(auteur, "Je me la ferme déjà sur %s" % (message[1]))
407 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
408 else:
409 self.quiet_channels.append(message[1])
410 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
411 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
412 else:
413 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
414 else:
415 notunderstood = True
416 elif cmd == u"noquiet":
417 if auteur in self.ops:
418 if len(message) > 1:
419 if message[1] in self.quiet_channels:
420 self.quiet_channels.remove(message[1])
421 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
422 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
423 else:
424 serv.privmsg(auteur, "Je ne me la ferme pas sur %s." % (message[1]))
425 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
426 else:
427 notunderstood = True
428 elif cmd == u"say":
429 if auteur in self.overops and len(message) > 2:
430 serv.privmsg(message[1].encode("utf-8"), (u" ".join(message[2:])).encode("utf-8"))
431 log(self.serveur, "priv", auteur, " ".join(message))
432 elif len(message) <= 2:
433 serv.privmsg(auteur, "Syntaxe : SAY <channel> <message>")
434 else:
435 notunderstood = True
436 elif cmd == u"do":
437 if auteur in self.overops and len(message) > 2:
438 serv.action(message[1], " ".join(message[2:]))
439 log(self.serveur, "priv", auteur, " ".join(message))
440 elif len(message) <= 2:
441 serv.privmsg(auteur, "Syntaxe : DO <channel> <action>")
442 else:
443 notunderstood = True
444 elif cmd == u"kick":
445 if auteur in self.overops and len(message) > 2:
446 serv.kick(message[1].encode("utf-8"), message[2].encode("utf-8"), " ".join(message[3:]).encode("utf-8"))
447 log(self.serveur, "priv", auteur, " ".join(message))
448 elif len(message) <= 2:
449 serv.privmsg(auteur, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
450 else:
451 notunderstood = True
452 elif cmd == u"lost":
453 if auteur in self.ops and len(message) > 1:
454 serv.privmsg(message[1], "J'ai perdu !")
455 log(self.serveur, "priv", auteur, " ".join(message))
456 elif len(message) <= 1:
457 serv.privmsg(auteur, "Syntaxe : LOST <channel>")
458 else:
459 notunderstood = True
460 elif cmd == u"solde":
461 if len(message) == 1:
462 if self.identities.has_key(auteur):
463 success, solde, pseudo = nk.get_solde(self.nk, self.identities[auteur]["idbde"], serv, auteur)
464 if success:
465 serv.privmsg(auteur, "%.2f (%s)" % (solde/100.0, pseudo.encode("utf8")))
466 log(self.serveur, "priv", auteur, " ".join(message) + ("[successful]" if success else "[failed]"))
467 else:
468 serv.privmsg(canal, "Je ne connais pas ton pseudo note.")
469 elif cmd == u"ops":
470 if auteur in self.overops:
471 serv.privmsg(auteur, " ".join(self.ops))
472 else:
473 notunderstood = True
474 elif cmd == u"overops":
475 if auteur in self.overops:
476 serv.privmsg(auteur, " ".join(self.overops))
477 else:
478 notunderstood = True
479 else:
480 notunderstood = True
481 if notunderstood:
482 serv.privmsg(auteur, "Je n'ai pas compris. Essayez HELP…")
483
484 def on_pubmsg(self, serv, ev):
485 """À la réception d'un message sur un channel."""
486 if ignore_event(serv, ev):
487 return
488 auteur = irclib.nm_to_n(ev.source())
489 canal = ev.target()
490 message = ev.arguments()[0]
491 try:
492 message = bot_unicode(message)
493 except UnicodeBotError:
494 if config.utf8_trigger and not canal in self.quiet_channels:
495 serv.privmsg(canal, (u"%s: %s"% ( auteur, random.choice(config.utf8_fail_answers))).encode("utf8"))
496 return
497 pour_moi, message = self.pourmoi(serv, message)
498 if pour_moi and message.split()!=[]:
499 cmd = message.split()[0].lower()
500 try:
501 args = " ".join(message.split()[1:])
502 except:
503 args = ""
504 if cmd in [u"meurs", u"die", u"crève"]:
505 if auteur in self.overops:
506 log(self.serveur, canal, auteur, message + "[successful]")
507 self.mourir()
508 else:
509 serv.privmsg(canal,(u"%s: %s"%(auteur, random.choice(config.quit_fail_messages))).encode("utf8"))
510 log(self.serveur, canal, auteur, message + "[failed]")
511 elif cmd == u"reload":
512 if auteur in self.ops:
513 self.execute_something("reload", {"auteur" : auteur}, place=canal, auteur=auteur)
514 elif cmd == u"crash":
515 if auteur in self.overops:
516 self.crash(auteur, message)
517 elif cmd in [u"part", u"leave", u"dégage", u"va-t-en", u"tut'tiresailleurs,c'estmesgalets"]:
518 if auteur in self.ops and (not (canal in self.stay_channels)
519 or auteur in self.overops):
520 self.quitter(canal)
521 log(self.serveur, canal, auteur, message + "[successful]")
522 if canal in self.chanlist:
523 self.chanlist.remove(canal)
524 else:
525 serv.privmsg(canal,(u"%s: %s" % (auteur, random.choice(config.leave_fail_messages))).encode("utf8"))
526 log(self.serveur, canal, auteur, message + "[failed]")
527
528 elif cmd == u"reconnect":
529 if auteur in self.ops:
530 try:
531 self.nk = self.new_connection_NK(serv, config.note_pseudo,
532 config.note_password, "special")[2]
533 except Exception as exc:
534 self.nk = None
535 log(self.serveur, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc))
536 if self.nk != None:
537 serv.privmsg(canal, "%s: done" % (auteur))
538 log(self.serveur, canal, auteur, message + "[successful]")
539 else:
540 serv.privmsg(canal, "%s: failed" % (auteur))
541 log(self.serveur, canal, auteur, message + "[failed]")
542 for report in self.report_bugs_to:
543 serv.privmsg(report, "Connection to NK2015 failed, invalid password ? Server dead ?")
544 else:
545 serv.privmsg(canal, "%s: %s" % (auteur, random.choice(config.pas_programme_pour_tobeir).encode("utf8")))
546 log(self.serveur, canal, auteur, message + "[failed]")
547
548 elif cmd in [u"deviens", u"pseudo"]:
549 if auteur in self.ops:
550 become = args
551 serv.nick(become)
552 log(self.serveur, canal, auteur, message + "[successful]")
553
554 if cmd in [u"meur", u"meurt", u"meurre", u"meurres"] and not canal in self.quiet_channels:
555 serv.privmsg(canal, '%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)' % (auteur))
556 elif cmd in [u"ping"] and not canal in self.quiet_channels:
557 serv.privmsg(canal, "%s: pong" % (auteur))
558
559 elif cmd in [u"solde", u"!solde", u"!coca"] or cmd.startswith("!"):
560 if self.identities.has_key(auteur):
561 idbde = self.identities[auteur]["idbde"]
562 if cmd in [u"solde", u"!solde"]:
563 success, solde, pseudo = nk.get_solde(self.nk, self.identities[auteur]["idbde"], serv, canal)
564 if success:
565 serv.privmsg(canal, "%s: %s (%s)" % (auteur, float(solde)/100, pseudo.encode("utf8")))
566 elif cmd in [u"!coca"] or cmd.startswith("!"):
567 success = nk.consomme(self.nk, self.identities[auteur]["idbde"], message[1:], serv, canal)
568 log(self.serveur, canal, auteur, message + ("[successful]" if success else "[failed]"))
569 else:
570 serv.privmsg(canal, "%s: Je ne connais pas votre pseudo note." % (auteur))
571 log(self.serveur, canal, auteur, message + "[unknown]")
572 elif (re.match("(pain au chocolat|chocolatine)", message.lower())
573 and not canal in self.quiet_channels):
574 serv.action(canal, "sert un pain au chocolat à %s" % (auteur))
575 elif re.match("manzana",message.lower()) and not canal in self.quiet_channels:
576 if auteur in config.manzana:
577 serv.action(canal, "sert une bouteille de manzana à %s" % (auteur))
578 elif auteur in config.manzana_bis:
579 serv.action(canal, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur))
580 else:
581 serv.action(canal, "sert un verre de manzana à %s" % (auteur))
582 if isit.is_insult(message) and not canal in self.quiet_channels:
583 if isit.is_not_insult(message):
584 answer = random.choice(config.compliment_answers)
585 for ligne in answer.split("\n"):
586 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
587 else:
588 answer = random.choice(config.insultes_answers)
589 for ligne in answer.split("\n"):
590 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
591 elif isit.is_compliment(message) and not canal in self.quiet_channels:
592 answer = random.choice(config.compliment_answers)
593 for ligne in answer.split("\n"):
594 serv.privmsg(canal, "%s: %s" % (auteur,ligne.encode("utf8")))
595 gros_match = isit.is_gros(message)
596 if gros_match and not canal in self.quiet_channels:
597 taille = get_filesize()
598 answer = u"Mais non, je ne suis pas %s, %sKo tout au plus…" % (gros_match.groups()[0], taille)
599 serv.privmsg(canal, "%s: %s"%(auteur, answer.encode("utf8")))
600 if isit.is_tesla(message) and not canal in self.quiet_channels:
601 l1, l2 = config.tesla_answers, config.tesla_actions
602 n1, n2 = len(l1), len(l2)
603 i = random.randrange(n1 + n2)
604 if i >= n1:
605 serv.action(canal, l2[i - n1].encode("utf8"))
606 else:
607 serv.privmsg(canal, "%s: %s" % (auteur, l1[i].encode("utf8")))
608 if isit.is_tamere(message) and not canal in self.quiet_channels:
609 answer = random.choice(config.tamere_answers)
610 for ligne in answer.split("\n"):
611 serv.privmsg(canal, "%s: %s"%(auteur, ligne.encode("utf8")))
612 if isit.is_tag(message) and not canal in self.quiet_channels:
613 if auteur in self.ops:
614 action = random.choice(config.tag_actions)
615 serv.action(canal, action.encode("utf8"))
616 self.quiet_channels.append(canal)
617 else:
618 answer = random.choice(config.tag_answers)
619 for ligne in answer.split("\n"):
620 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
621 if isit.is_merci(message):
622 answer = random.choice(config.merci_answers)
623 for ligne in answer.split("\n"):
624 serv.privmsg(canal, "%s: %s"%(auteur, ligne.encode("utf8")))
625 out = re.match(ur"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$", message.upper())
626 if re.match("ma bite dans ton oreille", message) and not canal in self.quiet_channels:
627 serv.privmsg(canal, "%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !" % (auteur))
628 if out and not canal in self.quiet_channels:
629 out = out.groups()[0]
630 try:
631 iout = int(out)
632 serv.privmsg(canal, "%s: %s !" % (auteur, iout + 1))
633 if iout == 2147483647:
634 serv.privmsg(canal, "%s: Ciel, un maxint ! Heureusement que je suis en python…" % (auteur))
635 return
636 if iout + 1 > 1000 and random.randrange(4) == 0:
637 serv.privmsg(canal, "%s: Vous savez, moi et les chiffres…" % (auteur))
638 return
639 except Exception as exc:
640 pass
641 if re.match("[A-Y]", out):
642 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
643 serv.privmsg(canal, "%s: %s !"%(auteur, alphabet[alphabet.index(out) + 1]))
644 elif out == "Z":
645 serv.privmsg(canal, "%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?" % (auteur))
646 elif out in "[\\":
647 serv.privmsg(canal, "%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…" % (auteur))
648 elif re.match(ur"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+", out):
649 def translate(mess):
650 return "".join([{u"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i]:u"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i]
651 for i in range(20)}[j]
652 for j in mess])
653 out = int(translate(out))
654 serv.privmsg(canal,"%s: %s !" % (auteur, translate(str(out + 1)).encode("utf8")))
655 if isit.is_bonjour(message) and not canal in self.quiet_channels:
656 if isit.is_night():
657 answer = random.choice(config.night_answers)
658 elif isit.is_day():
659 answer = random.choice(config.bonjour_answers)
660 else:
661 answer = random.choice(config.bonsoir_answers)
662 serv.privmsg(canal, answer.format(auteur).encode("utf8"))
663 if isit.is_bonne_nuit(message) and not canal in self.quiet_channels:
664 answer = random.choice(config.bonne_nuit_answers)
665 serv.privmsg(canal, answer.format(auteur).encode("utf8"))
666 if isit.is_pan(message) and not canal in self.quiet_channels:
667 serv.privmsg(canal, "%s: ce n'est pas sur moi qu'il faut tirer, même si je sais que j'attire l'œil !" % (auteur))
668 else:
669 if message in [u"!pain au chocolat", u"!chocolatine"] and not canal in self.quiet_channels:
670 serv.action(canal, "sert un pain au chocolat à %s" % (auteur))
671 if message in [u"!manzana"] and not canal in self.quiet_channels:
672 if auteur in config.manzana:
673 serv.action(canal, "sert une bouteille de manzana à %s" % (auteur))
674 elif auteur in config.manzana_bis:
675 serv.action(canal, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur))
676 else:
677 serv.action(canal, "sert un verre de manzana à %s" % (auteur))
678 if re.match(u'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$', message) and not canal in self.quiet_channels:
679 failanswers = config.buffer_fail_answers
680 answer = random.choice(failanswers)
681 serv.privmsg(canal, ("%s: %s"%(auteur,answer)).encode("utf8"))
682 if not canal in self.quiet_channels:
683 mypseudo = self.nick
684 if re.match((u"^(" + u"|".join(config.bonjour_triggers)
685 + ur")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
686 ).format(mypseudo).lower(), message.strip().lower()):
687 answer = random.choice(config.bonjour_answers)
688 serv.privmsg(canal, answer.format(auteur).encode("utf8"))
689 if (isit.is_perdu(message) and not canal in self.quiet_channels):
690 # proba de perdre sur trigger :
691 # avant 30min (enfin, config) : 0
692 # ensuite, +25%/30min, linéairement
693 deltat = time.time() - self.last_perdu
694 barre = (deltat - config.time_between_perdu)/(2*3600.0)
695 if random.uniform(0, 1) < barre:
696 serv.privmsg(canal, "%s: J'ai perdu !" % (auteur))
697 self.last_perdu = time.time()
698
699 def on_action(self, serv, ev):
700 """À la réception d'une action."""
701 if ignore_event(serv, ev):
702 return
703 action = ev.arguments()[0]
704 auteur = irclib.nm_to_n(ev.source())
705 channel = ev.target()
706 try:
707 action = bot_unicode(action)
708 except UnicodeBotError:
709 if config.utf8_trigger and not channel in self.quiet_channels:
710 serv.privmsg(channel, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf8"))
711 return
712 mypseudo = self.nick
713
714 if isit.is_bad_action_trigger(action, mypseudo) and not channel in self.quiet_channels:
715 l1, l2 = config.bad_action_answers, config.bad_action_actions
716 n1, n2 = len(l1), len(l2)
717 i = random.randrange(n1 + n2)
718 if i >= n1:
719 serv.action(channel, l2[i - n1].format(auteur).encode("utf8"))
720 else:
721 serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
722 if isit.is_good_action_trigger(action, mypseudo) and not channel in self.quiet_channels:
723 l1, l2 = config.good_action_answers, config.good_action_actions
724 n1, n2 = len(l1), len(l2)
725 i = random.randrange(n1 + n2)
726 if i >= n1:
727 serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
728 else:
729 serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
730
731 def on_kick(self, serv, ev):
732 """À la réception d'une action."""
733 auteur = irclib.nm_to_n(ev.source())
734 channel = ev.target()
735 victime = ev.arguments()[0]
736 raison = ev.arguments()[1]
737 if victime == self.nick:
738 log(self.serveur, u"%s kické de %s par %s (raison : %s)" % (victime, channel.decode("utf-8"), auteur, raison))
739 time.sleep(2)
740 serv.join(channel)
741 l1, l2 = config.kick_answers, config.kick_actions
742 n1, n2 = len(l1), len(l2)
743 i = random.randrange(n1 + n2)
744 if i >= n1:
745 serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
746 else:
747 serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
748
749 ### .fork trick
750 def start_as_daemon(self, outfile):
751 sys.stderr = Logger(outfile)
752 self.start()
753
754
755 class Logger(object):
756 """Pour écrire ailleurs que sur stdout"""
757 def __init__(self, filename="basile.full.log"):
758 self.filename = filename
759
760 def write(self, message):
761 f = open(self.filename, "a")
762 f.write(message)
763 f.close()
764
765 def main():
766 """Exécution principal : lecture des paramètres et lancement du bot."""
767 if len(sys.argv) == 1:
768 print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
769 print " --outfile sans --no-output ni --daemon n'a aucun effet"
770 exit(1)
771 serveur = sys.argv[1]
772 if "--daemon" in sys.argv:
773 thisfile = os.path.realpath(__file__)
774 thisdirectory = thisfile.rsplit("/", 1)[0]
775 os.chdir(thisdirectory)
776 daemon = True
777 else:
778 daemon = False
779 if "debug" in sys.argv or "--debug" in sys.argv:
780 debug = True
781 else:
782 debug = False
783 if "--quiet" in sys.argv:
784 config.debug_stdout = False
785 serveurs = {"a♡" : "acoeur.crans.org",
786 "acoeur" : "acoeur.crans.org",
787 "acoeur.crans.org" : "acoeur.crans.org",
788 "irc" : "irc.crans.org",
789 "crans" : "irc.crans.org",
790 "irc.crans.org" : "irc.crans.org"}
791 if "--no-output" in sys.argv or "--daemon" in sys.argv:
792 outfile = "/var/log/bots/basile.full.log"
793 for arg in sys.argv:
794 arg = arg.split("=")
795 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
796 outfile = arg[1]
797 sys.stdout = Logger(outfile)
798 try:
799 serveur = serveurs[serveur]
800 except KeyError:
801 print "Server Unknown : %s" % (serveur)
802 exit(404)
803 basile = Basile(serveur,debug)
804 # Si on reçoit un SIGHUP, on reload la config
805 def sighup_handler(signum, frame):
806 basile.execute_reload(auteur="SIGHUP")
807 signal.signal(signal.SIGHUP, sighup_handler)
808 # Daemonization
809 if daemon:
810 child_pid = os.fork()
811 if child_pid == 0:
812 os.setsid()
813 basile.start_as_daemon(outfile)
814 else:
815 # on enregistre le pid de basile
816 pidfile = "/var/run/bots/basile.pid"
817 for arg in sys.argv:
818 arg = arg.split("=")
819 if arg[0].strip('-') in ["pidfile"]:
820 pidfile = arg[1]
821 f = open(pidfile, "w")
822 f.write("%s\n" % child_pid)
823 f.close()
824 else:
825 basile.start()
826
827 if __name__ == "__main__":
828 main()