gcli2api / internal /syncer /cred.go
a3216's picture
chore: 同步到上游 1.12.0-panel + 凭证同步/独立启动器/保活
6d60378 verified
Raw History Blame Contribute Delete
3.67 kB
package syncer
import (
"encoding/json"
"strings"
)
// credential 是从一个 auth 文件里抽出来的、同步对账需要的**最小字段集**。
//
// 为什么只留这几个:冲突裁决只需要回答"哪一端的 accessToken 更新",
// 判据就是 expiresAt(外加 refreshToken 有无)。没必要把整份凭证建模一遍——
// 少建模就少一处会被上游改动作废的地方。
type credential struct {
UID string
Nickname string
ExpiresAt int64
HasRefresh bool
Realm string
Domain string
}
// parseCredential 解析 auth 文件的两种磁盘形态,返回 (凭证, 是否为可用凭证)。
//
// 形态(与网关 auth.Parse 同口径):
//
// 嵌套形 {"auth":{...},"account":{...}} —— 插件 OAuth 输出
// 扁平形 {"accessToken":...,"uid":...} —— 手写 / 旧版
//
// ⚠️ 这里**刻意不复用 internal/auth**,尽管那是网关自己的解析器。
// 原因:本工具要做成**独立 exe**(用户换掉上游新版的 wb2api.exe 时,启动器不受影响),
// 一旦 import 网关内部包,启动器和网关的源码目录就绑死了——上游重构 internal/auth
// 会让启动器连编译都过不去。自带一份容错解析,代价是几十行,换来的是两边解耦。
//
// 容错原则:解析不出来**不是错误**。返回 false 表示"这只是一堆字节",对账会退化成
// 按内容哈希直接比较(仍能正确同步,只是失去了 expiresAt 这档更精细的裁决能力)。
func parseCredential(raw []byte) (credential, bool) {
var probe map[string]json.RawMessage
if len(raw) == 0 || json.Unmarshal(raw, &probe) != nil {
return credential{}, false
}
var c credential
if _, nested := probe["auth"]; nested {
var n struct {
Auth struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ExpiresAt int64 `json:"expiresAt"`
Domain string `json:"domain"`
Realm string `json:"realm"`
} `json:"auth"`
Account struct {
UID string `json:"uid"`
Nickname string `json:"nickname"`
} `json:"account"`
}
if json.Unmarshal(raw, &n) != nil {
return credential{}, false
}
if strings.TrimSpace(n.Auth.AccessToken) == "" {
return credential{}, false
}
c = credential{
UID: n.Account.UID,
Nickname: n.Account.Nickname,
ExpiresAt: n.Auth.ExpiresAt,
HasRefresh: strings.TrimSpace(n.Auth.RefreshToken) != "",
Realm: n.Auth.Realm,
Domain: n.Auth.Domain,
}
} else {
var f struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ExpiresAt int64 `json:"expiresAt"`
Domain string `json:"domain"`
Realm string `json:"realm"`
UID string `json:"uid"`
Nickname string `json:"nickname"`
}
if json.Unmarshal(raw, &f) != nil {
return credential{}, false
}
if strings.TrimSpace(f.AccessToken) == "" {
return credential{}, false
}
c = credential{
UID: f.UID,
Nickname: f.Nickname,
ExpiresAt: f.ExpiresAt,
HasRefresh: strings.TrimSpace(f.RefreshToken) != "",
Realm: f.Realm,
Domain: f.Domain,
}
}
c.Realm = resolveRealm(c.Realm, c.Domain)
return c, true
}
// resolveRealm 归一化 realm:显式非空优先,否则按 domain 后缀推断,最后回落 "cn"。
func resolveRealm(explicit, domain string) string {
if r := strings.TrimSpace(explicit); r != "" {
return r
}
d := strings.ToLower(strings.TrimSpace(domain))
if d == "workbuddy.ai" || strings.HasSuffix(d, ".workbuddy.ai") {
return "global"
}
return "cn"
}