]>
gitweb.pimeys.fr Git - bots/ibot.git/blob - ibot.py
4 # Codé par 20-100 (commencé le 23/04/12)
6 # Un bot IRC qui, un jour, s'interfacera avec la Note Kfet 2015
11 import socket
, ssl
, json
18 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
19 sys
.path
.insert(0, "/home/vincent/scripts/python-myirclib")
23 from commands
import getstatusoutput
as ex
25 # on récupère la config
28 # la partie qui réfère au fichier lui-même est mieux ici
29 # sinon on réfère la config et pas le fichier lui-même
31 config
.thisfile
= os
.path
.realpath( __file__
)
33 def get_config_logfile(serveur
):
34 serveurs
={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
35 return config
.logfile_template
%(serveurs
[serveur
])
37 def log(serveur
,channel
,auteur
=None,message
=None):
38 f
=open(get_config_logfile(serveur
),"a")
39 if auteur
==message
==None:
40 # alors c'est que c'est pas un channel mais juste une ligne de log
41 chain
="%s %s"%(time
.strftime("%F %T"),channel
)
43 chain
="%s [%s:%s] %s"%(time
.strftime("%F %T"),channel
,auteur
,message
)
45 if config
.debug_stdout
:
49 def ignore_event(serv
, ev
):
50 """Retourne ``True`` si il faut ignorer cet évènement."""
51 for (blackmask
, exceptmask
) in config
.blacklisted_masks
:
52 usermask
= ev
.source()
53 if exceptmask
is None:
56 exceptit
= bool(irclib
.mask_matches(usermask
, exceptmask
))
57 blackit
= bool(irclib
.mask_matches(usermask
, blackmask
))
58 return blackit
and not exceptit
60 def is_something(chain
,matches
,avant
=u
".*(?:^| )",apres
=u
"(?:$|\.| |,|;).*",case_sensitive
=False,debug
=False):
62 chain
=unicode(chain
,"utf8")
64 chain
=unicode(chain
,"utf8").lower()
65 allmatches
="("+"|".join(matches
)+")"
66 reg
=(avant
+allmatches
+apres
).lower()
70 def is_insult(chain
,debug
=True):
71 return is_something(chain
,config
.insultes
,avant
=".*(?:^| |')")
72 def is_not_insult(chain
):
73 chain
=unicode(chain
,"utf8")
74 insult_regexp
=u
"("+u
"|".join(config
.insultes
)+u
")"
75 middle_regexp
=u
"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
76 reg
=".*pas %s%s.*"%(middle_regexp
,insult_regexp
)
77 if re
.match(reg
,chain
):
81 def is_compliment(chain
,debug
=True):
82 return is_something(chain
,config
.compliment_triggers
,avant
=".*(?:^| |')")
84 return is_something(chain
,config
.perdu
)
86 return is_something(chain
,config
.tag_triggers
)
88 return is_something(chain
,config
.gros
)
90 return is_something(chain
,config
.tesla_triggers
,avant
=u
"^",apres
=u
"$",debug
=True)
92 return is_something(chain
,config
.merci_triggers
)
94 return is_something(chain
,config
.tamere_triggers
)
95 def is_bad_action_trigger(chain
,pseudo
):
96 return is_something(chain
,config
.bad_action_triggers
,avant
=u
"^",
97 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
98 def is_good_action_trigger(chain
,pseudo
):
99 return is_something(chain
,config
.good_action_triggers
,avant
=u
"^",
100 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
101 def is_bonjour(chain
):
102 return is_something(chain
,config
.bonjour_triggers
,avant
=u
"^")
103 def is_bonne_nuit(chain
):
104 return is_something(chain
,config
.bonne_nuit_triggers
,avant
=u
"^")
106 return re
.match(u
"^(pan|bim|bang)( .*)?$",unicode(chain
,"utf8").lower().strip())
109 _
,_
,_
,h
,m
,s
,_
,_
,_
=time
.localtime()
110 return (conf
[0],0,0)<(h
,m
,s
)<(conf
[1],0,0)
112 return is_time(config
.daytime
)
114 return is_time(config
.nighttime
)
117 class UnicodeBotError(Exception):
120 class CrashError(Exception):
121 """Pour pouvoir faire crasher le bot, parce que ça a l'air drôle"""
124 def bot_unicode(chain
):
126 unicode(chain
,"utf8")
127 except UnicodeDecodeError as exc
:
128 raise UnicodeBotError
131 class Ibot(ircbot
.SingleServerIRCBot
):
132 def __init__(self
,serveur
,debug
=False):
133 temporary_pseudo
=config
.irc_pseudo
+str(random
.randrange(10000,100000))
134 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
135 temporary_pseudo
,"iDon't care", 10)
138 self
.overops
=config
.overops
139 self
.ops
=self
.overops
+config
.ops
140 self
.report_bugs_to
=config
.report_bugs_to
141 self
.chanlist
=config
.chanlist
142 self
.stay_channels
=config
.stay_channels
143 self
.i_channels
=config
.i_channels
144 self
.quiet_channels
=config
.quiet_channels
148 def give_me_my_pseudo(self
,serv
):
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
))
152 serv
.nick(config
.irc_pseudo
)
154 def on_welcome(self
, serv
, ev
):
155 self
.serv
=serv
# ça serv ira :)
156 self
.give_me_my_pseudo(serv
)
157 serv
.privmsg("NickServ","identify %s"%(config
.irc_password
))
158 log(self
.serveur
,"Connected")
160 self
.chanlist
=["#bot"]
161 for c
in self
.chanlist
:
162 log(self
.serveur
,"JOIN %s"%(c))
165 def pourmoi(self
, serv
, message
):
166 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
169 if message
[:size
]==pseudo
and len(message
)>size
and message
[size
]==":":
170 return (True,message
[size
+1:].lstrip(" "))
172 return (False,message
)
174 def on_privmsg(self
, serv
, ev
):
175 if ignore_event(serv
, ev
):
177 message
=ev
.arguments()[0]
178 auteur
= irclib
.nm_to_n(ev
.source())
180 test
=bot_unicode(message
)
181 except UnicodeBotError
:
182 if config
.utf8_trigger
:
183 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
185 message
=message
.split()
186 cmd
=message
[0].lower()
189 helpdico
={"help": ["""HELP <commande>
190 Affiche de l'aide sur la commande""",None,None],
191 "join": [None, """JOIN <channel>
192 Me fait rejoindre le channel""",None],
193 "leave": [None,"""LEAVE <channel>
194 Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
195 "ichannel": [None, """ICHANNEL <channel>,
196 Rend le channel i-nazi""", None],
197 "noichannel": [None, """NOICHANNEL <channel>,
198 Dé-i-nazifie le channel""", None],
199 "reload": [None,"""RELOAD
200 Recharge la configuration.""",None],
201 "say": [None,None,"""SAY <channel> <message>
202 Me fait parler sur le channel."""],
203 "do": [None,None,"""DO <channel> <action>
204 Me fait faitre une action (/me) sur le channel."""],
205 "stay": [None,None,"""STAY <channel>
206 Ajoute le channel à ma stay_list."""],
207 "nostay": [None,None,"""NOSTAY <channel>
208 Retire le channel de ma stay_list."""],
209 "ops": [None,None,"""OPS
210 Affiche la liste des ops."""],
211 "overops": [None,None,"""OVEROPS
212 Affiche la liste des overops."""],
213 "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
214 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
215 "die": [None,None,"""DIE
216 Me déconnecte du serveur IRC."""],
217 "crash": [None,None,"""CRASH
220 helpmsg_default
="Liste des commandes disponibles :\nHELP "
221 helpmsg_ops
=" JOIN LEAVE QUIET NOQUIET LOST RELOAD ICHANNEL NOICHANNEL"
222 helpmsg_overops
=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE CRASH"
223 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
225 helpmsg
=helpmsg_default
229 helpmsg
+=helpmsg_overops
231 helpmsgs
=helpdico
.get(message
[1].lower(),["Commande inconnue.",None,None])
233 if op
and helpmsgs
[1]:
235 helpmsg
+="\n"+helpmsgs
[1]
238 if overop
and helpmsgs
[2]:
240 helpmsg
+="\n"+helpmsgs
[2]
243 for ligne
in helpmsg
.split("\n"):
244 serv
.privmsg(auteur
,ligne
)
246 if auteur
in self
.ops
:
248 if message
[1] in self
.chanlist
:
249 serv
.privmsg(auteur
,"Je suis déjà sur %s"%(message
[1]))
251 serv
.join(message
[1])
252 self
.chanlist
.append(message
[1])
253 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
254 log(self
.serveur
,"priv",auteur
," ".join(message
))
256 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
260 if auteur
in self
.ops
and len(message
)>1:
261 if message
[1] in self
.chanlist
:
262 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
263 self
.quitter(message
[1]," ".join(message
[2:]))
264 self
.chanlist
.remove(message
[1])
265 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
267 serv
.privmsg(auteur
,"Non, je reste !")
268 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
270 serv
.privmsg(auteur
,"Je ne suis pas sur %s"%(message
[1]))
274 if auteur
in self
.overops
:
276 if message
[1] in self
.stay_channels
:
277 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
278 serv
.privmsg(auteur
,"Je stay déjà sur %s."%(message
[1]))
280 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
281 self
.stay_channels
.append(message
[1])
282 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
284 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
288 if auteur
in self
.overops
:
290 if message
[1] in self
.stay_channels
:
291 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
292 self
.stay_channels
.remove(message
[1])
293 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
295 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
296 serv
.privmsg(auteur
,"Je ne stay pas sur %s."%(message
[1]))
300 elif cmd
=="ichannel":
301 if auteur
in self
.ops
:
303 if message
[1] in self
.i_channels
:
304 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
305 serv
.privmsg(auteur
,"%s est déjà i-nazi."%(message
[1]))
307 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
308 self
.i_channels
.append(message
[1])
309 serv
.privmsg(auteur
,"I-channels : "+" ".join(self
.i_channels
))
311 serv
.privmsg(auteur
,"I-channels : "+" ".join(self
.i_channels
))
314 elif cmd
=="noichannel":
315 if auteur
in self
.ops
:
317 if message
[1] in self
.i_channels
:
318 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
319 self
.i_channels
.remove(message
[1])
320 serv
.privmsg(auteur
,"I-channels : "+" ".join(self
.i_channels
))
322 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
323 serv
.privmsg(auteur
,"%s n'est pas i-nazi."%(message
[1]))
328 if auteur
in self
.overops
:
329 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
334 if auteur
in self
.overops
:
335 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
340 if auteur
in self
.ops
:
342 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
346 if auteur
in self
.ops
:
348 if message
[1] in self
.quiet_channels
:
349 serv
.privmsg(auteur
,"Je me la ferme déjà sur %s"%(message
[1]))
350 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
352 self
.quiet_channels
.append(message
[1])
353 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
354 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
356 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
360 if auteur
in self
.ops
:
362 if message
[1] in self
.quiet_channels
:
363 self
.quiet_channels
.remove(message
[1])
364 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
365 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
367 serv
.privmsg(auteur
,"Je ne me la ferme pas sur %s."%(message
[1]))
368 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
372 if auteur
in self
.overops
and len(message
)>2:
373 serv
.privmsg(message
[1]," ".join(message
[2:]))
374 log(self
.serveur
,"priv",auteur
," ".join(message
))
375 elif len(message
)<=2:
376 serv
.privmsg(auteur
,"Syntaxe : SAY <channel> <message>")
380 if auteur
in self
.overops
and len(message
)>2:
381 serv
.action(message
[1]," ".join(message
[2:]))
382 log(self
.serveur
,"priv",auteur
," ".join(message
))
383 elif len(message
)<=2:
384 serv
.privmsg(auteur
,"Syntaxe : DO <channel> <action>")
388 if auteur
in self
.overops
and len(message
)>2:
389 serv
.kick(message
[1],message
[2]," ".join(message
[3:]))
390 log(self
.serveur
,"priv",auteur
," ".join(message
))
391 elif len(message
)<=2:
392 serv
.privmsg(auteur
,"Syntaxe : KICK <channel> <pseudo> [<raison>]")
396 if auteur
in self
.overops
:
397 serv
.privmsg(auteur
," ".join(self
.ops
))
401 if auteur
in self
.overops
:
402 serv
.privmsg(auteur
," ".join(self
.overops
))
408 serv
.privmsg(auteur
,"Je n'ai pas compris. Essayez HELP…")
410 def on_pubmsg(self
, serv
, ev
):
411 if ignore_event(serv
, ev
):
413 auteur
= irclib
.nm_to_n(ev
.source())
415 message
= ev
.arguments()[0]
417 test
=bot_unicode(message
)
418 except UnicodeBotError
:
419 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
420 serv
.privmsg(canal
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
422 pour_moi
,message
=self
.pourmoi(serv
,message
)
423 if pour_moi
and message
.split()!=[]:
424 cmd
=message
.split()[0].lower()
426 args
=" ".join(message
.split()[1:])
429 if cmd
in ["meurs","die","crève"]:
430 if auteur
in self
.overops
:
431 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
433 elif cmd
== "reload":
434 if auteur
in self
.ops
:
435 log(self
.serveur
, canal
, auteur
, message
+"[successful]")
438 if auteur
in self
.overops
:
440 elif cmd
in ["part","leave","dégage","va-t-en"]:
441 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
442 or auteur
in self
.overops
):
444 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
445 if canal
in self
.chanlist
:
446 self
.chanlist
.remove(canal
)
447 elif cmd
in ["deviens","pseudo"]:
448 if auteur
in self
.ops
:
451 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
454 if not re
.match(u
'i.*',message
.decode("utf8").lower().strip(u
" ")) and canal
in self
.i_channels
:
455 serv
.kick(canal
, auteur
, u
"iKick".encode("utf8"))
457 def on_action(self
, serv
, ev
):
458 if ignore_event(serv
, ev
):
460 action
= ev
.arguments()[0]
461 auteur
= irclib
.nm_to_n(ev
.source())
462 channel
= ev
.target()
464 test
=bot_unicode(action
)
465 except UnicodeBotError
:
466 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
467 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
471 def on_kick(self
,serv
,ev
):
472 auteur
= irclib
.nm_to_n(ev
.source())
473 channel
= ev
.target()
474 victime
= ev
.arguments()[0]
475 raison
= ev
.arguments()[1]
476 if victime
==self
.nick
:
477 log(self
.serveur
,"%s kické de %s par %s (raison : %s)" %(victime
,channel
,auteur
,raison
))
481 def quitter(self
,chan
,leave_message
=None):
482 if leave_message
==None:
483 leave_message
=random
.choice(config
.leave_messages
)
484 self
.serv
.part(chan
,message
=leave_message
.encode("utf8"))
487 quit_message
=random
.choice(config
.quit_messages
)
488 self
.die(msg
=quit_message
.encode("utf8"))
491 return self
.serv
.get_nickname()
492 nick
=property(_getnick
)
494 def reload(self
, auteur
=None):
496 if auteur
in [None, "SIGHUP"]:
497 towrite
= "Config reloaded" + " (SIGHUP received)"*(auteur
== "SIGHUP")
498 for to
in config
.report_bugs_to
:
499 self
.serv
.privmsg(to
, towrite
)
500 log(self
.serveur
, towrite
)
502 self
.serv
.privmsg(auteur
,"Config reloaded")
507 def start_as_daemon(self
, outfile
):
508 sys
.stderr
= Logger(outfile
)
512 class Logger(object):
513 """Pour écrire ailleurs que sur stdout"""
514 def __init__(self
, filename
="ibot.full.log"):
515 self
.filename
= filename
517 def write(self
, message
):
518 f
= open(self
.filename
, "a")
524 print "Usage : ibot.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
525 print " --outfile sans --no-output ni --daemon n'a aucun effet"
528 if "--daemon" in sys
.argv
:
529 thisfile
= os
.path
.realpath(__file__
)
530 thisdirectory
= thisfile
.rsplit("/", 1)[0]
531 os
.chdir(thisdirectory
)
535 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
539 if "--quiet" in sys
.argv
:
540 config
.debug_stdout
=False
541 serveurs
={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
542 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
543 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
544 outfile
= "/var/log/bots/ibot.full.log"
547 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
549 sys
.stdout
= Logger(outfile
)
551 serveur
=serveurs
[serveur
]
553 print "Server Unknown : %s"%(serveur)
555 ibot
=Ibot(serveur
,debug
)
556 # Si on reçoit un SIGHUP, on reload la config
557 def sighup_handler(signum
, frame
):
558 ibot
.reload("SIGHUP")
559 signal
.signal(signal
.SIGHUP
, sighup_handler
)
561 child_pid
= os
.fork()
564 ibot
.start_as_daemon(outfile
)
566 # on enregistre le pid du bot
567 pidfile
= "/var/run/bots/ibot.pid"
570 if arg
[0].strip('-') in ["pidfile"]:
572 f
= open(pidfile
, "w")
573 f
.write("%s\n" % child_pid
)
578 if __name__
== "__main__":