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