Corrections du fork
Notre branche fixes part du dernier commit du projet d'origine (97b3251, archivé en mars 2026). Nous avons utilisé les autres forks (DraftBot, davfsa, Melonly, LorittaBot, TicketsBot, PluralKit, WelcomerTeam…) comme catalogue de bugs connus, pas comme code à fusionner : chaque bug a été vérifié sur le code d'origine, contre la documentation de Discord et une capture de vraies réponses de Discord, puis corrigé avec un test qui échoue sans la correction.
La liste complète, avec les preuves et l'origine de chaque bug, est dans FIXES.md.
Compilation
#1Ne compilait plus avec Go 1.23+ : la version épinglée degolang.org/x/netutilisait ungo:linknamerefusé par les chaînes récentes.
Limites de débit
#2Le verrou global ne servait à rien : écrit après une 429 globale, il n'était jamais lu, et les requêtes continuaient d'arriver dans la limite.#3Toute 404 sous/webhooks/bloquait la route comme webhook inconnu, y compris pour un message de webhook supprimé.#5Toutes les requêtes/channels/{id}partageaient une seule file, tous salons confondus : verrouiller beaucoup de salons pendant un raid les sérialisait, et un salon épuisé les endormait tous.#6/guilds/{id}/channelspartageait une file entre tous les serveurs.#7Le suivi et la réponse d'origine d'une interaction tournaient dans deux files, alors que Discord les compte ensemble.#8Les interactions attendaient et dépensaient la limite globale, dont Discord les exempte.#9Renommer un salon ou changer son sujet partageait la file de toutes les modifications de salon, et après la 429 de sa sous-limite, seule la courte réinitialisation du bucket était attendue.#12La limite globale était déduite demax_concurrency, une heuristique non documentée qui coûte une requête/gateway/bot: désactivée par défaut, remplacée parBOT_RATELIMIT_OVERRIDES.#14La file dédiée aux suppressions de messages de plus de 14 jours ne s'appliquait jamais, à cause d'une comparaison sur le mauvais segment du chemin.#15La 429 générée par le proxy envoyaitx-ratelimit-after, un en-tête que Discord n'envoie jamais, au lieu deX-RateLimit-Reset-After.
Requêtes et sécurité
#4L'URL vers Discord était reconstruite à partir du chemin décodé : l'emoji keycap#️⃣transformait son#en fragment, et la réaction arrivait tronquée à Discord. Le saut entre nœuds du cluster avait le même bug.#10Les requêtes invalides n'étaient pas suivies, alors que 10 000 en dix minutes font bannir l'adresse IP : voir Métriques.#11strings.SplitN(url, "?", 1)ne découpait jamais rien : une longue valeur dans la query string pouvait exempter une requête du verrou sur les 401.
Configuration
#13L'adresse de Discord était codée en dur :DISCORD_URLpermet de viser un simulateur.#16Un nœud annonçait l'adresse devinée par memberlist, parfois injoignable dans un conteneur :CLUSTER_ADVERTISE_ADDR.
Ce que nous n'avons pas repris
- Le modèle « seau à jetons » de davfsa, qui renvoie des requêtes dès qu'un jeton revient : sa détection n'avait pas de test, et un client basé sur @discordjs/rest attend de toute façon la recharge complète. Le proxy attend le temps annoncé par Discord, ce qui peut être trop long, jamais trop court, quel que soit le modèle du bucket.
- La pause de 250 ms après chaque réaction (DraftBot, PluralKit, héritée d'eris) : aucune capture ne la justifie pour l'instant.
- Les requêtes concurrentes sur un même bucket (davfsa) : un client @discordjs/rest n'envoie qu'une requête par bucket à la fois.
Les files d'origine ignorent la méthode HTTP, ce qui les rend plus grossières que les buckets de Discord : cela peut faire attendre plus que nécessaire, jamais envoyer dans une limite, donc nous les gardons ainsi.