135 lines
5.0 KiB
JavaScript
135 lines
5.0 KiB
JavaScript
// 方案2:把 coderelay 的 auth 文件转换为 wb-switch 的 accounts.json 记录格式
|
||
// 依据 wb-switch-rust.exe 中提取的结构字符串:
|
||
// Account 字段: id uid nickname email access_token refresh_token token_type
|
||
// expiresAt refreshExpiresAt refreshedAt createdAt domain
|
||
// enterpriseId enterpriseName variant needs_relogin needs_relogin_reason
|
||
// auth_raw profile_raw
|
||
// credit_usage_snapshots: { accountId, accountName, variant, remaining, total, ts }
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
|
||
const AUTH_DIR = 'C:/Users/admin/AppData/Roaming/com.coderelay.desktop/sidecar-runtime/auths';
|
||
const WB_DIR = 'C:/Users/admin/.wb-switch';
|
||
const ACCOUNTS = path.join(WB_DIR, 'accounts.json');
|
||
const SNAPSHOTS = path.join(WB_DIR, 'credit_usage_snapshots.json');
|
||
|
||
const DRY_RUN = !process.argv.includes('--apply');
|
||
|
||
// ---- 1. 读取 coderelay 的 auth 文件 ----
|
||
const authFiles = fs.readdirSync(AUTH_DIR).filter((f) => f.endsWith('.json'));
|
||
const converted = [];
|
||
const now = Date.now();
|
||
|
||
for (const file of authFiles) {
|
||
const raw = fs.readFileSync(path.join(AUTH_DIR, file), 'utf8');
|
||
const a = JSON.parse(raw);
|
||
|
||
// uid 是唯一稳定的账号标识;id 与 uid 取同一个值(wb-switch 用它关联积分快照)
|
||
const id = a.uid;
|
||
if (!id) {
|
||
console.warn(`[skip] ${file}: 无 uid`);
|
||
continue;
|
||
}
|
||
|
||
// JWT exp 解出过期时间,用于 expiresAt(毫秒)
|
||
let expMs;
|
||
try {
|
||
const payload = JSON.parse(
|
||
Buffer.from(a.access_token.split('.')[1], 'base64url').toString('utf8'),
|
||
);
|
||
if (payload.exp) expMs = payload.exp * 1000;
|
||
} catch {
|
||
/* 保守处理:解不出就不写 expiresAt */
|
||
}
|
||
|
||
const nickname = a.email || id;
|
||
// profile_raw 承载 plan/quota 等展示字段,让界面能显示套餐与积分
|
||
const profileRaw = {
|
||
planType: a.payment_type ?? null,
|
||
planRank: a.plan_rank ?? null,
|
||
region: a.region ?? null,
|
||
enterpriseId: a.enterprise_id ?? '',
|
||
baseUrl: a.base_url ?? null,
|
||
type: a.type ?? null,
|
||
};
|
||
|
||
converted.push({
|
||
id,
|
||
uid: id,
|
||
nickname,
|
||
email: a.email ?? '',
|
||
access_token: a.access_token,
|
||
refresh_token: a.refresh_token,
|
||
token_type: 'Bearer',
|
||
domain: a.domain ?? 'www.codebuddy.cn',
|
||
enterpriseId: a.enterprise_id ?? '',
|
||
enterpriseName: '',
|
||
variant: a.region === 'cn' ? 'cn' : 'ai',
|
||
expiresAt: expMs ?? null,
|
||
createdAt: now,
|
||
refreshedAt: now,
|
||
// auth_raw 保存原始认证响应,是 wb-switch 认账的关键字段
|
||
auth_raw: raw,
|
||
profile_raw: JSON.stringify(profileRaw),
|
||
remaining: typeof a.quota_remain === 'number' ? a.quota_remain : undefined,
|
||
});
|
||
}
|
||
|
||
console.log(`coderelay auth 文件: ${authFiles.length} 个,成功转换 ${converted.length} 条`);
|
||
|
||
// ---- 2. 读取现有 accounts.json,保留非本次导入的账号 ----
|
||
const existing = JSON.parse(fs.readFileSync(ACCOUNTS, 'utf8'));
|
||
const importedUids = new Set(converted.map((c) => c.uid));
|
||
|
||
// 残缺记录:无 id 且 uid 属于本次导入范围。
|
||
// 注意:这类记录是 wb-switch 刷新时 append 出来的孤儿(每条 token 都不同,
|
||
// 并非同一 token 的复制),清除它们不会丢失任何独立账号。
|
||
const isJunk = (r) => r.id === undefined && importedUids.has(r.uid);
|
||
const kept = existing.filter((r) => !isJunk(r));
|
||
const removed = existing.length - kept.length;
|
||
|
||
console.log(`现有记录 ${existing.length} 条 → 清除残缺重复 ${removed} 条,保留 ${kept.length} 条`);
|
||
|
||
// ---- 3. 组装新 accounts.json ----
|
||
const cleaned = converted.map(({ remaining, ...rest }) => rest);
|
||
const merged = [...kept, ...cleaned];
|
||
|
||
console.log(`\n新 accounts.json 将包含 ${merged.length} 条:`);
|
||
for (const r of merged) {
|
||
console.log(` - ${r.nickname} | id=${r.id} | variant=${r.variant ?? '-'}`);
|
||
}
|
||
|
||
// ---- 4. 积分快照 ----
|
||
const snapshots = JSON.parse(fs.readFileSync(SNAPSHOTS, 'utf8'));
|
||
const newSnaps = converted
|
||
.filter((c) => c.remaining !== undefined)
|
||
.map((c) => ({
|
||
accountId: c.id,
|
||
accountName: c.nickname,
|
||
remaining: c.remaining,
|
||
total: 0,
|
||
ts: now,
|
||
variant: c.variant,
|
||
}));
|
||
|
||
const snapMerged = [...snapshots, ...newSnaps];
|
||
console.log(`\n积分快照 ${snapshots.length} → ${snapMerged.length} 条(新增 ${newSnaps.length})`);
|
||
for (const s of newSnaps) {
|
||
console.log(` - ${s.accountName} | remaining=${s.remaining} | accountId=${s.accountId}`);
|
||
}
|
||
|
||
// ---- 5. 写入 ----
|
||
const accountsJson = JSON.stringify(merged, null, 2);
|
||
const snapJson = JSON.stringify(snapMerged, null, 2);
|
||
|
||
if (DRY_RUN) {
|
||
fs.writeFileSync('D:/workspace/code/fms/.tmp-probe/accounts.new.json', accountsJson);
|
||
fs.writeFileSync('D:/workspace/code/fms/.tmp-probe/snapshots.new.json', snapJson);
|
||
console.log('\n[DRY-RUN] 未改动 wb-switch。预览已写入 .tmp-probe/');
|
||
console.log('确认无误后加 --apply 执行');
|
||
} else {
|
||
fs.writeFileSync(ACCOUNTS, accountsJson);
|
||
fs.writeFileSync(SNAPSHOTS, snapJson);
|
||
console.log('\n[APPLIED] 已写入 accounts.json 和 credit_usage_snapshots.json');
|
||
}
|