]>
gitweb.pimeys.fr Git - bots/helixbot.git/blob - helixbot.py
2 # -*- encoding: utf-8 -*-
6 Un bot IRC qui donne les réponses de Helix the fossil.
16 from commands
import getstatusoutput
as ex
18 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
19 sys
.path
.insert(0, "/home/vincent/scripts/python-myirclib")
22 # on récupère la config
26 def get_config_logfile(serveur
):
27 """Renvoie le nom du fichier de log en fonction du serveur."""
28 serveurs
= {"acoeur.crans.org" : "acoeur", "irc.crans.org" : "crans"}
29 return config
.logfile_template
% (serveurs
[serveur
])
31 def log(serveur
, channel
, auteur
=None, message
=None):
33 print "\n", serveur
, channel
, auteur
, message
, get_config_logfile(serveur
), os
.getenv("USER")
35 f
= open(get_config_logfile(serveur
), "a")
36 if auteur
== message
== None:
37 # alors c'est que c'est pas un channel mais juste une ligne de log
38 chain
= "%s %s" % (time
.strftime("%F %T"), channel
)
40 chain
= "%s [%s:%s] %s" % (time
.strftime("%F %T"), channel
, auteur
, message
)
42 if config
.debug_stdout
:
46 def is_something(chain
, matches
, avant
=u
".*(?:^| )", apres
=u
"(?:$|\.| |,|;).*", case_sensitive
=False, debug
=False):
48 chain
= unicode(chain
, "utf8")
50 chain
= unicode(chain
, "utf8").lower()
51 allmatches
= "("+"|".join(matches
)+")"
52 reg
= (avant
+allmatches
+apres
).lower()
53 o
= re
.match(reg
, chain
)
57 return is_something(chain
, config
.tag_triggers
)
59 return is_something(chain
, config
.tesla_triggers
, avant
=u
"^", apres
=u
"$", debug
=True)
62 class UnicodeBotError(Exception):
64 def bot_unicode(chain
):
66 unicode(chain
, "utf8")
67 except UnicodeDecodeError as exc
:
70 class HelixBot(ircbot
.SingleServerIRCBot
):
71 def __init__(self
, serveur
, debug
=False):
72 temporary_pseudo
= config
.irc_pseudo
+ str(random
.randrange(10000, 100000))
73 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
74 temporary_pseudo
, config
.ircname
, 10)
76 self
.serveur
= serveur
77 self
.overops
= config
.overops
78 self
.ops
= self
.overops
+config
.ops
79 self
.chanlist
= config
.chanlist
80 self
.stay_channels
= config
.stay_channels
81 self
.quiet_channels
= config
.quiet_channels
83 def give_me_my_pseudo(self
, serv
):
84 serv
.privmsg("NickServ", "RECOVER %s %s" % (config
.irc_pseudo
, config
.irc_password
))
85 serv
.privmsg("NickServ", "RELEASE %s %s" % (config
.irc_pseudo
, config
.irc_password
))
87 serv
.nick(config
.irc_pseudo
)
89 def on_welcome(self
, serv
, ev
):
90 self
.serv
= serv
# ça serv ira :)
91 self
.give_me_my_pseudo(serv
)
92 serv
.privmsg("NickServ", "identify %s" % (config
.irc_password
))
93 log(self
.serveur
, "Connected")
95 self
.chanlist
= ["#bot"]
96 for c
in self
.chanlist
:
97 log(self
.serveur
, "JOIN %s" % (c
))
100 def pourmoi(self
, serv
, message
):
101 """renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
104 if message
[:size
] == pseudo
and len(message
)>size
and message
[size
] == ":":
105 return (True, message
[size
+1:].lstrip(" "))
107 return (False, message
)
109 def on_privmsg(self
, serv
, ev
):
110 message
= ev
.arguments()[0]
111 auteur
= irclib
.nm_to_n(ev
.source())
114 except UnicodeBotError
:
115 if config
.utf8_trigger
:
116 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
118 message
= message
.split()
119 cmd
= message
[0].lower()
120 notunderstood
= False
122 helpdico
= {"help":["""HELP <commande>
123 Affiche de l'aide sur la commande""", None, None],
124 "join": [None, """JOIN <channel>
125 Me fait rejoindre le channel""", None],
126 "leave": [None, """LEAVE <channel>
127 Me fait quitter le channel (sauf s'il est dans ma stay_list).""", None],
128 "quiet": [None, """QUIET <channel>
129 Me rend silencieux sur le channel.""", None],
130 "noquiet": [None, """NOQUIET <channel>
131 Me rend la parole sur le channel.""", None],
132 "say": [None, None, """SAY <channel> <message>
133 Me fait parler sur le channel."""],
134 "do": [None, None, """DO <channel> <action>
135 Me fait faitre une action (/me) sur le channel."""],
136 "stay": [None, None, """STAY <channel>
137 Ajoute le channel à ma stay_list."""],
138 "nostay": [None, None, """NOSTAY <channel>
139 Retire le channel de ma stay_list."""],
140 "ops": [None, None, """OPS
141 Affiche la liste des ops."""],
142 "overops": [None, None, """OVEROPS
143 Affiche la liste des overops."""],
144 "kick": [None, None, """KICK <channel> <pseudo> [<raison>]
145 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
146 "die": [None, None, """DIE
147 Me déconnecte du serveur IRC."""]
149 helpmsg_default
= "Liste des commandes disponibles :\nHELP"
150 helpmsg_ops
= " JOIN LEAVE QUIET NOQUIET RECONNECT"
151 helpmsg_overops
= " SAY DO STAY NOSTAY OPS OVEROPS KICK DIE"
152 op
, overop
= auteur
in self
.ops
, auteur
in self
.overops
153 if len(message
) == 1:
154 helpmsg
= helpmsg_default
156 helpmsg
+= helpmsg_ops
158 helpmsg
+= helpmsg_overops
160 helpmsgs
= helpdico
.get(message
[1].lower(), ["Commande inconnue.", None, None])
161 helpmsg
= helpmsgs
[0]
162 if op
and helpmsgs
[1]:
164 helpmsg
+= "\n"+helpmsgs
[1]
166 helpmsg
= helpmsgs
[1]
167 if overop
and helpmsgs
[2]:
169 helpmsg
+= "\n"+helpmsgs
[2]
171 helpmsg
= helpmsgs
[2]
172 for ligne
in helpmsg
.split("\n"):
173 serv
.privmsg(auteur
, ligne
)
175 if auteur
in self
.ops
:
177 if message
[1] in self
.chanlist
:
178 serv
.privmsg(auteur
, "Je suis déjà sur %s" % (message
[1]))
180 serv
.join(message
[1])
181 self
.chanlist
.append(message
[1])
182 serv
.privmsg(auteur
, "Channels : "+" ".join(self
.chanlist
))
183 log(self
.serveur
, "priv", auteur
, " ".join(message
))
185 serv
.privmsg(auteur
, "Channels : "+" ".join(self
.chanlist
))
189 if auteur
in self
.ops
and len(message
)>1:
190 if message
[1] in self
.chanlist
:
191 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
192 self
.quitter(message
[1], " ".join(message
[2:]))
193 self
.chanlist
.remove(message
[1])
194 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[successful]")
196 serv
.privmsg(auteur
, "Non, je reste !")
197 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[failed]")
199 serv
.privmsg(auteur
, "Je ne suis pas sur %s" % (message
[1]))
203 if auteur
in self
.overops
:
205 if message
[1] in self
.stay_channels
:
206 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[failed]")
207 serv
.privmsg(auteur
, "Je stay déjà sur %s." % (message
[1]))
209 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[successful]")
210 self
.stay_channels
.append(message
[1])
211 serv
.privmsg(auteur
, "Stay channels : "+" ".join(self
.stay_channels
))
213 serv
.privmsg(auteur
, "Stay channels : "+" ".join(self
.stay_channels
))
216 elif cmd
== "nostay":
217 if auteur
in self
.overops
:
219 if message
[1] in self
.stay_channels
:
220 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[successful]")
221 self
.stay_channels
.remove(message
[1])
222 serv
.privmsg(auteur
, "Stay channels : "+" ".join(self
.stay_channels
))
224 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[failed]")
225 serv
.privmsg(auteur
, "Je ne stay pas sur %s." % (message
[1]))
230 if auteur
in self
.overops
:
231 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[successful]")
236 if auteur
in self
.ops
:
238 if message
[1] in self
.quiet_channels
:
239 serv
.privmsg(auteur
, "Je me la ferme déjà sur %s" % (message
[1]))
240 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[failed]")
242 self
.quiet_channels
.append(message
[1])
243 serv
.privmsg(auteur
, "Quiet channels : "+" ".join(self
.quiet_channels
))
244 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[successful]")
246 serv
.privmsg(auteur
, "Quiet channels : "+" ".join(self
.quiet_channels
))
249 elif cmd
== "noquiet":
250 if auteur
in self
.ops
:
252 if message
[1] in self
.quiet_channels
:
253 self
.quiet_channels
.remove(message
[1])
254 serv
.privmsg(auteur
, "Quiet channels : "+" ".join(self
.quiet_channels
))
255 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[successful]")
257 serv
.privmsg(auteur
, "Je ne me la ferme pas sur %s." % (message
[1]))
258 log(self
.serveur
, "priv", auteur
, " ".join(message
)+"[failed]")
262 if auteur
in self
.overops
and len(message
)>2:
263 serv
.privmsg(message
[1], " ".join(message
[2:]))
264 log(self
.serveur
, "priv", auteur
, " ".join(message
))
265 elif len(message
) <= 2:
266 serv
.privmsg(auteur
, "Syntaxe : SAY <channel> <message>")
270 if auteur
in self
.overops
and len(message
)>2:
271 serv
.action(message
[1], " ".join(message
[2:]))
272 log(self
.serveur
, "priv", auteur
, " ".join(message
))
273 elif len(message
) <= 2:
274 serv
.privmsg(auteur
, "Syntaxe : DO <channel> <action>")
278 if auteur
in self
.overops
and len(message
)>2:
279 serv
.kick(message
[1], message
[2], " ".join(message
[3:]))
280 log(self
.serveur
, "priv", auteur
, " ".join(message
))
281 elif len(message
) <= 2:
282 serv
.privmsg(auteur
, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
286 if auteur
in self
.overops
:
287 serv
.privmsg(auteur
, " ".join(self
.ops
))
290 elif cmd
== "overops":
291 if auteur
in self
.overops
:
292 serv
.privmsg(auteur
, " ".join(self
.overops
))
298 serv
.privmsg(auteur
, "Je n'ai pas compris. Essayez HELP…")
300 def on_pubmsg(self
, serv
, ev
):
301 auteur
= irclib
.nm_to_n(ev
.source())
303 message
= ev
.arguments()[0]
306 except UnicodeBotError
:
307 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
308 serv
.privmsg(canal
, (u
"%s: %s" % (auteur
, random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
310 pour_moi
, message
= self
.pourmoi(serv
, message
)
311 if pour_moi
and message
.split() != []:
312 cmd
= message
.split()[0].lower()
314 args
= " ".join(message
.split()[1:])
317 if cmd
in ["meurs", "die", "crève"]:
318 if auteur
in self
.overops
:
319 log(self
.serveur
, canal
, auteur
, message
+"[successful]")
322 serv
.privmsg(canal
, ("%s: %s" % (auteur
, random
.choice(config
.quit_fail_messages
))).encode("utf8"))
323 log(self
.serveur
, canal
, auteur
, message
+"[failed]")
325 elif cmd
in ["part", "leave", "dégage", "va-t-en", "tut'tiresailleurs, c'estmesgalets"]:
326 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
327 or auteur
in self
.overops
):
329 log(self
.serveur
, canal
, auteur
, message
+"[successful]")
330 if canal
in self
.chanlist
:
331 self
.chanlist
.remove(canal
)
333 serv
.privmsg(canal
, ("%s: %s" % (auteur
, random
.choice(config
.leave_fail_messages
))).encode("utf8"))
334 log(self
.serveur
, canal
, auteur
, message
+"[failed]")
336 elif cmd
in ["deviens", "pseudo"]:
337 if auteur
in self
.ops
:
340 log(self
.serveur
, canal
, auteur
, message
+"[successful]")
342 elif cmd
in ["ping"] and not canal
in self
.quiet_channels
:
343 serv
.privmsg(canal
, "%s: pong" % (auteur
))
344 if is_tag(message
) and not canal
in self
.quiet_channels
:
345 if auteur
in self
.ops
:
346 action
= random
.choice(config
.tag_actions
)
347 serv
.action(canal
, action
.encode("utf8"))
348 self
.quiet_channels
.append(canal
)
350 answer
= random
.choice(config
.tag_answers
)
351 for ligne
in answer
.split("\n"):
352 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
353 elif (any([re
.match(trig
, message
) for trig
in config
.fossil_triggers
]) and
354 not canal
in self
.quiet_channels
):
355 answer
= random
.choice(config
.fossil_answers
)
356 serv
.privmsg(canal
, "%s: %s" % (auteur
, answer
))
360 def on_action(self
, serv
, ev
):
361 action
= ev
.arguments()[0]
362 auteur
= irclib
.nm_to_n(ev
.source())
363 channel
= ev
.target()
366 except UnicodeBotError
:
367 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
368 serv
.privmsg(channel
, (u
"%s: %s" % (auteur
, random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
373 def on_kick(self
, serv
, ev
):
374 auteur
= irclib
.nm_to_n(ev
.source())
375 channel
= ev
.target()
376 victime
= ev
.arguments()[0]
377 raison
= ev
.arguments()[1]
378 if victime
== self
.nick
:
379 log(self
.serveur
, "%s kické de %s par %s (raison : %s)" % (victime
, channel
, auteur
, raison
))
382 return # pas de réaction verbale au kick
383 l1
, l2
= config
.kick_answers
, config
.kick_actions
384 n1
, n2
= len(l1
), len(l2
)
385 i
= random
.randrange(n1
+n2
)
387 serv
.action(channel
, l2
[i
-n1
].format(auteur
).encode("utf8"))
389 serv
.privmsg(channel
, l1
[i
].format(auteur
).encode("utf8"))
391 def quitter(self
, chan
, leave_message
=None):
392 if leave_message
== None:
393 leave_message
= random
.choice(config
.leave_messages
)
394 self
.serv
.part(chan
, message
=leave_message
.encode("utf8"))
397 quit_message
= random
.choice(config
.quit_messages
)
398 self
.die(msg
=quit_message
.encode("utf8"))
401 return self
.serv
.get_nickname()
402 nick
= property(_getnick
)
405 def start_as_daemon(self
, outfile
):
406 sys
.stderr
= Logger(outfile
)
409 class Logger(object):
410 """Pour écrire ailleurs que sur stdout"""
411 def __init__(self
, filename
="basile.full.log"):
412 self
.filename
= filename
414 def write(self
, message
):
415 f
= open(self
.filename
, "a")
420 """Exécution principale : lecture des paramètres et lancement du bot."""
421 if len(sys
.argv
) == 1:
422 print "Usage : helixbot.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
423 print " --outfile sans --no-output ni --daemon n'a aucun effet"
425 serveur
= sys
.argv
[1]
426 if "--daemon" in sys
.argv
:
427 thisfile
= os
.path
.realpath(__file__
)
428 thisdirectory
= thisfile
.rsplit("/", 1)[0]
429 os
.chdir(thisdirectory
)
433 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
437 if "--quiet" in sys
.argv
:
438 config
.debug_stdout
= False
439 serveurs
= {"a♡" : "acoeur.crans.org",
440 "acoeur" : "acoeur.crans.org",
441 "acoeur.crans.org" : "acoeur.crans.org",
442 "irc" : "irc.crans.org",
443 "crans" : "irc.crans.org",
444 "irc.crans.org" : "irc.crans.org"}
445 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
446 outfile
= "/var/log/bots/helixbot.full.log"
449 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
451 sys
.stdout
= Logger(outfile
)
453 serveur
= serveurs
[serveur
]
455 print "Server Unknown : %s" % (serveur
)
457 helix
= HelixBot(serveur
,debug
)
458 # Si on reçoit un SIGHUP, on reload la config
459 def sighup_handler(signum
, frame
):
460 helix
.execute_reload(auteur
="SIGHUP")
461 signal
.signal(signal
.SIGHUP
, sighup_handler
)
464 child_pid
= os
.fork()
467 helix
.start_as_daemon(outfile
)
469 # on enregistre le pid de basile
470 pidfile
= "/var/run/bots/helixbot.pid"
473 if arg
[0].strip('-') in ["pidfile"]:
475 f
= open(pidfile
, "w")
476 f
.write("%s\n" % child_pid
)
481 if __name__
== "__main__":