mslxdff 0.1.200 → 0.1.203
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/README.md +3 -3
- package/bin/mslxdff.js +1 -1
- package/package.json +38 -38
- package/plugins/README.md +14 -14
- package/plugins/prefer-model.mjs.example +14 -14
- package/src/anthropic/count-tokens.js +61 -61
- package/src/anthropic/request-body.js +126 -126
- package/src/anthropic/shared.js +18 -18
- package/src/anthropic/sse-aggregate.js +56 -56
- package/src/anthropic/translate.js +287 -287
- package/src/anthropic/web-search.js +361 -361
- package/src/api-kind.js +31 -31
- package/src/autostart.js +288 -288
- package/src/bench/probe.js +1 -1
- package/src/bench/runner.js +1 -1
- package/src/bench/via-probe.js +1 -1
- package/src/bench/via.js +144 -144
- package/src/bench/workbuddy-bench.js +1 -1
- package/src/chat/config.js +11 -11
- package/src/chat/cooling.js +81 -81
- package/src/chat/direct.js +58 -58
- package/src/chat/index.js +6 -6
- package/src/chat/spinner.js +38 -38
- package/src/chat/sse.js +69 -69
- package/src/chat/stats.js +247 -247
- package/src/chat/store.js +48 -48
- package/src/chat/tool-handlers.js +67 -67
- package/src/chat/tools.js +1 -1
- package/src/chat/upstream.js +59 -59
- package/src/chat-pipeline/auto-race.js +158 -158
- package/src/chat-pipeline/empty-turn.js +147 -147
- package/src/chat-pipeline/engine.js +17 -17
- package/src/chat-pipeline/serial-trial.js +283 -283
- package/src/chat-pipeline/terminal-verdict.js +147 -147
- package/src/chooser.js +54 -54
- package/src/claude-desktop/apply.js +101 -101
- package/src/claude-desktop/context1m.js +38 -38
- package/src/claude-desktop/doctor.js +146 -146
- package/src/claude-desktop/slots.js +152 -152
- package/src/cli/bootstrap.js +1 -1
- package/src/cli/commands/claude-desktop.js +284 -284
- package/src/cli/commands/daemon.js +231 -231
- package/src/cli/commands/model/list-live.js +22 -22
- package/src/cli/commands/model/list-providers.js +76 -76
- package/src/cli/commands/model/list-render.js +77 -77
- package/src/cli/commands/model/list-sort.js +52 -52
- package/src/cli/commands/model/list-tty.js +76 -76
- package/src/cli/commands/model/list.js +166 -166
- package/src/cli/commands/model/live-models.js +24 -24
- package/src/cli/commands/model/stats.js +43 -43
- package/src/cli/commands/model/status.js +47 -47
- package/src/cli/commands/model.js +51 -51
- package/src/cli/commands/provider/add.js +108 -108
- package/src/cli/commands/provider/allowlist.js +99 -99
- package/src/cli/commands/provider/cline-free.js +99 -99
- package/src/cli/commands/provider/cline-login.js +218 -218
- package/src/cli/commands/provider/cline-quota.js +65 -65
- package/src/cli/commands/provider/codearts-login.js +45 -45
- package/src/cli/commands/provider/config.js +119 -119
- package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
- package/src/cli/commands/provider/keys-guard.js +50 -50
- package/src/cli/commands/provider/keys.js +161 -161
- package/src/cli/commands/provider/qoder-checkin.js +81 -81
- package/src/cli/commands/provider/qoder-login.js +102 -102
- package/src/cli/commands/provider/raccoon-login.js +180 -180
- package/src/cli/commands/provider/raccoon-password.js +162 -162
- package/src/cli/commands/provider/raccoon-quota.js +109 -109
- package/src/cli/commands/provider/traework-checkin.js +144 -144
- package/src/cli/commands/provider/traework-login.js +164 -164
- package/src/cli/commands/provider/workbuddy-login.js +153 -153
- package/src/cli/commands/provider/zcode-login.js +77 -77
- package/src/cli/commands/provider/zcode-quota.js +55 -55
- package/src/cli/commands/provider.js +1 -1
- package/src/cli/commands/stats.js +149 -149
- package/src/cli/commands/system.js +261 -261
- package/src/cli/commands/timezone.js +47 -47
- package/src/cli/commands/use-group.js +60 -60
- package/src/cli/commands/workbuddy.js +189 -189
- package/src/cli/format.js +141 -141
- package/src/cli/group-helpers.js +70 -70
- package/src/cli/help.js +80 -80
- package/src/cli/index.js +70 -70
- package/src/cli/interactive.js +121 -121
- package/src/cli/join-core.js +99 -99
- package/src/cli/join-wizard.js +175 -175
- package/src/cli/policy.js +136 -136
- package/src/cli/provider-row.js +126 -126
- package/src/cli/util.js +24 -24
- package/src/compat.js +53 -53
- package/src/events.js +38 -38
- package/src/free-lane.js +131 -131
- package/src/free-watcher.js +63 -63
- package/src/groups.js +262 -262
- package/src/logs.js +195 -195
- package/src/metrics.js +62 -62
- package/src/model-capabilities/context-lookup.js +65 -65
- package/src/model-capabilities/merge.js +142 -142
- package/src/model-trace.js +187 -187
- package/src/models.js +1 -1
- package/src/opencode-identity.js +72 -72
- package/src/peers.js +254 -251
- package/src/plugins.js +107 -107
- package/src/providers/base.js +164 -164
- package/src/providers/classify.js +22 -22
- package/src/providers/cline/allowlist-sync.js +93 -93
- package/src/providers/cline/free-catalog.js +71 -71
- package/src/providers/cline/headers.js +37 -37
- package/src/providers/cline/index.js +130 -130
- package/src/providers/cline/models.js +150 -150
- package/src/providers/cline/usage.js +274 -274
- package/src/providers/codearts/auth-pool.js +148 -148
- package/src/providers/codearts/concurrency.js +34 -34
- package/src/providers/codearts/const.js +65 -65
- package/src/providers/codearts/dpop.js +95 -95
- package/src/providers/codearts/index.js +175 -175
- package/src/providers/codearts/login.js +214 -214
- package/src/providers/codearts/models.js +182 -182
- package/src/providers/codearts/sign.js +96 -96
- package/src/providers/codearts/sse.js +204 -204
- package/src/providers/codearts/stream.js +209 -209
- package/src/providers/codearts/sts.js +124 -124
- package/src/providers/codearts.js +1 -1
- package/src/providers/dispatcher.js +146 -146
- package/src/providers/generic.js +156 -156
- package/src/providers/globalqwenwork/account-store.js +141 -141
- package/src/providers/globalqwenwork/constants.js +73 -73
- package/src/providers/globalqwenwork/crypto.js +2 -2
- package/src/providers/globalqwenwork/http.js +2 -2
- package/src/providers/globalqwenwork/index.js +331 -331
- package/src/providers/globalqwenwork/payload.js +147 -147
- package/src/providers/globalqwenwork/rsa.js +2 -2
- package/src/providers/globalqwenwork/sse.js +2 -2
- package/src/providers/globalqwenwork/stream.js +2 -2
- package/src/providers/globalqwenwork.js +1 -1
- package/src/providers/globalworkbuddy/account-store.js +198 -198
- package/src/providers/globalworkbuddy/auth.js +97 -97
- package/src/providers/globalworkbuddy/catalog.js +138 -138
- package/src/providers/globalworkbuddy/constants.js +73 -73
- package/src/providers/globalworkbuddy/device-auth.js +120 -120
- package/src/providers/globalworkbuddy/errors.js +98 -98
- package/src/providers/globalworkbuddy/headers.js +89 -89
- package/src/providers/globalworkbuddy/models.js +96 -96
- package/src/providers/globalworkbuddy/payload.js +80 -80
- package/src/providers/index.js +5 -5
- package/src/providers/model-id.js +172 -172
- package/src/providers/opencode.js +19 -19
- package/src/providers/openrouter.js +31 -31
- package/src/providers/qoder/account-store.js +106 -106
- package/src/providers/qoder/aggregate.js +60 -60
- package/src/providers/qoder/baseprompt.json +591 -591
- package/src/providers/qoder/checkin.js +160 -160
- package/src/providers/qoder/constants.js +44 -44
- package/src/providers/qoder/encode.js +41 -41
- package/src/providers/qoder/fingerprint.js +38 -38
- package/src/providers/qoder/models.js +77 -77
- package/src/providers/qoder/oauth.js +84 -84
- package/src/providers/qoder/payload.js +162 -162
- package/src/providers/qoder/request.js +27 -27
- package/src/providers/qoder/session.js +134 -134
- package/src/providers/qoder/sticky.js +38 -38
- package/src/providers/qoder/stream.js +116 -116
- package/src/providers/qoder.js +1 -1
- package/src/providers/raccoon/account-store.js +216 -216
- package/src/providers/raccoon/auth.js +67 -67
- package/src/providers/raccoon/chat.js +228 -228
- package/src/providers/raccoon/const.js +180 -165
- package/src/providers/raccoon/credits.js +185 -175
- package/src/providers/raccoon/crypto.js +43 -0
- package/src/providers/raccoon/envelope.js +31 -31
- package/src/providers/raccoon/headers.js +45 -45
- package/src/providers/raccoon/index.js +216 -216
- package/src/providers/raccoon/login.js +279 -279
- package/src/providers/raccoon/models.js +160 -160
- package/src/providers/raccoon/password-login.js +102 -87
- package/src/providers/raccoon/qr-codewords.js +115 -115
- package/src/providers/raccoon/qr.js +236 -236
- package/src/providers/raccoon/sse.js +135 -135
- package/src/providers/responses-channel.js +120 -120
- package/src/providers/share-keys.js +53 -53
- package/src/providers/traework/account-store.js +234 -234
- package/src/providers/traework/chat.js +217 -217
- package/src/providers/traework/checkin.js +77 -77
- package/src/providers/traework/constants.js +41 -41
- package/src/providers/traework/errors.js +52 -52
- package/src/providers/traework/headers.js +79 -79
- package/src/providers/traework/index.js +132 -132
- package/src/providers/traework/market-identity.js +116 -116
- package/src/providers/traework/models.js +98 -98
- package/src/providers/traework/payload.js +86 -86
- package/src/providers/traework/sse.js +184 -184
- package/src/providers/traework/token.js +52 -52
- package/src/providers/traework.js +2 -2
- package/src/providers/workbuddy/account-store.js +186 -186
- package/src/providers/workbuddy/auth.js +158 -158
- package/src/providers/workbuddy/balance.js +86 -86
- package/src/providers/workbuddy/cat-travel.js +125 -125
- package/src/providers/workbuddy/checkin.js +75 -75
- package/src/providers/workbuddy/desktop-info.js +78 -78
- package/src/providers/workbuddy/growth-api.js +67 -67
- package/src/providers/workbuddy/growth-plans.js +63 -63
- package/src/providers/workbuddy/growth.js +181 -181
- package/src/providers/workbuddy/index.js +217 -217
- package/src/providers/workbuddy/models.js +126 -126
- package/src/providers/workbuddy/payload.js +141 -141
- package/src/providers/workbuddy/rotation-log.js +83 -83
- package/src/providers/workbuddy/sanitize-tools.js +61 -61
- package/src/providers/workbuddy/sdk-chat.js +26 -26
- package/src/providers/workbuddy-balance.js +78 -78
- package/src/providers/zcode/account-store.js +129 -129
- package/src/providers/zcode/auth.js +28 -28
- package/src/providers/zcode/chat.js +179 -179
- package/src/providers/zcode/const.js +54 -54
- package/src/providers/zcode/context-shape.js +162 -162
- package/src/providers/zcode/headers.js +93 -93
- package/src/providers/zcode/index.js +124 -124
- package/src/providers/zcode/models.js +65 -65
- package/src/providers/zcode/oauth.js +120 -120
- package/src/providers/zcode/quota.js +176 -176
- package/src/providers/zcode/sse.js +196 -196
- package/src/readline-compat.js +25 -25
- package/src/responses/translate.js +311 -311
- package/src/routes/chat/broadband-handler.js +72 -72
- package/src/routes/chat/empty-turn-report.js +31 -31
- package/src/routes/chat/exhausted-handler.js +87 -87
- package/src/routes/chat/hedge-handler.js +130 -130
- package/src/routes/chat/index.js +1 -1
- package/src/routes/chat/local-handler.js +63 -63
- package/src/routes/chat/peer-handler.js +81 -81
- package/src/routes/chat.js +3 -3
- package/src/routes/fallback.js +88 -88
- package/src/routes/groups-relay.js +214 -214
- package/src/routes/groups.js +122 -122
- package/src/routes/helpers.js +83 -83
- package/src/routes/index.js +147 -147
- package/src/routes/messages-route.js +365 -365
- package/src/routes/peers.js +23 -1
- package/src/routes/relay-queue.js +181 -181
- package/src/routes/relay.js +60 -60
- package/src/routes/responses-route.js +213 -213
- package/src/routes/stream-body.js +84 -84
- package/src/routes/stream-hold.js +337 -337
- package/src/routes/stream-scan.js +366 -366
- package/src/routes.js +3 -3
- package/src/runtime/auto-update.js +113 -113
- package/src/runtime/bootstrap.js +8 -0
- package/src/runtime/broadband-stream.js +79 -79
- package/src/runtime/broadband.js +103 -103
- package/src/runtime/group-sync.js +81 -81
- package/src/runtime/keep-warm.js +120 -0
- package/src/runtime/lifecycle-forensics.js +116 -116
- package/src/runtime/lifecycle-log.js +87 -87
- package/src/runtime/log-stamp.js +73 -73
- package/src/runtime/proc-info.js +137 -137
- package/src/runtime/qoder-checkin.js +93 -93
- package/src/runtime/raccoon-checkin.js +126 -126
- package/src/runtime/server-lifecycle.js +189 -189
- package/src/runtime/traework-checkin.js +118 -118
- package/src/runtime/workbuddy-checkin.js +120 -120
- package/src/runtime/workbuddy-growth.js +133 -133
- package/src/server.js +7 -0
- package/src/state/facade.js +58 -58
- package/src/state/memory.js +162 -162
- package/src/state/merge.js +25 -25
- package/src/state/migrations/cline-unify.js +94 -94
- package/src/state/migrations.js +33 -33
- package/src/state/persist.js +42 -42
- package/src/state/provider-config.js +142 -142
- package/src/state/schemas/group.js +31 -31
- package/src/state/schemas/model.js +94 -94
- package/src/state/schemas/peer.js +31 -31
- package/src/state/schemas/port.js +10 -10
- package/src/state/schemas/provider.js +202 -202
- package/src/state/schemas/timezone.js +70 -70
- package/src/state/schemas/token.js +48 -48
- package/src/state/schemas/use-group.js +102 -102
- package/src/state/store.js +176 -176
- package/src/state.js +1 -1
- package/src/sync-claude-desktop.js +234 -234
- package/src/sync-claude.js +152 -152
- package/src/sync-codex.js +116 -116
- package/src/sync-workbuddy.js +158 -158
- package/src/talk-full-redact.js +129 -129
- package/src/talk-full.js +131 -131
- package/src/talk-log.js +257 -257
- package/src/text-tokens.js +52 -52
- package/src/thinking-effort.js +67 -67
- package/src/time.js +106 -106
- package/src/timeline.js +26 -26
- package/src/transport/index.js +1 -1
- package/src/upstream-engine/index.js +74 -74
- package/src/upstream-engine/mode.js +15 -15
- package/src/upstream-engine/sdk/attempt.js +221 -221
- package/src/upstream-engine/sdk/chat.js +28 -28
- package/src/upstream-engine/sdk/convert.js +192 -192
- package/src/upstream-engine/sdk/diagnose.js +62 -62
- package/src/upstream-engine/sdk/dispatch.js +55 -55
- package/src/upstream-engine/sdk/responses.js +176 -176
- package/src/upstream-engine/sdk/sse.js +166 -166
- package/src/upstream-responses.js +305 -305
- package/src/usage/record.js +106 -106
- package/src/usage/report.js +195 -195
- package/src/v6pool/claim.js +157 -157
- package/src/v6pool/config.js +65 -65
- package/src/v6pool/detect.js +130 -130
- package/src/v6pool/index.js +152 -152
- package/src/v6pool/prefix.js +140 -140
- package/src/v6pool/probe.js +40 -40
- package/src/v6pool/report.js +104 -104
- package/src/v6pool/rotate.js +263 -263
- package/src/warp-pool/config.js +74 -74
- package/src/warp-pool/health.js +106 -106
- package/src/warp-pool/identity.js +141 -141
- package/src/warp-pool/index.js +358 -358
- package/src/warp-pool/keys.js +29 -29
- package/src/warp-pool/nodes.js +95 -95
- package/src/warp-pool/ports.js +160 -160
- package/src/warp-pool/register.js +113 -113
- package/src/warp-pool/report.js +232 -232
- package/src/warp-pool/revive.js +168 -168
- package/src/warp-pool/rotate.js +214 -214
- package/src/warp-pool/strategy.js +43 -43
- package/src/warp-pool/verdict.js +69 -69
- package/src/warp-pool/wireproxy.js +162 -162
- package/docs/cli_help_mini.md +0 -146
- package/src/providers/AGENTS.md +0 -49
- package/src/routes/AGENTS.md +0 -37
|
@@ -1,175 +1,185 @@
|
|
|
1
|
-
// raccoon 积分:余额五分项查询 + 登录奖励领取(幂等,判据只认账单)。
|
|
2
|
-
// 这家是**积分制**,三种积分来源的性质(2026-10-10 账单流水实测 + 参考实现双向取证):
|
|
3
|
-
// · 新人注册礼包 3000 —— 注册时服务端自动发,**无端点可领**
|
|
4
|
-
// · 每日积分发放 300 —— 每天服务端自动发,**当天 23:59:59 清零**(账单里有 `daily_expire`),也**无端点可领**
|
|
5
|
-
// · 桌面端登录奖励 3000 —— **每号一次性**,走 `POST /desktop/v1/login/points/grant`,唯一需要领的
|
|
6
|
-
// ⚠ 上游对「已经领过」的重复请求**照样回 code:0**,但积分一分不到账 —— 所以 `code:0` 绝不能当成功凭据,
|
|
7
|
-
// 必须用账单流水裁决(见 claimRaccoonLoginReward)。谎报成功会让 daemon/CLI 输出假到账,比报错更坏。
|
|
8
|
-
import {
|
|
9
|
-
RACCOON_LOGIN_REWARD_EVENT_NAME,
|
|
10
|
-
RACCOON_LOGIN_REWARD_POINTS,
|
|
11
|
-
RACCOON_REQUEST_TIMEOUT_MS,
|
|
12
|
-
raccoonBalanceUrl,
|
|
13
|
-
raccoonBillsUrl,
|
|
14
|
-
raccoonLoginGrantUrl,
|
|
15
|
-
} from "./const.js";
|
|
16
|
-
import { raccoonAuthHeaders } from "./headers.js";
|
|
17
|
-
import { confirmRaccoonSession } from "./login.js";
|
|
18
|
-
import { parseRaccoonEnvelope, raccoonEnvelopeText } from "./envelope.js";
|
|
19
|
-
|
|
20
|
-
const num = (v) => {
|
|
21
|
-
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
22
|
-
if (typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim())) return Number(v.trim());
|
|
23
|
-
return undefined;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/** 上游「新会话未确认」闸门码:先 POST /iam/confirm 再重试一次(与 chat.js 同款兜底)。 */
|
|
27
|
-
const IAM_CONFIRM_REQUIRED = 200901;
|
|
28
|
-
|
|
29
|
-
async function request(url, { credential, fetchImpl = fetch, env = process.env, method = "GET", body, timeoutMs = RACCOON_REQUEST_TIMEOUT_MS } = {}) {
|
|
30
|
-
const once = async () => {
|
|
31
|
-
const res = await fetchImpl(url, {
|
|
32
|
-
method,
|
|
33
|
-
headers: raccoonAuthHeaders(credential, { env }),
|
|
34
|
-
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
35
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
36
|
-
});
|
|
37
|
-
let parsed;
|
|
38
|
-
try {
|
|
39
|
-
parsed = await res.json();
|
|
40
|
-
} catch {
|
|
41
|
-
throw new Error(`raccoon: 响应不是 JSON(HTTP ${res.status})`);
|
|
42
|
-
}
|
|
43
|
-
return { res, envelope: parseRaccoonEnvelope(parsed, res.status) };
|
|
44
|
-
};
|
|
45
|
-
let { res, envelope } = await once();
|
|
46
|
-
// 未确认会话闸门:确认一次后重试(老号 / CLI 直连都可能撞上,登录时已确认的号不会走到这)
|
|
47
|
-
if (res.status === 400 && Number(envelope.code) === IAM_CONFIRM_REQUIRED) {
|
|
48
|
-
if (await confirmRaccoonSession(credential, { fetchImpl, env })) {
|
|
49
|
-
({ res, envelope } = await once());
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return envelope;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 余额五分项(缺项即省略,合计优先取上游的 available_points)。 */
|
|
56
|
-
export function parseRaccoonBalance(data) {
|
|
57
|
-
const src = typeof data === "object" && data !== null ? data : {};
|
|
58
|
-
const available = num(src.available_points);
|
|
59
|
-
const reward = num(src.reward_points);
|
|
60
|
-
const daily = num(src.daily_points);
|
|
61
|
-
const topup = num(src.topup_points);
|
|
62
|
-
const monthly = num(src.monthly_points);
|
|
63
|
-
const items = [];
|
|
64
|
-
if (reward !== undefined) items.push({ key: "reward", label: "奖励积分", value: reward });
|
|
65
|
-
if (daily !== undefined) items.push({ key: "daily", label: "每日积分", value: daily });
|
|
66
|
-
if (monthly !== undefined) items.push({ key: "monthly", label: "月度积分", value: monthly });
|
|
67
|
-
if (topup !== undefined) items.push({ key: "topup", label: "充值积分", value: topup });
|
|
68
|
-
const total = available !== undefined ? available : items.reduce((s, i) => s + i.value, 0);
|
|
69
|
-
return { total, items };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function fetchRaccoonBalance(opts = {}) {
|
|
73
|
-
const envelope = await request(raccoonBalanceUrl(), opts);
|
|
74
|
-
if (envelope.code !== 0) {
|
|
75
|
-
const err = new Error(`raccoon: 查询积分失败:${raccoonEnvelopeText(envelope)}`);
|
|
76
|
-
err.code = envelope.code;
|
|
77
|
-
err.status = envelope.status;
|
|
78
|
-
throw err;
|
|
79
|
-
}
|
|
80
|
-
return parseRaccoonBalance(envelope.data);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** 账单流水数组(上游把 list/items 两种形状都回过,容错取)。 */
|
|
84
|
-
function billsRows(data) {
|
|
85
|
-
const list = Array.isArray(data?.list) ? data.list : Array.isArray(data?.items) ? data.items : Array.isArray(data) ? data : null;
|
|
86
|
-
return list === null ? undefined : list.filter((x) => typeof x === "object" && x !== null);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** 上游的时间字段可能是秒/毫秒数字或 ISO 串,统一成毫秒。 */
|
|
90
|
-
function billTimeMs(item) {
|
|
91
|
-
const at = item.created_at ?? item.createdAt ?? item.time;
|
|
92
|
-
if (typeof at === "number" && Number.isFinite(at)) return at > 1e12 ? at : at * 1000;
|
|
93
|
-
if (typeof at === "string") {
|
|
94
|
-
const ms = Date.parse(at);
|
|
95
|
-
if (Number.isFinite(ms)) return ms;
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 从账单里找「这个号历史上领过登录奖励没有」,返回 `{ points, at }` 或 undefined。
|
|
102
|
-
* ⚠ 判据是**全历史**,不看日期窗口:登录奖励是每号一次性,用「今天」当窗口会让已领的号每天重新发请求,
|
|
103
|
-
* 还会把"昨天领的"判成"没领过"。注册礼包同样是 `reward_grant`,所以 `event_name` 必须逐字比对。
|
|
104
|
-
*/
|
|
105
|
-
export function findLoginRewardInBills(data) {
|
|
106
|
-
const rows = billsRows(data) || [];
|
|
107
|
-
for (const item of rows) {
|
|
108
|
-
if (item.biz_type !== "reward_grant") continue;
|
|
109
|
-
if (item.event_name !== RACCOON_LOGIN_REWARD_EVENT_NAME) continue;
|
|
110
|
-
const at = billTimeMs(item);
|
|
111
|
-
return { points: num(item.points) ?? RACCOON_LOGIN_REWARD_POINTS, ...(at === undefined ? {} : { at }) };
|
|
112
|
-
}
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** 拉一次账单流水;网络/解析任何失败都返回 undefined(判定降级,不抛)。 */
|
|
117
|
-
async function readBills(opts) {
|
|
118
|
-
try {
|
|
119
|
-
const envelope = await request(raccoonBillsUrl(), opts);
|
|
120
|
-
return envelope?.code === 0 ? billsRows(envelope.data) : undefined;
|
|
121
|
-
} catch {
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 领取「桌面端登录奖励」(每号一次性)。返回三种非抛错结果之一:
|
|
128
|
-
* - `{ claimed: true, points }` 本次真到账(账单里出现了登录奖励那笔)
|
|
129
|
-
* - `{ claimed: false, alreadyClaimed: true, points }`
|
|
130
|
-
* - `{ claimed: false, phantom: true, points: 0 }` 上游回了 code:0 但账单没有这笔 —— 虚回,如实报未到账
|
|
131
|
-
* - `{ claimed: false, unverified: true, points: 0 }` 账单查不动,无法确认到账(不猜、不谎报)
|
|
132
|
-
* grant 请求本身失败(业务码非 0)且账单也查不到已领 → 抛错(如实透出)。
|
|
133
|
-
*/
|
|
134
|
-
export async function claimRaccoonLoginReward({ credential, fetchImpl = fetch, env = process.env, timeoutMs } = {}) {
|
|
135
|
-
const opts = { credential, fetchImpl, env, timeoutMs };
|
|
136
|
-
|
|
137
|
-
const before = await readBills(opts);
|
|
138
|
-
const already = before === undefined ? undefined : findLoginRewardInBills(before);
|
|
139
|
-
if (already) return { claimed: false, alreadyClaimed: true, points: already.points };
|
|
140
|
-
|
|
141
|
-
let envelope;
|
|
142
|
-
try {
|
|
143
|
-
envelope = await request(raccoonLoginGrantUrl(), { ...opts, method: "POST", body: {} });
|
|
144
|
-
} catch (error) {
|
|
145
|
-
envelope = { code: -1, message: error?.message ?? String(error), status: 0, data: undefined };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
// raccoon 积分:余额五分项查询 + 登录奖励领取(幂等,判据只认账单)。
|
|
2
|
+
// 这家是**积分制**,三种积分来源的性质(2026-10-10 账单流水实测 + 参考实现双向取证):
|
|
3
|
+
// · 新人注册礼包 3000 —— 注册时服务端自动发,**无端点可领**
|
|
4
|
+
// · 每日积分发放 300 —— 每天服务端自动发,**当天 23:59:59 清零**(账单里有 `daily_expire`),也**无端点可领**
|
|
5
|
+
// · 桌面端登录奖励 3000 —— **每号一次性**,走 `POST /desktop/v1/login/points/grant`,唯一需要领的
|
|
6
|
+
// ⚠ 上游对「已经领过」的重复请求**照样回 code:0**,但积分一分不到账 —— 所以 `code:0` 绝不能当成功凭据,
|
|
7
|
+
// 必须用账单流水裁决(见 claimRaccoonLoginReward)。谎报成功会让 daemon/CLI 输出假到账,比报错更坏。
|
|
8
|
+
import {
|
|
9
|
+
RACCOON_LOGIN_REWARD_EVENT_NAME,
|
|
10
|
+
RACCOON_LOGIN_REWARD_POINTS,
|
|
11
|
+
RACCOON_REQUEST_TIMEOUT_MS,
|
|
12
|
+
raccoonBalanceUrl,
|
|
13
|
+
raccoonBillsUrl,
|
|
14
|
+
raccoonLoginGrantUrl,
|
|
15
|
+
} from "./const.js";
|
|
16
|
+
import { raccoonAuthHeaders } from "./headers.js";
|
|
17
|
+
import { confirmRaccoonSession } from "./login.js";
|
|
18
|
+
import { parseRaccoonEnvelope, raccoonEnvelopeText } from "./envelope.js";
|
|
19
|
+
|
|
20
|
+
const num = (v) => {
|
|
21
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
22
|
+
if (typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim())) return Number(v.trim());
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** 上游「新会话未确认」闸门码:先 POST /iam/confirm 再重试一次(与 chat.js 同款兜底)。 */
|
|
27
|
+
const IAM_CONFIRM_REQUIRED = 200901;
|
|
28
|
+
|
|
29
|
+
async function request(url, { credential, fetchImpl = fetch, env = process.env, method = "GET", body, timeoutMs = RACCOON_REQUEST_TIMEOUT_MS } = {}) {
|
|
30
|
+
const once = async () => {
|
|
31
|
+
const res = await fetchImpl(url, {
|
|
32
|
+
method,
|
|
33
|
+
headers: raccoonAuthHeaders(credential, { env }),
|
|
34
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
35
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
36
|
+
});
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = await res.json();
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error(`raccoon: 响应不是 JSON(HTTP ${res.status})`);
|
|
42
|
+
}
|
|
43
|
+
return { res, envelope: parseRaccoonEnvelope(parsed, res.status) };
|
|
44
|
+
};
|
|
45
|
+
let { res, envelope } = await once();
|
|
46
|
+
// 未确认会话闸门:确认一次后重试(老号 / CLI 直连都可能撞上,登录时已确认的号不会走到这)
|
|
47
|
+
if (res.status === 400 && Number(envelope.code) === IAM_CONFIRM_REQUIRED) {
|
|
48
|
+
if (await confirmRaccoonSession(credential, { fetchImpl, env })) {
|
|
49
|
+
({ res, envelope } = await once());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return envelope;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 余额五分项(缺项即省略,合计优先取上游的 available_points)。 */
|
|
56
|
+
export function parseRaccoonBalance(data) {
|
|
57
|
+
const src = typeof data === "object" && data !== null ? data : {};
|
|
58
|
+
const available = num(src.available_points);
|
|
59
|
+
const reward = num(src.reward_points);
|
|
60
|
+
const daily = num(src.daily_points);
|
|
61
|
+
const topup = num(src.topup_points);
|
|
62
|
+
const monthly = num(src.monthly_points);
|
|
63
|
+
const items = [];
|
|
64
|
+
if (reward !== undefined) items.push({ key: "reward", label: "奖励积分", value: reward });
|
|
65
|
+
if (daily !== undefined) items.push({ key: "daily", label: "每日积分", value: daily });
|
|
66
|
+
if (monthly !== undefined) items.push({ key: "monthly", label: "月度积分", value: monthly });
|
|
67
|
+
if (topup !== undefined) items.push({ key: "topup", label: "充值积分", value: topup });
|
|
68
|
+
const total = available !== undefined ? available : items.reduce((s, i) => s + i.value, 0);
|
|
69
|
+
return { total, items };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function fetchRaccoonBalance(opts = {}) {
|
|
73
|
+
const envelope = await request(raccoonBalanceUrl(), opts);
|
|
74
|
+
if (envelope.code !== 0) {
|
|
75
|
+
const err = new Error(`raccoon: 查询积分失败:${raccoonEnvelopeText(envelope)}`);
|
|
76
|
+
err.code = envelope.code;
|
|
77
|
+
err.status = envelope.status;
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
return parseRaccoonBalance(envelope.data);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 账单流水数组(上游把 list/items 两种形状都回过,容错取)。 */
|
|
84
|
+
function billsRows(data) {
|
|
85
|
+
const list = Array.isArray(data?.list) ? data.list : Array.isArray(data?.items) ? data.items : Array.isArray(data) ? data : null;
|
|
86
|
+
return list === null ? undefined : list.filter((x) => typeof x === "object" && x !== null);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 上游的时间字段可能是秒/毫秒数字或 ISO 串,统一成毫秒。 */
|
|
90
|
+
function billTimeMs(item) {
|
|
91
|
+
const at = item.created_at ?? item.createdAt ?? item.time;
|
|
92
|
+
if (typeof at === "number" && Number.isFinite(at)) return at > 1e12 ? at : at * 1000;
|
|
93
|
+
if (typeof at === "string") {
|
|
94
|
+
const ms = Date.parse(at);
|
|
95
|
+
if (Number.isFinite(ms)) return ms;
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 从账单里找「这个号历史上领过登录奖励没有」,返回 `{ points, at }` 或 undefined。
|
|
102
|
+
* ⚠ 判据是**全历史**,不看日期窗口:登录奖励是每号一次性,用「今天」当窗口会让已领的号每天重新发请求,
|
|
103
|
+
* 还会把"昨天领的"判成"没领过"。注册礼包同样是 `reward_grant`,所以 `event_name` 必须逐字比对。
|
|
104
|
+
*/
|
|
105
|
+
export function findLoginRewardInBills(data) {
|
|
106
|
+
const rows = billsRows(data) || [];
|
|
107
|
+
for (const item of rows) {
|
|
108
|
+
if (item.biz_type !== "reward_grant") continue;
|
|
109
|
+
if (item.event_name !== RACCOON_LOGIN_REWARD_EVENT_NAME) continue;
|
|
110
|
+
const at = billTimeMs(item);
|
|
111
|
+
return { points: num(item.points) ?? RACCOON_LOGIN_REWARD_POINTS, ...(at === undefined ? {} : { at }) };
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 拉一次账单流水;网络/解析任何失败都返回 undefined(判定降级,不抛)。 */
|
|
117
|
+
async function readBills(opts) {
|
|
118
|
+
try {
|
|
119
|
+
const envelope = await request(raccoonBillsUrl(), opts);
|
|
120
|
+
return envelope?.code === 0 ? billsRows(envelope.data) : undefined;
|
|
121
|
+
} catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 领取「桌面端登录奖励」(每号一次性)。返回三种非抛错结果之一:
|
|
128
|
+
* - `{ claimed: true, points }` 本次真到账(账单里出现了登录奖励那笔)
|
|
129
|
+
* - `{ claimed: false, alreadyClaimed: true, points }` 历史已领(账单查到,或上游回 `granted:false`)
|
|
130
|
+
* - `{ claimed: false, phantom: true, points: 0 }` 上游回了 code:0 但账单没有这笔 —— 虚回,如实报未到账
|
|
131
|
+
* - `{ claimed: false, unverified: true, points: 0 }` 账单查不动,无法确认到账(不猜、不谎报)
|
|
132
|
+
* grant 请求本身失败(业务码非 0)且账单也查不到已领 → 抛错(如实透出)。
|
|
133
|
+
*/
|
|
134
|
+
export async function claimRaccoonLoginReward({ credential, fetchImpl = fetch, env = process.env, timeoutMs } = {}) {
|
|
135
|
+
const opts = { credential, fetchImpl, env, timeoutMs };
|
|
136
|
+
|
|
137
|
+
const before = await readBills(opts);
|
|
138
|
+
const already = before === undefined ? undefined : findLoginRewardInBills(before);
|
|
139
|
+
if (already) return { claimed: false, alreadyClaimed: true, points: already.points };
|
|
140
|
+
|
|
141
|
+
let envelope;
|
|
142
|
+
try {
|
|
143
|
+
envelope = await request(raccoonLoginGrantUrl(), { ...opts, method: "POST", body: {} });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
envelope = { code: -1, message: error?.message ?? String(error), status: 0, data: undefined };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 上游用 `data.granted` 直接回答「这次到底发没发」—— 这是比账单更权威的判据,优先采信。
|
|
149
|
+
// `granted === false` ⇒ 本号此前已发过(一次性礼包),**不是「未到账」**。
|
|
150
|
+
// 为什么不能只靠账单:重度使用的号(如 daemon 正在跑的那个)账单会被「对话消耗」刷满,
|
|
151
|
+
// 分页窗口里根本翻不到那条陈年 reward_grant —— 此时只信账单就会把「早就领过」
|
|
152
|
+
// 误报成「上游回执成功但账单查不到 → 未到账」(实测踩到,教训)。
|
|
153
|
+
// 官方前端同款判据:granted 为真才弹「积分已到账」,为假就静默(见 app.asar grantDesktopLoginPoints 调用点)。
|
|
154
|
+
if (envelope?.code === 0 && envelope.data?.granted === false) {
|
|
155
|
+
return { claimed: false, alreadyClaimed: true, points: RACCOON_LOGIN_REWARD_POINTS };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 未给 granted 判据(如上游老版本/测试桩)时回退账单复查 —— 到账凭据仍以账单为准。
|
|
159
|
+
const after = await readBills(opts);
|
|
160
|
+
const landed = after === undefined ? undefined : findLoginRewardInBills(after);
|
|
161
|
+
if (landed) {
|
|
162
|
+
const points = num(envelope?.data?.points) ?? num(envelope?.data?.popup?.points) ?? landed.points ?? RACCOON_LOGIN_REWARD_POINTS;
|
|
163
|
+
return { claimed: true, points };
|
|
164
|
+
}
|
|
165
|
+
if (after === undefined) return { claimed: false, unverified: true, points: 0 };
|
|
166
|
+
if (envelope?.code === 0) return { claimed: false, phantom: true, points: 0 };
|
|
167
|
+
|
|
168
|
+
const err = new Error(`raccoon: 领取登录积分失败:${raccoonEnvelopeText(envelope, "上游未返回原因")}`);
|
|
169
|
+
err.code = envelope.code;
|
|
170
|
+
err.status = envelope.status;
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* claimRaccoonLoginReward 的结果 → 统一状态字。daemon 与 CLI 共用,避免两处各写一份分支后漂移。
|
|
176
|
+
* claimed=真到账 · already=一次性礼包此前已领过(正常态) · phantom=上游回执成功但账单没这笔 ·
|
|
177
|
+
* unverified=账单查不动,不下结论 · error=真失败。
|
|
178
|
+
*/
|
|
179
|
+
export function raccoonClaimStatus(r) {
|
|
180
|
+
if (r?.claimed) return "claimed";
|
|
181
|
+
if (r?.alreadyClaimed) return "already";
|
|
182
|
+
if (r?.phantom) return "phantom";
|
|
183
|
+
if (r?.unverified) return "unverified";
|
|
184
|
+
return "error";
|
|
185
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// raccoon 登录参数加密(手机号 / 密码)—— 单一源。
|
|
2
|
+
//
|
|
3
|
+
// 取证(2026-10-18:app.asar webpack module 68284 原文 + 本机真实账号实测):
|
|
4
|
+
// 官方登录页拿到输入后先调 `U.yv()` 再拼 body,而 `yv` 的真身就是这段:
|
|
5
|
+
// function c(e, t = "senseraccoon2023") {
|
|
6
|
+
// const a = CryptoJS.enc.Utf8.parse(t);
|
|
7
|
+
// const i = CryptoJS.lib.WordArray.random(16); // 随机 IV
|
|
8
|
+
// const r = CryptoJS.AES.encrypt(e, a, { iv: i, mode: CFB, padding: NoPadding });
|
|
9
|
+
// return i.concat(r.ciphertext).toString(CryptoJS.enc.Base64);
|
|
10
|
+
// }
|
|
11
|
+
// 即 **key 与模式都硬编码在前端**:AES-128-CFB、key = "senseraccoon2023"、无 padding,
|
|
12
|
+
// 输出 Base64(IV(16 字节) ‖ 密文)。手机号与密码走的是同一个函数 —— 两个字段都要加密。
|
|
13
|
+
//
|
|
14
|
+
// 为什么必须加密(否则极难自查):明文发送时服务端解得空串再去比对,一律回
|
|
15
|
+
// `AUTH_ACCOUNT_OR_PASSWORD_INVALID`("手机号或密码不正确")—— 用「密码错」的口径
|
|
16
|
+
// 掩盖了「格式错」。本机用真实账号取证:明文 → 401;按本算法加密 → 200。
|
|
17
|
+
// 且只加密手机号、密码留明文也是 401,可证**两个字段都加密**。
|
|
18
|
+
//
|
|
19
|
+
// ⚠ 这个 key 是**上游公开常量**(打包进任意客户端),不是本项目的机密;照抄它只为对齐协议,
|
|
20
|
+
// 不代表传输具备保密性 —— 真正的保护来自 HTTPS,以及本项目「密码不落盘/不打印」的纪律。
|
|
21
|
+
import { createCipheriv, randomBytes } from "node:crypto";
|
|
22
|
+
import { RACCOON_PASSWORD_ENC_KEY } from "./const.js";
|
|
23
|
+
|
|
24
|
+
const IV_BYTES = 16;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 加密单个登录字段(手机号或密码)→ Base64(IV ‖ AES-128-CFB 密文)。
|
|
28
|
+
*
|
|
29
|
+
* 与上游前端**逐位对齐**:同 key、同模式(CFB128,对齐 CryptoJS 的 `mode.CFB`)、
|
|
30
|
+
* 同 padding(NoPadding —— CFB 是流模式,密文长度恒等于明文长度)、同拼接顺序。
|
|
31
|
+
* 每次调用生成新的随机 IV(上游亦然):同一输入两次调用密文不同,这是正确行为。
|
|
32
|
+
*/
|
|
33
|
+
export function raccoonEncryptField(plain, keyStr = RACCOON_PASSWORD_ENC_KEY) {
|
|
34
|
+
const key = Buffer.from(String(keyStr ?? ""), "utf8");
|
|
35
|
+
if (key.length !== 16) throw new Error("raccoon: 登录加密 key 必须是 16 字节(AES-128)");
|
|
36
|
+
const iv = randomBytes(IV_BYTES);
|
|
37
|
+
const cipher = createCipheriv("aes-128-cfb", key, iv);
|
|
38
|
+
const body = Buffer.concat([
|
|
39
|
+
cipher.update(Buffer.from(String(plain ?? ""), "utf8")),
|
|
40
|
+
cipher.final(),
|
|
41
|
+
]);
|
|
42
|
+
return Buffer.concat([iv, body]).toString("base64");
|
|
43
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
// raccoon 上游统一信封:`{ code, message, details, data }`(与 pack.js 的 parseEnvelope 同形)。
|
|
2
|
-
// ⚠ 两代通道的成功码不同(2026-10 上游改版):旧 /api/web/* 用数字 `0`,新 /api/iam/v1/* 用字符串 `"OK"`。
|
|
3
|
-
// 故 `code` 原样保留(数字或字符串),判成功一律走 isRaccoonOk()——**不能**再用 `code === 0`:
|
|
4
|
-
// 字符串码一律不等于 0(把 "OK" 判成失败),而把它兜底成 0 又会把 `AUTH_QRCODE_EXPIRED` 判成成功。
|
|
5
|
-
// 登录态类业务码:200001 未带 token、200003 登录态过期 —— 见 const.js 的 RACCOON_ERROR_KINDS。
|
|
6
|
-
export function parseRaccoonEnvelope(payload, status = 200) {
|
|
7
|
-
const record = typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : {};
|
|
8
|
-
const raw = record.code;
|
|
9
|
-
const code = typeof raw === "number" || typeof raw === "string" ? raw : status >= 400 ? status : 0;
|
|
10
|
-
const message = typeof record.message === "string" ? record.message : "";
|
|
11
|
-
const details = typeof record.details === "string" ? record.details : "";
|
|
12
|
-
const data = typeof record.data === "object" && record.data !== null && !Array.isArray(record.data) ? record.data : undefined;
|
|
13
|
-
return { code, message, details, data, status };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** 业务成功判据(两代通道通用):数字 0,或新 IAM 通道的字符串 "OK"。 */
|
|
17
|
-
export function isRaccoonOk(envelope) {
|
|
18
|
-
return envelope?.code === 0 || envelope?.code === "OK";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** 人话错误文本:`message: details`,都没有时用调用方给的兜底句。 */
|
|
22
|
-
export function raccoonEnvelopeText(envelope, fallback = "请求失败") {
|
|
23
|
-
const parts = [envelope?.message, envelope?.details].filter((s) => typeof s === "string" && s.length > 0);
|
|
24
|
-
return parts.length > 0 ? parts.join(":") : fallback;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** 登录态失效判据:HTTP 401 或业务码 200001/200003。 */
|
|
28
|
-
export function isRaccoonAuthFailure(envelope, status) {
|
|
29
|
-
if (status === 401) return true;
|
|
30
|
-
return envelope?.code === 200001 || envelope?.code === 200003;
|
|
31
|
-
}
|
|
1
|
+
// raccoon 上游统一信封:`{ code, message, details, data }`(与 pack.js 的 parseEnvelope 同形)。
|
|
2
|
+
// ⚠ 两代通道的成功码不同(2026-10 上游改版):旧 /api/web/* 用数字 `0`,新 /api/iam/v1/* 用字符串 `"OK"`。
|
|
3
|
+
// 故 `code` 原样保留(数字或字符串),判成功一律走 isRaccoonOk()——**不能**再用 `code === 0`:
|
|
4
|
+
// 字符串码一律不等于 0(把 "OK" 判成失败),而把它兜底成 0 又会把 `AUTH_QRCODE_EXPIRED` 判成成功。
|
|
5
|
+
// 登录态类业务码:200001 未带 token、200003 登录态过期 —— 见 const.js 的 RACCOON_ERROR_KINDS。
|
|
6
|
+
export function parseRaccoonEnvelope(payload, status = 200) {
|
|
7
|
+
const record = typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : {};
|
|
8
|
+
const raw = record.code;
|
|
9
|
+
const code = typeof raw === "number" || typeof raw === "string" ? raw : status >= 400 ? status : 0;
|
|
10
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
11
|
+
const details = typeof record.details === "string" ? record.details : "";
|
|
12
|
+
const data = typeof record.data === "object" && record.data !== null && !Array.isArray(record.data) ? record.data : undefined;
|
|
13
|
+
return { code, message, details, data, status };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 业务成功判据(两代通道通用):数字 0,或新 IAM 通道的字符串 "OK"。 */
|
|
17
|
+
export function isRaccoonOk(envelope) {
|
|
18
|
+
return envelope?.code === 0 || envelope?.code === "OK";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 人话错误文本:`message: details`,都没有时用调用方给的兜底句。 */
|
|
22
|
+
export function raccoonEnvelopeText(envelope, fallback = "请求失败") {
|
|
23
|
+
const parts = [envelope?.message, envelope?.details].filter((s) => typeof s === "string" && s.length > 0);
|
|
24
|
+
return parts.length > 0 ? parts.join(":") : fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 登录态失效判据:HTTP 401 或业务码 200001/200003。 */
|
|
28
|
+
export function isRaccoonAuthFailure(envelope, status) {
|
|
29
|
+
if (status === 401) return true;
|
|
30
|
+
return envelope?.code === 200001 || envelope?.code === 200003;
|
|
31
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
// raccoon 出站请求头:与官方客户端同形(逐字段对齐 pack.js 的 raccoonHeaders)。
|
|
2
|
-
// 风控按版本/头判断,形态错会 401/403 —— 所以这里不做「优化」,只做照抄。
|
|
3
|
-
import {
|
|
4
|
-
RACCOON_CLIENT_LANGUAGE,
|
|
5
|
-
RACCOON_CLIENT_PLATFORM,
|
|
6
|
-
raccoonClientVersion,
|
|
7
|
-
raccoonUserAgent,
|
|
8
|
-
} from "./const.js";
|
|
9
|
-
|
|
10
|
-
/** 匿名头:登录轮询等不需要 token 的调用(官方客户端在这些路径也不带 Authorization)。 */
|
|
11
|
-
export function raccoonAnonymousHeaders({ env = process.env, accept = "application/json" } = {}) {
|
|
12
|
-
return {
|
|
13
|
-
Accept: accept,
|
|
14
|
-
"Content-Type": "application/json",
|
|
15
|
-
"User-Agent": raccoonUserAgent(env),
|
|
16
|
-
"X-Raccoon-Language": RACCOON_CLIENT_LANGUAGE,
|
|
17
|
-
"X-Client-Platform": RACCOON_CLIENT_PLATFORM,
|
|
18
|
-
"X-Client-Version": raccoonClientVersion(env),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 带 token 的出站头。
|
|
24
|
-
* - `X-Org-Code`:个人号为空串(官方客户端总是发送该头)。
|
|
25
|
-
* - `X-Client-Device-ID`:**为空则不发**(个人号可选;被网关拒时的退化路径)。
|
|
26
|
-
* - `X-Client-Version` / `User-Agent` 走 env 可配(见 const.js)。
|
|
27
|
-
*/
|
|
28
|
-
export function raccoonAuthHeaders(credential, { env = process.env, accept = "application/json", version } = {}) {
|
|
29
|
-
const headers = {
|
|
30
|
-
Accept: accept,
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
Authorization: `Bearer ${String(credential?.access_token ?? "")}`,
|
|
33
|
-
"X-Org-Code": String(credential?.office_identity ?? ""),
|
|
34
|
-
"X-Raccoon-Language": RACCOON_CLIENT_LANGUAGE,
|
|
35
|
-
"X-Client-Platform": RACCOON_CLIENT_PLATFORM,
|
|
36
|
-
"X-Client-Version": version || raccoonClientVersion(env),
|
|
37
|
-
"User-Agent": raccoonUserAgent(env),
|
|
38
|
-
};
|
|
39
|
-
const deviceId = String(credential?.device_id ?? "").trim();
|
|
40
|
-
if (deviceId) headers["X-Client-Device-ID"] = deviceId;
|
|
41
|
-
return headers;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** 聊天的 Accept 要比 JSON 宽:上游流式与非流式共用同一端点。 */
|
|
45
|
-
export const RACCOON_CHAT_ACCEPT = "text/event-stream, application/json";
|
|
1
|
+
// raccoon 出站请求头:与官方客户端同形(逐字段对齐 pack.js 的 raccoonHeaders)。
|
|
2
|
+
// 风控按版本/头判断,形态错会 401/403 —— 所以这里不做「优化」,只做照抄。
|
|
3
|
+
import {
|
|
4
|
+
RACCOON_CLIENT_LANGUAGE,
|
|
5
|
+
RACCOON_CLIENT_PLATFORM,
|
|
6
|
+
raccoonClientVersion,
|
|
7
|
+
raccoonUserAgent,
|
|
8
|
+
} from "./const.js";
|
|
9
|
+
|
|
10
|
+
/** 匿名头:登录轮询等不需要 token 的调用(官方客户端在这些路径也不带 Authorization)。 */
|
|
11
|
+
export function raccoonAnonymousHeaders({ env = process.env, accept = "application/json" } = {}) {
|
|
12
|
+
return {
|
|
13
|
+
Accept: accept,
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
"User-Agent": raccoonUserAgent(env),
|
|
16
|
+
"X-Raccoon-Language": RACCOON_CLIENT_LANGUAGE,
|
|
17
|
+
"X-Client-Platform": RACCOON_CLIENT_PLATFORM,
|
|
18
|
+
"X-Client-Version": raccoonClientVersion(env),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 带 token 的出站头。
|
|
24
|
+
* - `X-Org-Code`:个人号为空串(官方客户端总是发送该头)。
|
|
25
|
+
* - `X-Client-Device-ID`:**为空则不发**(个人号可选;被网关拒时的退化路径)。
|
|
26
|
+
* - `X-Client-Version` / `User-Agent` 走 env 可配(见 const.js)。
|
|
27
|
+
*/
|
|
28
|
+
export function raccoonAuthHeaders(credential, { env = process.env, accept = "application/json", version } = {}) {
|
|
29
|
+
const headers = {
|
|
30
|
+
Accept: accept,
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
Authorization: `Bearer ${String(credential?.access_token ?? "")}`,
|
|
33
|
+
"X-Org-Code": String(credential?.office_identity ?? ""),
|
|
34
|
+
"X-Raccoon-Language": RACCOON_CLIENT_LANGUAGE,
|
|
35
|
+
"X-Client-Platform": RACCOON_CLIENT_PLATFORM,
|
|
36
|
+
"X-Client-Version": version || raccoonClientVersion(env),
|
|
37
|
+
"User-Agent": raccoonUserAgent(env),
|
|
38
|
+
};
|
|
39
|
+
const deviceId = String(credential?.device_id ?? "").trim();
|
|
40
|
+
if (deviceId) headers["X-Client-Device-ID"] = deviceId;
|
|
41
|
+
return headers;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 聊天的 Accept 要比 JSON 宽:上游流式与非流式共用同一端点。 */
|
|
45
|
+
export const RACCOON_CHAT_ACCEPT = "text/event-stream, application/json";
|