Aller au contenu principal

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​

  • #1 Ne compilait plus avec Go 1.23+ : la version épinglée de golang.org/x/net utilisait un go:linkname refusé par les chaînes récentes.

Limites de débit​

  • #2 Le 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.
  • #3 Toute 404 sous /webhooks/ bloquait la route comme webhook inconnu, y compris pour un message de webhook supprimé.
  • #5 Toutes 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}/channels partageait une file entre tous les serveurs.
  • #7 Le suivi et la réponse d'origine d'une interaction tournaient dans deux files, alors que Discord les compte ensemble.
  • #8 Les interactions attendaient et dépensaient la limite globale, dont Discord les exempte.
  • #9 Renommer 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.
  • #12 La limite globale était déduite de max_concurrency, une heuristique non documentée qui coûte une requête /gateway/bot : désactivée par défaut, remplacée par BOT_RATELIMIT_OVERRIDES.
  • #14 La 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.
  • #15 La 429 générée par le proxy envoyait x-ratelimit-after, un en-tête que Discord n'envoie jamais, au lieu de X-RateLimit-Reset-After.

Requêtes et sécurité​

  • #4 L'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.
  • #10 Les requêtes invalides n'étaient pas suivies, alors que 10 000 en dix minutes font bannir l'adresse IP : voir Métriques.
  • #11 strings.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​

  • #13 L'adresse de Discord était codée en dur : DISCORD_URL permet de viser un simulateur.
  • #16 Un 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.