137 lines
6.4 KiB
Markdown
137 lines
6.4 KiB
Markdown
# WB(WorkBuddy)与 Trae 每日签到核心请求
|
||
|
||
> 本文档整理自仓库现有实现(源码为准):
|
||
> - `app/src/main/java/com/rainy/token/data/repository/TraeRepository.kt`
|
||
> - `app/src/main/java/com/rainy/token/data/repository/WorkBuddyRepository.kt`
|
||
>
|
||
> 其中 **WB = 腾讯 WorkBuddy**(非微博)。所有请求均为 `POST`,body 默认为 `{}`。
|
||
|
||
## 一、端点汇总
|
||
|
||
| 服务 | 用途 | URL | Body |
|
||
| --- | --- | --- | --- |
|
||
| Trae(CN) | 查询签到状态 | `https://api.trae.cn/trae/api/v2/ug/checkin_credits/status` | `{}` |
|
||
| Trae(CN) | 执行签到 | `https://api.trae.cn/trae/api/v2/ug/checkin_credits/claim` | `{}`(9074 时换 `{"req_source":2}` → `{"req_source":1}`) |
|
||
| WorkBuddy | 查询签到状态(优先) | `https://www.codebuddy.cn/v2/billing/meter/checkin-activity-status` | `{}` |
|
||
| WorkBuddy | 查询签到状态(兜底) | `https://www.codebuddy.cn/v2/billing/meter/checkin-status` | `{}` |
|
||
| WorkBuddy | 执行签到 | `https://www.codebuddy.cn/v2/billing/meter/daily-checkin` | `{}` |
|
||
| WorkBuddy | accessToken 刷新 | `https://copilot.tencent.com/v2/plugin/auth/token/refresh` | 头 `X-Refresh-Token` |
|
||
|
||
## 二、Trae 核心请求源码
|
||
|
||
认证头:`Authorization: Cloud-IDE-JWT <jwt>`,`x-device-id` 必须是账号绑定的官方客户端设备 ID。
|
||
|
||
```kotlin
|
||
// 官方签到接口路径
|
||
const val CHECKIN_STATUS_PATH = "/trae/api/v2/ug/checkin_credits/status"
|
||
const val CHECKIN_CLAIM_PATH = "/trae/api/v2/ug/checkin_credits/claim"
|
||
|
||
private fun checkinRequest(
|
||
jwt: String,
|
||
deviceId: String,
|
||
deviceSrc: String,
|
||
path: String,
|
||
bodyJson: String = "{}"
|
||
): Result<String> {
|
||
val requestId = java.util.UUID.randomUUID().toString()
|
||
val request = Request.Builder()
|
||
.url("https://api.trae.cn" + path)
|
||
.addHeader("Content-Type", "application/json")
|
||
.addHeader("Accept", "*/*")
|
||
.addHeader("User-Agent", "VSCode 1.107.1 (TRAE SOLO CN)")
|
||
.addHeader("Authorization", "Cloud-IDE-JWT " + jwt)
|
||
.addHeader("X-User-Region", "CN")
|
||
.addHeader("Accept-Language", "zh-CN")
|
||
.addHeader("Package-Type", "stable_cn")
|
||
.addHeader("X-Lgw-Req-Sdk-Type", "3")
|
||
.addHeader("X-Market-Client-Id", "VSCode 1.107.1")
|
||
.addHeader("X-Device-Brand", "83DG")
|
||
.addHeader("X-Device-Type", "windows")
|
||
.addHeader("X-OS-Version", "Windows 11 Pro")
|
||
.addHeader("App-Version", "0.1.61")
|
||
.addHeader("X-Request-Id", requestId)
|
||
.addHeader("X-TT-Trace-Id", ttTraceId(requestId))
|
||
.addHeader("X-Device-Id", deviceId)
|
||
.addHeader("Sec-Fetch-Dest", "empty")
|
||
.addHeader("Sec-Fetch-Mode", "no-cors")
|
||
.addHeader("Sec-Fetch-Site", "none")
|
||
.post(bodyJson.toRequestBody("application/json".toMediaType()))
|
||
.build()
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### Trae 签到结果判定
|
||
|
||
- `code = 0 / 200` → **SUCCESS**(成功)
|
||
- `code = 1001 / 10001`,或文案含 `已签到 / 已经签到 / 明日再来 / 今日已完成 / 已领取 / already / checked / claimed` → **ALREADY**(今日已签)
|
||
- 文案含 `设备 / device / machine` → **DEVICE_BLOCKED**(设备级拦截)
|
||
- 文案含 `参与 / 稍后重试 / 火爆 / 繁忙 / 限流 / 排队 / too many / retry / busy / popular / later` → **RETRYABLE**(临时限流)
|
||
- 其余 → **FAILED**
|
||
|
||
### Trae 注意事项
|
||
|
||
- UA 必须是 `VSCode 1.107.1 (TRAE SOLO CN)`,这是签到走的 VSCode 插件进程身份;与 IDE 主进程 / Web 混用会触发风控。
|
||
- `X-Device-Id` 不可用固定 ClientID `en1oxy7wnw8j9n`(所有用户相同),会按设备风控拒绝。
|
||
- 仓库内置默认设备 ID:`ACCOUNT_BOUND_DEVICE_ID = "895482260673817"`,账号级,换设备需更新。
|
||
- INTL 区签到接口未实测,仓库实现直接不支持。
|
||
|
||
## 三、WorkBuddy(WB)核心请求源码
|
||
|
||
认证头:`Authorization: Bearer <accessToken>`。签到域名是 `www.codebuddy.cn`(余额主查询才是 `www.workbuddy.cn`)。
|
||
|
||
```kotlin
|
||
// 查询今日签到状态(两条路径依次 fallback,共享 token 轮换状态)
|
||
suspend fun fetchCheckinStatus(accountId: String? = null): Result<CheckinStatus> {
|
||
val body = authedPostFirstSuccessful(
|
||
credential,
|
||
accountId,
|
||
"/v2/billing/meter/checkin-activity-status",
|
||
"/v2/billing/meter/checkin-status"
|
||
) ?: return Result.failure(RepositoryError.Unknown())
|
||
return runCatching { parseCheckin(body) }...
|
||
}
|
||
|
||
// 执行签到
|
||
suspend fun checkIn(accountId: String? = null): Result<CheckinStatus> {
|
||
val body = when (val r = authedPost(credential, "/v2/billing/meter/daily-checkin")) {
|
||
is AuthedPostResult.Ok -> r.body
|
||
is AuthedPostResult.Failed -> r.body
|
||
// ...
|
||
}
|
||
// code=0 成功;code=10001 或文案含「已签/今日/already」→ 已签
|
||
}
|
||
|
||
// 带自动 refresh 一次的 POST 核心
|
||
private fun authedPost(credential: Credential.WorkBuddyCredential, path: String): AuthedPostResult {
|
||
var access = credential.accessToken
|
||
var refresh = credential.refreshToken
|
||
repeat(2) { attempt ->
|
||
val request = Request.Builder()
|
||
.url("https://www.codebuddy.cn" + path)
|
||
.addHeader("Authorization", "Bearer " + access)
|
||
.addHeader("Accept", "application/json")
|
||
.addHeader("Content-Type", "application/json")
|
||
.post("{}".toRequestBody(jsonMedia))
|
||
.build()
|
||
// 2xx → Ok
|
||
// 401/403 且第一次尝试 → tryRefesh(refresh) 换新 token 后重试
|
||
// refresh 请求头携带 X-Refresh-Token <refreshToken>
|
||
// 轮换出的新凭据必须先落库再用,否则旧 refreshToken 单次消费后失效
|
||
}
|
||
}
|
||
```
|
||
|
||
### WorkBuddy 签到结果判定
|
||
|
||
- `code = 0` → **SUCCESS**
|
||
- `code = 10001`,或文案含 `已签 / 已经签到 / 今日 / already / checked / claimed` → **ALREADY**(今日已签)
|
||
- 其余 → **FAILED**(HTTP 400 body 也可能携带签到业务语义,需透传解析,不能直接当网络错误丢弃)
|
||
|
||
## 四、通用约定
|
||
|
||
1. Trae 用 `Cloud-IDE-JWT`,WorkBuddy 用 `Bearer`,两者凭据类型不同,不能混用。
|
||
2. 两者签到都幂等:今日已签时服务端返回已签语义,客户端本地也会记录 `lastCheckinDate` 短路,避免重复请求。
|
||
3. 401/403:Trae 直接视为凭据失效;WorkBuddy 会尝试用 refreshToken 轮换一次。
|
||
4. 不要随机生成设备 ID(Trae)或篡改客户端头,否则会被风控(Trae 9074「参与用户太多」)。
|