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