creditdaddy 1.2.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +40 -10
- package/bin/creditdaddy.js +131 -2
- package/package.json +29 -29
- package/src/accounts.js +18 -7
- package/src/authDevice.js +3 -0
- package/src/bgdaemon.js +332 -0
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +10 -4
- package/src/constants.js +4 -2
- package/src/daemon.js +137 -13
- package/src/localDetect.js +147 -147
- package/src/logger.js +104 -6
- package/src/minimaxAuth.js +127 -0
- package/src/minimaxClient.js +339 -0
- package/src/minimaxGateway.js +410 -0
- package/src/minimaxLocal.js +259 -0
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +459 -36
- package/src/providers.js +26 -0
- package/src/qoderApp.js +3 -9
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +65 -7
- package/src/tenrouter.js +8 -0
- package/src/tenrouterAccounts.js +38 -3
- package/src/traeClient.js +6 -1
- package/src/traeGateway.js +679 -0
- package/src/transfer.js +24 -1
- package/src/usageSync.js +505 -505
- package/src/winProc.js +36 -0
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +93 -18
- package/src/zcodeGateway.js +104 -46
- package/src/zcodePlanShape.json +3 -3
- package/src/zcrypto.js +160 -160
package/src/mirasimClient.js
CHANGED
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mirasim API 客户端 — 用户资料、套餐、5h/7d 滚动窗口配额查询。
|
|
3
|
-
*
|
|
4
|
-
* GET https://auth.mirasim.ai/auth/me 用户资料(name, email, plan, plan_exp, id)
|
|
5
|
-
* GET https://relay.mirasim.ai/v1/limits 滚动窗口(5h, 7d, 7d_claude 等)
|
|
6
|
-
* 注:relay.mirasim.ai 对大陆直连限制区域(429),需走统一出口 fetchJsonRace 代理
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { fetchJsonRace } from './zcodeClient.js';
|
|
10
|
-
import { FETCH_TIMEOUT_MS } from './constants.js';
|
|
11
|
-
|
|
12
|
-
const AUTH_ME_URL = 'https://auth.mirasim.ai/auth/me';
|
|
13
|
-
const RELAY_LIMITS_URL = 'https://relay.mirasim.ai/v1/limits';
|
|
14
|
-
const AUTH_REFRESH_URL = 'https://auth.mirasim.ai/auth/refresh';
|
|
15
|
-
|
|
16
|
-
export async function fetchMirasimProfile(token) {
|
|
17
|
-
const res = await fetchJsonRace(AUTH_ME_URL, {
|
|
18
|
-
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
19
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
20
|
-
});
|
|
21
|
-
if (!res.ok) {
|
|
22
|
-
if (res.status === 401) { const e = new Error('mirasim 登录凭据已过期'); e.auth = true; throw e; }
|
|
23
|
-
throw new Error(`获取 mirasim 用户信息失败: HTTP ${res.status}`);
|
|
24
|
-
}
|
|
25
|
-
return res.json();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 用 refreshToken 换新凭据(refreshToken 会轮换,两个都要保存)。
|
|
30
|
-
* 返回 { token, refreshToken, expiresAt };没有 refreshToken 或刷新失败抛错。
|
|
31
|
-
*/
|
|
32
|
-
export async function refreshMirasimToken(account) {
|
|
33
|
-
if (!account.refreshToken) throw new Error('没有 refreshToken,无法刷新,请在 mirasim 客户端重新登录后重新导入');
|
|
34
|
-
const res = await fetchJsonRace(AUTH_REFRESH_URL, {
|
|
35
|
-
method: 'POST',
|
|
36
|
-
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
37
|
-
body: { refresh_token: account.refreshToken },
|
|
38
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
39
|
-
});
|
|
40
|
-
if (!res.ok) {
|
|
41
|
-
if (res.status === 401 || res.status === 403) {
|
|
42
|
-
const e = new Error('refreshToken 已失效,请在 mirasim 客户端重新登录后到「添加账号 → 本机导入」同步');
|
|
43
|
-
e.authInvalid = true;
|
|
44
|
-
throw e;
|
|
45
|
-
}
|
|
46
|
-
throw new Error(`mirasim 凭据刷新失败: HTTP ${res.status}`);
|
|
47
|
-
}
|
|
48
|
-
const d = await res.json();
|
|
49
|
-
const token = d.access_token || d.accessToken;
|
|
50
|
-
if (!token) throw new Error('mirasim 刷新响应缺少 access_token(上游结构可能变化)');
|
|
51
|
-
let expiresAt = null;
|
|
52
|
-
try {
|
|
53
|
-
const p = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
|
|
54
|
-
if (Number.isFinite(p.exp)) expiresAt = new Date(p.exp * 1000).toISOString();
|
|
55
|
-
} catch {}
|
|
56
|
-
return { token, refreshToken: d.refresh_token || d.refreshToken || account.refreshToken, expiresAt };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 以有效 token 执行 fn(token);401 时刷新一次再重试。
|
|
61
|
-
* 刷新成功经 ctx.onRefresh(newCreds) 回写账号库与 setting.json(调用方负责持久化)。
|
|
62
|
-
*/
|
|
63
|
-
async function withToken(account, fn, ctx) {
|
|
64
|
-
let token = account.token;
|
|
65
|
-
let refreshed = false;
|
|
66
|
-
const doRefresh = async () => {
|
|
67
|
-
const creds = await refreshMirasimToken(account);
|
|
68
|
-
account.token = creds.token;
|
|
69
|
-
account.refreshToken = creds.refreshToken;
|
|
70
|
-
if (creds.expiresAt) account.expiresAt = creds.expiresAt;
|
|
71
|
-
token = creds.token;
|
|
72
|
-
refreshed = true;
|
|
73
|
-
await ctx?.onRefresh?.(creds);
|
|
74
|
-
};
|
|
75
|
-
let r = await fn(token);
|
|
76
|
-
if (r.status === 401 && !refreshed) {
|
|
77
|
-
await doRefresh();
|
|
78
|
-
r = await fn(token);
|
|
79
|
-
}
|
|
80
|
-
return r;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** 统一配额结构(对齐 Qoder/WorkBuddy/ZCode) */
|
|
84
|
-
export function normalizeMirasimLimits(limits, profile = null) {
|
|
85
|
-
const windows = Array.isArray(limits?.windows) ? limits.windows : [];
|
|
86
|
-
const epochToIso = (sec) => (Number.isFinite(sec) && sec > 0 ? new Date(sec * 1000).toISOString() : null);
|
|
87
|
-
|
|
88
|
-
const WINDOW_LABELS = {
|
|
89
|
-
'5h': '5小时窗口',
|
|
90
|
-
'7d': '7天全局窗口',
|
|
91
|
-
'7d_claude': '7天 Claude 上限',
|
|
92
|
-
'7d_sonnet': '7天 Sonnet 上限',
|
|
93
|
-
'7d_opus': '7天 Opus 上限',
|
|
94
|
-
'7d_fable': '7天 Fable 上限',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const parts = windows.map((w) => {
|
|
98
|
-
const budget = Number(w.budget) || 0;
|
|
99
|
-
const used = Number(w.used) || 0;
|
|
100
|
-
const remaining = Math.max(0, budget - used);
|
|
101
|
-
const usedPct = budget > 0 ? Math.min(100, Math.round((used / budget) * 1000) / 10) : 0;
|
|
102
|
-
const remPct = Math.max(0, Math.round((100 - usedPct) * 10) / 10);
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
name: WINDOW_LABELS[w.name] || w.name,
|
|
106
|
-
total: budget,
|
|
107
|
-
used,
|
|
108
|
-
remaining,
|
|
109
|
-
percent: usedPct,
|
|
110
|
-
remainingPercentage: remPct,
|
|
111
|
-
unit: '',
|
|
112
|
-
resetAt: epochToIso(w.reset_at),
|
|
113
|
-
expiresAt: epochToIso(w.reset_at),
|
|
114
|
-
recurring: true,
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// 瓶颈短板原则:5h 窗口与 7d 全局窗口是“与”的关系(任何一个见底,请求就发不出去)
|
|
119
|
-
// 逻辑上:周额度没了,5 小时即使是 100% 也没有意义!所以主额度必须反映实际可用状态
|
|
120
|
-
const w5h = parts.find((p) => p.name.includes('5小时'));
|
|
121
|
-
const w7d = parts.find((p) => p.name.includes('7天全局'));
|
|
122
|
-
const bottleneck = w7d && w7d.remainingPercentage <= 0 ? w7d
|
|
123
|
-
: w5h && w5h.remainingPercentage <= 0 ? w5h
|
|
124
|
-
: w7d && w5h ? (w7d.remainingPercentage < w5h.remainingPercentage ? w7d : w5h)
|
|
125
|
-
: parts[0] || null;
|
|
126
|
-
|
|
127
|
-
const rem = bottleneck ? bottleneck.remainingPercentage : 0;
|
|
128
|
-
const isExhausted = bottleneck ? bottleneck.remainingPercentage <= 0 : false;
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
total: 100,
|
|
132
|
-
used: 100 - rem,
|
|
133
|
-
remaining: rem,
|
|
134
|
-
unit: '%',
|
|
135
|
-
plan: profile?.plan ? String(profile.plan).toUpperCase() : (limits?.paid ? 'PRO' : 'FREE'),
|
|
136
|
-
planExpiresAt: epochToIso(profile?.plan_exp),
|
|
137
|
-
bottleneckLabel: isExhausted ? (bottleneck?.name || '额度') + '已用尽' : null,
|
|
138
|
-
parts,
|
|
139
|
-
empty: parts.length === 0,
|
|
140
|
-
exceeded: limits?.suspended === true || isExhausted,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** 查询 mirasim 完整配额;401 时自动用 refreshToken 续期一次并重试(ctx.onRefresh 回写凭据) */
|
|
145
|
-
export async function fetchMirasimQuota(account, ctx = {}) {
|
|
146
|
-
if (!account.token) throw new Error('账号缺少 token');
|
|
147
|
-
|
|
148
|
-
const getProfile = (token) => fetchJsonRace(AUTH_ME_URL, {
|
|
149
|
-
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
150
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
151
|
-
});
|
|
152
|
-
const getLimits = (token) => fetchJsonRace(RELAY_LIMITS_URL, {
|
|
153
|
-
headers: { Authorization: `Bearer ${token}`, 'x-mirasim-probe': 'usage', Accept: 'application/json' },
|
|
154
|
-
timeoutMs: FETCH_TIMEOUT_MS,
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
let profile = null;
|
|
158
|
-
try {
|
|
159
|
-
const pres = await withToken(account, getProfile, ctx);
|
|
160
|
-
if (pres.ok) profile = await pres.json();
|
|
161
|
-
} catch (e) {
|
|
162
|
-
// 刷新也失败 / 网络错误:没有 refreshToken 或 refreshToken 失效时如实抛出,其余继续试 limits
|
|
163
|
-
if (e.authInvalid) throw e;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const res = await withToken(account, getLimits, ctx).catch((e) => {
|
|
167
|
-
if (e.authInvalid) throw e;
|
|
168
|
-
throw new Error(`查询额度失败:${e.message}`);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
if (!res.ok) {
|
|
172
|
-
if (res.status === 401) { const e = new Error('mirasim 登录凭据已过期,请在 mirasim 客户端重新登录后重新导入'); e.auth = true; throw e; }
|
|
173
|
-
if (res.status === 429) {
|
|
174
|
-
// 区域受限时返回 profile 里的基础套餐信息,不完全阻断
|
|
175
|
-
return {
|
|
176
|
-
total: 100, used: 0, remaining: 100, unit: '%',
|
|
177
|
-
plan: profile?.plan || 'Plus',
|
|
178
|
-
planExpiresAt: profile?.plan_exp ? new Date(profile.plan_exp * 1000).toISOString() : null,
|
|
179
|
-
parts: [],
|
|
180
|
-
empty: false,
|
|
181
|
-
warning: '云端限流或直连网络受限(配置代理后可查询实时 5h/7d 窗口)',
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
throw new Error(`查询额度失败: HTTP ${res.status}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const limits = await res.json();
|
|
188
|
-
return normalizeMirasimLimits(limits, profile);
|
|
189
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* mirasim API 客户端 — 用户资料、套餐、5h/7d 滚动窗口配额查询。
|
|
3
|
+
*
|
|
4
|
+
* GET https://auth.mirasim.ai/auth/me 用户资料(name, email, plan, plan_exp, id)
|
|
5
|
+
* GET https://relay.mirasim.ai/v1/limits 滚动窗口(5h, 7d, 7d_claude 等)
|
|
6
|
+
* 注:relay.mirasim.ai 对大陆直连限制区域(429),需走统一出口 fetchJsonRace 代理
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { fetchJsonRace } from './zcodeClient.js';
|
|
10
|
+
import { FETCH_TIMEOUT_MS } from './constants.js';
|
|
11
|
+
|
|
12
|
+
const AUTH_ME_URL = 'https://auth.mirasim.ai/auth/me';
|
|
13
|
+
const RELAY_LIMITS_URL = 'https://relay.mirasim.ai/v1/limits';
|
|
14
|
+
const AUTH_REFRESH_URL = 'https://auth.mirasim.ai/auth/refresh';
|
|
15
|
+
|
|
16
|
+
export async function fetchMirasimProfile(token) {
|
|
17
|
+
const res = await fetchJsonRace(AUTH_ME_URL, {
|
|
18
|
+
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
19
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
if (res.status === 401) { const e = new Error('mirasim 登录凭据已过期'); e.auth = true; throw e; }
|
|
23
|
+
throw new Error(`获取 mirasim 用户信息失败: HTTP ${res.status}`);
|
|
24
|
+
}
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 用 refreshToken 换新凭据(refreshToken 会轮换,两个都要保存)。
|
|
30
|
+
* 返回 { token, refreshToken, expiresAt };没有 refreshToken 或刷新失败抛错。
|
|
31
|
+
*/
|
|
32
|
+
export async function refreshMirasimToken(account) {
|
|
33
|
+
if (!account.refreshToken) throw new Error('没有 refreshToken,无法刷新,请在 mirasim 客户端重新登录后重新导入');
|
|
34
|
+
const res = await fetchJsonRace(AUTH_REFRESH_URL, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
37
|
+
body: { refresh_token: account.refreshToken },
|
|
38
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
if (res.status === 401 || res.status === 403) {
|
|
42
|
+
const e = new Error('refreshToken 已失效,请在 mirasim 客户端重新登录后到「添加账号 → 本机导入」同步');
|
|
43
|
+
e.authInvalid = true;
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`mirasim 凭据刷新失败: HTTP ${res.status}`);
|
|
47
|
+
}
|
|
48
|
+
const d = await res.json();
|
|
49
|
+
const token = d.access_token || d.accessToken;
|
|
50
|
+
if (!token) throw new Error('mirasim 刷新响应缺少 access_token(上游结构可能变化)');
|
|
51
|
+
let expiresAt = null;
|
|
52
|
+
try {
|
|
53
|
+
const p = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
|
|
54
|
+
if (Number.isFinite(p.exp)) expiresAt = new Date(p.exp * 1000).toISOString();
|
|
55
|
+
} catch {}
|
|
56
|
+
return { token, refreshToken: d.refresh_token || d.refreshToken || account.refreshToken, expiresAt };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 以有效 token 执行 fn(token);401 时刷新一次再重试。
|
|
61
|
+
* 刷新成功经 ctx.onRefresh(newCreds) 回写账号库与 setting.json(调用方负责持久化)。
|
|
62
|
+
*/
|
|
63
|
+
async function withToken(account, fn, ctx) {
|
|
64
|
+
let token = account.token;
|
|
65
|
+
let refreshed = false;
|
|
66
|
+
const doRefresh = async () => {
|
|
67
|
+
const creds = await refreshMirasimToken(account);
|
|
68
|
+
account.token = creds.token;
|
|
69
|
+
account.refreshToken = creds.refreshToken;
|
|
70
|
+
if (creds.expiresAt) account.expiresAt = creds.expiresAt;
|
|
71
|
+
token = creds.token;
|
|
72
|
+
refreshed = true;
|
|
73
|
+
await ctx?.onRefresh?.(creds);
|
|
74
|
+
};
|
|
75
|
+
let r = await fn(token);
|
|
76
|
+
if (r.status === 401 && !refreshed) {
|
|
77
|
+
await doRefresh();
|
|
78
|
+
r = await fn(token);
|
|
79
|
+
}
|
|
80
|
+
return r;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 统一配额结构(对齐 Qoder/WorkBuddy/ZCode) */
|
|
84
|
+
export function normalizeMirasimLimits(limits, profile = null) {
|
|
85
|
+
const windows = Array.isArray(limits?.windows) ? limits.windows : [];
|
|
86
|
+
const epochToIso = (sec) => (Number.isFinite(sec) && sec > 0 ? new Date(sec * 1000).toISOString() : null);
|
|
87
|
+
|
|
88
|
+
const WINDOW_LABELS = {
|
|
89
|
+
'5h': '5小时窗口',
|
|
90
|
+
'7d': '7天全局窗口',
|
|
91
|
+
'7d_claude': '7天 Claude 上限',
|
|
92
|
+
'7d_sonnet': '7天 Sonnet 上限',
|
|
93
|
+
'7d_opus': '7天 Opus 上限',
|
|
94
|
+
'7d_fable': '7天 Fable 上限',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const parts = windows.map((w) => {
|
|
98
|
+
const budget = Number(w.budget) || 0;
|
|
99
|
+
const used = Number(w.used) || 0;
|
|
100
|
+
const remaining = Math.max(0, budget - used);
|
|
101
|
+
const usedPct = budget > 0 ? Math.min(100, Math.round((used / budget) * 1000) / 10) : 0;
|
|
102
|
+
const remPct = Math.max(0, Math.round((100 - usedPct) * 10) / 10);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
name: WINDOW_LABELS[w.name] || w.name,
|
|
106
|
+
total: budget,
|
|
107
|
+
used,
|
|
108
|
+
remaining,
|
|
109
|
+
percent: usedPct,
|
|
110
|
+
remainingPercentage: remPct,
|
|
111
|
+
unit: '',
|
|
112
|
+
resetAt: epochToIso(w.reset_at),
|
|
113
|
+
expiresAt: epochToIso(w.reset_at),
|
|
114
|
+
recurring: true,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// 瓶颈短板原则:5h 窗口与 7d 全局窗口是“与”的关系(任何一个见底,请求就发不出去)
|
|
119
|
+
// 逻辑上:周额度没了,5 小时即使是 100% 也没有意义!所以主额度必须反映实际可用状态
|
|
120
|
+
const w5h = parts.find((p) => p.name.includes('5小时'));
|
|
121
|
+
const w7d = parts.find((p) => p.name.includes('7天全局'));
|
|
122
|
+
const bottleneck = w7d && w7d.remainingPercentage <= 0 ? w7d
|
|
123
|
+
: w5h && w5h.remainingPercentage <= 0 ? w5h
|
|
124
|
+
: w7d && w5h ? (w7d.remainingPercentage < w5h.remainingPercentage ? w7d : w5h)
|
|
125
|
+
: parts[0] || null;
|
|
126
|
+
|
|
127
|
+
const rem = bottleneck ? bottleneck.remainingPercentage : 0;
|
|
128
|
+
const isExhausted = bottleneck ? bottleneck.remainingPercentage <= 0 : false;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
total: 100,
|
|
132
|
+
used: 100 - rem,
|
|
133
|
+
remaining: rem,
|
|
134
|
+
unit: '%',
|
|
135
|
+
plan: profile?.plan ? String(profile.plan).toUpperCase() : (limits?.paid ? 'PRO' : 'FREE'),
|
|
136
|
+
planExpiresAt: epochToIso(profile?.plan_exp),
|
|
137
|
+
bottleneckLabel: isExhausted ? (bottleneck?.name || '额度') + '已用尽' : null,
|
|
138
|
+
parts,
|
|
139
|
+
empty: parts.length === 0,
|
|
140
|
+
exceeded: limits?.suspended === true || isExhausted,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 查询 mirasim 完整配额;401 时自动用 refreshToken 续期一次并重试(ctx.onRefresh 回写凭据) */
|
|
145
|
+
export async function fetchMirasimQuota(account, ctx = {}) {
|
|
146
|
+
if (!account.token) throw new Error('账号缺少 token');
|
|
147
|
+
|
|
148
|
+
const getProfile = (token) => fetchJsonRace(AUTH_ME_URL, {
|
|
149
|
+
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
|
150
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
151
|
+
});
|
|
152
|
+
const getLimits = (token) => fetchJsonRace(RELAY_LIMITS_URL, {
|
|
153
|
+
headers: { Authorization: `Bearer ${token}`, 'x-mirasim-probe': 'usage', Accept: 'application/json' },
|
|
154
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
let profile = null;
|
|
158
|
+
try {
|
|
159
|
+
const pres = await withToken(account, getProfile, ctx);
|
|
160
|
+
if (pres.ok) profile = await pres.json();
|
|
161
|
+
} catch (e) {
|
|
162
|
+
// 刷新也失败 / 网络错误:没有 refreshToken 或 refreshToken 失效时如实抛出,其余继续试 limits
|
|
163
|
+
if (e.authInvalid) throw e;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const res = await withToken(account, getLimits, ctx).catch((e) => {
|
|
167
|
+
if (e.authInvalid) throw e;
|
|
168
|
+
throw new Error(`查询额度失败:${e.message}`);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
if (res.status === 401) { const e = new Error('mirasim 登录凭据已过期,请在 mirasim 客户端重新登录后重新导入'); e.auth = true; throw e; }
|
|
173
|
+
if (res.status === 429) {
|
|
174
|
+
// 区域受限时返回 profile 里的基础套餐信息,不完全阻断
|
|
175
|
+
return {
|
|
176
|
+
total: 100, used: 0, remaining: 100, unit: '%',
|
|
177
|
+
plan: profile?.plan || 'Plus',
|
|
178
|
+
planExpiresAt: profile?.plan_exp ? new Date(profile.plan_exp * 1000).toISOString() : null,
|
|
179
|
+
parts: [],
|
|
180
|
+
empty: false,
|
|
181
|
+
warning: '云端限流或直连网络受限(配置代理后可查询实时 5h/7d 窗口)',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`查询额度失败: HTTP ${res.status}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const limits = await res.json();
|
|
188
|
+
return normalizeMirasimLimits(limits, profile);
|
|
189
|
+
}
|