1932 字
10 分钟
Balance Island v0.9.1 开发日志:把 ChatGPT/Codex 套餐余量接进浮岛

Balance Island v0.9.1 的目标很具体:不用频繁打开其他应用,也能在状态栏浮岛里看到 ChatGPT/Codex 套餐的剩余额度与下次重置时间。

看似只是多显示一组数字,实际却牵涉非公开接口、WebView 登录态、代理、额度窗口语义、浮岛轮播模型和通知去重。下面记录这次从问题定位到正式发布的完整过程。

从 HTTP 401 开始#

最初的实现让用户在应用内 WebView 登录 ChatGPT,再直接携带同源 Cookie 请求套餐用量接口。但实际测试中,ChatGPT 页面已经登录,读取请求仍然返回 HTTP 401。

原因是当前接口不能只依靠 Cookie。最终采用的流程是:

  1. chatgpt.com 页面上下文中请求同源 session。
  2. 只在 JavaScript 局部变量中读取临时访问令牌。
  3. 立即使用该令牌请求套餐用量接口。
  4. 在结果返回 Android 原生层之前,只保留套餐类型、剩余百分比、窗口时长、重置时间和更新时间。

访问令牌不会进入 SharedPreferences、日志、错误提示或浮岛文本,也不会通过原生接口暴露给网页。这一调整恢复了读取,同时尽量维持原有的安全边界。

WebView 白屏与应用内代理#

在模拟器环境中,WebView 直连可能长时间白屏,用户只能猜测是页面问题、网络问题还是实现问题。v0.9.1 为实验页面补上了完整的加载状态:

  • 页面加载进度;
  • 25 秒超时;
  • 网络、HTTP、SSL 和渲染进程错误提示;
  • 可见的重试入口;
  • WebView 渲染进程退出后的恢复方式。

同时加入了默认关闭的应用内 HTTP 代理。代理通过 AndroidX WebKit 的进程级 WebView override 实现,只影响 Balance Island 的实验 WebView,不修改设备系统代理,也不影响 API Provider 的网络请求。

代理并不等于绕过安全检查。SSL 证书错误仍然直接拒绝,界面也明确提醒:代理可以看到连接目标;如果设备安装了代理 CA,代理还可能解密登录会话。因此这里只应该使用自己信任的代理。

不再把 primary_window 猜成 5 小时#

另一个容易踩坑的地方是接口字段名。早期界面把 primary_window 固定解释为 5 小时额度,但实际响应中它也可能代表每周额度。

新版本改为读取 limit_window_seconds

  • 约 4–6 小时识别为“5 小时”;
  • 约 6–8 天识别为“周”;
  • 缺失或未知时长统一显示为中性的“额度”。

这种做法不依赖字段顺序,也不会在接口语义不明确时给出看似精确、实际错误的名称。

自动更新必须有明确边界#

套餐读取使用的是等同密码的登录会话,而且依赖非公开接口,因此自动更新没有被设计成默认后台常驻任务。

v0.9.1 提供了一个默认关闭、需要单独风险确认的开关。启用后:

  • 只在实验页面保持打开时工作;
  • 数据过期时先读取一次,此后每 5 分钟读取;
  • 手动读取和自动读取共用单飞锁,避免并发请求;
  • HTTP 401、403 或 429 会暂停当前页面会话中的自动更新;
  • 网络或解析失败保留最后一次成功数据;
  • 断开、重载或销毁页面后,迟到的回调会被丢弃。

“断开并清除”还会停止自动更新、使在途结果失效,并清理 Cookie、WebStorage、缓存、历史、表单、SSL 状态和本地套餐数据。

把套餐余量变成浮岛中的独立条目#

原有浮岛只认识 API 账户快照。为了让套餐余量在没有 API Key 的情况下也能显示,新版本把它建模为一个带 OpenAI 图标的独立显示条目,而不是伪装成 API 账户。

“浮岛显示 → 显示账户”现在支持三种套餐编排方式:

  • 自动轮播:全部已配置 API 账户与套餐余量共同轮换;
  • 自定义滚动分组:API 与套餐可以任意组合,也可以只保留套餐;
  • 固定 ChatGPT/Codex 套餐余量:只显示套餐信息。

浮岛使用紧凑格式,例如:

周 94% / 6天11时

读取成功、切换模式或分组、清除套餐数据时,运行中的浮岛会收到应用内广播并立即重绘。对于套餐数据失效、自定义分组变空、仅套餐无 API 账户等边界情况,也增加了自动回退和防空选择处理。

“重置次数”其实不是一个可读取字段#

套餐接口提供下一次重置时间或剩余秒数,但没有可信的历史累计重置次数。为了不伪造数据,浮岛展示的是本地计算的重置倒计时。

重置通知同样基于可验证的数据:

  1. 上一次保存的重置时间已经到达;
  2. 本次读取的重置时间明显向后推进;
  3. 推进幅度至少达到 15 分钟,用来过滤时间抖动和服务端微调。

只有同时满足这些条件,才会认为额度进入了新周期并发送一次通知。保存的新时间会成为下一次比较基准,因此同一周期不会重复提醒。

通知默认关闭,启用时会申请系统通知权限。它复用手动读取或页面内自动读取的结果,不会额外增加后台网络请求。因此,实验页面关闭期间不会实时发现重置;下一次成功读取时仍可以识别此前已经发生的新周期。

安全措施与仍然存在的风险#

这次改进保留并加强了以下保护:

  • 只允许在 HTTPS chatgpt.com 主机上执行读取;
  • 禁止文件访问、content 访问和 HTTP 混合内容;
  • 启用 Safe Browsing,默认关闭第三方 Cookie;
  • 登录 Activity 使用 FLAG_SECURE,阻止系统截图和最近任务缩略图;
  • 不使用 addJavascriptInterface 暴露原生对象;
  • 错误提示不包含接口正文、Cookie 或令牌;
  • 应用配置只保存筛选后的套餐字段和功能开关。

但它仍然是实验功能。ChatGPT/Codex 套餐余量没有公开的官方集成 API,接口路径、字段结构和可用性都可能随时变化,也可能触发限流或重新登录。使用代理还会引入新的信任边界。

测试与发布#

本次发布完成了以下验证:

  • 简体中文、繁體中文(台灣)、English、日本語和 한국어共 266 个资源键一致;
  • 16 项单元测试覆盖代理地址、窗口分类、倒计时、显示策略和新周期判定;
  • Android Lint 通过;
  • Debug APK 构建通过;
  • 在 MuMu 模拟器中验证读取、代理、自动更新、实际浮岛、显示分组和通知开关;
  • GitHub 受保护的 main 分支通过 PR 必需检查后合并;
  • GitHub Actions 完成 Release keystore 校验、正式签名构建、签名验证和 Release 上传。

Balance Island v0.9.1 已正式发布:

这次工作的核心不是“找到一个接口然后把数字显示出来”,而是给一个不稳定、敏感的实验能力补上可理解的状态、明确的安全边界和可验证的失败处理。只有这样,它才适合真正出现在日常使用的浮岛里。