用 Cloudflare Workers 自動簽到 PTT 並推送到 Telegram
PTT 的鄉民大概都遇過:太久沒登入帳號就被砍。社群早期有 PTTAutoSign 這類工具,跑在 VPS 或 Raspberry Pi 上排程登入。可是為了一個每天只跑一次的小腳本去顧一台機器,實在不太划算。
ptt-autosign-worker 把這件事搬到 Cloudflare Workers 上:每天 02:30 UTC 由 Cron Trigger 觸發,自動登入 PTT、抓登入資訊、丟到 Telegram。不用 VPS,不用 Docker,crontab 也省了,Free plan 就跑得起來。
為什麼是 Cloudflare Workers
原版的 PTTAutoSign 是 Python 寫的,吃 PyPtt 這類函式庫,內部會起一個 terminal emulator 處理 PTT 的 ANSI escape code 和狀態切換。要搬到 Workers 有幾個阻礙:Workers 沒有 Node.js 完整 runtime,nodejs_compat 也包不到 terminal emulator;相依太重的話 cold start 會明顯變慢;而且每次執行有 wall-clock 上限,Free plan 30 秒就要結束。
所以這版乾脆不用 terminal emulator,直接打 PTT 的 WebSocket gateway (wss://ws.ptt.cc/bbs/),靠一個小型的 login state machine 推進流程。整個 Worker 零 runtime 依賴,部署檔很小,cold start 幾乎感覺不到。
它做了什麼
每天排程觸發後,Worker 會對 PTT_ACCOUNTS 中每個帳號執行:
- 連上
wss://ws.ptt.cc/bbs/ - 送出
username,\rpassword\r(後面那個逗號是 PTT 的 UTF-8 協商觸發字元) - 走過 login state machine — 處理重複登入踢人、any-key 提示等
- 進入
T → Q → username抓「登入次數」、「信箱狀況」,並從歡迎橫幅抓「上次登入 IP」 - 送
G\rY\r登出 - 把結果丟到 Telegram
成功的訊息長這樣:
✅ PTT alice 已成功簽到
📆 已登入 1234 天
📫 信箱中已無新信件
🌐 上次登入來源 172.69.194.125
#ptt #20260504
失敗時會帶上 reason,方便排查:
❌ PTT alice 簽到失敗
原因:wrong_password
#ptt #20260504Big5 與 UTF-8 並行解碼
PTT 的編碼是個老問題:使用者可以切 Big5 或 UTF-8 模式,連登入過程中都可能切換。傳統做法是先猜編碼、解錯了再 fallback,但這在串流式的 WebSocket 上特別麻煩,狀態機可能因為解錯字就錯過某個關鍵 prompt。
這版的解法很土砲:直接開兩個 TextDecoder,Big5 跟 UTF-8 並行解碼,state machine 對兩條解碼結果各比對一次,誰先看到 prompt 就誰過。PTT 當下用什麼編碼都能正確推進。
Free Plan 的時間預算
Free plan 的 scheduled handler 有 30 秒 wall-clock 上限。每個帳號的時間預算抓起來大概是 10–17 秒,最壞情況 22 秒。所以 2 個帳號剛好塞進 Free plan,3 個以上比較建議升 Paid plan。
PTT 那邊改了 prompt 文字導致狀態機卡住的話,超過 15 秒會直接 timeout,不會把整個 handler 拖死。
路由
Worker 開了三個路由方便操作和除錯:
| Route | 用途 | 認證 |
|---|---|---|
GET /spike | 開 WS 連到 ws.ptt.cc,把歡迎橫幅原文 dump 出來 | 無 |
GET /run?secret=<RUN_TOKEN> | 手動觸發一次(等同 cron) | RUN_TOKEN |
scheduled() | 每天 02:30 UTC | Cloudflare Cron Trigger |
/spike 在 PTT 改版時特別有用,直接看橫幅原文比較容易判斷 prompt 字串怎麼變。/run 則是部署後的 smoke test 入口:
curl "https://ptt-autosign.<sub>.workers.dev/run?secret=<RUN_TOKEN>" | jq部署
不需要 KV,也不需要 D1,純粹靠 secrets:
npm install
npx wrangler login
npx wrangler deploy
npx wrangler secret put TELEGRAM_BOT_TOKEN
npx wrangler secret put TELEGRAM_CHAT_ID
npx wrangler secret put PTT_ACCOUNTS # JSON: [{"id":"alice","password":"..."}]
npx wrangler secret put RUN_TOKEN # 任意亂數字串
PTT_ACCOUNTS 是 JSON 陣列,一次部署就能幫一整批帳號簽到。
已知的軟肋
幾個 PTT 改版時可能會踩到的點,README 也直接列了出來:
getUser() 用的 regex 登入次數》(\d+) 和 信箱[^》]*》 是根據經典版 user card 推測的,PTT 換 layout 就抓不到。不過抓不到只會讓 Telegram 訊息少幾行,登入本身不會失敗。
lastLoginIp 同樣是 best-effort。第一次登入沒有 IP(PTT 顯示「歡迎您第一次拜訪本站」),那行 🌐 會直接省掉。另外,這裡抓到的 IP 是 PTT 看到的入站 IP,對 Cloudflare Workers 來說會是 CF 的 egress IP(172.69.x.x 那類),不是你家的對外 IP。
Cron 精準度方面,Cloudflare 的 cron 會在指定時間的數秒內觸發,沒有保證準時。在意被偵測規律的話,可以在 scheduled() 裡自己加 jitter。
收尾
把每天跑一次的小工作搬到 Cloudflare Workers 上挺合理:cron trigger、secrets 管理都內建,不用自己顧伺服器,Free plan 足夠。比起再開一台 VPS 或佔用家裡的 Raspberry Pi,維運成本基本上是零。
對 PTTAutoSign 的使用者來說,這個 port 保留了原版的核心行為(登入 → 抓資訊 → 推 Telegram),但所有 runtime 依賴都拿掉了。整個專案只有三個 TypeScript 檔案:index.ts、ptt.ts、telegram.ts。要客製化或加新功能都不難。
專案原始碼:GitHub