]> gitweb.pimeys.fr Git - bots/ibot.git/blob - ibot.py
Modification de l'initscript pour qu'il recrée le dossier/fichier de PID si il n...
[bots/ibot.git] / ibot.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3
4 # Codé par 20-100 (commencé le 23/04/12)
5
6 # Un bot IRC qui, un jour, s'interfacera avec la Note Kfet 2015
7
8 import threading
9 import random
10 import time
11 import socket, ssl, json
12 import pickle
13 import re
14 import os
15 import signal
16 import sys
17
18 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
19 sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
20 import irclib
21 import ircbot
22
23 from commands import getstatusoutput as ex
24
25 # on récupère la config
26 import config
27
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
30 import os
31 config.thisfile= os.path.realpath( __file__ )
32
33 def get_config_logfile(serveur):
34 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
35 return config.logfile_template%(serveurs[serveur])
36
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)
42 else:
43 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
44 f.write(chain+"\n")
45 if config.debug_stdout:
46 print chain
47 f.close()
48
49 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
50 if case_sensitive:
51 chain=unicode(chain,"utf8")
52 else:
53 chain=unicode(chain,"utf8").lower()
54 allmatches="("+"|".join(matches)+")"
55 reg=(avant+allmatches+apres).lower()
56 o=re.match(reg,chain)
57 return o
58
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):
67 return True
68 else:
69 return False
70 def is_compliment(chain,debug=True):
71 return is_something(chain,config.compliment_triggers,avant=".*(?:^| |')")
72 def is_perdu(chain):
73 return is_something(chain,config.perdu)
74 def is_tag(chain):
75 return is_something(chain,config.tag_triggers)
76 def is_gros(chain):
77 return is_something(chain,config.gros)
78 def is_tesla(chain):
79 return is_something(chain,config.tesla_triggers,avant=u"^",apres=u"$",debug=True)
80 def is_merci(chain):
81 return is_something(chain,config.merci_triggers)
82 def is_tamere(chain):
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"^")
94 def is_pan(chain):
95 return re.match(u"^(pan|bim|bang)( .*)?$",unicode(chain,"utf8").lower().strip())
96
97 def is_time(conf):
98 _,_,_,h,m,s,_,_,_=time.localtime()
99 return (conf[0],0,0)<(h,m,s)<(conf[1],0,0)
100 def is_day():
101 return is_time(config.daytime)
102 def is_night():
103 return is_time(config.nighttime)
104
105
106 class UnicodeBotError(Exception):
107 pass
108
109 class CrashError(Exception):
110 """Pour pouvoir faire crasher le bot, parce que ça a l'air drôle"""
111 pass
112
113 def bot_unicode(chain):
114 try:
115 unicode(chain,"utf8")
116 except UnicodeDecodeError as exc:
117 raise UnicodeBotError
118
119
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)
125 self.debug=debug
126 self.serveur=serveur
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
134 self.last_perdu=0
135
136
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))
140 time.sleep(0.3)
141 serv.nick(config.irc_pseudo)
142
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")
148 if self.debug:
149 self.chanlist=["#bot"]
150 for c in self.chanlist:
151 log(self.serveur,"JOIN %s"%(c))
152 serv.join(c)
153
154 def pourmoi(self, serv, message):
155 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
156 pseudo=self.nick
157 size=len(pseudo)
158 if message[:size]==pseudo and len(message)>size and message[size]==":":
159 return (True,message[size+1:].lstrip(" "))
160 else:
161 return (False,message)
162
163 def on_privmsg(self, serv, ev):
164 message=ev.arguments()[0]
165 auteur = irclib.nm_to_n(ev.source())
166 try:
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"))
171 return
172 message=message.split()
173 cmd=message[0].lower()
174 notunderstood=False
175 if cmd=="help":
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
205 Me fait crasher"""]
206 }
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
211 if len(message)==1:
212 helpmsg=helpmsg_default
213 if op:
214 helpmsg+=helpmsg_ops
215 if overop:
216 helpmsg+=helpmsg_overops
217 else:
218 helpmsgs=helpdico.get(message[1].lower(),["Commande inconnue.",None,None])
219 helpmsg=helpmsgs[0]
220 if op and helpmsgs[1]:
221 if helpmsg:
222 helpmsg+="\n"+helpmsgs[1]
223 else:
224 helpmsg=helpmsgs[1]
225 if overop and helpmsgs[2]:
226 if helpmsg:
227 helpmsg+="\n"+helpmsgs[2]
228 else:
229 helpmsg=helpmsgs[2]
230 for ligne in helpmsg.split("\n"):
231 serv.privmsg(auteur,ligne)
232 elif cmd=="join":
233 if auteur in self.ops:
234 if len(message)>1:
235 if message[1] in self.chanlist:
236 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
237 else:
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))
242 else:
243 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
244 else:
245 notunderstood=True
246 elif cmd=="leave":
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]")
253 else:
254 serv.privmsg(auteur,"Non, je reste !")
255 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
256 else:
257 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
258 else:
259 notunderstood=True
260 elif cmd=="stay":
261 if auteur in self.overops:
262 if len(message)>1:
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]))
266 else:
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))
270 else:
271 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
272 else:
273 notunderstood=True
274 elif cmd=="nostay":
275 if auteur in self.overops:
276 if len(message)>1:
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))
281 else:
282 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
283 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
284
285 else:
286 notunderstood=True
287 elif cmd=="ichannel":
288 if auteur in self.ops:
289 if len(message)>1:
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]))
293 else:
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))
297 else:
298 serv.privmsg(auteur,"I-channels : "+" ".join(self.i_channels))
299 else:
300 notunderstood=True
301 elif cmd=="noichannel":
302 if auteur in self.ops:
303 if len(message)>1:
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))
308 else:
309 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
310 serv.privmsg(auteur,"%s n'est pas i-nazi."%(message[1]))
311
312 else:
313 notunderstood=True
314 elif cmd=="die":
315 if auteur in self.overops:
316 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
317 self.mourir()
318 else:
319 notunderstood=True
320 elif cmd=="crash":
321 if auteur in self.overops:
322 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
323 self.crash()
324 else:
325 notunderstood=True
326 elif cmd=="reload":
327 if auteur in self.ops:
328 self.reload(auteur)
329 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
330 else:
331 notunderstood=True
332 elif cmd=="quiet":
333 if auteur in self.ops:
334 if len(message)>1:
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]")
338 else:
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]")
342 else:
343 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
344 else:
345 notunderstood=True
346 elif cmd=="noquiet":
347 if auteur in self.ops:
348 if len(message)>1:
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]")
353 else:
354 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
355 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
356 else:
357 notunderstood=True
358 elif cmd=="say":
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>")
364 else:
365 notunderstood=True
366 elif cmd=="do":
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>")
372 else:
373 notunderstood=True
374 elif cmd=="kick":
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>]")
380 else:
381 notunderstood=True
382 elif cmd=="ops":
383 if auteur in self.overops:
384 serv.privmsg(auteur," ".join(self.ops))
385 else:
386 notunderstood=True
387 elif cmd=="overops":
388 if auteur in self.overops:
389 serv.privmsg(auteur," ".join(self.overops))
390 else:
391 notunderstood=True
392 else:
393 notunderstood=True
394 if notunderstood:
395 serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…")
396
397 def on_pubmsg(self, serv, ev):
398 auteur = irclib.nm_to_n(ev.source())
399 canal = ev.target()
400 message = ev.arguments()[0]
401 try:
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"))
406 return
407 pour_moi,message=self.pourmoi(serv,message)
408 if pour_moi and message.split()!=[]:
409 cmd=message.split()[0].lower()
410 try:
411 args=" ".join(message.split()[1:])
412 except:
413 args=""
414 if cmd in ["meurs","die","crève"]:
415 if auteur in self.overops:
416 log(self.serveur,canal,auteur,message+"[successful]")
417 self.mourir()
418 elif cmd == "reload":
419 if auteur in self.ops:
420 log(self.serveur, canal, auteur, message+"[successful]")
421 self.reload(canal)
422 elif cmd == "crash":
423 if auteur in self.overops:
424 self.crash()
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):
428 self.quitter(canal)
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:
434 become=args
435 serv.nick(become)
436 log(self.serveur,canal,auteur,message+"[successful]")
437
438 else:
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"))
441
442 def on_action(self, serv, ev):
443 action = ev.arguments()[0]
444 auteur = irclib.nm_to_n(ev.source())
445 channel = ev.target()
446 try:
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"))
451 return
452 mypseudo=self.nick
453
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))
461 time.sleep(2)
462 serv.join(channel)
463
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"))
468
469 def mourir(self):
470 quit_message=random.choice(config.quit_messages)
471 self.die(msg=quit_message.encode("utf8"))
472
473 def _getnick(self):
474 return self.serv.get_nickname()
475 nick=property(_getnick)
476
477 def reload(self, auteur=None):
478 reload(config)
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)
484 else:
485 self.serv.privmsg(auteur,"Config reloaded")
486
487 def crash(self):
488 raise CrashError
489
490 def start_as_daemon(self, outfile):
491 sys.stderr = Logger(outfile)
492 self.start()
493
494
495 class Logger(object):
496 """Pour écrire ailleurs que sur stdout"""
497 def __init__(self, filename="ibot.full.log"):
498 self.filename = filename
499
500 def write(self, message):
501 f = open(self.filename, "a")
502 f.write(message)
503 f.close()
504
505 def main():
506 if len(sys.argv)==1:
507 print "Usage : ibot.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
508 print " --outfile sans --no-output ni --daemon n'a aucun effet"
509 exit(1)
510 serveur=sys.argv[1]
511 if "--daemon" in sys.argv:
512 thisfile = os.path.realpath(__file__)
513 thisdirectory = thisfile.rsplit("/", 1)[0]
514 os.chdir(thisdirectory)
515 daemon = True
516 else:
517 daemon = False
518 if "debug" in sys.argv or "--debug" in sys.argv:
519 debug=True
520 else:
521 debug=False
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"
528 for arg in sys.argv:
529 arg = arg.split("=")
530 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
531 outfile = arg[1]
532 sys.stdout = Logger(outfile)
533 try:
534 serveur=serveurs[serveur]
535 except KeyError:
536 print "Server Unknown : %s"%(serveur)
537 exit(404)
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)
543 if daemon:
544 child_pid = os.fork()
545 if child_pid == 0:
546 os.setsid()
547 ibot.start_as_daemon(outfile)
548 else:
549 # on enregistre le pid du bot
550 pidfile = "/var/run/bots/ibot.pid"
551 for arg in sys.argv:
552 arg = arg.split("=")
553 if arg[0].strip('-') in ["pidfile"]:
554 pidfile = arg[1]
555 f = open(pidfile, "w")
556 f.write("%s\n" % child_pid)
557 f.close()
558 else:
559 ibot.start()
560
561 if __name__ == "__main__":
562 main()