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 pushpara 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):
- Pilha moderna, aprender coisas no caminho (Rust, Devvit, Astro/Tailwind4/shadcn)
- Contrato da API permanece compatível com versões anteriores
- 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.
- Marretada na reescrita; preservar código antigo em
praw-python-archive/
Decisões fixas
| Caminho | Opt-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 Devvit | devvit.json (formato oficial com $schema) |
| Estrutura Devvit | reddit/devvit-template-bare, removido UI de post/menu |
| Linguagem do backend | Rust |
| Framework do backend | Axum 0.8 (Tokio + Hyper) |
| Extração de artigos | Crate dom_smoothie (≈0.17, porta Rust do Readability que segue de perto o readability.js da Mozilla, mantida ativamente em 2026-03) |
| Análise HTML | Crate scraper (construída sobre html5ever) |
| Cliente HTTP | reqwest |
| Driver Postgres | sqlx (consultas verificadas em tempo de compilação) |
| Banco de dados | Postgres 17 no Scaleway Managed Database (DB DEV-S ou nível superior, dimensionado para ~2GB + margem de crescimento). 1,7M linhas confirmadas. |
| Site | Astro 5 + Tailwind 4 + shadcn/ui |
| Hospedagem | Scaleway Serverless Containers (Paris/AMS, nuvem francesa de origem UE) |
| Serviço único | Um binário Rust serve /api/* e estáticos Astro via tower-http::ServeDir |
| Observabilidade | Apenas Scaleway Cockpit (logs + métricas integrados) inicialmente |
| Hospedagem de código | GitHub (monorepo existente) |
| DNS | Cloudflare (existente) — também gerencia limite de taxa |
| Autenticação da API | Nenhuma em qualquer rota — totalmente aberta. Cloudflare gerencia abuso. |
| Limite de taxa | Apenas Cloudflare, sem middleware Rust |
| Domínio | Único — tudo em www.amputatorbot.com |
| Subreddit de teste | r/test |
| Ferramentas de IA | Claude Code + Devvit MCP + JetBrains MCP no IntelliJ |
| Opt-out por usuário | Removido. 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 subreddit | Removido (mods desinstalam ou desativam autoReply nas configurações de instalação) |
| Modmail / DMs | Nenhum. 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 karma | Nenhum |
| Submissor do App Directory | u/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 é principalmenteResult/?, 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_smoothieem vez ders-trafilaturae outras portas do Readability. O bot existente usanewspaper3k+difflib.SequenceMatcherpara 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_smoothie0.17 (último lançamento 2026-03, ~40k downloads recentes, repositórioniklak/dom_smoothie) — segue de pertoreadability.js, dois modos (imitação Mozilla + personalizado), análise de metadados JSON-LD + OG, mantido ativamente.readability0.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-readability0.4 (último lançamento 2022-12, estagnado).llm_readability0.0.17 (ativo, mas 0.0.x — muito inicial para produção).
dom_smoothievence por manutenção ativa + linhagem explícita doreadability.jsda Mozilla. Usamos seu modo padrão (imitação Mozilla) para pontuação de similaridade. Nenhum spike necessário; fixar emdom_smoothie = "0.17"no M2. -
Sem autenticação, em lugar nenhum. A produção atualmente tem
authorization_required=Falseem/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 quemodels/table.pynã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 emhelpers/canonical_methods.pysã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 mvtudo que é antigo parapraw-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 URLsgac— adivinhar-e-verificar, padrãotruemd— profundidade máxima, padrãostatic.MAX_DEPTH(portar o valor)gc— gerar markdown de resposta na resposta, padrãofalser— modo de redirecionamento (303 para canônico em vez de retornar JSON), padrãofalse
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:
- URLs codificadas sempre funcionam, independentemente da posição de
qna lista de parâmetros.?q=https%3A%2F%2F...&gac=truee?gac=true&q=https%3A%2F%2F...são ambos válidos. - 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.qentre outros parâmetros conhecidos é
…
[truncado para tradução]