Files
workspace/code/fms/.tmp-probe/wb-switch-migrate.mjs
T
2026-09-19 21:17:02 +08:00

135 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 方案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');
}