Plan de migration AmputatorBot → Devvit v7
Statut : brouillon vivant — M1–M5 livrés, M6 (déploiement cloud + lancement) en cours. Rédigé le : 2026-05-25 Plafond budgétaire : 15 €/mois au total
Style de travail — À LIRE EN PREMIER
Dev aime être consulté sur les décisions architecturales avant que le code ne soit écrit. Pour tout choix qui concerne :
- La bibliothèque, le framework ou le modèle à utiliser
- La conception du modèle de données ou les décisions de schéma
- La structure d'un nouveau module ou d'une nouvelle couche
- Les compromis coût vs. complexité
- L'écart par rapport à ce plan de manière non triviale
… arrêtez-vous et demandez avant de procéder. Présentez les options, expliquez honnêtement les compromis (y compris les objections si vous n'êtes pas d'accord) et attendez une décision. Le travail d'implémentation mécanique ne nécessite pas de consultation ; la forme architecturale, si.
Actions externes que Dev effectue manuellement, pas Claude :
- Exécution de requêtes sur les bases de données MySQL / Postgres de production (Claude peut écrire le SQL ; Dev l'exécute)
- Inscription aux services (Scaleway, compte développeur Reddit, soumission à l'App Directory, inscription aux programmes de primes)
- Tout ce qui touche à un compte externe avec l'identité de Dev (connexions OAuth,
devvit login,scw init, modifications de facturation) - Changements DNS chez Cloudflare
- Arrêt/démarrage du bot PythonAnywhere de production
git pushvers les dépôts affectant la production- Publication de l'application Devvit (
devvit upload --publish)
Claude prépare les commandes exactes et dit à Dev quand les exécuter. Lire le code, écrire le code, exécuter les tests locaux, les builds Docker locaux, les cargo/npm locaux — Claude fait tout cela.
Contexte du projet
Qu'est-ce qu'AmputatorBot : Un bot Reddit qui détecte les URLs AMP dans les commentaires, publications et messages privés et répond avec le lien canonique (non-AMP). Maintenu par Dev (u/Killed_Mufasa). En ligne sur https://github.com/jvdburgh/AmputatorBot. ~181 étoiles GitHub. Fonctionne à grande échelle. Le programme de migration de la plateforme développeur de Reddit offre 1 000 $ aux bots qui migrent de l'API Data vers Devvit avant le 31 décembre 2026.
Ce qui est migré :
- Bot Reddit (Python + PRAW) → Application Devvit (TypeScript,
@devvit/web) - API HTTP (Flask) → Rust (Axum + port de Mozilla-Readability + sqlx)
- Site web (Flask + Bootstrap) → Astro 5 + Tailwind 4 + shadcn/ui
- Base de données (MySQL sur PythonAnywhere) → Postgres 17 sur Scaleway Managed Database
- Hébergement (PythonAnywhere) → Scaleway (Paris, France — cloud géré d'origine UE)
Changement de modèle architectural : Devvit est une option par sous-reddit, pas globale. Les modérateurs installent l'application par sous-reddit ; elle ne peut pas voir le contenu là où elle n'est pas installée. Les réponses sont publiées sous l'identité de l'application installée, pas u/AmputatorBot. Accepté. Chute estimée du volume de réponses : 30–60 %.
Objectifs (déclarés par l'utilisateur) :
- Stack moderne, apprendre des choses en chemin (Rust, Devvit, Astro/Tailwind4/shadcn)
- Le contrat API reste rétrocompatible
- Pendant la réécriture (M1–M5), l'ancien bot continue à tourner sur PythonAnywhere pour que le nouveau côté puisse casser librement sans conséquence visible pour l'utilisateur. L'ancien bot est retiré lors du basculement M6 — pas d'exécution parallèle permanente.
- Massacre la réécriture ; préserver l'ancien code dans
praw-python-archive/
Décisions verrouillées
| Chemin | Opt-in par sous-reddit. Basculement direct à M6 (ancien bot maintenu actif pendant le développement M1–M5 uniquement) |
| SDK Devvit | @devvit/web (modèle serveur moderne) |
| Configuration Devvit | devvit.json (format officiel avec $schema) |
| Échafaudage Devvit | reddit/devvit-template-bare, dépouillé de l'interface de publication/menu |
| Langage backend | Rust |
| Framework backend | Axum 0.8 (Tokio + Hyper) |
| Extraction d'articles | Crate dom_smoothie (≈0.17, port Rust Readability qui suit de près readability.js de Mozilla, activement maintenue en date de mars 2026) |
| Analyse HTML | Crate scraper (basée sur html5ever) |
| Client HTTP | reqwest |
| Pilote Postgres | sqlx (requêtes vérifiées à la compilation) |
| Base de données | Postgres 17 sur Scaleway Managed Database (DB DEV-S ou un niveau supérieur, dimensionnée pour ~2 Go + marge de croissance). 1,7 million de lignes confirmé. |
| Site web | Astro 5 + Tailwind 4 + shadcn/ui |
| Hébergement | Scaleway Serverless Containers (Paris/AMS, cloud français d'origine UE) |
| Service unique | Un binaire Rust sert /api/* et le statique Astro via tower-http::ServeDir |
| Observabilité | Scaleway Cockpit (journaux + métriques intégrés) uniquement initialement |
| Hébergement de code | GitHub (dépôt monolithique existant) |
| DNS | Cloudflare (existant) — gère également la limitation de débit |
| Authentification API | Aucune sur toutes les routes — complètement ouvert. Cloudflare gère les abus. |
| Limitation de débit | Cloudflare uniquement, pas de middleware Rust |
| Domaine | Unique — tout sur www.amputatorbot.com |
| Sous-reddit de test | r/test |
| Outillage IA | Claude Code + Devvit MCP + JetBrains MCP dans IntelliJ |
| Désinscription par utilisateur | Abandonné. Les utilisateurs bloquent le compte du bot ; l'identité de l'application par installation fait fonctionner le blocage comme désinscription au niveau de l'installation. |
| Désinscription par sous-reddit | Abandonné (les mods désinstallent, ou désactivent autoReply dans les paramètres d'installation) |
| Modmail / MP | Aucun. Pas de gestionnaire de modmail entrant, pas de modmail de bienvenue, pas de MP — le badge "App" de Reddit gère la divulgation du bot, et la liste de l'App Directory Devvit couvre l'intégration. |
| Seuil de karma | Aucun |
| Soumission à l'App Directory | u/Killed_Mufasa |
Pourquoi ces choix
La raison derrière les décisions ci-dessus. Chacune a été un choix délibéré par rapport à une alternative crédible.
-
Rust + Axum plutôt que Node/TS ou Python. Binaire statique unique, pas d'exécution, pas de virtualenv, pas de
node_modules. Les parties difficiles de Rust (durées de vie, unsafe, génériques avancés) n'apparaissent pas vraiment dans ce genre de service (fetch HTTP + regex + requête HTML + écriture DB), donc la courbe d'apprentissage est principalementResult/?, async/Tokio, et les macros sqlx — tout cela vaut la peine d'être connu. Axum 0.8 est le framework consensuel en 2026 (Tokio + Hyper + middleware Tower). sqlx offre des requêtes vérifiées à la compilation. -
dom_smoothieplutôt quers-trafilaturaet autres ports Readability. Le bot existant utilisenewspaper3k+difflib.SequenceMatcherpour le scoring de confiance par estimation (helpers/article_comparer.py). Pour le port Rust, nous voulons un extracteur aligné sur Mozilla-Readability. Les candidats en mai 2026 :dom_smoothie0.17 (dernière version mars 2026, ~40k téléchargements récents, dépôtniklak/dom_smoothie) — suit de prèsreadability.js, deux modes (imitation Mozilla + personnalisé), analyse des métadonnées JSON-LD + OG, activement maintenu.readability0.3 par kumabook (dernière version décembre 2023, stagnant) — port du Readability original d'arc90 (antérieur au fork de Mozilla). Nombre de téléchargements historiques plus élevé mais non maintenu.readable-readability0.4 (dernière version décembre 2022, stagnant).llm_readability0.0.17 (actif mais 0.0.x — trop précoce pour la production).
dom_smoothiegagne grâce à sa maintenance active + sa filiation explicite avecreadability.jsde Mozilla. Nous utilisons son mode standard (imitation Mozilla) pour le scoring de similarité. Pas de spike nécessaire ; fixer àdom_smoothie = "0.17"dans M2. -
Pas d'authentification, nulle part. La production a actuellement
authorization_required=Falsesur/convert(AmputatorBotCom/main.py:162). Personne ne dépend de l'authentification bearer, et avec la suppression de la désinscription, le backend n'a plus de routes privilégiées du tout. Cloudflare gère les abus. -
Limitation de débit côté Cloudflare. Cloudflare est déjà devant le domaine. Les règles par IP y sont zéro code Rust et configurables sans redéploiement.
-
Pas de mécanisme de désinscription, pas de modmail, pas de MP. Les utilisateurs et mods qui ne veulent pas du bot peuvent bloquer l'identité de l'application par installation ou désinstaller l'application respectivement. C'est suffisant — construire un registre de désinscription séparé par-dessus est un mécanisme dont nous n'avons pas besoin. Cela supprime le déclencheur modmail, le modmail de bienvenue, les routes privilégiées d'authentification bearer, la table
optouts, le cache Redis des désinscriptions et le chemin hérité "invoqué via MP" en un seul coup. -
Moderniser le schéma de base de données pendant la migration. L'outil de migration s'exécute une fois. Nettoyer
VARCHAR(4000)→TEXT, ajouter les index qui manquent àmodels/table.py, et supprimer les colonnes mortes est peu coûteux à faire une fois et jamais plus. -
Observabilité par journaux uniquement initialement. Scaleway Cockpit (le flux de journaux + métriques intégré) est suffisant pour fouiller lorsque des problèmes surviennent. Ajouter Sentry/Bugsink uniquement si fouiller les journaux devient pénible.
-
Porter les méthodes existantes de détection AMP + recherche canonique, mais corriger les faux positifs sur place. Les 14 motifs de sous-chaîne (
static/static.py) sont bruyants (entités HTML&,lamp_post,streaming_amplifier). Les 10 méthodes canoniques et leur ordre danshelpers/canonical_methods.pysont bien réglés et restent. Les améliorations se produisent au niveau de la couche de détection AMP (limites de mots, cadrage sur les composants d'URL après extraction) et dans les listes blanches de chemin rapide pour les marqueurs à fort signal. -
Rafraîchir le markdown de réponse du bot. Un passage de conception unique sur un nouveau modèle, appliqué à la migration. Il n'a pas besoin de ressembler à l'ancien bot.
-
git mvde tout l'ancien danspraw-python-archive/en un seul commit. Massacre. L'ancien bot PRAW continue à tourner sur PythonAnywhere pendant M1–M5 pour que le nouveau côté puisse casser librement ; il est arrêté au moment du basculement M6. -
Six jalons, pas de calendrier. Priorité locale : M1–M5 sont tout le développement local (échafaudage, moteur canonique, parité API, Astro, test Devvit). M6 est le cloud + le lancement public (provisionnement Scaleway, migration des données de production, basculement DNS, App Directory, retraite de l'ancien bot). Pas de date limite externe (la prime est en décembre 2026). L'ancien bot couvre la production jusqu'au basculement. Apprendre Rust + Devvit + Astro fait partie de l'objectif, donc le rythme doit correspondre à la compréhension, pas à un calendrier.
Architecture
┌─────────────────────────────────────────────┐
│ Sous-reddit X (mod a installé amputatorbot) │
│ Commentaires / publications │
└──────────────┬──────────────────────────────┘
│ Déclencheurs Devvit (HTTP POST vers routes internes)
▼
┌─────────────────────────────────────────────┐
│ Application Devvit (Node, @devvit/web) │
│ S'exécute sur l'infrastructure Reddit │
│ │
│ Routes HTTP internes : │
│ /internal/triggers/comment-submit │
│ /internal/triggers/post-submit │
│ │
│ Redis intégré Devvit : │
│ handled:{commentId} TTL 1h │
│ │
│ Paramètres par installation : │
│ autoReply, customFooter, killSwitch │
│ │
│ Liste blanche (devvit.json) : │
│ http.domains: ["www.amputatorbot.com"] │
└──────────────┬──────────────────────────────┘
│ HTTPS (pas d'authentification) :
│ GET /api/v1/convert?q=...&gac=true&md=3
▼
┌─────────────────────────────────────────────┐
│ Cloudflare (DNS + limitation de débit + WAF)│
└──────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ Scaleway (Paris/AMS, UE) │
│ www.amputatorbot.com │
│ │
│ Backend Rust (Axum 0.8) — binaire unique │
│ /api/v1/convert (contrat existant) │
│ /api/v2/convert (JSON en camelCase) │
│ /api/v2/stats (compteur mis en cache) │
│ /api/v2/health │
│ fallback → statique Astro (ServeDir) │
│ │
│ Recherche canonique (10 méthodes, même │
│ ordre que l'implémentation Python) : │
│ REL, CANURL, OG_URL, │
│ GOOGLE_MANUAL_REDIRECT, GOOGLE_JS_…, │
│ BING_ORIGINAL_URL, SCHEMA_MAINENTITY, │
│ TCO_PAGETITLE, META_REDIRECT, │
│ GUESS_AND_CHECK (confiance de similarité│
│ d'article via Mozilla Readability), │
│ DATABASE (recherche en cache) │
│ │
│ Postgres 17 (Scaleway Managed Database) : │
│ liens │
└─────────────────────────────────────────────┘
Pourquoi un seul service Rust servant l'API + le site statique : La liste blanche de Devvit est au niveau du domaine. Un seul conteneur Scaleway Serverless maintient l'histoire de déploiement simple. tower-http::ServeDir comme fallback fait ~5 lignes.
Contrat API
Deux points de terminaison sont livrés côte à côte :
GET /api/v1/convert— contrat hérité préservé (chaîne de requête en snake_case, réponse JSON en snake_case). GET uniquement car le bot hérité était toujours basé sur la chaîne de requête ; POST est réservé à v2. Déprécié pour les nouveaux appelants mais pris en charge indéfiniment pour ne pas casser les services externes.POST /api/v2/convert— contrat moderne : corps JSON en camelCase, réponse JSON en camelCase.entryTypeest un champ du corps au lieu d'un en-tête. Validation stricte (champs inconnus → 422). Recommandé pour Devvit (M5) + site web (M4) + nouveaux appelants externes.
Chaque écriture dans le cache links enregistre api_version (1 ou 2 — NULL pour les lignes importées du CSV du bot hérité pré-v7) afin que l'adoption soit interrogeable : SELECT api_version, COUNT(*) FROM links GROUP BY 1.
v1 — GET /api/v1/convert (de AmputatorBotCom/main.py:161+)
Paramètres de requête :
q(obligatoire) — URL ou texte contenant des URLsgac— estimation, par défauttruemd— profondeur maximale, par défautstatic.MAX_DEPTH(porter la valeur)gc— générer le markdown de réponse dans la réponse, par défautfalser— mode redirection (303 vers le canonique au lieu de retourner JSON), par défautfalse
Comportement d'encodage d'URL — les deux doivent fonctionner
L'API prend en charge deux types d'appelants : les clients bien élevés qui percent-encodent leurs URLs, et les humains qui collent des URLs brutes dans la barre d'adresse du navigateur. Le contrat v7 :
- Les URLs encodées fonctionnent toujours, quelle que soit la position de
qdans la liste des paramètres.?q=https%3A%2F%2F...&gac=trueet?gac=true&q=https%3A%2F%2F...sont tous deux valides. - Les URLs non encodées fonctionnent lorsque
qest le dernier paramètre.?gac=true&q=https://example.eu/article?id=1est correct — les paramètres de fin de l'URL (?id=1) sont préservés.qentre d'autres paramètres connus est...
…[tronqué pour la traduction]