]> gitweb.pimeys.fr Git - bots/themis.git/blob - themis.py
C'est cool d'être daemonizable
[bots/themis.git] / themis.py
1 #!/usr/bin/python
2 # -*- coding:utf8 -*-
3 # Codé par 20-100 (commencé le 21/06/12)
4
5 # Un bot IRC pour kicker à tour de bras de #déprime
6
7 import threading
8 import random
9 import time
10 import socket, ssl, json
11 import pickle
12 import re
13 import os
14 import signal
15 import sys
16
17 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
18 sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
19 import irclib
20 import ircbot
21
22 import sys
23 config_debug_stdout=True
24 if "--quiet" in sys.argv:
25 config_debug_stdout=False
26
27 config_irc_password="YouMustObeyGaétan"
28 config_irc_pseudo="Themis"
29 config_chanlist=["#déprime"]
30 config_stay_channels=["#déprime"]
31 config_quiet_channels=[]
32 config_logfile_template="themis.%s.log"
33 def get_config_logfile(serveur):
34 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
35 return config_logfile_template%(serveurs[serveur])
36 config_overops=["[20-100]","Gaetan"]
37 config_ops=[]
38 config_report_bugs_to=["[20-100]"]
39
40 # config UTF8-fail
41 config_utf8_fail = [u"Ton encodage me déprime…"]
42 config_utf8_trigger = True
43 # config "tu m'traites ?"
44 config_insultes=[u"conna(rd|sse)",u"pute",u"con(|ne)",u"enf(oiré|lure)",
45 u"sal(op(|e(|rie)|ard)|aud)",u"p(e|')tite bite",u"imbécile",u"idiot",u"stupid(|e)",u"débile",u"crétin",
46 u"pétasse",u"enculé",u"chagasse",u"cagole",u"abruti",u"ahuri",u"analphabète",u"andouille",
47 u"atardé",u"avorton",u"bachibouzouk",u"(balais|brosse) (de|à) chiotte(|s)",
48 u"batard",u"blaireau",u"bouffon",u"branque",u"bouseux",u"branleur",u"catin",u"chacal",
49 u"charogne",u"chiant(|e)",u"chieur",u"cochon",u"coprophage",u"couillon",u"crapule",u"crevard",
50 u"cruche",u"cuistre",u"ducon",u"décérébré",
51 u"emmerdeur",u"feignasse",u"fainéant",u"fourbe",u"freluquet",u"frigide",
52 u"garce",u"glandu",u"gogol",u"goujat",u"gourdasse",u"gredin",u"gringalet",u"grognasse",
53 u"naze",u"truie",u"iconoclaste",
54 u"peigne(-|)cul",u"ignare",u"illétré",u"lèche(|-)cul",u"malotru",u"motherfucker",u"nabot",u"nigaud",
55 u"nul",u"escroc",u"pouffiasse",u"pourriture",u"raclure",u"relou",u"sagouin",u"putain",
56 u"péripatéticienne"]
57 config_insultes_answers=[
58 u"Oh non ! Quelle insulte ! Je crois que je ne m'en relèverai jamais…\nEnfin presque.",
59 u"J'entends comme un vague murmure, vous disiez ?",
60 u"Je vais prendre ça pour un compliment.",
61 u"Vous savez, pour vous c'est peut-être une insulte, mais pour moi ce n'est qu'une suite de 0 et de 1…",
62 u"Permettez-moi de vous retourner le compliment.",
63 u"Votre indélicatesse vous sied à ravir.",
64 u"Parfois, je me demande pourquoi je fais encore ce métier…",
65 u"Le saviez-vous : l'invective ne déshonore que son auteur.",
66 u"Le saviez-vous : vous perdez plus de temps à m'insulter qu'à vous taire.",
67 u"Mais je ne vous permets pas ! Enfin, pas comme ça…"]
68
69 # config "jeu", d'ailleurs, j'ai perdu.
70 config_premier_groupe_terminaisons=u"(e|es|ons|ez|ent|er(|ai|as|a|ons|ez|ont)|(|er)(ais|ait|ions|iez|aient)|(a(i|s|)|â(mes|tes|t)|èrent)|ass(e(|s|nt)|i(ons|ez))|é(|s|e|es))"
71 config_regexp_etre=u"(être|suis|e(s|t)|so(mmes|nt)|êtes|(ét|ser)(ai(s|t|ent)|i(ons|ent)|)|ser(ai|as|a|ons|ez|ont)|so(i(s|t|ent)|y(ons|ez))|f(u(s|t|rent)|û(mes|tes|t))|fuss(e(|s|nt)|i(ons|ez))|étant)"
72 config_regexp_etre_avec_c=u"c'(e(s|st)|étai(t|ent))"
73 config_regexp_faire=u"fais"
74 config_perdu=[u"perd(|s|ons|ez|ent|r(e|ai|as|a|ons|ez|ont)|(|r)(ais|ait|ions|iez|aient))"
75 u"perd(i(s|t|rent)|î(mes|tes|t))", # oui, j'ai inclus qu'il perdît
76 u"perdiss(e(|s|nt)|i(ons|ez))",
77 u"perdu(|s|e|es)",u"perdant(|s|e|es)",u"perte(|s)",
78
79 u"(gagn|trouv)"+config_premier_groupe_terminaisons,u"gagnant(|s|e|es)",u"gain(|s)",
80
81 u"trouvant",u"trouvaille(|s)",
82
83 u"victoire(|s)",u"vaincu(|s|e|es)",
84 u"loose",u"lost",u"looser(|s)",u"win(|ner)(|s)",
85 u"jeu(|x)",u"game(|s)"]
86 config_time_between_perdu_trigger=3600*3 #temps moyen pour perdre en l'absence de trigger
87 config_time_between_perdu_trigger_delta = 30*60 #marge autorisée autour de ^^^
88 config_time_between_perdu=30*60 #temps pendant lequel on ne peut pas perdre
89
90 # config "tais-toi"
91 config_tag_triggers=[u"t(|a)g",u"ta gueule",u"la ferme",u"ferme( |-)la",u"tais-toi",u"chut",u"tu fais trop de bruit",u"tu parles trop"]
92 config_tag_actions=[u"se tait",u"se tient coi"]
93 config_tag_answers=[
94 u"Ç'aurait été avec plaisir, mais je ne crois pas que vous puissiez vous passer de mes services.",
95 u"Dès que cela sera utile.",
96 u"Une autre fois, peut-être.",
97 u"Si je me tais, qui vous rappellera combien vous me devez ?",
98 u"J'aurais aimé accéder à votre requête, mais après mûre réflexion, j'en ai perdu l'envie.",
99 u"Je ne ressens pas de besoin irrésistible de me taire, navré."]
100
101 # config ping
102 config_tesla_triggers=[u"t('|u )es là \?",u"\?",u"plop \?",u"plouf \?"]
103 config_tesla_answers=[
104 u"Oui, je suis là.",
105 ]
106 config_tesla_actions=[u"déprime",u"est prêt à kicker les gens heureux"]
107
108 # config en cas de non-insulte
109 config_compliment_triggers=[u"gentil",u"cool",u"sympa",u"efficace"]
110 config_compliment_answers=[
111 u"Merci, c'est gentil de votre part. :)",
112 u"Permettez-moi de vous retourner le compliment, sans ironie cette fois.",
113 u"Je vous remercie.",
114 u"C'est trop d'honneur.",
115 u"Vous êtes bien aimable."
116 ]
117
118 # config merci
119 config_merci_triggers=[u"merci",u"remercie",u"thx",u"thank(|s)"]
120 config_merci_answers=[u"Mais de rien.",u"À votre service. ;)",u"Quand vous voulez. :)",
121 u"Tout le plaisir est pour moi."]
122
123 # config "ta mère"
124 config_tamere_triggers=[u"ta mère"]
125 config_tamere_answers=[u"Laissez donc ma mère en dehors de ça !",
126 u"Puis-je préciser que je n'ai pas de mère ? Seulement deux pères…",
127 u"""Un certain Max chantait "♩ J'ai vu ta mère sur chat rouleeeeeeette ♫", vous êtes de sa famille ?""",
128 u"""N'avait-on pas dit "pas les mamans" ?"""]
129
130 # config pour les actions désagréables
131 config_bad_action_triggers=[u"(frappe|cogne|tape)(| sur)",u"(démolit|dégomme|fouette|agresse|tabasse)",
132 u"(vomit|pisse|chie|crache) sur",u"slap(|s)"]
133 config_bad_action_answers=[
134 u"Je ne peux pas dire que j'apprécie, mais je l'ai sans doute bien mérité.",
135 u"{}: Pourquoi tant de violence en ce monde si doux ?",
136 u"""Si je n'étais pas aussi prude, je dirais "Mais euh…", cependant, je me contenterai de hausser un sourcil.""",
137 u"{}: J'aurais préféré que vous ne fassiez pas cela en public.",
138 u"{}: Entre nous, cela vous gratifie-t-il ?",
139 u"{}: Une telle relation entre nous deux n'est pas saine, revenons à quelque chose de plus conventionnel. :D",
140 u"J'ai la désagréable impression que {} cherche comment tuer le temps en ce moment…"
141 ]
142 config_bad_action_actions=[u"prend de la distance, par précaution…",u"esquive",u"est bon pour prendre une semaine de repos… virtuel !",u"n'aime pas servir de souffre douleur, mais n'a malheureusement pas le choix", u"s'en souviendra sans doute longtemps… de quoi parlait-on déjà ?"]
143
144 # config pour les actions agréables
145 config_good_action_triggers=[u"fait (:?des bisous|un c(?:â|a)lin|des c(?:â|a)lins) à",u"embrasse",u"c(?:â|a)line",u"caresse"]
146 config_good_action_answers=[u":D",u"{}: Moi aussi je vous aime. ♡",u"Tant de délicatesse ne saurait être ignorée !",u"Pour une fois que quelqu'un me considère à ma juste valeur…"]
147 config_good_action_actions=[u"ronronne",u"aimerait exprimer avec des mots simples le bonheur que {} lui procure !",u"éprouve une joie indescriptible",u"apprécie que des personnes comme {} soient sur IRC, sans quoi il n'y aurait sans doute jamais personne pour tenir compte de lui"]
148
149 # config bonjour/bonsoir/que fais-tu encore debout à cette heure, gros sale !
150 config_bonjour_triggers=[u"(s|)(a|'|)lu(t|)",u"hello",u"pl(o|i)p",u"pr(ou|ü)t",u"bonjour",u"bonsoir",u"coucou"]
151 config_bonjour_answers=[u"Bien le bonjour, {}.",u"Bonjour {}.",u"{}: bonjour.",u"{}: Quel beau temps aujourd'hui (arrêtez-moi si je me trompe) !",u"Meteo: Cachan"]
152 config_bonsoir_answers=[u"Bonsoir {} !",u"{}: bonsoir.",u"Quel beau te… euh… bonsoir !",u"{}: Je cherche désespérément une formule pour vous dire bonsoir, mais j'avoue que mon lexique est un peu… limité."]
153 config_night_answers=[u"{}: vous m'avez fait peur, je m'étais assoupi !", u"Debout à une heure pareille, {} ? Que vous arrive-t-il ?",u"Vous venez prendre la relève, {} ?"]
154 config_daytime = [7,18]
155 config_nighttime = [3, 6]
156
157 # config dodo
158 config_bonne_nuit_triggers=[u"bonne nuit",u"'?nite",u"'?nuit",u"'?night",u"good night",u"'?nenuit"]
159 config_bonne_nuit_answers=[u"{}: thanks, make sweet dreams tonight ! ;)",u"Bonne nuit {}.",u"À demain {}. :)",u"{}: si seulement j'avais le droit de dormir… enfin, bonne nuit !",u"{}: à vous aussi !"]
160
161 # config quelqu'un est encore en train d'abuser de ses droits.
162 config_kick_answers=[u"Suis-je de trop ici ?",u"{}: je m'excuse pour ce bruit indu qui a stimulé votre colère",u"{} a le /kick facile, sans doute la fatigue.",u"{}: j'ai l'impression que vous n'allez pas bien aujourd'hui, vous vous en prenez à un robot !"]
163 config_kick_actions=[u"sera désormais exemplaire",u"prépare une lettre d'excuses à {}",u"essaiera de ne plus s'attirer les foudres de {}",u"croyait avoir tout bien fait… cruelle déception."]
164
165 # config on m'a demandé de mourir/partir
166 config_quit_messages=[u"J'ai enfin trouvé une corde et un tabouret"]
167 config_leave_messages=config_quit_messages
168 config_quit_fail_messages=[u"Tu rêves là."]
169 config_leave_fail_messages=config_quit_fail_messages
170
171
172 # config de kick
173 config_kick_channels=config_chanlist
174
175 config_smileys = [ur':(-|o)?\)', u'\^(_|\.)?\^', u':-?(p|P)', u'=(\)|D|p|P)', ur'\\o/', ur':-?D', ur'x(\)|D)', u'krkr', ur':-?(\]|>)', ur'(<|d|q|\(|\[)(:|=)', u'mdr']
176 config_anglicismes = [u"wtf", u"ftfy", u"it works?", u"fyi", u"kill[^ ]*", u"kick[^ ]*", u"chan(nel)?", u"join",
177 u"btw", u"lmgtfy", u"rtfm", u"asap", u"afaik", u"shit", u"damn", u"fuck", u"bitch", u"updat(e|ed|ing)", u"lol", u"buffer[^ ]*", u"rofl"]
178
179 def log(serveur,channel,auteur=None,message=None):
180 f=open(get_config_logfile(serveur),"a")
181 if auteur==message==None:
182 # alors c'est que c'est pas un channel mais juste une ligne de log
183 chain="%s %s"%(time.strftime("%F %T"),channel)
184 else:
185 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
186 f.write(chain+"\n")
187 if config_debug_stdout:
188 print chain
189 f.close()
190
191 reg_is_smiley = re.compile(u".*("+u"|".join(config_smileys)+u")")
192 def is_smiley(chain):
193 chain=unicode(chain,"utf8")
194 o=re.match(reg_is_smiley,chain)
195 return o
196
197 reg_is_anglicisme = re.compile(u".*(?:^| )(" + u"|".join(config_anglicismes) + u")(?:$|\.| |,|;)")
198 def is_anglicisme(chain):
199 chain = unicode(chain, "utf8").lower()
200 o = re.match(reg_is_anglicisme, chain)
201 return o
202
203 # Cette liste contient la liste des raisons pour lesquelles on peut se faire kicker
204 # chaque élément contient :
205 # - la fonction de détection du kick (qui matchera une regexp et renverra l'objet de match)
206 # - la raison donnée au moment du kick ({0} étant remplacé par ce qui a matché)
207 config_kicking_list = [
208 [is_smiley, u'"{0}" ? Ici on déprime.'],
209 [is_anglicisme, u'"{0}" ? Get out, you and your fucking anglicism !']
210 ]
211
212 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
213 if case_sensitive:
214 chain=unicode(chain,"utf8")
215 else:
216 chain=unicode(chain,"utf8").lower()
217 allmatches="("+"|".join(matches)+")"
218 reg=(avant+allmatches+apres).lower()
219 o=re.match(reg,chain)
220 return o
221
222 def is_insult(chain,debug=True):
223 return is_something(chain,config_insultes,avant=".*(?:^| |')")
224 def is_not_insult(chain):
225 chain=unicode(chain,"utf8")
226 insult_regexp=u"("+u"|".join(config_insultes)+u")"
227 middle_regexp=u"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
228 reg=".*pas %s%s.*"%(middle_regexp,insult_regexp)
229 if re.match(reg,chain):
230 return True
231 else:
232 return False
233 def is_compliment(chain,debug=True):
234 return is_something(chain,config_compliment_triggers,avant=".*(?:^| |')")
235 def is_perdu(chain):
236 return is_something(chain,config_perdu)
237 def is_tag(chain):
238 return is_something(chain,config_tag_triggers)
239 def is_tesla(chain):
240 return is_something(chain,config_tesla_triggers,avant=u"^",apres=u"$",debug=True)
241 def is_merci(chain):
242 return is_something(chain,config_merci_triggers)
243 def is_tamere(chain):
244 return is_something(chain,config_tamere_triggers)
245 def is_bad_action_trigger(chain,pseudo):
246 return is_something(chain,config_bad_action_triggers,avant=u"^",
247 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
248 def is_good_action_trigger(chain,pseudo):
249 return is_something(chain,config_good_action_triggers,avant=u"^",
250 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
251 def is_bonjour(chain):
252 return is_something(chain,config_bonjour_triggers,avant=u"^")
253 def is_bonne_nuit(chain):
254 return is_something(chain,config_bonne_nuit_triggers,avant=u"^")
255 def is_pan(chain):
256 return re.match(u"^(pan|bim|bang)( .*)?$",unicode(chain,"utf8").lower().strip())
257
258 def is_time(conf):
259 _,_,_,h,m,s,_,_,_=time.localtime()
260 return (conf[0],0,0)<(h,m,s)<(conf[1],0,0)
261 def is_day():
262 return is_time(config_daytime)
263 def is_night():
264 return is_time(config_nighttime)
265
266
267 class UnicodeBotError(Exception):
268 pass
269 def bot_unicode(chain):
270 try:
271 unicode(chain,"utf8")
272 except UnicodeDecodeError as exc:
273 raise UnicodeBotError
274
275 class Themis(ircbot.SingleServerIRCBot):
276 def __init__(self,serveur,debug=False):
277 temporary_pseudo=config_irc_pseudo+str(random.randrange(10000,100000))
278 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
279 temporary_pseudo,"Des[bot]e de #déprime", 10)
280 self.debug=debug
281 self.serveur=serveur
282 self.overops=config_overops
283 self.ops=self.overops+config_ops
284 self.report_bugs_to=config_report_bugs_to
285 self.chanlist=config_chanlist
286 self.kick_channels=config_kick_channels
287 self.stay_channels=config_stay_channels
288 self.quiet_channels=config_quiet_channels
289 self.last_perdu=0
290
291
292 def give_me_my_pseudo(self,serv):
293 serv.privmsg("NickServ","RECOVER %s %s"%(config_irc_pseudo,config_irc_password))
294 serv.privmsg("NickServ","RELEASE %s %s"%(config_irc_pseudo,config_irc_password))
295 time.sleep(0.3)
296 serv.nick(config_irc_pseudo)
297
298 def give_me_my_op_status(self,serv,chan):
299 serv.privmsg("ChanServ","OP %s"%(chan))
300
301 def on_welcome(self, serv, ev):
302 self.serv=serv # ça serv ira
303 self.give_me_my_pseudo(serv)
304 serv.privmsg("NickServ","identify %s"%(config_irc_password))
305 log(self.serveur,"Connected")
306 if self.debug:
307 self.chanlist=["#bot"]
308 self.kick_channels=["#bot"]
309 for c in self.chanlist:
310 log(self.serveur,"JOIN %s"%(c))
311 serv.join(c)
312 self.give_me_my_op_status(serv,c)
313
314
315 def lost(self,serv,channel,forced=False):
316 if self.last_perdu+config_time_between_perdu<time.time() or forced:
317 if not channel in self.quiet_channels or forced:
318 serv.privmsg(channel,"J'ai perdu !")
319 self.last_perdu=time.time()
320 delay=config_time_between_perdu_trigger
321 delta=config_time_between_perdu_trigger_delta
322 serv.execute_delayed(random.randrange(delay-delta,delay+delta),self.lost,(serv,channel))
323
324 def pourmoi(self, serv, message):
325 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
326 pseudo=self.nick
327 size=len(pseudo)
328 if message[:size]==pseudo and len(message)>size and message[size]==":":
329 return (True,message[size+1:].lstrip(" "))
330 else:
331 return (False,message)
332
333 def on_privmsg(self, serv, ev):
334 message=ev.arguments()[0]
335 auteur = irclib.nm_to_n(ev.source())
336 try:
337 test=bot_unicode(message)
338 except UnicodeBotError:
339 if config_utf8_trigger:
340 serv.privmsg(auteur, config_utf8_fail)
341 return
342 message=message.split()
343 cmd=message[0].lower()
344 notunderstood=False
345 if cmd=="help":
346 helpdico={"help":["""HELP <commande>
347 Affiche de l'aide sur la commande""",None,None],
348 "join": [None, """JOIN <channel>
349 Me fait rejoindre le channel""",None],
350 "leave": [None,"""LEAVE <channel>
351 Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
352 "quiet": [None,"""QUIET <channel>
353 Me rend silencieux sur le channel.""",None],
354 "noquiet": [None,"""NOQUIET <channel>
355 Me rend la parole sur le channel.""",None],
356 "say": [None,None,"""SAY <channel> <message>
357 Me fait parler sur le channel."""],
358 "do": [None,None,"""DO <channel> <action>
359 Me fait faitre une action (/me) sur le channel."""],
360 "stay": [None,None,"""STAY <channel>
361 Ajoute le channel à ma stay_list."""],
362 "nostay": [None,None,"""NOSTAY <channel>
363 Retire le channel de ma stay_list."""],
364 "ops": [None,None,"""OPS
365 Affiche la liste des ops."""],
366 "overops": [None,None,"""OVEROPS
367 Affiche la liste des overops."""],
368 "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
369 Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
370 "die": [None,None,"""DIE
371 Me déconnecte du serveur IRC."""]
372 }
373 helpmsg_default="Liste des commandes disponibles :\nHELP"
374 helpmsg_ops=" JOIN LEAVE QUIET NOQUIET LOST"
375 helpmsg_overops=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE"
376 op,overop=auteur in self.ops, auteur in self.overops
377 if len(message)==1:
378 helpmsg=helpmsg_default
379 if op:
380 helpmsg+=helpmsg_ops
381 if overop:
382 helpmsg+=helpmsg_overops
383 else:
384 helpmsgs=helpdico.get(message[1].lower(),["Commande inconnue.",None,None])
385 helpmsg=helpmsgs[0]
386 if op and helpmsgs[1]:
387 if helpmsg:
388 helpmsg+="\n"+helpmsgs[1]
389 else:
390 helpmsg=helpmsgs[1]
391 if overop and helpmsgs[2]:
392 if helpmsg:
393 helpmsg+="\n"+helpmsgs[2]
394 else:
395 helpmsg=helpmsgs[2]
396 for ligne in helpmsg.split("\n"):
397 serv.privmsg(auteur,ligne)
398 elif cmd=="join":
399 if auteur in self.ops:
400 if len(message)>1:
401 if message[1] in self.chanlist:
402 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
403 else:
404 serv.join(message[1])
405 self.chanlist.append(message[1])
406 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
407 log(self.serveur,"priv",auteur," ".join(message))
408 else:
409 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
410 else:
411 notunderstood=True
412 elif cmd=="leave":
413 if auteur in self.ops and len(message)>1:
414 if message[1] in self.chanlist:
415 if not (message[1] in self.stay_channels) or auteur in self.overops:
416 self.quitter(message[1]," ".join(message[2:]))
417 self.chanlist.remove(message[1])
418 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
419 else:
420 serv.privmsg(auteur,random.choice(config_leave_fail_messages))
421 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
422 else:
423 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
424 else:
425 notunderstood=True
426 elif cmd=="stay":
427 if auteur in self.overops:
428 if len(message)>1:
429 if message[1] in self.stay_channels:
430 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
431 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
432 else:
433 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
434 self.stay_channels.append(message[1])
435 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
436 else:
437 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
438 else:
439 notunderstood=True
440 elif cmd=="nostay":
441 if auteur in self.overops:
442 if len(message)>1:
443 if message[1] in self.stay_channels:
444 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
445 self.stay_channels.remove(message[1])
446 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
447 else:
448 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
449 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
450
451 else:
452 notunderstood=True
453 elif cmd=="die":
454 if auteur in self.overops:
455 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
456 self.mourir()
457 else:
458 notunderstood=True
459 elif cmd=="quiet":
460 if auteur in self.ops:
461 if len(message)>1:
462 if message[1] in self.quiet_channels:
463 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
464 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
465 else:
466 self.quiet_channels.append(message[1])
467 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
468 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
469 else:
470 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
471 else:
472 notunderstood=True
473 elif cmd=="noquiet":
474 if auteur in self.ops:
475 if len(message)>1:
476 if message[1] in self.quiet_channels:
477 self.quiet_channels.remove(message[1])
478 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
479 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
480 else:
481 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
482 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
483 else:
484 notunderstood=True
485 elif cmd=="say":
486 if auteur in self.overops and len(message)>2:
487 serv.privmsg(message[1]," ".join(message[2:]))
488 log(self.serveur,"priv",auteur," ".join(message))
489 elif len(message)<=2:
490 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
491 else:
492 notunderstood=True
493 elif cmd=="do":
494 if auteur in self.overops and len(message)>2:
495 serv.action(message[1]," ".join(message[2:]))
496 log(self.serveur,"priv",auteur," ".join(message))
497 elif len(message)<=2:
498 serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
499 else:
500 notunderstood=True
501 elif cmd=="kick":
502 if auteur in self.overops and len(message)>2:
503 serv.kick(message[1],message[2]," ".join(message[3:]))
504 log(self.serveur,"priv",auteur," ".join(message))
505 elif len(message)<=2:
506 serv.privmsg(auteur,"Syntaxe : KICK <channel> <pseudo> [<raison>]")
507 else:
508 notunderstood=True
509 elif cmd=="ops":
510 if auteur in self.overops:
511 serv.privmsg(auteur," ".join(self.ops))
512 else:
513 notunderstood=True
514 elif cmd=="overops":
515 if auteur in self.overops:
516 serv.privmsg(auteur," ".join(self.overops))
517 else:
518 notunderstood=True
519 else:
520 notunderstood=True
521 if notunderstood:
522 serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…")
523
524 def on_pubmsg(self, serv, ev):
525 auteur = irclib.nm_to_n(ev.source())
526 canal = ev.target()
527 message = ev.arguments()[0]
528 try:
529 test=bot_unicode(message)
530 except UnicodeBotError:
531 if not canal in self.quiet_channels and config_utf8_trigger:
532 serv.privmsg(canal, "%s: %s"%(auteur,config_utf8_fail))
533 return
534 pour_moi,message=self.pourmoi(serv,message)
535 for (detect, reason) in config_kicking_list:
536 matching = detect(message)
537 if matching:
538 if canal in self.kick_channels:
539 serv.kick(canal,auteur,(reason.format(matching.groups()[0])).encode("utf8"))
540 return
541 if pour_moi and message.split()!=[]:
542 cmd=message.split()[0].lower()
543 try:
544 args=" ".join(message.split()[1:])
545 except:
546 args=""
547 if cmd in ["meurs","die","crève","pends-toi"]:
548 if auteur in self.overops:
549 log(self.serveur,canal,auteur,message+"[successful]")
550 self.mourir()
551 else:
552 serv.privmsg(canal,"%s: %s"%(auteur,random.choice(config_quit_fail_messages)))
553 log(self.serveur,canal,auteur,message+"[failed]")
554
555 elif cmd in ["part","leave","dégage","va-t-en"]:
556 if auteur in self.ops and (not (canal in self.stay_channels)
557 or auteur in self.overops):
558 self.quitter(canal)
559 log(self.serveur,canal,auteur,message+"[successful]")
560 if canal in self.chanlist:
561 self.chanlist.remove(canal)
562 else:
563 serv.privmsg(canal,"%s: %s"%(auteur,random.choice(config_leave_fail_messages)))
564 log(self.serveur,canal,auteur,message+"[failed]")
565
566 elif cmd in ["deviens","pseudo"]:
567 if auteur in self.ops:
568 become=args
569 serv.nick(become)
570 log(self.serveur,canal,auteur,message+"[successful]")
571
572 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
573 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
574 elif cmd in ["ping"] and not canal in self.quiet_channels:
575 serv.privmsg(canal,"%s: pong"%(auteur))
576 # if is_insult(message) and not canal in self.quiet_channels:
577 # if is_not_insult(message):
578 # answer=random.choice(config_compliment_answers)
579 # for ligne in answer.split("\n"):
580 # serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
581 # else:
582 # answer=random.choice(config_insultes_answers)
583 # for ligne in answer.split("\n"):
584 # serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
585 # elif is_compliment(message) and not canal in self.quiet_channels:
586 # answer=random.choice(config_compliment_answers)
587 # for ligne in answer.split("\n"):
588 # serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
589 if is_tesla(message) and not canal in self.quiet_channels:
590 l1,l2=config_tesla_answers,config_tesla_actions
591 n1,n2=len(l1),len(l2)
592 i=random.randrange(n1+n2)
593 if i>=n1:
594 serv.action(canal,l2[i-n1].encode("utf8"))
595 else:
596 serv.privmsg(canal,"%s: %s"%(auteur,l1[i].encode("utf8")))
597 if is_tag(message) and not canal in self.quiet_channels:
598 if auteur in self.ops:
599 action=random.choice(config_tag_actions)
600 serv.action(canal,action.encode("utf8"))
601 self.quiet_channels.append(canal)
602 else:
603 answer=random.choice(config_tag_answers)
604 for ligne in answer.split("\n"):
605 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
606 if is_bonjour(message) and not canal in self.quiet_channels:
607 if is_night():
608 answer=random.choice(config_night_answers)
609 elif is_day():
610 answer=random.choice(config_bonjour_answers)
611 else:
612 answer=random.choice(config_bonsoir_answers)
613 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
614 if is_bonne_nuit(message) and not canal in self.quiet_channels:
615 answer=random.choice(config_bonne_nuit_answers)
616 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
617 else:
618 if not canal in self.quiet_channels:
619 mypseudo=self.nick
620 if re.match((u"^("+u"|".join(config_bonjour_triggers)
621 +u")( {}| all| tout le monde|(|à) tous)(\.|( |)!|)$"
622 ).format(mypseudo).lower(), message.strip().lower()):
623 answer=random.choice(config_bonjour_answers)
624 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
625
626 def on_action(self, serv, ev):
627 action = ev.arguments()[0]
628 auteur = irclib.nm_to_n(ev.source())
629 channel = ev.target()
630 try:
631 test=bot_unicode(action)
632 except UnicodeBotError:
633 if not channel in self.quiet_channels and config_utf8_trigger:
634 serv.privmsg(channel, "%s: %s"%(auteur,config_utf8_fail))
635 return
636 mypseudo=self.nick
637
638 # if is_bad_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
639 # l1,l2=config_bad_action_answers,config_bad_action_actions
640 # n1,n2=len(l1),len(l2)
641 # i=random.randrange(n1+n2)
642 # if i>=n1:
643 # serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
644 # else:
645 # serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
646 # if is_good_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
647 # l1,l2=config_good_action_answers,config_good_action_actions
648 # n1,n2=len(l1),len(l2)
649 # i=random.randrange(n1+n2)
650 # if i>=n1:
651 # serv.action(channel,l2[i-n1].format(auteur).format(auteur).encode("utf8"))
652 # else:
653 # serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
654
655 def on_kick(self,serv,ev):
656 auteur = irclib.nm_to_n(ev.source())
657 channel = ev.target()
658 victime = ev.arguments()[0]
659 raison = ev.arguments()[1]
660 if victime==self.nick:
661 log(self.serveur,"%s kické de %s par %s (raison : %s)" %(victime,channel,auteur,raison))
662 time.sleep(2)
663 serv.join(channel)
664 username = irclib.nm_to_u(ev.source()).lower()
665 print ev.source()
666 print channel, username
667 if channel == "#déprime" and "peb" in username or "becue" in username:
668 time.sleep(5)
669 serv.kick(auteur, "Va abuser de tes droits ailleurs !")
670
671 def kicker(self, chan, pseudo, raison=None):
672 if raison==None:
673 raison = config_kick_default_reason
674 self.serv.kick(chan,pseudo,raison)
675
676 def quitter(self,chan,leave_message=None):
677 if leave_message==None:
678 leave_message=random.choice(config_leave_messages)
679 self.serv.part(chan,message=leave_message.encode("utf8"))
680
681 def mourir(self):
682 quit_message=random.choice(config_quit_messages)
683 self.die(msg=quit_message.encode("utf8"))
684
685 def _getnick(self):
686 return self.serv.get_nickname()
687 nick=property(_getnick)
688
689 def start_as_daemon(self, outfile):
690 sys.stderr = Logger(outfile)
691 self.start()
692
693
694 class Logger(object):
695 """Pour écrire ailleurs que sur stdout"""
696 def __init__(self, filename="themis.full.log"):
697 self.filename = filename
698
699 def write(self, message):
700 f = open(self.filename, "a")
701 f.write(message)
702 f.close()
703
704
705 if __name__=="__main__":
706 import sys
707 if len(sys.argv)==1:
708 print "Usage : themis.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
709 print " --outfile sans --no-output ni --daemon n'a aucun effet"
710 exit(1)
711 serveur=sys.argv[1]
712 if "--daemon" in sys.argv:
713 thisfile = os.path.realpath(__file__)
714 thisdirectory = thisfile.rsplit("/", 1)[0]
715 os.chdir(thisdirectory)
716 daemon = True
717 else:
718 daemon = False
719 if "debug" in sys.argv or "--debug" in sys.argv:
720 debug=True
721 else:
722 debug=False
723 if "--no-output" in sys.argv or "--daemon" in sys.argv:
724 outfile = "/var/log/bots/themis.full.log"
725 for arg in sys.argv:
726 arg = arg.split("=")
727 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
728 outfile = arg[1]
729 sys.stdout = Logger(outfile)
730 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
731 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
732 try:
733 serveur=serveurs[serveur]
734 except KeyError:
735 print "Server Unknown : %s"%(serveur)
736 exit(404)
737 themis=Themis(serveur,debug)
738 if daemon:
739 child_pid = os.fork()
740 if child_pid == 0:
741 os.setsid()
742 themis.start_as_daemon(outfile)
743 else:
744 # on enregistre le pid de themis
745 pidfile = "/var/run/bots/themis.pid"
746 for arg in sys.argv:
747 arg = arg.split("=")
748 if arg[0].strip('-') in ["pidfile"]:
749 pidfile = arg[1]
750 f = open(pidfile, "w")
751 f.write("%s\n" % child_pid)
752 f.close()
753 else:
754 themis.start()
755