AmputatorBot → Devvit-Migrationsplan v7
Status: lebendiger Entwurf — M1–M5 ausgeliefert, M6 (Cloud-Deploy + Launch) in Arbeit. Erstellt: 2026-05-25 Budgetobergrenze: 15 €/Monat gesamt
Arbeitsweise — BITTE ZUERST LESEN
Dev möchte vor dem Schreiben von Code zu Architekturentscheidungen konsultiert werden. Für jede Wahl, die:
- Bibliothek, Framework oder Muster betrifft
- Datenmodell-Design oder Schema-Entscheidungen betrifft
- Die Struktur eines neuen Moduls oder Layers betrifft
- Kosten- vs. Komplexitäts-Kompromisse betrifft
- In nicht-trivialer Weise von diesem Plan abweicht
…anhalten und vorher fragen. Die Optionen darlegen, die Kompromisse ehrlich erklären (einschließlich Widerspruch, falls ihr nicht einverstanden seid), und auf eine Entscheidung warten. Mechanische Implementierungsarbeit benötigt keine Konsultation; architektonische Form schon.
Externe Aktionen, die Dev manuell ausführt, nicht Claude:
- Abfragen gegen die Produktions-MySQL / Postgres-Datenbanken ausführen (Claude kann das SQL schreiben; Dev führt es aus)
- Dienste anmelden (Scaleway, Reddit-Entwicklerkonto, App Directory-Einreichung, Prämienprogramm-Registrierung)
- Alles, was ein externes Konto mit Devs Identität betrifft (OAuth-Logins,
devvit login,scw init, Änderungen an der Abrechnung) - DNS-Änderungen in Cloudflare
- Stoppen/Starten des Produktions-PythonAnywhere-Bots
git pushzu produktionsrelevanten Remotes- Veröffentlichen der Devvit-App (
devvit upload --publish)
Claude bereitet die genauen Befehle vor und sagt Dev, wann sie ausgeführt werden sollen. Code lesen, Code schreiben, lokale Tests, lokale Docker-Builds, lokales cargo/npm — Claude erledigt das.
Projektkontext
Was AmputatorBot ist: Ein Reddit-Bot, der AMP-URLs in Kommentaren/Posts/PMs erkennt und mit dem kanonischen (Nicht-AMP) Link antwortet. Wird von Dev (u/Killed_Mufasa) gewartet. Live unter https://github.com/jvdburgh/AmputatorBot. ~181 GitHub-Sterne. Läuft in großem Maßstab. Das Reddit Developer Platform-Migrationsprogramm bietet 1.000 $ für Bots, die bis zum 31. Dezember 2026 von der Data API zu Devvit migrieren.
Was migriert wird:
- Reddit-Bot (Python + PRAW) → Devvit-App (TypeScript,
@devvit/web) - HTTP-API (Flask) → Rust (Axum + Mozilla-Readability-Port + sqlx)
- Website (Flask + Bootstrap) → Astro 5 + Tailwind 4 + shadcn/ui
- Datenbank (MySQL auf PythonAnywhere) → Postgres 17 auf Scaleway Managed Database
- Host (PythonAnywhere) → Scaleway (Paris, Frankreich — EU-verwaltete Cloud)
Änderung des Architekturmodells: Devvit ist pro-Subreddit-opt-in, nicht global. Mods installieren die App pro Sub; sie kann keine Inhalte sehen, wo sie nicht installiert ist. Antworten werden als Pro-Installations-App-Identität gepostet, nicht als u/AmputatorBot. Akzeptiert. Geschätzter Rückgang des Antwortvolumens: 30–60 %.
Ziele (vom Benutzer angegeben):
- Moderner Stack, dabei lernen (Rust, Devvit, Astro/Tailwind4/shadcn)
- API-Vertrag bleibt abwärtskompatibel
- Während des Rewrites (M1–M5) lief der alte Bot auf PythonAnywhere weiter, damit die neue Seite frei brechen konnte, ohne dass es für den Benutzer Konsequenzen hatte. Der alte Bot wird zum M6-Cutover stillgelegt — kein permanenter Parallelbetrieb.
- Mit dem Vorschlaghammer durch den Rewrite; alten Code in
praw-python-archive/aufbewahren
Festgelegte Entscheidungen
| Pfad | Pro-Subreddit-Opt-in. Direkter Cutover bei M6 (alter Bot während der M1–M5-Entwicklung nur am Leben gehalten) |
| Devvit SDK | @devvit/web (modernes Servermodell) |
| Devvit-Konfiguration | devvit.json (offizielles Format mit $schema) |
| Devvit-Gerüst | reddit/devvit-template-bare, ohne Post/Menü-UI |
| Backend-Sprache | Rust |
| Backend-Framework | Axum 0.8 (Tokio + Hyper) |
| Artikel-Extraktion | dom_smoothie Crate (≈0.17, Rust Readability-Port, der Mozillas readability.js genau folgt, aktiv gewartet seit 2026-03) |
| HTML-Parsing | scraper Crate (basiert auf html5ever) |
| HTTP-Client | reqwest |
| Postgres-Treiber | sqlx (zur Kompilierzeit geprüfte Abfragen) |
| Datenbank | Postgres 17 auf Scaleway Managed Database (DB DEV-S oder nächsthöhere Stufe, dimensioniert für ~2 GB + Wachstumsreserve). 1,7 Mio. Zeilen bestätigt. |
| Website | Astro 5 + Tailwind 4 + shadcn/ui |
| Hosting | Scaleway Serverless Containers (Paris/AMS, EU-ursprüngliche französische Cloud) |
| Single Service | Eine Rust-Binärdatei bedient /api/* und Astro-Statik via tower-http::ServeDir |
| Observability | Zunächst nur Scaleway Cockpit (integrierte Logs + Metriken) |
| Code-Hosting | GitHub (bestehendes Monorepo) |
| DNS | Cloudflare (bestehend) — übernimmt auch Ratenbegrenzung |
| API-Authentifizierung | Keine auf irgendeiner Route — vollständig offen. Cloudflare behandelt Missbrauch. |
| Ratenbegrenzung | Nur Cloudflare, keine Rust-Middleware |
| Domain | Single — alles auf www.amputatorbot.com |
| Test-Subreddit | r/test |
| KI-Tooling | Claude Code + Devvit MCP + JetBrains MCP in IntelliJ |
| Pro-Benutzer-Opt-out | Gestrichen. Benutzer blockieren das Bot-Konto; die Pro-Installations-App-Identität macht blockbasiertes Opt-out auf Installationsebene funktionsfähig. |
| Pro-Subreddit-Opt-out | Gestrichen (Mods deinstallieren oder schalten autoReply in den Installationseinstellungen aus) |
| Modmail / DMs | Keine. Kein eingehender Modmail-Handler, keine Willkommens-Modmail, keine DMs — Reddits "App"-Abzeichen deckt die Bot-Offenlegung ab, und der Devvit App Directory-Eintrag deckt das Onboarding ab. |
| Karma-Schwelle | Keine |
| App Directory-Einreicher | u/Killed_Mufasa |
Warum diese Entscheidungen
Die Begründung für die obigen Entscheidungen. Jede war eine bewusste Wahl gegenüber einer glaubwürdigen Alternative.
-
Rust + Axum statt Node/TS oder Python. Einzelne statische Binärdatei, keine Laufzeit, kein virtualenv, kein
node_modules. Die schwierigen Teile von Rust (Lebensdauern, unsicher, fortgeschrittene Generics) tauchen bei dieser Art von Dienst (HTTP-Abruf + Regex + HTML-Abfrage + DB-Schreibvorgang) kaum auf, daher ist die Lernkurve hauptsächlichResult/?, async/Tokio und sqlx-Makros — alles wissenswert. Axum 0.8 ist das Konsens-Framework im Jahr 2026 (Tokio + Hyper + Tower-Middleware). sqlx gibt zur Kompilierzeit geprüfte Abfragen. -
dom_smoothiestattrs-trafilaturaund anderen Readability-Ports. Der bestehende Bot verwendetnewspaper3k+difflib.SequenceMatcherfür Raten-und-Prüfen-Konfidenz-Scoring (helpers/article_comparer.py). Für den Rust-Port wollen wir einen Mozilla-Readability-konformen Extraktor. Die Kandidaten ab Mai 2026:dom_smoothie0.17 (letzte Veröffentlichung 2026-03, ~40k aktuelle Downloads, Reponiklak/dom_smoothie) — folgtreadability.jsgenau, zwei Modi (Mozilla-Nachahmung + benutzerdefiniert), JSON-LD + OG-Metadaten-Parsing, aktiv gewartet.readability0.3 von kumabook (letzte Veröffentlichung 2023-12, stagniert) — Port der ursprünglichen arc90-Readability (vor Mozillas Fork). Höhere historische Downloadzahl, aber ungewartet.readable-readability0.4 (letzte Veröffentlichung 2022-12, stagniert).llm_readability0.0.17 (aktiv, aber 0.0.x — zu früh für die Produktion).
dom_smoothiegewinnt aufgrund aktiver Wartung + expliziter Mozillareadability.js-Abstammung. Wir verwenden seinen Standard (Mozilla-Nachahmungs)-Modus für Ähnlichkeitsbewertung. Kein Spike nötig; in M2 aufdom_smoothie = "0.17"festlegen. -
Keine Authentifizierung, nirgendwo. Die Produktion hat derzeit
authorization_required=Falseauf/convert(AmputatorBotCom/main.py:162). Niemand ist auf Bearer-Auth angewiesen, und nachdem das Opt-out weg ist, hat das Backend überhaupt keine privilegierten Routen mehr. Cloudflare behandelt Missbrauch. -
Ratenbegrenzung auf Cloudflare-Seite. Cloudflare ist bereits vor der Domain. Per-IP-Regeln dort sind kein Rust-Code und ohne erneuten Deploy konfigurierbar.
-
Kein Opt-out-Mechanismus, keine Modmail, keine DMs. Benutzer und Mods, die den Bot nicht wollen, können die Pro-Installations-App-Identität blockieren bzw. die App deinstallieren. Das ist genug — darüber hinaus ein separates Opt-out-Register zu bauen, ist ein Mechanismus, den wir nicht brauchen. Schneidet den Modmail-Trigger, die Willkommens-Modmail, die Bearer-Auth-privilegierten Routen, die
optouts-Tabelle, den Redis-Opt-out-Cache und den Legacy-"per DM herbeigerufen"-Pfad auf einen Schlag ab. -
Datenbankschema während der Migration modernisieren. Das Migrationstool läuft einmal. Das Bereinigen von
VARCHAR(4000)→TEXT, das Hinzufügen der Indizes, diemodels/table.pyfehlen, und das Entfernen toter Spalten ist günstig, es einmal zu tun und nie wieder. -
Zunächst nur Logs-Observability. Scaleway Cockpit (der integrierte Log- + Metrik-Stream) reicht aus, um zu greppen, wenn Probleme auftauchen. Füge Sentry/Bugsink nur hinzu, wenn Log-Grep schmerzhaft wird.
-
Die bestehenden AMP-Erkennungs- + kanonischen Methoden portieren, aber Fehlalarme an Ort und Stelle beheben. Die 14 Substring-Muster (
static/static.py) sind verrauscht (&HTML-Entitäten,lamp_post,streaming_amplifier). Die 10 kanonischen Methoden und ihre Reihenfolge inhelpers/canonical_methods.pysind gut abgestimmt und bleiben. Die Verbesserungen erfolgen auf der AMP-Erkennungsebene (Wortgrenzen, Bereichsbegrenzung auf URL-Komponenten nach der Extraktion) und in Fast-Path-Allowlists für High-Signal-Marker. -
Bot-Antwort-Markdown auffrischen. Einmaliger Design-Durchlauf für eine neue Vorlage, angewendet bei der Migration. Muss nicht identisch mit dem alten Bot aussehen.
-
git mvalles Alte inpraw-python-archive/in einem Commit. Vorschlaghammer. Alter PRAW-Bot läuft während M1–M5 auf PythonAnywhere weiter, damit die neue Seite frei brechen kann; er wird zum M6-Cutover gestoppt. -
Sechs Meilensteine, kein Kalender. Lokal-zuerst: M1–M5 sind alle lokale Entwicklung (Gerüst, kanonische Engine, API-Parität, Astro, Devvit-Playtest). M6 ist der Cloud- + öffentliche Launch (Scaleway-Bereitstellung, Produktionsdatenmigration, DNS-Cutover, App Directory, Alter-Bot-Ruhestand). Keine externe Frist (Prämie ist Dezember 2026). Alter Bot deckt die Produktion bis zum Cutover ab. Rust + Devvit + Astro zu lernen ist Teil des Ziels, daher sollte das Tempo dem Verständnis entsprechen, nicht einem Zeitplan.
Architektur
┌─────────────────────────────────────────────┐
│ Subreddit X (Mod hat amputatorbot installiert)│
│ Kommentare / Posts │
└──────────────┬──────────────────────────────┘
│ Devvit-Trigger (HTTP POST zu internen Routen)
▼
┌─────────────────────────────────────────────┐
│ Devvit-App (Node, @devvit/web) │
│ Läuft auf Reddit-Infrastruktur │
│ │
│ Interne HTTP-Routen: │
│ /internal/triggers/comment-submit │
│ /internal/triggers/post-submit │
│ │
│ Devvit Redis (integriert): │
│ handled:{commentId} TTL 1h │
│ │
│ Pro-Installationseinstellungen: │
│ autoReply, customFooter, killSwitch │
│ │
│ Allowlist (devvit.json): │
│ http.domains: ["www.amputatorbot.com"] │
└──────────────┬──────────────────────────────┘
│ HTTPS (keine Authentifizierung):
│ GET /api/v1/convert?q=...&gac=true&md=3
▼
┌─────────────────────────────────────────────┐
│ Cloudflare (DNS + Ratenbegrenzung + WAF) │
└──────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ Scaleway (Paris/AMS, EU) │
│ www.amputatorbot.com │
│ │
│ Rust-Backend (Axum 0.8) — einzelne Binär │
│ /api/v1/convert (bestehender Vertrag)
│ /api/v2/convert (camelCase JSON) │
│ /api/v2/stats (zwischengespeicherter Zähler)│
│ /api/v2/health │
│ Fallback → Astro-Statik (ServeDir) │
│ │
│ Kanonische-Suche (10 Methoden, gleiche │
│ Reihenfolge wie Python-Impl.): │
│ REL, CANURL, OG_URL, │
│ GOOGLE_MANUAL_REDIRECT, GOOGLE_JS_…, │
│ BING_ORIGINAL_URL, SCHEMA_MAINENTITY, │
│ TCO_PAGETITLE, META_REDIRECT, │
│ GUESS_AND_CHECK (Artikel-Ähnlichkeit- │
│ Konfidenz via Mozilla Readability), │
│ DATABASE (Cache-Lookup) │
│ │
│ Postgres 17 (Scaleway Managed Database): │
│ links │
└─────────────────────────────────────────────┘
Warum einzelner Rust-Dienst, der API + statische Website bedient: Devvits Allowlist ist auf Domain-Ebene. Ein Scaleway Serverless Container hält die Deploy-Geschichte einfach. tower-http::ServeDir als Fallback sind ~5 Zeilen.
API-Vertrag
Zwei Endpunkte werden parallel bereitgestellt:
GET /api/v1/convert— Legacy-Vertrag beibehalten (Snake_Case Query-String, Snake_Case JSON-Antwort). Nur GET, da der Legacy-Bot immer query-string-basiert war; POST ist nur v2. Für neue Aufrufer veraltet, aber auf unbestimmte Zeit unterstützt, damit externe Dienste nicht brechen.POST /api/v2/convert— Moderner Vertrag: JSON-Body in CamelCase, JSON-Antwort in CamelCase.entryTypeist ein Body-Feld statt eines Headers. Strenge Validierung (unbekannte Felder → 422). Empfohlen für Devvit (M5) + Website (M4) + neue externe Aufrufer.
Jeder Schreibvorgang in den links-Cache zeichnet api_version (1 oder 2 — NULL für die vor-v7-Legacy-Bot-CSV-importierten Zeilen) auf, damit die Adoption abfragbar ist: SELECT api_version, COUNT(*) FROM links GROUP BY 1.
v1 — GET /api/v1/convert (aus AmputatorBotCom/main.py:161+)
Query-Parameter:
q(erforderlich) — URL oder Text, der URLs enthältgac— Raten-und-Prüfen, Standardtruemd— maximale Tiefe, Standardstatic.MAX_DEPTH(Wert portieren)gc— Antwort-Markdown in der Antwort generieren, Standardfalser— Weiterleitungsmodus (303 zur kanonischen URL statt JSON-Antwort), Standardfalse
URL-Kodierungsverhalten — beide müssen funktionieren
Die API unterstützt zwei Aufrufer: wohlverhaltene Clients, die ihre URLs Prozent-kodieren, und Menschen, die rohe URLs in die Adressleiste des Browsers einfügen. Der v7-Vertrag:
- Kodierte URLs funktionieren immer, unabhängig von der Position von
qin der Parameterliste.?q=https%3A%2F%2F...&gac=trueund?gac=true&q=https%3A%2F%2F...sind beide gültig. - Unkodierte URLs funktionieren, wenn
qder letzte Parameter ist.?gac=true&q=https://example.eu/article?id=1ist in Ordnung — die Tail-Parameter der URL (?id=1) bleiben erhalten.qzwischen anderen bekannten Parametern ist …