]>
gitweb.pimeys.fr Git - bots/ibot.git/blob - ibot.py
9fadb8a0ac3732d65698fec0944acb2c43d87912
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 is_something(chain
,matches
,avant
=u
".*(?:^| )",apres
=u
"(?:$|\.| |,|;).*",case_sensitive
=False,debug
=False):
51 chain
=unicode(chain
,"utf8")
53 chain
=unicode(chain
,"utf8").lower()
54 allmatches
="("+"|".join(matches
)+")"
55 reg
=(avant
+allmatches
+apres
).lower()
59 def is_insult(chain
,debug
=True):
60 return is_something(chain
,config
.insultes
,avant
=".*(?:^| |')")
61 def is_not_insult(chain
):
62 chain
=unicode(chain
,"utf8")
63 insult_regexp
=u
"("+u
"|".join(config
.insultes
)+u
")"
64 middle_regexp
=u
"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
65 reg
=".*pas %s%s.*"%(middle_regexp
,insult_regexp
)
66 if re
.match(reg
,chain
):
70 def is_compliment(chain
,debug
=True):
71 return is_something(chain
,config
.compliment_triggers
,avant
=".*(?:^| |')")
73 return is_something(chain
,config
.perdu
)
75 return is_something(chain
,config
.tag_triggers
)
77 return is_something(chain
,config
.gros
)
79 return is_something(chain
,config
.tesla_triggers
,avant
=u
"^",apres
=u
"$",debug
=True)
81 return is_something(chain
,config
.merci_triggers
)
83 return is_something(chain
,config
.tamere_triggers
)
84 def is_bad_action_trigger(chain
,pseudo
):
85 return is_something(chain
,config
.bad_action_triggers
,avant
=u
"^",
86 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
87 def is_good_action_trigger(chain
,pseudo
):
88 return is_something(chain
,config
.good_action_triggers
,avant
=u
"^",
89 apres
="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
90 def is_bonjour(chain
):
91 return is_something(chain
,config
.bonjour_triggers
,avant
=u
"^")
92 def is_bonne_nuit(chain
):
93 return is_something(chain
,config
.bonne_nuit_triggers
,avant
=u
"^")
95 return re
.match(u
"^(pan|bim|bang)( .*)?$",unicode(chain
,"utf8").lower().strip())
98 _
,_
,_
,h
,m
,s
,_
,_
,_
=time
.localtime()
99 return (conf
[0],0,0)<(h
,m
,s
)<(conf
[1],0,0)
101 return is_time(config
.daytime
)
103 return is_time(config
.nighttime
)
106 class UnicodeBotError(Exception):
109 class CrashError(Exception):
110 """Pour pouvoir faire crasher le bot, parce que ça a l'air drôle"""
113 def bot_unicode(chain
):
115 unicode(chain
,"utf8")
116 except UnicodeDecodeError as exc
:
117 raise UnicodeBotError
120 class Ibot(ircbot
.SingleServerIRCBot
):
121 def __init__(self
,serveur
,debug
=False):
122 temporary_pseudo
=config
.irc_pseudo
+str(random
.randrange(10000,100000))
123 ircbot
.SingleServerIRCBot
.__init
__(self
, [(serveur
, 6667)],
124 temporary_pseudo
,"iDon't care", 10)
127 self
.overops
=config
.overops
128 self
.ops
=self
.overops
+config
.ops
129 self
.report_bugs_to
=config
.report_bugs_to
130 self
.chanlist
=config
.chanlist
131 self
.stay_channels
=config
.stay_channels
132 self
.i_channels
=config
.i_channels
133 self
.quiet_channels
=config
.quiet_channels
137 def give_me_my_pseudo(self
,serv
):
138 serv
.privmsg("NickServ","RECOVER %s %s"%(config
.irc_pseudo
,config
.irc_password
))
139 serv
.privmsg("NickServ","RELEASE %s %s"%(config
.irc_pseudo
,config
.irc_password
))
141 serv
.nick(config
.irc_pseudo
)
143 def on_welcome(self
, serv
, ev
):
144 self
.serv
=serv
# ça serv ira :)
145 self
.give_me_my_pseudo(serv
)
146 serv
.privmsg("NickServ","identify %s"%(config
.irc_password
))
147 log(self
.serveur
,"Connected")
149 self
.chanlist
=["#bot"]
150 for c
in self
.chanlist
:
151 log(self
.serveur
,"JOIN %s"%(c))
154 def pourmoi(self
, serv
, message
):
155 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
158 if message
[:size
]==pseudo
and len(message
)>size
and message
[size
]==":":
159 return (True,message
[size
+1:].lstrip(" "))
161 return (False,message
)
163 def on_privmsg(self
, serv
, ev
):
164 message
=ev
.arguments()[0]
165 auteur
= irclib
.nm_to_n(ev
.source())
167 test
=bot_unicode(message
)
168 except UnicodeBotError
:
169 if config
.utf8_trigger
:
170 serv
.privmsg(auteur
, random
.choice(config
.utf8_fail_answers
).encode("utf8"))
172 message
=message
.split()
173 cmd
=message
[0].lower()
176 helpdico
={"help": ["""HELP <commande>
177 Affiche de l'aide sur la commande""",None,None],
178 "join": [None, """JOIN <channel>
179 Me fait rejoindre le channel""",None],
180 "leave": [None,"""LEAVE <channel>
181 Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
182 "ichannel": [None, """ICHANNEL <channel>,
183 Rend le channel i-nazi""", None],
184 "noichannel": [None, """NOICHANNEL <channel>,
185 Dé-i-nazifie le channel""", None],
186 "reload": [None,"""RELOAD
187 Recharge la configuration.""",None],
188 "say": [None,None,"""SAY <channel> <message>
189 Me fait parler sur le channel."""],
190 "do": [None,None,"""DO <channel> <action>
191 Me fait faitre une action (/me) sur le channel."""],
192 "stay": [None,None,"""STAY <channel>
193 Ajoute le channel à ma stay_list."""],
194 "nostay": [None,None,"""NOSTAY <channel>
195 Retire le channel de ma stay_list."""],
196 "ops": [None,None,"""OPS
197 Affiche la liste des ops."""],
198 "overops": [None,None,"""OVEROPS
199 Affiche la liste des overops."""],
200 "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
201 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
202 "die": [None,None,"""DIE
203 Me déconnecte du serveur IRC."""],
204 "crash": [None,None,"""CRASH
207 helpmsg_default
="Liste des commandes disponibles :\nHELP "
208 helpmsg_ops
=" JOIN LEAVE QUIET NOQUIET LOST RELOAD ICHANNEL NOICHANNEL"
209 helpmsg_overops
=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE CRASH"
210 op
,overop
=auteur
in self
.ops
, auteur
in self
.overops
212 helpmsg
=helpmsg_default
216 helpmsg
+=helpmsg_overops
218 helpmsgs
=helpdico
.get(message
[1].lower(),["Commande inconnue.",None,None])
220 if op
and helpmsgs
[1]:
222 helpmsg
+="\n"+helpmsgs
[1]
225 if overop
and helpmsgs
[2]:
227 helpmsg
+="\n"+helpmsgs
[2]
230 for ligne
in helpmsg
.split("\n"):
231 serv
.privmsg(auteur
,ligne
)
233 if auteur
in self
.ops
:
235 if message
[1] in self
.chanlist
:
236 serv
.privmsg(auteur
,"Je suis déjà sur %s"%(message
[1]))
238 serv
.join(message
[1])
239 self
.chanlist
.append(message
[1])
240 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
241 log(self
.serveur
,"priv",auteur
," ".join(message
))
243 serv
.privmsg(auteur
,"Channels : "+" ".join(self
.chanlist
))
247 if auteur
in self
.ops
and len(message
)>1:
248 if message
[1] in self
.chanlist
:
249 if not (message
[1] in self
.stay_channels
) or auteur
in self
.overops
:
250 self
.quitter(message
[1]," ".join(message
[2:]))
251 self
.chanlist
.remove(message
[1])
252 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
254 serv
.privmsg(auteur
,"Non, je reste !")
255 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
257 serv
.privmsg(auteur
,"Je ne suis pas sur %s"%(message
[1]))
261 if auteur
in self
.overops
:
263 if message
[1] in self
.stay_channels
:
264 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
265 serv
.privmsg(auteur
,"Je stay déjà sur %s."%(message
[1]))
267 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
268 self
.stay_channels
.append(message
[1])
269 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
271 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
275 if auteur
in self
.overops
:
277 if message
[1] in self
.stay_channels
:
278 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
279 self
.stay_channels
.remove(message
[1])
280 serv
.privmsg(auteur
,"Stay channels : "+" ".join(self
.stay_channels
))
282 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
283 serv
.privmsg(auteur
,"Je ne stay pas sur %s."%(message
[1]))
287 elif cmd
=="ichannel":
288 if auteur
in self
.ops
:
290 if message
[1] in self
.i_channels
:
291 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
292 serv
.privmsg(auteur
,"%s est déjà i-nazi."%(message
[1]))
294 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
295 self
.i_channels
.append(message
[1])
296 serv
.privmsg(auteur
,"I-channels : "+" ".join(self
.i_channels
))
298 serv
.privmsg(auteur
,"I-channels : "+" ".join(self
.i_channels
))
302 if auteur
in self
.ops
:
304 if message
[1] in self
.i_channels
:
305 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
306 self
.i_channels
.remove(message
[1])
307 serv
.privmsg(auteur
,"I-channels : "+" ".join(self
.i_channels
))
309 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
310 serv
.privmsg(auteur
,"%s n'est pas i-nazi."%(message
[1]))
315 if auteur
in self
.overops
:
316 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
321 if auteur
in self
.overops
:
322 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
327 if auteur
in self
.ops
:
329 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
333 if auteur
in self
.ops
:
335 if message
[1] in self
.quiet_channels
:
336 serv
.privmsg(auteur
,"Je me la ferme déjà sur %s"%(message
[1]))
337 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
339 self
.quiet_channels
.append(message
[1])
340 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
341 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
343 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
347 if auteur
in self
.ops
:
349 if message
[1] in self
.quiet_channels
:
350 self
.quiet_channels
.remove(message
[1])
351 serv
.privmsg(auteur
,"Quiet channels : "+" ".join(self
.quiet_channels
))
352 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[successful]")
354 serv
.privmsg(auteur
,"Je ne me la ferme pas sur %s."%(message
[1]))
355 log(self
.serveur
,"priv",auteur
," ".join(message
)+"[failed]")
359 if auteur
in self
.overops
and len(message
)>2:
360 serv
.privmsg(message
[1]," ".join(message
[2:]))
361 log(self
.serveur
,"priv",auteur
," ".join(message
))
362 elif len(message
)<=2:
363 serv
.privmsg(auteur
,"Syntaxe : SAY <channel> <message>")
367 if auteur
in self
.overops
and len(message
)>2:
368 serv
.action(message
[1]," ".join(message
[2:]))
369 log(self
.serveur
,"priv",auteur
," ".join(message
))
370 elif len(message
)<=2:
371 serv
.privmsg(auteur
,"Syntaxe : DO <channel> <action>")
375 if auteur
in self
.overops
and len(message
)>2:
376 serv
.kick(message
[1],message
[2]," ".join(message
[3:]))
377 log(self
.serveur
,"priv",auteur
," ".join(message
))
378 elif len(message
)<=2:
379 serv
.privmsg(auteur
,"Syntaxe : KICK <channel> <pseudo> [<raison>]")
383 if auteur
in self
.overops
:
384 serv
.privmsg(auteur
," ".join(self
.ops
))
388 if auteur
in self
.overops
:
389 serv
.privmsg(auteur
," ".join(self
.overops
))
395 serv
.privmsg(auteur
,"Je n'ai pas compris. Essayez HELP…")
397 def on_pubmsg(self
, serv
, ev
):
398 auteur
= irclib
.nm_to_n(ev
.source())
400 message
= ev
.arguments()[0]
402 test
=bot_unicode(message
)
403 except UnicodeBotError
:
404 if config
.utf8_trigger
and not canal
in self
.quiet_channels
:
405 serv
.privmsg(canal
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
407 pour_moi
,message
=self
.pourmoi(serv
,message
)
408 if pour_moi
and message
.split()!=[]:
409 cmd
=message
.split()[0].lower()
411 args
=" ".join(message
.split()[1:])
414 if cmd
in ["meurs","die","crève"]:
415 if auteur
in self
.overops
:
416 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
418 elif cmd
== "reload":
419 if auteur
in self
.ops
:
420 log(self
.serveur
, canal
, auteur
, message
+"[successful]")
423 if auteur
in self
.overops
:
425 elif cmd
in ["part","leave","dégage","va-t-en"]:
426 if auteur
in self
.ops
and (not (canal
in self
.stay_channels
)
427 or auteur
in self
.overops
):
429 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
430 if canal
in self
.chanlist
:
431 self
.chanlist
.remove(canal
)
432 elif cmd
in ["deviens","pseudo"]:
433 if auteur
in self
.ops
:
436 log(self
.serveur
,canal
,auteur
,message
+"[successful]")
439 if not re
.match(u
'i.*',message
.decode("utf8").lower().strip(u
" ")) and canal
in self
.i_channels
:
440 serv
.kick(canal
, auteur
, u
"iKick".encode("utf8"))
442 def on_action(self
, serv
, ev
):
443 action
= ev
.arguments()[0]
444 auteur
= irclib
.nm_to_n(ev
.source())
445 channel
= ev
.target()
447 test
=bot_unicode(action
)
448 except UnicodeBotError
:
449 if config
.utf8_trigger
and not channel
in self
.quiet_channels
:
450 serv
.privmsg(channel
, (u
"%s: %s"%(auteur
,random
.choice(config
.utf8_fail_answers
))).encode("utf8"))
454 def on_kick(self
,serv
,ev
):
455 auteur
= irclib
.nm_to_n(ev
.source())
456 channel
= ev
.target()
457 victime
= ev
.arguments()[0]
458 raison
= ev
.arguments()[1]
459 if victime
==self
.nick
:
460 log(self
.serveur
,"%s kické de %s par %s (raison : %s)" %(victime
,channel
,auteur
,raison
))
464 def quitter(self
,chan
,leave_message
=None):
465 if leave_message
==None:
466 leave_message
=random
.choice(config
.leave_messages
)
467 self
.serv
.part(chan
,message
=leave_message
.encode("utf8"))
470 quit_message
=random
.choice(config
.quit_messages
)
471 self
.die(msg
=quit_message
.encode("utf8"))
474 return self
.serv
.get_nickname()
475 nick
=property(_getnick
)
477 def reload(self
, auteur
=None):
479 if auteur
in [None, "SIGHUP"]:
480 towrite
= "Config reloaded" + " (SIGHUP received)"*(auteur
== "SIGHUP")
481 for to
in config
.report_bugs_to
:
482 self
.serv
.privmsg(to
, towrite
)
483 log(self
.serveur
, towrite
)
485 self
.serv
.privmsg(auteur
,"Config reloaded")
490 def start_as_daemon(self
, outfile
):
491 sys
.stderr
= Logger(outfile
)
495 class Logger(object):
496 """Pour écrire ailleurs que sur stdout"""
497 def __init__(self
, filename
="ibot.full.log"):
498 self
.filename
= filename
500 def write(self
, message
):
501 f
= open(self
.filename
, "a")
507 print "Usage : ibot.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
508 print " --outfile sans --no-output ni --daemon n'a aucun effet"
511 if "--daemon" in sys
.argv
:
512 thisfile
= os
.path
.realpath(__file__
)
513 thisdirectory
= thisfile
.rsplit("/", 1)[0]
514 os
.chdir(thisdirectory
)
518 if "debug" in sys
.argv
or "--debug" in sys
.argv
:
522 if "--quiet" in sys
.argv
:
523 config
.debug_stdout
=False
524 serveurs
={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
525 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
526 if "--no-output" in sys
.argv
or "--daemon" in sys
.argv
:
527 outfile
= "/var/log/bots/ibot.full.log"
530 if arg
[0].strip('-') in ["out", "outfile", "logfile"]:
532 sys
.stdout
= Logger(outfile
)
534 serveur
=serveurs
[serveur
]
536 print "Server Unknown : %s"%(serveur)
538 ibot
=Ibot(serveur
,debug
)
539 # Si on reçoit un SIGHUP, on reload la config
540 def sighup_handler(signum
, frame
):
541 ibot
.reload("SIGHUP")
542 signal
.signal(signal
.SIGHUP
, sighup_handler
)
544 child_pid
= os
.fork()
547 ibot
.start_as_daemon(outfile
)
549 # on enregistre le pid du bot
550 pidfile
= "/var/run/bots/ibot.pid"
553 if arg
[0].strip('-') in ["pidfile"]:
555 f
= open(pidfile
, "w")
556 f
.write("%s\n" % child_pid
)
561 if __name__
== "__main__":