Communitygithub.com

jvdburgh/AmputatorBot

Plan de migración de AmputatorBot v7: decisiones bloqueadas, hitos M1–M6, contrato API, esquema, notas de migración. Cargar al trabajar en la arquitectura, reescritura o despliegue M6 de AmputatorBot.

¿Qué es AmputatorBot?

Este skill documenta el plan de migración v7 de AmputatorBot, un bot de Reddit que detecta enlaces AMP y responde con enlaces canónicos. La migración mueve el bot de Python (PRAW) a Devvit (TypeScript), la API HTTP de Flask a Rust (Axum) con extracción de artículos usando `dom_smoothie`, el sitio web de Flask+Bootstrap a Astro 5+Tailwind 4+shadcn/ui, y la base de datos de MySQL en PythonAnywhere a Postgres 17 en Scaleway. Incluye decisiones bloqueadas como la adopción de un modelo de opt-in por subreddit (sin paralelismo permanente), la eliminación de mecanismos de exclusión voluntaria y de autenticación (Cloudflare gestiona el abuso), y seis hitos (M1–M5 locales, M6 en la nube). También detalla el contrato de API heredado (v1, snake_case, GET) y moderno (v2, camelCase, POST), el esquema de base de datos actualizado y el estilo de trabajo colaborativo entre el desarrollador y Claude (consultas SQL manuales, despliegues supervisados, y automatización de tareas mecánicas). Es especialmente útil para equipos que reescriben bots legacy de Reddit en Devvit o migran servicios Python a Rust con observabilidad mínima.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jvdburgh/AmputatorBot/tree/HEAD/.claude/skills/amputatorbot-migration

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Plan de Migración AmputatorBot → Devvit v7

Estado: borrador vivo — M1–M5 enviados, M6 (despliegue en la nube + lanzamiento) en progreso. Redactado: 2026-05-25 Límite presupuestario: €15/mes en total


Estilo de trabajo — LÉE ESTO PRIMERO

Al desarrollador le gusta ser consultado sobre decisiones arquitectónicas antes de que se escriba código. Para cualquier elección que sea:

  • Sobre qué librería, framework o patrón usar
  • Sobre el diseño del modelo de datos o decisiones de esquema
  • Sobre cómo estructurar un nuevo módulo o capa
  • Sobre compensaciones entre costo y complejidad
  • Sobre desviarse de este plan de forma no trivial

…detente y pregunta antes de proceder. Presenta las opciones, explica las compensaciones honestamente (incluyendo objeciones si no estás de acuerdo) y espera una decisión. El trabajo de implementación mecánica no necesita consulta; la forma arquitectónica sí.

Acciones externas que el desarrollador hace manualmente, no Claude:

  • Ejecutar consultas contra las bases de datos MySQL/Postgres de producción (Claude puede escribir el SQL; el desarrollador lo ejecuta)
  • Registrarse en servicios (Scaleway, cuenta de desarrollador de Reddit, envío al Directorio de Apps, registro en el programa de recompensas)
  • Cualquier cosa que toque una cuenta externa con la identidad del desarrollador (inicios de sesión OAuth, devvit login, scw init, cambios de facturación)
  • Cambios de DNS en Cloudflare
  • Detener/iniciar el bot de producción en PythonAnywhere
  • git push a repos remotos que afectan a producción
  • Publicar la App de Devvit (devvit upload --publish)

Claude prepara los comandos exactos y le dice al desarrollador cuándo ejecutarlos. Leer código, escribir código, ejecutar pruebas locales, builds locales de Docker, cargo/npm local — Claude hace todo eso.


Contexto del proyecto

Qué es AmputatorBot: Un bot de Reddit que detecta URLs AMP en comentarios/publicaciones/mensajes privados y responde con el enlace canónico (no AMP). Mantenido por el desarrollador (u/Killed_Mufasa). Vive en https://github.com/jvdburgh/AmputatorBot. ~181 estrellas de GitHub. Funciona a escala. El programa de migración de la Plataforma de Desarrolladores de Reddit ofrece $1,000 a los bots que migren de la Data API a Devvit antes del 31 de diciembre de 2026.

Qué se migra:

  • Bot de Reddit (Python + PRAW) → App Devvit (TypeScript, @devvit/web)
  • API HTTP (Flask) → Rust (Axum + puerto de Mozilla-Readability + sqlx)
  • Sitio web (Flask + Bootstrap) → Astro 5 + Tailwind 4 + shadcn/ui
  • Base de datos (MySQL en PythonAnywhere) → Postgres 17 en Scaleway Managed Database
  • Alojamiento (PythonAnywhere) → Scaleway (París, Francia — nube gestionada de origen UE)

Cambio en el modelo arquitectónico: Devvit es opt-in por subreddit, no global. Los moderadores instalan la app por subreddit; no puede ver contenido donde no está instalada. Las respuestas se publican como identidad de la app por instalación, no como u/AmputatorBot. Aceptado. Caída estimada en el volumen de respuestas: 30–60%.

Objetivos (declarados por el usuario):

  1. Stack moderno, aprender cosas en el camino (Rust, Devvit, Astro/Tailwind4/shadcn)
  2. El contrato API se mantiene retrocompatible
  3. Durante la reescritura (M1–M5), el bot antiguo seguía funcionando en PythonAnywhere para que el nuevo pudiera romperse libremente sin consecuencias visibles para el usuario. El bot antiguo se retira en el corte de M6 — sin ejecución paralela permanente.
  4. Aplastar la reescritura; preservar el código antiguo en praw-python-archive/

Decisiones bloqueadas

RutaOpt-in por subreddit. Corte directo en M6 (bot antiguo mantenido vivo durante el desarrollo de M1–M5 solamente)
SDK Devvit@devvit/web (modelo de servidor moderno)
Config Devvitdevvit.json (formato oficial con $schema)
Andamio Devvitreddit/devvit-template-bare, sin UI de publicaciones/menús
Lenguaje backendRust
Framework backendAxum 0.8 (Tokio + Hyper)
Extracción de artículoscrate dom_smoothie (≈0.17, puerto Rust de Readability que sigue de cerca el readability.js de Mozilla, mantenido activamente a partir de 2026-03)
Análisis HTMLcrate scraper (construido sobre html5ever)
Cliente HTTPreqwest
Driver Postgressqlx (consultas verificadas en tiempo de compilación)
Base de datosPostgres 17 en Scaleway Managed Database (DB DEV-S o un nivel superior, dimensionada para ~2GB + margen de crecimiento). 1.7M filas confirmadas.
Sitio webAstro 5 + Tailwind 4 + shadcn/ui
AlojamientoScaleway Serverless Containers (París/AMS, nube francesa de origen UE)
Servicio únicoUn binario Rust sirve /api/* y contenido estático de Astro via tower-http::ServeDir
ObservabilidadSolo Scaleway Cockpit (registros y métricas integrados) inicialmente
Alojamiento de códigoGitHub (monorepo existente)
DNSCloudflare (existente) — también maneja limitación de velocidad
Auth APINinguna en ninguna ruta — completamente abierto. Cloudflare maneja el abuso.
Limitación de velocidadSolo Cloudflare, sin middleware Rust
DominioÚnico — todo en www.amputatorbot.com
Subreddit de pruebar/test
Herramientas IAClaude Code + Devvit MCP + JetBrains MCP en IntelliJ
Exclusión voluntaria por usuarioEliminada. Los usuarios bloquean la cuenta del bot; la identidad de app por instalación hace que la exclusión basada en bloqueo funcione a nivel de instalación.
Exclusión voluntaria por subredditEliminada (los moderadores desinstalan o desactivan autoReply en la configuración de instalación)
Modmail / MDsNinguno. Sin manejador de modmail entrante, sin modmail de bienvenida, sin MDs — la insignia de "App" de Reddit maneja la divulgación del bot, y la lista del Directorio de Apps de Devvit cubre la incorporación.
Umbral de karmaNinguno
Remitente del Directorio de Appsu/Killed_Mufasa

Por qué estas elecciones

La razón detrás de las decisiones anteriores. Cada una fue una selección deliberada frente a una alternativa creíble.

  • Rust + Axum sobre Node/TS o Python. Binario estático único, sin tiempo de ejecución, sin virtualenv, sin node_modules. Las partes difíciles de Rust (lifetimes, unsafe, genéricos avanzados) realmente no aparecen en este tipo de servicio (fetch HTTP + regex + consulta HTML + escritura DB), así que la curva de aprendizaje es principalmente Result/?, asíncrono/Tokio y macros de sqlx — todo vale la pena saberlo. Axum 0.8 es el framework de consenso en 2026 (Tokio + Hyper + middleware Tower). sqlx proporciona consultas verificadas en tiempo de compilación.

  • dom_smoothie sobre rs-trafilatura y otros puertos de Readability. El bot existente usa newspaper3k + difflib.SequenceMatcher para puntuación de confianza por adivinación y verificación (helpers/article_comparer.py). Para el puerto Rust queremos un extractor alineado con Mozilla-Readability. Los candidatos a mayo de 2026:

    • dom_smoothie 0.17 (último lanzamiento 2026-03, ~40k descargas recientes, repo niklak/dom_smoothie) — sigue de cerca readability.js, dos modos (imitación Mozilla + personalizado), análisis de metadatos JSON-LD + OG, mantenido activamente.
    • readability 0.3 de kumabook (último lanzamiento 2023-12, estancado) — puerto del Readability original de arc90 (anterior a la bifurcación de Mozilla). Mayor número histórico de descargas pero sin mantenimiento.
    • readable-readability 0.4 (último lanzamiento 2022-12, estancado).
    • llm_readability 0.0.17 (activo pero 0.0.x — demasiado temprano para producción).

    dom_smoothie gana por mantenimiento activo + linaje explícito de readability.js de Mozilla. Usamos su modo estándar (imitación Mozilla) para puntuación de similitud. No se necesita spike; fijar a dom_smoothie = "0.17" en M2.

  • Sin autenticación, en ninguna parte. La producción actualmente tiene authorization_required=False en /convert (AmputatorBotCom/main.py:162). Nadie depende de autenticación bearer, y con la exclusión voluntaria eliminada, el backend no tiene rutas privilegiadas en absoluto. Cloudflare maneja el abuso.

  • Limitación de velocidad del lado de Cloudflare. Cloudflare ya está al frente del dominio. Las reglas por IP allí son código Rust cero y configurables sin redeploy.

  • Sin mecanismo de exclusión voluntaria, sin modmail, sin MDs. Los usuarios y moderadores que no quieran el bot pueden bloquear la identidad de la app por instalación o desinstalar la app respectivamente. Eso es suficiente — construir un registro de exclusión separado encima es un mecanismo que no necesitamos. Esto elimina el activador de modmail, el modmail de bienvenida, las rutas privilegiadas con autenticación bearer, la tabla optouts, la caché de exclusión Redis y la ruta heredada de "invocado por MD" de un solo golpe.

  • Modernizar el esquema de DB durante la migración. La herramienta de migración se ejecuta una vez. Limpiar VARCHAR(4000) → TEXT, agregar los índices que faltan en models/table.py y eliminar columnas muertas es barato de hacer una vez y nunca más.

  • Observabilidad solo de registros inicialmente. Scaleway Cockpit (el flujo integrado de registros y métricas) es suficiente para hacer grep cuando surjan problemas. Agregar Sentry/Bugsink solo si el grep de registros se vuelve doloroso.

  • Pasar los métodos existentes de detección AMP + canónicos, pero corregir falsos positivos en su lugar. Los 14 patrones de subcadena (static/static.py) son ruidosos (entidades HTML &, lamp_post, streaming_amplifier). Los 10 métodos canónicos y su orden en helpers/canonical_methods.py están bien ajustados y se mantienen. Las mejoras ocurren en la capa de detección AMP (límites de palabras, alcance a componentes de URL después de la extracción) y en listas blancas de ruta rápida para marcadores de alta señal.

  • Actualizar el markdown de respuesta del bot. Un pase de diseño único en una nueva plantilla, aplicado en la migración. No necesita verse idéntico al bot antiguo.

  • git mv todo lo antiguo a praw-python-archive/ en un solo commit. Aplastar. El bot PRAW antiguo sigue funcionando en PythonAnywhere durante M1–M5 para que el nuevo lado pueda romperse libremente; se detiene en el corte de M6.

  • Seis hitos, sin calendario. Local primero: M1–M5 son todo desarrollo local (andamio, motor canónico, paridad API, Astro, prueba de juego Devvit). M6 es la nube + lanzamiento público (aprovisionamiento de Scaleway, migración de datos de producción, corte de DNS, Directorio de Apps, retiro del bot antiguo). Sin fecha límite externa (la recompensa es diciembre de 2026). El bot antiguo cubre la producción hasta el corte. Aprender Rust + Devvit + Astro es parte del objetivo, así que el ritmo debe coincidir con la comprensión, no con un cronograma.


Arquitectura

┌─────────────────────────────────────────────┐
│ Subreddit X (mod instaló amputatorbot)      │
│   Comentarios / publicaciones               │
└──────────────┬──────────────────────────────┘
               │ Desencadenadores Devvit (HTTP POST a rutas internas)
               ▼
┌─────────────────────────────────────────────┐
│ App Devvit (Node, @devvit/web)              │
│   Se ejecuta en infraestructura de Reddit   │
│                                             │
│   Rutas HTTP internas:                      │
│     /internal/triggers/comment-submit       │
│     /internal/triggers/post-submit          │
│                                             │
│   Redis integrado de Devvit:                │
│     handled:{commentId}  TTL 1h             │
│                                             │
│   Configuración por instalación:            │
│     autoReply, customFooter, killSwitch     │
│                                             │
│   Lista blanca (devvit.json):               │
│     http.domains: ["www.amputatorbot.com"]  │
└──────────────┬──────────────────────────────┘
               │ HTTPS (sin auth):
               │   GET /api/v1/convert?q=...&gac=true&md=3
               ▼
┌─────────────────────────────────────────────┐
│ Cloudflare (DNS + limitación de velocidad + WAF) │
└──────────────┬──────────────────────────────┘
               ▼
┌─────────────────────────────────────────────┐
│ Scaleway (París/AMS, UE)                    │
│ www.amputatorbot.com                        │
│                                             │
│   Backend Rust (Axum 0.8) — binario único   │
│     /api/v1/convert    (contrato existente) │
│     /api/v2/convert    (JSON en camelCase)  │
│     /api/v2/stats      (conteo en caché)   │
│     /api/v2/health                          │
│     fallback → Astro estático (ServeDir)    │
│                                             │
│   Búsqueda canónica (10 métodos, mismo     │
│   orden que la impl. Python):              │
│     REL, CANURL, OG_URL,                   │
│     GOOGLE_MANUAL_REDIRECT, GOOGLE_JS_…,   │
│     BING_ORIGINAL_URL, SCHEMA_MAINENTITY,  │
│     TCO_PAGETITLE, META_REDIRECT,          │
│     GUESS_AND_CHECK (confianza de similitud│
│       de artículo via Mozilla Readability),│
│     DATABASE (búsqueda en caché)           │
│                                             │
│   Postgres 17 (Scaleway Managed Database):  │
│     links                                   │
└─────────────────────────────────────────────┘

Por qué un solo servicio Rust que sirve API + sitio estático: La lista blanca de Devvit es a nivel de dominio. Un solo contenedor Serverless de Scaleway mantiene la historia de despliegue simple. tower-http::ServeDir como fallback son ~5 líneas.


Contrato API

Dos endpoints se envían lado a lado:

  • GET /api/v1/convert — contrato heredado preservado (cadena de consulta snake_case, respuesta JSON snake_case). Solo GET ya que el bot heredado siempre estaba basado en cadena de consulta; POST es solo para v2. Obsoleto para nuevos llamadores pero soportado indefinidamente para que los servicios externos no se rompan.
  • POST /api/v2/convert — contrato moderno: cuerpo JSON en camelCase, respuesta JSON en camelCase. entryType es un campo del cuerpo en lugar de un encabezado. Validación estricta (campos desconocidos → 422). Recomendado para Devvit (M5) + sitio web (M4) + nuevos llamadores externos.

Cada escritura en la caché links registra api_version (1 o 2 — NULL para las filas importadas del bot heredado pre-v7) para que la adopción sea consultable: SELECT api_version, COUNT(*) FROM links GROUP BY 1.

v1 — GET /api/v1/convert (de AmputatorBotCom/main.py:161+)

Parámetros de consulta:

  • q (obligatorio) — URL o texto que contiene URLs
  • gac — adivinación y verificación, por defecto true
  • md — profundidad máxima, por defecto static.MAX_DEPTH (pasar el valor)
  • gc — generar markdown de respuesta en la respuesta, por defecto false
  • r — modo de redirección (303 a canónico en lugar de devolver JSON), por defecto false

Comportamiento de codificación URL — ambos deben funcionar

La API soporta dos llamadores: clientes bien comportados que codifican sus URLs con porcentaje, y humanos que pegan URLs sin codificar en la barra de direcciones del navegador. El contrato v7:

  1. Las URLs codificadas siempre funcionan, independientemente de la posición de q en la lista de parámetros. ?q=https%3A%2F%2F...&gac=true y ?gac=true&q=https%3A%2F%2F... son ambos válidos.
  2. Las URLs sin codificar funcionan cuando q es el último parámetro. ?gac=true&q=https://example.eu/article?id=1 está bien — los parámetros finales de la URL (?id=1) se conservan. q entre otros parámetros conocidos es

…

[truncado para traducción]

Skills relacionados