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