用 Codex 的小伙伴想必都知道 Tibo,OpenAI Codex 的负责人。这哥们隔三差五就会重置所有人的 Codex 使用额度,堪称赛博大善人:

但 Tibo 的重置时间完全不固定:
- 有时候一周来好几次,有时候一等就是一两个月
- 有时候先预告,过一段时间才发放
- 有时候直接发一张可以随时使用的重置卡
所以我经常是额度快用完了,舍不得用,结果第二天一看,昨晚已经重置过了,白白浪费了一波旧额度。要么就是天天去刷 Tibo 的推,看他今天心情好不好🐶。
其实也有一些现成的监控站,比如 Codex Resets,支持 Telegram 和邮件通知。但对我来说不太方便,我还是更希望可以直接推到微信上。
所以我就实现了这么一个小工具:Codex 重置,让它帮我盯着 Codex 的公开重置信息,然后马上通过微信通知我:


下面具体说说这个小工具的实现思路。
Codex 重置是什么
先简单解释一下这里的「重置」到底指什么,因为 Codex 里跟重置有关的东西有好几种:
- 全员重置:Tibo 在 X 上宣布后,所有付费账号的额度自动回满,不用做任何操作
- 发重置卡:往你账户里存一次随时可用的重置次数
- 个人每周重置:账号自己的固定周期,每个人时间不一样
前两种是 Tibo 公开发的,大家都一样,这个工具监控的就是这两种:

功能长什么样
这个工具的页面很简单,上面是当前状态:

下面是几项统计,比如最近一次重置是几天前、总共重置了多少次、平均间隔、最长等待:

再往下是三个按钮:
- 开启提醒:订阅微信通知,有新状态就推送
- 分享给好友:分享出去的标题也会跟着状态变,比如「Tibo 已经重置」
- 加入日历:这个按钮只有 Tibo 预告了明确时间才会出现,到点前 10 分钟提醒

数据来源
数据我没有自己去爬 X,而是直接用了 Codex Resets 的公开 API,免费且不需要 key,这里主要用到两个接口:
| 接口 |
用途 |
GET /api/v1/status |
当前状态,包括最近一次重置、已预告的重置、次数、平均间隔 |
GET /api/v1/resets |
历史重置列表,用来计算「最长等待」 |
后端轮询 + 缓存
Tibo 的重置本来就是低频的,没必要每次有人打开小程序就去请求一次上游。
所以我的做法是:后端每 2 分钟轮询一次 Codex Resets,带上 ETag,上游没变化就直接返回 304:
const headers: Record<string, string> = { Accept: 'application/json' }
if (etag) headers['If-None-Match'] = etag
const response = await fetch(config.tiboReset.statusUrl, { headers })
if (response.status === 304) return { status: 304, payload: {}, etag }
就算返回了 200,内容和上次一样也不写库。只有数据真的发生了变化,才会更新快照,并且触发提醒推送。
小程序这边只请求我自己的 /api/tibo-reset,读的是后端存好的快照。而且会先用本地缓存把页面渲染出来,接口数据回来之后,有变化才刷新。

状态怎么判断
拿到上游数据后,我会把它归成四种状态,规则很简单:
if (scheduled) {
// 有预告:banked 是发重置卡,regular 是即将重置
kind = scheduled.reset_type === 'banked' ? 'card' : 'announced'
} else if (latest && now - Date.parse(latest.announced_at) <= 36 * 3600 * 1000) {
// 最近 36 小时内有过重置
kind = latest.reset_type === 'banked' ? 'card' : 'reset'
} else {
kind = 'waiting'
}
这里有两个关键点:
- 上游的
reset_type 有 regular 和 banked 两种,banked 就是重置卡
- 「已经重置」只保留 36 小时,过了就回到「暂无新预告」,不然首页会一直挂着一个过期的状态

微信订阅消息提醒
提醒用的是微信小程序的订阅消息。
后台选模板
操作步骤如下:
- 登录微信公众平台,进入「功能 → 订阅消息」

- 打开「公共模板库」,搜一个合适的模板,比如「待办事项提醒」

- 挑字段,我这里用了名称 + 时间 + 内容

这里要注意,字段的 key 一定以模板详情页为准,比如我选的这张模板,字段是 thing54、time11、thing1:

模板 ID 放在后端
模板 ID 和字段 key 我都放在后端环境变量里:
TIBO_REMIND_ANNOUNCED_TMPL=模板ID
TIBO_REMIND_RESET_TMPL=模板ID
TIBO_REMIND_CARD_TMPL=模板ID
TIBO_REMIND_FIELD_TITLE=thing54
TIBO_REMIND_FIELD_TIME=time11
TIBO_REMIND_FIELD_HINT=thing1
小程序点「开启提醒」的时候,再从 /api/tibo-reset 接口把模板 ID 拉下来,然后调起订阅弹窗:
wx.requestSubscribeMessage({
tmplIds: ids,
success: (res) => {
resolve(ids.filter((id) => res[id] === 'accept'))
},
fail: () => resolve([]),
})
这样做的好处是,换模板、改字段只需要改后端配置重启一下,小程序不用重新提审。

结语
整个工具的核心逻辑并不算复杂:后端定时轮询上游、更新快照、触发订阅消息推送,小程序端负责展示状态并申请订阅授权。这类轻量化的自动化监控方案在 云栈社区 也有不少讨论,感兴趣的可以去看看。