Aller au contenu

04 / 05 En service · serveur Discord de Patchoumio

Patchoumio

Bot Discord du serveur Warframe francophone du youtubeur Patchoumio : des fiches rivens (prix du marché, stats à viser) et des liens de builds, servis à qui tape un nom dans le bon salon. Le contenu se gère dans un panel web d'administration. Bot, panel et déploiement : conçus et développés seul.

Coupe du trajet d'un nom d'arme tapé dans un salon Un membre tape un nom dans le salon rivens ou builds de Discord. Le bot cherche une correspondance exacte, puis approchée, et répond par une fiche. Le prix vient de warframe.market, une requête par seconde au plus, avec un cache de 10 minutes et un prix manuel en repli. Le contenu est écrit par le panel Laravel dans une base SQLite que le bot ne fait que lire, sur la même machine. La règle d'héritage et le format des fiches sont partagés en JavaScript par le bot et l'aperçu du panel. message ≤ 40 car. lecture seule Discord · salons rivens, builds un nom tapé, sans commande 1 Bot · Node 24 exact, puis Fuse.js(seuil 0,25) :3 suggestions 2 warframe.market API v1, 1 req/scache 10 minrepli : prix manuel 3 SQLite · WAL base partagéerelue toutesles 30 s 4 Panel · Laravel 12 seul à écrireconnexion Discord,accès par rôle 4 Même machine montage :z,jamais :Z 4 shared/ · JS héritage, embedsmêmes fixtures 5 Coupe · Patchoumio discord.js · Laravel 12
Coupe du trajet d'un nom d'arme tapé dans un salon. Les numéros renvoient aux passages du texte. Trait tireté : système externe ; hachures : contrainte.

Le problème

Dans le salon rivens, un membre tape le nom d'une arme et reçoit une fiche : le prix de base des rivens, et les stats positives et négatives qui comptent, chacune avec un emoji d'appréciation et une explication. Dans le salon builds, il tape le nom d'une frame, d'une arme ou d'un compagnon et reçoit les liens de builds et leurs images. Ce contenu n'est pas écrit par un développeur : il est tenu par les membres qui ont le rôle d'accès au panel, et chaque réglage doit atteindre le bot sans redémarrage.

Ce qui rendait la solution évidente inutilisable

La façon évidente d'écrire un bot Discord passe par des commandes slash : elles n'ont besoin d'aucun accès au texte des messages. Mais ici, on tape juste un nom, sans commande. Pour le lire, le bot demande l'intent MessageContent, qui est privilégié et doit être activé à la main dans le portail développeur de Discord.

Cet accès est borné dans le code : le bot ne regarde que les salons choisis dans le panel et ignore tout message de plus de 40 caractères. Un nom d'arme y tient ; une conversation, non. Un mot sans correspondance, comme « salut », ne reçoit pas de réponse.

Une recherche exacte ne suffit pas : les joueurs écrivent « zénit » pour Zénith, « rubico prime » ou « karak kuva ». Le nom est d'abord normalisé (minuscules, accents et ponctuation retirés), puis comparé aux noms et aux alias. Les variantes (Prime, Kuva, Vandal, Tenet…) sont des alias de l'arme de base, dans les deux ordres. Sans correspondance exacte, Fuse.js propose jusqu'à trois suggestions en boutons, avec un seuil de 0,25 calibré sur les 420 armes réelles. Une arme peut aussi avoir des onglets (« Crit », « Statut »…), affichés en boutons sous sa fiche.

Ce qui a été choisi, et ce que ça a coûté

Le prix vient de warframe.market. La recherche d'enchères de rivens n'existe que dans la version 1 de son API, la version 2 ne fournit que les référentiels : le bot dépend donc d'une API ancienne, et l'appel est isolé dans un seul module de chaque côté pour le jour où elle disparaîtra. Aucune limite de débit n'est publiée. Le bot s'en tient à une requête à la fois, au plus une par seconde, avec un cache de 10 minutes par arme ; deux demandes simultanées pour la même arme ne déclenchent qu'un appel. L'API ne filtre pas les vendeurs connectés : le filtre est fait côté bot. En cas d'échec, la fiche affiche le prix manuel saisi dans le panel, sinon « Prix indisponible ».

Le panel Laravel et le bot partagent un seul fichier SQLite, en mode WAL. Le panel gère le schéma et il est le seul à écrire ; le bot ouvre la base en lecture seule et la relit toutes les 30 secondes. Un réglage fait dans le panel arrive donc sur Discord sans redémarrage, et il n'y a ni API entre les deux ni serveur de base de données à faire tourner.

Le prix : les deux doivent tourner sur la même machine et voir le même fichier. Sous SELinux, le dossier de la base est monté en :z, étiquette partagée, et jamais en :Z. Avec :Z, chaque conteneur réétiquette le dossier pour lui seul, les verrous WAL ne sont plus partagés, et les lectures du bot échouent pendant les écritures du panel. Constaté en test, puis corrigé et mesuré : aucune erreur avec :z.

Chaque catégorie d'armes (Fusil, Mêlée…) définit, pour chaque stat, l'emoji, l'explication et la visibilité ; une arme en hérite et ne stocke que ce qu'elle surcharge. Une chaîne vide est une vraie valeur, « pas d'explication », distincte de l'absence de valeur, « hérite ». Cette règle est écrite deux fois, en PHP pour le panel et en JavaScript pour le bot, et les deux implémentations sont testées sur le même fichier de cas, avec Pest et Vitest. Le code JavaScript partagé sert aussi au format des fiches : l'aperçu en direct du panel est rendu par le même code que l'embed envoyé sur Discord.

Les 420 armes à riven et les builds (frames, armes, compagnons) sont importés automatiquement depuis warframe.market et les données de la communauté. L'import se relance sans doublon : un nom, une catégorie, un prix ou une image modifiés dans le panel sont conservés. Les emojis et les messages du bot se modifient aussi dans le panel.

Le bot est écrit en TypeScript, exécuté directement par Node 24, qui retire les types à la volée : il n'y a pas d'étape de build. Le panel est en Laravel 12, Inertia, Vue 3 et Tailwind 4. L'ensemble se lance avec Docker ou Podman Compose.

Vérifiable

Le dépôt est privé, et le bot ne se voit qu'en rejoignant le serveur Discord de Patchoumio : rien n'est consultable directement.

Chiffres déclarés, dépôt privé :

Données
420 armes à riven importées depuis warframe.market, au 12 octobre 2026