]> gitweb.pimeys.fr Git - bots/basile.git/blobdiff - basile.py
Meteo : bot mort et enterré depuis longtemps
[bots/basile.git] / basile.py
index 2bc9533808fd0e10a78087bd0ef725b6d8c14b51..ad22ab649f74ded43ca500733377546a6cc20634 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 "mais PEB aussi est passé par là"
-config_manzana_bis = ["PEB"]
-
-# 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"Mais je ne vous permets pas ! Enfin, pas comme ça…"]
-
-# config "à peine quelques kilos octets"
-config_gros=[u"gros",u"énorme",u"lourd"]
-config_thisfile= os.path.realpath( __file__ )
-def get_filesize():
-    return ex("ls -s %s"%(config_thisfile))[1].split()[0]
-
-# 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à ?"]
-
-# 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"]
-
-# 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 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 !"]
-
-# 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."]
-
-# config on m'a demandé de mourir/partir
-config_quit_messages=[u"Bien que cela me désole, je me vois dans l'obligation de vous abandonner."]
-config_leave_messages=config_quit_messages
+# Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
+sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
+import irclib
+# On veut réagir sur la partie du whois qui dit qu'un nick est registered
+irclib.numeric_events['307'] = "whoisregnick"
+import ircbot
 
-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)
+from commands import getstatusoutput as ex
 
-class NKRefused(NKError):
-    pass
+#: Config de basile
+import config
+#: Module responsable du dialogue avec la NoteKfet2015
+import nk
+#: Module de réponse aux questions de base
+import isit
+#: Module définissant les erreurs
+import errors
+#: Module de gestion des utilisateurs
+import users
+
+# 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
+config.thisfile = os.path.realpath(__file__)
 
-class NKHelloFailed(NKError):
-    pass
+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],)
 
-class NKUnknownError(NKError):
-    pass
+def get_filesize():
+    """Récupère la taille de ce fichier."""
+    return ex("ls -s %s" % (config.thisfile))[1].split()[0]
 
-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.encode("utf-8")
+
+def ignore_event(serv, ev):
+    """Retourne ``True`` si il faut ignorer cet évènement."""
+    for (blackmask, exceptlist) in config.blacklisted_masks:
+        usermask = ev.source()
+        blackit = bool(irclib.mask_matches(usermask, blackmask))
+        exceptit = any([bool(irclib.mask_matches(usermask, exceptmask)) for exceptmask in exceptlist])
+        if exceptit: # Il est exempté
+            return False
+        else:
+            if blackit: # Il n'est pas exempté et matche la blacklist
+                return True
 
-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 is_something(chain,matches,avant=u".*(?:^| )",apres=u"(?:$|\.| |,|;).*",case_sensitive=False,debug=False):
-    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)
-    return o
-
-def is_insult(chain,debug=True):
-    return is_something(chain,config_insultes,avant=".*(?:^| |')")
-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=".*(?:^| |')")
-def is_perdu(chain):
-    return is_something(chain,config_perdu)
-def is_tag(chain):
-    return is_something(chain,config_tag_triggers)
-def is_gros(chain):
-    return is_something(chain,config_gros)
-def is_tesla(chain):
-    return is_something(chain,config_tesla_triggers,avant=u"^",apres=u"$",debug=True)
-def is_merci(chain):
-    return is_something(chain,config_merci_triggers)
-def is_tamere(chain):
-    return is_something(chain,config_tamere_triggers)
-def is_bad_action_trigger(chain,pseudo):
-    return is_something(chain,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))
-def is_bonjour(chain):
-    return is_something(chain,config_bonjour_triggers,avant=u"^")
-def is_bonne_nuit(chain):
-    return is_something(chain,config_bonne_nuit_triggers,avant=u"^")
-def is_pan(chain):
-    return re.match(u"^(pan|bim|bang)( .*)?$",unicode(chain,"utf8").lower().strip())
-
-def is_time(conf):
-    _,_,_,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)
-def is_night():
-    return is_time(config_nighttime)
 
-      
-class UnicodeBotError(Exception):
-    pass
 def bot_unicode(chain):
+    """Essaye de décoder ``chain`` en UTF-8.
+       Lève une py:class:`errors.UnicodeBotError` en cas d'échec."""
     try:
-        unicode(chain,"utf8")
+        return chain.decode("utf8")
     except UnicodeDecodeError as exc:
-        raise UnicodeBotError
+        raise errors.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.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.stay_channels = config.stay_channels
+        self.quiet_channels = config.quiet_channels
+        self.last_perdu = 0
+        # On charge la base de données d'utilisateurs
+        self.users = users.UserDB()
+        self.users.load()
+    
+    ### 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)
+        isit.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, who="nobody", chan="nowhere"):
+        """Fait crasher le bot."""
+        where = "en privé" if chan == "priv" else "sur le chan %s" % chan
+        raise errors.CrashError((u"Crash demandé par %s %s" % (who, where)).encode("utf-8"))
+    
+    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]"))
+    
+    def whois(self, pseudo, askedwhy, askedby):
+        """Demande un whois sur ``pseudo``. La réponse sera handled par une autre fonction."""
+        self.users.pending_whois[pseudo] = ["pending", askedwhy, askedby, None]
+        self.serv.whois([pseudo])
+    
+    ### 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))
+                serv.privmsg(report, "Connection to NK2015 failed, invalid password ?")
+
+    def on_whoisregnick(self, serv, ev):
+        """Appelée sur une réponse à un whois au moment où ça dit "is a registered nick".
+           J'ai vérifié, "is a registered nick" ça inclu le fiat qu'il est identified correctement.
+           
+           On stocke l'information comme quoi cette personne est registered, et quand c'était."""
+        pseudo, phrase = ev.arguments()
+        if phrase == 'is a registered nick':
+            # Le whois n'est plus "pending"
+            if self.users.pending_whois.has_key(pseudo):
+                self.users.pending_whois[pseudo][0] = "registered"
+                self.users.pending_whois[pseudo][3] = time.time()
+                _, askedwhy, askedby, _ = self.users.pending_whois[pseudo]
+                if askedwhy == "cmd WHOIS":
+                    # Ce whois a été demandé par quelqu'un, on lui répond
+                    self.serv.privmsg(askedby, "%s is a registered nick" % (pseudo,))
+    
+    def on_endofwhois(self, serv, ev):
+        """Si on arrive à la fin du whois, on va voir si on n'a pas reçu "is a registered nick"
+           c'est que le pseudo n'est pas identifié. """
+        pseudo = ev.arguments()[0]
+        # On laisse le temps au bot de souffler un coup
+        serv.execute_delayed(config.whois_timeout, self.fail_whoisregnick, (pseudo,))
+
+    def fail_whoisregnick(self, pseudo):
+        """Maintenant qu'on a laissé quelques secondes au bot pour gérer les affaires courantes,
+           on considère que le pseudo n'est pas registered. """
+        # Attention, parce qu'il se pourrait qu'on n'ait pas sollicité ce whois
+        # et que donc pending_whois n'ai pas été peuplé en conséquence
+        if self.users.pending_whois.has_key(pseudo):
+            status, askedwhy, askedby, _ = self.users.pending_whois[pseudo]
+            if status == "pending":
+                # Si le status est encore pending, on n'a pas eu de réponse positive, donc elle est négative
+                self.users.pending_whois[pseudo] = "notregistered"
+                if askedwhy == "cmd WHOIS":
+                    self.serv.privmsg(askedby, "%s is NOT a registered nick" % (pseudo,))
     
-    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)
-
     def on_privmsg(self, serv, ev):
-        message=ev.arguments()[0]
+        """À la réception d'un message en privé."""
+        if ignore_event(serv, ev):
+            return
+        message = ev.arguments()[0]
         auteur = irclib.nm_to_n(ev.source())
         try:
-            test=bot_unicode(message)
-        except UnicodeBotError:
-            serv.privmsg(auteur,
-              "Si je n'avais pas été créé avec la plus grande attention, votre encodage aurait eu raison de moi…")
+            message = bot_unicode(message)
+        except errors.UnicodeBotError:
+            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=="help":
-            helpdico={"help":["""HELP <commande>
- Affiche de l'aide sur la commande""",None,None],
-"identify": ["""IDENTIFY <username> <password>
- Vérifie le mot de passe et me permet de savoir à l'avenir quel est votre pseudo note kfet.
- Sans paramètre, je vous précise sous quel pseudo je vous connais.""",None,None],
-"drop":["""DROP <password>
- Vérifie le mot de passe et me fait d'oublier votre pseudo note kfet.""",None,None],
-"solde": ["""SOLDE
- Affiche votre solde, si je connais votre pseudo note kfet.""",
- """SOLDE <pseudo>
- Affiche le solde de la personne désignée (par son pseudo note).""",None],
- "join": [None, """JOIN <channel>
- Me fait rejoindre le channel""",None],
- "leave": [None,"""LEAVE <channel>
- Me fait quitter le channel (sauf s'il est dans ma stay_list).""",None],
- "quiet": [None,"""QUIET <channel>
- Me rend silencieux sur le channel.""",None],
- "noquiet": [None,"""NOQUIET <channel>
- Me rend la parole sur le channel.""",None],
- "lost": [None,"""LOST <channel>
- Me fait perdre sur le channel.""",None],
- "say": [None,None,"""SAY <channel> <message>
- Me fait parler sur le channel."""],
- "do": [None,None,"""DO <channel> <action>
- Me fait faitre une action (/me) sur le channel."""],
- "stay": [None,None,"""STAY <channel>
- Ajoute le channel à ma stay_list."""],
- "nostay": [None,None,"""NOSTAY <channel>
- Retire le channel de ma stay_list."""],
- "ops": [None,None,"""OPS
- Affiche la liste des ops."""],
- "overops": [None,None,"""OVEROPS
- Affiche la liste des overops."""],
- "kick": [None,None,"""KICK <channel> <pseudo> [<raison>]
- Kicke <pseudo> du channel (Il faut bien entendu que j'y sois op)."""],
- "die": [None,None,"""DIE
- Me déconnecte du serveur IRC."""]
- }
-            helpmsg_default="Liste des commandes disponibles :\nHELP IDENTIFY DROP SOLDE"
-            helpmsg_ops=" JOIN LEAVE QUIET NOQUIET LOST"
-            helpmsg_overops=" SAY DO STAY NOSTAY OPS OVEROPS KICK DIE"
+        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
+                helpmsg = config.helpmsg_default
                 if op:
-                    helpmsg+=helpmsg_ops
+                    helpmsg += config.helpmsg_ops
                 if overop:
-                    helpmsg+=helpmsg_overops
+                    helpmsg += config.helpmsg_overops
             else:
-                helpmsgs=helpdico.get(message[1].lower(),["Commande inconnue.",None,None])
-                helpmsg=helpmsgs[0]
+                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]
+                        helpmsg += "\n" + helpmsgs[1]
                     else:
-                        helpmsg=helpmsgs[1]
+                        helpmsg = helpmsgs[1]
                 if overop and helpmsgs[2]:
                     if helpmsg:
-                        helpmsg+="\n"+helpmsgs[2]
+                        helpmsg += "\n" + helpmsgs[2]
                     else:
-                        helpmsg=helpmsgs[2]
+                        helpmsg = helpmsgs[2]
+                if not helpmsg: # Un non-op a demandé de l'aide sur une commande dont il n'est pas censé connaître l'existence
+                    helpmsg = "Commande inacessible."
             for ligne in helpmsg.split("\n"):
-                serv.privmsg(auteur,ligne)
-        elif cmd=="identify":
-            if len(message)==1:
-                if self.identities.has_key(auteur):
-                    serv.privmsg(auteur,"Je vous connais sous le pseudo note %s."%(
-                                     self.identities[auteur].encode("utf8")))
+                serv.privmsg(auteur, ligne.encode("utf-8"))
+        elif cmd == u"identify":
+            if len(message) == 1:
+                if self.users.has(auteur):
+                    infos = self.users[auteur].get_infos(self.nk, serv, auteur)
+                    serv.privmsg(auteur, (u"Vous avez le compte note n°%(idbde)s, pseudo : %(pseudo)s." % infos
+                                     ).encode("utf8"))
                 else:
-                    serv.privmsg(auteur,"Je ne connais pas votre 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 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]")
+                    self.users.add(auteur, info["idbde"])
+                    serv.privmsg(auteur, "Pseudo enregistré.")
                 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:
-                if self.identities.has_key(auteur):
-                    password=" ".join(message[1:])
-                    success,_=self.new_connection_NK(serv,self.identities[auteur],password)
+                serv.privmsg(auteur, "Syntaxe : IDENTIFY [<username> <password>]")
+        elif cmd == u"drop":
+            if len(message) > 1:
+                if self.users.has(auteur):
+                    idbde = self.users[auteur].idbde
+                    password = " ".join(message[1:])
+                    success, _, _ = self.new_connection_NK(serv, "#%s" % idbde, 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.")
+                        self.users.drop(idbde)
+                        log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                        serv.privmsg(auteur, "Pseudo oublié.")
                     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 votre 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, (u"Je suis déjà sur %s" % (message[1])).encode("utf-8"))
                     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:
-                        self.quitter(message[1]," ".join(message[2:]))
+                        self.quitter(message[1].encode("utf-8"), " ".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]")
+                log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
                 self.mourir()
             else:
-                notunderstood=True
-        elif cmd=="quiet":
+                notunderstood = True
+        elif cmd == u"crash":
+            if auteur in self.overops:
+                log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
+                self.crash(auteur, "priv")
+            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 == u"reconnect":
+            if auteur in self.ops:
+                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 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].encode("utf-8"), (u" ".join(message[2:])).encode("utf-8"))
+                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> [<raison>]")
+                notunderstood = True
+        elif cmd == u"kick":
+            if auteur in self.overops and len(message) > 2:
+                serv.kick(message[1].encode("utf-8"), message[2].encode("utf-8"), " ".join(message[3:]).encode("utf-8"))
+                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:
-                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")))
-                    log(self.serveur,"priv",auteur," ".join(message)+"[successful]")
+                notunderstood = True
+        elif cmd == u"solde":
+            if len(message) == 1:
+                if self.users.has(auteur):
+                    success, solde, pseudo = nk.get_solde(self.nk, self.users[auteur].idbde, serv, auteur)
+                    if success:
+                        serv.privmsg(auteur, "%.2f (%s)" % (solde/100.0, 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")))
-        elif cmd=="ops":
+                    serv.privmsg(auteur, "Je ne connais pas votre note.")
+        elif cmd == u"ops":
             if auteur in self.overops:
-                serv.privmsg(auteur," ".join(self.ops))
+                serv.privmsg(auteur, " ".join(self.ops))
             else:
-                notunderstood=True
-        elif cmd=="overops":
+                notunderstood = True
+        elif cmd == u"overops":
             if auteur in self.overops:
-                serv.privmsg(auteur," ".join(self.overops))
+                serv.privmsg(auteur, " ".join(self.overops))
             else:
-                notunderstood=True
+                notunderstood = True
+        elif cmd == u"whois":
+            if auteur in self.ops and len(message) > 1:
+                self.whois(message[1], askedwhy="cmd WHOIS", askedby=auteur)
+            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."""
+        if ignore_event(serv, ev):
+            return
         auteur = irclib.nm_to_n(ev.source())
         canal = ev.target()
         message = ev.arguments()[0]
         try:
-            test=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 aurait eu raison de moi…"%(auteur))
+            message = bot_unicode(message)
+        except errors.UnicodeBotError:
+            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]")
+                    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, canal)
+            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):
                     self.quitter(canal)
-                    log(self.serveur,canal,auteur,message+"[successful]")
+                    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))
-
-            elif cmd in ["solde","!solde"]:
-                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]")
+            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 [u"solde", u"!solde", u"!coca"] or cmd.startswith("!"):
+                if self.users.has(auteur):
+                    idbde = self.users[auteur].idbde
+                    if cmd in [u"solde", u"!solde"]:
+                        success, solde, pseudo = nk.get_solde(self.nk, idbde, serv, canal)
+                        if success:
+                            serv.privmsg(canal, "%s: %s (%s)" % (auteur, float(solde)/100, pseudo.encode("utf8")))
+                    elif cmd in [u"!coca"] or cmd.startswith("!"):
+                        success = nk.consomme(self.nk, idbde, message[1:], 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))
-            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))
-                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))
+                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))
+                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 is_insult(message) and not canal in self.quiet_channels:
-                if is_not_insult(message):
-                    answer=random.choice(config_compliment_answers)
+                    serv.action(canal, "sert un verre de manzana à %s" % (auteur))
+            if isit.is_insult(message) and not canal in self.quiet_channels:
+                if isit.is_not_insult(message):
+                    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")))
-            elif is_compliment(message) and not canal in self.quiet_channels:
-                answer=random.choice(config_compliment_answers)
+                        serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
+            elif isit.is_compliment(message) and not canal in self.quiet_channels:
+                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 = isit.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")))
-            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"))
+                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 isit.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"))
                 else:
-                    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)
+                    serv.privmsg(canal, "%s: %s" % (auteur, l1[i].encode("utf8")))
+            if isit.is_tamere(message) and not canal in self.quiet_channels:
+                answer = random.choice(config.tamere_answers)
                 for ligne in answer.split("\n"):
-                    serv.privmsg(canal,"%s: %s"%(auteur,ligne.encode("utf8")))
-            if is_tag(message) and not canal in self.quiet_channels:
+                    serv.privmsg(canal, "%s: %s"%(auteur, ligne.encode("utf8")))
+            if isit.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")))
-            if is_merci(message):
-                answer=random.choice(config_merci_answers)
+                        serv.privmsg(canal, "%s: %s" % (auteur, ligne.encode("utf8")))
+            if isit.is_merci(message):
+                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")))
-            if is_bonjour(message) and not canal in self.quiet_channels:
-                if is_night():
-                    answer=random.choice(config_night_answers)
-                elif is_day():
-                    answer=random.choice(config_bonjour_answers)
+                    out = int(translate(out))
+                    serv.privmsg(canal,"%s: %s !" % (auteur, translate(str(out + 1)).encode("utf8")))
+            if isit.is_bonjour(message) and not canal in self.quiet_channels:
+                if isit.is_night():
+                    answer = random.choice(config.night_answers)
+                elif isit.is_day():
+                    answer = random.choice(config.bonjour_answers)
                 else:
-                    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"))
-            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))
+                    answer = random.choice(config.bonsoir_answers)
+                serv.privmsg(canal, answer.format(auteur).encode("utf8"))
+            if isit.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"))
+            if isit.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))
         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))
-                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))
+            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(u'^ *(.|§|!|/|/|:|)(w|b) [0-9]+$',message.decode("utf8")) 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(config.buffer_fail_regexp, message, flags=re.UNICODE) 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"))
-        if (is_perdu(message) and not canal in self.quiet_channels):
+                    answer = random.choice(config.bonjour_answers)
+                    serv.privmsg(canal, answer.format(auteur).encode("utf8"))
+        if (isit.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."""
+        if ignore_event(serv, ev):
+            return
         action = ev.arguments()[0]
         auteur = irclib.nm_to_n(ev.source())
         channel = ev.target()
         try:
-            test=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))
+            action = bot_unicode(action)
+        except errors.UnicodeBotError:
+            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 isit.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 isit.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)).decode("utf-8"))
             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"))
+                serv.privmsg(channel, l1[i].format(auteur).encode("utf8"))
     
-    def quitter(self,chan,leave_message=None):
-        if leave_message==None:
-            leave_message=random.choice(config_leave_messages)
-        self.serv.part(chan,message=leave_message.encode("utf8"))
-    
-    def mourir(self):
-        quit_message=random.choice(config_quit_messages)
-        self.die(msg=quit_message.encode("utf8"))
+    ### .fork trick
+    def start_as_daemon(self, outfile):
+        sys.stderr = Logger(outfile)
+        self.start()
     
-    def _getnick(self):
-        return self.serv.get_nickname()
-    nick=property(_getnick)
 
-
-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 principale : 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()