01. 系列主题

Habitica 是一个游戏化的 GTD 工具,支持组队打怪,我计划开发一个适合小白用户部署的开源 Web 应用,让用户可以自动接受任务。

02. 本期目标

上期我已经完成了应用的搭建和 webhook 的安装,只等待新任务触发 webhook 了。

下期的任务就比较简单了,等待下一次任务邀请,获取 webhook 请求的具体信息,然后做自动接受任务的处理,就可以完成这个小工具的开发了。

https://anl.gg/post/308/

Image

但是当收到新任务邀请,我去查看日志时,却发现 Vercel 免费只支持查看最近一个小时的日志。

Image

而我们团队 Black Knights of Justice 的团长 Urazi 是个老外,他的时区和我不同,我早上醒来看到任务,已经是好几个小时之后了。

贫穷限制了哥的能力。

Image

使用 Vercel 看不到日志,那只能另外想办法了。

03 寻找新方法

现在我能想到两条路可以走。

1. 重新找一个可以临时接 webhook 并支持查看较长时间日志的服务

像 Google Apps Script 就可以满足,不过编写略麻烦,还得部署什么的。也可以使用一些 Request Bin 之类的临时服务,比如 Post Bin ( postb.in ), 甚至都不需要注册。

不过这又得等好几天,因为当前任务的 webhook 已经发过了。

2. 不依赖 webhook 返回任务详情,直接接受任务

其实 Habitica 提供了获取当前任务信息的 API,只要收到 webhook, 就可以直接去处理后续了,不需要依赖 webhook 的 request body.

甚至接受任务的 API 本身,也不需要任务相关的 ID,仅需要提供团队的固有名称 party 即可。

Image

而接受任务的请求也会返回任务详情,如果需要做后续的邮件/通知之类的操作,可以直接使用。

Image

04. 实现自动接受任务

之前已经实现过 webhook 的请求,仅会发送任务邀请的 webhook,可以在这个请求里面实现自动接受任务。

虽然几乎用不了 Vercel 的日志,但还是记录下日志比较好。

收到 webhook 时,调用 habitica api 自动接受任务。

API 参考
https://apidoc.habitica.com/#api-Quest-AcceptQuest

返回的 Quest Object 参考
https://github.com/HabitRPG/habitica/blob/develop/website/server/models/group.js#L99-L110

使用 log 记录调用 api 的信息。

经过 AI 一番折腾,发现结果里面它还是处理了 webhook 的 request body, 这不符合我的计划。

    // Parse the webhook event
    var event QuestWebhookEvent
    if err := json.Unmarshal(body, &event); err != nil {
        fmt.Printf("[Webhook Event] Failed to parse JSON body: %v\n", err)
        c.JSON(http.StatusOK, gin.H{"success": true})
        return
    }
    fmt.Printf("[Webhook Event] Parsed event: type=%s, group=%s (%s), quest=%s\n",
        event.Type, event.Group.Name, event.Group.ID, event.Quest.Key)
    // Only handle questInvited events
    if event.Type != "questInvited" {
        fmt.Printf("[Webhook Event] Ignoring event type: %s\n", event.Type)
        c.JSON(http.StatusOK, gin.H{"success": true})
        return
    }

追加修改,简化请求中的处理逻辑。

webhook 中直接调用接受任务的 API 即可,没有必要解析 webhook 请求做类型检查
我们注册 webhook 时,已经限定了只接受 questInvited, 移除不必要的处理。

groupId 固定使用 party 即可。

AI 经常会做额外的处理,比如判断 webhook 类型是否匹配,反而会增加复杂度,时不时需要限制一下他的手脚,让它不要没苦硬吃。

05. 通知方案

日志不靠谱,我又不想升级 Vercel 的套餐,那么要用什么方法通知呢?

Vercel 市场里面,倒是有邮件通知相关的服务,但是需要配置 API KEY, 自用倒是方便,但是想让小白用户也能轻松安装的话,会抬高不少成本,这个方案不能用

Image

https://vercel.com/marketplace/resend

传统方法不合适,再看看 Habitica 自己的方式。

看到一个给群组发消息的接口,但是这个消息会发到小队消息里面,会对团队成员过度打扰,感觉也不是很合适。

POST https://habitica.com/api/v3/groups/:groupId/chat

翻了翻 Habitica 网站,发现站内信可以用,而且似乎我之前安装过的一个自动接受任务的脚本,也是用的这个通道,那就选它了。

Image

Image

在文档里面找到了这个接口,接下来补充修改。

任务接受成功之后,给自己发一条站内信,内容为 "Quest Accepted." 

API 参考
https://apidoc.habitica.com/#api-Member-SendPrivateMessage

推送代码后,Vercel 会自动部署,是挺省事儿的。

Image

Image

06. 测试

漫长的等待后,终于团长发布了新任务,但是我的自动接受任务的 webhook 并没有生效,任务依然是未接受状态。

Image

从 Vercel 的日志看,的确能够看到一条 2xx 的请求,应该就是 Webhook 了,没有报错,但是因为时长超过了一小时,我也看不到日志。只能本地调试看看了。

Image

收到新的任务邀请了,prod 环境的确有收到 webhook, 状态为 2xx 但是任务并没有自动接受。
本地使用 curl 触发一下这个 webhook 请求,我们调试看看是什么原因。
我已经启动了 devserver, 可以通过 http://localhost:3000/ 访问。

本地触发后,我发现竟然成功了。

Image

本地的 API 日志也显示了相关的信息,这个我就抓瞎了。只能给 AI 说明情况,提供日志,继续分析了。

Image

07. 问题排查

我看到本地日志中包含下面的内容。
我查看 habitica  发现任务已经自动接受了。
分析一下,为什么本地成功了。但 prod 环境没有。

[GIN] 2026/06/17 - 22:04:16 | 200 | 0s | ::1 | POST "/api/webhook/event"
[Accept Quest] Response status: 200, body: {"success":true, ... }
[Accept Quest] Quest accepted successfully!
[Send PM] Sending private message: ...
[Send PM] Response status: 200, body: ...
[Send PM] Private message sent successfully!

AI 似乎还挺靠谱的

问题分析

本地成功了,但生产环境(Vercel)失败的原因很清楚:

关键代码 — handleWebhookEvent:

func handleWebhookEvent(c *gin.Context) {
    fmt.Printf("[Webhook Event] Received quest invitation, accepting...\n")
    go acceptQuest()  // ← 问题在这里!
    c.JSON(http.StatusOK, gin.H{"success": true})
}

go acceptQuest() 将任务接受操作放在了goroutine 中异步执行,然后立即返回 200。

这在本地长期运行的服务中没问题,goroutine 有充足的时间完成。但在 Vercel Serverless 环境下:

  1. 请求处理函数返回 200 响应后,Vercel 会立即冻结/终止该实例
  2. 后台 goroutine 还没来得及执行完就被强制终止了
  3. 所以 Habitica 收到了 2xx 响应,但实际上 acceptQuest() 根本没来得及调用 Habitica API

修复方案

将 acceptQuest() 改为同步执行,确保在返回响应之前任务已经接受完成。

提交修改,重新部署,只能等下次任务再进一步测试了。

08. 两次测试

经过了几天,团长终于又开团了,这次稳稳地成功了,自动接受了任务,也收到了站内信通知。

Image

至此,这个基于 Vercel 的自动接受任务的 webhook 应用就最终完成。还有一些收尾的工作,比如 README 更新,指导用户安装,不过这就不在主要功能开发过程里面了,不作赘述。

09. 总结

这个功能并不复杂,之所以 Vibe Coding 这么长时间,主要在于测试上面,因为依赖 Habitica 团长开团来触发 Webhook 请求,每次调整和测试,都需要等待好几天,不过也算完整的记录了我的开发思路。

开发工具使用的是 Opencode 和 DeepSeek V4 Flash

代码开源在 Github, 可以直接点击 README 中的 Deploy 按钮安装。

https://github.com/greatghoul/HabitiQuest


文章同步发表于微信公众号老狗拾光,欢迎关注。

微信公众号老狗拾光