- fetchBalance 改为 GET https://opencode.ai/zen/go/v1/usage(Bearer API Key),移除 dashboard HTML 抓取与 hydration 解析(parseWindows/ScrapedWindow) - 新增 parseUsageResponse:percent 钳制 0-100,resetsAt 兼容 ISO-8601 与 epoch 秒/毫秒,倒计时可注入基准时间并 coerce >=0 - 错误映射:无 Key/401/403→凭据失效、429→限流(Retry-After)、其余 4xx/5xx→服务端异常,瞬时重试沿用 RetryHelper - auth cookie + workspaceId 仅保留给用量记录页与 _server 模型级明细增强,缺失时跳过不影响余额主数据 - 设置页表单重排 API Key 主位 + 选填分区标题;测试并保存按钮 enabled 与 ViewModel 校验四象限自洽 - 三语言文案与 README 同步;单测重写为 parseUsageResponse JSON 解析,保留 parseModelRows/windowQuotaRaw 独立 subagent 审计两轮通过(阻断项与按钮自洽性已修复);testDebugUnitTest 160 测试 0 失败
239 lines
14 KiB
Markdown
239 lines
14 KiB
Markdown
# 🌧️ 雨晴Token (RainyToken)
|
||
|
||
> *"AI 用量,尽在掌握 — AI Balance & Usage at a Glance"*
|
||
|
||
[](https://github.com/CATMIAOZHI/Rainytoken/actions/workflows/ci.yml)
|
||
[](https://github.com/CATMIAOZHI/Rainytoken/actions)
|
||
[](https://github.com/CATMIAOZHI/Rainytoken/releases)
|
||
Android AI 余额与用量查询 APP —— 统一查看 DeepSeek、OpenCode Go、CommandCode、Codex / ChatGPT、Ollama 的余额与用量配额。粉色调品牌 UI,配套桌面小组件。
|
||
RainyToken(雨晴Token)— AI Balance & Usage Quota Query · the Rainy Family tools.
|
||
|
||
---
|
||
|
||
## 📸 截图
|
||
|
||
<p align="center">
|
||
<img src="docs/screenshots/dashboard-light.jpg" width="200" alt="仪表盘(亮色)" />
|
||
<img src="docs/screenshots/dashboard-dark.jpg" width="200" alt="仪表盘(深色)" />
|
||
<img src="docs/screenshots/detail.jpg" width="200" alt="用量图表" />
|
||
<img src="docs/screenshots/ollama-detail.jpg" width="200" alt="Ollama 模型调用次数" />
|
||
</p>
|
||
|
||
<p align="center">
|
||
<em>仪表盘(亮色) · 仪表盘(深色) · 用量图表 · Ollama 模型调用次数</em>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<img src="docs/screenshots/widget.jpg" width="300" alt="桌面小组件" />
|
||
<img src="docs/screenshots/ollama-card.jpg" width="200" alt="Ollama 卡片" />
|
||
</p>
|
||
|
||
<p align="center">
|
||
<em>桌面小组件 · Ollama 首页卡片</em>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<img src="docs/screenshots/heatmap.jpg" width="200" alt="Token 活动" />
|
||
</p>
|
||
|
||
<p align="center">
|
||
<em>Token 活动</em>
|
||
</p>
|
||
|
||
---
|
||
|
||
## ✨ 功能特性
|
||
|
||
| 特性 | 说明 |
|
||
|------|------|
|
||
| 📊 **仪表盘** | DeepSeek 余额(¥)+ 各服务用量/余额卡片;OCGO/CCGO 卡片可直达用量详情;长按拖动自由排序(持久化);下拉全局刷新 · 平板自适应双窗格布局 |
|
||
| 📈 **用量图表** | 3 张 Canvas 手绘图表 — 消耗金额 / API 请求次数 / Token 消耗(OCGO & CCGO 双数据源);支持 UTC+0/UTC+8 时区切换和自定义日/月/范围;自动降级(近5h无数据→12h→7天→当月);平板并排展示 |
|
||
| 🗓️ **Token活动** | OCGO每日 Token热力图(GitHub贡献图风格):每日 /每周 /累计三视图 +年份切换(默认最近 365 天,可切换自然年);个人资料 +活动洞察(总请求次数 /最多请求时段 Top 3)+年度统计(累计 /峰值 /连续天数);6 级粉色分位色阶,点击 /滑动查看 |
|
||
| 📱 **平板适配** | 全局 `BoxWithConstraints` 自适应容器宽度;≥600dp 卡片双列,≥700dp 图表并排;双窗格 35/65 左右分栏(Expanded 模式);支持 Android 13+ 预见性返回手势 |
|
||
| 📋 **详细数据** | 原始记录分页浏览,支持时间 + 模型筛选,点击查看完整字段 |
|
||
| 🔍 **多粒度筛选** | 5小时 / 12小时(10分钟桶) / 24小时 / 今天 / 昨天 / 最近7天 / 最近30天 / 当月 / 自定义日·月·范围 |
|
||
| 🏷️ **模型筛选** | 多选 / 单选 / 全选,动态图例自适应换行 |
|
||
| 📱 **桌面小组件** | 不打开 APP 也能看用量;支持四服务切换(OCGO/CCGO/Codex/Ollama)+ DeepSeek 余额;左上角 > 进 APP、其他区域点切换、↻ 刷新;可拖入负一屏;划到即自动刷新(MIUI 曝光刷新) |
|
||
| 🔄 **自动同步** | 首页下拉自动同步用量;无缓存时启动自动全量同步;CCGO 详情页支持手动清除并重新同步 |
|
||
| 🌙 **深色模式** | 全局自适应 — App 内文字/图标/背景自动切换,小组件独立适配暗色布局 |
|
||
| ➕ **一键添桌面** | APP 内点 + 直接添加小组件,不用去系统列表翻;二次确认 + 权限检测 |
|
||
| 💡 **使用小技巧** | 首页随机展示一条操作提示(每次启动刷新);设置页可查看全部 13 条隐藏操作技巧 |
|
||
| ⚡ **Room 数据库** | 用量记录存 Room(indexed on workspaceId+timeCreated),DAO 查询替代全量 JSON 序列化;首次启动自动从旧 DataStore 迁移 |
|
||
| 🎀 **雨晴粉主题** | Material Design 3 · 草莓粉 #FF85A2 · 樱粉 #FFD1DC |
|
||
| 🔐 **Codex OAuth 登录** | 无头模式 OAuth PKCE:APP 生成授权链接 → 外部浏览器登录 → 粘贴回调 URL 完成授权,无需手动导出 auth.json |
|
||
| ⚡ **Codex 一键激活用量** | Codex 详情页可向 ChatGPT API 发送简短请求触发用量统计;模型列表从 models.dev 动态获取并持久化,支持手动刷新;响应弹窗可复制 |
|
||
| ⚡ **OCGO / Ollama 一键激活用量** | OCGO 详情页可向 `opencode.ai/zen/v1` 发送请求,Ollama 详情页可向 `ollama.com/v1` 发送请求触发用量统计;API Key 在设置页手动填写,模型列表从 models.dev 动态获取 |
|
||
| 🐛 **调试日志** | APP 内「调试日志」页面,查看 Repository 网络请求、Token 刷新、解析错误等详细日志,无需连接电脑 |
|
||
| 🌐 **多语言** | 简体中文 / 繁體中文 / English 三语言界面;设置页一键切换(跟随系统 / 简体 / 繁体 / English);Android 13+ 与系统「应用语言」页双向同步;非中英系统语言自动回退英文;桌面 APP 名称与小组件跟随语言 |
|
||
|
||
---
|
||
|
||
## 📦 下载
|
||
|
||
前往 [Releases](https://github.com/CATMIAOZHI/Rainytoken/releases) 下载最新 APK。
|
||
|
||
> ⚠️ 需要配置 DeepSeek API Key、OpenCode Go API Key、CommandCode API Key、Codex(OAuth 登录或粘贴 auth.json)或 Ollama Cookie 才能拉取数据。OpenCode Go 的 auth cookie + workspaceId 选填,仅用于用量记录与模型级明细。
|
||
|
||
---
|
||
|
||
## 🏗️ 技术架构
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────┐
|
||
│ Android App │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────┐ │
|
||
│ │ Compose UI(3 层页面) │ │
|
||
│ │ 仪表盘 · 用量图表 · 总统计 │ │
|
||
│ │ 详细数据 · Token活动 · 设置 │ │
|
||
│ └────────────────────┬────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────▼─────────────────────────┐ │
|
||
│ │ ViewModel 层(MVVM) │ │
|
||
│ │ DashboardVM · UsageVM · UsageChartVM │ │
|
||
│ │ · UsageDataVM(Hilt 注入) │ │
|
||
│ └────────────────────┬────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────▼─────────────────────────┐ │
|
||
│ │ UseCase 层 │ │
|
||
│ │ RefreshBalanceUseCase(余额) │ │
|
||
│ │ SyncUsageUseCase / SyncCommandCodeUsageUseCase│ │
|
||
│ └────────────────────┬────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────▼─────────────────────────┐ │
|
||
│ │ Repository + Network │ │
|
||
│ │ DeepSeekApi(Retrofit)· OpenCodeGo 网页抓取
|
||
│ │ · OpenCodeUsageRepository · CommandCodeUsageRepository · CodexRepository · OllamaRepository(OkHttp)│ │
|
||
│ └────────────────────┬────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────▼─────────────────────────┐ │
|
||
│ │ 本地存储 │ │
|
||
│ │ BalanceCache(DataStore) │ │
|
||
│ │ UsageCache(Room,indexed on workspaceId+timeCreated) │ │
|
||
│ │ CredentialRepository(Keystore AES-256 GCM) │ │
|
||
│ └──────────────────────────────────────────────┘ │
|
||
└──────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 📁 项目结构
|
||
|
||
```
|
||
Rainytoken/
|
||
├── app/src/main/java/com/rainy/token/
|
||
│ ├── data/
|
||
│ │ ├── cache/ # BalanceCache(DataStore)
|
||
│ │ ├── local/ # UsageCache(Room)、UsageRecordEntity、UsageDao、UsageDatabase、ChartBucket
|
||
│ │ ├── remote/ # DeepSeekApi(Retrofit)+ OpenCodeGo 抓取 + UsageRepository
|
||
│ │ └── repository/ # DeepSeek / OpenCodeGo / CommandCode / Codex / Ollama / Credential Repository
|
||
│ ├── domain/
|
||
│ │ ├── model/ # ServiceBalance、Credential 等
|
||
│ │ ├── service/ # ServiceType 枚举
|
||
│ │ └── usecase/ # RefreshBalanceUseCase / SyncUsageUseCase / SyncCommandCodeUsageUseCase
|
||
│ ├── ui/
|
||
│ │ ├── dashboard/ # DashboardScreen / UsageDetailScreen / UsageOverviewScreen / UsageDataScreen
|
||
│ │ ├── heatmap/ # Token活动热力图(HeatmapScreen / HeatmapCanvas / HeatmapViewModel)
|
||
│ │ ├── servicedetail/ # 服务详情(ServiceDetailScreen)
|
||
│ │ ├── settings/ # 设置 /凭据编辑(SettingsScreen / CredentialEditScreen)
|
||
│ │ ├── webview/ # 网页登录 / Codex OAuth(WebViewLoginScreen / CodexOAuthScreen)
|
||
│ │ ├── widget/ # 桌面小组件(OpenCodeGoWidgetProvider)
|
||
│ │ ├── components/ # ServiceIcon / StatusChip 等
|
||
│ │ ├── theme/ # 雨晴粉主题(StrawberryPink / InkMuted)
|
||
│ │ └── RainyTokenNavHost.kt # Navigation 路由
|
||
│ └── di/ # Hilt 模块
|
||
├── gradle/libs.versions.toml # Version Catalog 依赖管理
|
||
├── build.gradle.kts # 项目级配置
|
||
└── settings.gradle.kts # 项目设置
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ 构建
|
||
|
||
### 方式一:Android Studio(推荐)
|
||
1. Clone 仓库:`git clone https://github.com/CATMIAOZHI/Rainytoken.git`
|
||
2. 用 Android Studio 打开项目
|
||
3. 同步 Gradle,连接设备,Run ▶️
|
||
|
||
### 方式二:命令行
|
||
```bash
|
||
# 运行单元测试
|
||
./gradlew testDebugUnitTest
|
||
|
||
# 构建 Debug APK
|
||
./gradlew assembleDebug
|
||
# APK 输出:app/build/outputs/apk/debug/app-debug.apk
|
||
|
||
# 构建 Release APK
|
||
./gradlew assembleRelease
|
||
```
|
||
|
||
<details>
|
||
<summary>🔧 ARM64 环境说明(非必需)</summary>
|
||
|
||
项目内置了 ARM64 AAPT2 二进制,在 Proot/Termux 等 ARM64 环境中自动启用:
|
||
```bash
|
||
chmod +x ./setup_android_env.sh
|
||
./setup_android_env.sh
|
||
```
|
||
该脚本会配置 `$ANDROID_HOME` 并使用项目内置的 ARM64 build-tools。
|
||
|
||
**Release 构建额外配置**:ARM64 Proot 下 AGP 9.0 的 `optimizeReleaseResources` 传入 `--resource-path-shortening-map=<path>` 等号参数,ARM64 AAPT2 不接受此语法,需在 `~/.gradle/gradle.properties`(全局,不提交项目仓库)中配置 AAPT2 wrapper:
|
||
```properties
|
||
android.aapt2FromMavenOverride=/path/to/android-aapt2-wrapper/aapt2
|
||
```
|
||
wrapper 脚本将等号形式拆分为空格分隔的两个独立 argv。项目 `build.gradle.kts` 中已内置 `guardReleaseResources` 任务作为构建防护(optimized `.ap_` 缺失时自动 fallback 到 linked `.ap_`)。
|
||
> 在 x86_64 环境(GitHub Actions / 普通 Linux)中会自动走官方 AAPT2,无需额外操作。
|
||
</details>
|
||
|
||
---
|
||
|
||
## 📦 依赖管理
|
||
|
||
项目使用 Gradle Version Catalog (`gradle/libs.versions.toml`) 统一管理依赖。
|
||
|
||
| 依赖 | 用途 |
|
||
|------|------|
|
||
| `androidx.compose:compose-bom` | Jetpack Compose BOM |
|
||
| `androidx.navigation:navigation-compose` | 页面导航 |
|
||
| `com.squareup.retrofit2:retrofit` | DeepSeek REST API |
|
||
| `com.squareup.okhttp3:okhttp` | OpenCode Go 网页抓取 |
|
||
| `org.jetbrains.kotlinx:kotlinx-serialization-json` | JSON 序列化 |
|
||
| `androidx.room:room-runtime` | 用量数据本地数据库(Room) |
|
||
| `androidx.datastore:datastore-preferences` | 本地缓存 |
|
||
| `com.google.dagger:hilt-android` | 依赖注入 |
|
||
| `com.google.devtools.ksp:symbol-processing-api` | KSP 注解处理 |
|
||
|
||
---
|
||
|
||
## 🔒 安全说明
|
||
|
||
- ✅ API Key / Session 凭据存入 **Android Keystore**(AES-256 GCM 加密)
|
||
- ✅ 网络请求仅向 DeepSeek / OpenCode 官方 API 发出
|
||
- ✅ `allowBackup=false`:凭据密文、用量数据均不参与系统备份,避免换机恢复后密文无法解密
|
||
- ✅ 签名密钥固定,每次 Release 可覆盖安装
|
||
- ✅ GitHub Secrets 加密存储签名密钥及密码,CI 中解码使用
|
||
|
||
---
|
||
|
||
## 🐱 关于
|
||
|
||
RainyToken(雨晴Token)是「雨晴系列」的第 4 个成员,由 [雨晴喵](https://github.com/CATMIAOZHI) 开发维护,随 [雨晴系列](https://github.com/CATMIAOZHI?tab=repositories) 发布:
|
||
|
||
- [RainyLLM](https://github.com/CATMIAOZHI/RainyLLM) — 纯离线 Android 本地 LLM 推理服务器(Gemma + OpenAI 兼容 API)
|
||
- [RainyScanner](https://github.com/CATMIAOZHI/RainyScanner) — 不拦截不跳转的 Android 扫码工具
|
||
- [Rainy2FA](https://github.com/CATMIAOZHI/Rainy2FA) — 纯本地 · 零联网 · 生物识别保护的 TOTP 验证器
|
||
- **RainyToken** — AI 余额与用量查询 APP | AI Balance & Usage Quota Query(本项目)
|
||
|
||
> 守护每一分 AI 算力预算 💖
|
||
|
||
---
|
||
|
||
## 📄 License
|
||
|
||
MIT License © 2026 Rainy
|
||
|
||
---
|
||
|
||
<p align="center">RainyToken · the Rainy Family tools</p> |