]> gitweb.pimeys.fr Git - bots/basile.git/blobdiff - basile.py
On peut consommer des cocas \o/
[bots/basile.git] / basile.py
index 9a53182bb4d6351ef644891250b705f0db174323..b467ac85b33c72387723d45b1f3daa2fb813017b 100755 (executable)
--- a/basile.py
+++ b/basile.py
 
 # Codé par 20-100 (commencé le 23/04/12)
 
-# Un bot IRC qui, un jour, s'interfacera avec la Note Kfet 2015
+""" Un bot IRC destiné à s'interfacer avec la Note Kfet 2015 """
 
-import irclib
-import ircbot
 import threading
 import random
 import time
-import socket, ssl, json
-import pickle
+import json
 import re
 import os
-from commands import getstatusoutput as ex
-
+import signal
 import sys
-config_debug_stdout=True
-if "--quiet" in sys.argv:
-    config_debug_stdout=False
-
-config_irc_password="NK2015BasileB0t"
-config_irc_pseudo="Basile"
-config_chanlist=["#bot","#flood"]
-config_stay_channels=["#bot","#flood"]
-config_quiet_channels=[]
-config_note_pseudo="Basile"
-config_note_password="NK2015BasileB0tr4nd0omp4assword]6_+{#]78{"
-config_logfile_template="basile.%s.log"
-def get_config_logfile(serveur):
-    serveurs={"acoeur.crans.org":"acoeur","irc.crans.org":"crans"}
-    return config_logfile_template%(serveurs[serveur])
-config_overops=["[20-100]","[20-100]_", "PEB"]
-config_ops=["Nit"]
-config_report_bugs_to=["[20-100]"]
-
-# config "ce bot a été codé par 20-100, tu te rappelles ?"
-config_manzana = ["[20-100]", "Petite-Peste"] 
-
-# config "tu m'traites ?"
-config_insultes=[u"conna(rd|sse)",u"pute",u"con(|ne)",u"enf(oiré|lure)",
-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",
-u"pétasse",u"enculé",u"chagasse",u"cagole",u"abruti",u"ahuri",u"analphabète",u"andouille",
-u"atardé",u"avorton",u"bachibouzouk",u"(balais|brosse) (de|à) chiotte(|s)",
-u"batard",u"blaireau",u"bouffon",u"branque",u"bouseux",u"branleur",u"catin",u"chacal",
-u"charogne",u"chiant(|e)",u"chieur",u"cochon",u"coprophage",u"couillon",u"crapule",u"crevard",
-u"cruche",u"cuistre",u"ducon",u"décérébré",
-u"emmerdeur",u"feignasse",u"fainéant",u"fourbe",u"freluquet",u"frigide",
-u"garce",u"glandu",u"gogol",u"goujat",u"gourdasse",u"gredin",u"gringalet",u"grognasse",
-u"naze",u"truie",u"iconoclaste",
-u"peigne(-|)cul",u"ignare",u"illétré",u"lèche(|-)cul",u"malotru",u"motherfucker",u"nabot",u"nigaud",
-u"nul",u"escroc",u"pouffiasse",u"pourriture",u"raclure",u"relou",u"sagouin",u"putain",
-u"péripatéticienne"]
-config_insultes_answers=[
-u"Oh non ! Quelle insulte ! Je crois que je ne m'en relèverai jamais…\nEnfin presque.",
-u"J'entends comme un vague murmure, vous disiez ?",
-u"Je vais prendre ça pour un compliment.",
-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…",
-u"Permettez-moi de vous retourner le compliment.",
-u"Votre indélicatesse vous sied à ravir.",
-u"Parfois, je me demande pourquoi je fais encore ce métier…",
-u"Le saviez-vous : l'invective ne déshonore que son auteur.",
-u"Le saviez-vous : vous perdez plus de temps à m'insulter qu'à vous taire.",
-u"Suis-je contraint à tolérer une telle outrecuidance ?",
-u"Mais je ne vous permets pas ! Enfin, pas comme ça…"]
-
-# config "à peine quelques kilos octets"
-config_gros=[u"gros",u"énorme",u"lourd"]
-
-# config spéciale-iota
-config_buffer_fail_answers=[u"Pas de chance !",u"Révisez vos classiques !",
-u"Encore un effort, je sais que vous pouvez le faire. ;)",
-u"Where did you learn to type?"]
-
-# config "jeu", d'ailleurs, j'ai perdu.
-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))"
-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)"
-config_regexp_etre_avec_c=u"c'(e(s|st)|étai(t|ent))"
-config_regexp_faire=u"fais"
-config_perdu=[u"perd(|s|ons|ez|ent|r(e|ai|as|a|ons|ez|ont)|(|r)(ais|ait|ions|iez|aient))"
-u"perd(i(s|t|rent)|î(mes|tes|t))", # oui, j'ai inclus qu'il perdît
-u"perdiss(e(|s|nt)|i(ons|ez))",
-u"perdu(|s|e|es)",u"perdant(|s|e|es)",u"perte(|s)",
-
-u"(gagn|trouv)"+config_premier_groupe_terminaisons,u"gagnant(|s|e|es)",u"gain(|s)",
-
-u"trouvant",u"trouvaille(|s)",
-
-u"victoire(|s)",u"vaincu(|s|e|es)",
-u"loose",u"lost",u"looser(|s)",u"win(|ner)(|s)",
-u"jeu(|x)",u"game(|s)"]
-config_time_between_perdu_trigger=3600*3 #temps moyen pour perdre en l'absence de trigger
-config_time_between_perdu_trigger_delta = 30*60 #marge autorisée autour de ^^^
-config_time_between_perdu=30*60 #temps pendant lequel on ne peut pas perdre
-
-# config "tais-toi"
-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"]
-config_tag_actions=[u"se tait",u"se tient coi"]
-config_tag_answers=[
-u"Ç'aurait été avec plaisir, mais je ne crois pas que vous puissiez vous passer de mes services.",
-u"Dès que cela sera utile.",
-u"Une autre fois, peut-être.",
-u"Si je me tais, qui vous rappellera combien vous me devez ?",
-u"J'aurais aimé accéder à votre requête, mais après mûre réflexion, j'en ai perdu l'envie.",
-u"Je ne ressens pas de besoin irrésistible de me taire, navré."]
-
-# config ping
-config_tesla_triggers=[u"t('|u )es là \?",u"\?",u"plop \?",u"plouf \?"]
-config_tesla_answers=[
-u"Oui, je suis là.",
-u"J'écoute.",
-u"En quoi puis-je me rendre utile ?",
-u"On a besoin de moi ?"
-]
-config_tesla_actions=[u"est là",u"attend des instructions",u"est toujours disponible"]
-
-# config en cas de non-insulte
-config_compliment_triggers=[u"gentil",u"cool",u"sympa",u"efficace"]
-config_compliment_answers=[
-u"Merci, c'est gentil de votre part. :)",
-u"Permettez-moi de vous retourner le compliment, sans ironie cette fois.",
-u"Je vous remercie.",
-u"C'est trop d'honneur.",
-u"Vous êtes bien aimable."
-]
 
-# config merci
-config_merci_triggers=[u"merci",u"remercie",u"thx",u"thank(|s)"]
-config_merci_answers=[u"Mais de rien.",u"À votre service. ;)",u"Quand vous voulez. :)",
-u"Tout le plaisir est pour moi."]
-
-# config "ta mère" 
-config_tamere_triggers=[u"ta mère"]
-config_tamere_answers=[u"Laissez donc ma mère en dehors de ça !",
-u"Puis-je préciser que je n'ai pas de mère ? Seulement deux pères…",
-u"""Un certain Max chantait "♩ J'ai vu ta mère sur chat rouleeeeeeette ♫", vous êtes de sa famille ?""",
-u"""N'avait-on pas dit "pas les mamans" ?"""]
-
-# config pour les actions désagréables à Basile
-config_bad_action_triggers=[u"(frappe|cogne|tape)(| sur)",u"(démolit|dégomme|fouette|agresse|tabasse)",
-u"(vomit|pisse|chie|crache) sur",u"slap(|s)"]
-config_bad_action_answers=[
-u"Je ne peux pas dire que j'apprécie, mais je l'ai sans doute bien mérité.",
-u"{}: Pourquoi tant de violence en ce monde si doux ?",
-u"""Si je n'étais pas aussi prude, je dirais "Mais euh…", cependant, je me contenterai de hausser un sourcil.""",
-u"{}: J'aurais préféré que vous ne fassiez pas cela en public.",
-u"{}: Entre nous, cela vous gratifie-t-il ?",
-u"{}: Une telle relation entre nous deux n'est pas saine, revenons à quelque chose de plus conventionnel. :D",
-u"J'ai la désagréable impression que {} cherche comment tuer le temps en ce moment…"
-]
-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à ?"]
+# Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
+sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
+import irclib
+import ircbot
 
-# config pour les actions agréables à Basile
-config_good_action_triggers=[u"fait (:?des bisous|un c(?:â|a)lin|des c(?:â|a)lins) à",u"embrasse",u"c(?:â|a)line",u"caresse"]
-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…"]
-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"]
+from commands import getstatusoutput as ex
 
-# config bonjour/bonsoir/que fais-tu encore debout à cette heure, gros sale !
-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"]
-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"]
-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é."]
-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, {} ?"]
-config_daytime = [7,18]
-config_nighttime = [3, 6]
+#: Config de basile
+import config
+#: Module responsable du dialogue avec la NoteKfet2015
+import nk
 
-# config dodo
-config_bonne_nuit_triggers=[u"bonne nuit",u"'?nite",u"'?nuit",u"'?night",u"good night",u"'?nenuit"]
-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 !"]
+# la partie qui réfère au fichier lui-même est mieux ici
+# sinon on réfère la config et pas le fichier lui-même
+import os
+config.thisfile = os.path.realpath(__file__)
 
-# config PEB est encore en train d'abuser de ses droits.
-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 !"]
-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."]
+def get_config_logfile(serveur):
+    """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
+    serveurs = {"acoeur.crans.org" : "acoeur",
+                "irc.crans.org" : "crans"}
+    return config.logfile_template % (serveurs[serveur],)
 
-config_thisfile= os.path.realpath( __file__ )
 def get_filesize():
-    return ex("ls -s %s"%(config_thisfile))[1].split()[0]
-
-class NKError(Exception):
-    def __init__(self,msg):
-        Exception.__init__(self)
-        self.msg=msg
-    def __str__(self):
-        return str(self.msg)
-    def __unicode__(self):
-        return unicode(self.msg)
+    """Récupère la taille de ce fichier."""
+    return ex("ls -s %s" % (config.thisfile))[1].split()[0]
 
-class NKRefused(NKError):
-    pass
-
-class NKHelloFailed(NKError):
-    pass
-
-class NKUnknownError(NKError):
-    pass
-
-def log(serveur,channel,auteur=None,message=None):
-    f=open(get_config_logfile(serveur),"a")
-    if auteur==message==None:
+def log(serveur, channel, auteur=None, message=None):
+    """Enregistre une ligne de log."""
+    if auteur == message == None:
         # alors c'est que c'est pas un channel mais juste une ligne de log
-        chain="%s %s"%(time.strftime("%F %T"),channel)
+        chain = u"%s %s" % (time.strftime("%F %T"), channel)
     else:
-        chain="%s [%s:%s] %s"%(time.strftime("%F %T"),channel,auteur,message)
-    f.write(chain+"\n")
-    if config_debug_stdout:
-        print chain
+        chain = u"%s [%s:%s] %s" % (time.strftime("%F %T"), channel, auteur, message)
+    f = open(get_config_logfile(serveur), "a")
+    f.write((chain + u"\n").encode("utf-8"))
     f.close()
+    if config.debug_stdout:
+        print chain
 
-def connect_NK():
-    sock=socket.socket()
-    try:
-        # On établit la connexion sur port 4242
-        sock.connect(("127.0.0.1",4242))
-        # On passe en SSL
-        sock=ssl.wrap_socket(sock,ca_certs='../keys/ca_.crt')
-        # On fait un hello
-        sock.write('hello "Basile"')
-        # On récupère la réponse du hello
-        out=sock.read()
-        out=json.loads(out)
-    except Exception as exc:
-        # Si on a foiré quelque part, c'est que le serveur est down
-        raise NKRefused(str(exc))
-    if out["retcode"]==0:
-        return sock
-    elif out["retcode"]==11:
-        raise NKHelloFailed(out["errmsg"])
-    else:
-        raise NKUnknownError(out["errmsg"])
-
-def login_NK(username,password,typ="bdd"):
-    sock=connect_NK()
-    if typ=="special": # ça c'est pour Basile lui-même
-        masque='["note"]'
-    elif typ=="bdd":
-        masque='[["all"],["all"],false]'
-    try:
-        # Basile a un compte special user
-        commande='login [%s,%s,"%s",%s]'%(json.dumps(username),json.dumps(password),typ,masque)
-        sock.write(commande)
-        out=sock.read()
-    except Exception as exc:
-        # Si on a foiré quelque part, c'est que le serveur est down
-        raise NKRefused(str(exc))
-    # On vérifie ensuite que le login
-    return json.loads(out),sock
-
+def regex_join(liste, avant=u".*(?:^| )", apres=u"(?:$|\.| |,|;).*"):
+    """Fabrique une regexp à partir d'une liste d'éléments à matcher."""
+    return avant + u"(" + u"|".join(liste) + u")" + apres
 
-def is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
+def is_something(chain, regexp=None, matches=[], avant=u".*(?:^| )", apres=u"(?:$|\.| |,|;).*",
+                 case_sensitive=False):
+    """Vérifie si chain contient un des éléments de ``matches``.
+       Si ``regexp`` est fournie, c'est simplement elle qui est testée"""
     if case_sensitive:
-        chain=unicode(chain,"utf8")
-    else:
-        chain=unicode(chain,"utf8").lower()
-    allmatches="("+"|".join(matches)+")"
-    reg=(avant+allmatches+apres).lower()
-    o=re.match(reg,chain)
+        chain = chain.lower()
+    if regexp == None:
+        regexp = regex_join(matches, avant, apres)
+        regexp = re.compile(regexp)
+    o = regexp.match(chain)
     return o
 
-def is_insult(chain,debug=True):
-    return is_something(chain,config_insultes,avant=".*(?:^| |')")
+def regexp_compile():
+    """Compilation des regexp à partir de la conf.
+       Place les résultats dans le namespace de ``config``"""
+    config.insult_regexp = regex_join(config.insultes, avant=u".*(?:^| |')")
+    config.insult_regexp_compiled = re.compile(config.insult_regexp)
+    
+    config.not_insult_regexp = u".*pas %s%s" % (config.amplifier_regexp, config.insult_regexp)
+    config.not_insult_regexp_compiled = re.compile(config.not_insult_regexp)
+    
+    config.compliment_regexp = regex_join(config.compliment_triggers, avant=u".*(?:^| |')")
+    config.compliment_regexp_compiled = re.compile(config.compliment_regexp)
+    
+    config.perdu_regexp = regex_join(config.perdu)
+    config.perdu_regexp_compiled = re.compile(config.perdu_regexp)
+    
+    config.tag_regexp = regex_join(config.tag_triggers)
+    config.tag_regexp_compiled = re.compile(config.tag_regexp)
+    
+    config.gros_regexp = regex_join(config.gros)
+    config.gros_regexp_compiled = re.compile(config.gros_regexp)
+    
+    config.tesla_regexp = regex_join(config.tesla_triggers, avant=u"^", apres="$")
+    config.tesla_regexp_compiled = re.compile(config.tesla_regexp)
+    
+    config.merci_regexp = regex_join(config.merci_triggers)
+    config.merci_regexp_compiled = re.compile(config.merci_regexp)
+    
+    config.tamere_regexp = regex_join(config.tamere_triggers)
+    config.tamere_regexp_compiled = re.compile(config.tamere_regexp)
+    
+    config.bonjour_regexp = regex_join(config.bonjour_triggers, avant=u"^")
+    config.bonjour_regexp_compiled = re.compile(config.bonjour_regexp)
+    
+    config.bonne_nuit_regexp = regex_join(config.bonne_nuit_triggers, avant=u"^")
+    config.bonne_nuit_regexp_compiled = re.compile(config.bonne_nuit_regexp)
+    
+    config.pan_regexp = regex_join(config.pan_triggers, avant=".*", apres=".*")
+    config.pan_regexp_compiled = re.compile(config.pan_regexp)
+    
+    
+regexp_compile()
+def is_insult(chain, debug=True):
+    """Vérifie si ``chain`` contient une insulte."""
+    return is_something(chain, config.insult_regexp_compiled)
 def is_not_insult(chain):
-    chain=unicode(chain,"utf8")
-    insult_regexp=u"("+u"|".join(config_insultes)+u")"
-    middle_regexp=u"(une? (?:(?:putain|enfoiré) d(?:e |'))*|)(?:| super )(?: (?:gros|petit|grand|énorme) |)"
-    reg=".*pas %s%s.*"%(middle_regexp,insult_regexp)
-    if re.match(reg,chain):
-        return True
-    else:
-        return False
-def is_compliment(chain,debug=True):
-    return is_something(chain,config_compliment_triggers,avant=".*(?:^| |')")
+    """Vérifie si ``chain`` contient une insulte à la forme négative."""
+    return is_something(chain, config.not_insult_regexp_compiled)
+def is_compliment(chain, debug=True):
+    """Vérifie si ``chain`` contient un compliment."""
+    return is_something(chain, config.compliment_regexp_compiled)
 def is_perdu(chain):
-    return is_something(chain,config_perdu)
+    """Vérifie si ``chain`` contient une raison de perdre."""
+    return is_something(chain, config.perdu_regexp_compiled)
 def is_tag(chain):
-    return is_something(chain,config_tag_triggers)
+    """Vérifie si ``chain`` demande de fermer sa gueule."""
+    return is_something(chain, config.tag_regexp_compiled)
 def is_gros(chain):
-    return is_something(chain,config_gros)
+    """Vérifie si ``chain`` traite de gros."""
+    return is_something(chain, config.gros_regexp_compiled)
 def is_tesla(chain):
-    return is_something(chain,config_tesla_triggers,avant=u"^",apres=u"$",debug=True)
+    """Vérifie si ``chain`` est un ping."""
+    return is_something(chain, config.tesla_regexp_compiled)
 def is_merci(chain):
-    return is_something(chain,config_merci_triggers)
+    """Vérifie si ``chain`` contient un remerciement."""
+    return is_something(chain, config.merci_regexp_compiled)
 def is_tamere(chain):
-    return is_something(chain,config_tamere_triggers)
+    """Vérifie si ``chain`` traite ma mère."""
+    return is_something(chain, config.tamere_regexp_compiled)
 def is_bad_action_trigger(chain,pseudo):
-    return is_something(chain,config_bad_action_triggers,avant=u"^",
-                            apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
+    """Vérifie si ``chain`` est une action méchante.
+       On a besoin d'une regexp dynamique à cause du pseudo qui peut changer."""
+    return is_something(chain, matches=config.bad_action_triggers, avant=u"^",
+                            apres="(?: [a-z]*ment)? %s($|\.| |,|;).*" % (pseudo))
 def is_good_action_trigger(chain,pseudo):
-    return is_something(chain,config_good_action_triggers,avant=u"^",
-                            apres="(?: [a-z]*ment)? %s($|\.| |,|;).*"%(pseudo))
+    """Vérifie si ``chain`` est une action gentille.
+       On a besoin d'une regexp dynamique à cause du pseudo qui peut changer."""
+    return is_something(chain, matches=config.good_action_triggers, avant=u"^",
+                            apres="(?: [a-z]*ment)? %s($|\.| |,|;).*" % (pseudo))
 def is_bonjour(chain):
-    return is_something(chain,config_bonjour_triggers,avant=u"^")
+    """Vérifie si ``chain`` contient un bonjour."""
+    return is_something(chain, config.bonjour_regexp_compiled)
 def is_bonne_nuit(chain):
-    return is_something(chain,config_bonne_nuit_triggers,avant=u"^")
+    """Vérifie si ``chain`` contient un bonne nuit."""
+    return is_something(chain, config.bonne_nuit_regexp_compiled)
 def is_pan(chain):
-    return re.match(u"^(pan|bim|bang)( .*)?$",unicode(chain,"utf8").lower().strip())
+    """Vérifie si ``chain`` contient un pan."""
+    return is_something(chain, config.pan_regexp_compiled)
 
 def is_time(conf):
-    _,_,_,h,m,s,_,_,_=time.localtime()
-    return (conf[0],0,0)<(h,m,s)<(conf[1],0,0)
+    """Vérifie si l'heure actuelle est entre les deux heures ``conf[0]`` et ``conf[1]``"""
+    _, _, _, h, m, s, _, _, _ = time.localtime()
+    return (conf[0], 0, 0) < (h, m, s) < (conf[1], 0, 0)
 def is_day():
-    return is_time(config_daytime)
+    """Vérifie si on est le jour."""
+    return is_time(config.daytime)
 def is_night():
-    return is_time(config_nighttime)
+    """Vérifie si on est la nuit."""
+    return is_time(config.nighttime)
 
       
 class UnicodeBotError(Exception):
+    """Erreur levée si quelqu'un fait du caca avec son encodage."""
     pass
+
+class CrashError(Exception):
+    """Pour pouvoir faire crasher Basile, parce que ça a l'air drôle."""
+    def __init__(self, msg=""):
+        Exception.__init__(self, msg)
+
 def bot_unicode(chain):
+    """Essaye de décoder ``chain`` en UTF-8.
+       Lève une py:class:`UnicodeBotError` en cas d'échec."""
     try:
-        unicode(chain,"utf8")
+        return chain.decode("utf8")
     except UnicodeDecodeError as exc:
         raise UnicodeBotError
 
+
 class Basile(ircbot.SingleServerIRCBot):
-    def __init__(self,serveur,debug=False):
-        temporary_pseudo=config_irc_pseudo+str(random.randrange(10000,100000))
+    """Classe principale : définition du bot Basile."""
+    def __init__(self, serveur, debug=False):
+        temporary_pseudo = config.irc_pseudo + str(random.randrange(10000,100000))
         ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
-                              temporary_pseudo,"Basile, le bot irc.[Codé par 20-100, fouettez-le]", 10)
-        self.debug=debug
-        self.serveur=serveur
-        self.overops=config_overops
-        self.ops=self.overops+config_ops
-        self.report_bugs_to=config_report_bugs_to
-        self.chanlist=config_chanlist
-        self.sockets={}
-        self.identities=pickle.load(open("identities.pickle","r"))
-        self.stay_channels=config_stay_channels
-        self.quiet_channels=config_quiet_channels
-        self.last_perdu=0
-
-
-    def new_connection_NK(self,serv,username,password,typ="bdd"):
+                              temporary_pseudo, "Basile, le bot irc. [Codé par 20-100]", 10)
+        self.debug = debug
+        self.serveur = serveur
+        self.overops = config.overops
+        self.ops = self.overops + config.ops
+        self.report_bugs_to = config.report_bugs_to
+        self.chanlist = config.chanlist
+        self.identities = json.load(open(config.identities_file, "r"))
+        self.stay_channels = config.stay_channels
+        self.quiet_channels = config.quiet_channels
+        self.last_perdu = 0
+    
+    ### Communication NK
+    def new_connection_NK(self, serv, username, password, typ="bdd"):
+        """Renvoie (``True``, <une socket ouverte et authentifiée sur la NoteKfet2015>)
+           ou bien (``False``, None)"""
         try:
-            login_result,sock=login_NK(username,password,typ)
-            droits,retcode,errmsg=login_result["msg"],login_result["retcode"],login_result["errmsg"]
-        except NKRefused as exc:
+            login_result, sock = nk.login(username, password, typ)
+            info, retcode, errmsg = login_result["msg"], login_result["retcode"], login_result["errmsg"]
+        except nk.NKRefused as exc:
             for report in self.report_bugs_to:
-                serv.privmsg(report,"Le Serveur NK2015 est down.")
-            return (False,None)
-        except NKHelloFailed as exc:
+                serv.privmsg(report, "Le Serveur NK2015 est down.")
+            return (False, None, None)
+        except nk.NKHelloFailed as exc:
             for report in self.report_bugs_to:
                 serv.privmsg(report,
-                             "La version du site utilisée n'est pas supportée par le serveur NK2015.")
-            return (False,None)
-        except NKUnknownError as exc:
-            erreurs=["Une fucking erreur inconnue s'est produite"]
-            erreurs+=str(exc).split("\n")
+                             "La version du protocole utilisée n'est pas supportée par le serveur NK2015.")
+            return (False, None, None)
+        except nk.NKUnknownError as exc:
+            erreurs = ["Une fucking erreur inconnue s'est produite"]
+            erreurs += str(exc).split("\n")
             for report in self.report_bugs_to:
                 for err in erreurs:
-                    serv.privmsg(report,err)
-            return (False,None)
+                    serv.privmsg(report, err)
+            return (False, None, None)
         except Exception as exc:
             # Exception qui ne vient pas de la communication avec le serveur NK2015
-            log(self.serveur,"Erreur dans new_connection_NK\n"+str(exc))
-            return (False,None)
-        if retcode==0:
-            return (True,sock)
+            log(self.serveur, "Erreur dans new_connection_NK\n" + str(exc))
+            return (False, None, None)
+        if retcode == 0:
+            return (True, info, sock)
         else:
-            return (False,None)
-
-    def give_me_my_pseudo(self,serv):
-        serv.privmsg("NickServ","RECOVER %s %s"%(config_irc_pseudo,config_irc_password))
-        serv.privmsg("NickServ","RELEASE %s %s"%(config_irc_pseudo,config_irc_password))
+            return (False, None, None)
+    
+    ### Utilitaires
+    def _getnick(self):
+        """Récuère le nick effectif du bot sur le serveur."""
+        return self.serv.get_nickname()
+    nick = property(_getnick)
+    
+    def give_me_my_pseudo(self, serv):
+        """Récupère le pseudo auprès de NickServ."""
+        serv.privmsg("NickServ", "RECOVER %s %s" % (config.irc_pseudo, config.irc_password))
+        serv.privmsg("NickServ", "RELEASE %s %s" % (config.irc_pseudo, config.irc_password))
         time.sleep(0.3)
-        serv.nick(config_irc_pseudo)
+        serv.nick(config.irc_pseudo)
+    
+    def pourmoi(self, serv, message):
+        """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
+        pseudo = self.nick
+        pseudo = pseudo.decode("utf-8")
+        size = len(pseudo)
+        if message[:size] == pseudo and len(message) > size and message[size] == ":":
+            return (True, message[size+1:].lstrip(" "))
+        else:
+            return (False, message)
+    
+    ### Exécution d'actions
+    def lost(self, serv, channel, forced=False):
+        """Réaction à un trigger de perdu.
+           Annonce "J'ai perdu" sur le channel si on n'a pas perdu depuis un certain temps."""
+        if self.last_perdu + config.time_between_perdu < time.time() or forced:
+            if not channel in self.quiet_channels or forced:
+                serv.privmsg(channel, "J'ai perdu !")
+            self.last_perdu=time.time()
+            delay=config.time_between_perdu_trigger
+            delta=config.time_between_perdu_trigger_delta
+            serv.execute_delayed(random.randrange(delay-delta,delay+delta),self.lost,(serv,channel))
+    
+    def quitter(self, chan, leave_message=None):
+        """Quitter un channel avec un message customisable."""
+        if leave_message == None:
+            leave_message = random.choice(config.leave_messages)
+        self.serv.part(chan, message=leave_message.encode("utf8"))
+    
+    def mourir(self):
+        """Se déconnecter du serveur IRC avec un message customisable."""
+        quit_message = random.choice(config.quit_messages)
+        self.die(msg=quit_message.encode("utf8"))
+    
+    def execute_reload(self, auteur=None):
+        """Recharge la config."""
+        reload(config)
+        regexp_compile()
+        if auteur in [None, "SIGHUP"]:
+            towrite = "Config reloaded" + " (SIGHUP received)" * (auteur == "SIGHUP")
+            for to in config.report_bugs_to:
+                self.serv.privmsg(to, towrite)
+            log(self.serveur, towrite)
+            return True, None
+        else:
+            return True, u"Config reloaded"
     
+    def crash(self, chan="nowhere", who="nobody"):
+        """Fait crasher le bot."""
+        where = "en privé" if chan == "priv" else "sur le chan %s" % chan
+        raise CrashError("Crash demandé par %s %s" % (who, where))
+    
+    ACTIONS = {
+        "reload" : execute_reload,
+        }
+    
+    def execute_something(self, something, params, place=None, auteur=None):
+        """Exécute une action et répond son résultat à ``auteur``
+           sur un chan ou en privé en fonction de ``place``"""
+        action = self.ACTIONS[something]
+        success, message = action(self, **params)
+        if message:
+            if irclib.is_channel(place):
+                message = "%s: %s" % (auteur, message.encode("utf-8"))
+            self.serv.privmsg(place, message)
+        log(self.serveur, place, auteur, something + "%r" % params + ("[successful]" if success else "[failed]"))
+    
+    ### Surcharge des events du Bot
     def on_welcome(self, serv, ev):
-        self.serv=serv # ça serv ira :)
+        """À l'arrivée sur le serveur."""
+        self.serv = serv # ça serv ira :)
         self.give_me_my_pseudo(serv)
-        serv.privmsg("NickServ","identify %s"%(config_irc_password))
-        log(self.serveur,"Connected")
+        serv.privmsg("NickServ", "IDENTIFY %s" % (config.irc_password))
+        log(self.serveur, "Connected")
         if self.debug:
-            self.chanlist=["#bot"]
+            self.chanlist = ["#bot"]
         for c in self.chanlist:
-            log(self.serveur,"JOIN %s"%(c))
+            log(self.serveur, "JOIN %s" % (c))
             serv.join(c)
         # on ouvre la connexion note de Basile, special user
-        self.nk=self.new_connection_NK(serv,config_note_pseudo,config_note_password,"special")[1]
-        if self.nk==None:
+        self.nk = self.new_connection_NK(serv, config.note_pseudo, config.note_password, "special")[2]
+        if self.nk == None:
             for report in self.report_bugs_to:
-                serv.privmsg(report,"Connection to NK2015 failed, invalid password ?")
-
-    def lost(self,serv,channel,forced=False):
-        if self.last_perdu+config_time_between_perdu<time.time() or forced:
-            if not channel in self.quiet_channels or forced:
-                serv.privmsg(channel,"J'ai perdu !")
-            self.last_perdu=time.time()
-            delay=config_time_between_perdu_trigger
-            delta=config_time_between_perdu_trigger_delta
-            serv.execute_delayed(random.randrange(delay-delta,delay+delta),self.lost,(serv,channel))
-    
-    def pourmoi(self, serv, message):
-        """renvoie (False,lemessage) ou (True, le message amputé de "pseudo: ")"""
-        pseudo=self.nick
-        size=len(pseudo)
-        if message[:size]==pseudo and len(message)>size and message[size]==":":
-            return (True,message[size+1:].lstrip(" "))
-        else:
-            return (False,message)
+                serv.privmsg(report, "Connection to NK2015 failed, invalid password ?")
 
     def on_privmsg(self, serv, ev):
-        message=ev.arguments()[0]
+        """À la réception d'un message en privé."""
+        message = ev.arguments()[0]
         auteur = irclib.nm_to_n(ev.source())
         try:
-            test=bot_unicode(message)
+            message = bot_unicode(message)
         except UnicodeBotError:
-            serv.privmsg(auteur,
-              "Si je n'avais pas été créé avec la plus grande attention, votre encodage m'aurait déjà tué…")
+            if config.utf8_trigger:
+                serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf8"))
             return
-        message=message.split()
-        cmd=message[0].lower()
-        notunderstood=False
-        if cmd=="connect":
-            if not len(message) in [2,3]:
-                serv.privmsg(auteur,"Syntaxe : CONNECT [<username>] <password>")
-                return
-            username=auteur
-            if len(message)>2:
-                username=(message[1])
-                password=" ".join(message[2:])
-            else:
-                password=" ".join(message[1:])
-            success,sock=self.new_connection_NK(serv,username,password)
-            if success:
-                self.sockets[username]=sock
-                serv.privmsg(auteur,"Connection successful")
-                log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
-            else:
-                serv.privmsg(auteur,"Connection failed")
-                log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-
-        elif cmd=="help":
-            helpdico={"connect": """CONNECT [<username>] <password>
- Ouvre une connexion au serveur NoteKfet.
- Si <username> n'est pas précisé, j'utiliserais l'identité sous laquelle je te connais, ou, à défaut, ton pseudo.""", 
-"identify": """IDENTIFY <username> <password>
- Vérifie le mot de passe et me permet de savoir à l'avenir quel est ton pseudo note kfet.
- Sans paramètre, je réponds sous quel pseudo je te connais.""",
-"drop":"""DROP <password>
- Vérifie le mot de passe et me fait d'oublier ton pseudo note kfet."""}
-            helpmsg_default="""Liste des commandes :
- HELP       Affiche de l'aide sur une commande.
- CONNECT    Ouvre une connection au serveur Note Kfet.
- IDENTIFY   Me permet de savoir qui tu es sur la note kfet.
- DROP       Me fait oublier ton identité.
- SOLDE      Obtenir ton solde"""
-            helpmsg_ops="""
- JOIN       Faire rejoindre un chan
- LEAVE      Faire quitter un chan
- QUIET      Se taire sur un chan
- NOQUIET    Opposé de QUIET
- LOST       Perdre sur un chan
- SOLDE <pseudo>  Donner le solde de quelqu'un"""
-            helpmsg_overops="""
- SAY        Fait envoyer un message sur un chan ou à une personne
- DO         Fait faire une action sur un chan
- STAY       Ignorera les prochains LEAVE pour un chan
- NOSTAY     Opposé de STAY
- DIE        Mourir"""
+        message = message.split()
+        cmd = message[0].lower()
+        notunderstood = False
+        if cmd == u"help":
+            op,overop=auteur in self.ops, auteur in self.overops
             if len(message)==1:
-                helpmsg=helpmsg_default
-                if auteur in self.ops:
-                    helpmsg+=helpmsg_ops
-                if auteur in self.overops:
-                    helpmsg+=helpmsg_overops
+                helpmsg = config.helpmsg_default
+                if op:
+                    helpmsg += config.helpmsg_ops
+                if overop:
+                    helpmsg += config.helpmsg_overops
             else:
-                helpmsg=helpdico.get(message[1].lower(),"Commande inconnue.")
+                helpmsgs = config.helpdico.get(message[1].lower(), ["Commande inconnue.", None, None])
+                helpmsg = helpmsgs[0]
+                if op and helpmsgs[1]:
+                    if helpmsg:
+                        helpmsg += "\n" + helpmsgs[1]
+                    else:
+                        helpmsg = helpmsgs[1]
+                if overop and helpmsgs[2]:
+                    if helpmsg:
+                        helpmsg += "\n" + helpmsgs[2]
+                    else:
+                        helpmsg = helpmsgs[2]
             for ligne in helpmsg.split("\n"):
-                serv.privmsg(auteur,ligne)
-        elif cmd=="identify":
-            if len(message)==1:
+                serv.privmsg(auteur, ligne.encode("utf-8"))
+        elif cmd == u"identify":
+            if len(message) == 1:
                 if self.identities.has_key(auteur):
-                    serv.privmsg(auteur,"Je te connais sous le pseudo note %s."%(
-                                     self.identities[auteur].encode("utf8")))
+                    serv.privmsg(auteur, "Je vous connais sous le pseudo note %s." % (
+                                     self.identities[auteur]["pseudo"].encode("utf8")))
                 else:
-                    serv.privmsg(auteur,"Je ne connais pas ton pseudo note.")
-            elif len(message)>=3:
-                username,password=message[1],unicode(" ".join(message[2:]),"utf8")
-                success,_=self.new_connection_NK(serv,username,password)
+                    serv.privmsg(auteur, "Je ne connais pas votre pseudo note.")
+            elif len(message) >= 3:
+                username, password = message[1], " ".join(message[2:])
+                success, info, _ = self.new_connection_NK(serv, username, password)
                 if success:
-                    log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
-                    serv.privmsg(auteur,"Identité enregistrée.")
-                    self.identities[auteur]=username
-                    pickle.dump(self.identities,open("identities.pickle","w"))
+                    log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                    serv.privmsg(auteur, "Identité enregistrée.")
+                    self.identities[auteur] = info
+                    json.dump(self.identities, open(config.identities_file,"w"))
                 else:
-                    log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-                    serv.privmsg(auteur,"Mot de passe invalide. (ou serveur down)")
+                    log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
+                    serv.privmsg(auteur, "Mot de passe invalide. (ou serveur down)")
             else:
-                serv.privmsg(auteur,u"Syntaxe : IDENTIFY [<username> <password>]")
-        elif cmd=="drop":
-            if len(message)>1:
+                serv.privmsg(auteur, "Syntaxe : IDENTIFY [<username> <password>]")
+        elif cmd == u"drop":
+            if len(message) > 1:
                 if self.identities.has_key(auteur):
-                    password=" ".join(message[1:])
-                    success,_=self.new_connection_NK(serv,self.identities[auteur],password)
+                    password = " ".join(message[1:])
+                    success, _, _ = self.new_connection_NK(serv, self.identities[auteur], password)
                     if success:
                         del self.identities[auteur]
-                        log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
-                        pickle.dump(self.identities,open("identities.pickle","w"))
-                        serv.privmsg(auteur,"Identité oubliée.")
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                        json.dump(self.identities, open(config.identities_file, "w"))
+                        serv.privmsg(auteur, "Identité oubliée.")
                     else:
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-                        serv.privmsg(auteur,"Mot de passe invalide. (ou serveur down)")
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
+                        serv.privmsg(auteur, "Mot de passe invalide. (ou serveur down)")
                 else:
-                    serv.privmsg(auteur,"Je ne connais pas ton pseudo note.")
+                    serv.privmsg(auteur, "Je ne connais pas ton pseudo note.")
             else:
-                serv.privmsg(auteur,"Syntaxe : DROP <password>")
-        elif cmd=="join":
+                serv.privmsg(auteur, "Syntaxe : DROP <password>")
+        elif cmd == u"join":
             if auteur in self.ops:
-                if len(message)>1:
+                if len(message) > 1:
                     if message[1] in self.chanlist:
-                        serv.privmsg(auteur,"Je suis déjà sur %s"%(message[1]))
+                        serv.privmsg(auteur, "Je suis déjà sur %s" % (message[1]))
                     else:
                         serv.join(message[1])
                         self.chanlist.append(message[1])
-                        serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
-                        log(self.serveur,"priv",auteur," ".join(message))
+                        serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
+                        log(self.serveur, "priv", auteur, " ".join(message))
                 else:
-                    serv.privmsg(auteur,"Channels : "+" ".join(self.chanlist))
+                    serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
             else:
-                notunderstood=True
-        elif cmd=="leave":
-            if auteur in self.ops and len(message)>1:
+                notunderstood = True
+        elif cmd == u"leave":
+            if auteur in self.ops and len(message) > 1:
                 if message[1] in self.chanlist:
                     if not (message[1] in self.stay_channels) or auteur in self.overops:
-                        serv.part(message[1])
+                        self.quitter(message[1], " ".join(message[2:]))
                         self.chanlist.remove(message[1])
-                        log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
                     else:
-                        serv.privmsg(auteur,"Non, je reste !")
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
+                        serv.privmsg(auteur, "Non, je reste !")
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
                 else:
-                    serv.privmsg(auteur,"Je ne suis pas sur %s"%(message[1]))
+                    serv.privmsg(auteur, "Je ne suis pas sur %s" % (message[1]))
             else:
-                notunderstood=True
-        elif cmd=="stay":
+                notunderstood = True
+        elif cmd == u"stay":
             if auteur in self.overops:
-                if len(message)>1:
+                if len(message) > 1:
                     if message[1] in self.stay_channels:
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-                        serv.privmsg(auteur,"Je stay déjà sur %s."%(message[1]))
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
+                        serv.privmsg(auteur, "Je stay déjà sur %s." % (message[1]))
                     else:
-                        log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
                         self.stay_channels.append(message[1])
-                        serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
+                        serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
                 else:
-                    serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
+                    serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
             else:
-                notunderstood=True
-        elif cmd=="nostay":
+                notunderstood = True
+        elif cmd == u"nostay":
             if auteur in self.overops:
-                if len(message)>1:
+                if len(message) > 1:
                     if message[1] in self.stay_channels:
-                        log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
                         self.stay_channels.remove(message[1])
-                        serv.privmsg(auteur,"Stay channels : "+" ".join(self.stay_channels))
+                        serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
                     else:
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-                        serv.privmsg(auteur,"Je ne stay pas sur %s."%(message[1]))
-
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
+                        serv.privmsg(auteur, "Je ne stay pas sur %s." % (message[1]))
             else:
-                notunderstood=True
-        elif cmd=="die":
+                notunderstood = True
+        elif cmd == u"die":
             if auteur in self.overops:
-                log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
-                self.die()
+                log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                self.mourir()
+            else:
+                notunderstood = True
+        elif cmd == u"crash":
+            if auteur in self.overops:
+                log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                self.crash("priv", auteur)
+            else:
+                notunderstood = True
+        elif cmd == u"reload":
+            if auteur in self.ops:
+                self.execute_something("reload", {"auteur" : auteur}, place=auteur, auteur=auteur)
             else:
-                notunderstood=True
-        elif cmd=="quiet":
+                notunderstood = True
+        elif cmd == u"reconnect":
             if auteur in self.ops:
-                if len(message)>1:
+                try:
+                    self.nk = self.new_connection_NK(serv, config.note_pseudo,
+                                            config.note_password, "special")[2]
+                except Exception as exc:
+                    self.nk = None
+                    log(self.serveur, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc))
+                if self.nk != None:
+                    serv.privmsg(auteur, "%s: done" % (auteur))
+                    log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                else:
+                    serv.privmsg(auteur, "%s: failed" % (auteur))
+                    log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
+                    for report in self.report_bugs_to:
+                        serv.privmsg(report, "Connection to NK2015 failed, invalid password ? Server dead ?")
+            else:
+                notunderstood = True
+        elif cmd == u"quiet":
+            if auteur in self.ops:
+                if len(message) > 1:
                     if message[1] in self.quiet_channels:
-                        serv.privmsg(auteur,"Je me la ferme déjà sur %s"%(message[1]))
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
+                        serv.privmsg(auteur, "Je me la ferme déjà sur %s" % (message[1]))
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
                     else:
                         self.quiet_channels.append(message[1])
-                        serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
-                        log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
+                        serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
                 else:
-                    serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
+                    serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
             else:
-                notunderstood=True
-        elif cmd=="noquiet":
+                notunderstood = True
+        elif cmd == u"noquiet":
             if auteur in self.ops:
-                if len(message)>1:
+                if len(message) > 1:
                     if message[1] in self.quiet_channels:
                         self.quiet_channels.remove(message[1])
-                        serv.privmsg(auteur,"Quiet channels : "+" ".join(self.quiet_channels))
-                        log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
+                        serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
                     else:
-                        serv.privmsg(auteur,"Je ne me la ferme pas sur %s."%(message[1]))
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
+                        serv.privmsg(auteur, "Je ne me la ferme pas sur %s." % (message[1]))
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
             else:
-                notunderstood=True
-        elif cmd=="say":
-            if auteur in self.overops and len(message)>2:
-                serv.privmsg(message[1]," ".join(message[2:]))
-                log(self.serveur,"priv",auteur," ".join(message))
-            elif len(message)<=2:
-                serv.privmsg(auteur,"Syntaxe : SAY <channel> <message>")
+                notunderstood = True
+        elif cmd == u"say":
+            if auteur in self.overops and len(message) > 2:
+                serv.privmsg(message[1], " ".join(message[2:]))
+                log(self.serveur, "priv", auteur, " ".join(message))
+            elif len(message) <= 2:
+                serv.privmsg(auteur, "Syntaxe : SAY <channel> <message>")
             else:
-                notunderstood=True
-        elif cmd=="do":
-            if auteur in self.overops and len(message)>2:
-                serv.action(message[1]," ".join(message[2:]))
-                log(self.serveur,"priv",auteur," ".join(message))
-            elif len(message)<=2:
-                serv.privmsg(auteur,"Syntaxe : DO <channel> <action>")
+                notunderstood = True
+        elif cmd == u"do":
+            if auteur in self.overops and len(message) > 2:
+                serv.action(message[1], " ".join(message[2:]))
+                log(self.serveur, "priv", auteur, " ".join(message))
+            elif len(message) <= 2:
+                serv.privmsg(auteur, "Syntaxe : DO <channel> <action>")
             else:
-                notunderstood=True
-        elif cmd=="kick":
-            if auteur in self.overops and len(message)>2:
-                serv.kick(message[1],message[2]," ".join(message[3:]))
-                log(self.serveur,"priv",auteur," ".join(message))
-            elif len(message)<=2:
-                serv.privmsg(auteur,"Syntaxe : KICK <channel> <pseudo>")
+                notunderstood = True
+        elif cmd == u"kick":
+            if auteur in self.overops and len(message) > 2:
+                serv.kick(message[1], message[2], " ".join(message[3:]))
+                log(self.serveur, "priv", auteur, " ".join(message))
+            elif len(message) <= 2:
+                serv.privmsg(auteur, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
             else:
-                notunderstood=True
-        elif cmd=="lost":
-            if auteur in self.ops and len(message)>1:
-                serv.privmsg(message[1],"J'ai perdu !")
-                log(self.serveur,"priv",auteur," ".join(message))
-            elif len(message)<=1:
-                serv.privmsg(auteur,"Syntaxe : LOST <channel>")
+                notunderstood = True
+        elif cmd == u"lost":
+            if auteur in self.ops and len(message) > 1:
+                serv.privmsg(message[1], "J'ai perdu !")
+                log(self.serveur, "priv", auteur, " ".join(message))
+            elif len(message) <= 1:
+                serv.privmsg(auteur, "Syntaxe : LOST <channel>")
             else:
-                notunderstood=True
-        elif cmd=="solde":
-            if len(message)==1:
+                notunderstood = True
+        elif cmd == u"solde":
+            if len(message) == 1:
                 if self.identities.has_key(auteur):
-                    try:
-                        self.nk.write('search ["x",["pseudo"],%s]'%(json.dumps(self.identities[auteur])))
-                        ret=json.loads(self.nk.read())
-                        solde=ret["msg"][0]["solde"]
-                        pseudo=ret["msg"][0]["pseudo"]
-                    except Exception as exc:
-                        print exc
-                        serv.privmsg(auteur,"failed")
-                        log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-                        return
-                    serv.privmsg(auteur,"%s (%s)"%(float(solde)/100,pseudo.encode("utf8")))
+                    success, solde, pseudo = nk.get_solde(self.nk, self.identities[auteur]["idbde"], serv, auteur)
+                    if success:
+                        serv.privmsg(auteur, "%s (%s)" % (float(solde)/100, pseudo.encode("utf8")))
+                    log(self.serveur, "priv", auteur, " ".join(message) + ("[successful]" if success else "[failed]"))
                 else:
-                    serv.privmsg(canal,"Je ne connais pas ton pseudo note.")
-            elif auteur in self.ops:
-                try:
-                    self.nk.write('search ["x",["pseudo"],%s]'%(json.dumps(message[1])))
-                    ret=json.loads(self.nk.read())
-                    solde=ret["msg"][0]["solde"]
-                    pseudo=ret["msg"][0]["pseudo"]
-                except Exception as exc:
-                    serv.privmsg(auteur,"failed")
-                    log(self.serveur,"priv",auteur," ".join(message)+"[failed]")
-                    return
-                serv.privmsg(auteur,"%s (%s)"%(float(solde)/100,pseudo.encode("utf8")))
+                    serv.privmsg(canal, "Je ne connais pas ton pseudo note.")
+        elif cmd == u"ops":
+            if auteur in self.overops:
+                serv.privmsg(auteur, " ".join(self.ops))
+            else:
+                notunderstood = True
+        elif cmd == u"overops":
+            if auteur in self.overops:
+                serv.privmsg(auteur, " ".join(self.overops))
+            else:
+                notunderstood = True
         else:
-            notunderstood=True
+            notunderstood = True
         if notunderstood:
-            serv.privmsg(auteur,"Je n'ai pas compris. Essayez HELP…")
+            serv.privmsg(auteur, "Je n'ai pas compris. Essayez HELP…")
     
     def on_pubmsg(self, serv, ev):
+        """À la réception d'un message sur un channel."""
         auteur = irclib.nm_to_n(ev.source())
         canal = ev.target()
         message = ev.arguments()[0]
         try:
-            test=bot_unicode(message)
+            message = bot_unicode(message)
         except UnicodeBotError:
-            if not canal in self.quiet_channels:
-                serv.privmsg(canal,
-                  "%s: Si je n'avais pas été créé avec la plus grande attention, votre encodage m'aurait déjà tué…"%(auteur))
+            if config.utf8_trigger and not canal in self.quiet_channels:
+                serv.privmsg(canal, (u"%s: %s"% ( auteur, random.choice(config.utf8_fail_answers))).encode("utf8"))
             return
-        pour_moi,message=self.pourmoi(serv,message)
+        pour_moi, message = self.pourmoi(serv, message)
         if pour_moi and message.split()!=[]:
-            cmd=message.split()[0].lower()
+            cmd = message.split()[0].lower()
             try:
-                args=" ".join(message.split()[1:])
+                args = " ".join(message.split()[1:])
             except:
-                args=""
-            if cmd in ["meurs","die","crève"]:
+                args = ""
+            if cmd in [u"meurs", u"die", u"crève"]:
                 if auteur in self.overops:
-                    log(self.serveur,canal,auteur,message+"[successful]")
-                    self.die()
+                    log(self.serveur, canal, auteur, message + "[successful]")
+                    self.mourir()
                 else:
-                    serv.privmsg(canal,"%s: mourrez vous-même !"%(auteur))
-                    log(self.serveur,canal,auteur,message+"[failed]")
-    
-            elif cmd in ["part","leave","dégage","va-t-en","tut'tiresailleurs,c'estmesgalets"]:
+                    serv.privmsg(canal,(u"%s: %s"%(auteur, random.choice(config.quit_fail_messages))).encode("utf8"))
+                    log(self.serveur, canal, auteur, message + "[failed]")
+            elif cmd == u"reload":
+                if auteur in self.ops:
+                    self.execute_something("reload", {"auteur" : auteur}, place=canal, auteur=auteur)
+            elif cmd == u"crash":
+                if auteur in self.overops:
+                    self.crash(auteur, message)
+            elif cmd in [u"part", u"leave", u"dégage", u"va-t-en", u"tut'tiresailleurs,c'estmesgalets"]:
                 if auteur in self.ops and (not (canal in self.stay_channels)
                                            or auteur in self.overops):
-                    serv.part(canal,message="Éjecté par %s"%(auteur))
-                    log(self.serveur,canal,auteur,message+"[successful]")
+                    self.quitter(canal)
+                    log(self.serveur, canal, auteur, message + "[successful]")
                     if canal in self.chanlist:
                         self.chanlist.remove(canal)
                 else:
-                    serv.privmsg(canal,"%s: Navré, mais je me vois contraint de refuser, je ne peux pas céder aux exigences du premier venu."%(auteur))
-                    log(self.serveur,canal,auteur,message+"[failed]")
+                    serv.privmsg(canal,(u"%s: %s" % (auteur, random.choice(config.leave_fail_messages))).encode("utf8"))
+                    log(self.serveur, canal, auteur, message + "[failed]")
             
-            elif cmd in ["reconnect"]:
+            elif cmd == u"reconnect":
                 if auteur in self.ops:
                     try:
-                        self.nk=self.new_connection_NK(serv,config_note_pseudo,
-                                              config_note_password,"special")[1]
+                        self.nk = self.new_connection_NK(serv, config.note_pseudo,
+                                              config.note_password, "special")[2]
                     except Exception as exc:
-                        self.nk=None
-                        log(self.serveur,"""Erreur dans on_pubmsg/"cmd in ["reconnect"]\n"""+str(exc))
-                    if self.nk!=None:
-                        serv.privmsg(canal,"%s: done"%(auteur))
-                        log(self.serveur,canal,auteur,message+"[successful]")
+                        self.nk = None
+                        log(self.serveur, 'Erreur dans on_pubmsg/"cmd in ["reconnect"]\n' + str(exc))
+                    if self.nk != None:
+                        serv.privmsg(canal, "%s: done" % (auteur))
+                        log(self.serveur, canal, auteur, message + "[successful]")
                     else:
-                        serv.privmsg(canal,"%s: failed"%(auteur))
-                        log(self.serveur,canal,auteur,message+"[failed]")
+                        serv.privmsg(canal, "%s: failed" % (auteur))
+                        log(self.serveur, canal, auteur, message + "[failed]")
                         for report in self.report_bugs_to:
-                            serv.privmsg(report,"Connection to NK2015 failed, invalid password ?")
+                            serv.privmsg(report, "Connection to NK2015 failed, invalid password ? Server dead ?")
                 else:
-                    serv.privmsg(canal,"%s: crève !"%(auteur))
-                    log(self.serveur,canal,auteur,message+"[failed]")
+                    serv.privmsg(canal, "%s: %s" % (auteur, random.choice(config.pas_programme_pour_tobeir).encode("utf8")))
+                    log(self.serveur, canal, auteur, message + "[failed]")
 
-            elif cmd in ["deviens","pseudo"]:
+            elif cmd in [u"deviens", u"pseudo"]:
                 if auteur in self.ops:
-                    become=args
+                    become = args
                     serv.nick(become)
-                    log(self.serveur,canal,auteur,message+"[successful]")
+                    log(self.serveur, canal, auteur, message + "[successful]")
     
-            if cmd in ["meur", "meurt","meurre","meurres"] and not canal in self.quiet_channels:
-                serv.privmsg(canal,'%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)'%(auteur))
-            elif cmd in ["ping"] and not canal in self.quiet_channels:
-                serv.privmsg(canal,"%s: pong"%(auteur))
+            if cmd in [u"meur", u"meurt", u"meurre", u"meurres"] and not canal in self.quiet_channels:
+                serv.privmsg(canal, '%s: Mourir, impératif, 2ème personne du singulier : "meurs" (de rien)' % (auteur))
+            elif cmd in [u"ping"] and not canal in self.quiet_channels:
+                serv.privmsg(canal, "%s: pong" % (auteur))
 
-            elif cmd in ["solde","!solde"]:
+            elif cmd in [u"solde", u"!solde", u"!coca"]:
                 if self.identities.has_key(auteur):
-                    pseudo=self.identities[auteur]
-                    try:
-                        self.nk.write('search ["x",["pseudo"],%s]'%(json.dumps(pseudo)))
-                        ret=json.loads(self.nk.read())
-                        solde=ret["msg"][0]["solde"]
-                        pseudo=ret["msg"][0]["pseudo"]
-                    except Exception as exc:
-                        serv.privmsg(canal,"%s: failed"%(auteur))
-                        log(self.serveur,canal,auteur,message+"[failed]")
-                    else:
-                        serv.privmsg(canal,"%s: %s (%s)"%(auteur,float(solde)/100,pseudo.encode("utf8")))
-                        log(self.serveur,canal,auteur,message+"[successful]")
+                    idbde = self.identities[auteur]["idbde"]
+                    if cmd in [u"solde", u"!solde"]:
+                        success, solde, pseudo = nk.get_solde(self.nk, self.identities[auteur]["idbde"], serv, canal)
+                        if success:
+                            serv.privmsg(canal, "%s: %s (%s)" % (auteur, float(solde)/100, pseudo.encode("utf8")))
+                    elif cmd in [u"!coca"]:
+                        success = nk.consomme(self.nk, self.identities[auteur]["idbde"], u"Coca", serv, canal)
+                    log(self.serveur, canal, auteur, message + ("[successful]" if success else "[failed]"))
                 else:
-                    serv.privmsg(canal,"%s: Je ne connais pas votre pseudo note."%(auteur))
-                    log(self.serveur,canal,auteur,message+"[unknown]")
-            elif (re.match("!?(pain au chocolat|chocolatine)",message.lower())
+                    serv.privmsg(canal, "%s: Je ne connais pas votre pseudo note." % (auteur))
+                    log(self.serveur, canal, auteur, message + "[unknown]")
+            elif (re.match("!?(pain au chocolat|chocolatine)", message.lower())
                  and not canal in self.quiet_channels):
-                serv.action(canal,"sert un pain au chocolat à %s"%(auteur))
+                serv.action(canal, "sert un pain au chocolat à %s" % (auteur))
             elif re.match("!?manzana",message.lower()) and not canal in self.quiet_channels:
-                if auteur in config_manzana:
-                    serv.action(canal,"sert une bouteille de manzana à %s"%(auteur))
+                if auteur in config.manzana:
+                    serv.action(canal, "sert une bouteille de manzana à %s" % (auteur))
+                elif auteur in config.manzana_bis:
+                    serv.action(canal, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur))
                 else:
-                    serv.action(canal,"sert un verre de manzana à %s"%(auteur))
+                    serv.action(canal, "sert un verre de manzana à %s" % (auteur))
             if is_insult(message) and not canal in self.quiet_channels:
                 if is_not_insult(message):
-                    answer=random.choice(config_compliment_answers)
+                    answer = random.choice(config.compliment_answers)
                     for ligne in answer.split("\n"):
-                        serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
+                        serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
                 else:
-                    answer=random.choice(config_insultes_answers)
+                    answer = random.choice(config.insultes_answers)
                     for ligne in answer.split("\n"):
-                        serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
+                        serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
             elif is_compliment(message) and not canal in self.quiet_channels:
-                answer=random.choice(config_compliment_answers)
+                answer = random.choice(config.compliment_answers)
                 for ligne in answer.split("\n"):
-                    serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
-            gros_match=is_gros(message)
+                    serv.privmsg(canal, "%s: %s" % (auteur,ligne.encode("utf8")))
+            gros_match = is_gros(message)
             if gros_match and not canal in self.quiet_channels:
-                taille=get_filesize()
-                answer=u"Mais non, je ne suis pas %s, %sKo tout au plus…"%(gros_match.groups()[0],taille)
-                serv.privmsg(canal,"%s: %s"%(auteur,answer.encode("utf8")))
+                taille = get_filesize()
+                answer = u"Mais non, je ne suis pas %s, %sKo tout au plus…" % (gros_match.groups()[0], taille)
+                serv.privmsg(canal, "%s: %s"%(auteur, answer.encode("utf8")))
             if is_tesla(message) and not canal in self.quiet_channels:
-                l1,l2=config_tesla_answers,config_tesla_actions
-                n1,n2=len(l1),len(l2)
-                i=random.randrange(n1+n2)
-                if i>=n1:
-                    serv.action(canal,l2[i-n1].encode("utf8"))
+                l1, l2 = config.tesla_answers, config.tesla_actions
+                n1, n2 = len(l1), len(l2)
+                i = random.randrange(n1 + n2)
+                if i >= n1:
+                    serv.action(canal, l2[i - n1].encode("utf8"))
                 else:
-                    serv.privmsg(canal,"%s: %s"%(auteur,l1[i].encode("utf8")))
+                    serv.privmsg(canal, "%s: %s" % (auteur, l1[i].encode("utf8")))
             if is_tamere(message) and not canal in self.quiet_channels:
-                answer=random.choice(config_tamere_answers)
+                answer = random.choice(config.tamere_answers)
                 for ligne in answer.split("\n"):
-                    serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
+                    serv.privmsg(canal, "%s: %s"%(auteur, ligne.encode("utf8")))
             if is_tag(message) and not canal in self.quiet_channels:
                 if auteur in self.ops:
-                    action=random.choice(config_tag_actions)
-                    serv.action(canal,action.encode("utf8"))
+                    action = random.choice(config.tag_actions)
+                    serv.action(canal, action.encode("utf8"))
                     self.quiet_channels.append(canal)
                 else:
-                    answer=random.choice(config_tag_answers)
+                    answer = random.choice(config.tag_answers)
                     for ligne in answer.split("\n"):
-                        serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
+                        serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
             if is_merci(message):
-                answer=random.choice(config_merci_answers)
+                answer = random.choice(config.merci_answers)
                 for ligne in answer.split("\n"):
-                    serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
-            out=re.match(ur"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$",
-                         unicode(message.upper(),"utf8"))
-            if re.match("ma bite dans ton oreille",message) and not canal in self.quiet_channels:
-                serv.privmsg(canal,"%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !"%(auteur))
+                    serv.privmsg(canal, "%s: %s"%(auteur, ligne.encode("utf8")))
+            out = re.match(ur"^([A-Z[]|\\|[0-9]+|(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+)(?:| \?| !)$", message.upper())
+            if re.match("ma bite dans ton oreille", message) and not canal in self.quiet_channels:
+                serv.privmsg(canal, "%s: Seul un olasd peut imiter un olasd dans un de ses grands jours !" % (auteur))
             if out and not canal in self.quiet_channels:
-                out=out.groups()[0]
+                out = out.groups()[0]
                 try:
-                    out=int(out)
-                    serv.privmsg(canal,"%s: %s !"%(auteur,out+1))
-                    if out==2147483647:
-                        serv.privmsg(canal,"%s: Ciel, un maxint ! Heureusement que je suis en python…"%(auteur))
+                    iout = int(out)
+                    serv.privmsg(canal, "%s: %s !" % (auteur, iout + 1))
+                    if iout == 2147483647:
+                        serv.privmsg(canal, "%s: Ciel, un maxint ! Heureusement que je suis en python…" % (auteur))
                         return
-                    if out+1>1000 and random.randrange(4)==0:
-                        serv.privmsg(canal,"%s: Vous savez, moi et les chiffres…"%(auteur))
+                    if iout + 1 > 1000 and random.randrange(4) == 0:
+                        serv.privmsg(canal, "%s: Vous savez, moi et les chiffres…" % (auteur))
                         return
                 except Exception as exc:
                     pass
-                if re.match("[A-Y]",out):
-                    alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                    serv.privmsg(canal,"%s: %s !"%(auteur,alphabet[alphabet.index(out)+1]))
-                elif out=="Z":
-                    serv.privmsg(canal,"%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?"%(auteur))
+                if re.match("[A-Y]", out):
+                    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                    serv.privmsg(canal, "%s: %s !"%(auteur, alphabet[alphabet.index(out) + 1]))
+                elif out == "Z":
+                    serv.privmsg(canal, "%s: Je ne vous remercie pas, j'ai l'air idiot ainsi… [ ?" % (auteur))
                 elif out in "[\\":
-                    serv.privmsg(canal,"%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…"%(auteur))
-                elif re.match(ur"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+",out):
+                    serv.privmsg(canal, "%s: Nous devrions nous en tenir là, ça va finir par poser des problèmes…" % (auteur))
+                elif re.match(ur"(¹|²|³|⁴|⁵|⁶|⁷|⁸|⁹|⁰)+", out):
                     def translate(mess):
                         return "".join([{u"⁰¹²³⁴⁵⁶⁷⁸⁹0123456789"[i]:u"0123456789⁰¹²³⁴⁵⁶⁷⁸⁹"[i]
                                         for i in range(20)}[j]
                                        for j in mess])
-                    out=int(translate(out))
-                    serv.privmsg(canal,"%s: %s !"%(auteur,translate(str(out+1)).encode("utf8")))
+                    out = int(translate(out))
+                    serv.privmsg(canal,"%s: %s !" % (auteur, translate(str(out + 1)).encode("utf8")))
             if is_bonjour(message) and not canal in self.quiet_channels:
                 if is_night():
-                    answer=random.choice(config_night_answers)
+                    answer = random.choice(config.night_answers)
                 elif is_day():
-                    answer=random.choice(config_bonjour_answers)
+                    answer = random.choice(config.bonjour_answers)
                 else:
-                    answer=random.choice(config_bonsoir_answers)
-                serv.privmsg(canal,answer.format(auteur).encode("utf8"))
+                    answer = random.choice(config.bonsoir_answers)
+                serv.privmsg(canal, answer.format(auteur).encode("utf8"))
             if is_bonne_nuit(message) and not canal in self.quiet_channels:
-                answer=random.choice(config_bonne_nuit_answers)
-                serv.privmsg(canal,answer.format(auteur).encode("utf8"))
+                answer = random.choice(config.bonne_nuit_answers)
+                serv.privmsg(canal, answer.format(auteur).encode("utf8"))
             if is_pan(message) and not canal in self.quiet_channels:
-                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))
+                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))
         else:
-            if message in ["!pain au chocolat","!chocolatine"] and not canal in self.quiet_channels:
-                serv.action(canal,"sert un pain au chocolat à %s"%(auteur))
-            if message in ["!manzana"] and not canal in self.quiet_channels:
-                if auteur in config_manzana:
-                    serv.action(canal,"sert une bouteille de manzana à %s"%(auteur))
+            if message in [u"!pain au chocolat", u"!chocolatine"] and not canal in self.quiet_channels:
+                serv.action(canal, "sert un pain au chocolat à %s" % (auteur))
+            if message in [u"!manzana"] and not canal in self.quiet_channels:
+                if auteur in config.manzana:
+                    serv.action(canal, "sert une bouteille de manzana à %s" % (auteur))
+                elif auteur in config.manzana_bis:
+                    serv.action(canal, "sert un grand verre de jus de pomme à %s : tout le monde sait qu'il ne boit pas." % (auteur))
                 else:
-                    serv.action(canal,"sert un verre de manzana à %s"%(auteur))
-            if re.match('^(.|§|:|)(w|b) [0-9]+$',message) and not canal in self.quiet_channels:
-                failanswers=config_buffer_fail_answers
-                answer=random.choice(failanswers)
-                serv.privmsg(canal,("%s: %s"%(auteur,answer)).encode("utf8"))
+                    serv.action(canal, "sert un verre de manzana à %s" % (auteur))
+            if re.match(u'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$', message) and not canal in self.quiet_channels:
+                failanswers = config.buffer_fail_answers
+                answer = random.choice(failanswers)
+                serv.privmsg(canal, ("%s: %s"%(auteur,answer)).encode("utf8"))
             if not canal in self.quiet_channels:
-                mypseudo=self.nick
-                if re.match((u"^("+u"|".join(config_bonjour_triggers)
-                                  +u")( {}| all| tout le monde|(|à) tous)(\.|( |)!|)$"
+                mypseudo = self.nick
+                if re.match((u"^(" + u"|".join(config.bonjour_triggers)
+                                   + ur")( {}| all| tout le monde| (à )?tous)(\.| ?!)?$"
                              ).format(mypseudo).lower(), message.strip().lower()):
-                    answer=random.choice(config_bonjour_answers)
-                    serv.privmsg(canal,answer.format(auteur).encode("utf8"))
+                    answer = random.choice(config.bonjour_answers)
+                    serv.privmsg(canal, answer.format(auteur).encode("utf8"))
         if (is_perdu(message) and not canal in self.quiet_channels):
             # proba de perdre sur trigger :
             #  avant 30min (enfin, config) : 0
             #  ensuite, +25%/30min, linéairement
-            deltat=time.time()-self.last_perdu
-            barre=(deltat-config_time_between_perdu)/(2*3600.0)
-            if random.uniform(0,1)<barre:
-                serv.privmsg(canal,"%s: J'ai perdu !"%(auteur))
-                self.last_perdu=time.time()
+            deltat = time.time() - self.last_perdu
+            barre = (deltat - config.time_between_perdu)/(2*3600.0)
+            if random.uniform(0, 1) < barre:
+                serv.privmsg(canal, "%s: J'ai perdu !" % (auteur))
+                self.last_perdu = time.time()
 
     def on_action(self, serv, ev):
+        """À la réception d'une action."""
         action = ev.arguments()[0]
         auteur = irclib.nm_to_n(ev.source())
         channel = ev.target()
         try:
-            test=bot_unicode(action)
+            action = bot_unicode(action)
         except UnicodeBotError:
-            serv.privmsg(channel, 
-                  "%s: Si je n'avais pas été créé avec la plus grande attention, votre encodage m'aurait déjà tué…"%(auteur))
+            if config.utf8_trigger and not channel in self.quiet_channels:
+                serv.privmsg(channel, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf8"))
             return
-        mypseudo=self.nick
+        mypseudo = self.nick
         
-        if is_bad_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
-            l1,l2=config_bad_action_answers,config_bad_action_actions
-            n1,n2=len(l1),len(l2)
-            i=random.randrange(n1+n2)
-            if i>=n1:
-                serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
+        if is_bad_action_trigger(action, mypseudo) and not channel in self.quiet_channels:
+            l1, l2 = config.bad_action_answers, config.bad_action_actions
+            n1, n2 = len(l1), len(l2)
+            i = random.randrange(n1 + n2)
+            if i >= n1:
+                serv.action(channel, l2[i - n1].format(auteur).encode("utf8"))
             else:
-                serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
-        if is_good_action_trigger(action,mypseudo) and not channel in self.quiet_channels:
-            l1,l2=config_good_action_answers,config_good_action_actions
-            n1,n2=len(l1),len(l2)
-            i=random.randrange(n1+n2)
-            if i>=n1:
-                serv.action(channel,l2[i-n1].format(auteur).format(auteur).encode("utf8"))
+                serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
+        if is_good_action_trigger(action, mypseudo) and not channel in self.quiet_channels:
+            l1, l2 = config.good_action_answers, config.good_action_actions
+            n1, n2 = len(l1), len(l2)
+            i = random.randrange(n1 + n2)
+            if i >= n1:
+                serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
             else:
-                serv.privmsg(channel,l1[i].format(auteur).format(auteur).encode("utf8"))
+                serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
     
-    def on_kick(self,serv,ev):
+    def on_kick(self, serv, ev):
+        """À la réception d'une action."""
         auteur = irclib.nm_to_n(ev.source())
         channel = ev.target()
         victime = ev.arguments()[0]
         raison = ev.arguments()[1]
-        if victime==self.nick:
-            log(self.serveur,"%s kické par %s (raison : %s)" %(victime,auteur,raison))
+        if victime == self.nick:
+            log(self.serveur, "%s kické de %s par %s (raison : %s)" % (victime, channel, auteur, raison))
             time.sleep(2)
             serv.join(channel)
-            l1,l2=config_kick_answers,config_kick_actions
-            n1,n2=len(l1),len(l2)
-            i=random.randrange(n1+n2)
-            if i>=n1:
-                serv.action(channel,l2[i-n1].format(auteur).encode("utf8"))
+            l1, l2 = config.kick_answers, config.kick_actions
+            n1, n2 = len(l1), len(l2)
+            i = random.randrange(n1 + n2)
+            if i >= n1:
+                serv.action(channel, l2[i-n1].format(auteur).encode("utf8"))
             else:
-                serv.privmsg(channel,l1[i].format(auteur).encode("utf8"))
-
-    def _getnick(self):
-        return self.serv.get_nickname()
-    nick=property(_getnick)
-
+                serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
+    
+    ### .fork trick
+    def start_as_daemon(self, outfile):
+        sys.stderr = Logger(outfile)
+        self.start()
+    
 
-if __name__=="__main__":
-    import sys
-    if len(sys.argv)==1:
-        print "Usage : basile.py <serveur> [--debug]"
+class Logger(object):
+    """Pour écrire ailleurs que sur stdout"""
+    def __init__(self, filename="basile.full.log"):
+        self.filename = filename
+
+    def write(self, message):
+        f = open(self.filename, "a")
+        f.write(message)
+        f.close()
+
+def main():
+    """Exécution principal : lecture des paramètres et lancement du bot."""
+    if len(sys.argv) == 1:
+        print "Usage : basile.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
+        print "        --outfile sans --no-output ni --daemon n'a aucun effet"
         exit(1)
-    serveur=sys.argv[1]
+    serveur = sys.argv[1]
+    if "--daemon" in sys.argv:
+        thisfile = os.path.realpath(__file__)
+        thisdirectory = thisfile.rsplit("/", 1)[0]
+        os.chdir(thisdirectory)
+        daemon = True
+    else:
+        daemon = False
     if "debug" in sys.argv or "--debug" in sys.argv:
-        debug=True
+        debug = True
     else:
-        debug=False
-    serveurs={"a♡":"acoeur.crans.org","acoeur":"acoeur.crans.org","acoeur.crans.org":"acoeur.crans.org",
-              "irc":"irc.crans.org","crans":"irc.crans.org","irc.crans.org":"irc.crans.org"}
+        debug = False
+    if "--quiet" in sys.argv:
+        config.debug_stdout = False
+    serveurs = {"a♡" : "acoeur.crans.org",
+              "acoeur" : "acoeur.crans.org",
+              "acoeur.crans.org" : "acoeur.crans.org",
+              "irc" : "irc.crans.org",
+              "crans" : "irc.crans.org",
+              "irc.crans.org" : "irc.crans.org"}
+    if "--no-output" in sys.argv or "--daemon" in sys.argv:
+        outfile = "/var/log/bots/basile.full.log"
+        for arg in sys.argv:
+            arg = arg.split("=")
+            if arg[0].strip('-') in ["out", "outfile", "logfile"]:
+                outfile = arg[1]
+        sys.stdout = Logger(outfile)
     try:
-        serveur=serveurs[serveur]
+        serveur = serveurs[serveur]
     except KeyError:
-        print "Server Unknown : %s"%(serveur)
+        print "Server Unknown : %s" % (serveur)
         exit(404)
-    basile=Basile(serveur,debug)
-    basile.start()
+    basile = Basile(serveur,debug)
+    # Si on reçoit un SIGHUP, on reload la config
+    def sighup_handler(signum, frame):
+        basile.execute_reload(auteur="SIGHUP")
+    signal.signal(signal.SIGHUP, sighup_handler)
+    # Daemonization
+    if daemon:
+        child_pid = os.fork()
+        if child_pid == 0:
+            os.setsid()
+            basile.start_as_daemon(outfile)
+        else:
+            # on enregistre le pid de basile
+            pidfile = "/var/run/bots/basile.pid"
+            for arg in sys.argv:
+                arg = arg.split("=")
+                if arg[0].strip('-') in ["pidfile"]:
+                    pidfile = arg[1]
+            f = open(pidfile, "w")
+            f.write("%s\n" % child_pid)
+            f.close()
+    else:
+        basile.start()
+
+if __name__ == "__main__":
+    main()