Webhook会漏事件?拆解OpenReply评论轮询协调器(Comment Reconciler)的兜底机制
【免费下载链接】openreplyThe open-source Manychat alternative项目地址: https://gitcode.com/gh_mirrors/op/openreply
OpenReply 是一款开源的 Manychat 替代工具,主打 Instagram 评论自动回复与 DM 自动发送。而它内置的评论轮询协调器(Comment Reconciler),正是解决"Webhook 漏事件"这一行业性难题的兜底机制——每 5 分钟主动扫描一次近期评论,把 Webhook 没送到的评论补进自动回复队列,确保每一条关键词评论都不被漏掉。
为什么 Webhook 总会漏掉评论?
如果你做过 Instagram 自动化,多半遇到过这种情况:明明有人评论了"AI",自动化却毫无反应。OpenReply 的 comment-reconciler.ts 文件开头的注释一针见血:
Instagram webhooks arebest-effort(尽力投递),而且有一大类评论永远不会触发Webhook。
具体会漏掉哪些?👇
| 漏报场景 | 说明 |
|---|---|
| "加载更多"折叠区下的评论 | 用户没点开评论区深处的回复,Meta 可能不推送 |
| 非粉丝 / 低信号账号的评论 | Meta 对这类评论的推送优先级极低 |
| 被 Instagram 过滤器拦截的评论 | 垃圾词、敏感词过滤直接吞掉事件 |
| 付费广告(Boost)上的评论 | 广告有独立 media id,Webhook 常常只送到原帖 |
对自动回复业务来说,漏一条评论 = 漏一次商机。光靠 Webhook 这条路,天然不可靠。
Comment Reconciler 是怎么兜底的?
OpenReply 的思路很朴素:别指望推送,定期去拉一遍。核心逻辑就一个"扫"(sweep)动作,运行在 worker/dm-worker.ts 里:
启动 10 秒后先扫一次 → 之后每 5 分钟扫一轮(COMMENT_POLL_INTERVAL_MS 可调)每一轮扫描会遍历所有启用中的自动化活动,逐个检查它的帖子(如果是"任意帖子"模式,则扫描最近 10 篇内容),并只处理同时满足两个条件的评论:
- ✅ 评论命中了活动的关键词(复用与 Webhook 完全相同的 keyword-matcher.ts 匹配逻辑);
- ✅ 账号本人还没回复过这条评论(真实读取评论下的 replies 判断)。
命中后就走与 Webhook 完全相同的process-comment队列,限流、日志、发送行为一模一样——兜底路径不会"另搞一套"。
几个关键的"收窄"设计(都定义在 comment-reconciler.ts):
- 只回看最近 72 小时(
COMMENT_POLL_LOOKBACK_HOURS):更老的评论已超出 Instagram 私聊回复窗口,补了也发不出去; - 单轮每活动最多入队 30 条(
COMMENT_POLL_MAX_PER_SWEEP):爆款帖子被慢慢消化,避免瞬间打爆评论 API(Meta 对 368 限流非常激进); - 按时间从旧到新排序:先评论的读者先被回复,公平且稳定。
四道防线,确保轮询不会"重复轰炸"
轮询兜底最大的风险不是漏,而是重复发送——每 5 分钟扫一次,扫到已经处理过的评论怎么办?OpenReply 用四道防线锁死了这个问题 🔒:
第一道:Instagram 侧的"已回复"检查直接读评论的真实 replies,只要账号本人回过(无论是人工还是工具),这条评论永久跳过。
第二道:数据库侧的 DmLog 状态检查入队前再查一遍 DmLog 表,判断是否"完全处理完"。这里有个很细腻的细节:完成标准取决于活动配置——如果活动开启了公开评论回复,那 DM 发出去还不算完,必须公开回复也落地(publicReplySentAt)才算数。于是"DM 成功但公开回复失败"的评论,下一轮轮询会自动回来重试回复这一腿。
第三道:全局 3 次尝试预算发送尝试次数记录在数据库里,跨 Webhook、重试、轮询所有来源合计最多 3 次。轮询每轮都生成新的队列任务(不能复用固定 jobId,否则会被误判为重复而静默丢弃),但它重置不了这个预算——这是 docs/delivery-retries.md 明确设计的策略。
第四道:原子认领机制Worker 调用 Instagram 之前先用原子更新 DmLog 认领"发送中"状态,网络超时、进程崩溃都不会释放认领,宁可少发、绝不重发。
对应的回归测试在 comment-reconciler.test.ts,覆盖了"不安全的旧评论保持不动、继续处理新评论"等边界场景。
广告评论:最容易被漏掉的"隐形副本"
一个很隐蔽的坑:给帖子开付费推广(Boost)后,帖子会产生第二个 media id。留在广告上的评论,Webhook 带的是广告的 id——如果只扫原帖,这些评论就永远丢了,而这恰恰是评论量最大的场景。
OpenReply 的解法很巧妙:不去调额外的广告 API(那要多申请一个权限),而是从已收到的 Webhook 事件里反查广告 id(adMediaFor 函数)。只要广告上来过一条评论,它就进入扫描范围;查询失败也绝不影响原帖的正常扫描。
为什么放在 Worker 里跑,而不是定时任务?
很多项目的"兜底轮询"会丢给 Vercel Cron,但 dm-worker.ts 的注释解释了原因:Vercel 免费档的 Cron 一天只触发一次,完全兜不住"每 5 分钟"的需求。所以 OpenReply 把轮询放进常驻的 DM Worker 进程,和消息消费共用同一个进程,顺便共享限流与日志体系。
每轮扫描还会把结果(入队数、命中数、已回复数、错误)写入运营事件表,只在"有动作或出错误"时记录,方便你在诊断页回溯兜底机制到底救回了多少评论。
相关源码与配置一览
| 模块 | 路径 | 职责 |
|---|---|---|
| 轮询协调器核心 | lib/polling/comment-reconciler.ts | 扫描、过滤、去重、入队 |
| Worker 调度 | worker/dm-worker.ts | 5 分钟定时触发 + 心跳 |
| Webhook 主链路 | lib/queue/process-webhook.ts | 评论事件入队(兜底的对立面) |
| 重试与去重策略 | docs/delivery-retries.md | 3 次预算、原子认领说明 |
| 回归测试 | tests/comment-reconciler.test.ts | 防重复发送场景验证 |
| 表结构迁移 | 20260722220418_add_comment_reconciliation | ProcessedComment 与回复状态字段 |
可调参数速查
| 环境变量 | 默认值 | 含义 |
|---|---|---|
COMMENT_POLL_INTERVAL_MS | 5 分钟 | 两轮扫描的间隔 |
COMMENT_POLL_LOOKBACK_HOURS | 72 小时 | 只回看多新的评论 |
COMMENT_POLL_MAX_PER_SWEEP | 30 条 | 单轮单活动最大入队数 |
一点已知边界 📌
代码注释里也坦率地写明了:被 Instagram"隐藏词/垃圾过滤"彻底过滤掉的评论,Graph API 可能根本不返回——这类评论轮询同样救不回来,需要在 Instagram 账号设置里关闭相应过滤器来扩大覆盖范围。
总结:OpenReply 的 Comment Reconciler 用"窄范围扫描 + 双重已处理检查 + 全局尝试预算 + 原子认领"四层设计,把不可靠的 Webhook 补成了一条不漏评论的可靠链路——这几乎是所有做评论自动回复工具都该抄的兜底作业。
【免费下载链接】openreplyThe open-source Manychat alternative项目地址: https://gitcode.com/gh_mirrors/op/openreply
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考