]> gitweb.pimeys.fr Git - bots/basile.git/blob - basile.py
Une insult_answer de plus
[bots/basile.git] / basile.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 irclib
9 import ircbot
10 import threading
11 import random
12 import time
13 import socket, ssl, json
14 import pickle
15 import re
16 import os
17 from commands import getstatusoutput as ex
18
19 import sys
20 config_debug_stdout=True
21 if "--quiet" in sys.argv:
22 config_debug_stdout=False
23
24 config_irc_password="NK2015BasileB0t"
25 config_irc_pseudo="Basile"
26 config_chanlist=["#bot","#flood"]
27 config_stay_channels=["#bot","#flood"]
28 config_quiet_channels=[]
29 config_note_pseudo="Basile"
30 config_note_password="NK2015BasileB0tr4nd0omp4assword]6_+{#]78{"
31 config_logfile_template="basile.%s.log"
32 def get_config_logfile(serveur):
33 serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
34 return config_logfile_template%(serveurs[serveur])
35 config_overops=["[20-100]","[20-100]_", "PEB"]
36 config_ops=["Nit"]
37 config_report_bugs_to=["[20-100]"]
38
39 # config "ce bot a été codé par 20-100, tu te rappelles ?"
40 config_manzana = ["[20-100]", "Petite-Peste"]
41
42 # config "tu m'traites ?"
43 config_insultes=[u"conna(rd|sse)",u"pute",u"con(|ne)",u"enf(oiré|lure)",
44 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",
45 u"pétasse",u"enculé",u"chagasse",u"cagole",u"abruti",u"ahuri",u"analphabète",u"andouille",
46 u"atardé",u"avorton",u"bachibouzouk",u"(balais|brosse) (de|à) chiotte(|s)",
47 u"batard",u"blaireau",u"bouffon",u"branque",u"bouseux",u"branleur",u"catin",u"chacal",
48 u"charogne",u"chiant(|e)",u"chieur",u"cochon",u"coprophage",u"couillon",u"crapule",u"crevard",
49 u"cruche",u"cuistre",u"ducon",u"décérébré",
50 u"emmerdeur",u"feignasse",u"fainéant",u"fourbe",u"freluquet",u"frigide",
51 u"garce",u"glandu",u"gogol",u"goujat",u"gourdasse",u"gredin",u"gringalet",u"grognasse",
52 u"naze",u"truie",u"iconoclaste",
53 u"peigne(-|)cul",u"ignare",u"illétré",u"lèche(|-)cul",u"malotru",u"motherfucker",u"nabot",u"nigaud",
54 u"nul",u"escroc",u"pouffiasse",u"pourriture",u"raclure",u"relou",u"sagouin",u"putain",
55 u"péripatéticienne"]
56 config_insultes_answers=[
57 u"Oh non ! Quelle insulte ! Je crois que je ne m'en relèverai jamais…\nEnfin presque.",
58 u"J'entends comme un vague murmure, vous disiez ?",
59 u"Je vais prendre ça pour un compliment.",
60 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…",
61 u"Permettez-moi de vous retourner le compliment.",
62 u"Votre indélicatesse vous sied à ravir.",
63 u"Parfois, je me demande pourquoi je fais encore ce métier…",
64 u"Le saviez-vous : l'invective ne déshonore que son auteur.",
65 u"Le saviez-vous : vous perdez plus de temps à m'insulter qu'à vous taire.",
66 u"Suis-je contraint à tolérer une telle outrecuidance ?",
67 u"Mais je ne vous permets pas ! Enfin, pas comme ça…"]
68
69 # config "à peine quelques kilos octets"
70 config_gros=[u"gros",u"énorme",u"lourd"]
71
72 # config spéciale-iota
73 config_buffer_fail_answers=[u"Pas de chance !",u"Révisez vos classiques !",
74 u"Encore un effort, je sais que vous pouvez le faire. ;)",
75 u"Where did you learn to type?"]
76
77 # config "jeu", d'ailleurs, j'ai perdu.
78 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))"
79 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)"
80 config_regexp_etre_avec_c=u"c'(e(s|st)|étai(t|ent))"
81 config_regexp_faire=u"fais"
82 config_perdu=[u"perd(|s|ons|ez|ent|r(e|ai|as|a|ons|ez|ont)|(|r)(ais|ait|ions|iez|aient))"
83 u"perd(i(s|t|rent)|î(mes|tes|t))", # oui, j'ai inclus qu'il perdît
84 u"perdiss(e(|s|nt)|i(ons|ez))",
85 u"perdu(|s|e|es)",u"perdant(|s|e|es)",u"perte(|s)",
86
87 u"(gagn|trouv)"+config_premier_groupe_terminaisons,u"gagnant(|s|e|es)",u"gain(|s)",
88
89 u"trouvant",u"trouvaille(|s)",
90
91 u"victoire(|s)",u"vaincu(|s|e|es)",
92 u"loose",u"lost",u"looser(|s)",u"win(|ner)(|s)",
93 u"jeu(|x)",u"game(|s)"]
94 config_time_between_perdu_trigger=3600*3 #temps moyen pour perdre en l'absence de trigger
95 config_time_between_perdu_trigger_delta = 30*60 #marge autorisée autour de ^^^
96 config_time_between_perdu=30*60 #temps pendant lequel on ne peut pas perdre
97
98 # config "tais-toi"
99 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"]
100 config_tag_actions=[u"se tait",u"se tient coi"]
101 config_tag_answers=[
102 u"Ç'aurait été avec plaisir, mais je ne crois pas que vous puissiez vous passer de mes services.",
103 u"Dès que cela sera utile.",
104 u"Une autre fois, peut-être.",
105 u"Si je me tais, qui vous rappellera combien vous me devez ?",
106 u"J'aurais aimé accéder à votre requête, mais après mûre réflexion, j'en ai perdu l'envie.",
107 u"Je ne ressens pas de besoin irrésistible de me taire, navré."]
108
109 # config ping
110 config_tesla_triggers=[u"t('|u )es là \?",u"\?",u"plop \?",u"plouf \?"]
111 config_tesla_answers=[
112 u"Oui, je suis là.",
113 u"J'écoute.",
114 u"En quoi puis-je me rendre utile ?",
115 u"On a besoin de moi ?"
116 ]
117 config_tesla_actions=[u"est là",u"attend des instructions",u"est toujours disponible"]
118
119 # config en cas de non-insulte
120 config_compliment_triggers=[u"gentil",u"cool",u"sympa",u"efficace"]
121 config_compliment_answers=[
122 u"Merci, c'est gentil de votre part. :)",
123 u"Permettez-moi de vous retourner le compliment, sans ironie cette fois.",
124 u"Je vous remercie.",
125 u"C'est trop d'honneur.",
126 u"Vous êtes bien aimable."
127 ]
128
129 # config merci
130 config_merci_triggers=[u"merci",u"remercie",u"thx",u"thank(|s)"]
131 config_merci_answers=[u"Mais de rien.",u"À votre service. ;)",u"Quand vous voulez. :)",
132 u"Tout le plaisir est pour moi."]
133
134 # config "ta mère"
135 config_tamere_triggers=[u"ta mère"]
136 config_tamere_answers=[u"Laissez donc ma mère en dehors de ça !",
137 u"Puis-je préciser que je n'ai pas de mère ? Seulement deux pères…",
138 u"""Un certain Max chantait "♩ J'ai vu ta mère sur chat rouleeeeeeette ♫", vous êtes de sa famille ?""",
139 u"""N'avait-on pas dit "pas les mamans" ?"""]
140
141 # config pour les actions désagréables à Basile
142 config_bad_action_triggers=[u"(frappe|cogne|tape)(| sur)",u"(démolit|dégomme|fouette|agresse|tabasse)",
143 u"(vomit|pisse|chie|crache) sur",u"slap(|s)"]
144 config_bad_action_answers=[
145 u"Je ne peux pas dire que j'apprécie, mais je l'ai sans doute bien mérité.",
146 u"{}: Pourquoi tant de violence en ce monde si doux ?",
147 u"""Si je n'étais pas aussi prude, je dirais "Mais euh…", cependant, je me contenterai de hausser un sourcil.""",
148 u"{}: J'aurais préféré que vous ne fassiez pas cela en public.",
149 u"{}: Entre nous, cela vous gratifie-t-il ?",
150 u"{}: Une telle relation entre nous deux n'est pas saine, revenons à quelque chose de plus conventionnel. :D",
151 u"J'ai la désagréable impression que {} cherche comment tuer le temps en ce moment…"
152 ]
153 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à ?"]
154
155 # config pour les actions agréables à Basile
156 config_good_action_triggers=[u"fait (:?des bisous|un c(?:â|a)lin|des c(?:â|a)lins) à",u"embrasse",u"c(?:â|a)line",u"caresse"]
157 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…"]
158 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"]
159
160 # config bonjour/bonsoir/que fais-tu encore debout à cette heure, gros sale !
161 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"]
162 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"]
163 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é."]
164 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, {} ?"]
165 config_daytime = [7,18]
166 config_nighttime = [3, 6]
167
168 # config dodo
169 config_bonne_nuit_triggers=[u"bonne nuit",u"'?nite",u"'?nuit",u"'?night",u"good night",u"'?nenuit"]
170 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 !"]
171
172 # config PEB est encore en train d'abuser de ses droits.
173 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 !"]
174 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."]
175
176 config_thisfile= os.path.realpath( __file__ )
177 def get_filesize():
178 return ex("ls -s %s"%(config_thisfile))[1].split()[0]
179
180 class NKError(Exception):
181 def __init__(self,msg):
182 Exception.__init__(self)
183 self.msg=msg
184 def __str__(self):
185 return str(self.msg)
186 def __unicode__(self):
187 return unicode(self.msg)
188
189 class NKRefused(NKError):
190 pass
191
192 class NKHelloFailed(NKError):
193 pass
194
195 class NKUnknownError(NKError):
196 pass
197
198 def log(serveur,channel,auteur=None,message=None):
199 f=open(get_config_logfile(serveur),"a")
200 if auteur==message==None:
201 # alors c'est que c'est pas un channel mais juste une ligne de log
202 chain="%s %s"%(time.strftime("%F %T"),channel)
203 else:
204 chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
205 f.write(chain+"\n")
206 if config_debug_stdout:
207 print chain
208 f.close()
209
210 def connect_NK():
211 sock=socket.socket()
212 try:
213 # On établit la connexion sur port 4242
214 sock.connect(("127.0.0.1",4242))
215 # On passe en SSL
216 sock=ssl.wrap_socket(sock,ca_certs='../keys/ca_.crt')
217 # On fait un hello
218 sock.write('hello "Basile"')
219 # On récupère la réponse du hello
220 out=sock.read()
221 out=json.loads(out)
222 except Exception as exc:
223 # Si on a foiré quelque part, c'est que le serveur est down
224 raise NKRefused(str(exc))
225 if out["retcode"]==0:
226 return sock
227 elif out["retcode"]==11:
228 raise NKHelloFailed(out["errmsg"])
229 else:
230 raise NKUnknownError(out["errmsg"])
231
232 def login_NK(username,password,typ="bdd"):
233 sock=connect_NK()
234 if typ=="special": # ça c'est pour Basile lui-même
235 masque='["note"]'
236 elif typ=="bdd":
237 masque='[["all"],["all"],false]'
238 try:
239 # Basile a un compte special user
240 commande='login [%s,%s,"%s",%s]'%(json.dumps(username),json.dumps(password),typ,masque)
241 sock.write(commande)
242 out=sock.read()
243 except Exception as exc:
244 # Si on a foiré quelque part, c'est que le serveur est down
245 raise NKRefused(str(exc))
246 # On vérifie ensuite que le login
247 return json.loads(out),sock
248
249
250 def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
251 if case_sensitive:
252 chain=unicode(chain,"utf8")
253 else:
254 chain=unicode(chain,"utf8").lower()
255 allmatches="("+"|".join(matches)+")"
256 reg=(avant+allmatches+apres).lower()
257 o=re.match(reg,chain)
258 return o
259
260 def is_insult(chain,debug=True):
261 return is_something(chain,config_insultes,avant=".*(?:^| |')")
262 def is_not_insult(chain):
263 chain=unicode(chain,"utf8")
264 insult_regexp=u"("+u"|".join(config_insultes)+u")"
265 middle_regexp=u"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
266 reg=".*pas %s%s.*"%(middle_regexp,insult_regexp)
267 if re.match(reg,chain):
268 return True
269 else:
270 return False
271 def is_compliment(chain,debug=True):
272 return is_something(chain,config_compliment_triggers,avant=".*(?:^| |')")
273 def is_perdu(chain):
274 return is_something(chain,config_perdu)
275 def is_tag(chain):
276 return is_something(chain,config_tag_triggers)
277 def is_gros(chain):
278 return is_something(chain,config_gros)
279 def is_tesla(chain):
280 return is_something(chain,config_tesla_triggers,avant=u"^",apres=u"$",debug=True)
281 def is_merci(chain):
282 return is_something(chain,config_merci_triggers)
283 def is_tamere(chain):
284 return is_something(chain,config_tamere_triggers)
285 def is_bad_action_trigger(chain,pseudo):
286 return is_something(chain,config_bad_action_triggers,avant=u"^",
287 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
288 def is_good_action_trigger(chain,pseudo):
289 return is_something(chain,config_good_action_triggers,avant=u"^",
290 apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
291 def is_bonjour(chain):
292 return is_something(chain,config_bonjour_triggers,avant=u"^")
293 def is_bonne_nuit(chain):
294 return is_something(chain,config_bonne_nuit_triggers,avant=u"^")
295 def is_pan(chain):
296 return re.match(u"^(pan|bim|bang)( .*)?$",unicode(chain,"utf8").lower().strip())
297
298 def is_time(conf):
299 _,_,_,h,m,s,_,_,_=time.localtime()
300 return (conf[0],0,0)<(h,m,s)<(conf[1],0,0)
301 def is_day():
302 return is_time(config_daytime)
303 def is_night():
304 return is_time(config_nighttime)
305
306
307 class UnicodeBotError(Exception):
308 pass
309 def bot_unicode(chain):
310 try:
311 unicode(chain,"utf8")
312 except UnicodeDecodeError as exc:
313 raise UnicodeBotError
314
315 class Basile(ircbot.SingleServerIRCBot):
316 def __init__(self,serveur,debug=False):
317 temporary_pseudo=config_irc_pseudo+str(random.randrange(10000,100000))
318 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
319 temporary_pseudo,"Basile, le bot irc.[Codé par 20-100, fouettez-le]", 10)
320 self.debug=debug
321 self.serveur=serveur
322 self.overops=config_overops
323 self.ops=self.overops+config_ops
324 self.report_bugs_to=config_report_bugs_to
325 self.chanlist=config_chanlist
326 self.sockets={}
327 self.identities=pickle.load(open("identities.pickle","r"))
328 self.stay_channels=config_stay_channels
329 self.quiet_channels=config_quiet_channels
330 self.last_perdu=0
331
332
333 def new_connection_NK(self,serv,username,password,typ="bdd"):
334 try:
335 login_result,sock=login_NK(username,password,typ)
336 droits,retcode,errmsg=login_result["msg"],login_result["retcode"],login_result["errmsg"]
337 except NKRefused as exc:
338 for report in self.report_bugs_to:
339 serv.privmsg(report,"Le Serveur NK2015 est down.")
340 return (False,None)
341 except NKHelloFailed as exc:
342 for report in self.report_bugs_to:
343 serv.privmsg(report,
344 "La version du site utilisée n'est pas supportée par le serveur NK2015.")
345 return (False,None)
346 except NKUnknownError as exc:
347 erreurs=["Une fucking erreur inconnue s'est produite"]
348 erreurs+=str(exc).split("\n")
349 for report in self.report_bugs_to:
350 for err in erreurs:
351 serv.privmsg(report,err)
352 return (False,None)
353 except Exception as exc:
354 # Exception qui ne vient pas de la communication avec le serveur NK2015
355 log(self.serveur,"Erreur dans new_connection_NK\n"+str(exc))
356 return (False,None)
357 if retcode==0:
358 return (True,sock)
359 else:
360 return (False,None)
361
362 def give_me_my_pseudo(self,serv):
363 serv.privmsg("NickServ","RECOVER %s %s"%(config_irc_pseudo,config_irc_password))
364 serv.privmsg("NickServ","RELEASE %s %s"%(config_irc_pseudo,config_irc_password))
365 time.sleep(0.3)
366 serv.nick(config_irc_pseudo)
367
368 def on_welcome(self, serv, ev):
369 self.serv=serv # ça serv ira :)
370 self.give_me_my_pseudo(serv)
371 serv.privmsg("NickServ","identify %s"%(config_irc_password))
372 log(self.serveur,"Connected")
373 if self.debug:
374 self.chanlist=["#bot"]
375 for c in self.chanlist:
376 log(self.serveur,"JOIN %s"%(c))
377 serv.join(c)
378 # on ouvre la connexion note de Basile, special user
379 self.nk=self.new_connection_NK(serv,config_note_pseudo,config_note_password,"special")[1]
380 if self.nk==None:
381 for report in self.report_bugs_to:
382 serv.privmsg(report,"Connection to NK2015 failed, invalid password ?")
383
384 def lost(self,serv,channel,forced=False):
385 if self.last_perdu+config_time_between_perdu<time.time() or forced:
386 if not channel in self.quiet_channels or forced:
387 serv.privmsg(channel,"J'ai perdu !")
388 self.last_perdu=time.time()
389 delay=config_time_between_perdu_trigger
390 delta=config_time_between_perdu_trigger_delta
391 serv.execute_delayed(random.randrange(delay-delta,delay+delta),self.lost,(serv,channel))
392
393 def pourmoi(self, serv, message):
394 """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
395 pseudo=self.nick
396 size=len(pseudo)
397 if message[:size]==pseudo and len(message)>size and message[size]==":":
398 return (True,message[size+1:].lstrip(" "))
399 else:
400 return (False,message)
401
402 def on_privmsg(self, serv, ev):
403 message=ev.arguments()[0]
404 auteur = irclib.nm_to_n(ev.source())
405 try:
406 test=bot_unicode(message)
407 except UnicodeBotError:
408 serv.privmsg(auteur,
409 "Si je n'avais pas été créé avec la plus grande attention, votre encodage m'aurait déjà tué…")
410 return
411 message=message.split()
412 cmd=message[0].lower()
413 notunderstood=False
414 if cmd=="connect":
415 if not len(message) in [2,3]:
416 serv.privmsg(auteur,"Syntaxe : CONNECT [<username>] <password>")
417 return
418 username=auteur
419 if len(message)>2:
420 username=(message[1])
421 password=" ".join(message[2:])
422 else:
423 password=" ".join(message[1:])
424 success,sock=self.new_connection_NK(serv,username,password)
425 if success:
426 self.sockets[username]=sock
427 serv.privmsg(auteur,"Connection successful")
428 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
429 else:
430 serv.privmsg(auteur,"Connection failed")
431 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
432
433 elif cmd=="help":
434 helpdico={"connect": """CONNECT [<username>] <password>
435 Ouvre une connexion au serveur NoteKfet.
436 Si <username> n'est pas précisé, j'utiliserais l'identité sous laquelle je te connais, ou, à défaut, ton pseudo.""",
437 "identify": """IDENTIFY <username> <password>
438 Vérifie le mot de passe et me permet de savoir à l'avenir quel est ton pseudo note kfet.
439 Sans paramètre, je réponds sous quel pseudo je te connais.""",
440 "drop":"""DROP <password>
441 Vérifie le mot de passe et me fait d'oublier ton pseudo note kfet."""}
442 helpmsg_default="""Liste des commandes :
443 HELP Affiche de l'aide sur une commande.
444 CONNECT Ouvre une connection au serveur Note Kfet.
445 IDENTIFY Me permet de savoir qui tu es sur la note kfet.
446 DROP Me fait oublier ton identité.
447 SOLDE Obtenir ton solde"""
448 helpmsg_ops="""
449 JOIN Faire rejoindre un chan
450 LEAVE Faire quitter un chan
451 QUIET Se taire sur un chan
452 NOQUIET Opposé de QUIET
453 LOST Perdre sur un chan
454 SOLDE <pseudo> Donner le solde de quelqu'un"""
455 helpmsg_overops="""
456 SAY Fait envoyer un message sur un chan ou à une personne
457 DO Fait faire une action sur un chan
458 STAY Ignorera les prochains LEAVE pour un chan
459 NOSTAY Opposé de STAY
460 DIE Mourir"""
461 if len(message)==1:
462 helpmsg=helpmsg_default
463 if auteur in self.ops:
464 helpmsg+=helpmsg_ops
465 if auteur in self.overops:
466 helpmsg+=helpmsg_overops
467 else:
468 helpmsg=helpdico.get(message[1].lower(),"Commande inconnue.")
469 for ligne in helpmsg.split("\n"):
470 serv.privmsg(auteur,ligne)
471 elif cmd=="identify":
472 if len(message)==1:
473 if self.identities.has_key(auteur):
474 serv.privmsg(auteur,"Je te connais sous le pseudo note %s."%(
475 self.identities[auteur].encode("utf8")))
476 else:
477 serv.privmsg(auteur,"Je ne connais pas ton pseudo note.")
478 elif len(message)>=3:
479 username,password=message[1],unicode(" ".join(message[2:]),"utf8")
480 success,_=self.new_connection_NK(serv,username,password)
481 if success:
482 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
483 serv.privmsg(auteur,"Identité enregistrée.")
484 self.identities[auteur]=username
485 pickle.dump(self.identities,open("identities.pickle","w"))
486 else:
487 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
488 serv.privmsg(auteur,"Mot de passe invalide. (ou serveur down)")
489 else:
490 serv.privmsg(auteur,u"Syntaxe : IDENTIFY [<username> <password>]")
491 elif cmd=="drop":
492 if len(message)>1:
493 if self.identities.has_key(auteur):
494 password=" ".join(message[1:])
495 success,_=self.new_connection_NK(serv,self.identities[auteur],password)
496 if success:
497 del self.identities[auteur]
498 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
499 pickle.dump(self.identities,open("identities.pickle","w"))
500 serv.privmsg(auteur,"Identité oubliée.")
501 else:
502 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
503 serv.privmsg(auteur,"Mot de passe invalide. (ou serveur down)")
504 else:
505 serv.privmsg(auteur,"Je ne connais pas ton pseudo note.")
506 else:
507 serv.privmsg(auteur,"Syntaxe : DROP <password>")
508 elif cmd=="join":
509 if auteur in self.ops:
510 if len(message)>1:
511 if message[1] in self.chanlist:
512 serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
513 else:
514 serv.join(message[1])
515 self.chanlist.append(message[1])
516 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
517 log(self.serveur,"priv",auteur," ".join(message))
518 else:
519 serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
520 else:
521 notunderstood=True
522 elif cmd=="leave":
523 if auteur in self.ops and len(message)>1:
524 if message[1] in self.chanlist:
525 if not (message[1] in self.stay_channels) or auteur in self.overops:
526 serv.part(message[1])
527 self.chanlist.remove(message[1])
528 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
529 else:
530 serv.privmsg(auteur,"Non, je reste !")
531 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
532 else:
533 serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
534 else:
535 notunderstood=True
536 elif cmd=="stay":
537 if auteur in self.overops:
538 if len(message)>1:
539 if message[1] in self.stay_channels:
540 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
541 serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
542 else:
543 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
544 self.stay_channels.append(message[1])
545 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
546 else:
547 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
548 else:
549 notunderstood=True
550 elif cmd=="nostay":
551 if auteur in self.overops:
552 if len(message)>1:
553 if message[1] in self.stay_channels:
554 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
555 self.stay_channels.remove(message[1])
556 serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
557 else:
558 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
559 serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
560
561 else:
562 notunderstood=True
563 elif cmd=="die":
564 if auteur in self.overops:
565 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
566 self.die()
567 else:
568 notunderstood=True
569 elif cmd=="quiet":
570 if auteur in self.ops:
571 if len(message)>1:
572 if message[1] in self.quiet_channels:
573 serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
574 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
575 else:
576 self.quiet_channels.append(message[1])
577 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
578 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
579 else:
580 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
581 else:
582 notunderstood=True
583 elif cmd=="noquiet":
584 if auteur in self.ops:
585 if len(message)>1:
586 if message[1] in self.quiet_channels:
587 self.quiet_channels.remove(message[1])
588 serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
589 log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
590 else:
591 serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
592 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
593 else:
594 notunderstood=True
595 elif cmd=="say":
596 if auteur in self.overops and len(message)>2:
597 serv.privmsg(message[1]," ".join(message[2:]))
598 log(self.serveur,"priv",auteur," ".join(message))
599 elif len(message)<=2:
600 serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
601 else:
602 notunderstood=True
603 elif cmd=="do":
604 if auteur in self.overops and len(message)>2:
605 serv.action(message[1]," ".join(message[2:]))
606 log(self.serveur,"priv",auteur," ".join(message))
607 elif len(message)<=2:
608 serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
609 else:
610 notunderstood=True
611 elif cmd=="kick":
612 if auteur in self.overops and len(message)>2:
613 serv.kick(message[1],message[2]," ".join(message[3:]))
614 log(self.serveur,"priv",auteur," ".join(message))
615 elif len(message)<=2:
616 serv.privmsg(auteur,"Syntaxe : KICK <channel> <pseudo>")
617 else:
618 notunderstood=True
619 elif cmd=="lost":
620 if auteur in self.ops and len(message)>1:
621 serv.privmsg(message[1],"J'ai perdu !")
622 log(self.serveur,"priv",auteur," ".join(message))
623 elif len(message)<=1:
624 serv.privmsg(auteur,"Syntaxe : LOST <channel>")
625 else:
626 notunderstood=True
627 elif cmd=="solde":
628 if len(message)==1:
629 if self.identities.has_key(auteur):
630 try:
631 self.nk.write('search ["x",["pseudo"],%s]'%(json.dumps(self.identities[auteur])))
632 ret=json.loads(self.nk.read())
633 solde=ret["msg"][0]["solde"]
634 pseudo=ret["msg"][0]["pseudo"]
635 except Exception as exc:
636 print exc
637 serv.privmsg(auteur,"failed")
638 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
639 return
640 serv.privmsg(auteur,"%s (%s)"%(float(solde)/100,pseudo.encode("utf8")))
641 else:
642 serv.privmsg(canal,"Je ne connais pas ton pseudo note.")
643 elif auteur in self.ops:
644 try:
645 self.nk.write('search ["x",["pseudo"],%s]'%(json.dumps(message[1])))
646 ret=json.loads(self.nk.read())
647 solde=ret["msg"][0]["solde"]
648 pseudo=ret["msg"][0]["pseudo"]
649 except Exception as exc:
650 serv.privmsg(auteur,"failed")
651 log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
652 return
653 serv.privmsg(auteur,"%s (%s)"%(float(solde)/100,pseudo.encode("utf8")))
654 else:
655 notunderstood=True
656 if notunderstood:
657 serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…")
658
659 def on_pubmsg(self, serv, ev):
660 auteur = irclib.nm_to_n(ev.source())
661 canal = ev.target()
662 message = ev.arguments()[0]
663 try:
664 test=bot_unicode(message)
665 except UnicodeBotError:
666 if not canal in self.quiet_channels:
667 serv.privmsg(canal,
668 "%s: Si je n'avais pas été créé avec la plus grande attention, votre encodage m'aurait déjà tué…"%(auteur))
669 return
670 pour_moi,message=self.pourmoi(serv,message)
671 if pour_moi and message.split()!=[]:
672 cmd=message.split()[0].lower()
673 try:
674 args=" ".join(message.split()[1:])
675 except:
676 args=""
677 if cmd in ["meurs","die","crève"]:
678 if auteur in self.overops:
679 log(self.serveur,canal,auteur,message+"[successful]")
680 self.die()
681 else:
682 serv.privmsg(canal,"%s: mourrez vous-même !"%(auteur))
683 log(self.serveur,canal,auteur,message+"[failed]")
684
685 elif cmd in ["part","leave","dégage","va-t-en","tut'tiresailleurs,c'estmesgalets"]:
686 if auteur in self.ops and (not (canal in self.stay_channels)
687 or auteur in self.overops):
688 serv.part(canal,message="Éjecté par %s"%(auteur))
689 log(self.serveur,canal,auteur,message+"[successful]")
690 if canal in self.chanlist:
691 self.chanlist.remove(canal)
692 else:
693 serv.privmsg(canal,"%s: Navré, mais je me vois contraint de refuser, je ne peux pas céder aux exigences du premier venu."%(auteur))
694 log(self.serveur,canal,auteur,message+"[failed]")
695
696 elif cmd in ["reconnect"]:
697 if auteur in self.ops:
698 try:
699 self.nk=self.new_connection_NK(serv,config_note_pseudo,
700 config_note_password,"special")[1]
701 except Exception as exc:
702 self.nk=None
703 log(self.serveur,"""Erreur dans on_pubmsg/"cmd in ["reconnect"]\n"""+str(exc))
704 if self.nk!=None:
705 serv.privmsg(canal,"%s: done"%(auteur))
706 log(self.serveur,canal,auteur,message+"[successful]")
707 else:
708 serv.privmsg(canal,"%s: failed"%(auteur))
709 log(self.serveur,canal,auteur,message+"[failed]")
710 for report in self.report_bugs_to:
711 serv.privmsg(report,"Connection to NK2015 failed, invalid password ?")
712 else:
713 serv.privmsg(canal,"%s: crève !"%(auteur))
714 log(self.serveur,canal,auteur,message+"[failed]")
715
716 elif cmd in ["deviens","pseudo"]:
717 if auteur in self.ops:
718 become=args
719 serv.nick(become)
720 log(self.serveur,canal,auteur,message+"[successful]")
721
722 if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
723 serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
724 elif cmd in ["ping"] and not canal in self.quiet_channels:
725 serv.privmsg(canal,"%s: pong"%(auteur))
726
727 elif cmd in ["solde","!solde"]:
728 if self.identities.has_key(auteur):
729 pseudo=self.identities[auteur]
730 try:
731 self.nk.write('search ["x",["pseudo"],%s]'%(json.dumps(pseudo)))
732 ret=json.loads(self.nk.read())
733 solde=ret["msg"][0]["solde"]
734 pseudo=ret["msg"][0]["pseudo"]
735 except Exception as exc:
736 serv.privmsg(canal,"%s: failed"%(auteur))
737 log(self.serveur,canal,auteur,message+"[failed]")
738 else:
739 serv.privmsg(canal,"%s: %s (%s)"%(auteur,float(solde)/100,pseudo.encode("utf8")))
740 log(self.serveur,canal,auteur,message+"[successful]")
741 else:
742 serv.privmsg(canal,"%s: Je ne connais pas votre pseudo note."%(auteur))
743 log(self.serveur,canal,auteur,message+"[unknown]")
744 elif (re.match("!?(pain au chocolat|chocolatine)",message.lower())
745 and not canal in self.quiet_channels):
746 serv.action(canal,"sert un pain au chocolat à %s"%(auteur))
747 elif re.match("!?manzana",message.lower()) and not canal in self.quiet_channels:
748 if auteur in config_manzana:
749 serv.action(canal,"sert une bouteille de manzana à %s"%(auteur))
750 else:
751 serv.action(canal,"sert un verre de manzana à %s"%(auteur))
752 if is_insult(message) and not canal in self.quiet_channels:
753 if is_not_insult(message):
754 answer=random.choice(config_compliment_answers)
755 for ligne in answer.split("\n"):
756 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
757 else:
758 answer=random.choice(config_insultes_answers)
759 for ligne in answer.split("\n"):
760 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
761 elif is_compliment(message) and not canal in self.quiet_channels:
762 answer=random.choice(config_compliment_answers)
763 for ligne in answer.split("\n"):
764 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
765 gros_match=is_gros(message)
766 if gros_match and not canal in self.quiet_channels:
767 taille=get_filesize()
768 answer=u"Mais non, je ne suis pas %s, %sKo tout au plus…"%(gros_match.groups()[0],taille)
769 serv.privmsg(canal,"%s: %s"%(auteur,answer.encode("utf8")))
770 if is_tesla(message) and not canal in self.quiet_channels:
771 l1,l2=config_tesla_answers,config_tesla_actions
772 n1,n2=len(l1),len(l2)
773 i=random.randrange(n1+n2)
774 if i>=n1:
775 serv.action(canal,l2[i-n1].encode("utf8"))
776 else:
777 serv.privmsg(canal,"%s: %s"%(auteur,l1[i].encode("utf8")))
778 if is_tamere(message) and not canal in self.quiet_channels:
779 answer=random.choice(config_tamere_answers)
780 for ligne in answer.split("\n"):
781 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
782 if is_tag(message) and not canal in self.quiet_channels:
783 if auteur in self.ops:
784 action=random.choice(config_tag_actions)
785 serv.action(canal,action.encode("utf8"))
786 self.quiet_channels.append(canal)
787 else:
788 answer=random.choice(config_tag_answers)
789 for ligne in answer.split("\n"):
790 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
791 if is_merci(message):
792 answer=random.choice(config_merci_answers)
793 for ligne in answer.split("\n"):
794 serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
795 out=re.match(ur"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$",
796 unicode(message.upper(),"utf8"))
797 if re.match("ma bite dans ton oreille",message) and not canal in self.quiet_channels:
798 serv.privmsg(canal,"%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !"%(auteur))
799 if out and not canal in self.quiet_channels:
800 out=out.groups()[0]
801 try:
802 out=int(out)
803 serv.privmsg(canal,"%s: %s !"%(auteur,out+1))
804 if out==2147483647:
805 serv.privmsg(canal,"%s: Ciel, un maxint ! Heureusement que je suis en python…"%(auteur))
806 return
807 if out+1>1000 and random.randrange(4)==0:
808 serv.privmsg(canal,"%s: Vous savez, moi et les chiffres…"%(auteur))
809 return
810 except Exception as exc:
811 pass
812 if re.match("[A-Y]",out):
813 alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
814 serv.privmsg(canal,"%s: %s !"%(auteur,alphabet[alphabet.index(out)+1]))
815 elif out=="Z":
816 serv.privmsg(canal,"%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?"%(auteur))
817 elif out in "[\\":
818 serv.privmsg(canal,"%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…"%(auteur))
819 elif re.match(ur"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+",out):
820 def translate(mess):
821 return "".join([{u"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i]:u"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i]
822 for i in range(20)}[j]
823 for j in mess])
824 out=int(translate(out))
825 serv.privmsg(canal,"%s: %s !"%(auteur,translate(str(out+1)).encode("utf8")))
826 if is_bonjour(message) and not canal in self.quiet_channels:
827 if is_night():
828 answer=random.choice(config_night_answers)
829 elif is_day():
830 answer=random.choice(config_bonjour_answers)
831 else:
832 answer=random.choice(config_bonsoir_answers)
833 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
834 if is_bonne_nuit(message) and not canal in self.quiet_channels:
835 answer=random.choice(config_bonne_nuit_answers)
836 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
837 if is_pan(message) and not canal in self.quiet_channels:
838 serv.privmsg(canal,"%s: ce n'est pas sur moi qu'il faut tirer, même si je sais que j'attire l'œil !"%(auteur))
839 else:
840 if message in ["!pain au chocolat","!chocolatine"] and not canal in self.quiet_channels:
841 serv.action(canal,"sert un pain au chocolat à %s"%(auteur))
842 if message in ["!manzana"] and not canal in self.quiet_channels:
843 if auteur in config_manzana:
844 serv.action(canal,"sert une bouteille de manzana à %s"%(auteur))
845 else:
846 serv.action(canal,"sert un verre de manzana à %s"%(auteur))
847 if re.match('^(.|§|:|)(w|b) [0-9]+$',message) and not canal in self.quiet_channels:
848 failanswers=config_buffer_fail_answers
849 answer=random.choice(failanswers)
850 serv.privmsg(canal,("%s: %s"%(auteur,answer)).encode("utf8"))
851 if not canal in self.quiet_channels:
852 mypseudo=self.nick
853 if re.match((u"^("+u"|".join(config_bonjour_triggers)
854 +u")( {}| all| tout le monde|(|à) tous)(\.|( |)!|)$"
855 ).format(mypseudo).lower(), message.strip().lower()):
856 answer=random.choice(config_bonjour_answers)
857 serv.privmsg(canal,answer.format(auteur).encode("utf8"))
858 if (is_perdu(message) and not canal in self.quiet_channels):
859 # proba de perdre sur trigger :
860 # avant 30min (enfin, config) : 0
861 # ensuite, +25%/30min, linéairement
862 deltat=time.time()-self.last_perdu
863 barre=(deltat-config_time_between_perdu)/(2*3600.0)
864 if random.uniform(0,1)<barre:
865 serv.privmsg(canal,"%s: J'ai perdu !"%(auteur))
866 self.last_perdu=time.time()
867
868 def on_action(self, serv, ev):
869 action = ev.arguments()[0]
870 auteur = irclib.nm_to_n(ev.source())
871 channel = ev.target()
872 try:
873 test=bot_unicode(action)
874 except UnicodeBotError:
875 serv.privmsg(channel,
876 "%s: Si je n'avais pas été créé avec la plus grande attention, votre encodage m'aurait déjà tué…"%(auteur))
877 return
878 mypseudo=self.nick
879
880 if is_bad_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
881 l1,l2=config_bad_action_answers,config_bad_action_actions
882 n1,n2=len(l1),len(l2)
883 i=random.randrange(n1+n2)
884 if i>=n1:
885 serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
886 else:
887 serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
888 if is_good_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
889 l1,l2=config_good_action_answers,config_good_action_actions
890 n1,n2=len(l1),len(l2)
891 i=random.randrange(n1+n2)
892 if i>=n1:
893 serv.action(channel,l2[i-n1].format(auteur).format(auteur).encode("utf8"))
894 else:
895 serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
896
897 def on_kick(self,serv,ev):
898 auteur = irclib.nm_to_n(ev.source())
899 channel = ev.target()
900 victime = ev.arguments()[0]
901 raison = ev.arguments()[1]
902 if victime==self.nick:
903 log(self.serveur,"%s kické par %s (raison : %s)" %(victime,auteur,raison))
904 time.sleep(2)
905 serv.join(channel)
906 l1,l2=config_kick_answers,config_kick_actions
907 n1,n2=len(l1),len(l2)
908 i=random.randrange(n1+n2)
909 if i>=n1:
910 serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
911 else:
912 serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
913
914 def _getnick(self):
915 return self.serv.get_nickname()
916 nick=property(_getnick)
917
918
919 if __name__=="__main__":
920 import sys
921 if len(sys.argv)==1:
922 print "Usage : basile.py <serveur> [--debug]"
923 exit(1)
924 serveur=sys.argv[1]
925 if "debug" in sys.argv or "--debug" in sys.argv:
926 debug=True
927 else:
928 debug=False
929 serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
930 "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
931 try:
932 serveur=serveurs[serveur]
933 except KeyError:
934 print "Server Unknown : %s"%(serveur)
935 exit(404)
936 basile=Basile(serveur,debug)
937 basile.start()