AmputatorBot → Devvit 遷移計畫 v7
狀態: 進行中草稿 — M1–M5 已交付,M6(雲端部署 + 上線)進行中。 起草日期: 2026-05-25 預算上限: 每月 €15
工作風格 — 請先閱讀此項
Dev 希望在做架構決策前先被諮詢。對於任何以下類型的選擇:
- 關於要使用哪個函式庫、框架或模式
- 關於資料模型設計或綱要決策
- 關於如何組織新的模組或層
- 關於成本與複雜度的權衡
- 關於偏離本計畫的任何非微調方式
…請先停下來詢問再繼續。列出選項,誠實說明權衡(如果你不同意,也請提出反對意見),然後等待決策。機械性的實作工作不需要諮詢;但架構性的形塑需要。
Dev 手動執行、Claude 不負責的外部動作:
- 對正式環境的 MySQL / Postgres 資料庫執行查詢(Claude 可以撰寫 SQL;Dev 執行)
- 註冊服務(Scaleway、Reddit 開發者帳戶、App Directory 提交、獎金計畫註冊)
- 任何涉及 Dev 身分的外部帳戶操作(OAuth 登入、
devvit login、scw init、帳單變更) - Cloudflare 的 DNS 變更
- 停止/啟動正式環境的 PythonAnywhere 機器人
git push到影響正式環境的遠端儲存庫- 發布 Devvit 應用程式(
devvit upload --publish)
Claude 會準備好確切的指令,並在適當時機告知 Dev 執行。讀取程式碼、撰寫程式碼、執行本地測試、本地 Docker 建置、本地 cargo/npm — 這些由 Claude 負責。
專案背景
AmputatorBot 是什麼: 一個 Reddit 機器人,能夠在留言/貼文/私訊中偵測 AMP URL,並回覆標準(非 AMP)連結。由 Dev(u/Killed_Mufasa)維護。程式碼位於 https://github.com/jvdburgh/AmputatorBot。約 181 個 GitHub 星星。大規模運作。Reddit 的開發者平台遷移計畫提供 $1,000 獎金,給在 2026 年 12 月 31 日前從 Data API 遷移到 Devvit 的機器人。
正在遷移的項目:
- Reddit 機器人(Python + PRAW)→ Devvit 應用程式(TypeScript,
@devvit/web) - HTTP API(Flask)→ Rust(Axum + Mozilla-Readability-port + 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(DB DEV-S 同等或上一級,容量約 2GB + 成長空間)。已確認 170 萬行。 |
| 網站 | Astro 5 + Tailwind 4 + shadcn/ui |
| 主機 | Scaleway Serverless Containers(巴黎/阿姆斯特丹,歐盟境內法國雲端) |
| 單一服務 | 一個 Rust 二進位檔同時提供 /api/* 和 Astro 靜態檔案(透過 tower-http::ServeDir) |
| 可觀測性 | 初期僅使用 Scaleway Cockpit(內建日誌 + 指標) |
| 程式碼託管 | GitHub(現有單一儲存庫) |
| DNS | Cloudflare(現有)— 也負責速率限制 |
| API 驗證 | 所有路由皆無 — 完全開放。Cloudflare 處理濫用。 |
| 速率限制 | 僅 Cloudflare,無 Rust 中介軟體 |
| 網域 | 單一 — 全部在 www.amputatorbot.com 上 |
| 測試子版塊 | r/test |
| AI 工具 | Claude Code + Devvit MCP + JetBrains MCP(在 IntelliJ 中) |
| 個別用戶選擇退出 | 已放棄。用戶封鎖機器人帳戶;每個安裝的應用程式身分讓基於封鎖的選擇退出在安裝層級生效。 |
| 每個子版塊選擇退出 | 已放棄(版主解除安裝,或關閉安裝設定中的 autoReply) |
| 版主郵件 / 私訊 | 無。無入站版主郵件處理、無歡迎版主郵件、無私訊 — Reddit 的「應用程式」徽章負責機器人揭露,Devvit App Directory 清單涵蓋入門引導。 |
| Karma 門檻 | 無 |
| App Directory 提交者 | 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 由 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)。10 個標準方法及其在helpers/canonical_methods.py中的順序經過良好調整,將予以保留。改進將發生在 AMP 偵測層(單詞邊界、萃取後限縮到 URL 元件)以及高訊號標記的快速路徑白名單中。 -
重新整理機器人回覆的 markdown。 一次性設計新模板,在遷移時套用。不需要看起來與舊機器人完全相同。
-
在一次提交中將所有舊內容
git mv到praw-python-archive/。 全面處理。舊的 PRAW 機器人在 M1–M5 期間繼續在 PythonAnywhere 上運作,以便新版可以自由破壞;它將在 M6 切換時停止。 -
六個里程碑,沒有時間表。 以本地為先:M1–M5 全部是本地開發(初始化、標準引擎、API 相容性、Astro、Devvit 遊玩測試)。M6 是雲端 + 公開發布(Scaleway 準備、正式資料遷移、DNS 切換、App Directory、舊機器人退役)。沒有外部截止日期(獎金截止日期為 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 (駝峰式 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— 保留舊版合約(蛇形查詢字串,蛇形 JSON 回應)。僅 GET,因為舊機器人一直是基於查詢字串;POST 僅用於 v2。對新呼叫者棄用,但無限期支援,以免外部服務中斷。POST /api/v2/convert— 現代化合約:JSON 請求主體使用駝峰式,JSON 回應使用駝峰式。entryType是主體欄位而非標頭。嚴格的驗證(未知欄位 → 422)。推薦用於 Devvit (M5) + 網站 (M4) + 新的外部呼叫者。
每次對 links 快取的寫入都會記錄 api_version(1 或 2 — 對於 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 重新導向到標準連結,而非回傳 JSON),預設false
URL 編碼行為 — 兩者都必須能運作
API 支援兩種呼叫者:行為良好的客戶端會對其 URL 進行百分比編碼,以及直接將原始 URL 貼入瀏覽器網址列的人類。v7 合約:
- 編碼後的 URL 始終有效,無論
q在參數列表中的位置。?q=https%3A%2F%2F...&gac=true和?gac=true&q=https%3A%2F%2F...都是有效的。 - 當
q是最後一個參數時,未編碼的 URL 有效。?gac=true&q=https://example.eu/article?id=1是沒問題的 — URL 的尾部參數(?id=1)會被保留。q位於其他已知參數之間時
…
[為翻譯而截斷]