]>
gitweb.pimeys.fr Git - bots/josh.git/blob - josh.py
4 """ IRC bot to kickban spammers on c3 channels """
15 # Yup, I'va tweaked irclib to handle SIGHUP
16 sys
.path
.insert(0, "/home/vincent/scripts/python-myirclib")
22 #: Module de réponse aux questions de base
25 # la partie qui réfère au fichier lui-même est mieux ici
26 # sinon on réfère la config et pas le fichier lui-même
27 config
.thisfile
= os
.path
.realpath(__file__
)
29 def get_config_logfile(serveur
):
30 """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
31 serveurs
= {"irc.hackint.org" : "hackint",
32 "irc.crans.org" : "crans"}
33 return config
.logfile_template
% (serveurs
[serveur
],)
35 def log(serveur
, channel
, auteur
=None, message
=None):
36 """Enregistre une ligne de log."""
37 if auteur
== message
== None:
38 # alors c'est que c'est pas un channel mais juste une ligne de log
39 chain
= u
"%s %s" % (time
.strftime("%F %T"), channel
)
41 chain
= u
"%s [%s:%s] %s" % (time
.strftime("%F %T"), channel
, auteur
, message
)
42 f
= open(get_config_logfile(serveur
), "a")
43 f
.write((chain
+ u
"\n").encode("utf-8"))
45 if config
.debug_stdout
:
46 print chain
.encode("utf-8")
48 def ignore_event(serv
, ev
):
49 """Retourne ``True`` si il faut ignorer cet évènement."""
50 for (blackmask
, exceptlist
) in config
.blacklisted_masks
:
51 usermask
= ev
.source()
52 blackit
= bool(irclib
.mask_matches(usermask
, blackmask
))
53 exceptit
= any([bool(irclib
.mask_matches(usermask
, exceptmask
)) for exceptmask
in exceptlist
])
54 if exceptit
: # Il est exempté
57 if blackit
: # Il n'est pas exempté et matche la blacklist
61 def bot_unicode(chain
):
62 """Essaye de décoder ``chain`` en UTF-8.
63 Lève une py:class:`errors.UnicodeBotError` en cas d'échec."""
65 return chain
.decode("utf8")
66 except UnicodeDecodeError as exc
:
67 raise errors
.UnicodeBotError
70 class Josh(ircbot
.SingleServerIRCBot
):
71 """Classe principale : définition du bot"""
72 def __init__(self
, serveur
, debug
=False):
73 temporary_pseudo
= config
.irc_pseudo
+ str(random
.randrange(10000,100000))
74 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
75 temporary_pseudo
, "Basile, le bot irc. [Codé par 20-100]", 10)
77 self
.serveur
= serveur
78 self
.overops
= config
.overops
79 self
.ops
= self
.overops
+ config
.ops
80 self
.report_bugs_to
= config
.report_bugs_to
81 self
.chanlist
= config
.chanlist
82 self
.stay_channels
= config
.stay_channels
83 self
.quiet_channels
= config
.quiet_channels
88 """Récuère le nick effectif du bot sur le serveur."""
89 return self
.serv
.get_nickname()
90 nick
= property(_getnick
)
92 def give_me_my_pseudo(self
, serv
):
93 """Récupère le pseudo auprès de NickServ."""
94 serv
.privmsg("NickServ", "RECOVER %s %s" % (config
.irc_pseudo
, config
.irc_password
))
95 serv
.privmsg("NickServ", "RELEASE %s %s" % (config
.irc_pseudo
, config
.irc_password
))
97 serv
.nick(config
.irc_pseudo
)
99 def pourmoi(self
, serv
, message
):
100 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
102 pseudo
= pseudo
.decode("utf-8")
104 if message
[:size
] == pseudo
and len(message
) > size
and message
[size
] == ":":
105 return (True, message
[size
+1:].lstrip(" "))
107 return (False, message
)
109 ### Exécution d'actions
110 def quitter(self
, chan
, leave_message
=None):
111 """Quitter un channel avec un message customisable."""
112 if leave_message
== None:
113 leave_message
= random
.choice(config
.leave_messages
)
114 self
.serv
.part(chan
, message
=leave_message
.encode("utf8"))
117 """Se déconnecter du serveur IRC avec un message customisable."""
118 quit_message
= random
.choice(config
.quit_messages
)
119 self
.die(msg
=quit_message
.encode("utf8"))
121 def execute_reload(self
, auteur
=None):
122 """Recharge la config."""
124 isit
.regexp_compile()
125 if auteur
in [None, "SIGHUP"]:
126 towrite
= "Config reloaded" + " (SIGHUP received)" * (auteur
== "SIGHUP")
127 for to
in config
.report_bugs_to
:
128 self
.serv
.privmsg(to
, towrite
)
129 log(self
.serveur
, towrite
)
132 return True, u
"Config reloaded"
134 def crash(self
, who
="nobody", chan
="nowhere"):
135 """Fait crasher le bot."""
136 where
= "en privé" if chan
== "priv" else "sur le chan %s" % chan
137 raise errors
.CrashError((u
"Crash demandé par %s %s" % (who
, where
)).encode("utf-8"))
140 "reload" : execute_reload
,
143 def execute_something(self
, something
, params
, place
=None, auteur
=None):
144 """Exécute une action et répond son résultat à ``auteur``
145 sur un chan ou en privé en fonction de ``place``"""
146 action
= self
.ACTIONS
[something
]
147 success
, message
= action(self
, **params
)
149 if irclib
.is_channel(place
):
150 message
= "%s: %s" % (auteur
, message
.encode("utf-8"))
151 self
.serv
.privmsg(place
, message
)
152 log(self
.serveur
, place
, auteur
, something
+ "%r" % params
+ ("[successful]" if success
else "[failed]"))
154 ### Surcharge des events du Bot
155 def on_welcome(self
, serv
, ev
):
156 """À l'arrivée sur le serveur."""
157 self
.serv
= serv
# ça serv ira :)
158 self
.give_me_my_pseudo(serv
)
159 serv
.privmsg("NickServ", "IDENTIFY %s" % (config
.irc_password
))
160 log(self
.serveur
, "Connected")
162 self
.chanlist
= ["#bot"]
163 for c
in self
.chanlist
:
164 log(self
.serveur
, "JOIN %s" % (c
))
167 def on_privmsg(self
, serv
, ev
):
168 """À la réception d'un message en privé."""
169 if ignore_event(serv
, ev
):
171 message
= ev
.arguments()[0]
172 auteur
= irclib
.nm_to_n(ev
.source())
174 message
= bot_unicode(message
)
175 except errors
.UnicodeBotError
:
176 if config
.utf8_trigger
:
177 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
179 message
= message
.split()
180 cmd
= message
[0].lower()
181 notunderstood
= False
183 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
185 helpmsg
= config
.helpmsg_default
187 helpmsg
+= config
.helpmsg_ops
189 helpmsg
+= config
.helpmsg_overops
191 helpmsgs
= config
.helpdico
.get(message
[1].lower(), ["Commande inconnue.", None, None])
192 helpmsg
= helpmsgs
[0]
193 if op
and helpmsgs
[1]:
195 helpmsg
+= "\n" + helpmsgs
[1]
197 helpmsg
= helpmsgs
[1]
198 if overop
and helpmsgs
[2]:
200 helpmsg
+= "\n" + helpmsgs
[2]
202 helpmsg
= helpmsgs
[2]
203 for ligne
in helpmsg
.split("\n"):
204 serv
.privmsg(auteur
, ligne
.encode("utf-8"))
206 if auteur
in self
.ops
:
208 if message
[1] in self
.chanlist
:
209 serv
.privmsg(auteur
, (u
"Je suis déjà sur %s" % (message
[1])).encode("utf-8"))
211 serv
.join(message
[1])
212 self
.chanlist
.append(message
[1])
213 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
214 log(self
.serveur
, "priv", auteur
, " ".join(message
))
216 serv
.privmsg(auteur
, "Channels : " + " ".join(self
.chanlist
))
219 elif cmd
== u
"leave":
220 if auteur
in self
.ops
and len(message
) > 1:
221 if message
[1] in self
.chanlist
:
222 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
223 self
.quitter(message
[1].encode("utf-8"), " ".join(message
[2:]))
224 self
.chanlist
.remove(message
[1])
225 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
227 serv
.privmsg(auteur
, "Non, je reste !")
228 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
230 serv
.privmsg(auteur
, "Je ne suis pas sur %s" % (message
[1]))
234 if auteur
in self
.overops
:
236 if message
[1] in self
.stay_channels
:
237 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
238 serv
.privmsg(auteur
, "Je stay déjà sur %s." % (message
[1]))
240 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
241 self
.stay_channels
.append(message
[1])
242 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
244 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
247 elif cmd
== u
"nostay":
248 if auteur
in self
.overops
:
250 if message
[1] in self
.stay_channels
:
251 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
252 self
.stay_channels
.remove(message
[1])
253 serv
.privmsg(auteur
, "Stay channels : " + " ".join(self
.stay_channels
))
255 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
256 serv
.privmsg(auteur
, "Je ne stay pas sur %s." % (message
[1]))
260 if auteur
in self
.overops
:
261 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
265 elif cmd
== u
"crash":
266 if auteur
in self
.overops
:
267 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
268 self
.crash(auteur
, "priv")
271 elif cmd
== u
"reload":
272 if auteur
in self
.ops
:
273 self
.execute_something("reload", {"auteur" : auteur
}, place
=auteur
, auteur
=auteur
)
276 elif cmd
== u
"quiet":
277 if auteur
in self
.ops
:
279 if message
[1] in self
.quiet_channels
:
280 serv
.privmsg(auteur
, "Je me la ferme déjà sur %s" % (message
[1]))
281 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
283 self
.quiet_channels
.append(message
[1])
284 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
285 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
287 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
290 elif cmd
== u
"noquiet":
291 if auteur
in self
.ops
:
293 if message
[1] in self
.quiet_channels
:
294 self
.quiet_channels
.remove(message
[1])
295 serv
.privmsg(auteur
, "Quiet channels : " + " ".join(self
.quiet_channels
))
296 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[successful]")
298 serv
.privmsg(auteur
, "Je ne me la ferme pas sur %s." % (message
[1]))
299 log(self
.serveur
, "priv", auteur
, " ".join(message
) + "[failed]")
303 if auteur
in self
.overops
and len(message
) > 2:
304 serv
.privmsg(message
[1].encode("utf-8"), (u
" ".join(message
[2:])).encode("utf-8"))
305 log(self
.serveur
, "priv", auteur
, " ".join(message
))
306 elif len(message
) <= 2:
307 serv
.privmsg(auteur
, "Syntaxe : SAY <channel> <message>")
311 if auteur
in self
.overops
and len(message
) > 2:
312 serv
.action(message
[1], " ".join(message
[2:]))
313 log(self
.serveur
, "priv", auteur
, " ".join(message
))
314 elif len(message
) <= 2:
315 serv
.privmsg(auteur
, "Syntaxe : DO <channel> <action>")
319 if auteur
in self
.overops
and len(message
) > 2:
320 serv
.kick(message
[1].encode("utf-8"), message
[2].encode("utf-8"), " ".join(message
[3:]).encode("utf-8"))
321 log(self
.serveur
, "priv", auteur
, " ".join(message
))
322 elif len(message
) <= 2:
323 serv
.privmsg(auteur
, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
327 if auteur
in self
.overops
:
328 serv
.privmsg(auteur
, " ".join(self
.ops
))
331 elif cmd
== u
"overops":
332 if auteur
in self
.overops
:
333 serv
.privmsg(auteur
, " ".join(self
.overops
))
337 serv
.privmsg(auteur
, "I don't understand what you're saying. Try HELP…")
339 def on_pubmsg(self
, serv
, ev
):
340 """À la réception d'un message sur un channel."""
341 if ignore_event(serv
, ev
):
343 auteur
= irclib
.nm_to_n(ev
.source())
345 message
= ev
.arguments()[0]
347 message
= bot_unicode(message
)
348 except errors
.UnicodeBotError
:
349 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
350 serv
.privmsg(canal
, (u
"%s: %s"% ( auteur
, random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
352 pour_moi
, message
= self
.pourmoi(serv
, message
)
353 if pour_moi
and message
.split()!=[]:
354 cmd
= message
.split()[0].lower()
356 args
= " ".join(message
.split()[1:])
359 if cmd
in [u
"meurs", u
"die", u
"crève"]:
360 if auteur
in self
.overops
:
361 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
364 serv
.privmsg(canal
,(u
"%s: %s"%(auteur
, random
.choice(config
.quit_fail_messages
))).encode("utf8"))
365 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
366 elif cmd
== u
"reload":
367 if auteur
in self
.ops
:
368 self
.execute_something("reload", {"auteur" : auteur
}, place
=canal
, auteur
=auteur
)
369 elif cmd
== u
"crash":
370 if auteur
in self
.overops
:
371 self
.crash(auteur
, canal
)
372 elif cmd
in [u
"part", u
"leave", u
"dégage", u
"va-t-en"]:
373 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
374 or auteur
in self
.overops
):
376 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
377 if canal
in self
.chanlist
:
378 self
.chanlist
.remove(canal
)
380 serv
.privmsg(canal
,(u
"%s: %s" % (auteur
, random
.choice(config
.leave_fail_messages
))).encode("utf8"))
381 log(self
.serveur
, canal
, auteur
, message
+ "[failed]")
383 elif cmd
in [u
"deviens", u
"pseudo", u
"become", u
"nick"]:
384 if auteur
in self
.ops
:
387 log(self
.serveur
, canal
, auteur
, message
+ "[successful]")
389 elif cmd
in [u
"ping"] and not canal
in self
.quiet_channels
:
390 serv
.privmsg(canal
, "%s: pong" % (auteur
))
392 if isit
.is_tag(message
) and not canal
in self
.quiet_channels
:
393 if auteur
in self
.ops
:
394 action
= random
.choice(config
.tag_actions
)
395 serv
.action(canal
, action
.encode("utf8"))
396 self
.quiet_channels
.append(canal
)
398 answer
= random
.choice(config
.tag_answers
)
399 for ligne
in answer
.split("\n"):
400 serv
.privmsg(canal
, "%s: %s" % (auteur
, ligne
.encode("utf8")))
401 if isit
.is_allah(message
):
402 serv
.kick(canal
, auteur
, u
"Please go spread your proselytism somewhere else.")
404 def on_action(self
, serv
, ev
):
405 """À la réception d'une action."""
406 if ignore_event(serv
, ev
):
408 action
= ev
.arguments()[0]
409 auteur
= irclib
.nm_to_n(ev
.source())
410 channel
= ev
.target()
412 action
= bot_unicode(action
)
413 except errors
.UnicodeBotError
:
414 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
415 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
419 def on_kick(self
, serv
, ev
):
420 """À la réception d'une action."""
421 auteur
= irclib
.nm_to_n(ev
.source())
422 channel
= ev
.target()
423 victime
= ev
.arguments()[0]
424 raison
= ev
.arguments()[1]
425 if victime
== self
.nick
:
426 log(self
.serveur
, ("%s kické de %s par %s (raison : %s)" % (victime
, channel
, auteur
, raison
)).decode("utf-8"))
429 # l1, l2 = config.kick_answers, config.kick_actions
430 # n1, n2 = len(l1), len(l2)
431 # i = random.randrange(n1 + n2)
433 # serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
435 # serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
438 def start_as_daemon(self
, outfile
):
439 sys
.stderr
= Logger(outfile
)
443 class Logger(object):
444 """Pour écrire ailleurs que sur stdout"""
445 def __init__(self
, filename
="basile.full.log"):
446 self
.filename
= filename
448 def write(self
, message
):
449 f
= open(self
.filename
, "a")
454 """Exécution principal : lecture des paramètres et lancement du bot."""
455 if len(sys
.argv
) == 1:
456 print "Usage : josh.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
457 print " --outfile sans --no-output ni --daemon n'a aucun effet"
459 serveur
= sys
.argv
[1]
460 if "--daemon" in sys
.argv
:
461 thisfile
= os
.path
.realpath(__file__
)
462 thisdirectory
= thisfile
.rsplit("/", 1)[0]
463 os
.chdir(thisdirectory
)
467 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
471 if "--quiet" in sys
.argv
:
472 config
.debug_stdout
= False
474 "crans" : "irc.crans.org",
475 "irc.crans.org" : "irc.crans.org",
476 "hackint" : "irc.hackint.org",
477 "irc.hackint.org" : "irc.hackint.org",
479 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
480 outfile
= "/var/log/bots/josh.full.log"
483 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
485 sys
.stdout
= Logger(outfile
)
487 serveur
= serveurs
[serveur
]
489 print "Server Unknown : %s" % (serveur
)
491 josh
= Josh(serveur
,debug
)
492 # Si on reçoit un SIGHUP, on reload la config
493 def sighup_handler(signum
, frame
):
494 basile
.execute_reload(auteur
="SIGHUP")
495 signal
.signal(signal
.SIGHUP
, sighup_handler
)
498 child_pid
= os
.fork()
501 basile
.start_as_daemon(outfile
)
503 # on enregistre le pid du bot
504 pidfile
= "/var/run/bots/josh.pid"
507 if arg
[0].strip('-') in ["pidfile"]:
509 f
= open(pidfile
, "w")
510 f
.write("%s\n" % child_pid
)
515 if __name__
== "__main__":