从一个念头到自动化链路:Telegram 碎碎念入站记
一天之内打通 Telegram → GitHub → 博客的随手记功能
事情的起因很简单:我想要一个"随手记"的地方。
平时看到什么、想到什么,顺手发一条消息就完事了。但这些零碎的想法散落在聊天记录里或者仅存在与脑海之中,过几天就沉没了。我希望它们能自动汇集到博客的 Notes 页面上——每天的消息归到同一篇,标题就是当天日期,像一份"碎碎念合集"。
需求说起来就一句话,但真正动手做的时候,发现牵扯的东西远比想象中多,不过好在,基于Codex的给力输出,一下午就把基本功能搞定了。
选方案:为什么是 Telegram Bot Webhook
一开始摆在面前的核心问题是:怎么把 Telegram 里的消息送到 GitHub 仓库?
最终选定的方案是 Telegram Bot Webhook。简单说,Webhook 就是"你给我一个网址,有事我主动来敲门"——Telegram 每次收到消息,会主动把内容 POST 到我指定的地址,而不是我反复去轮询"有没有新消息"。
思路是这样的:我建一个 Bot,私聊发文本给它,Telegram 会把消息推送到 Webhook 地址。Webhook 收到消息后,调用 GitHub API 触发一个 repository_dispatch 事件(可以理解为"远程拍一下 GitHub 的肩膀,告诉它该干活了"),然后 GitHub Actions 接手,把消息内容写入当天的 Markdown 文件,提交到仓库。最后靠现有的部署流程自动发布到站点。
整条链路画出来大概是这样的:
Telegram 私聊 → Bot Webhook → Cloudflare Worker → GitHub repository_dispatch → Actions 脚本写入 notes → commit & push → 定时部署到 Pages
看起来环节不少,但每一环的职责都很清晰。Webhook 只做"接收和转发",Actions 脚本只做"写文件",部署还是复用原来的流程。
关于 Cloudflare Workers:一个意外的知识盲区
说来有点惭愧,一开始我甚至不太清楚 Cloudflare Workers 是什么。我的第一反应是:能不能直接用我现有的 GitHub Pages 域名来接 Webhook?
答案当然是不行——GitHub Pages 是纯静态托管,只能返回 HTML/CSS/JS 文件,没法处理 POST 请求和执行业务逻辑。打个比方,静态托管就像一个只会发传单的展位,你递给它一封信(POST 请求),它不知道怎么拆开、怎么处理。所以还是需要一个真正的"后端入口",哪怕它只有几十行代码。
Cloudflare Workers 本质上就是一个托管在云端的小型后端函数——你可以把它想象成一个 24 小时在线的小助手,有人敲门(收到请求)它就按你写好的逻辑去办事,办完就休息,不用你租一台一直开着的服务器。免费额度对我这个场景绰绰有余,部署也就是把代码粘进去点一下的事。唯一需要配置的是五个环境变量:Telegram 的 secret token(用来验证请求确实来自 Telegram)、我的聊天 ID 白名单(只接受我自己发的消息)、GitHub 仓库信息和一个最小权限的 PAT(Personal Access Token,相当于 GitHub 的"临时通行证",只给它读写仓库文件的权限)。
第一个坑:Worker 还是默认模板
所有代码写好、环境变量配好之后,我兴冲冲地给 Bot 发了第一条消息。
Cloudflare 的日志确实收到了请求——但 GitHub Actions 毫无动静。
我去看 Worker 的日志,发现一行非常眼熟的输出:Hello World Worker received a request!。原来我在 Cloudflare 的编辑器里创建 Worker 之后,忘了把默认模板替换成真正的 webhook 代码。它确实收到了 Telegram 的请求,但只是礼貌地回了一句"Hello World",根本没有调用 GitHub API。
把代码替换上去、重新部署之后,这一环就通了。
第二个坑:并发推送导致 non-fast-forward
消息终于能写入仓库了,但新的问题很快出现。
当我短时间内连发几条消息时,GitHub Actions 的 ingest workflow(负责把消息写入文件的自动化流程)会多次并行运行。每个 run 都是拉取 main 分支最新代码、修改文件、提交、推送——但如果前一个 run 已经推送成功,后一个 run 手里的代码就"过时"了,git push 直接被拒绝,报错信息是 non-fast-forward(直译就是"你的版本不是最新的,不能直接往前推")。
解决办法不复杂:在 workflow 里给 push 加了重试逻辑,失败时自动拉取远端最新代码、把自己的改动接在后面(git pull --rebase),然后重新推送,最多重试四次。同时也加了空变更保护——如果这条消息已经被前一个 run 写入了(也就是做了幂等去重,下面会解释),就不做无意义的提交。
第三个坑:时间显示错乱
写入链路稳定之后,我打开网页一看——时间不对。
我发的消息明明是北京时间下午 6 点,页面上却显示成了上午 10 点。原因也不难猜:ingest 脚本在 date 字段里写入了带 +08:00 时区后缀的 ISO 格式(比如 2026-02-20T18:02:51+08:00),但站点的构建服务器在 UTC 时区(比北京时间慢 8 小时),渲染的时候直接按 UTC 解读,6 点减 8 小时就变成了上午 10 点。
第一次修复的思路是"在前端强制按 Asia/Shanghai 时区格式化"。写了几个工具函数,改了列表页、详情页和首页组件。改完确实对了——但我随即意识到一个问题:之前所有手写的文章都用的是不带时区的日期格式(比如 "Jan 17 2026" 或 "2026-01-24"),这套新逻辑会不会影响旧文章的显示?
最终的决策是"以旧规则为准":前端的时间展示逻辑全部恢复原样,ingest 脚本那边改成写入不带时区后缀的格式,和旧文章保持一致。
第四个坑:定时部署不触发
GitHub 有一个防循环机制:由自动化流程(GITHUB_TOKEN)推送的代码变更,不会再触发其他由 push 事件启动的 workflow。这是为了防止"A 触发 B,B 又触发 A"的无限循环。但副作用是:ingest workflow 写完 notes 文件并 push 之后,deploy workflow 压根不知道有新内容,不会自动启动部署。
我先是加了 workflow_run 触发,让 deploy 监听 ingest 完成后自动运行。但这样每条消息都会触发一次完整构建,太频繁了。
后来改成了"双通道"策略:
- 我自己手动 push 到 main → 立即部署
- Bot 自动写入 → 不立即部署,靠每 30 分钟一次的定时任务检查是否有新变更,有就部署,没有就跳过
这个策略本身没问题,但实际跑起来又碰到了新状况:定时任务的"是否需要部署"检查逻辑有 bug。它去查"最近一次成功的 deploy run",结果把 bot push 触发但被 if 条件跳过的 run 也算进去了——那个 run 的 head_sha 恰好就是最新的,于是检查结论永远是"不需要部署"。
修复方式是把并发组按事件类型拆分,同时让 schedule-check 只认"真正执行过构建的成功 run",排除掉被跳过的那些。
一个小插曲:Dependabot 带来的分支爆炸
在处理主线功能的间隙,我顺手给仓库加了 Dependabot 来自动更新依赖——毕竟 npm audit 报了一堆告警,其中 Next.js 还有个 critical。
结果push一波之后发现,仓库多出了十几个 dependabot/... 分支,每个依赖包一个 PR。虽然是正常行为,但对于一个人维护的小项目来说,这些 PR 看着就让人焦虑。
先是改成了分组更新模式(npm 一组、actions 一组),后来想了想,干脆关掉了——对我来说,偶尔手动跑一次 npm update 比每周审核一堆自动 PR 更省心。
回过头来看
这半天下来,从萌生"随手记"的念头到整条链路基本跑通,中间经历了方案评估、代码实现、环境配置、五六轮问题排查和策略调整。真正写代码的时间可能并不算多,大部分精力花在了"让各个环节正确地衔接起来"上。
有几个收获值得记下来:
写入和发布应该解耦。 "解耦"说白了就是把两件事拆开、各管各的。一开始我本能地觉得"消息写进去就应该立刻发布",但实际上对于碎碎念这种内容,延迟几十分钟完全可以接受。把"写入仓库"和"构建发布网站"拆成两个独立的步骤之后,构建频率降下来了,并发冲突也少了。
幂等性不是可选项。 所谓"幂等",就是"同一个操作执行一次和执行一百次,结果都一样"。在这个场景里,Telegram 会重试、Actions 会并发、网络会抖动,同一条消息可能被投递多次。如果 ingest 脚本不按 message_id 做去重——也就是"这条消息已经写过了,再来一次我就跳过"——那同一条碎碎念可能会在页面上出现好几遍。这种"不管来多少次都只生效一次"的防御性设计,在一开始就该想到。
尊重已有的约定。 时区格式的问题本质上就是"新代码没有遵循旧约定"。与其让新功能强推一套新格式,不如让它适配已有的规则,哪怕那个规则不是最"正确"的。
现在这套东西已经能用了:给 Bot 发条消息,等半小时,网站上就能看到。后续要做的事情不多——观察定时部署的稳定性、找时间升级一下 Next.js 版本、可能的话给 ingest 加几条日志方便排查。
总之,它从一个"要是能这样就好了"的念头,变成了一条真正可用的自动化链路。这大概就是折腾的乐趣所在。