AmputatorBot → Devvit 迁移计划 v7
状态: 活文档 — M1–M5 已交付,M6(云部署 + 发布)进行中。 起草日期: 2026-05-25 预算上限: 每月 15 欧元
工作风格 — 请先阅读
开发者希望在编写代码之前就架构决策征求意见。对于任何涉及以下方面的选择:
- 库、框架或模式的选择
- 数据模型设计或架构决策
- 如何构建新模块或层
- 成本与复杂性的权衡
- 以任何非平凡的方式偏离本计划
…请在继续之前暂停并询问。列出选项,诚实地说明权衡(包括不同意见),并等待决策。机械性的实现工作不需要咨询;架构形态需要。
手动操作(由开发者执行,而非 Claude):
- 针对生产环境 MySQL / Postgres 数据库运行查询(Claude 可以编写 SQL;开发者执行)
- 注册服务(Scaleway、Reddit 开发者账户、App Directory 提交、赏金计划注册)
- 任何涉及开发者身份的外部账户操作(OAuth 登录、
devvit login、scw init、账单变更) - Cloudflare 上的 DNS 变更
- 启动/停止生产环境 PythonAnywhere 机器人
git push到影响生产的远程仓库- 发布 Devvit 应用(
devvit upload --publish)
Claude 准备确切的命令并告知开发者何时运行它们。读取代码、编写代码、运行本地测试、本地 Docker 构建、本地 cargo/npm — 这些由 Claude 完成。
项目背景
AmputatorBot 是什么: 一个 Reddit 机器人,用于检测评论/帖子/私信中的 AMP URL,并以规范(非 AMP)链接回复。由开发者(u/Killed_Mufasa)维护。仓库地址:https://github.com/jvdburgh/AmputatorBot。约 181 个 GitHub 星标。大规模运行。Reddit 开发者平台迁移计划为在 2026 年 12 月 31 日前从数据 API 迁移到 Devvit 的机器人提供 1000 美元奖励。
正在迁移的内容:
- Reddit 机器人(Python + PRAW)→ Devvit 应用(TypeScript,
@devvit/web) - HTTP API(Flask)→ Rust(Axum + Mozilla-Readability 移植 + sqlx)
- 网站(Flask + Bootstrap)→ Astro 5 + Tailwind 4 + shadcn/ui
- 数据库(PythonAnywhere 上的 MySQL)→ Scaleway 托管数据库上的 Postgres 17
- 托管(PythonAnywhere)→ Scaleway(法国巴黎 — 欧盟起源的托管云)
架构模型变更: Devvit 是按子版块选择加入的,而非全局。版主需要为每个子版块安装应用;应用无法看到未安装区域的内容。回复以按安装的应用身份发布,而非 u/AmputatorBot。已接受。预计回复量下降 30–60%。
目标(用户声明):
- 现代化技术栈,学习新知识(Rust、Devvit、Astro/Tailwind4/shadcn)
- API 契约保持向后兼容
- 重写期间(M1–M5),旧机器人继续在 PythonAnywhere 上运行,以便新端可以随意破坏,不对用户造成影响。旧机器人在 M6 切换时退役 — 不进行永久并行运行。
- 快速完成重写;将旧代码保留在
praw-python-archive/中
已锁定的决策
| 路径 | 按子版块选择加入。M6 时直接切换(开发 M1–M5 期间保留旧机器人) |
| Devvit SDK | @devvit/web(现代服务器模型) |
| Devvit 配置 | devvit.json(官方格式,包含 $schema) |
| Devvit 脚手架 | reddit/devvit-template-bare,移除帖子/菜单 UI |
| 后端语言 | Rust |
| 后端框架 | Axum 0.8(Tokio + Hyper) |
| 文章提取 | dom_smoothie crate(约 0.17,Rust Readability 移植,紧密跟踪 Mozilla 的 readability.js,截至 2026-03 仍在积极维护) |
| HTML 解析 | scraper crate(基于 html5ever) |
| HTTP 客户端 | reqwest |
| Postgres 驱动 | sqlx(编译时检查的查询) |
| 数据库 | Scaleway 托管数据库上的 Postgres 17(DEV-S 等效或更高一级,大小约 2GB + 增长空间)。确认有 170 万行。 |
| 网站 | Astro 5 + Tailwind 4 + shadcn/ui |
| 托管 | Scaleway Serverless Containers(巴黎/阿姆斯特丹,欧盟起源的法国云) |
| 单一服务 | 一个 Rust 二进制文件通过 tower-http::ServeDir 同时服务 /api/* 和 Astro 静态文件 |
| 可观测性 | 仅初始使用 Scaleway Cockpit(内置日志 + 指标) |
| 代码托管 | GitHub(现有单体仓库) |
| DNS | Cloudflare(现有)— 也处理速率限制 |
| API 认证 | 所有路由均无认证 — 完全开放。Cloudflare 处理滥用。 |
| 速率限制 | 仅 Cloudflare,无 Rust 中间件 |
| 域名 | 单一 — 全部位于 www.amputatorbot.com |
| 测试子版块 | r/test |
| AI 工具 | Claude Code + Devvit MCP + IntelliJ 中的 JetBrains MCP |
| 按用户选择退出 | 已移除。用户屏蔽机器人账户;按安装的应用身份使基于屏蔽的选择退出在安装级别生效。 |
| 按子版块选择退出 | 已移除(版主卸载,或在安装设置中切换 autoReply 为关闭) |
| 版主邮件 / 私信 | 无。没有入站版主邮件处理器,没有欢迎版主邮件,没有私信 — Reddit 的“应用”徽章处理机器人披露,Devvit 应用目录列表涵盖使用入门。 |
| 业力阈值 | 无 |
| 应用目录提交人 | u/Killed_Mufasa |
为什么选择这些方案
上述决策背后的理由。每一个都是在可信的替代方案中深思熟虑的选择。
-
选择 Rust + Axum 而非 Node/TS 或 Python。 单个静态二进制文件,无运行时,无 virtualenv,无
node_modules。Rust 的难点(生命周期、unsafe、高级泛型)在这种服务中(HTTP 获取 + 正则 + HTML 查询 + 数据库写入)基本不会出现,因此学习曲线主要是Result/?、async/Tokio 和 sqlx 宏 — 都值得学习。Axum 0.8 是 2026 年的共识框架(Tokio + Hyper + Tower 中间件)。sqlx 提供编译时检查的查询。 -
选择
dom_smoothie而非rs-trafilatura和其他 Readability 移植版本。 现有机器人使用newspaper3k+difflib.SequenceMatcher进行猜测和检查的置信度评分(helpers/article_comparer.py)。对于 Rust 移植版,我们想要一个与 Mozilla Readability 对齐的提取器。截至 2026 年 5 月的候选方案:dom_smoothie0.17(最后发布 2026-03,最近下载量约 4 万,仓库niklak/dom_smoothie)— 紧密跟随readability.js,两种模式(Mozilla 模仿 + 自定义),JSON-LD + OG 元数据解析,积极维护。readability0.3 by kumabook(最后发布 2023-12,停滞)— 移植了 arc90 的原始 Readability(早于 Mozilla 的分支)。历史下载量更高但已无人维护。readable-readability0.4(最后发布 2022-12,停滞)。llm_readability0.0.17(活跃但仍处于 0.0.x 阶段 — 对于生产环境来说太早期)。
dom_smoothie在积极维护和明确的 Mozillareadability.js血统上胜出。我们使用其标准(Mozilla 模仿)模式进行相似度评分。无需 spike;在 M2 中锁定为dom_smoothie = "0.17"。 -
无认证,无处可寻。 生产环境当前在
/convert上设置authorization_required=False(AmputatorBotCom/main.py:162)。没有人依赖 bearer 认证,并且随着选择退出机制的移除,后端根本没有特权路由。Cloudflare 处理滥用。 -
Cloudflare 端速率限制。 Cloudflare 已经前置了域名。那里的按 IP 规则是零 Rust 代码,并且无需重新部署即可配置。
-
无选择退出机制、无版主邮件、无私信。 不希望看到机器人的用户和版主可以分别屏蔽按安装的应用身份或卸载应用。这已经足够了 — 在上面构建一个单独的选择退出注册表是我们不需要的机制。一次性砍掉了版主邮件触发器、欢迎版主邮件、bearer 认证特权路由、
optouts表、Redis 选择退出缓存以及通过私信召唤的遗留路径。 -
在迁移期间现代化数据库架构。 迁移工具运行一次。清理
VARCHAR(4000)→TEXT,添加models/table.py缺少的索引,并删除任何死列 — 一次性完成,成本低廉,且无需再次进行。 -
初始仅日志的可观测性。 Scaleway Cockpit(内置日志 + 指标流)足以在问题出现时进行 grep。仅在日志 grep 变得痛苦时添加 Sentry/Bugsink。
-
移植现有 AMP 检测 + 规范方法,但原地修复误报。 14 个子字符串模式(
static/static.py)是有噪声的(&HTML 实体、lamp_post、streaming_amplifier)。helpers/canonical_methods.py中的 10 种规范方法及其顺序已经过良好调优,保持不变。改进发生在 AMP 检测层(词边界、提取后限定到 URL 组件)以及高信号标记的快速通配白名单中。 -
刷新机器人回复 Markdown。 一次性为新模板进行设计审核,在迁移时应用。不必看起来与旧机器人完全相同。
-
将旧内容通过
git mv一次提交到praw-python-archive/。 快速执行。旧的 PRAW 机器人在 M1–M5 期间继续在 PythonAnywhere 上运行,以便新端可以自由破坏;它在 M6 切换时停止。 -
六个里程碑,无日历。 本地优先:M1–M5 都是本地开发(脚手架、规范引擎、API 对等、Astro、Devvit 测试)。M6 是云 + 公开发布(Scaleway 配置、生产数据迁移、DNS 切换、应用目录、旧机器人退役)。没有外部截止日期(赏金截至 2026 年 12 月)。旧机器人在切换前覆盖生产环境。学习 Rust + Devvit + Astro 是目标的一部分,因此节奏应与理解程度相匹配,而非按时间表。
架构
┌─────────────────────────────────────────────┐
│ 子版块 X(版主安装了 amputatorbot) │
│ 评论 / 帖子 │
└──────────────┬──────────────────────────────┘
│ Devvit 触发器(HTTP POST 到内部路由)
▼
┌─────────────────────────────────────────────┐
│ Devvit 应用 (Node, @devvit/web) │
│ 运行在 Reddit 基础设施上 │
│ │
│ 内部 HTTP 路由: │
│ /internal/triggers/comment-submit │
│ /internal/triggers/post-submit │
│ │
│ Devvit Redis(内置): │
│ handled:{commentId} TTL 1h │
│ │
│ 按安装设置: │
│ autoReply, customFooter, killSwitch │
│ │
│ 白名单 (devvit.json): │
│ http.domains: ["www.amputatorbot.com"] │
└──────────────┬──────────────────────────────┘
│ HTTPS(无认证):
│ GET /api/v1/convert?q=...&gac=true&md=3
▼
┌─────────────────────────────────────────────┐
│ Cloudflare (DNS + 速率限制 + WAF) │
└──────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ Scaleway (巴黎/阿姆斯特丹, 欧盟) │
│ www.amputatorbot.com │
│ │
│ Rust 后端 (Axum 0.8) — 单个二进制文件 │
│ /api/v1/convert (现有契约) │
│ /api/v2/convert (camelCase JSON) │
│ /api/v2/stats (缓存计数) │
│ /api/v2/health │
│ 回退 → Astro 静态文件 (ServeDir) │
│ │
│ 规范发现 (10 种方法,顺序与 │
│ Python 实现相同): │
│ REL, CANURL, OG_URL, │
│ GOOGLE_MANUAL_REDIRECT, GOOGLE_JS_…, │
│ BING_ORIGINAL_URL, SCHEMA_MAINENTITY, │
│ TCO_PAGETITLE, META_REDIRECT, │
│ GUESS_AND_CHECK (文章相似度 │
│ 置信度通过 Mozilla Readability), │
│ DATABASE (缓存查找) │
│ │
│ Postgres 17 (Scaleway 托管数据库): │
│ links │
└─────────────────────────────────────────────┘
为什么采用单一 Rust 服务同时服务 API 和静态网站: Devvit 的白名单是域名级别的。一个 Scaleway Serverless Container 使得部署过程简单。tower-http::ServeDir 作为回退大约只需 5 行代码。
API 契约
两个端点并行发布:
GET /api/v1/convert— 保留遗留契约(snake_case 查询字符串,snake_case JSON 响应)。仅支持 GET,因为遗留机器人始终基于查询字符串;POST 仅用于 v2。对于新调用者已弃用,但无限期支持以保持外部服务不中断。POST /api/v2/convert— 现代契约:JSON 请求体使用 camelCase,JSON 响应使用 camelCase。entryType是请求体字段而非标头。严格验证(未知字段 → 422)。推荐用于 Devvit (M5) + 网站 (M4) + 新的外部调用者。
每次写入 links 缓存都会记录 api_version(1 或 2 — 对于 pre-v7 遗留机器人 CSV 导入的行,为 NULL),以便可以查询采用率:SELECT api_version, COUNT(*) FROM links GROUP BY 1。
v1 — GET /api/v1/convert(来自 AmputatorBotCom/main.py:161+)
查询参数:
q(必填)— 包含 URL 的 URL 或文本gac— 猜测和检查,默认为truemd— 最大深度,默认为static.MAX_DEPTH(移植该值)gc— 在响应中生成回复 Markdown,默认为falser— 重定向模式(303 重定向到规范 URL 而非返回 JSON),默认为false
URL 编码行为 — 两种都必须工作
API 支持两类调用者:表现良好的客户端(对 URL 进行百分比编码)以及将原始 URL 粘贴到浏览器地址栏的人类用户。v7 契约:
- 编码后的 URL 始终有效,无论
q在参数列表中的位置如何。?q=https%3A%2F%2F...&gac=true和?gac=true&q=https%3A%2F%2F...都有效。 - 未编码的 URL 仅在
q是最后一个参数时有效。?gac=true&q=https://example.eu/article?id=1可以正常工作 — URL 的尾部参数(?id=1)会被保留。q位于其他已知参数之间时…
[为翻译而截断]