Communitygithub.com

jvdburgh/AmputatorBot

Plano de migração v7 do AmputatorBot — decisões fixas, marcos M1–M6, contrato da API, esquema, notas de portabilidade. Carregue ao trabalhar na arquitetura do AmputatorBot, na reescrita ou na implantação M6.

O que é AmputatorBot?

Este documento descreve o plano de migração v7 do AmputatorBot, um bot do Reddit que detecta URLs AMP e responde com links canônicos. A migração move o bot de uma pilha Python (PRAW + Flask + MySQL) para uma arquitetura moderna com Devvit (TypeScript), Rust (Axum), Astro 5 + Tailwind 4 + shadcn/ui, e Postgres 17 hospedado na Scaleway. O plano está dividido em seis marcos (M1–M6), com M1–M5 focados no desenvolvimento local (scaffolding, mecanismo canônico, paridade de API, site e testes no Devvit) e M6 na implantação em nuvem e lançamento público. Decisões importantes incluem: opt-in por subreddit, serviço único Rust que serve tanto a API quanto o site estático, uso da crate `dom_smoothie` para extração de artigos (porta do Mozilla Readability), ausência de autenticação (Cloudflare gerencia abuso), e remoção de mecanismos de opt-out em favor de bloqueio/desinstalação. O bot antigo permanece ativo durante M1–M5 e é desligado no corte M6.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Plano de Migração AmputatorBot → Devvit v7

Status: rascunho vivo — M1–M5 implantados, M6 (implantação em nuvem + lançamento) em andamento. Rascunhado: 2026-05-25 Teto de orçamento: €15/mês total


Estilo de trabalho — LEIA ISTO PRIMEIRO

O Dev gosta de ser consultado sobre decisões arquiteturais antes que o código seja escrito. Para qualquer escolha que seja:

  • Sobre qual biblioteca, framework ou padrão usar
  • Sobre design de modelo de dados ou decisões de esquema
  • Sobre como estruturar um novo módulo ou camada
  • Sobre tradeoffs de custo vs. complexidade
  • Sobre desviar deste plano de forma não trivial

…pare e pergunte antes de prosseguir. Apresente as opções, explique os tradeoffs honestamente (incluindo objeções se você discordar) e aguarde uma decisão. O trabalho de implementação mecânica não precisa de consulta; a forma arquitetural sim.

Ações externas que o Dev faz manualmente, não o Claude:

  • Executar consultas nos bancos de dados MySQL / Postgres de produção (o Claude pode escrever o SQL; o Dev executa)
  • Inscrever-se em serviços (Scaleway, conta de desenvolvedor Reddit, submissão ao App Directory, registro em programa de recompensas)
  • Qualquer coisa que acesse uma conta externa com a identidade do Dev (logins OAuth, devvit login, scw init, alterações de faturamento)
  • Alterações de DNS no Cloudflare
  • Parar/iniciar o bot de produção no PythonAnywhere
  • git push para repositórios que afetam a produção
  • Publicar o aplicativo Devvit (devvit upload --publish)

O Claude prepara os comandos exatos e diz ao Dev quando executá-los. Ler código, escrever código, executar testes locais, builds Docker locais, cargo/npm locais — o Claude faz esses.


Contexto do projeto

O que é o AmputatorBot: Um bot do Reddit que detecta URLs AMP em comentários/postagens/mensagens privadas e responde com o link canônico (não-AMP). Mantido pelo Dev (u/Killed_Mufasa). Ativo em https://github.com/jvdburgh/AmputatorBot. ~181 estrelas no GitHub. Opera em escala. O programa de migração da Plataforma de Desenvolvimento do Reddit oferece $1.000 para bots que migrarem da Data API para o Devvit até 31 de dezembro de 2026.

O que está sendo migrado:

  • Bot do Reddit (Python + PRAW) → Aplicativo Devvit (TypeScript, @devvit/web)
  • API HTTP (Flask) → Rust (Axum + porta do Mozilla-Readability + sqlx)
  • Site (Flask + Bootstrap) → Astro 5 + Tailwind 4 + shadcn/ui
  • Banco de dados (MySQL no PythonAnywhere) → Postgres 17 no Scaleway Managed Database
  • Hospedagem (PythonAnywhere) → Scaleway (Paris, França — nuvem gerenciada de origem UE)

Mudança no modelo arquitetural: Devvit é opt-in por subreddit, não global. Mods instalam o aplicativo por sub; ele não pode ver conteúdo onde não está instalado. As respostas são postadas como identidade do aplicativo por instalação, não como u/AmputatorBot. Aceito. Queda estimada no volume de respostas: 30–60%.

Objetivos (declarados pelo usuário):

  1. Pilha moderna, aprender coisas no caminho (Rust, Devvit, Astro/Tailwind4/shadcn)
  2. Contrato da API permanece compatível com versões anteriores
  3. Durante a reescrita (M1–M5), o bot antigo continuou rodando no PythonAnywhere para que o novo lado pudesse quebrar livremente sem consequências visíveis ao usuário. O bot antigo é aposentado no corte M6 — nenhuma execução paralela permanente.
  4. Marretada na reescrita; preservar código antigo em praw-python-archive/

Decisões fixas

CaminhoOpt-in por subreddit. Corte direto no M6 (bot antigo mantido vivo durante desenvolvimento M1–M5 apenas)
SDK Devvit@devvit/web (modelo de servidor moderno)
Config Devvitdevvit.json (formato oficial com $schema)
Estrutura Devvitreddit/devvit-template-bare, removido UI de post/menu
Linguagem do backendRust
Framework do backendAxum 0.8 (Tokio + Hyper)
Extração de artigosCrate dom_smoothie (≈0.17, porta Rust do Readability que segue de perto o readability.js da Mozilla, mantida ativamente em 2026-03)
Análise HTMLCrate scraper (construída sobre html5ever)
Cliente HTTPreqwest
Driver Postgressqlx (consultas verificadas em tempo de compilação)
Banco de dadosPostgres 17 no Scaleway Managed Database (DB DEV-S ou nível superior, dimensionado para ~2GB + margem de crescimento). 1,7M linhas confirmadas.
SiteAstro 5 + Tailwind 4 + shadcn/ui
HospedagemScaleway Serverless Containers (Paris/AMS, nuvem francesa de origem UE)
Serviço únicoUm binário Rust serve /api/* e estáticos Astro via tower-http::ServeDir
ObservabilidadeApenas Scaleway Cockpit (logs + métricas integrados) inicialmente
Hospedagem de códigoGitHub (monorepo existente)
DNSCloudflare (existente) — também gerencia limite de taxa
Autenticação da APINenhuma em qualquer rota — totalmente aberta. Cloudflare gerencia abuso.
Limite de taxaApenas Cloudflare, sem middleware Rust
DomínioÚnico — tudo em www.amputatorbot.com
Subreddit de tester/test
Ferramentas de IAClaude Code + Devvit MCP + JetBrains MCP no IntelliJ
Opt-out por usuárioRemovido. Usuários bloqueiam a conta do bot; a identidade do aplicativo por instalação faz o opt-out baseado em bloqueio funcionar no nível da instalação.
Opt-out por subredditRemovido (mods desinstalam ou desativam autoReply nas configurações de instalação)
Modmail / DMsNenhum. Nenhum manipulador de modmail de entrada, nenhum modmail de boas-vindas, nenhum DM — o selo "App" do Reddit lida com divulgação do bot, e a listagem no Devvit App Directory cobre a integração.
Limiar de karmaNenhum
Submissor do App Directoryu/Killed_Mufasa

Por que essas escolhas

A justificativa por trás das decisões acima. Cada uma foi uma escolha deliberada em relação a uma alternativa viável.

  • Rust + Axum em vez de Node/TS ou Python. Binário estático único, sem runtime, sem virtualenv, sem node_modules. As partes difíceis do Rust (lifetimes, unsafe, genéricos avançados) realmente não aparecem nesse tipo de serviço (fetch HTTP + regex + consulta HTML + escrita em BD), então a curva de aprendizado é principalmente Result/?, async/Tokio e macros sqlx — tudo que vale a pena saber. Axum 0.8 é o framework consenso em 2026 (Tokio + Hyper + middleware Tower). sqlx fornece consultas verificadas em tempo de compilação.

  • dom_smoothie em vez de rs-trafilatura e outras portas do Readability. O bot existente usa newspaper3k + difflib.SequenceMatcher para pontuação de confiança de adivinhar-e-verificar (helpers/article_comparer.py). Para a porta Rust, queremos um extrator alinhado com o Mozilla-Readability. Os candidatos em maio de 2026:

    • dom_smoothie 0.17 (último lançamento 2026-03, ~40k downloads recentes, repositório niklak/dom_smoothie) — segue de perto readability.js, dois modos (imitação Mozilla + personalizado), análise de metadados JSON-LD + OG, mantido ativamente.
    • readability 0.3 por kumabook (último lançamento 2023-12, estagnado) — porta do Readability original da arc90 (anterior ao fork da Mozilla). Maior contagem histórica de downloads, mas não mantido.
    • readable-readability 0.4 (último lançamento 2022-12, estagnado).
    • llm_readability 0.0.17 (ativo, mas 0.0.x — muito inicial para produção).

    dom_smoothie vence por manutenção ativa + linhagem explícita do readability.js da Mozilla. Usamos seu modo padrão (imitação Mozilla) para pontuação de similaridade. Nenhum spike necessário; fixar em dom_smoothie = "0.17" no M2.

  • Sem autenticação, em lugar nenhum. A produção atualmente tem authorization_required=False em /convert (AmputatorBotCom/main.py:162). Ninguém depende de autenticação bearer, e com o opt-out removido, o backend não tem rotas privilegiadas. Cloudflare gerencia abuso.

  • Limite de taxa no lado do Cloudflare. O Cloudflare já está na frente do domínio. Regras por IP lá são zero código Rust e configuráveis sem reimplantação.

  • Sem mecanismo de opt-out, sem modmail, sem DMs. Usuários e mods que não querem o bot podem bloquear a identidade do aplicativo por instalação ou desinstalar o aplicativo, respectivamente. Isso é suficiente — construir um registro de opt-out separado em cima é um mecanismo que não precisamos. Corta o gatilho de modmail, o modmail de boas-vindas, as rotas privilegiadas de autenticação bearer, a tabela optouts, o cache Redis de opt-out e o caminho legado "convocado via DM" em um só golpe.

  • Modernizar o esquema do BD durante a migração. A ferramenta de migração é executada uma vez. Limpar VARCHAR(4000) → TEXT, adicionar os índices que models/table.py não tem e remover colunas mortas é barato de fazer uma vez e nunca mais.

  • Observabilidade apenas logs inicialmente. O Scaleway Cockpit (o fluxo de log + métrica integrado) é suficiente para grepar quando problemas aparecem. Adicione Sentry/Bugsink apenas se grepar logs se tornar doloroso.

  • Portar os métodos existentes de detecção AMP + canônicos, mas corrigir falsos positivos no lugar. Os 14 padrões de substring (static/static.py) são ruidosos (entidades HTML &, lamp_post, streaming_amplifier). Os 10 métodos canônicos e sua ordem em helpers/canonical_methods.py são bem ajustados e permanecem. As melhorias ocorrem na camada de detecção AMP (limites de palavra, escopo para componentes de URL após extração) e em listas de permissão de caminho rápido para marcadores de alto sinal.

  • Atualizar o markdown de resposta do bot. Uma passagem de design única em um novo modelo, aplicado na migração. Não precisa parecer idêntico ao bot antigo.

  • git mv tudo que é antigo para praw-python-archive/ em um commit. Marretada. O bot PRAW antigo continua rodando no PythonAnywhere durante M1–M5 para que o novo lado possa quebrar livremente; ele é parado no corte M6.

  • Seis marcos, sem calendário. Local primeiro: M1–M5 são todo desenvolvimento local (scaffolding, mecanismo canônico, paridade de API, Astro, playtest Devvit). M6 é a nuvem + lançamento público (provisionamento Scaleway, migração de dados de produção, corte de DNS, App Directory, aposentadoria do bot antigo). Nenhum prazo externo (recompensa é dezembro de 2026). O bot antigo cobre a produção até o corte. Aprender Rust + Devvit + Astro é parte do objetivo, então o ritmo deve corresponder à compreensão, não a um cronograma.


Arquitetura

┌─────────────────────────────────────────────┐
│ Subreddit X (mod instalou amputatorbot)     │
│   Comentários / postagens                   │
└──────────────┬──────────────────────────────┘
               │ Gatilhos Devvit (HTTP POST para rotas internas)
               ▼
┌─────────────────────────────────────────────┐
│ Aplicativo Devvit (Node, @devvit/web)       │
│   Executa na infraestrutura do Reddit       │
│                                             │
│   Rotas HTTP internas:                      │
│     /internal/triggers/comment-submit       │
│     /internal/triggers/post-submit          │
│                                             │
│   Redis Devvit (integrado):                 │
│     handled:{commentId}  TTL 1h            │
│                                             │
│   Configurações por instalação:             │
│     autoReply, customFooter, killSwitch     │
│                                             │
│   Lista de permissão (devvit.json):         │
│     http.domains: ["www.amputatorbot.com"] │
└──────────────┬──────────────────────────────┘
               │ HTTPS (sem autenticação):
               │   GET /api/v1/convert?q=...&gac=true&md=3
               ▼
┌─────────────────────────────────────────────┐
│ Cloudflare (DNS + limite de taxa + WAF)     │
└──────────────┬──────────────────────────────┘
               ▼
┌─────────────────────────────────────────────┐
│ Scaleway (Paris/AMS, UE)                    │
│ www.amputatorbot.com                        │
│                                             │
│   Backend Rust (Axum 0.8) — binário único   │
│     /api/v1/convert    (contrato existente) │
│     /api/v2/convert    (JSON camelCase)     │
│     /api/v2/stats      (contagem em cache)  │
│     /api/v2/health                          │
│     fallback → Astro estático (ServeDir)    │
│                                             │
│   Localização canônica (10 métodos, mesma   │
│   ordem da implementação Python):           │
│     REL, CANURL, OG_URL,                    │
│     GOOGLE_MANUAL_REDIRECT, GOOGLE_JS_…,    │
│     BING_ORIGINAL_URL, SCHEMA_MAINENTITY,   │
│     TCO_PAGETITLE, META_REDIRECT,           │
│     GUESS_AND_CHECK (confiança de           │
│       similaridade de artigo via Mozilla    │
│       Readability),                         │
│     DATABASE (consulta em cache)            │
│                                             │
│   Postgres 17 (Scaleway Managed Database):  │
│     links                                   │
└─────────────────────────────────────────────┘

Por que serviço Rust único servindo API + site estático: A lista de permissão do Devvit é em nível de domínio. Um contêiner Scaleway Serverless mantém a história de implantação simples. tower-http::ServeDir como fallback é ~5 linhas.


Contrato da API

Dois endpoints rodam lado a lado:

  • GET /api/v1/convert — contrato legado preservado (string de consulta snake_case, resposta JSON snake_case). Apenas GET já que o bot legado sempre foi baseado em string de consulta; POST é apenas v2. Depreciado para novos chamadores, mas suportado indefinidamente para que serviços externos não quebrem.
  • POST /api/v2/convert — contrato moderno: corpo JSON em camelCase, resposta JSON em camelCase. entryType é um campo do corpo em vez de um cabeçalho. Validação estrita (campos desconhecidos → 422). Recomendado para Devvit (M5) + site (M4) + novos chamadores externos.

Cada escrita no cache links registra api_version (1 ou 2 — NULL para as linhas importadas do CSV do bot legado pré-v7) para que a adoção seja consultável: SELECT api_version, COUNT(*) FROM links GROUP BY 1.

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

Parâmetros de consulta:

  • q (obrigatório) — URL ou texto contendo URLs
  • gac — adivinhar-e-verificar, padrão true
  • md — profundidade máxima, padrão static.MAX_DEPTH (portar o valor)
  • gc — gerar markdown de resposta na resposta, padrão false
  • r — modo de redirecionamento (303 para canônico em vez de retornar JSON), padrão false

Comportamento de codificação de URL — ambos devem funcionar

A API suporta dois tipos de chamadores: clientes bem-comportados que codificam percentualmente suas URLs, e humanos que colam URLs brutas na barra de endereço do navegador. O contrato v7:

  1. URLs codificadas sempre funcionam, independentemente da posição de q na lista de parâmetros. ?q=https%3A%2F%2F...&gac=true e ?gac=true&q=https%3A%2F%2F... são ambos válidos.
  2. URLs não codificadas funcionam quando q é o último parâmetro. ?gac=true&q=https://example.eu/article?id=1 é válido — os parâmetros finais da URL (?id=1) são preservados. q entre outros parâmetros conhecidos é

…

[truncado para tradução]

Habilidades Relacionadas