博客 Telegram 频道自动化推送实战记录

2026-01-24

为了方便读者及时获取更新,近期我为博客增加了 Telegram 频道自动推送功能。目标很明确:每当有新文章发布时,GitHub Actions 自动触发脚本,将文章标题、摘要和链接发送到订阅频道,并且支持 Instant View(即时预览)以提供丝滑的阅读体验。

本文整合了整个开发过程中的实战经验,特别是解决“时序竞争”和“Instant View 适配”等棘手问题的方案。

1. 基础架构设计

整个自动化链路的基础逻辑如下:

  1. Git 检测:在 CI/CD 流程中检测本次 Commit 新增的 Markdown 文件。
  2. 内容解析:读取文件 Frontmatter,提取标题、日期、标签和摘要。
  3. API 推送:调用 Telegram Bot API 将消息发送到指定频道。

1.1 申请机器人与频道配置

这是最基础的一步:

  1. 在 Telegram 找 @BotFather 申请 Bot,获取 API Token
  2. 建立频道(Channel),将 Bot 拉入并设为管理员。
  3. 通过转发消息给 @getidsbot 获取频道的 Chat ID(通常以 -100 开头)。

1.2 自动化脚本逻辑

使用 Node.js 编写脚本 (scripts/telegram-notify.js)。核心难点在于准确识别“新文章”。最初我只检测 Added (A) 状态的文件,后来发现重命名文件(如修正文件名拼写)会导致漏推。

解决方案:将 git diff 的过滤器升级为 ACR,覆盖 Add、Copy、Rename 三种情况。

// 示例:获取变更文件列表
let command = 'git diff --name-only --diff-filter=ACR HEAD~1 HEAD';

2. 核心挑战与踩坑记录

虽然原理简单,但在实际落地过程中遇到了不少意想不到的坑。

2.1 时序陷阱:通知发了,预览却挂了

现象: 消息成功发送到了频道,但无论是在 App 端还是 Web 端,都没有显示文章的预览卡片(Preview Card)。

原因分析: 这是一个典型的**竞争条件(Race Condition)**问题。 最初的工作流顺序是:构建 -> 发布 -> 通知。 看似合理,但 GitHub Pages 的部署(Deploy)步骤完成后,CDN 的缓存刷新和全球分发需要时间。当脚本立即触发 Telegram Bot 发送消息时,Telegram 的爬虫服务器(Crawler)立刻去抓取该链接,此时新文章在 CDN 节点上可能还未生效(返回 404)。一旦爬虫抓取失败,它就会缓存这个失败结果,导致预览卡片无法生成。

解决方案: 调整 GitHub Actions 的执行顺序,并强制增加等待时间。

  1. 顺序调整:确保 通知 步骤严格在 部署 步骤成功之后执行。
  2. 增加延迟:在两者之间插入 sleep 60,给 GitHub Pages 的 CDN 留出足够的传播时间。
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        # ... 配置省略 ...

      - name: Wait for deployment propagation
        run: sleep 60  # 关键:等待 CDN 生效

      - name: Check for new content and notify
        if: success()
        run: node scripts/telegram-notify.js

2.2 Instant View 的“严苛”规范

为了提升阅读体验,我配置了 Telegram Instant View。但在测试规则时报错:Element <img> is not supported in <p>

原因: Hugo 或其他 Markdown 渲染器通常会将图片包裹在 <p> 标签中(例如 <p><img ...></p>)。然而,Telegram Instant View 的规范非常严格,不允许块级的 <img> 元素出现在 <p> 内部。

解决方案: 在 IV 模板规则中使用 @split_parent 指令,强制将图片从段落中“剥离”出来。

# Fix: <img> inside <p> is not allowed in Instant View
@split_parent: //p/img

此外,对于博客中嵌入的复杂 HTML 组件(如交互式图表、Hugo Shortcodes),Instant View 无法渲染,直接展示会显得凌乱。因此需要专门制定规则将其移除:

# Remove embedded HTML content and Hugo cards
@remove: //div[contains(@class, "embedded-html-card")]
@remove: //iframe

2.3 Web 端与 App 端的体验割裂

现象: 生成的 Instant View 链接 (t.me/iv?url=...) 在 Telegram App 上点击顺滑,但在 Telegram Web 端点击时往往无反应或报错,体验极差。

解决方案: 利用 Telegram Bot API 的 link_preview_options 功能。 我们在调用 API 发送消息时,显式指定用于生成预览卡片的 URL(即 IV 链接),而在消息正文的 [阅读原文] 按钮中保留博客的原始 URL。

这样实现了完美兼容:

  • App 用户:看到带有 Instant View 按钮的精美卡片,点击即读。
  • Web 用户:预览卡片可能退化为普通链接,但点击正文链接依然能直接跳转博客原站。

3. 最终工作流总结

经过多次迭代,目前的发布流程已经非常稳健:

  1. 写作:本地撰写 Markdown 文章。
  2. 推送git push 触发 GitHub Actions。
  3. 构建部署:Hugo 构建站点并推送到 gh-pages 分支。
  4. 等待生效:CI 流程自动暂停 60 秒。
  5. 智能通知:脚本检测到新文件 -> 生成标题/摘要/标签 -> 通过 Bot API 发送消息(携带 Instant View 配置)。
  6. 触达:频道订阅者收到推送,秒开阅读。

舒服了,继续搬砖。