# Chatbot Discord (Claude) Un bot Discord qui se comporte comme un membre du serveur : il discute dans un salon qui lui est réservé, répond quand on le mentionne ailleurs, et a une vraie présence sur le serveur (il regarde des animes, écoute de la musique, s'absente…). Il est propulsé par Claude, le modèle d'IA d'Anthropic, et sa personnalité se règle dans un simple fichier texte. Le bot parle français. Sa façon de parler, son caractère et son nom sont ceux que tu lui donnes dans sa personnalité. ## Fonctionnalités ### Conversation - **Salon dédié** : le bot répond à tous les messages du salon choisi (`CHANNEL_ID`). Il se souvient des derniers échanges, même après un redémarrage. Les messages envoyés pendant qu'il écrit reçoivent une seule réponse groupée, comme le ferait quelqu'un qui lit plusieurs messages d'un coup. - **Mentions ailleurs** : dans les autres salons, il répond quand on le mentionne, en lisant les derniers messages pour comprendre la discussion (sans mémoire d'un appel à l'autre). - **Messages cités et transférés** : avec « Répondre », il lit le message cité, même ancien, et ses images. Il lit aussi les messages transférés dans son salon. - **Messages modifiés ou supprimés** : sa mémoire suit. Un message supprimé est oublié, un message modifié prend son nouveau texte. - **Recherche web** (en option, `WEB_SEARCH=on`) : il peut chercher sur Internet et lire un lien partagé quand la question porte sur l'actualité ou des informations précises. - **Autres bots** : il les ignore, sauf quand ils le mentionnent ou répondent à l'un de ses messages : deux bots peuvent ainsi discuter ensemble. ### Pièces jointes Dans son salon comme dans les mentions, le bot lit jusqu'à 4 images ou un PDF, et un fichier texte, par message : - **Images** (JPEG, PNG, GIF, WebP, BMP, TIFF) : réduites et converties automatiquement si besoin. Avec plusieurs images, il peut les comparer ou parler de « la deuxième ». - **PDF** : texte, tableaux, schémas et pages scannées, 20 pages (`PDF_MAX_PAGES`) et 20 Mo au plus. Les PDF protégés par un mot de passe sont refusés. - **Fichiers texte** (`.txt`, `.md`, `.csv`, `.json`, `.log`, sous-titres, configuration, HTML, code…), 100 Ko au plus. Seul le nom du fichier reste ensuite en mémoire : le bot se souvient qu'il l'a lu, sans en repayer le contenu à chaque message. ### Vie sur le serveur - **Statut Discord** (`PRESENCE=on`) : sous son pseudo, le bot regarde quelques épisodes d'un anime (top 500 d'AniList), écoute quelques morceaux (tops Deezer), joue à un jeu de la boutique Steam, affiche une pensée du moment, ou passe absent ou en « Ne pas déranger » avec une raison qu'il écrit lui-même. Pour varier, un type de statut ne revient pas avant au moins deux autres. Il dort la nuit (`PRESENCE_NIGHT`) et sait ce qu'il est en train de faire quand on lui en parle. Après un redémarrage, il reprend son statut là où il en serait s'il ne s'était pas arrêté. - **Absences** : absent, il ne répond qu'à son retour ; en « Ne pas déranger », au bout de 5 à 15 minutes. S'il s'en va en pleine conversation, il prévient d'abord son salon. Les messages en attente sont gardés même après un redémarrage. - **Emojis du serveur** : le bot connaît automatiquement tous les emojis du serveur, animés compris, et peut les utiliser dans ses réponses. Un emoji ajouté ou retiré est pris en compte tout de suite. - **Stickers et emojis seuls** : à un sticker seul, il répond par un sticker du serveur ; à un emoji seul, par un emoji du serveur. ### Mémoire des membres Avec `MEMORY=on` : - **Ce qui est retenu** : goûts, projets, habitudes, y compris ce que les autres membres racontent, avec leur nom comme source (« d'après Toto ») : la personne concernée le voit dans sa fiche, même si Toto n'a pas activé la mémoire ou en a parlé dans un salon qu'elle ne voit pas. Jamais de confidences ni de jugements ; une information sensible (santé, religion, opinions, adresse, argent…) seulement si le membre la donne lui-même, et le bot ne l'évoque ensuite qu'avec lui. Il se sert de ce qu'il sait dans la conversation, mais ne récite pas la fiche d'un membre aux autres. - **Activation** : la mémoire est désactivée pour chacun par défaut. Un membre qui le souhaite l'active lui-même avec `/memoire`, après avoir lu ce qu'elle retient et qui peut voir sa fiche. Rien n'est noté sur un membre qui ne l'a pas activée, même quand les autres parlent de lui. Le bot n'envoie aucun message pour la présenter : c'est à vous d'en parler sur le serveur. - **`/memoire`** : chacun voit la fiche que le bot tient sur lui, dans un message visible par lui seul. Il peut la modifier dans une fenêtre déjà remplie, l'effacer (après confirmation) ou désactiver la mémoire : sa fiche et les lignes du fichier général qui citent un de ses noms Discord sont alors effacées (au-delà de 3 lignes, aucune n'est retirée et le log le signale à l'administrateur), son nom est remplacé par « un membre » là où il est cité comme source dans les autres fiches (« d'après … », « selon … »), et plus rien n'est noté sur lui, jusqu'à ce qu'il la réactive. Dans les deux cas, ses derniers messages restent dans l'historique de la conversation. Les administrateurs voient, modifient et effacent la fiche de n'importe quel membre avec `/memoire membre:@…` (la modifier seulement si le membre a activé la mémoire) ; seul le membre lui-même peut l'activer ou la désactiver. Exception : pour un membre parti du serveur (désigné par son identifiant), qui ne peut plus lancer `/memoire`, un administrateur peut désactiver la mémoire à sa place, ce qui efface tout comme s'il l'avait fait lui-même. Les membres en sont prévenus (avant d'activer la mémoire, et sous leur fiche dans `/memoire`), et chaque ouverture de la fiche d'un autre est notée dans le log (qui, quelle fiche, sans le contenu). - **`/memoire-serveur`** : le fichier général du serveur, à voir, modifier ou effacer. Réservé aux administrateurs. ### Modération - **`/reset`** : efface la mémoire du salon. Réservé aux membres qui ont la permission Administrateur. - **`/skip`** (avec `PRESENCE=on`) : abandonne le statut Discord en cours et en tire un nouveau tout de suite ; seul celui qui lance la commande voit le nouveau statut. Absent, le bot répond alors aux messages en attente s'il passe en ligne. Sans effet la nuit. Réservé aux mêmes personnes que `/reset`. - Le bot ne peut jamais pinger `@everyone`, `@here`, un rôle ou un membre. ## Avant de commencer Il te faut : - **Python 3.10 ou plus récent** (ou Docker, voir plus bas) ; - **une application Discord** avec un bot (voir l'étape suivante) ; - **une clé API Anthropic** : elle se crée sur https://platform.claude.com, rubrique API Keys. L'API est payante à l'usage, séparément de l'abonnement Claude Pro (voir [Coûts](#coûts-et-logs)) ; - en option, **une clé API Steam** (gratuite, https://steamcommunity.com/dev/apikey) pour le statut « joue à ». Sans elle, le bot ne joue simplement à rien. ## Créer le bot sur Discord 1. Sur https://discord.com/developers/applications, crée une application (**New Application**) et donne-lui le nom du bot. 2. Onglet **Installation** : dans **Installation Contexts**, décoche **User Install** (garde **Guild Install**), et choisis **None** comme **Install Link**. 3. Onglet **Bot** : - décoche **Public Bot** : sinon n'importe qui peut inviter le bot sur son serveur (Discord le refuse tant que l'Install Link de l'étape 2 n'est pas sur **None**) ; - clique sur **Reset Token** et copie le token : c'est le `DISCORD_TOKEN` (à garder secret) ; - dans **Privileged Gateway Intents**, active **Message Content Intent**. Les intents « Presence » et « Server Members » ne servent pas. 4. Onglet **OAuth2 > URL Generator** : - coche les scopes **bot** et **applications.commands** (ce dernier pour les commandes comme `/reset` et `/memoire`) ; - dans les permissions, coche **View Channels**, **Send Messages** et **Read Message History** ; - ouvre l'adresse générée et invite le bot sur ton serveur. 5. Dans Discord, active le mode développeur (**Paramètres utilisateur > Avancés > Mode développeur**), puis fais un clic droit sur le salon du bot > **Copier l'identifiant du salon** : c'est le `CHANNEL_ID`. Le bot n'a besoin d'aucune autre permission : il n'utilise que les emojis et stickers du serveur. Le bot ne sert qu'un serveur, celui du salon `CHANNEL_ID` : ailleurs (autre serveur, messages privés), il ignore les messages et refuse les commandes, puisque sa mémoire est commune. S'il a été ajouté à un autre serveur, le log le signale au démarrage. ## Installation 1. Ouvre un terminal dans le dossier du bot et crée un environnement virtuel : ``` python -m venv .venv ``` Puis active-le : `.venv\Scripts\activate` sous Windows, `source .venv/bin/activate` sous macOS et Linux. 2. Installe les dépendances : ``` pip install -r requirements.txt ``` 3. Copie `.env.exemple` en `.env`, puis remplis au minimum `DISCORD_TOKEN`, `ANTHROPIC_API_KEY` et `CHANNEL_ID`. Ne partage jamais ton `.env` : il contient tes clés. 4. Lance le bot : ``` python main.py ``` Il annonce son arrivée dans son salon avec sa version. En cas d'erreur de configuration (variable manquante, token invalide, clé Anthropic invalide, modèle inexistant, intent non activé, `CHANNEL_ID` qui ne désigne pas un salon textuel où le bot peut écrire, permission manquante), il s'arrête en expliquant quoi corriger. Seule exception : si l'un de ses serveurs est indisponible chez Discord au démarrage (panne), il patiente et s'annonce dès le retour du serveur. ## Configuration Tous les réglages se trouvent dans le `.env`, et chacun est expliqué dans `.env.exemple`. Les principaux, avec la valeur proposée dans `.env.exemple` : | Réglage | Rôle | Valeur proposée | | --- | --- | --- | | `CLAUDE_MODEL` | Modèle utilisé : `claude-sonnet-5-5` (recommandé), `claude-haiku-5-5` (bien moins cher) ou `claude-opus-5-5` (plus capable, plus cher) | `claude-sonnet-5-5` | | `THINKING` | Réflexion avant de répondre : `on` (plus réfléchi, plus lent et plus cher) ou `off` (vide = `off`) | `on` | | `EFFORT` | Niveau d'effort : `low`, `medium`, `high`, `xhigh` ou `max` (`xhigh` et `max` nécessitent `THINKING=on`, sauf sur `claude-opus-5-5`) ; vide = valeur du modèle | `medium` | | `MAX_TOKENS` | Longueur maximale d'une réponse (réflexion et notes de mémoire comprises), en tokens ; ne coûte que si elle est atteinte. Avec la mémoire, de quoi réécrire un fichier plein après la réflexion (voir `.env.exemple`) : le bot refuse de démarrer sous le minimum, et prévient dans le message « En ligne » si la réflexion risque de ne pas tenir | `6000` | | `MAX_HISTORY_LENGTH` | Messages gardés en mémoire dans le salon dédié (jusqu'à la moitié de plus avant d'y revenir d'un coup, ce qui garde la mémoire en cache) | `10` | | `CONTEXT_MESSAGE_LIMIT` | Messages lus autour d'une mention (100 au plus) | `20` | | `API_TIMEOUT` | Attente maximale d'une réponse, en secondes, relances comprises (une requête trop lente n'est pas relancée) ; le bot prévient au démarrage s'il est trop court pour `MAX_TOKENS` | `90` | | `WEB_SEARCH` | Recherche web et lecture de liens (`on` ou `off`) ; prévois alors un `API_TIMEOUT` d'au moins 90 secondes | `off` | | `PRESENCE` | Statut Discord et absences (`on` ou `off`) ; les `PRESENCE_*` règlent les chances de chaque statut et la nuit | `on` | | `MEMORY` | Mémoire des membres (`on` ou `off`) ; `MEMORY_MEMBER_LIMIT` et `MEMORY_SERVER_LIMIT` règlent la taille des fiches et du fichier général (3000 et 5000 caractères) | `off` | | `PDF_MAX_PAGES` | Pages maximales d'un PDF (0 = PDF refusés) | `20` | | `PERSONALITY_FILE` | Fichier de la personnalité | `personality_prompt.txt` | | `TIMEZONE` | Fuseau horaire de l'heure donnée à Claude | `Europe/Paris` | Un changement du `.env` demande de redémarrer le bot. ## Personnalité La personnalité est le texte donné à Claude avant chaque conversation : qui est le bot, son caractère, sa façon d'écrire. Elle se trouve dans `personality_prompt.txt` et peut être modifiée pendant que le bot tourne : elle est relue automatiquement. Pour une personnalité qui ne doit pas être publiée sur GitHub, crée `personality_prompt.local.txt` et indique-le dans `PERSONALITY_FILE` : les fichiers `*.local.txt` ne sont jamais envoyés sur GitHub ni copiés dans l'image Docker. Pas besoin d'y recopier la liste des emojis du serveur : le bot la donne lui-même à Claude. Il suffit de dire quand en mettre, par exemple « Tu termines chaque message par un seul emoji du serveur, directement après le dernier mot ». ## Docker Le `Dockerfile` construit une image prête à lancer. À chaque push sur `main`, GitHub Actions la construit et la publie sur Docker Hub (`/chatbot-maisonnette`, avec les secrets `DOCKER_USERNAME` et `DOCKER_PASSWORD` du dépôt). L'image ne contient ni `.env`, ni personnalité `*.local.txt`, ni historique, ni mémoire des membres. Les réglages se passent en variables d'environnement, et les fichiers à garder entre deux redémarrages doivent être sur un volume. Le dossier `memories/` se crée à côté de `HISTORY_FILE`, donc sur le même volume. Par exemple : ``` docker run -d --name chatbot \ --env-file .env \ -e HISTORY_FILE=/data/conversation_history.json \ -e LOG_FILE=/data/bot.log \ -v chatbot-data:/data \ /chatbot-maisonnette:latest ``` `--env-file` ne lit pas le `.env` comme le bot : il ne retire pas les commentaires en fin de ligne. Écris donc les valeurs sans guillemets et mets les commentaires sur leur propre ligne. Hors Docker, le fichier `.env` est prioritaire : une variable passée en ligne de commande (`MEMORY=on python main.py`) est ignorée si le `.env` la définit aussi. Pour une personnalité personnelle, monte le fichier dans le conteneur et indique son chemin dans `PERSONALITY_FILE`. ## Coûts et logs Chaque message envoyé à Claude est facturé selon le nombre de tokens lus (personnalité, mémoire, message) et écrits (la réponse). Pour limiter la facture : - la personnalité et la mémoire sont mises en cache : relues dans les 5 minutes, elles coûtent 10 % du prix normal ; - les images, PDF et fichiers ne sont payés qu'une fois, au message qui les contient ; - `claude-haiku-5-5` coûte environ 20 fois moins cher que `claude-sonnet-5-5` ; `THINKING=off` et un `EFFORT` bas réduisent aussi les réponses ; - chaque page de PDF coûte souvent plusieurs milliers de tokens, et chaque recherche web est payante en plus ; - avec `MEMORY=on`, les fiches jointes (5 au plus par message) sont relues à chaque message, hors cache : environ 850 tokens par fiche pleine de 3000 caractères. Le prompt ne liste que les membres qui ont une fiche, mais chaque nouvelle fiche, changement de pseudo ou note dans le fichier général fait réécrire la conversation en cache au message suivant. Chaque note demande en plus un court appel, la conversation étant alors relue en cache. Le fichier `bot.log` (`LOG_FILE`) garde l'activité du bot, avec pour chaque appel les tokens utilisés et le coût estimé en dollars. Les tarifs de Sonnet, Haiku et Opus 5.5 sont connus du bot ; pour un autre modèle, renseigne `PRICE_INPUT_PER_MTOK` et `PRICE_OUTPUT_PER_MTOK`. ## Fichiers créés par le bot À côté de l'historique (`HISTORY_FILE`), le bot crée : | Fichier | Contenu | | --- | --- | | `conversation_history.json` | Mémoire de chaque salon. Si le fichier est illisible, il est mis de côté sous un nom daté, jamais écrasé | | `waiting_messages.json` | Messages reçus pendant une absence, qui attendent leur réponse | | `presence_sources.json` | Listes d'animes, de musiques et de jeux, mises à jour une fois par jour | | `presence_state.json` | Statut Discord en cours, repris après un redémarrage | | `slash_commands.sha256` | Empreinte des commandes slash, pour ne les envoyer à Discord que quand elles changent | | `memories/registre.json` | Avec `MEMORY=on` : membres vus par le bot, leurs noms et anciens pseudos, et leurs réglages de la mémoire. Si le fichier est illisible, la mémoire des membres est coupée (comme avec `MEMORY=off`) et le fichier n'est pas modifié : corrige-le d'après le log, puis redémarre le bot. Le supprimer repart de zéro : la mémoire est désactivée pour tous, chacun la réactive avec `/memoire`. À la mise à jour vers la mémoire à activer soi-même, elle reste activée pour les membres qui avaient déjà une fiche et ne l'avaient pas désactivée | | `memories/membres/.md`, `memories/serveur.md` | Avec `MEMORY=on` : fiche de chaque membre et fichier général du serveur, tenus par Claude, en UTF-8. Un fichier illisible (enregistré dans un autre encodage, par exemple) est signalé dans le log : le bot continue de répondre, mais n'y écrit plus rien tant qu'il n'est pas corrigé. Après une modification directe sur le disque, redémarre le bot pour que la liste des membres (fiches existantes et surnoms) en tienne compte ; `/memoire` n'a pas besoin de redémarrage | | `memories/modifications.json` | Avec `MEMORY=on` : date de la dernière modification faite avec `/memoire` ou `/memoire-serveur` sur chaque fichier, montrée à Claude pour qu'il ne renote pas d'après la conversation ce qui a été retiré. S'il est illisible ou supprimé, ces dates sont oubliées, sans rien bloquer | | `bot.log` | Logs (5 Mo par fichier, 2 anciens fichiers gardés) | Le détail de chaque version est dans [`CHANGELOG.md`](CHANGELOG.md). ## Organisation du code ``` main.py lancement et logs bot/config.py lecture et validation du .env bot/models.py particularités de chaque modèle Claude (réflexion, tarifs) bot/claude_client.py appels à l'API de Claude, personnalité bot/history.py mémoire des conversations bot/members.py registre des membres (noms, anciens pseudos, réglages de la mémoire) bot/memory.py fiches des membres et fichier général : outil « memoire » de Claude bot/memory_views.py /memoire et /memoire-serveur : affichage, fenêtre « Modifier », confirmations bot/discord_bot.py événements Discord, réponses, commandes slash bot/emojis.py emojis du serveur : liste pour Claude, noms remplacés à l'envoi bot/presence.py statut Discord : tirage au sort et changements bot/presence_sources.py animes, musiques et jeux (AniList, Deezer, Steam) bot/images.py préparation des images (taille, format) bot/pdf_files.py vérification des PDF joints (pages, mot de passe) bot/text_files.py lecture des fichiers texte joints bot/text_utils.py découpage des longues réponses, réflexion affichée retirée bot/files.py écriture des fichiers sur le disque, sans fichier à moitié écrit tests/ tests automatisés : python -m unittest ```