]> gitweb.pimeys.fr Git - bots/skeleton.git/blob - skeleton.py
more PEP8 (oubli de " - " et " + ")
[bots/skeleton.git] / skeleton.py
1 #!/usr/bin/python
2 # -*- encoding: utf-8 -*-
3
4 """ Codé par 20-100
5
6 Un bot IRC qui ne fait rien. Base pour en coder un vrai.
7 """
8
9 import irclib
10 import ircbot
11 import random
12 import time
13 import re
14 from commands import getstatusoutput as ex
15
16 # on récupère la config
17 import config
18
19
20 def get_config_logfile(serveur):
21 """Renvoie le nom du fichier de log en fonction du serveur."""
22 serveurs = {"acoeur.crans.org" : "acoeur", "irc.crans.org" : "crans"}
23 return config.logfile_template % (serveurs[serveur])
24
25 def get_filesize():
26 """Récupère la taille de ce fichier."""
27 return ex("ls -s %s" % (config.thisfile))[1].split()[0]
28
29 def log(serveur, channel, auteur=None, message=None):
30 f = open(get_config_logfile(serveur), "a")
31 if auteur == message == None:
32 # alors c'est que c'est pas un channel mais juste une ligne de log
33 chain = "%s %s" % (time.strftime("%F %T"), channel)
34 else:
35 chain = "%s [%s:%s] %s" % (time.strftime("%F %T"), channel, auteur, message)
36 f.write(chain + "\n")
37 if config.debug_stdout:
38 print chain
39 f.close()
40
41 def is_something(chain, matches, avant=u".*(?:^| )", apres=u"(?:$|\.| |,|;).*", case_sensitive=False, debug=False):
42 if case_sensitive:
43 chain = unicode(chain, "utf8")
44 else:
45 chain = unicode(chain, "utf8").lower()
46 allmatches = "(" + "|".join(matches) + ")"
47 reg = (avant + allmatches + apres).lower()
48 o = re.match(reg, chain)
49 return o
50
51 def is_insult(chain, debug=True):
52 return is_something(chain, config.insultes, avant=".*(?:^| |')")
53 def is_not_insult(chain):
54 chain = unicode(chain, "utf8")
55 insult_regexp = u"(" + u"|".join(config.insultes) + u")"
56 middle_regexp = u"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
57 reg = ".*pas %s%s.*" % (middle_regexp, insult_regexp)
58 if re.match(reg, chain):
59 return True
60 else:
61 return False
62 def is_compliment(chain, debug=True):
63 return is_something(chain, config.compliment_triggers, avant=".*(?:^| |')")
64 def is_tag(chain):
65 return is_something(chain, config.tag_triggers)
66 def is_tesla(chain):
67 return is_something(chain, config.tesla_triggers, avant=u"^", apres=u"$", debug=True)
68 def is_merci(chain):
69 return is_something(chain, config.merci_triggers)
70 def is_tamere(chain):
71 return is_something(chain, config.tamere_triggers)
72 def is_bad_action_trigger(chain, pseudo):
73 return is_something(chain, config.bad_action_triggers, avant=u"^",
74 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*" % (pseudo))
75 def is_good_action_trigger(chain, pseudo):
76 return is_something(chain, config.good_action_triggers, avant=u"^",
77 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*" % (pseudo))
78 def is_bonjour(chain):
79 return is_something(chain, config.bonjour_triggers, avant=u"^")
80 def is_bonne_nuit(chain):
81 return is_something(chain, config.bonne_nuit_triggers, avant=u"^")
82 def is_pan(chain):
83 return re.match(u"^(pan|bim|bang)( .*)?$", unicode(chain, "utf8").lower().strip())
84
85 def is_time(conf):
86 _, _, _, h, m, s, _, _, _ = time.localtime()
87 return (conf[0], 0, 0)<(h, m, s)<(conf[1], 0, 0)
88 def is_day():
89 return is_time(config.daytime)
90 def is_night():
91 return is_time(config.nighttime)
92
93
94 class UnicodeBotError(Exception):
95 pass
96 def bot_unicode(chain):
97 try:
98 unicode(chain, "utf8")
99 except UnicodeDecodeError as exc:
100 raise UnicodeBotError
101
102 class Skeleton(ircbot.SingleServerIRCBot):
103 def __init__(self, serveur, debug=False):
104 temporary_pseudo = config.irc_pseudo + str(random.randrange(10000, 100000))
105 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
106 temporary_pseudo, "Ceci est l'ircname du bot", 10)
107 self.debug = debug
108 self.serveur = serveur
109 self.overops = config.overops
110 self.ops = self.overops + config.ops
111 self.chanlist = config.chanlist
112 self.stay_channels = config.stay_channels
113 self.quiet_channels = config.quiet_channels
114 self.last_perdu = 0
115
116 def give_me_my_pseudo(self, serv):
117 serv.privmsg("NickServ", "RECOVER %s %s" % (config.irc_pseudo, config.irc_password))
118 serv.privmsg("NickServ", "RELEASE %s %s" % (config.irc_pseudo, config.irc_password))
119 time.sleep(0.3)
120 serv.nick(config.irc_pseudo)
121
122 def on_welcome(self, serv, ev):
123 self.serv = serv # ça serv ira :)
124 self.give_me_my_pseudo(serv)
125 serv.privmsg("NickServ", "identify %s" % (config.irc_password))
126 log(self.serveur, "Connected")
127 if self.debug:
128 self.chanlist = ["#bot"]
129 for c in self.chanlist:
130 log(self.serveur, "JOIN %s" % (c))
131 serv.join(c)
132
133 def pourmoi(self, serv, message):
134 """renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
135 pseudo = self.nick
136 size = len(pseudo)
137 if message[:size] == pseudo and len(message)>size and message[size] == ":":
138 return (True, message[size + 1:].lstrip(" "))
139 else:
140 return (False, message)
141
142 def on_privmsg(self, serv, ev):
143 message = ev.arguments()[0]
144 auteur = irclib.nm_to_n(ev.source())
145 try:
146 bot_unicode(message)
147 except UnicodeBotError:
148 if config.utf8_trigger:
149 serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8"))
150 return
151 message = message.split()
152 cmd = message[0].lower()
153 notunderstood = False
154 if cmd == "help":
155 helpdico = {"help":["""HELP <commande>
156 Affiche de l'aide sur la commande""", None, None],
157 "join": [None, """JOIN <channel>
158 Me fait rejoindre le channel""", None],
159 "leave": [None, """LEAVE <channel>
160 Me fait quitter le channel (sauf s'il est dans ma stay_list).""", None],
161 "quiet": [None, """QUIET <channel>
162 Me rend silencieux sur le channel.""", None],
163 "noquiet": [None, """NOQUIET <channel>
164 Me rend la parole sur le channel.""", None],
165 "lost": [None, """LOST <channel>
166 Me fait perdre sur le channel.""", None],
167 "say": [None, None, """SAY <channel> <message>
168 Me fait parler sur le channel."""],
169 "do": [None, None, """DO <channel> <action>
170 Me fait faitre une action (/me) sur le channel."""],
171 "stay": [None, None, """STAY <channel>
172 Ajoute le channel à ma stay_list."""],
173 "nostay": [None, None, """NOSTAY <channel>
174 Retire le channel de ma stay_list."""],
175 "ops": [None, None, """OPS
176 Affiche la liste des ops."""],
177 "overops": [None, None, """OVEROPS
178 Affiche la liste des overops."""],
179 "kick": [None, None, """KICK <channel> <pseudo> [<raison>]
180 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
181 "die": [None, None, """DIE
182 Me déconnecte du serveur IRC."""]
183 }
184 helpmsg_default = "Liste des commandes disponibles :\nHELP"
185 helpmsg_ops = " JOIN LEAVE QUIET NOQUIET LOST RECONNECT"
186 helpmsg_overops = " SAY DO STAY NOSTAY OPS OVEROPS KICK DIE"
187 op, overop = auteur in self.ops, auteur in self.overops
188 if len(message) == 1:
189 helpmsg = helpmsg_default
190 if op:
191 helpmsg += helpmsg_ops
192 if overop:
193 helpmsg += helpmsg_overops
194 else:
195 helpmsgs = helpdico.get(message[1].lower(), ["Commande inconnue.", None, None])
196 helpmsg = helpmsgs[0]
197 if op and helpmsgs[1]:
198 if helpmsg:
199 helpmsg += "\n" + helpmsgs[1]
200 else:
201 helpmsg = helpmsgs[1]
202 if overop and helpmsgs[2]:
203 if helpmsg:
204 helpmsg += "\n" + helpmsgs[2]
205 else:
206 helpmsg = helpmsgs[2]
207 for ligne in helpmsg.split("\n"):
208 serv.privmsg(auteur, ligne)
209 elif cmd == "join":
210 if auteur in self.ops:
211 if len(message)>1:
212 if message[1] in self.chanlist:
213 serv.privmsg(auteur, "Je suis déjà sur %s" % (message[1]))
214 else:
215 serv.join(message[1])
216 self.chanlist.append(message[1])
217 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
218 log(self.serveur, "priv", auteur, " ".join(message))
219 else:
220 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
221 else:
222 notunderstood = True
223 elif cmd == "leave":
224 if auteur in self.ops and len(message)>1:
225 if message[1] in self.chanlist:
226 if not (message[1] in self.stay_channels) or auteur in self.overops:
227 self.quitter(message[1], " ".join(message[2:]))
228 self.chanlist.remove(message[1])
229 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
230 else:
231 serv.privmsg(auteur, "Non, je reste !")
232 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
233 else:
234 serv.privmsg(auteur, "Je ne suis pas sur %s" % (message[1]))
235 else:
236 notunderstood = True
237 elif cmd == "stay":
238 if auteur in self.overops:
239 if len(message)>1:
240 if message[1] in self.stay_channels:
241 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
242 serv.privmsg(auteur, "Je stay déjà sur %s." % (message[1]))
243 else:
244 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
245 self.stay_channels.append(message[1])
246 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
247 else:
248 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
249 else:
250 notunderstood = True
251 elif cmd == "nostay":
252 if auteur in self.overops:
253 if len(message)>1:
254 if message[1] in self.stay_channels:
255 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
256 self.stay_channels.remove(message[1])
257 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
258 else:
259 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
260 serv.privmsg(auteur, "Je ne stay pas sur %s." % (message[1]))
261
262 else:
263 notunderstood = True
264 elif cmd == "die":
265 if auteur in self.overops:
266 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
267 self.mourir()
268 else:
269 notunderstood = True
270 elif cmd == "quiet":
271 if auteur in self.ops:
272 if len(message)>1:
273 if message[1] in self.quiet_channels:
274 serv.privmsg(auteur, "Je me la ferme déjà sur %s" % (message[1]))
275 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
276 else:
277 self.quiet_channels.append(message[1])
278 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
279 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
280 else:
281 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
282 else:
283 notunderstood = True
284 elif cmd == "noquiet":
285 if auteur in self.ops:
286 if len(message)>1:
287 if message[1] in self.quiet_channels:
288 self.quiet_channels.remove(message[1])
289 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
290 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
291 else:
292 serv.privmsg(auteur, "Je ne me la ferme pas sur %s." % (message[1]))
293 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
294 else:
295 notunderstood = True
296 elif cmd == "say":
297 if auteur in self.overops and len(message)>2:
298 serv.privmsg(message[1], " ".join(message[2:]))
299 log(self.serveur, "priv", auteur, " ".join(message))
300 elif len(message) <= 2:
301 serv.privmsg(auteur, "Syntaxe : SAY <channel> <message>")
302 else:
303 notunderstood = True
304 elif cmd == "do":
305 if auteur in self.overops and len(message)>2:
306 serv.action(message[1], " ".join(message[2:]))
307 log(self.serveur, "priv", auteur, " ".join(message))
308 elif len(message) <= 2:
309 serv.privmsg(auteur, "Syntaxe : DO <channel> <action>")
310 else:
311 notunderstood = True
312 elif cmd == "kick":
313 if auteur in self.overops and len(message)>2:
314 serv.kick(message[1], message[2], " ".join(message[3:]))
315 log(self.serveur, "priv", auteur, " ".join(message))
316 elif len(message) <= 2:
317 serv.privmsg(auteur, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
318 else:
319 notunderstood = True
320 elif cmd == "ops":
321 if auteur in self.overops:
322 serv.privmsg(auteur, " ".join(self.ops))
323 else:
324 notunderstood = True
325 elif cmd == "overops":
326 if auteur in self.overops:
327 serv.privmsg(auteur, " ".join(self.overops))
328 else:
329 notunderstood = True
330 else:
331 notunderstood = True
332 if notunderstood:
333 serv.privmsg(auteur, "Je n'ai pas compris. Essayez HELP…")
334
335 def on_pubmsg(self, serv, ev):
336 auteur = irclib.nm_to_n(ev.source())
337 canal = ev.target()
338 message = ev.arguments()[0]
339 try:
340 bot_unicode(message)
341 except UnicodeBotError:
342 if config.utf8_trigger and not canal in self.quiet_channels:
343 serv.privmsg(canal, (u"%s: %s" % (auteur, random.choice(config.utf8_fail_answers))).encode("utf8"))
344 return
345 pour_moi, message = self.pourmoi(serv, message)
346 if pour_moi and message.split() != []:
347 cmd = message.split()[0].lower()
348 try:
349 args = " ".join(message.split()[1:])
350 except:
351 args = ""
352 if cmd in ["meurs", "die", "crève"]:
353 if auteur in self.overops:
354 log(self.serveur, canal, auteur, message + "[successful]")
355 self.mourir()
356 else:
357 serv.privmsg(canal, ("%s: %s" % (auteur, random.choice(config.quit_fail_messages))).encode("utf8"))
358 log(self.serveur, canal, auteur, message + "[failed]")
359
360 elif cmd in ["part", "leave", "dégage", "va-t-en", "tut'tiresailleurs, c'estmesgalets"]:
361 if auteur in self.ops and (not (canal in self.stay_channels)
362 or auteur in self.overops):
363 self.quitter(canal)
364 log(self.serveur, canal, auteur, message + "[successful]")
365 if canal in self.chanlist:
366 self.chanlist.remove(canal)
367 else:
368 serv.privmsg(canal, ("%s: %s" % (auteur, random.choice(config.leave_fail_messages))).encode("utf8"))
369 log(self.serveur, canal, auteur, message + "[failed]")
370
371 elif cmd in ["deviens", "pseudo"]:
372 if auteur in self.ops:
373 become = args
374 serv.nick(become)
375 log(self.serveur, canal, auteur, message + "[successful]")
376
377 if cmd in ["meur", "meurt", "meurre", "meurres"] and not canal in self.quiet_channels:
378 serv.privmsg(canal, '%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)' % (auteur))
379 elif cmd in ["ping"] and not canal in self.quiet_channels:
380 serv.privmsg(canal, "%s: pong" % (auteur))
381 if is_insult(message) and not canal in self.quiet_channels:
382 if is_not_insult(message):
383 answer = random.choice(config.compliment_answers)
384 for ligne in answer.split("\n"):
385 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
386 else:
387 answer = random.choice(config.insultes_answers)
388 for ligne in answer.split("\n"):
389 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
390 elif is_compliment(message) and not canal in self.quiet_channels:
391 answer = random.choice(config.compliment_answers)
392 for ligne in answer.split("\n"):
393 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
394 if is_tesla(message) and not canal in self.quiet_channels:
395 l1, l2 = config.tesla_answers, config.tesla_actions
396 n1, n2 = len(l1), len(l2)
397 i = random.randrange(n1 + n2)
398 if i >= n1:
399 serv.action(canal, l2[i - n1].encode("utf8"))
400 else:
401 serv.privmsg(canal, "%s: %s" % (auteur, l1[i].encode("utf8")))
402 if is_tamere(message) and not canal in self.quiet_channels:
403 answer = random.choice(config.tamere_answers)
404 for ligne in answer.split("\n"):
405 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
406 if is_tag(message) and not canal in self.quiet_channels:
407 if auteur in self.ops:
408 action = random.choice(config.tag_actions)
409 serv.action(canal, action.encode("utf8"))
410 self.quiet_channels.append(canal)
411 else:
412 answer = random.choice(config.tag_answers)
413 for ligne in answer.split("\n"):
414 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
415 if is_merci(message):
416 answer = random.choice(config.merci_answers)
417 for ligne in answer.split("\n"):
418 serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
419 if is_bonjour(message) and not canal in self.quiet_channels:
420 if is_night():
421 answer = random.choice(config.night_answers)
422 elif is_day():
423 answer = random.choice(config.bonjour_answers)
424 else:
425 answer = random.choice(config.bonsoir_answers)
426 serv.privmsg(canal, answer.format(auteur).encode("utf8"))
427 if is_bonne_nuit(message) and not canal in self.quiet_channels:
428 answer = random.choice(config.bonne_nuit_answers)
429 serv.privmsg(canal, answer.format(auteur).encode("utf8"))
430 else:
431 if not canal in self.quiet_channels:
432 mypseudo = self.nick
433 if re.match((u"^(" + u"|".join(config.bonjour_triggers)
434 + ur")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
435 ).format(mypseudo).lower(), message.decode("utf8").strip().lower()):
436 answer = random.choice(config.bonjour_answers)
437 serv.privmsg(canal, answer.format(auteur).encode("utf8"))
438
439 def on_action(self, serv, ev):
440 action = ev.arguments()[0]
441 auteur = irclib.nm_to_n(ev.source())
442 channel = ev.target()
443 try:
444 bot_unicode(action)
445 except UnicodeBotError:
446 if config.utf8_trigger and not channel in self.quiet_channels:
447 serv.privmsg(channel, (u"%s: %s" % (auteur, random.choice(config.utf8_fail_answers))).encode("utf8"))
448 return
449 mypseudo = self.nick
450
451 if is_bad_action_trigger(action, mypseudo) and not channel in self.quiet_channels:
452 l1, l2 = config.bad_action_answers, config.bad_action_actions
453 n1, n2 = len(l1), len(l2)
454 i = random.randrange(n1 + n2)
455 if i >= n1:
456 serv.action(channel, l2[i - n1].format(auteur).encode("utf8"))
457 else:
458 serv.privmsg(channel, l1[i].format(auteur).format(auteur).encode("utf8"))
459 if is_good_action_trigger(action, mypseudo) and not channel in self.quiet_channels:
460 l1, l2 = config.good_action_answers, config.good_action_actions
461 n1, n2 = len(l1), len(l2)
462 i = random.randrange(n1 + n2)
463 if i >= n1:
464 serv.action(channel, l2[i - n1].format(auteur).format(auteur).encode("utf8"))
465 else:
466 serv.privmsg(channel, l1[i].format(auteur).format(auteur).encode("utf8"))
467
468 def on_kick(self, serv, ev):
469 auteur = irclib.nm_to_n(ev.source())
470 channel = ev.target()
471 victime = ev.arguments()[0]
472 raison = ev.arguments()[1]
473 if victime == self.nick:
474 log(self.serveur, "%s kické de %s par %s (raison : %s)" % (victime, channel, auteur, raison))
475 time.sleep(2)
476 serv.join(channel)
477 l1, l2 = config.kick_answers, config.kick_actions
478 n1, n2 = len(l1), len(l2)
479 i = random.randrange(n1 + n2)
480 if i >= n1:
481 serv.action(channel, l2[i - n1].format(auteur).encode("utf8"))
482 else:
483 serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
484
485 def quitter(self, chan, leave_message=None):
486 if leave_message == None:
487 leave_message = random.choice(config.leave_messages)
488 self.serv.part(chan, message=leave_message.encode("utf8"))
489
490 def mourir(self):
491 quit_message = random.choice(config.quit_messages)
492 self.die(msg=quit_message.encode("utf8"))
493
494 def _getnick(self):
495 return self.serv.get_nickname()
496 nick = property(_getnick)
497
498
499 if __name__ == "__main__":
500 import sys
501 if len(sys.argv) == 1:
502 print "Usage : skeleton.py <serveur> [--debug]"
503 exit(1)
504 serveur = sys.argv[1]
505 if "debug" in sys.argv or "--debug" in sys.argv:
506 debug = True
507 else:
508 debug = False
509 if "--quiet" in sys.argv:
510 config.debug_stdout = False
511 serveurs = {"a♡":"acoeur.crans.org", "acoeur":"acoeur.crans.org", "acoeur.crans.org":"acoeur.crans.org",
512 "irc":"irc.crans.org", "crans":"irc.crans.org", "irc.crans.org":"irc.crans.org"}
513 try:
514 serveur = serveurs[serveur]
515 except KeyError:
516 print "Server Unknown : %s" % (serveur)
517 exit(404)
518 bot = Skeleton(serveur, debug)
519 bot.start()