@ikenxuan/amagi 7.0.0-beta.7 → 7.0.0-beta.8
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/dist/api-BENpfNA1.d.ts +137 -0
- package/dist/bv2av-DUm4x7as.mjs +52 -0
- package/dist/bv2av-Dw0qrO9g.cjs +63 -0
- package/dist/default/index.cjs +4 -3
- package/dist/default/index.d.ts +3 -2
- package/dist/default/index.mjs +2 -1
- package/dist/{api-BWAjDuTS.d.ts → endpoint-QPR2Pp-S.d.ts} +2 -138
- package/dist/exports/compat.cjs +4 -3
- package/dist/exports/compat.d.ts +4 -2
- package/dist/exports/compat.mjs +2 -1
- package/dist/exports/sign-steps.cjs +1 -1
- package/dist/exports/sign-steps.d.ts +2 -1
- package/dist/exports/sign-steps.mjs +1 -1
- package/dist/exports/signing.cjs +128 -427
- package/dist/exports/signing.d.ts +286 -1
- package/dist/exports/signing.mjs +50 -371
- package/dist/he-CuCIF0C7.cjs +3898 -0
- package/dist/he-q7589Kp7.mjs +3419 -0
- package/dist/{index-NkyAecv9.d.ts → index-BSNuPU57.d.ts} +4 -32
- package/dist/secsdkWebSign-DoqHj52s.d.ts +122 -0
- package/dist/{src-CNU3-zoM.cjs → src-BMmA6XJW.cjs} +392 -92
- package/dist/{src-BM1fZuVp.mjs → src-ZHFzJ9Lf.mjs} +376 -64
- package/dist/{steps-BO8NYQHx.cjs → steps-CpeOWLPf.cjs} +9 -1491
- package/dist/{steps-DQlwODdh.mjs → steps-bZrsb4W6.mjs} +2 -1459
- package/package.json +2 -2
- package/dist/tokens-BFqSOfhx.mjs +0 -1001
- package/dist/tokens-DHKAuPVW.cjs +0 -1294
package/dist/exports/compat.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { $ as unwrap, A as douyinFetcher, B as xiaohongshuSign, C as ValidationError, Ct as parseDmSegMobileReply, D as createBoundKuaishouFetcher, E as xiaohongshuFetcher, F as bilibiliFetcher, G as createDouyinRoutes, H as createKuaishouRoutes, I as createBoundBilibiliFetcher, J as bilibiliUtils, K as passport_exports, L as createClient, M as requestPassportQrcode, N as sendPassportVerifyCode, O as kuaishouFetcher, P as validatePassportVerifyCode, Q as isSuccess, R as xiaohongshuUtils, S as ApiError, St as emitNetworkRetry, T as createBoundXiaohongshuFetcher,
|
|
1
|
+
import { $ as unwrap, A as douyinFetcher, B as xiaohongshuSign, C as ValidationError, Ct as parseDmSegMobileReply, D as createBoundKuaishouFetcher, E as xiaohongshuFetcher, F as bilibiliFetcher, G as createDouyinRoutes, H as createKuaishouRoutes, I as createBoundBilibiliFetcher, J as bilibiliUtils, K as passport_exports, L as createClient, M as requestPassportQrcode, N as sendPassportVerifyCode, O as kuaishouFetcher, P as validatePassportVerifyCode, Q as isSuccess, R as xiaohongshuUtils, S as ApiError, St as emitNetworkRetry, T as createBoundXiaohongshuFetcher, U as kuaishouSign, V as kuaishouUtils, W as douyinUtils, X as AmagiThrownError, Y as createBilibiliRoutes, Z as isFailure, _ as validateBilibiliParams, _t as emitLogError, a as AdditionalType, at as wbi_sign, b as validateXiaohongshuParams, bt as emitLogWarn, c as createAmagiClient, ct as fetchResponse, d as assertValidBilibiliParams, dt as emitApiError, et as AMAGI_BUS_EVENT_NAMES, f as assertValidDouyinParams, ft as emitApiSuccess, g as createSuccessResponse, gt as emitLogDebug, h as createErrorResponse, ht as emitLog, i as DynamicType, it as qtparam, j as checkPassportQrcode, k as createBoundDouyinFetcher, l as kuaishouApiUrls, lt as isNetworkErrorResult, m as assertValidXiaohongshuParams, mt as emitHttpResponse, n as CreateApp, nt as KUAISHOU_PC_CAPTCHA_RESULT, o as MajorType, ot as bilibiliApiUrls, p as assertValidKuaishouParams, pt as emitHttpRequest, q as douyinSign, r as amagi, rt as parseKuaishouCaptcha, s as CommentType, st as fetchData, tt as KUAISHOU_H5_CAPTCHA_RESULT, u as douyinApiUrls, ut as amagiEvents, v as validateDouyinParams, vt as emitLogInfo, w as handleError, x as xiaohongshuApiUrls, xt as emitNetworkError, y as validateKuaishouParams, yt as emitLogMark, z as createXiaohongshuRoutes } from "../src-ZHFzJ9Lf.mjs";
|
|
2
|
+
import { n as bv2av, t as av2bv } from "../bv2av-DUm4x7as.mjs";
|
|
2
3
|
//#region src/exports/compat.ts
|
|
3
4
|
/** 模块级只执行一次的迁移提示(不刷屏) */
|
|
4
5
|
emitLogWarn("[@ikenxuan/amagi/compat] 你正在使用 v6 兼容入口;v7 迁移说明见 https://github.com/ikenxuan/amagi (v8 将移除 compat)");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_steps = require("../steps-
|
|
2
|
+
const require_steps = require("../steps-CpeOWLPf.cjs");
|
|
3
3
|
//#region src/exports/sign-steps.ts
|
|
4
4
|
/**
|
|
5
5
|
* 反爬参数的**原子 step 工厂**:给 `client.<平台>.request` 做精细签名控制用。
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as SignDecl, d as SignStep, l as SignFn, u as SignPhase } from "../endpoint-QPR2Pp-S.js";
|
|
2
|
+
import { a as KuaishouLiveApiRequest } from "../api-BENpfNA1.js";
|
|
2
3
|
//#region src/platforms/bilibili/sign/steps.d.ts
|
|
3
4
|
/** `wbi`:URL 追加 `&wts=&w_rid=`(comments / userDynamicList / userSpaceInfo) */
|
|
4
5
|
declare const wbi: () => SignStep;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as douyinXBogus, f as msToken, l as aBogus, m as xBogus, n as traceId, p as secsdk, r as xs, s as hxfalcon, t as rap, u as douyinBogus, v as qtparam, y as wbi } from "../steps-bZrsb4W6.mjs";
|
|
2
2
|
//#region src/exports/sign-steps.ts
|
|
3
3
|
/**
|
|
4
4
|
* 反爬参数的**原子 step 工厂**:给 `client.<平台>.request` 做精细签名控制用。
|
package/dist/exports/signing.cjs
CHANGED
|
@@ -1,437 +1,138 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* 各三个 SM3 字节。输入无法从摘要倒推出来,所以这个模块**从不假装能**。它做的是
|
|
27
|
-
* **校验**:给它一个候选值,它回答这个候选是不是签名封住的那一个,以及有多少位证据
|
|
28
|
-
* 支持这个结论。这才是哈希真正的逆运算,而且比猜一个值有用得多——它把
|
|
29
|
-
* 「我的请求被拒了」变成「我发的这个签名是对着另一条 URL 算的」。
|
|
30
|
-
* - **根本没被计算**:`msToken` 与访客令牌由平台签发或随机抽出来,底下没有明文可找。
|
|
31
|
-
* 说出这一点就是答案,而不是「没能给出答案」。
|
|
32
|
-
*
|
|
33
|
-
* 每个字段都携带自己属于哪一类,读者不会被留在「标签旁边那个数字是读出来的还是推出来的」
|
|
34
|
-
* 这种猜测里。
|
|
35
|
-
*
|
|
36
|
-
* 这里没有任何东西用于签名。它只跑在调用方已经持有的值上,不碰网络、不消耗身份。
|
|
37
|
-
*/
|
|
38
|
-
/** 字段是什么。控制台按它上色,调用方也可以对它分支 */
|
|
39
|
-
const KIND = {
|
|
40
|
-
/** 从载荷里直接读出,就是签名者放进去的东西 */
|
|
41
|
-
PLAIN: "plain",
|
|
42
|
-
/** 一个时钟,同时给出原始数字与 ISO-8601 时刻 */
|
|
43
|
-
TIME: "time",
|
|
44
|
-
/** 哈希或哈希的一个字节。输入不可还原——见 checks */
|
|
45
|
-
DIGEST: "digest",
|
|
46
|
-
/** 内部冗余。在这里重算,这正是解码得以自证的依据 */
|
|
47
|
-
CHECKSUM: "checksum",
|
|
48
|
-
/** SDK 对它自认为身处其中的页面所做的回报 */
|
|
49
|
-
ENVIRONMENT: "environment",
|
|
50
|
-
/** 每次捕获里都有、但含义未被确认的东西。命名它,而不是编造它 */
|
|
51
|
-
OPAQUE: "opaque"
|
|
52
|
-
};
|
|
53
|
-
/** 一个参数底下为什么没有明文 */
|
|
54
|
-
const REASON = {
|
|
55
|
-
MALFORMED: "malformed",
|
|
56
|
-
NOT_COMPUTED: "not_computed",
|
|
57
|
-
ONE_WAY: "one_way",
|
|
58
|
-
UNKNOWN_PARAMETER: "unknown_parameter"
|
|
59
|
-
};
|
|
60
|
-
/** 候选输入是不是签名封住的那一个 */
|
|
61
|
-
const CHECK = {
|
|
62
|
-
MATCH: "match",
|
|
63
|
-
DIFFERS: "differs",
|
|
64
|
-
NOT_SUPPLIED: "not_supplied"
|
|
65
|
-
};
|
|
66
|
-
/** 每条摘要链钉住输入的位数 */
|
|
67
|
-
const CHAIN_BITS = 24;
|
|
68
|
-
const FORTNIGHT_MS = 12096e5;
|
|
69
|
-
/** 毫秒时钟转 ISO-8601 UTC;数字荒诞到解析不了时退回原样,因为那通常正是它最有意思的地方 */
|
|
70
|
-
const instant = (milliseconds) => {
|
|
71
|
-
const date = new Date(milliseconds);
|
|
72
|
-
return Number.isNaN(date.getTime()) ? String(milliseconds) : date.toISOString().replace(".000Z", "Z");
|
|
73
|
-
};
|
|
74
|
-
const clockField = (name, milliseconds, detail) => ({
|
|
75
|
-
name,
|
|
76
|
-
value: `${milliseconds} (${instant(milliseconds)})`,
|
|
77
|
-
kind: KIND.TIME,
|
|
78
|
-
detail
|
|
79
|
-
});
|
|
80
|
-
const check = (name, expected, actual, covered) => expected.join(",") === actual.join(",") ? {
|
|
81
|
-
name,
|
|
82
|
-
status: CHECK.MATCH,
|
|
83
|
-
bits: CHAIN_BITS,
|
|
84
|
-
covered
|
|
85
|
-
} : {
|
|
86
|
-
name,
|
|
87
|
-
status: CHECK.DIFFERS,
|
|
88
|
-
bits: CHAIN_BITS
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* 拆开一份 `a_bogus`,并就调用方给出的候选值做校验。
|
|
92
|
-
* @param value - `a_bogus` 的值
|
|
93
|
-
* @param candidates - 候选的 query / body / User-Agent,给哪个校验哪个
|
|
94
|
-
* @returns 拆解结果;格式不合法时 `recovered` 为 `false` 并给出 `reason`
|
|
95
|
-
*/
|
|
96
|
-
const decodeABogus = (value, candidates = {}) => {
|
|
97
|
-
const problem = require_tokens.structureError(value);
|
|
98
|
-
if (problem !== null) return {
|
|
99
|
-
parameter: "a_bogus",
|
|
100
|
-
platform: "douyin",
|
|
101
|
-
algorithm: "a_bogus",
|
|
102
|
-
recovered: false,
|
|
103
|
-
reason: `${REASON.MALFORMED}:${problem}`,
|
|
104
|
-
fields: [],
|
|
105
|
-
checks: [],
|
|
106
|
-
notes: []
|
|
107
|
-
};
|
|
108
|
-
const raw = require_tokens.decode(value);
|
|
109
|
-
const scalars = raw.fields;
|
|
110
|
-
const fortnights = scalars.L26;
|
|
111
|
-
const fields = [
|
|
112
|
-
{
|
|
113
|
-
name: "header_magic",
|
|
114
|
-
value: `[${raw.headerMagic.join(", ")}]`,
|
|
115
|
-
kind: KIND.PLAIN
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "sdk_version",
|
|
119
|
-
value: raw.sdkVersion.join("."),
|
|
120
|
-
kind: KIND.PLAIN
|
|
121
|
-
},
|
|
122
|
-
clockField("now_ms", raw.nowMs),
|
|
123
|
-
clockField("ink_ms", raw.inkMs, "now_ms - 1,SDK 对自己的存活检查"),
|
|
124
|
-
{
|
|
125
|
-
name: "aid",
|
|
126
|
-
value: String(raw.aid),
|
|
127
|
-
kind: KIND.PLAIN
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
name: "page_id",
|
|
131
|
-
value: String(raw.pageId),
|
|
132
|
-
kind: KIND.PLAIN
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: "fortnights",
|
|
136
|
-
value: `${fortnights} (${instant(require_tokens.FORTNIGHT_EPOCH_MS + fortnights * FORTNIGHT_MS)})`,
|
|
137
|
-
kind: KIND.PLAIN,
|
|
138
|
-
detail: "自 a_bogus 纪元起的「两周」数"
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: "browser_info",
|
|
142
|
-
value: raw.browserInfo,
|
|
143
|
-
kind: KIND.ENVIRONMENT,
|
|
144
|
-
detail: "屏幕|可视区|…"
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: "tail",
|
|
148
|
-
value: raw.tail,
|
|
149
|
-
kind: KIND.PLAIN,
|
|
150
|
-
detail: "(now_ms + 3) & 0xFF"
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: "env_flags",
|
|
154
|
-
value: String(raw.envFlags),
|
|
155
|
-
kind: KIND.ENVIRONMENT
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: "detect_flags",
|
|
159
|
-
value: String(raw.detectFlags),
|
|
160
|
-
kind: KIND.ENVIRONMENT
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: "call_bucket",
|
|
164
|
-
value: String(raw.callBucket),
|
|
165
|
-
kind: KIND.ENVIRONMENT
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: "tripwire",
|
|
169
|
-
value: String(raw.tripwire),
|
|
170
|
-
kind: KIND.ENVIRONMENT
|
|
171
|
-
}
|
|
172
|
-
];
|
|
173
|
-
const inputByChain = {
|
|
174
|
-
query: candidates.query,
|
|
175
|
-
body: candidates.body,
|
|
176
|
-
user_agent: candidates.userAgent
|
|
177
|
-
};
|
|
178
|
-
const emptyBody = require_tokens.chainBytes(require_tokens.DIGEST_CHAINS.body, require_tokens.digestWith("", require_tokens.SALT));
|
|
179
|
-
const checks = [];
|
|
180
|
-
for (const [name, chain] of Object.entries(require_tokens.DIGEST_CHAINS)) {
|
|
181
|
-
const carried = chain.slots.map((slot) => scalars[slot]);
|
|
182
|
-
let detail = `${name} 链的 3 个字节,落在 ${chain.slots.join(", ")}`;
|
|
183
|
-
if (name === "body" && carried.join(",") === emptyBody.join(",")) detail = `${detail};与空 body 一致,GET 就是这种情况`;
|
|
184
|
-
fields.push({
|
|
185
|
-
name: `${name}_digest`,
|
|
186
|
-
value: carried.join(" "),
|
|
187
|
-
kind: KIND.DIGEST,
|
|
188
|
-
detail
|
|
189
|
-
});
|
|
190
|
-
const candidate = inputByChain[name];
|
|
191
|
-
if (candidate === void 0) {
|
|
192
|
-
checks.push({
|
|
193
|
-
name,
|
|
194
|
-
status: CHECK.NOT_SUPPLIED,
|
|
195
|
-
bits: CHAIN_BITS
|
|
196
|
-
});
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
const digest = name === "user_agent" ? require_tokens.userAgentDigest(candidate, {
|
|
200
|
-
envFlags: raw.envFlags,
|
|
201
|
-
detectFlags: raw.detectFlags
|
|
202
|
-
}) : require_tokens.digestWith(candidate, require_tokens.SALT);
|
|
203
|
-
checks.push(check(name, require_tokens.chainBytes(chain, digest), carried, candidate));
|
|
2
|
+
const require_bv2av = require("../bv2av-Dw0qrO9g.cjs");
|
|
3
|
+
const require_he = require("../he-CuCIF0C7.cjs");
|
|
4
|
+
//#region src/platforms/xiaohongshu/sign/shape.ts
|
|
5
|
+
/** x-s 之外的头部按什么规则查 */
|
|
6
|
+
const CHECKS = {
|
|
7
|
+
"x-t": {
|
|
8
|
+
label: "13 位毫秒时间戳",
|
|
9
|
+
test: (value) => /^1[0-9]{12}$/.test(value),
|
|
10
|
+
why: "generateXT 返回 Date.now(),13 位毫秒"
|
|
11
|
+
},
|
|
12
|
+
"x-b3-traceid": {
|
|
13
|
+
label: "16 位十六进制",
|
|
14
|
+
test: (value) => /^[0-9a-f]{16}$/.test(value),
|
|
15
|
+
why: "generateXB3Traceid 的输出形状"
|
|
16
|
+
},
|
|
17
|
+
"x-xray-traceid": {
|
|
18
|
+
label: "32 位十六进制",
|
|
19
|
+
test: (value) => /^[0-9a-f]{32}$/.test(value),
|
|
20
|
+
why: "generateXrayTraceid 的输出形状"
|
|
21
|
+
},
|
|
22
|
+
"x-s-common": {
|
|
23
|
+
label: "非空",
|
|
24
|
+
test: (value) => value.length > 0,
|
|
25
|
+
why: "压缩+编码的不透明串,底下没有明文可找"
|
|
204
26
|
}
|
|
205
|
-
return {
|
|
206
|
-
parameter: "a_bogus",
|
|
207
|
-
platform: "douyin",
|
|
208
|
-
algorithm: "a_bogus",
|
|
209
|
-
recovered: true,
|
|
210
|
-
fields,
|
|
211
|
-
checks,
|
|
212
|
-
notes: ["checksum_verified", "noise_not_recoverable"]
|
|
213
|
-
};
|
|
214
27
|
};
|
|
215
28
|
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* 也不依赖抖音是否接受了你的请求——这正是它能发现「请求照样成功但常量已经过期」的原因。
|
|
221
|
-
*
|
|
222
|
-
* ## 只有 query 链参与判定
|
|
223
|
-
*
|
|
224
|
-
* body 链在 GET 上与空串一致,而 UA 链**根本不吃盐值**(它走 RC4 加单次 SM3),
|
|
225
|
-
* 两条对判定都没有贡献,所以这里的候选盐值只对 query 生效。证据量是 24 位,
|
|
226
|
-
* 实战足够——但要说明,而不是藏起来。
|
|
227
|
-
* @param value - 一份真实的 `a_bogus`
|
|
228
|
-
* @param query - 该签名所覆盖的那条 query(不含 `a_bogus` 自身)
|
|
229
|
-
* @param salts - 待测的盐值候选
|
|
230
|
-
* @returns 判定结果
|
|
29
|
+
* 检查一个签名头部的形状。
|
|
30
|
+
* @param name - 头名(`x-s` / `x-s-common` / `x-t` / `x-xray-traceid` / `x-b3-traceid`)
|
|
31
|
+
* @param value - 头部的值
|
|
32
|
+
* @returns 检查结果;不认识的头名按「非空」处理
|
|
231
33
|
*/
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
};
|
|
238
|
-
const chain = require_tokens.DIGEST_CHAINS.query;
|
|
239
|
-
const scalars = require_tokens.decode(value).fields;
|
|
240
|
-
const observed = chain.slots.map((slot) => scalars[slot]);
|
|
241
|
-
const results = salts.map((salt) => {
|
|
242
|
-
const predicted = require_tokens.chainBytes(chain, require_tokens.digestWith(query, salt));
|
|
243
|
-
return {
|
|
244
|
-
salt,
|
|
245
|
-
predicted,
|
|
246
|
-
matched: predicted.join(",") === observed.join(",")
|
|
34
|
+
const inspectXhsHeader = (name, value) => {
|
|
35
|
+
if (name === "x-s") {
|
|
36
|
+
const inspection = {
|
|
37
|
+
name,
|
|
38
|
+
problems: []
|
|
247
39
|
};
|
|
248
|
-
|
|
40
|
+
if (value.startsWith("XYW_")) {
|
|
41
|
+
inspection.format = "XYW";
|
|
42
|
+
if (value.length <= 4) inspection.problems.push("XYW_ 之后是空的");
|
|
43
|
+
} else if (value.startsWith("XYS_")) {
|
|
44
|
+
inspection.format = "XYS";
|
|
45
|
+
if (value.length <= 4) inspection.problems.push("XYS_ 之后是空的");
|
|
46
|
+
} else inspection.problems.push("既不是 XYS_ 也不是 XYW_ 开头 —— 2026-03 之后数据接口会以 406 拒收");
|
|
47
|
+
return inspection;
|
|
48
|
+
}
|
|
49
|
+
const check = CHECKS[name];
|
|
50
|
+
if (!check) return {
|
|
51
|
+
name,
|
|
52
|
+
problems: value.length > 0 ? [] : ["是空的"]
|
|
53
|
+
};
|
|
249
54
|
return {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
results
|
|
55
|
+
name,
|
|
56
|
+
problems: check.test(value) ? [] : [`不符合 ${check.label}(${check.why})`]
|
|
253
57
|
};
|
|
254
58
|
};
|
|
255
|
-
/**
|
|
256
|
-
* 只按形状猜一个值是什么参数。用于调用方不知道自己在看什么的时候。
|
|
257
|
-
*
|
|
258
|
-
* 只做形状判断,不做任何密码学断言——猜错时返回 `null` 比返回一个错答案好。
|
|
259
|
-
* @param value - 待识别的值
|
|
260
|
-
* @returns 参数名或 `null`
|
|
261
|
-
*/
|
|
262
|
-
const identify = (value) => {
|
|
263
|
-
if (!value) return null;
|
|
264
|
-
if (require_tokens.structureError(value) === null) return "a_bogus";
|
|
265
|
-
if (/^verify_[0-9a-z]+_/.test(value)) return "verifyFp";
|
|
266
|
-
if (value.length === 28 && /^[A-Za-z0-9_=+/-]+$/.test(value)) return "X-Bogus";
|
|
267
|
-
if (value.length >= 100 && /^[A-Za-z0-9+-]+=*$/.test(value)) return "msToken";
|
|
268
|
-
if (/^[0-9a-f]{32}$/i.test(value)) return "x-secsdk-web-signature";
|
|
269
|
-
return null;
|
|
270
|
-
};
|
|
271
|
-
/**
|
|
272
|
-
* 签名**一定**不覆盖的参数。
|
|
273
|
-
*
|
|
274
|
-
* `a_bogus` 是签名自身;`timestamp` 与 `x-secsdk-web-signature` 由 secsdk 在签名
|
|
275
|
-
* **之后**追加 —— 这一点在真实捕获与 amagi 自己的管线里一致。
|
|
276
|
-
*/
|
|
277
|
-
const NEVER_SIGNED = [
|
|
278
|
-
"a_bogus",
|
|
279
|
-
"timestamp",
|
|
280
|
-
"x-secsdk-web-signature"
|
|
281
|
-
];
|
|
282
|
-
/**
|
|
283
|
-
* 是否被签名覆盖**随管线而异**的参数。
|
|
284
|
-
*
|
|
285
|
-
* 不要猜。真实浏览器捕获里 `uifid` 在 `a_bogus` **之前**(因而被签名覆盖),
|
|
286
|
-
* 而 amagi 的管线是 `webid → a_bogus → secsdk`,`uifid` 由 secsdk 在之后补上
|
|
287
|
-
* (因而没被覆盖)。同一个参数名,两边结论相反 —— 所以这里把四种参数做成
|
|
288
|
-
* 2⁴ 种组合逐个试,命中的那一种就是答案。
|
|
289
|
-
*/
|
|
290
|
-
const SIGNING_AMBIGUOUS = [
|
|
291
|
-
"uifid",
|
|
292
|
-
"msToken",
|
|
293
|
-
"verifyFp",
|
|
294
|
-
"fp"
|
|
295
|
-
];
|
|
296
|
-
/**
|
|
297
|
-
* 反推一份签名**当时覆盖的那条 query**,以及用的是哪个盐值。
|
|
298
|
-
*
|
|
299
|
-
* 这是 {@link diagnoseSalt} 的加强版:那份要求调用方已经知道签名覆盖了哪些参数,
|
|
300
|
-
* 而线上抓来的 URL 里,`uifid` / `msToken` / `verifyFp` / `fp` 到底在签名前还是
|
|
301
|
-
* 签名后,取决于页面当时挂的是哪条管线 —— 同一份代码在浏览器里和在 amagi 里顺序
|
|
302
|
-
* 就是不一样的。与其让调用方去猜,这里把 2⁴ 种组合全试一遍,报出命中的那一种。
|
|
303
|
-
*
|
|
304
|
-
* 命中意味着**两件事同时成立**:这是一份结构良好的 a_bogus,且它的 query 链在给定
|
|
305
|
-
* 盐值下重现了这条 query。所以它是「验证」而不只是「解码」。
|
|
306
|
-
* @param signature - `a_bogus` 的值(未做 URL 编码)
|
|
307
|
-
* @param url - 已签名的完整 URL
|
|
308
|
-
* @param salts - 待搜的盐值候选
|
|
309
|
-
* @returns 命中结果;没有任何组合命中时返回 `null`
|
|
310
|
-
*/
|
|
311
|
-
const recoverSignedQuery = (signature, url, salts = [require_tokens.SALT]) => {
|
|
312
|
-
if (require_tokens.structureError(signature) !== null) return null;
|
|
313
|
-
const chain = require_tokens.DIGEST_CHAINS.query;
|
|
314
|
-
const scalars = require_tokens.decode(signature).fields;
|
|
315
|
-
const observed = chain.slots.map((slot) => scalars[slot]).join(",");
|
|
316
|
-
let tried = 0;
|
|
317
|
-
for (let mask = 0; mask < 1 << SIGNING_AMBIGUOUS.length; mask++) {
|
|
318
|
-
const kept = SIGNING_AMBIGUOUS.filter((_, index) => (mask & 1 << index) !== 0);
|
|
319
|
-
const parsed = new URL(url);
|
|
320
|
-
for (const name of NEVER_SIGNED) parsed.searchParams.delete(name);
|
|
321
|
-
for (const name of SIGNING_AMBIGUOUS) if (!kept.includes(name)) parsed.searchParams.delete(name);
|
|
322
|
-
const query = parsed.searchParams.toString();
|
|
323
|
-
for (const salt of salts) {
|
|
324
|
-
tried++;
|
|
325
|
-
if (require_tokens.chainBytes(chain, require_tokens.digestWith(query, salt)).join(",") === observed) return {
|
|
326
|
-
query,
|
|
327
|
-
kept,
|
|
328
|
-
salt,
|
|
329
|
-
tried
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return null;
|
|
334
|
-
};
|
|
335
|
-
/**
|
|
336
|
-
* 从一条已签名的 URL 里重建出签名覆盖的那条 query(不再搜索,按固定规则摘参数)。
|
|
337
|
-
*
|
|
338
|
-
* **只在已知管线顺序时使用。** 面对来源不明的 URL 请用 {@link recoverSignedQuery},
|
|
339
|
-
* 它会搜索并告诉你哪条重建成立。保留这个函数是为了让「摘掉签名参数」这件事在
|
|
340
|
-
* 需要确定行为的地方仍然可用。
|
|
341
|
-
* @param url - 已签名的完整 URL
|
|
342
|
-
* @returns 摘掉 `a_bogus` / `timestamp` / `x-secsdk-web-signature` 之后的 query
|
|
343
|
-
*/
|
|
344
|
-
const signedQueryOf = (url) => {
|
|
345
|
-
const parsed = new URL(url);
|
|
346
|
-
for (const name of NEVER_SIGNED) parsed.searchParams.delete(name);
|
|
347
|
-
return parsed.searchParams.toString();
|
|
348
|
-
};
|
|
349
|
-
/**
|
|
350
|
-
* 拆开一条 URL 上所有认得出的签名参数。
|
|
351
|
-
*
|
|
352
|
-
* 会先尝试 {@link recoverSignedQuery} 找出签名真正覆盖的那条 query;找不到时退回
|
|
353
|
-
* {@link signedQueryOf} 的结果,并把这个不确定性体现在 `notes` 里。
|
|
354
|
-
* @param url - 已签名的完整 URL
|
|
355
|
-
* @param options - 可选的 User-Agent、显式 query 与盐值候选
|
|
356
|
-
* @returns 每个参数一条拆解结果
|
|
357
|
-
*/
|
|
358
|
-
const decodeUrl = (url, options = {}) => {
|
|
359
|
-
const aBogus = new URL(url).searchParams.get("a_bogus");
|
|
360
|
-
if (aBogus === null) return [];
|
|
361
|
-
const signature = decodeURIComponent(aBogus);
|
|
362
|
-
const recovery = options.query ? null : recoverSignedQuery(signature, url, options.salts ?? ["dhzx"]);
|
|
363
|
-
const query = options.query ?? recovery?.query ?? signedQueryOf(url);
|
|
364
|
-
const decoded = decodeABogus(signature, {
|
|
365
|
-
query,
|
|
366
|
-
body: options.body,
|
|
367
|
-
userAgent: options.userAgent
|
|
368
|
-
});
|
|
369
|
-
if (recovery) {
|
|
370
|
-
decoded.notes.push(`query_recovered:kept=${recovery.kept.join("+") || "(none)"}`);
|
|
371
|
-
decoded.fields.push({
|
|
372
|
-
name: "signed_query",
|
|
373
|
-
value: recovery.query,
|
|
374
|
-
kind: KIND.PLAIN,
|
|
375
|
-
detail: `从 ${recovery.tried} 种组合中命中:保留了 ${recovery.kept.join("、") || "(不保留任何可选参数)"}`
|
|
376
|
-
});
|
|
377
|
-
} else if (!options.query) decoded.notes.push("query_reconstructed_by_rule:no_combination_matched");
|
|
378
|
-
return [decoded];
|
|
379
|
-
};
|
|
380
59
|
//#endregion
|
|
381
|
-
exports.ABogus =
|
|
382
|
-
exports.AID =
|
|
383
|
-
exports.ALPHABETS =
|
|
384
|
-
exports.CALL_BUCKET =
|
|
385
|
-
exports.
|
|
386
|
-
exports.
|
|
387
|
-
exports.
|
|
388
|
-
exports.
|
|
389
|
-
exports.
|
|
390
|
-
exports.
|
|
391
|
-
exports.
|
|
392
|
-
exports.
|
|
393
|
-
exports.
|
|
394
|
-
exports.
|
|
395
|
-
exports.
|
|
396
|
-
exports.
|
|
397
|
-
exports.
|
|
398
|
-
exports.
|
|
399
|
-
exports.
|
|
400
|
-
exports.
|
|
401
|
-
exports.
|
|
402
|
-
exports.
|
|
403
|
-
exports.
|
|
404
|
-
exports.
|
|
405
|
-
exports.
|
|
406
|
-
exports.
|
|
407
|
-
exports.
|
|
408
|
-
exports.
|
|
409
|
-
exports.
|
|
410
|
-
exports.
|
|
411
|
-
exports.
|
|
412
|
-
exports.
|
|
413
|
-
exports.
|
|
414
|
-
exports.
|
|
415
|
-
exports.
|
|
416
|
-
exports.
|
|
417
|
-
exports.
|
|
418
|
-
exports.
|
|
419
|
-
exports.
|
|
420
|
-
exports.
|
|
421
|
-
exports.
|
|
422
|
-
exports.
|
|
423
|
-
exports.
|
|
424
|
-
exports.
|
|
425
|
-
exports.
|
|
426
|
-
exports.
|
|
427
|
-
exports.
|
|
428
|
-
exports.
|
|
429
|
-
exports.
|
|
430
|
-
exports.
|
|
431
|
-
exports.
|
|
432
|
-
exports.
|
|
433
|
-
exports.
|
|
434
|
-
exports.
|
|
435
|
-
exports.
|
|
436
|
-
exports.
|
|
437
|
-
exports.
|
|
60
|
+
exports.ABogus = require_he.ABogus;
|
|
61
|
+
exports.AID = require_he.AID;
|
|
62
|
+
exports.ALPHABETS = require_he.ALPHABETS;
|
|
63
|
+
exports.CALL_BUCKET = require_he.CALL_BUCKET;
|
|
64
|
+
exports.CANVAS_CONSTANT = require_he.CANVAS_CONSTANT;
|
|
65
|
+
exports.CHECK = require_he.CHECK;
|
|
66
|
+
exports.DEFAULT_BROWSER_INFO = require_he.DEFAULT_BROWSER_INFO;
|
|
67
|
+
exports.DETECT_FLAGS = require_he.DETECT_FLAGS;
|
|
68
|
+
exports.DIGEST_CHAINS = require_he.DIGEST_CHAINS;
|
|
69
|
+
exports.DOUYIN_MS_TOKEN_LENGTH = require_he.DOUYIN_MS_TOKEN_LENGTH;
|
|
70
|
+
exports.DOUYIN_MS_TOKEN_SIZES = require_he.DOUYIN_MS_TOKEN_SIZES;
|
|
71
|
+
exports.DOUYIN_TTWID = require_he.DOUYIN_TTWID;
|
|
72
|
+
exports.DOUYIN_TTWID_PAYLOAD = require_he.DOUYIN_TTWID_PAYLOAD;
|
|
73
|
+
exports.ENV_FLAGS = require_he.ENV_FLAGS;
|
|
74
|
+
exports.FIELD_ORDER = require_he.FIELD_ORDER;
|
|
75
|
+
exports.FORTNIGHT_EPOCH_MS = require_he.FORTNIGHT_EPOCH_MS;
|
|
76
|
+
exports.HEADER_MAGIC = require_he.HEADER_MAGIC;
|
|
77
|
+
exports.KIND = require_he.KIND;
|
|
78
|
+
exports.MS_TOKEN_ALPHABET = require_he.MS_TOKEN_ALPHABET;
|
|
79
|
+
exports.PAGE_ID = require_he.PAGE_ID;
|
|
80
|
+
exports.PAYLOAD_KEY = require_he.PAYLOAD_KEY;
|
|
81
|
+
exports.REASON = require_he.REASON;
|
|
82
|
+
exports.SALT = require_he.SALT;
|
|
83
|
+
exports.SDK_VERSION = require_he.SDK_VERSION;
|
|
84
|
+
exports.SECSDK_SIG_KEY = require_he.SECSDK_SIG_KEY;
|
|
85
|
+
exports.SM3_IV = require_he.SM3_IV;
|
|
86
|
+
exports.TRIPWIRE_LOCKED = require_he.TRIPWIRE_LOCKED;
|
|
87
|
+
exports.TTWID_REGISTER_URL = require_he.TTWID_REGISTER_URL;
|
|
88
|
+
exports.VERIFY_FP_ALPHABET = require_he.VERIFY_FP_ALPHABET;
|
|
89
|
+
exports.VERIFY_FP_LENGTH = require_he.VERIFY_FP_LENGTH;
|
|
90
|
+
exports.WEBSIGN_CONST = require_he.WEBSIGN_CONST;
|
|
91
|
+
exports.XBogus = require_he.XBogus;
|
|
92
|
+
exports.av2bv = require_bv2av.av2bv;
|
|
93
|
+
exports.browserInfoFromScreen = require_he.browserInfoFromScreen;
|
|
94
|
+
exports.buildBrowserInfo = require_he.buildBrowserInfo;
|
|
95
|
+
exports.bv2av = require_bv2av.bv2av;
|
|
96
|
+
exports.canary = require_he.canary;
|
|
97
|
+
exports.chainBytes = require_he.chainBytes;
|
|
98
|
+
exports.computeWbiSignature = require_he.computeWbiSignature;
|
|
99
|
+
exports.decode = require_he.decode;
|
|
100
|
+
exports.decodeABogus = require_he.decodeABogus;
|
|
101
|
+
exports.decodeBase64 = require_he.decodeBase64;
|
|
102
|
+
exports.decodeKuaishouHe = require_he.decodeKuaishouHe;
|
|
103
|
+
exports.decodeUrl = require_he.decodeUrl;
|
|
104
|
+
exports.decodeVerifyFp = require_he.decodeVerifyFp;
|
|
105
|
+
exports.decodeXBogus = require_he.decodeXBogus;
|
|
106
|
+
exports.deriveKuaishouHeHashFieldHex = require_he.deriveKuaishouHeHashFieldHex;
|
|
107
|
+
exports.deriveKuaishouHeHex = require_he.deriveKuaishouHeHex;
|
|
108
|
+
exports.deriveKuaishouPureSignature = require_he.deriveKuaishouPureSignature;
|
|
109
|
+
exports.diagnoseSalt = require_he.diagnoseSalt;
|
|
110
|
+
exports.digestOf = require_he.digestOf;
|
|
111
|
+
exports.digestWith = require_he.digestWith;
|
|
112
|
+
exports.encodeBase64 = require_he.encodeBase64;
|
|
113
|
+
exports.extractUifidFromCookie = require_he.extractUifidFromCookie;
|
|
114
|
+
exports.extractWbiKey = require_he.extractKey;
|
|
115
|
+
exports.genFalseMsToken = require_he.genFalseMsToken;
|
|
116
|
+
exports.genSVWebId = require_he.genSVWebId;
|
|
117
|
+
exports.genVerifyFp = require_he.genVerifyFp;
|
|
118
|
+
exports.identify = require_he.identify;
|
|
119
|
+
exports.inspectXhsHeader = inspectXhsHeader;
|
|
120
|
+
exports.isDecodeProblem = require_he.isDecodeProblem;
|
|
121
|
+
exports.isSecsdkProtected = require_he.isSecsdkProtected;
|
|
122
|
+
exports.jsBytes = require_he.jsBytes;
|
|
123
|
+
exports.msTokenPayload = require_he.msTokenPayload;
|
|
124
|
+
exports.rc4 = require_he.rc4;
|
|
125
|
+
exports.recoverSignedQuery = require_he.recoverSignedQuery;
|
|
126
|
+
exports.secsdkCanonicalQuery = require_he.canonicalQuery;
|
|
127
|
+
exports.signSecsdkWebQuery = require_he.signSecsdkWebQuery;
|
|
128
|
+
exports.signSecsdkWebUrl = require_he.signSecsdkWebUrl;
|
|
129
|
+
exports.signXBogus = require_he.signXBogus;
|
|
130
|
+
exports.signedQueryOf = require_he.signedQueryOf;
|
|
131
|
+
exports.sm3Hash = require_he.sm3Hash;
|
|
132
|
+
exports.sm3Hexdigest = require_he.sm3Hexdigest;
|
|
133
|
+
exports.sm3ToArray = require_he.sm3ToArray;
|
|
134
|
+
exports.structureError = require_he.structureError;
|
|
135
|
+
exports.toBase36 = require_he.toBase36;
|
|
136
|
+
exports.unmaskPair = require_he.unmaskPair;
|
|
137
|
+
exports.userAgentDigest = require_he.userAgentDigest;
|
|
138
|
+
exports.xBogusStructureError = require_he.xBogusStructureError;
|