@ikenxuan/amagi 6.4.0 → 6.6.0

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.
@@ -1,451 +1,13 @@
1
+ import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.mjs";
1
2
  import URL$1 from "node:url";
2
3
  import { EventEmitter } from "node:events";
3
4
  import zod from "zod";
4
5
  import { CryptoConfig, FingerprintGenerator, Xhshow } from "@ikenxuan/xhshow-ts";
5
6
  import crypto, { createCipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
6
7
  import axios, { AxiosError } from "axios";
7
- import { Chalk } from "chalk";
8
8
  import protobuf from "protobufjs";
9
9
  import express from "express";
10
- //#region src/utils/deprecation.ts
11
- /**
12
- * 废弃 API 注册表
13
- * 存储所有已注册的废弃 API 配置
14
- */
15
- const deprecatedApis = /* @__PURE__ */ new Map();
16
- /**
17
- * 注册一个废弃的 API
18
- *
19
- * 将 API 添加到废弃注册表中,后续可通过 checkDeprecation 检查
20
- *
21
- * @param config - 废弃配置对象
22
- *
23
- * @example
24
- * ```typescript
25
- * registerDeprecatedApi({
26
- * name: 'getDouyinData',
27
- * deprecatedIn: '6.0.0',
28
- * removedIn: '7.0.0',
29
- * replacement: 'douyinFetcher',
30
- * throwError: true
31
- * })
32
- * ```
33
- */
34
- function registerDeprecatedApi(config) {
35
- deprecatedApis.set(config.name, config);
36
- }
37
- /**
38
- * 检查 API 是否已废弃并进行相应处理
39
- *
40
- * 如果 API 已注册为废弃,根据配置决定是打印警告还是抛出错误
41
- *
42
- * @param apiName - 要检查的 API 名称
43
- * @throws {DeprecatedApiError} 如果 API 已废弃且配置为抛出错误
44
- *
45
- * @example
46
- * ```typescript
47
- * // 在函数开头调用检查
48
- * function getDouyinData(options) {
49
- * checkDeprecation('getDouyinData')
50
- * // ...
51
- * }
52
- * ```
53
- */
54
- function checkDeprecation(apiName) {
55
- const config = deprecatedApis.get(apiName);
56
- if (!config) return;
57
- const message = buildDeprecationMessage(config);
58
- if (config.throwError) throw new DeprecatedApiError(message, config);
59
- else console.warn(message);
60
- }
61
- /**
62
- * 根据配置构建废弃提示消息
63
- *
64
- * @param config - 废弃配置
65
- * @returns 格式化的废弃提示消息字符串
66
- */
67
- function buildDeprecationMessage(config) {
68
- const lines = [`[DEPRECATED] "${config.name}" 已在 v${config.deprecatedIn} 版本废弃。`];
69
- if (config.replacement) lines.push(`请使用 "${config.replacement}" 替代。`);
70
- else lines.push("此接口已被上游删除,无法继续使用,无可用替代方案。");
71
- if (config.removedIn) lines.push(`此 API 将在 v${config.removedIn} 版本移除。`);
72
- if (config.migrationGuide) lines.push(`迁移指南: ${config.migrationGuide}`);
73
- return lines.join("\n");
74
- }
75
- /**
76
- * 废弃 API 调用错误类
77
- *
78
- * 当调用已废弃且配置为抛出错误的 API 时抛出此错误
79
- * 包含完整的废弃配置信息,便于调试和迁移
80
- */
81
- var DeprecatedApiError = class DeprecatedApiError extends Error {
82
- /** 废弃配置信息,包含替代方案等详细信息 */
83
- config;
84
- /**
85
- * 创建废弃 API 错误实例
86
- *
87
- * @param message - 错误消息
88
- * @param config - 废弃配置对象
89
- */
90
- constructor(message, config) {
91
- super(message);
92
- this.name = "DeprecatedApiError";
93
- this.config = config;
94
- Error.captureStackTrace?.(this, DeprecatedApiError);
95
- }
96
- };
97
- registerDeprecatedApi({
98
- name: "getDouyinData",
99
- deprecatedIn: "6.0.0",
100
- removedIn: "7.0.0",
101
- replacement: "douyinFetcher 或 client.douyin.fetcher",
102
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
103
- throwError: true
104
- });
105
- registerDeprecatedApi({
106
- name: "getBilibiliData",
107
- deprecatedIn: "6.0.0",
108
- removedIn: "7.0.0",
109
- replacement: "bilibiliFetcher 或 client.bilibili.fetcher",
110
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
111
- throwError: true
112
- });
113
- registerDeprecatedApi({
114
- name: "getKuaishouData",
115
- deprecatedIn: "6.0.0",
116
- removedIn: "7.0.0",
117
- replacement: "kuaishouFetcher 或 client.kuaishou.fetcher",
118
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
119
- throwError: true
120
- });
121
- registerDeprecatedApi({
122
- name: "getXiaohongshuData",
123
- deprecatedIn: "6.0.0",
124
- removedIn: "7.0.0",
125
- replacement: "xiaohongshuFetcher 或 client.xiaohongshu.fetcher",
126
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
127
- throwError: true
128
- });
129
- [
130
- {
131
- name: "单个视频作品数据",
132
- replacement: "fetchVideoInfo"
133
- },
134
- {
135
- name: "单个视频下载信息数据",
136
- replacement: "fetchVideoStreamUrl"
137
- },
138
- {
139
- name: "评论数据",
140
- replacement: "fetchComments"
141
- },
142
- {
143
- name: "指定评论的回复",
144
- replacement: "fetchCommentReplies"
145
- },
146
- {
147
- name: "用户主页数据",
148
- replacement: "fetchUserCard"
149
- },
150
- {
151
- name: "用户主页动态列表数据",
152
- replacement: "fetchUserDynamicList"
153
- },
154
- {
155
- name: "用户空间详细信息",
156
- replacement: "fetchUserSpaceInfo"
157
- },
158
- {
159
- name: "获取UP主总播放量",
160
- replacement: "fetchUploaderTotalViews"
161
- },
162
- {
163
- name: "Emoji数据",
164
- replacement: "fetchEmojiList"
165
- },
166
- {
167
- name: "番剧基本信息数据",
168
- replacement: "fetchBangumiInfo"
169
- },
170
- {
171
- name: "番剧下载信息数据",
172
- replacement: "fetchBangumiStreamUrl"
173
- },
174
- {
175
- name: "动态详情数据",
176
- replacement: "fetchDynamicDetail"
177
- },
178
- {
179
- name: "直播间信息",
180
- replacement: "fetchLiveRoomInfo"
181
- },
182
- {
183
- name: "直播间初始化信息",
184
- replacement: "fetchLiveRoomInitInfo"
185
- },
186
- {
187
- name: "登录基本信息",
188
- replacement: "fetchLoginStatus"
189
- },
190
- {
191
- name: "申请二维码",
192
- replacement: "requestLoginQrcode"
193
- },
194
- {
195
- name: "二维码状态",
196
- replacement: "checkQrcodeStatus"
197
- },
198
- {
199
- name: "AV转BV",
200
- replacement: "convertAvToBv"
201
- },
202
- {
203
- name: "BV转AV",
204
- replacement: "convertBvToAv"
205
- },
206
- {
207
- name: "专栏正文内容",
208
- replacement: "fetchArticleContent"
209
- },
210
- {
211
- name: "专栏显示卡片信息",
212
- replacement: "fetchArticleCards"
213
- },
214
- {
215
- name: "专栏文章基本信息",
216
- replacement: "fetchArticleInfo"
217
- },
218
- {
219
- name: "文集基本信息",
220
- replacement: "fetchArticleListInfo"
221
- },
222
- {
223
- name: "实时弹幕",
224
- replacement: "fetchVideoDanmaku"
225
- },
226
- {
227
- name: "从_v_voucher_申请_captcha",
228
- replacement: "requestCaptchaFromVoucher"
229
- },
230
- {
231
- name: "验证验证码结果",
232
- replacement: "validateCaptchaResult"
233
- },
234
- {
235
- name: "视频作品数据",
236
- replacement: "fetchVideoWork"
237
- },
238
- {
239
- name: "图集作品数据",
240
- replacement: "fetchImageAlbumWork"
241
- },
242
- {
243
- name: "合辑作品数据",
244
- replacement: "fetchSlidesWork"
245
- },
246
- {
247
- name: "文字作品数据",
248
- replacement: "fetchTextWork"
249
- },
250
- {
251
- name: "聚合解析",
252
- replacement: "parseWork"
253
- },
254
- {
255
- name: "指定评论回复数据",
256
- replacement: "fetchCommentReplies"
257
- },
258
- {
259
- name: "用户主页视频列表数据",
260
- replacement: "fetchUserVideoList"
261
- },
262
- {
263
- name: "热点词数据",
264
- replacement: "fetchSuggestWords"
265
- },
266
- {
267
- name: "搜索数据",
268
- replacement: "searchContent"
269
- },
270
- {
271
- name: "音乐数据",
272
- replacement: "fetchMusicInfo"
273
- },
274
- {
275
- name: "直播间信息数据",
276
- replacement: "fetchLiveRoomInfo"
277
- },
278
- {
279
- name: "申请二维码数据",
280
- replacement: "requestLoginQrcode"
281
- },
282
- {
283
- name: "动态表情数据",
284
- replacement: "fetchDynamicEmojiList"
285
- },
286
- {
287
- name: "弹幕数据",
288
- replacement: "fetchDanmakuList"
289
- },
290
- {
291
- name: "单个视频作品数据",
292
- replacement: "fetchVideoWork"
293
- },
294
- {
295
- name: "首页推荐数据",
296
- replacement: "fetchHomeFeed"
297
- },
298
- {
299
- name: "单个笔记数据",
300
- replacement: "fetchNoteDetail"
301
- },
302
- {
303
- name: "用户数据",
304
- replacement: "fetchUserProfile"
305
- },
306
- {
307
- name: "用户笔记数据",
308
- replacement: "fetchUserNoteList"
309
- },
310
- {
311
- name: "表情列表",
312
- replacement: "fetchEmojiList"
313
- },
314
- {
315
- name: "搜索笔记",
316
- replacement: "searchNotes"
317
- }
318
- ].forEach(({ name, replacement }) => {
319
- registerDeprecatedApi({
320
- name: `methodType: '${name}'`,
321
- deprecatedIn: "6.0.0",
322
- removedIn: "7.0.0",
323
- replacement: `fetcher.${replacement}()`,
324
- throwError: true
325
- });
326
- });
327
- registerDeprecatedApi({
328
- name: `methodType: '动态卡片数据'`,
329
- deprecatedIn: "6.0.0",
330
- removedIn: "7.0.0",
331
- migrationGuide: "https://amagi-docs.vercel.app/docs/changelog/6.1.3",
332
- throwError: false
333
- });
334
- registerDeprecatedApi({
335
- name: "fetchDynamicCard",
336
- deprecatedIn: "6.1.3",
337
- removedIn: "7.0.0",
338
- migrationGuide: "https://amagi-docs.vercel.app/docs/changelog/6.1.3",
339
- throwError: false
340
- });
341
- //#endregion
342
- //#region src/model/DataFetchers.ts
343
- /**
344
- * 数据获取器模块 (已废弃)
345
- *
346
- * 此模块中的 getXXXData 函数已在 v6 版本废弃并移除
347
- * 请使用新的 fetcher API 替代
348
- *
349
- * @module model/DataFetchers
350
- * @deprecated v6 已废弃,请使用 fetcher API 替代
351
- */
352
- /**
353
- * 获取抖音数据
354
- *
355
- * @deprecated v6 已废弃,请使用 douyinFetcher 或 client.douyin.fetcher 替代
356
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
357
- *
358
- * @example
359
- * ```typescript
360
- * // 旧用法 (已废弃,会抛出错误)
361
- * const data = await getDouyinData('videoWork', { aweme_id: '123' }, cookie)
362
- *
363
- * // 新用法
364
- * import { douyinFetcher } from '@ikenxuan/amagi'
365
- * const data = await douyinFetcher.fetchVideoWork({ aweme_id: '123' }, cookie)
366
- *
367
- * // 或使用客户端实例
368
- * const client = createAmagiClient({ cookies: { douyin: cookie } })
369
- * const data = await client.douyin.fetcher.fetchVideoWork({ aweme_id: '123' })
370
- * ```
371
- */
372
- function getDouyinData(..._args) {
373
- checkDeprecation("getDouyinData");
374
- throw new Error("getDouyinData 已废弃");
375
- }
376
- /**
377
- * 获取B站数据
378
- *
379
- * @deprecated v6 已废弃,请使用 bilibiliFetcher 或 client.bilibili.fetcher 替代
380
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
381
- *
382
- * @example
383
- * ```typescript
384
- * // 旧用法 (已废弃,会抛出错误)
385
- * const data = await getBilibiliData('videoInfo', { bvid: 'BV123' }, cookie)
386
- *
387
- * // 新用法
388
- * import { bilibiliFetcher } from '@ikenxuan/amagi'
389
- * const data = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV123' }, cookie)
390
- *
391
- * // 或使用客户端实例
392
- * const client = createAmagiClient({ cookies: { bilibili: cookie } })
393
- * const data = await client.bilibili.fetcher.fetchVideoInfo({ bvid: 'BV123' })
394
- * ```
395
- */
396
- function getBilibiliData(..._args) {
397
- checkDeprecation("getBilibiliData");
398
- throw new Error("getBilibiliData 已废弃");
399
- }
400
- /**
401
- * 获取快手数据
402
- *
403
- * @deprecated v6 已废弃,请使用 kuaishouFetcher 或 client.kuaishou.fetcher 替代
404
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
405
- *
406
- * @example
407
- * ```typescript
408
- * // 旧用法 (已废弃,会抛出错误)
409
- * const data = await getKuaishouData('videoWork', { photoId: '123' }, cookie)
410
- *
411
- * // 新用法
412
- * import { kuaishouFetcher } from '@ikenxuan/amagi'
413
- * const data = await kuaishouFetcher.fetchVideoWork({ photoId: '123' }, cookie)
414
- *
415
- * // 或使用客户端实例
416
- * const client = createAmagiClient({ cookies: { kuaishou: cookie } })
417
- * const data = await client.kuaishou.fetcher.fetchVideoWork({ photoId: '123' })
418
- * ```
419
- */
420
- function getKuaishouData(..._args) {
421
- checkDeprecation("getKuaishouData");
422
- throw new Error("getKuaishouData 已废弃");
423
- }
424
- /**
425
- * 获取小红书数据
426
- *
427
- * @deprecated v6 已废弃,请使用 xiaohongshuFetcher 或 client.xiaohongshu.fetcher 替代
428
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
429
- *
430
- * @example
431
- * ```typescript
432
- * // 旧用法 (已废弃,会抛出错误)
433
- * const data = await getXiaohongshuData('noteDetail', { note_id: '123' }, cookie)
434
- *
435
- * // 新用法
436
- * import { xiaohongshuFetcher } from '@ikenxuan/amagi'
437
- * const data = await xiaohongshuFetcher.fetchNoteDetail({ note_id: '123' }, cookie)
438
- *
439
- * // 或使用客户端实例
440
- * const client = createAmagiClient({ cookies: { xiaohongshu: cookie } })
441
- * const data = await client.xiaohongshu.fetcher.fetchNoteDetail({ note_id: '123' })
442
- * ```
443
- */
444
- function getXiaohongshuData(..._args) {
445
- checkDeprecation("getXiaohongshuData");
446
- throw new Error("getXiaohongshuData 已废弃");
447
- }
448
- //#endregion
10
+ import { Chalk } from "chalk";
449
11
  //#region src/platform/bilibili/API.ts
450
12
  /**
451
13
  * B站 API URL 构建类
@@ -517,15 +79,6 @@ var BilibiliAPI = class {
517
79
  getDynamicDetail(data) {
518
80
  return `https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?id=${data.dynamic_id}&features=itemOpusStyle,opusBigCover,onlyfansVote,endFooterHidden,decorationCard,onlyfansAssetsV2,ugcDelete,onlyfansQaCard,editable,opusPrivateVisible,avatarAutoTheme`;
519
81
  }
520
- /**
521
- * 获取动态卡片信息
522
- *
523
- * @deprecated B站官方已于 `2025-08-09` 删除原 `dynamic_svr` 接口,该接口已停用。
524
- * 调用将返回错误信息,请使用 {@link getDynamicDetail} 替代。
525
- */
526
- getDynamicCard(data) {
527
- return this.getDynamicDetail(data);
528
- }
529
82
  /** 获取用户名片信息 */
530
83
  getUserCard(data) {
531
84
  return `https://api.bilibili.com/x/web-interface/card?mid=${data.host_mid}&photo=true`;
@@ -617,86 +170,6 @@ var BilibiliAPI = class {
617
170
  /** B站 API URL 构建器实例 */
618
171
  const bilibiliApiUrls = new BilibiliAPI();
619
172
  //#endregion
620
- //#region src/platform/bilibili/BilibiliApi.ts
621
- /**
622
- * 创建废弃的 API 存根函数
623
- */
624
- const createDeprecatedStub$3 = (methodName) => {
625
- return (..._args) => {
626
- checkDeprecation("getBilibiliData");
627
- throw new Error(`bilibili.${methodName} 已废弃,请使用 bilibiliFetcher 替代`);
628
- };
629
- };
630
- /**
631
- * B站相关 API 的命名空间。
632
- *
633
- * @deprecated v6 已废弃,请使用 bilibiliFetcher 或 client.bilibili.fetcher 替代
634
- */
635
- const bilibili = {
636
- /** @deprecated 请使用 bilibiliFetcher.fetchVideoInfo 替代 */
637
- getVideoInfo: createDeprecatedStub$3("getVideoInfo"),
638
- /** @deprecated 请使用 bilibiliFetcher.fetchVideoStreamUrl 替代 */
639
- getVideoStream: createDeprecatedStub$3("getVideoStream"),
640
- /** @deprecated 请使用 bilibiliFetcher.fetchComments 替代 */
641
- getComments: createDeprecatedStub$3("getComments"),
642
- /** @deprecated 请使用 bilibiliFetcher.fetchCommentReplies 替代 */
643
- getCommentReply: createDeprecatedStub$3("getCommentReply"),
644
- /** @deprecated 请使用 bilibiliFetcher.fetchUserCard 替代 */
645
- getUserProfile: createDeprecatedStub$3("getUserProfile"),
646
- /** @deprecated 请使用 bilibiliFetcher.fetchUserDynamicList 替代 */
647
- getUserDynamic: createDeprecatedStub$3("getUserDynamic"),
648
- /** @deprecated 请使用 bilibiliFetcher.fetchEmojiList 替代 */
649
- getEmojiList: createDeprecatedStub$3("getEmojiList"),
650
- /** @deprecated 请使用 bilibiliFetcher.fetchBangumiInfo 替代 */
651
- getBangumiInfo: createDeprecatedStub$3("getBangumiInfo"),
652
- /** @deprecated 请使用 bilibiliFetcher.fetchBangumiStreamUrl 替代 */
653
- getBangumiStream: createDeprecatedStub$3("getBangumiStream"),
654
- /** @deprecated 请使用 bilibiliFetcher.fetchDynamicDetail 替代 */
655
- getDynamicInfo: createDeprecatedStub$3("getDynamicInfo"),
656
- /** @deprecated 请使用 bilibiliFetcher.fetchDynamicCard 替代 */
657
- getDynamicCard: createDeprecatedStub$3("getDynamicCard"),
658
- /** @deprecated 请使用 bilibiliFetcher.fetchLiveRoomInfo 替代 */
659
- getLiveRoomDetail: createDeprecatedStub$3("getLiveRoomDetail"),
660
- /** @deprecated 请使用 bilibiliFetcher.fetchLiveRoomInitInfo 替代 */
661
- getLiveRoomInitInfo: createDeprecatedStub$3("getLiveRoomInitInfo"),
662
- /** @deprecated 请使用 bilibiliFetcher.fetchLoginStatus 替代 */
663
- getLoginBasicInfo: createDeprecatedStub$3("getLoginBasicInfo"),
664
- /** @deprecated 请使用 bilibiliFetcher.requestLoginQrcode 替代 */
665
- getLoginQrcode: createDeprecatedStub$3("getLoginQrcode"),
666
- /** @deprecated 请使用 bilibiliFetcher.checkQrcodeStatus 替代 */
667
- checkQrcodeStatus: createDeprecatedStub$3("checkQrcodeStatus"),
668
- /** @deprecated 请使用 bilibiliFetcher.fetchUploaderTotalViews 替代 */
669
- getUserTotalPlayCount: createDeprecatedStub$3("getUserTotalPlayCount"),
670
- /** @deprecated 请使用 bilibiliFetcher.convertAvToBv 替代 */
671
- convertAvToBv: createDeprecatedStub$3("convertAvToBv"),
672
- /** @deprecated 请使用 bilibiliFetcher.convertBvToAv 替代 */
673
- convertBvToAv: createDeprecatedStub$3("convertBvToAv"),
674
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleContent 替代 */
675
- getArticleContent: createDeprecatedStub$3("getArticleContent"),
676
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleCards 替代 */
677
- getArticleCard: createDeprecatedStub$3("getArticleCard"),
678
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleInfo 替代 */
679
- getArticleInfo: createDeprecatedStub$3("getArticleInfo"),
680
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleListInfo 替代 */
681
- getColumnInfo: createDeprecatedStub$3("getColumnInfo"),
682
- /** @deprecated 请使用 bilibiliFetcher.fetchUserSpaceInfo 替代 */
683
- getUserProfileDetail: createDeprecatedStub$3("getUserProfileDetail"),
684
- /** @deprecated 请使用 bilibiliFetcher.requestCaptchaFromVoucher 替代 */
685
- applyVoucherCaptcha: createDeprecatedStub$3("applyVoucherCaptcha"),
686
- /** @deprecated 请使用 bilibiliFetcher.validateCaptchaResult 替代 */
687
- validateCaptcha: createDeprecatedStub$3("validateCaptcha"),
688
- /** @deprecated 请使用 bilibiliFetcher.fetchVideoDanmaku 替代 */
689
- getDanmaku: createDeprecatedStub$3("getDanmaku")
690
- };
691
- /**
692
- * 创建绑定了cookie的B站API对象
693
- *
694
- * @deprecated v6 已废弃,请使用 createBoundBilibiliFetcher 替代
695
- */
696
- const createBoundBilibiliApi = (_cookie, _requestConfig) => {
697
- return { ...bilibili };
698
- };
699
- //#endregion
700
173
  //#region src/model/events.ts
701
174
  /**
702
175
  * Amagi 事件系统
@@ -1009,7 +482,7 @@ const BilibiliBangumiStreamParamsSchema = zod.object({
1009
482
  });
1010
483
  /** 动态参数验证 */
1011
484
  const BilibiliDynamicParamsSchema = zod.object({
1012
- methodType: zod.enum(["dynamicDetail", "dynamicCard"], { error: "方法类型必须是\"dynamicDetail\"或\"dynamicCard\"" }),
485
+ methodType: zod.literal("dynamicDetail", { error: "方法类型必须是\"dynamicDetail\"" }),
1013
486
  dynamic_id: zod.string({ error: "动态ID必须是字符串" }).min(1, { error: "动态ID不能为空" })
1014
487
  });
1015
488
  /** 直播间参数验证 */
@@ -1091,7 +564,6 @@ const BilibiliValidationSchemas = {
1091
564
  bangumiInfo: BilibiliBangumiInfoParamsSchema,
1092
565
  bangumiStream: BilibiliBangumiStreamParamsSchema,
1093
566
  dynamicDetail: BilibiliDynamicParamsSchema,
1094
- dynamicCard: BilibiliDynamicParamsSchema,
1095
567
  liveRoomInfo: BilibiliLiveParamsSchema,
1096
568
  liveRoomInit: BilibiliLiveParamsSchema,
1097
569
  loginStatus: BilibiliLoginParamsSchema,
@@ -1122,7 +594,6 @@ const BilibiliMethodRoutes = {
1122
594
  bangumiInfo: "/fetch_bangumi_video_info",
1123
595
  bangumiStream: "/fetch_bangumi_video_playurl",
1124
596
  dynamicDetail: "/fetch_dynamic_info",
1125
- dynamicCard: "/fetch_dynamic_card",
1126
597
  liveRoomInfo: "/fetch_live_room_detail",
1127
598
  liveRoomInit: "/fetch_liveroom_def",
1128
599
  loginStatus: "/login_basic_info",
@@ -1699,18 +1170,20 @@ const xiaohongshuApiUrls = {
1699
1170
  * @returns 完整的接口URL
1700
1171
  */
1701
1172
  noteComments(data) {
1173
+ const baseUrl = "https://edith.xiaohongshu.com/api/sns/web/v2/comment/page";
1174
+ const params = {
1175
+ note_id: data.note_id,
1176
+ cursor: data.cursor ?? "",
1177
+ image_formats: [
1178
+ "jpg",
1179
+ "webp",
1180
+ "avif"
1181
+ ].join(","),
1182
+ xsec_token: data.xsec_token
1183
+ };
1702
1184
  return {
1703
1185
  apiPath: "/api/sns/web/v2/comment/page",
1704
- Url: `https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?${buildQueryString$1({
1705
- note_id: data.note_id,
1706
- cursor: data.cursor ?? "",
1707
- image_formats: [
1708
- "jpg",
1709
- "webp",
1710
- "avif"
1711
- ].join(","),
1712
- xsec_token: data.xsec_token
1713
- })}`
1186
+ Url: `${baseUrl}?${buildQueryString$1(params)}`
1714
1187
  };
1715
1188
  },
1716
1189
  /**
@@ -1730,19 +1203,21 @@ const xiaohongshuApiUrls = {
1730
1203
  * @returns 完整的接口URL
1731
1204
  */
1732
1205
  userNoteList(data) {
1206
+ const baseUrl = "https://edith.xiaohongshu.com/api/sns/web/v1/user_posted";
1207
+ const params = {
1208
+ user_id: data.user_id,
1209
+ cursor: data.cursor ?? "",
1210
+ num: data.num ?? 30,
1211
+ image_formats: [
1212
+ "jpg",
1213
+ "webp",
1214
+ "avif"
1215
+ ].join(","),
1216
+ xsec_source: "pc_feed"
1217
+ };
1733
1218
  return {
1734
1219
  apiPath: "/api/sns/web/v1/user_posted",
1735
- Url: `https://edith.xiaohongshu.com/api/sns/web/v1/user_posted?${buildQueryString$1({
1736
- user_id: data.user_id,
1737
- cursor: data.cursor ?? "",
1738
- num: data.num ?? 30,
1739
- image_formats: [
1740
- "jpg",
1741
- "webp",
1742
- "avif"
1743
- ].join(","),
1744
- xsec_source: "pc_feed"
1745
- })}`
1220
+ Url: `${baseUrl}?${buildQueryString$1(params)}`
1746
1221
  };
1747
1222
  },
1748
1223
  /**
@@ -1959,7 +1434,8 @@ const createErrorResponse = (error, message, code = 500, data) => {
1959
1434
  async function fetchBilibiliInternal(methodType, options, config) {
1960
1435
  const startTime = Date.now();
1961
1436
  try {
1962
- const rawData = await fetchBilibili({ ...validateBilibiliParams(methodType, options) }, config.cookie, config.requestConfig);
1437
+ const apiParams = { ...validateBilibiliParams(methodType, options) };
1438
+ const rawData = await fetchBilibili(apiParams, config.cookie, config.requestConfig);
1963
1439
  const duration = Date.now() - startTime;
1964
1440
  if (rawData.code !== 0) {
1965
1441
  const errorMessage = rawData.message || "B站数据获取失败";
@@ -1971,11 +1447,12 @@ async function fetchBilibiliInternal(methodType, options, config) {
1971
1447
  url: void 0,
1972
1448
  duration
1973
1449
  });
1974
- return createErrorResponse(rawData.amagiError ?? {
1450
+ const amagiError = rawData.amagiError ?? {
1975
1451
  errorDescription: errorMessage,
1976
1452
  requestType: methodType,
1977
1453
  requestUrl: void 0
1978
- }, errorMessage, rawData.code, rawData);
1454
+ };
1455
+ return createErrorResponse(amagiError, errorMessage, rawData.code, rawData);
1979
1456
  }
1980
1457
  const result = createSuccessResponse(rawData, "获取成功", 200);
1981
1458
  emitApiSuccess({
@@ -2282,31 +1759,6 @@ async function fetchDynamicDetail(options, cookie, requestConfig) {
2282
1759
  requestConfig
2283
1760
  });
2284
1761
  }
2285
- /**
2286
- * 获取B站动态卡片信息
2287
- *
2288
- * @deprecated v6.1.3 已废弃,B站官方已于 `2025-08-09` 删除原 `dynamic_svr` 接口。
2289
- * 调用将返回错误信息
2290
- * 计划于 v7.0.0 移除。
2291
- *
2292
- * @param options - 动态参数
2293
- * @param options.dynamic_id - 动态 ID
2294
- * @param cookie - B站 Cookie (可选)
2295
- * @param requestConfig - 请求配置 (可选)
2296
- * @returns 动态卡片数据(已停用,返回错误信息)
2297
- * @example
2298
- * ```typescript
2299
- * const result = await fetchDynamicCard({ dynamic_id: '123456789' }, cookie)
2300
- * // result.success === false,错误信息提示接口已停用
2301
- * ```
2302
- */
2303
- async function fetchDynamicCard(options, cookie, requestConfig) {
2304
- checkDeprecation("fetchDynamicCard");
2305
- return fetchBilibiliInternal("dynamicCard", options, {
2306
- cookie,
2307
- requestConfig
2308
- });
2309
- }
2310
1762
  //#endregion
2311
1763
  //#region src/model/fetchers/bilibili/live.ts
2312
1764
  /**
@@ -2625,8 +2077,6 @@ function createBoundBilibiliFetcher(cookie, requestConfig) {
2625
2077
  fetchUserSpaceInfo: (options, override) => fetchUserSpaceInfo(options, ...resolveRequest(override)),
2626
2078
  fetchUploaderTotalViews: (options, override) => fetchUploaderTotalViews(options, ...resolveRequest(override)),
2627
2079
  fetchDynamicDetail: (options, override) => fetchDynamicDetail(options, ...resolveRequest(override)),
2628
- /** @deprecated v6.1.3 已废弃,调用将返回错误信息 */
2629
- fetchDynamicCard: (options, override) => fetchDynamicCard(options, ...resolveRequest(override)),
2630
2080
  fetchBangumiInfo: (options, override) => fetchBangumiInfo(options, ...resolveRequest(override)),
2631
2081
  fetchBangumiStreamUrl: (options, override) => fetchBangumiStreamUrl(options, ...resolveRequest(override)),
2632
2082
  fetchLiveRoomInfo: (options, override) => fetchLiveRoomInfo$2(options, ...resolveRequest(override)),
@@ -2646,51 +2096,1739 @@ function createBoundBilibiliFetcher(cookie, requestConfig) {
2646
2096
  };
2647
2097
  }
2648
2098
  //#endregion
2649
- //#region src/model/fetchers/bilibili/index.ts
2099
+ //#region src/model/fetchers/bilibili/index.ts
2100
+ /**
2101
+ * B站 Fetcher 模块入口
2102
+ * @module fetchers/bilibili
2103
+ */
2104
+ /**
2105
+ * B站数据获取器
2106
+ * 包含所有 B站 API 方法,调用时需要传递 cookie
2107
+ * @example
2108
+ * ```typescript
2109
+ * import { bilibiliFetcher } from '@ikenxuan/amagi'
2110
+ *
2111
+ * const result = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD' }, cookie)
2112
+ * ```
2113
+ */
2114
+ const bilibiliFetcher = {
2115
+ fetchVideoInfo,
2116
+ fetchVideoStreamUrl,
2117
+ fetchVideoDanmaku,
2118
+ fetchComments,
2119
+ fetchCommentReplies: fetchCommentReplies$1,
2120
+ fetchUserCard,
2121
+ fetchUserDynamicList,
2122
+ fetchUserLiveStatus,
2123
+ fetchUserSpaceInfo,
2124
+ fetchUploaderTotalViews,
2125
+ fetchDynamicDetail,
2126
+ fetchBangumiInfo,
2127
+ fetchBangumiStreamUrl,
2128
+ fetchLiveRoomInfo: fetchLiveRoomInfo$2,
2129
+ fetchLiveRoomInitInfo,
2130
+ fetchArticleContent,
2131
+ fetchArticleCards,
2132
+ fetchArticleInfo,
2133
+ fetchArticleListInfo,
2134
+ fetchLoginStatus,
2135
+ requestLoginQrcode: requestLoginQrcode$1,
2136
+ checkQrcodeStatus,
2137
+ requestCaptchaFromVoucher,
2138
+ validateCaptchaResult,
2139
+ convertAvToBv,
2140
+ convertBvToAv,
2141
+ fetchEmojiList: fetchEmojiList$3
2142
+ };
2143
+ //#endregion
2144
+ //#region src/platform/douyin/passport/sm3.ts
2145
+ /**
2146
+ * SM3 摘要(GM/T 0004-2012),抖音 bdms 签名链使用的变体
2147
+ *
2148
+ * 与标准实现的唯一差异:字符串按 `charCodeAt` 逐字符取字节(非 UTF-8 编码),
2149
+ * 与浏览器里 bdms 的 `strToBytes` 行为一致。签名输入均为 ASCII,实际不会踩到多字节分支,
2150
+ * 但仍保留该分支以保证与浏览器实现逐位一致。
2151
+ */
2152
+ /** 循环左移 32 位 */
2153
+ const rotl = (x, n) => {
2154
+ const shift = n % 32;
2155
+ return (x << shift | x >>> 32 - shift) >>> 0;
2156
+ };
2157
+ /** 轮常量 Tj */
2158
+ const tj = (j) => j < 16 ? 2043430169 : 2055708042;
2159
+ /** 布尔函数 FFj */
2160
+ const ff = (j, x, y, z) => j < 16 ? (x ^ y ^ z) >>> 0 : (x & y | x & z | y & z) >>> 0;
2161
+ /** 布尔函数 GGj */
2162
+ const gg = (j, x, y, z) => j < 16 ? (x ^ y ^ z) >>> 0 : (x & y | ~x & z) >>> 0;
2163
+ /** 初始向量 IV */
2164
+ const IV = [
2165
+ 1937774191,
2166
+ 1226093241,
2167
+ 388252375,
2168
+ 3666478592,
2169
+ 2842636476,
2170
+ 372324522,
2171
+ 3817729613,
2172
+ 2969243214
2173
+ ];
2174
+ /** 字符串转字节数组:逐字符 charCodeAt,大于一字节时按大端拆分 */
2175
+ const strToBytes = (input) => {
2176
+ const bytes = [];
2177
+ for (let i = 0; i < input.length; i++) {
2178
+ let code = input.charCodeAt(i);
2179
+ const chunk = [];
2180
+ do {
2181
+ chunk.push(code & 255);
2182
+ code >>= 8;
2183
+ } while (code);
2184
+ bytes.push(...chunk.reverse());
2185
+ }
2186
+ return bytes;
2187
+ };
2188
+ /** 消息扩展:生成 W[0..67] 与 W'[0..63](后者放在 68 之后) */
2189
+ const expand = (block) => {
2190
+ const w = new Array(132);
2191
+ for (let i = 0; i < 16; i++) w[i] = (block[i * 4] << 24 | block[i * 4 + 1] << 16 | block[i * 4 + 2] << 8 | block[i * 4 + 3]) >>> 0;
2192
+ for (let j = 16; j < 68; j++) {
2193
+ let x = w[j - 16] ^ w[j - 9] ^ rotl(w[j - 3], 15);
2194
+ x = x ^ rotl(x, 15) ^ rotl(x, 23);
2195
+ w[j] = (x ^ rotl(w[j - 13], 7) ^ w[j - 6]) >>> 0;
2196
+ }
2197
+ for (let j = 0; j < 64; j++) w[j + 68] = (w[j] ^ w[j + 4]) >>> 0;
2198
+ return w;
2199
+ };
2200
+ /** SM3 压缩函数:就地更新寄存器 */
2201
+ const compress = (reg, block) => {
2202
+ const w = expand(block);
2203
+ const r = reg.slice();
2204
+ for (let j = 0; j < 64; j++) {
2205
+ let ss1 = rotl(r[0], 12) + r[4] + rotl(tj(j), j) & 4294967295;
2206
+ ss1 = rotl(ss1 >>> 0, 7);
2207
+ const ss2 = (ss1 ^ rotl(r[0], 12)) >>> 0;
2208
+ const tt1 = ff(j, r[0], r[1], r[2]) + r[3] + ss2 + w[j + 68] >>> 0;
2209
+ const tt2 = gg(j, r[4], r[5], r[6]) + r[7] + ss1 + w[j] >>> 0;
2210
+ r[3] = r[2];
2211
+ r[2] = rotl(r[1], 9);
2212
+ r[1] = r[0];
2213
+ r[0] = tt1;
2214
+ r[7] = r[6];
2215
+ r[6] = rotl(r[5], 19);
2216
+ r[5] = r[4];
2217
+ r[4] = (tt2 ^ rotl(tt2, 9) ^ rotl(tt2, 17)) >>> 0;
2218
+ }
2219
+ for (let i = 0; i < 8; i++) reg[i] = (reg[i] ^ r[i]) >>> 0;
2220
+ };
2221
+ /** 按 SM3 规则填充消息(0x80 + 0 + 64 位比特长度) */
2222
+ const pad = (bytes) => {
2223
+ const padded = bytes.slice();
2224
+ const bitLength = bytes.length * 8;
2225
+ padded.push(128);
2226
+ while (padded.length % 64 !== 56) padded.push(0);
2227
+ const high = Math.floor(bitLength / 4294967296);
2228
+ for (let i = 0; i < 4; i++) padded.push(high >>> (3 - i) * 8 & 255);
2229
+ for (let i = 0; i < 4; i++) padded.push(bitLength >>> (3 - i) * 8 & 255);
2230
+ return padded;
2231
+ };
2232
+ /**
2233
+ * 计算 SM3 摘要
2234
+ * @param message 待摘要的字符串或字节数组
2235
+ * @returns 32 字节摘要
2236
+ */
2237
+ const sm3 = (message) => {
2238
+ const bytes = typeof message === "string" ? strToBytes(message) : message;
2239
+ const reg = IV.slice();
2240
+ const padded = pad(bytes);
2241
+ for (let i = 0; i < padded.length; i += 64) compress(reg, padded.slice(i, i + 64));
2242
+ const digest = new Array(32);
2243
+ for (let i = 0; i < 8; i++) {
2244
+ digest[i * 4] = reg[i] >>> 24 & 255;
2245
+ digest[i * 4 + 1] = reg[i] >>> 16 & 255;
2246
+ digest[i * 4 + 2] = reg[i] >>> 8 & 255;
2247
+ digest[i * 4 + 3] = reg[i] & 255;
2248
+ }
2249
+ return digest;
2250
+ };
2251
+ /**
2252
+ * 连续两次 SM3(bdms 对 URL 与盐值的处理方式)
2253
+ * @param message 待摘要的字符串或字节数组
2254
+ * @returns 32 字节摘要
2255
+ */
2256
+ const sm3Twice = (message) => sm3(sm3(message));
2257
+ /**
2258
+ * 十六进制摘要,仅用于测试与排查
2259
+ * @param message 待摘要的字符串或字节数组
2260
+ */
2261
+ const sm3Hex = (message) => sm3(message).map((byte) => byte.toString(16).padStart(2, "0")).join("");
2262
+ //#endregion
2263
+ //#region src/platform/douyin/passport/aBogus.ts
2264
+ /**
2265
+ * a_bogus 签名(bdms 1.0.1.19 形态,passport 登录接口使用)
2266
+ *
2267
+ * 抖音同时在线着多个 a_bogus 版本:`@ikenxuan/amagi` 里带的是 web 数据接口用的旧版
2268
+ * (盐值 `cus`、pageId 6241),而 login.douyin.com 的 passport SDK 用的是本文件实现的
2269
+ * 新版(盐值 `dhzx`、pageId 7571、sdkVersion 1.0.1.19-fix.01)。两者互不通用,
2270
+ * 所以这里单独实现一份,不复用 amagi 的签名。
2271
+ *
2272
+ * 算法为社区公开的逆向结论(见 PR 说明中的参考链接),此处按 kkk 的代码风格重写:
2273
+ * - SM3 为抖音变体(连续两次摘要),见 ./sm3
2274
+ * - RC4 为变体:S 盒递减初始化 + `j = (j * S[i] + j + K[i]) % 256`
2275
+ * - Base64 使用自定义字符表,UA 段用 s3、最终结果用 s4
2276
+ * - `random()` 保留了原实现里 `Math.random`(函数对象而非调用)参与位运算的行为,
2277
+ * 该表达式恒为 NaN → 位运算结果恒为 0,这里直接写成常量而不是照抄错误代码
2278
+ */
2279
+ /** RC4 / S 盒长度 */
2280
+ const BOX_SIZE = 256;
2281
+ /** bdms 1.0.1.19 的盐值 */
2282
+ const SALT = "dhzx";
2283
+ /** bdms SDK 版本号,同时也是 passport 通用参数里的 p_bd */
2284
+ const BDMS_SDK_VERSION = "1.0.1.19-fix.01";
2285
+ /** 版本号基准时间戳(bdms 内部按 14 天为一档计数) */
2286
+ const VERSION_EPOCH = 17218368e5;
2287
+ /** 模块加载时刻,等价于浏览器里的「进入页面时间」 */
2288
+ const ENTER_PAGE_TS = Date.now();
2289
+ /** 自定义 Base64 字符表 */
2290
+ const BASE64_TABLES = {
2291
+ s0: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
2292
+ s1: "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=",
2293
+ s2: "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=",
2294
+ s3: "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe",
2295
+ s4: "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"
2296
+ };
2297
+ /**
2298
+ * 浏览器环境快照。服务器上没有真实窗口,这里给出一组常见的桌面分辨率组合;
2299
+ * 该值只影响指纹内容本身,不需要与任何真实设备对应。
2300
+ */
2301
+ const BROWSER_ENV = {
2302
+ innerWidth: 2048,
2303
+ innerHeight: 960,
2304
+ outerWidth: 2554,
2305
+ outerHeight: 1386,
2306
+ availWidth: 2560,
2307
+ availHeight: 1392,
2308
+ sizeWidth: 2560,
2309
+ sizeHeight: 1440,
2310
+ platform: "Win32"
2311
+ };
2312
+ /** RC4 密钥:bdms 用 `[1 / 256, 1 % 256, 14 % 256]` 造出 "\x00\x01\x0e" */
2313
+ const rc4Key = String.fromCharCode(...[
2314
+ 1 / BOX_SIZE,
2315
+ 1 % BOX_SIZE,
2316
+ 14 % BOX_SIZE
2317
+ ]);
2318
+ /**
2319
+ * 变体 RC4
2320
+ * @param key 密钥
2321
+ * @param text 明文(按 charCode 处理)
2322
+ */
2323
+ const rc4 = (key, text) => {
2324
+ const s = new Uint8Array(BOX_SIZE);
2325
+ const k = new Uint8Array(BOX_SIZE);
2326
+ for (let i = 0; i < BOX_SIZE; i++) {
2327
+ s[i] = 255 - i;
2328
+ k[i] = key.charCodeAt(i % key.length);
2329
+ }
2330
+ let j = 0;
2331
+ for (let i = 0; i < BOX_SIZE; i++) {
2332
+ j = (j * s[i] + j + k[i]) % BOX_SIZE;
2333
+ [s[i], s[j]] = [s[j], s[i]];
2334
+ }
2335
+ let x = 0;
2336
+ let y = 0;
2337
+ let cipher = "";
2338
+ for (let n = 0; n < text.length; n++) {
2339
+ x = (x + 1) % BOX_SIZE;
2340
+ y = (y + s[x]) % BOX_SIZE;
2341
+ [s[x], s[y]] = [s[y], s[x]];
2342
+ cipher += String.fromCharCode(text.charCodeAt(n) ^ s[(s[x] + s[y]) % BOX_SIZE]);
2343
+ }
2344
+ return cipher;
2345
+ };
2346
+ /**
2347
+ * 自定义字符表 Base64
2348
+ * @param input 明文(按 charCode 取低 8 位)
2349
+ * @param table 字符表名(s0 ~ s4)
2350
+ */
2351
+ const base64 = (input, table) => {
2352
+ const alphabet = BASE64_TABLES[table];
2353
+ let output = "";
2354
+ let i = 0;
2355
+ while (i < input.length) {
2356
+ const c1 = input.charCodeAt(i++);
2357
+ const c2 = input.charCodeAt(i++);
2358
+ const c3 = input.charCodeAt(i++);
2359
+ const chunk = (c1 & 255) << 16 | (Number.isNaN(c2) ? 0 : (c2 & 255) << 8) | (Number.isNaN(c3) ? 0 : c3 & 255);
2360
+ output += alphabet.charAt(chunk >> 18 & 63);
2361
+ output += alphabet.charAt(chunk >> 12 & 63);
2362
+ output += Number.isNaN(c2) ? "=" : alphabet.charAt(chunk >> 6 & 63);
2363
+ output += Number.isNaN(c3) ? "=" : alphabet.charAt(chunk & 63);
2364
+ }
2365
+ return output;
2366
+ };
2367
+ /**
2368
+ * 生成 4 字节随机混淆段
2369
+ * @param seed 两字节的基准值
2370
+ * @param flag 0 = 完全随机;1 = 高位固定为 0;2 = 低位受限、高位固定为 178
2371
+ */
2372
+ const mix = (seed, flag) => {
2373
+ const r = Math.random() * 65535 | 0;
2374
+ let low = r & 255;
2375
+ let high = r >> 8 & 255;
2376
+ if (flag === 1) high = 0;
2377
+ if (flag === 2) {
2378
+ low = Math.random() * 240 >> 0;
2379
+ if (low > 109) low += low % 2 + 1;
2380
+ high = 178;
2381
+ }
2382
+ return [
2383
+ low & 170 | seed[0] & 85,
2384
+ low & 85 | seed[0] & 170,
2385
+ high & 170 | seed[1] & 85,
2386
+ high & 85 | seed[1] & 170
2387
+ ];
2388
+ };
2389
+ /** SDK 版本号拆成数字数组,非纯数字段(如 `19-fix`)取 0 */
2390
+ const versionSegments = (version) => version.split(".").map((segment) => ~~Number(segment));
2391
+ /** 每 3 字节扩成 4 字节,掺入随机位 */
2392
+ const spread = (bytes) => {
2393
+ const masks = [
2394
+ 145,
2395
+ 110,
2396
+ 66,
2397
+ 189,
2398
+ 44,
2399
+ 211
2400
+ ];
2401
+ const out = [];
2402
+ for (let i = 0; i < bytes.length; i += 3) {
2403
+ if (i + 2 >= bytes.length) {
2404
+ out.push(bytes[i]);
2405
+ if (bytes[i + 1] !== void 0) out.push(bytes[i + 1]);
2406
+ continue;
2407
+ }
2408
+ const noise = Math.random() * 1e3 & 255;
2409
+ out.push(noise & masks[0] | bytes[i] & masks[1], noise & masks[2] | bytes[i + 1] & masks[3], noise & masks[4] | bytes[i + 2] & masks[5], bytes[i] & masks[0] | bytes[i + 1] & masks[2] | bytes[i + 2] & masks[4]);
2410
+ }
2411
+ return out;
2412
+ };
2413
+ /**
2414
+ * 生成 a_bogus
2415
+ * @param query 除 a_bogus 之外的完整查询串(未加 `?`,保持实际发送顺序)
2416
+ * @param userAgent 与请求头一致的 UA
2417
+ * @returns a_bogus 参数值(未做 URL 编码)
2418
+ */
2419
+ const aBogus = (query, userAgent) => {
2420
+ const salted = query.endsWith(SALT) ? query : query + SALT;
2421
+ const queryDigest = sm3Twice(salted);
2422
+ const saltDigest = sm3Twice(SALT);
2423
+ const uaEncoded = base64(rc4(rc4Key, userAgent), "s3");
2424
+ const uaDigest = sm3(uaEncoded);
2425
+ const now = Date.now();
2426
+ const ink = now - 1;
2427
+ /** 指纹字节表,下标沿用 bdms 内部编号,便于与逆向资料对照 */
2428
+ const b = {};
2429
+ b[24] = 41;
2430
+ b[26] = (now - VERSION_EPOCH) / 1e3 / 60 / 60 / 24 / 14 >> 0;
2431
+ b[27] = 6;
2432
+ b[28] = now - ENTER_PAGE_TS + 3 & 255;
2433
+ b[29] = now & 255;
2434
+ b[30] = now >> 8 & 255;
2435
+ b[31] = now >> 16 & 255;
2436
+ b[32] = now >> 24 & 255;
2437
+ b[33] = now / 2 ** 32 & 255;
2438
+ b[34] = now / 2 ** 40 & 255;
2439
+ b[35] = 1;
2440
+ b[36] = 0;
2441
+ b[38] = 129;
2442
+ b[39] = 0;
2443
+ b[40] = 0;
2444
+ b[41] = 0;
2445
+ b[42] = 0;
2446
+ b[43] = 0;
2447
+ b[44] = 14;
2448
+ b[45] = 0;
2449
+ b[46] = 0;
2450
+ b[47] = 0;
2451
+ b[48] = queryDigest[9];
2452
+ b[49] = queryDigest[18];
2453
+ b[51] = queryDigest[3];
2454
+ b[52] = saltDigest[10];
2455
+ b[53] = saltDigest[19];
2456
+ b[55] = saltDigest[4];
2457
+ b[56] = uaDigest[11];
2458
+ b[57] = uaDigest[21];
2459
+ b[59] = uaDigest[5];
2460
+ b[60] = ink & 255;
2461
+ b[61] = ink >> 8 & 255;
2462
+ b[62] = ink >> 16 & 255;
2463
+ b[63] = ink >> 24 & 255;
2464
+ b[64] = ink / 2 ** 32 & 255;
2465
+ b[65] = ink / 2 ** 40 & 255;
2466
+ b[66] = 3;
2467
+ b[67] = 147;
2468
+ b[68] = 29;
2469
+ b[69] = 0;
2470
+ b[70] = 0;
2471
+ b[71] = 239;
2472
+ b[72] = 24;
2473
+ b[73] = 0;
2474
+ b[74] = 0;
2475
+ const envSnapshot = Object.values(BROWSER_ENV).join("|");
2476
+ const envBytes = Array.from(envSnapshot, (char) => char.charCodeAt(0));
2477
+ b[79] = envBytes.length & 255;
2478
+ b[80] = envBytes.length >> 8 & 255;
2479
+ const tail = `${now + 3 & 255},`;
2480
+ const tailBytes = Array.from(tail, (char) => char.charCodeAt(0));
2481
+ b[84] = tailBytes.length & 255;
2482
+ b[85] = tailBytes.length >> 8 & 255;
2483
+ const version = versionSegments(BDMS_SDK_VERSION);
2484
+ const noise = mix([version[0], version[1]], 0).concat(mix([version[0], version[1]], 2));
2485
+ const checksum = [
2486
+ 24,
2487
+ 26,
2488
+ 27,
2489
+ 28,
2490
+ 29,
2491
+ 30,
2492
+ 31,
2493
+ 32,
2494
+ 33,
2495
+ 34,
2496
+ 35,
2497
+ 36,
2498
+ 38,
2499
+ 39,
2500
+ 40,
2501
+ 41,
2502
+ 42,
2503
+ 43,
2504
+ 44,
2505
+ 45,
2506
+ 46,
2507
+ 47,
2508
+ 48,
2509
+ 49,
2510
+ 51,
2511
+ 52,
2512
+ 53,
2513
+ 55,
2514
+ 56,
2515
+ 57,
2516
+ 59,
2517
+ 60,
2518
+ 61,
2519
+ 62,
2520
+ 63,
2521
+ 64,
2522
+ 65,
2523
+ 66,
2524
+ 67,
2525
+ 68,
2526
+ 69,
2527
+ 70,
2528
+ 71,
2529
+ 72,
2530
+ 73,
2531
+ 74,
2532
+ 79,
2533
+ 80,
2534
+ 84,
2535
+ 85
2536
+ ].reduce((acc, key) => acc ^ b[key], noise.reduce((acc, value) => acc ^ value, 0));
2537
+ /** 打乱后的字节顺序,与 bdms 内部的 tlist 一致 */
2538
+ const shuffled = [
2539
+ 34,
2540
+ 44,
2541
+ 56,
2542
+ 61,
2543
+ 73,
2544
+ 29,
2545
+ 70,
2546
+ 45,
2547
+ 35,
2548
+ 49,
2549
+ 38,
2550
+ 66,
2551
+ 51,
2552
+ 68,
2553
+ 28,
2554
+ 48,
2555
+ 64,
2556
+ 47,
2557
+ 30,
2558
+ 71,
2559
+ 26,
2560
+ 55,
2561
+ 31,
2562
+ 69,
2563
+ 59,
2564
+ 40,
2565
+ 62,
2566
+ 63,
2567
+ 27,
2568
+ 72,
2569
+ 41,
2570
+ 74,
2571
+ 57,
2572
+ 52,
2573
+ 42,
2574
+ 39,
2575
+ 33,
2576
+ 67,
2577
+ 53,
2578
+ 43,
2579
+ 65,
2580
+ 46,
2581
+ 36,
2582
+ 24,
2583
+ 60,
2584
+ 32,
2585
+ 79,
2586
+ 80,
2587
+ 84,
2588
+ 85
2589
+ ].map((key) => b[key]);
2590
+ const payload = spread(shuffled.concat(envBytes, tailBytes, [checksum]));
2591
+ const prefix = String.fromCharCode(...mix([3, 82], 1));
2592
+ const encrypted = rc4(String.fromCharCode(211), String.fromCharCode(...noise.concat(payload)));
2593
+ return base64(prefix + encrypted, "s4");
2594
+ };
2595
+ //#endregion
2596
+ //#region src/platform/douyin/passport/cookieJar.ts
2597
+ /**
2598
+ * 登录流程用的轻量 CookieJar
2599
+ *
2600
+ * 登录过程会跨 `www.douyin.com` / `login.douyin.com` / `ttwid.bytedance.com` 三个域,
2601
+ * 且同一个 cookie 名会被多次下发(例如 `ttwid` 在换取可信指纹后会被替换、
2602
+ * `sessionid` 在二次验证通过后会被升级)。这里只做一件事:**按下发顺序覆盖同名 cookie**,
2603
+ * 保证最终拿到的永远是最后一次下发的值,同时正确处理服务端的删除指令。
2604
+ *
2605
+ * 不做域/路径隔离:整个登录流程都在抖音自己的域下,隔离反而会漏掉跨子域下发的凭证。
2606
+ *
2607
+ * 另外承载一小部分**本地会话状态**(见 `INTERNAL_PREFIX`):passport 的几个接口对外是
2608
+ * 无状态的,会话全靠 cookie 串在调用之间传递,而 bd-ticket-guard 需要在多次调用之间
2609
+ * 记住自己生成的密钥与服务端签发的票据。这些条目以 `__amagi_` 开头,
2610
+ * `toString()` 不会把它们放进 Cookie 请求头,只有 `serialize()` 才会带上。
2611
+ */
2612
+ /** 本地会话状态的 cookie 名前缀,这些条目永远不会发给服务端 */
2613
+ const INTERNAL_PREFIX = "__amagi_";
2614
+ /** 是否为本地会话状态条目 */
2615
+ const isInternal = (name) => name.startsWith(INTERNAL_PREFIX);
2616
+ /** 判断一条 Set-Cookie 是否表示「删除该 cookie」 */
2617
+ const isDeletion = (value, attributes) => {
2618
+ if (value === "") return true;
2619
+ for (const attribute of attributes) {
2620
+ const [rawName, ...rest] = attribute.split("=");
2621
+ const name = rawName.trim().toLowerCase();
2622
+ const rawValue = rest.join("=").trim();
2623
+ if (name === "max-age") {
2624
+ const maxAge = Number(rawValue);
2625
+ if (Number.isFinite(maxAge) && maxAge <= 0) return true;
2626
+ }
2627
+ if (name === "expires") {
2628
+ const expires = Date.parse(rawValue);
2629
+ if (Number.isFinite(expires) && expires <= Date.now()) return true;
2630
+ }
2631
+ }
2632
+ return false;
2633
+ };
2634
+ var CookieJar = class {
2635
+ /** Map 保留插入顺序,重复 set 只更新值、不改变位置 */
2636
+ cookies = /* @__PURE__ */ new Map();
2637
+ /**
2638
+ * @param initial 初始 cookie 串,形如 `a=1; b=2`
2639
+ */
2640
+ constructor(initial) {
2641
+ if (initial) this.merge(initial);
2642
+ }
2643
+ /** 当前持有的 cookie 数量 */
2644
+ get size() {
2645
+ return this.cookies.size;
2646
+ }
2647
+ /**
2648
+ * 写入一条 cookie
2649
+ * @param name cookie 名
2650
+ * @param value cookie 值
2651
+ */
2652
+ set(name, value) {
2653
+ this.cookies.set(name, value);
2654
+ return this;
2655
+ }
2656
+ /**
2657
+ * 读取一条 cookie
2658
+ * @param name cookie 名
2659
+ */
2660
+ get(name) {
2661
+ return this.cookies.get(name);
2662
+ }
2663
+ /**
2664
+ * 是否持有某条 cookie
2665
+ * @param name cookie 名
2666
+ */
2667
+ has(name) {
2668
+ return this.cookies.has(name);
2669
+ }
2670
+ /**
2671
+ * 合并一段 `name=value; name=value` 形式的 cookie 串
2672
+ * @param cookieString cookie 串,空值直接忽略
2673
+ */
2674
+ merge(cookieString) {
2675
+ if (!cookieString) return this;
2676
+ for (const pair of cookieString.split(";")) {
2677
+ const index = pair.indexOf("=");
2678
+ if (index <= 0) continue;
2679
+ const name = pair.slice(0, index).trim();
2680
+ const value = pair.slice(index + 1).trim();
2681
+ if (name && value) this.cookies.set(name, value);
2682
+ }
2683
+ return this;
2684
+ }
2685
+ /**
2686
+ * 应用响应的 Set-Cookie 头
2687
+ * @param setCookies 单条或多条 Set-Cookie 原始值
2688
+ */
2689
+ applySetCookie(setCookies) {
2690
+ if (!setCookies) return this;
2691
+ for (const line of Array.isArray(setCookies) ? setCookies : [setCookies]) {
2692
+ const [pair, ...attributes] = line.split(";");
2693
+ const index = pair.indexOf("=");
2694
+ if (index <= 0) continue;
2695
+ const name = pair.slice(0, index).trim();
2696
+ const value = pair.slice(index + 1).trim();
2697
+ if (!name) continue;
2698
+ if (isDeletion(value, attributes)) {
2699
+ this.cookies.delete(name);
2700
+ continue;
2701
+ }
2702
+ this.cookies.set(name, value);
2703
+ }
2704
+ return this;
2705
+ }
2706
+ /**
2707
+ * 是否已经拿到登录态凭证(`ttwid` 是匿名设备指纹,不算登录)
2708
+ */
2709
+ isLoggedIn() {
2710
+ return this.has("sessionid") || this.has("sessionid_ss") || this.has("sid_guard");
2711
+ }
2712
+ /** 序列化为可直接放进 Cookie 请求头的字符串,不含本地会话状态 */
2713
+ toString() {
2714
+ return [...this.cookies].filter(([name]) => !isInternal(name)).map(([name, value]) => `${name}=${value}`).join("; ");
2715
+ }
2716
+ /**
2717
+ * 序列化为在两次调用之间传递的会话串,包含本地会话状态
2718
+ *
2719
+ * 登录流程内部用这个;最终落库的登录凭证用 `toString()`,避免把本地密钥写进配置。
2720
+ */
2721
+ serialize() {
2722
+ return [...this.cookies].map(([name, value]) => `${name}=${value}`).join("; ");
2723
+ }
2724
+ /** 导出为普通对象,便于断言与日志 */
2725
+ toJSON() {
2726
+ return Object.fromEntries(this.cookies);
2727
+ }
2728
+ };
2729
+ //#endregion
2730
+ //#region src/platform/douyin/passport/params.ts
2731
+ /**
2732
+ * passport 登录 SDK 的参数与签名构造
2733
+ *
2734
+ * login.douyin.com 的接口一共要过四道签名:
2735
+ * - `p_no`:SDK 版本相关参数排序后取 sha256
2736
+ * - `sign`:`排序后的前 10 个 query 参数 & 排序后的 body 参数 & app_key` 取 sha256
2737
+ * - `qs`:参与 sign 的参数名列表逐字节异或 5 后转十六进制
2738
+ * - `x-tt-passport-aid-sign` 请求头:以 appKey 为消息、当日 UTC 正午时间戳为密钥做 HMAC 派生
2739
+ *
2740
+ * 这些常量都是 SDK 自身的版本号与固定 app_key,属于协议的一部分,不含任何设备/账号信息。
2741
+ */
2742
+ /** passport 登录 SDK 的 app_key */
2743
+ const APP_KEY = "163e7ce78d58971a41f5b969996d85c2";
2744
+ /** 抖音 web 的 aid */
2745
+ const PASSPORT_AID = "6383";
2746
+ /** 登录接口域名 */
2747
+ const LOGIN_HOST = "login.douyin.com";
2748
+ /** 抖音主站域名 */
2749
+ const WEB_HOST = "www.douyin.com";
2750
+ /** 登录 SDK(normal 形态)版本号 */
2751
+ const JSSDK_VERSION = "3.1.3";
2752
+ /** 验证页 SDK(lite 形态)版本号 */
2753
+ const LITE_JSSDK_VERSION = "5.1.2";
2754
+ /** 验证页使用的 authn SDK 版本号 */
2755
+ const LITE_AUTHN_VERSION = "1.0.0.420-web";
2756
+ /** 各子 SDK 版本号,参与 p_no 计算 */
2757
+ const SDK_VERSIONS = {
2758
+ pVer: "1.1.3",
2759
+ pZt: "3.3.14",
2760
+ pUi: "2.1.9-alpha.6",
2761
+ pCa: "4.0.17",
2762
+ pCaReal: "1.0.0.874"
2763
+ };
2764
+ /** 与 aBogus 中 BROWSER_ENV 对应的窗口尺寸,用于生成 account_sdk_source_info */
2765
+ const ENV_VIEWPORT = {
2766
+ innerWidth: 2048,
2767
+ innerHeight: 960,
2768
+ outerWidth: 2554,
2769
+ outerHeight: 1386
2770
+ };
2771
+ const sha256Hex = (input) => crypto.createHash("sha256").update(input, "utf8").digest("hex");
2772
+ const hmacSha256 = (key, message) => crypto.createHmac("sha256", Buffer.from(key)).update(Buffer.from(message)).digest();
2773
+ const hexToBytes = (hex) => Uint8Array.from(hex.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []);
2774
+ /**
2775
+ * 逐字节异或 5 后转十六进制,SDK 用它编码参数名列表、验证码与密码
2776
+ * @param input 明文
2777
+ */
2778
+ const xor5Hex = (input) => Array.from(Buffer.from(input, "utf8"), (byte) => (byte ^ 5).toString(16).padStart(2, "0")).join("");
2779
+ /** 随机十六进制串,用于 biz_trace_id 一类的追踪 ID */
2780
+ const randomHex = (length) => crypto.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
2781
+ /** 当日 UTC 12:00 的秒级时间戳,aid-sign 以此为密钥基准 */
2782
+ const utcNoonTimestamp = (now = /* @__PURE__ */ new Date()) => Math.floor(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 12, 0, 0, 0) / 1e3);
2783
+ /**
2784
+ * 按 SDK 规则序列化参数:键名排序后拼成 `k=v&k=v`
2785
+ * @param params 参数对象
2786
+ * @param limit 大于等于 0 时只取排序后的前 limit 个键
2787
+ */
2788
+ const serializeSorted = (params, limit = -1) => {
2789
+ const keys = Object.keys(params).sort();
2790
+ if (limit >= 0) keys.splice(limit);
2791
+ return {
2792
+ text: keys.map((key) => `${key}=${typeof params[key] === "object" ? JSON.stringify(params[key]) : params[key]}`).join("&"),
2793
+ keys
2794
+ };
2795
+ };
2796
+ /**
2797
+ * 计算 sign 与 qs
2798
+ * @param params query 参数(仅排序后的前 10 个参与签名)
2799
+ * @param data body 参数,GET 请求传空对象
2800
+ */
2801
+ const makeSignAndQs = (params, data = {}) => {
2802
+ const { text: paramsText, keys } = serializeSorted(params, 10);
2803
+ const { text: dataText } = serializeSorted(data);
2804
+ return {
2805
+ sign: sha256Hex(`${paramsText}&${dataText}&app_key=${APP_KEY}`),
2806
+ qs: xor5Hex(keys.join(","))
2807
+ };
2808
+ };
2809
+ /**
2810
+ * 计算 p_no
2811
+ * @param pTs 毫秒时间戳,与 query 里的 p_ts 保持一致
2812
+ */
2813
+ const makePNo = (pTs) => {
2814
+ const parts = {
2815
+ passport_jssdk_version: JSSDK_VERSION,
2816
+ p_bd: BDMS_SDK_VERSION,
2817
+ p_ca: SDK_VERSIONS.pCa,
2818
+ p_ts: pTs,
2819
+ p_ver: SDK_VERSIONS.pVer,
2820
+ p_zt: SDK_VERSIONS.pZt
2821
+ };
2822
+ return sha256Hex(Object.keys(parts).sort().map((key) => `${key}=${parts[key]}`).join("&"));
2823
+ };
2824
+ /**
2825
+ * HKDF 风格的密钥派生,aid-sign 内部使用
2826
+ * @param keyHex 初始密钥(十六进制)
2827
+ * @param length 输出长度
2828
+ */
2829
+ const deriveKey = (keyHex, length) => {
2830
+ const output = [];
2831
+ let previous = "";
2832
+ let counter = 0;
2833
+ while (output.length < length) {
2834
+ counter++;
2835
+ const message = Uint8Array.from([...hexToBytes(previous), counter]);
2836
+ previous = hmacSha256(hexToBytes(keyHex), message).toString("hex");
2837
+ output.push(...hexToBytes(previous));
2838
+ }
2839
+ return Uint8Array.from(output.slice(0, length));
2840
+ };
2841
+ /**
2842
+ * 计算 `x-tt-passport-aid-sign` 请求头
2843
+ * @param urlPath 接口路径,如 `/passport/web/get_qrcode/`
2844
+ * @param timestamp 当日 UTC 正午时间戳(秒),默认取当前
2845
+ */
2846
+ const makeAidSign = (urlPath, timestamp = utcNoonTimestamp()) => {
2847
+ const encoder = new TextEncoder();
2848
+ const ts = String(timestamp);
2849
+ const seed = hmacSha256(encoder.encode(ts), encoder.encode(APP_KEY)).toString("hex");
2850
+ const key = deriveKey(seed, 32);
2851
+ return hmacSha256(key, encoder.encode(`aid=${PASSPORT_AID}&path=${urlPath}&ts=${ts}`)).toString("hex");
2852
+ };
2853
+ /**
2854
+ * 生成 account_sdk_source_info:SDK 采集的浏览器环境快照,异或 5 后转十六进制。
2855
+ *
2856
+ * 上游参考实现内联的是作者本机抓包值(含显卡型号、堆内存占用、带 query 的个人主页 URL),
2857
+ * 不适合进仓库,这里换成一份等价形态的通用快照。
2858
+ *
2859
+ * 实测服务端在 `get_qrcode` 阶段不校验该字段内容(删掉、置空、填垃圾值都同样返回
2860
+ * `error_code: 0`),保留它只是为了与 SDK 的真实请求形态一致。
2861
+ */
2862
+ const makeAccountSdkSourceInfo = () => xor5Hex(JSON.stringify({
2863
+ hardwareConcurrency: 8,
2864
+ webdriver: false,
2865
+ chromedriver: false,
2866
+ shelldriver: false,
2867
+ plugins: 5,
2868
+ innerHeight: ENV_VIEWPORT.innerHeight,
2869
+ innerWidth: ENV_VIEWPORT.innerWidth,
2870
+ outerHeight: ENV_VIEWPORT.outerHeight,
2871
+ outerWidth: ENV_VIEWPORT.outerWidth,
2872
+ webgl: {
2873
+ vendor: "Google Inc. (Intel)",
2874
+ renderer: "ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)"
2875
+ },
2876
+ performance: {
2877
+ timeOrigin: Date.now(),
2878
+ navigationTiming: {
2879
+ entryType: "navigation",
2880
+ initiatorType: "navigation",
2881
+ name: `https://${WEB_HOST}/`,
2882
+ renderBlockingStatus: "non-blocking"
2883
+ }
2884
+ },
2885
+ browser: {
2886
+ bit_protocol: "false",
2887
+ bit_helper: false
2888
+ }
2889
+ }));
2890
+ /**
2891
+ * 构造 passport 登录 SDK 的通用 query 参数
2892
+ *
2893
+ * 参数顺序即实际发送顺序:SDK 各拦截器依次注入,`request_host` 在这里先编码一次,
2894
+ * 拼 URL 时会再编码一次,所以线上抓包看到的是双重编码。
2895
+ * @param extra 业务参数(GET 时并入 query)
2896
+ */
2897
+ const makeCommonParams = (extra = {}) => {
2898
+ const pTs = String(Date.now());
2899
+ return {
2900
+ passport_jssdk_version: JSSDK_VERSION,
2901
+ passport_jssdk_type: "normal",
2902
+ is_from_ttaccountsdk: "1",
2903
+ aid: PASSPORT_AID,
2904
+ language: "zh",
2905
+ account_app_language: "zh-CN",
2906
+ ts: String(utcNoonTimestamp()),
2907
+ ...Object.fromEntries(Object.entries(extra).map(([key, value]) => [key, String(value)])),
2908
+ is_from_iesaccountsaas: "1",
2909
+ p_ui: SDK_VERSIONS.pUi,
2910
+ p_ca: SDK_VERSIONS.pCa,
2911
+ p_ca_real: SDK_VERSIONS.pCaReal,
2912
+ account_sdk_source: "web",
2913
+ account_sdk_source_info: makeAccountSdkSourceInfo(),
2914
+ p_js_v: JSSDK_VERSION,
2915
+ p_js_t: "pro",
2916
+ p_zt: SDK_VERSIONS.pZt,
2917
+ p_ver: SDK_VERSIONS.pVer,
2918
+ p_ver_real: "0",
2919
+ request_host: encodeURIComponent(`https://${WEB_HOST}`),
2920
+ p_bd: BDMS_SDK_VERSION,
2921
+ p_ts: pTs,
2922
+ p_no: makePNo(pTs),
2923
+ biz_trace_id: randomHex(8),
2924
+ device_platform: "web_app"
2925
+ };
2926
+ };
2927
+ /**
2928
+ * 构造验证页 SDK(lite 形态)的固定 query 参数
2929
+ * @param bizTraceId 业务追踪 ID
2930
+ */
2931
+ const makeLiteParams = (bizTraceId) => ({
2932
+ passport_jssdk_version: LITE_JSSDK_VERSION,
2933
+ passport_jssdk_type: "lite",
2934
+ is_from_ttaccountsdk: "1",
2935
+ aid: PASSPORT_AID,
2936
+ language: "zh",
2937
+ account_app_language: "zh-CN",
2938
+ new_authn_sdk_version: LITE_AUTHN_VERSION,
2939
+ biz_trace_id: bizTraceId
2940
+ });
2941
+ /**
2942
+ * 按插入顺序序列化为查询串(不排序,`request_host` 因此产生二次编码)
2943
+ * @param params 参数对象
2944
+ */
2945
+ const serializeQuery = (params) => Object.entries(params).map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`).join("&");
2946
+ //#endregion
2947
+ //#region src/platform/douyin/passport/ticketGuard.ts
2948
+ /**
2949
+ * bd-ticket-guard 设备票据
2950
+ *
2951
+ * 抖音的设备真实性风控。常见的说法是必须从浏览器 localStorage 里导出一对密钥才能用,
2952
+ * 但那只适用于「已登录之后」的接口 —— **登录流程本身就是票据的签发入口**:
2953
+ *
2954
+ * 1. 本地生成一对 P-256 密钥
2955
+ * 2. 把公钥写进 `bd_ticket_guard_client_data` cookie,在取二维码**之前**交给服务端
2956
+ * 3. 服务端在后续响应的 `bd-ticket-guard-server-data` 头里签发 `{ticket, ts_sign, client_cert}`
2957
+ * 4. 之后每个请求用 `ECDH(自己的私钥, client_cert 里的服务端公钥)` 派生的密钥
2958
+ * 对 `ticket=…&path=…&timestamp=…` 做 HMAC,放进 `bd-ticket-guard-client-data` 头
2959
+ *
2960
+ * 全程不需要浏览器,票据是服务端真实签发给我们自己这把密钥的,没有任何伪造数据。
2961
+ * 密钥与票据以 `__amagi_` 前缀存在 CookieJar 里,只在调用之间传递,不会发给服务端。
2962
+ */
2963
+ /** 交给服务端的公钥所在的 cookie */
2964
+ const CLIENT_DATA_COOKIE = "bd_ticket_guard_client_data";
2965
+ /** 声明 web 域版本的 cookie */
2966
+ const CLIENT_WEB_DOMAIN_COOKIE = "bd_ticket_guard_client_web_domain";
2967
+ /** 服务端也可能把签发结果放在这条 cookie 里 */
2968
+ const SERVER_DATA_COOKIE = "bd_ticket_guard_server_data";
2969
+ /** 服务端签发结果所在的响应头 */
2970
+ const SERVER_DATA_HEADER = "bd-ticket-guard-server-data";
2971
+ /** 本地会话状态:PKCS#8 私钥(base64) */
2972
+ const KEY_ENTRY = `${INTERNAL_PREFIX}tg_key`;
2973
+ /** 本地会话状态:服务端签发的票据 */
2974
+ const TICKET_ENTRY = `${INTERNAL_PREFIX}tg_ticket`;
2975
+ /** 本地会话状态:票据的时间戳签名 */
2976
+ const TS_SIGN_ENTRY = `${INTERNAL_PREFIX}tg_ts_sign`;
2977
+ /** 本地会话状态:ECDH 派生出的 HMAC 密钥(hex) */
2978
+ const ECDH_ENTRY = `${INTERNAL_PREFIX}tg_ecdh`;
2979
+ /** SDK 声明的 ticket-guard 版本 */
2980
+ const GUARD_VERSION = "2";
2981
+ /** SDK 声明的迭代版本 */
2982
+ const ITERATION_VERSION = "1";
2983
+ /** P-256 公钥的 SubjectPublicKeyInfo 前缀,其后紧跟 65 字节未压缩点 */
2984
+ const P256_SPKI_PREFIX = Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex");
2985
+ /** 被签名内容的字段列表,需与实际拼接顺序一致 */
2986
+ const REQ_CONTENT = "ticket,path,timestamp";
2987
+ /** 紧凑 JSON,与浏览器的 `JSON.stringify` 一致 */
2988
+ const compactJson = (value) => JSON.stringify(value);
2989
+ /**
2990
+ * 从 PEM 证书或 `pub.<base64>` 里取出服务端公钥
2991
+ * @param clientCert 服务端下发的 client_cert
2992
+ */
2993
+ const readServerPublicKey = (clientCert) => {
2994
+ if (clientCert.startsWith("pub.")) {
2995
+ const point = Buffer.from(clientCert.slice(4), "base64");
2996
+ const body = point.length === 65 ? point.subarray(1) : point;
2997
+ const spki = Buffer.concat([
2998
+ P256_SPKI_PREFIX,
2999
+ Buffer.from([4]),
3000
+ body
3001
+ ]);
3002
+ return crypto.createPublicKey({
3003
+ key: spki,
3004
+ format: "der",
3005
+ type: "spki"
3006
+ });
3007
+ }
3008
+ return new crypto.X509Certificate(clientCert).publicKey;
3009
+ };
3010
+ /**
3011
+ * bd-ticket-guard 会话
3012
+ *
3013
+ * 状态全部读写自传入的 CookieJar,因此与 passport 的无状态调用形态天然兼容。
3014
+ */
3015
+ var TicketGuard = class {
3016
+ jar;
3017
+ /**
3018
+ * @param jar 当前会话 cookie
3019
+ */
3020
+ constructor(jar) {
3021
+ this.jar = jar;
3022
+ }
3023
+ /** 本会话的私钥,缺失时生成一把并写回 CookieJar */
3024
+ get privateKey() {
3025
+ const stored = this.jar.get(KEY_ENTRY);
3026
+ if (stored) return crypto.createPrivateKey({
3027
+ key: Buffer.from(stored, "base64"),
3028
+ format: "der",
3029
+ type: "pkcs8"
3030
+ });
3031
+ const { privateKey } = crypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
3032
+ const der = privateKey.export({
3033
+ format: "der",
3034
+ type: "pkcs8"
3035
+ });
3036
+ this.jar.set(KEY_ENTRY, der.toString("base64"));
3037
+ return privateKey;
3038
+ }
3039
+ /** 未压缩格式的公钥(base64),即 `bd-ticket-guard-ree-public-key` */
3040
+ get reePublicKey() {
3041
+ const spki = crypto.createPublicKey(this.privateKey).export({
3042
+ format: "der",
3043
+ type: "spki"
3044
+ });
3045
+ return spki.subarray(spki.length - 65).toString("base64");
3046
+ }
3047
+ /** 已签发的票据,未签发时为 undefined */
3048
+ get state() {
3049
+ const ticket = this.jar.get(TICKET_ENTRY);
3050
+ const tsSign = this.jar.get(TS_SIGN_ENTRY);
3051
+ const ecdh = this.jar.get(ECDH_ENTRY);
3052
+ if (!ticket || !tsSign || !ecdh) return void 0;
3053
+ return {
3054
+ ticket,
3055
+ tsSign,
3056
+ ecdhKey: Buffer.from(ecdh, "hex")
3057
+ };
3058
+ }
3059
+ /**
3060
+ * 在首个 passport 请求之前把公钥交给服务端
3061
+ *
3062
+ * 缺了这一步扫码依然能成功,但服务端不会签发票据,后续请求也就无从携带。
3063
+ */
3064
+ publishPublicKey() {
3065
+ const payload = compactJson({
3066
+ "bd-ticket-guard-version": Number(GUARD_VERSION),
3067
+ "bd-ticket-guard-iteration-version": Number(ITERATION_VERSION),
3068
+ "bd-ticket-guard-ree-public-key": this.reePublicKey,
3069
+ "bd-ticket-guard-web-version": Number(GUARD_VERSION)
3070
+ });
3071
+ this.jar.set(CLIENT_DATA_COOKIE, encodeURIComponent(Buffer.from(payload, "utf8").toString("base64")));
3072
+ this.jar.set(CLIENT_WEB_DOMAIN_COOKIE, GUARD_VERSION);
3073
+ }
3074
+ /**
3075
+ * 消化响应里可能带回的票据签发结果
3076
+ * @param headers 响应头
3077
+ * @returns 是否收到了新票据
3078
+ */
3079
+ applyServerData(headers) {
3080
+ const fromHeader = headers[SERVER_DATA_HEADER];
3081
+ const raw = typeof fromHeader === "string" && fromHeader ? fromHeader : this.jar.get(SERVER_DATA_COOKIE);
3082
+ if (!raw) return false;
3083
+ let info;
3084
+ try {
3085
+ info = JSON.parse(Buffer.from(decodeURIComponent(raw), "base64").toString("utf8"));
3086
+ } catch {
3087
+ return false;
3088
+ }
3089
+ if (!info.ticket || !info.ts_sign || !info.client_cert) return false;
3090
+ let ecdhKey;
3091
+ try {
3092
+ ecdhKey = this.deriveEcdhKey(info.client_cert);
3093
+ } catch {
3094
+ return false;
3095
+ }
3096
+ this.jar.set(TICKET_ENTRY, info.ticket);
3097
+ this.jar.set(TS_SIGN_ENTRY, info.ts_sign);
3098
+ this.jar.set(ECDH_ENTRY, ecdhKey.toString("hex"));
3099
+ return true;
3100
+ }
3101
+ /**
3102
+ * 生成本次请求的 bd-ticket-guard 请求头
3103
+ *
3104
+ * 尚未拿到票据时只声明公钥,让服务端有机会签发;拿到之后带完整签名。
3105
+ * @param path 请求路径,不含 query
3106
+ * @param timestamp 秒级时间戳,默认取当前
3107
+ */
3108
+ headers(path, timestamp = Math.floor(Date.now() / 1e3)) {
3109
+ const base = {
3110
+ "bd-ticket-guard-version": GUARD_VERSION,
3111
+ "bd-ticket-guard-iteration-version": ITERATION_VERSION,
3112
+ "bd-ticket-guard-ree-public-key": this.reePublicKey
3113
+ };
3114
+ const state = this.state;
3115
+ if (!state) return base;
3116
+ const signed = `ticket=${state.ticket}&path=${path}&timestamp=${timestamp}`;
3117
+ const clientData = compactJson({
3118
+ ts_sign: state.tsSign,
3119
+ req_content: REQ_CONTENT,
3120
+ req_sign: crypto.createHmac("sha256", state.ecdhKey).update(signed, "utf8").digest("base64"),
3121
+ timestamp
3122
+ });
3123
+ return {
3124
+ ...base,
3125
+ "bd-ticket-guard-client-data": Buffer.from(clientData, "utf8").toString("base64"),
3126
+ "bd-ticket-guard-web-version": state.tsSign.startsWith("ts.1") ? "1" : GUARD_VERSION,
3127
+ "bd-ticket-guard-web-sign-type": "1"
3128
+ };
3129
+ }
3130
+ /**
3131
+ * ECDH + HKDF-SHA256 派生 HMAC 密钥
3132
+ * @param clientCert 服务端下发的证书或裸公钥
3133
+ */
3134
+ deriveEcdhKey(clientCert) {
3135
+ const shared = crypto.diffieHellman({
3136
+ privateKey: this.privateKey,
3137
+ publicKey: readServerPublicKey(clientCert)
3138
+ });
3139
+ return Buffer.from(crypto.hkdfSync("sha256", shared, Buffer.alloc(32), Buffer.alloc(0), 32));
3140
+ }
3141
+ };
3142
+ //#endregion
3143
+ //#region src/platform/douyin/passport/client.ts
3144
+ /**
3145
+ * passport 登录的 HTTP 客户端
3146
+ *
3147
+ * 走 amagi 自己的 `fetchResponse`(axios),因此代理、超时、重试与网络事件与其它接口一致。
3148
+ *
3149
+ * 客户端本身是无状态的:会话状态(`msToken`、`passport_csrf_token`)都以 cookie 形式
3150
+ * 随调用方传入的 cookie 串进出,`x-tt-passport-verify-portrait` 则由 `ttwid` 派生,
3151
+ * 因此同一份 cookie 在整个登录过程中会得到稳定的 portrait,调用方无需额外保存任何东西。
3152
+ */
3153
+ /** 与签名里的浏览器环境保持一致的 UA */
3154
+ const PASSPORT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36";
3155
+ /** 单次请求默认超时 */
3156
+ const DEFAULT_TIMEOUT = 15e3;
3157
+ /** SSO 跳转最多跟随的次数 */
3158
+ const MAX_REDIRECT_HOPS = 5;
3159
+ /** 浏览器客户端提示头,服务端会与 UA 交叉校验 */
3160
+ const CLIENT_HINTS = {
3161
+ "sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Chromium\";v=\"142\"",
3162
+ "sec-ch-ua-mobile": "?0",
3163
+ "sec-ch-ua-platform": "\"Windows\""
3164
+ };
3165
+ /** 安全解析 JSON,失败返回空对象 */
3166
+ const parseJson = (text) => {
3167
+ try {
3168
+ return JSON.parse(text);
3169
+ } catch {
3170
+ return {};
3171
+ }
3172
+ };
3173
+ /**
3174
+ * 由 ttwid 派生出稳定的 verify portrait
3175
+ *
3176
+ * 浏览器里这个值在一次登录页生命周期内固定不变。这里用 cookie 中已有的设备指纹推导,
3177
+ * 既保证同一会话内多次调用得到同一个值,又不需要调用方额外携带状态。
3178
+ * @param jar 当前会话 cookie
3179
+ */
3180
+ const deriveVerifyPortrait = (jar) => {
3181
+ const seed = jar.get("ttwid") ?? jar.get("__ac_nonce") ?? "douyin-passport";
3182
+ const hex = crypto.createHash("sha256").update(seed).digest("hex");
3183
+ return `${[
3184
+ hex.slice(0, 8),
3185
+ hex.slice(8, 12),
3186
+ `4${hex.slice(13, 16)}`,
3187
+ `8${hex.slice(17, 20)}`,
3188
+ hex.slice(20, 32)
3189
+ ].join("-")}.login`;
3190
+ };
3191
+ var DouyinPassportClient = class {
3192
+ requestConfig;
3193
+ /** 会话 cookie */
3194
+ cookies;
3195
+ /** bd-ticket-guard 设备票据,状态随 cookie 一起流转 */
3196
+ ticketGuard;
3197
+ /**
3198
+ * @param cookie 已有的会话 cookie 串
3199
+ * @param requestConfig amagi 的请求配置(代理、超时、额外请求头)
3200
+ */
3201
+ constructor(cookie, requestConfig) {
3202
+ this.requestConfig = requestConfig;
3203
+ this.cookies = new CookieJar(cookie);
3204
+ this.ticketGuard = new TicketGuard(this.cookies);
3205
+ }
3206
+ /** CSRF token:优先用服务端下发的,缺失时本地生成并同步写进 cookie(双提交校验) */
3207
+ get csrfToken() {
3208
+ const fromCookie = this.cookies.get("passport_csrf_token");
3209
+ if (fromCookie) return fromCookie;
3210
+ const generated = randomHex(32);
3211
+ this.cookies.set("passport_csrf_token", generated);
3212
+ this.cookies.set("passport_csrf_token_default", generated);
3213
+ return generated;
3214
+ }
3215
+ /**
3216
+ * 初始化登录环境指纹
3217
+ *
3218
+ * 依次请求抖音首页拿 `__ac_nonce`、再向 ttwid 服务注册拿 `ttwid`。两步都是匿名的,
3219
+ * 任意机器、任意系统都能跑;失败不抛错,只会让后续更容易命中风控。
3220
+ */
3221
+ async bootstrap() {
3222
+ this.ticketGuard.publishPublicKey();
3223
+ if (this.cookies.has("ttwid") && this.cookies.has("__ac_nonce")) return;
3224
+ await this.send({
3225
+ method: "GET",
3226
+ url: `https://${WEB_HOST}/`,
3227
+ headers: {
3228
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
3229
+ ...CLIENT_HINTS
3230
+ }
3231
+ });
3232
+ await this.send({
3233
+ method: "POST",
3234
+ url: "https://ttwid.bytedance.com/ttwid/register/",
3235
+ headers: {
3236
+ "Content-Type": "application/json",
3237
+ Origin: `https://${WEB_HOST}`,
3238
+ Referer: `https://${WEB_HOST}/`
3239
+ },
3240
+ data: JSON.stringify({
3241
+ aid: 6383,
3242
+ service: WEB_HOST
3243
+ })
3244
+ });
3245
+ emitLogDebug(`[douyin passport] 环境指纹就绪: ttwid=${this.cookies.has("ttwid")}, ac_nonce=${this.cookies.has("__ac_nonce")}`);
3246
+ }
3247
+ /**
3248
+ * 请求 login.douyin.com 的 passport 接口(四重签名 + a_bogus 形态)
3249
+ * @param path 接口路径,如 `/passport/web/get_qrcode/`
3250
+ * @param params 业务参数,并入 query
3251
+ */
3252
+ async request(path, params = {}) {
3253
+ const common = makeCommonParams(params);
3254
+ const { sign, qs } = makeSignAndQs(common, {});
3255
+ const query = {
3256
+ ...common,
3257
+ sign,
3258
+ qs
3259
+ };
3260
+ const msToken = this.cookies.get("msToken");
3261
+ if (msToken) query.msToken = msToken;
3262
+ const queryString = serializeQuery(query);
3263
+ const url = `https://${LOGIN_HOST}${path}?${queryString}&a_bogus=${encodeURIComponent(aBogus(queryString, PASSPORT_USER_AGENT))}`;
3264
+ return this.send({
3265
+ method: "GET",
3266
+ url,
3267
+ headers: {
3268
+ Accept: "application/json, text/javascript",
3269
+ "Content-Type": "application/x-www-form-urlencoded",
3270
+ Referer: `https://${WEB_HOST}/`,
3271
+ "x-tt-passport-aid-sign": makeAidSign(path),
3272
+ "x-tt-passport-csrf-token": this.csrfToken,
3273
+ "x-tt-passport-verify-portrait": deriveVerifyPortrait(this.cookies),
3274
+ "x-tt-passport-trace-id": String(common.biz_trace_id),
3275
+ ...this.ticketGuard.headers(path),
3276
+ ...CLIENT_HINTS
3277
+ }
3278
+ });
3279
+ }
3280
+ /**
3281
+ * 请求 www.douyin.com 的验证页接口(lite 形态:固定 query + 表单 body,无签名)
3282
+ * @param path 接口路径,如 `/passport/web/send_code/`
3283
+ * @param params 业务参数,进 body
3284
+ * @param bizTraceId 业务追踪 ID,同一次验证流程内保持一致
3285
+ */
3286
+ async liteRequest(path, params, bizTraceId) {
3287
+ return this.send({
3288
+ method: "POST",
3289
+ url: `https://${WEB_HOST}${path}?${serializeQuery(makeLiteParams(bizTraceId))}`,
3290
+ headers: {
3291
+ Accept: "application/json, text/javascript",
3292
+ "Content-Type": "application/x-www-form-urlencoded",
3293
+ Origin: `https://${WEB_HOST}`,
3294
+ Referer: `https://${WEB_HOST}/`,
3295
+ "x-tt-passport-aid-sign": makeAidSign(path),
3296
+ "x-tt-passport-csrf-token": this.csrfToken,
3297
+ "x-tt-passport-verify-portrait": deriveVerifyPortrait(this.cookies),
3298
+ "x-tt-passport-trace-id": bizTraceId,
3299
+ ...this.ticketGuard.headers(path),
3300
+ ...CLIENT_HINTS
3301
+ },
3302
+ data: serializeQuery(params)
3303
+ });
3304
+ }
3305
+ /**
3306
+ * 跟随扫码确认后下发的 SSO 跳转链,把最终的登录凭证收进 CookieJar
3307
+ * @param redirectUrl `check_qrconnect` 返回的 redirect_url
3308
+ * @returns 是否拿到登录态 cookie
3309
+ */
3310
+ async followSsoRedirect(redirectUrl) {
3311
+ let current = redirectUrl;
3312
+ for (let hop = 0; hop < MAX_REDIRECT_HOPS; hop++) {
3313
+ const response = await this.send({
3314
+ method: "GET",
3315
+ url: current,
3316
+ maxRedirects: 0,
3317
+ headers: {
3318
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
3319
+ Referer: `https://${LOGIN_HOST}/`,
3320
+ ...CLIENT_HINTS
3321
+ }
3322
+ });
3323
+ const location = response.location;
3324
+ if (!location || response.status < 300 || response.status >= 400) break;
3325
+ current = new URL(location, current).toString();
3326
+ }
3327
+ return this.cookies.isLoggedIn();
3328
+ }
3329
+ /** 实际发请求:合并 cookie、消化 Set-Cookie 与 msToken */
3330
+ async send(config) {
3331
+ const cookie = this.cookies.toString();
3332
+ const response = await fetchResponse({
3333
+ timeout: this.requestConfig?.timeout ?? DEFAULT_TIMEOUT,
3334
+ proxy: this.requestConfig?.proxy,
3335
+ ...config,
3336
+ responseType: "text",
3337
+ maxRedirects: config.maxRedirects ?? 5,
3338
+ headers: {
3339
+ "User-Agent": PASSPORT_USER_AGENT,
3340
+ ...config.headers,
3341
+ ...cookie ? { Cookie: cookie } : {}
3342
+ }
3343
+ });
3344
+ if (isNetworkErrorResult(response)) throw new Error(response.error.amagiError.errorDescription);
3345
+ const axiosResponse = response;
3346
+ this.cookies.applySetCookie(axiosResponse.headers["set-cookie"]);
3347
+ const refreshed = axiosResponse.headers["x-ms-token"];
3348
+ if (typeof refreshed === "string" && refreshed) this.cookies.set("msToken", refreshed);
3349
+ if (this.ticketGuard.applyServerData(axiosResponse.headers)) emitLogDebug("[douyin passport] 已获得 bd-ticket-guard 票据");
3350
+ const raw = typeof axiosResponse.data === "string" ? axiosResponse.data : JSON.stringify(axiosResponse.data);
3351
+ return {
3352
+ status: axiosResponse.status,
3353
+ raw,
3354
+ body: parseJson(raw),
3355
+ cookie: this.cookies.serialize(),
3356
+ location: axiosResponse.headers.location
3357
+ };
3358
+ }
3359
+ };
3360
+ //#endregion
3361
+ //#region src/platform/douyin/passport/parser.ts
3362
+ /** 触发账号二次验证的错误码 */
3363
+ const ERROR_SECOND_VERIFY = 2046;
3364
+ /** 验证码错误 */
3365
+ const ERROR_WRONG_CODE = 1202;
3366
+ /** 发码过于频繁 */
3367
+ const ERROR_RATE_LIMITED = 1206;
3368
+ /** 命中风控 / 设备环境异常 */
3369
+ const RISK_ERROR_CODES = /* @__PURE__ */ new Set([2156, 4031]);
3370
+ /**
3371
+ * 轮询过于频繁
3372
+ *
3373
+ * 描述文案是「访问太频繁」,但设备指纹不完整时服务端也用这个码兜底,
3374
+ * 属于可重试的瞬时状态,退避后继续轮询即可,不能当致命错误。
3375
+ */
3376
+ const ERROR_POLL_BUSY = 7;
3377
+ /** 命中限频后的退避倍率 */
3378
+ const BUSY_BACKOFF = 2;
3379
+ /** 轮询间隔下限与默认值,服务端偶尔会给 0 */
3380
+ const MIN_INTERVAL = 1e3;
3381
+ const DEFAULT_INTERVAL = 3e3;
3382
+ /** 验证会话票据字段,需原样透传 */
3383
+ const STD_KEYS = [
3384
+ "std_verify_flow_id",
3385
+ "std_verify_scene",
3386
+ "std_verify_template",
3387
+ "std_verify_token",
3388
+ "std_verify_type",
3389
+ "std_verify_way"
3390
+ ];
3391
+ const asString = (value) => typeof value === "string" ? value : "";
3392
+ const asNumber = (value) => {
3393
+ const parsed = typeof value === "number" ? value : Number(value);
3394
+ return Number.isFinite(parsed) ? parsed : void 0;
3395
+ };
3396
+ /** 从响应体的多个可能位置取错误码 */
3397
+ const readErrorCode = (payload) => asNumber(payload.data?.error_code) ?? asNumber(payload.error_code);
3398
+ /** 取一段人类可读的错误描述 */
3399
+ const readMessage = (payload) => asString(payload.data?.description) || asString(payload.description) || asString(payload.message) || "";
3400
+ /**
3401
+ * 解析 `get_qrcode` 响应
3402
+ * @param payload 服务端响应体
3403
+ * @returns 二维码信息,缺少 token 时返回 null
3404
+ */
3405
+ const parseQrcode = (payload) => {
3406
+ const data = payload.data ?? {};
3407
+ const token = asString(data.token);
3408
+ if (!token) return null;
3409
+ return {
3410
+ token,
3411
+ content: asString(data.qrcode_index_url) || token,
3412
+ expireTime: asNumber(data.expire_time) ?? 0
3413
+ };
3414
+ };
3415
+ /** 解析服务端下发的可选验证方式 */
3416
+ const parseVerifyWays = (raw) => {
3417
+ if (!Array.isArray(raw)) return [];
3418
+ return raw.map((item) => {
3419
+ const way = item;
3420
+ return {
3421
+ verifyWay: asString(way?.verify_way),
3422
+ mobile: asString(way?.mobile) || void 0
3423
+ };
3424
+ }).filter((way) => way.verifyWay !== "");
3425
+ };
3426
+ /**
3427
+ * 从轮询响应里提取二次验证上下文
3428
+ * @param data 轮询响应的 data 段
3429
+ */
3430
+ const parseVerifyContext = (data) => {
3431
+ const stdParams = {};
3432
+ for (const key of STD_KEYS) {
3433
+ const value = asString(data[key]);
3434
+ if (value) stdParams[key] = value;
3435
+ }
3436
+ return {
3437
+ encryptUid: asString(data.encrypt_uid),
3438
+ verifyTicket: asString(data.verify_ticket),
3439
+ stdParams,
3440
+ copywritingKey: asString(data.copywriting_key) || "qr_connect",
3441
+ diversionTag: asString(data.ies_safety_diversion_tag) || "mfa",
3442
+ newVerifyFlow: asString(data.new_verify_flow),
3443
+ verifyWays: parseVerifyWays(data.verify_ways)
3444
+ };
3445
+ };
3446
+ /**
3447
+ * 解析 `check_qrconnect` 响应为状态机可消费的结果
3448
+ * @param payload 服务端响应体
3449
+ */
3450
+ const parsePollResult = (payload) => {
3451
+ const data = payload.data ?? {};
3452
+ const rawInterval = asNumber(data.interval) ?? 0;
3453
+ const interval = rawInterval >= MIN_INTERVAL ? rawInterval : DEFAULT_INTERVAL;
3454
+ const status = asString(data.status);
3455
+ const errorCode = readErrorCode(payload);
3456
+ if (errorCode === ERROR_SECOND_VERIFY || asString(data.account_flow) === "verify") return {
3457
+ status: "verify",
3458
+ interval,
3459
+ verify: parseVerifyContext(data)
3460
+ };
3461
+ if (errorCode !== void 0 && RISK_ERROR_CODES.has(errorCode)) return {
3462
+ status: "risk",
3463
+ interval,
3464
+ message: readMessage(payload) || `error_code=${errorCode}`
3465
+ };
3466
+ if (errorCode === ERROR_POLL_BUSY) return {
3467
+ status: "busy",
3468
+ interval: interval * BUSY_BACKOFF,
3469
+ message: readMessage(payload) || "轮询过于频繁"
3470
+ };
3471
+ switch (status) {
3472
+ case "new":
3473
+ case "scanned":
3474
+ case "expired": return {
3475
+ status,
3476
+ interval
3477
+ };
3478
+ case "confirmed": return {
3479
+ status: "confirmed",
3480
+ interval,
3481
+ redirectUrl: asString(data.redirect_url) || (Array.isArray(data.redirect_urls) ? asString(data.redirect_urls[0]) : "")
3482
+ };
3483
+ default: return {
3484
+ status: "unknown",
3485
+ interval,
3486
+ message: readMessage(payload) || (status ? `status=${status}` : `error_code=${errorCode ?? "unknown"}`)
3487
+ };
3488
+ }
3489
+ };
3490
+ /**
3491
+ * 解析 `send_code` 响应
3492
+ * @param payload 服务端响应体
3493
+ */
3494
+ const parseSendCodeResult = (payload) => {
3495
+ const data = payload.data ?? {};
3496
+ const errorCode = readErrorCode(payload);
3497
+ const retryAfter = asNumber(data.retry_time) ?? 60;
3498
+ const mobile = asString(data.mobile);
3499
+ if (errorCode === 0 || errorCode === void 0 && payload.message === "success") return {
3500
+ ok: true,
3501
+ mobile,
3502
+ retryAfter,
3503
+ message: ""
3504
+ };
3505
+ return {
3506
+ ok: false,
3507
+ mobile,
3508
+ retryAfter,
3509
+ errorCode,
3510
+ message: readMessage(payload) || (errorCode === ERROR_RATE_LIMITED ? "短信发送过于频繁" : `发码失败 error_code=${errorCode ?? "unknown"}`)
3511
+ };
3512
+ };
3513
+ /**
3514
+ * 解析 `validate_code` 响应
3515
+ * @param payload 服务端响应体
3516
+ */
3517
+ const parseValidateCodeResult = (payload) => {
3518
+ const data = payload.data ?? {};
3519
+ const errorCode = readErrorCode(payload);
3520
+ if (errorCode === 0 || asString(data.ticket) || errorCode === void 0 && payload.message === "success") return {
3521
+ ok: true,
3522
+ wrongCode: false,
3523
+ message: ""
3524
+ };
3525
+ return {
3526
+ ok: false,
3527
+ wrongCode: errorCode === ERROR_WRONG_CODE,
3528
+ errorCode,
3529
+ message: readMessage(payload) || (errorCode === ERROR_WRONG_CODE ? "验证码错误" : `验证失败 error_code=${errorCode ?? "unknown"}`)
3530
+ };
3531
+ };
3532
+ //#endregion
3533
+ //#region src/platform/douyin/passport/index.ts
3534
+ var passport_exports = /* @__PURE__ */ __exportAll({
3535
+ BDMS_SDK_VERSION: () => BDMS_SDK_VERSION,
3536
+ CookieJar: () => CookieJar,
3537
+ DouyinPassportClient: () => DouyinPassportClient,
3538
+ INTERNAL_PREFIX: () => INTERNAL_PREFIX,
3539
+ PASSPORT_USER_AGENT: () => PASSPORT_USER_AGENT,
3540
+ TicketGuard: () => TicketGuard,
3541
+ aBogus: () => aBogus,
3542
+ makeAidSign: () => makeAidSign,
3543
+ makeSignAndQs: () => makeSignAndQs,
3544
+ parsePollResult: () => parsePollResult,
3545
+ parseQrcode: () => parseQrcode,
3546
+ parseSendCodeResult: () => parseSendCodeResult,
3547
+ parseValidateCodeResult: () => parseValidateCodeResult,
3548
+ randomHex: () => randomHex,
3549
+ sm3: () => sm3,
3550
+ sm3Hex: () => sm3Hex,
3551
+ sm3Twice: () => sm3Twice,
3552
+ utcNoonTimestamp: () => utcNoonTimestamp,
3553
+ xor5Hex: () => xor5Hex
3554
+ });
3555
+ //#endregion
3556
+ //#region src/types/NetworksConfigType.ts
3557
+ /** 快手平台API错误码 */
3558
+ let kuaishouAPIErrorCode = /* @__PURE__ */ function(kuaishouAPIErrorCode) {
3559
+ /** Cookie无效或已过期 */
3560
+ kuaishouAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
3561
+ /** 未知错误 */
3562
+ kuaishouAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
3563
+ return kuaishouAPIErrorCode;
3564
+ }({});
3565
+ /** 小红书平台API错误码 */
3566
+ let xiaohongshuAPIErrorCode = /* @__PURE__ */ function(xiaohongshuAPIErrorCode) {
3567
+ /** Cookie无效或已过期 */
3568
+ xiaohongshuAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
3569
+ /** 未知错误 */
3570
+ xiaohongshuAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
3571
+ /** 非法请求 */
3572
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ILLEGAL_REQUEST"] = 500] = "ILLEGAL_REQUEST";
3573
+ /** 检测到帐号异常,请稍后重试 */
3574
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ACCOUNT_ABNORMAL"] = 300011] = "ACCOUNT_ABNORMAL";
3575
+ /** 网络连接异常,请检查网络设置后重试 */
3576
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["NETWORK_ERROR"] = 300012] = "NETWORK_ERROR";
3577
+ /** 访问频次异常,请勿频繁操作 */
3578
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["FREQUENCY_ERROR"] = 300013] = "FREQUENCY_ERROR";
3579
+ /** 浏览器异常,请尝试更换浏览器后重试 */
3580
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["BROWSER_ERROR"] = 300015] = "BROWSER_ERROR";
3581
+ return xiaohongshuAPIErrorCode;
3582
+ }({});
3583
+ //#endregion
3584
+ //#region src/model/fetchers/douyin/auth.ts
3585
+ /**
3586
+ * 抖音登录认证相关 API(passport 扫码登录)
3587
+ *
3588
+ * 与其它 fetcher 的差别:这几个接口不走 `DouyinData` 的 URL 拼装 + a_bogus 流水线,
3589
+ * 因为 passport 体系有自己的三重 query 签名、独立的 a_bogus 形态与双重编码规则。
3590
+ * 对外形态保持一致:同样是 `(options, cookie?, requestConfig?) => Result<T>`,
3591
+ * 同样发 `apiSuccess` / `apiError` 事件,同样复用 amagi 的代理、超时与重试。
3592
+ *
3593
+ * 这几个方法都是无状态的:会话状态全部装在 cookie 串里,调用方拿到返回的 `cookie`
3594
+ * 后在下一次调用时传回来即可。轮询循环由调用方维护。
3595
+ *
3596
+ * @module fetchers/douyin/auth
3597
+ */
3598
+ /** 短信验证码的验证方式标识,服务端未给出可用方式时的兜底值 */
3599
+ const SMS_VERIFY_WAY = "mobile_sms_verify";
3600
+ /**
3601
+ * 可以用「收 6 位验证码」这套流程走完的验证方式
3602
+ *
3603
+ * 除官方常见的 `mobile_sms_verify`,账号被判定需要辅助验证时会给出
3604
+ * `assist_mobile_sms_verify`,两者都是下行短信收码,走同一对
3605
+ * `send_code` / `validate_code` 接口,区别只在 `std_verify_way` 的取值。
3606
+ * 上行短信(`*_up_sms_verify`)要求用户从手机发短信出去,是另一套接口,不在此列。
3607
+ */
3608
+ const SMS_CODE_WAY_PATTERN = /^(assist_)?mobile_sms_verify$/;
3609
+ /**
3610
+ * 判断某个验证方式能否用短信验证码流程完成
3611
+ * @param verifyWay 服务端下发的 verify_way
3612
+ */
3613
+ const isSmsCodeVerifyWay = (verifyWay) => SMS_CODE_WAY_PATTERN.test(verifyWay);
3614
+ /**
3615
+ * 选出本次要用的 std_verify_way
3616
+ *
3617
+ * 优先用调用方指定的;否则从服务端给出的可选方式里挑一个能收码的;都没有才回退到默认值。
3618
+ * 之前这里写死 `mobile_sms_verify`,遇到辅助验证的账号会因为 way 对不上而失败。
3619
+ * @param verify 轮询下发的验证上下文
3620
+ * @param requested 调用方显式指定的验证方式
3621
+ */
3622
+ const resolveVerifyWay = (verify, requested) => requested ?? verify.verifyWays.find((way) => isSmsCodeVerifyWay(way.verifyWay))?.verifyWay ?? verify.stdParams.std_verify_way ?? SMS_VERIFY_WAY;
3623
+ /** 短信验证码的 act_type */
3624
+ const SMS_ACT_TYPE = "3737";
3625
+ /** 验证页 SDK 版本,随表单一起提交 */
3626
+ const AUTHN_VERSION = "1.0.0.420-web";
3627
+ /** 抖音 web 的 aid */
3628
+ const AID = "6383";
3629
+ /** 扫码成功后的跳转地址 */
3630
+ const NEXT_URL = "https://www.douyin.com";
3631
+ /**
3632
+ * 发码与验码共用的表单字段
3633
+ *
3634
+ * 字段顺序与「空值也要占位」的行为对齐官方验证页 SDK 的抓包形态:
3635
+ * `verify_ticket` / `new_verify_flow` / `std_verify_flow_id` / `std_verify_token`
3636
+ * 即使为空也必须出现,缺字段会被判为伪造请求。
3637
+ * @param verify 轮询下发的验证上下文
3638
+ * @param verifyWay 本次使用的验证方式,原样进 `std_verify_way`
3639
+ * @param tail 追加在 std_verify_way 之后的字段(发码是 is6Digits,验码是 code)
3640
+ */
3641
+ const buildVerifyBody = (verify, verifyWay, tail) => ({
3642
+ mix_mode: "1",
3643
+ type: SMS_ACT_TYPE,
3644
+ encrypt_uid: verify.encryptUid,
3645
+ verify_ticket: verify.verifyTicket,
3646
+ copywriting_key: verify.copywritingKey,
3647
+ ies_safety_diversion_tag: verify.diversionTag,
3648
+ new_verify_flow: verify.newVerifyFlow,
3649
+ std_verify_flow_id: verify.stdParams.std_verify_flow_id ?? "",
3650
+ std_verify_scene: verify.stdParams.std_verify_scene ?? "account_login",
3651
+ std_verify_template: verify.stdParams.std_verify_template ?? "ato_web",
3652
+ std_verify_token: verify.stdParams.std_verify_token ?? "",
3653
+ std_verify_type: verify.stdParams.std_verify_type ?? "MFA",
3654
+ std_verify_way: verifyWay,
3655
+ ...tail,
3656
+ aid: AID,
3657
+ new_authn_sdk_version: AUTHN_VERSION
3658
+ });
2650
3659
  /**
2651
- * B站 Fetcher 模块入口
2652
- * @module fetchers/bilibili
2653
- */
3660
+ * 构造 passport 侧的业务错误响应
3661
+ * @param methodType 方法名,进 amagiError.requestType
3662
+ * @param message 错误描述
3663
+ */
3664
+ const passportError = (methodType, message) => createErrorResponse({
3665
+ code: "UNKNOWN_ERROR",
3666
+ data: null,
3667
+ amagiError: {
3668
+ errorDescription: message,
3669
+ requestType: methodType,
3670
+ requestUrl: `https://login.douyin.com/passport/`
3671
+ },
3672
+ amagiMessage: message
3673
+ }, message);
3674
+ /** 统一包一层事件上报与异常兜底 */
3675
+ const run = async (methodType, task) => {
3676
+ const startTime = Date.now();
3677
+ try {
3678
+ const result = await task();
3679
+ const duration = Date.now() - startTime;
3680
+ if (result.code === 200) emitApiSuccess({
3681
+ platform: "douyin",
3682
+ methodType,
3683
+ response: result,
3684
+ statusCode: 200,
3685
+ duration
3686
+ });
3687
+ else emitApiError({
3688
+ platform: "douyin",
3689
+ methodType,
3690
+ errorCode: result.code,
3691
+ errorMessage: result.message,
3692
+ duration
3693
+ });
3694
+ return result;
3695
+ } catch (error) {
3696
+ const duration = Date.now() - startTime;
3697
+ const errorMessage = error instanceof Error ? error.message : "未知错误";
3698
+ emitApiError({
3699
+ platform: "douyin",
3700
+ methodType,
3701
+ errorMessage,
3702
+ duration
3703
+ });
3704
+ throw new Error(`抖音登录请求失败: ${errorMessage}`);
3705
+ }
3706
+ };
2654
3707
  /**
2655
- * B站数据获取器
2656
- * 包含所有 B站 API 方法,调用时需要传递 cookie
3708
+ * 申请抖音扫码登录二维码
3709
+ *
3710
+ * 首次调用会自动完成环境指纹初始化(`__ac_nonce` + `ttwid`),无需额外准备。
3711
+ * @param options - 请求选项 (可选)
3712
+ * @param cookie - 已有的会话 Cookie (可选,续用同一会话时传入)
3713
+ * @param requestConfig - 请求配置 (可选)
3714
+ * @returns 二维码令牌、内容与会话 cookie
2657
3715
  * @example
2658
3716
  * ```typescript
2659
- * import { bilibiliFetcher } from '@ikenxuan/amagi'
3717
+ * const qrcode = await requestPassportQrcode()
3718
+ * console.log(qrcode.data.content) // 拿去生成二维码图片
3719
+ * ```
3720
+ */
3721
+ async function requestPassportQrcode(options, cookie, requestConfig) {
3722
+ return run("passportQrcode", async () => {
3723
+ const client = new DouyinPassportClient(cookie, requestConfig);
3724
+ await client.bootstrap();
3725
+ const response = await client.request("/passport/web/get_qrcode/", {
3726
+ next: NEXT_URL,
3727
+ need_short_url: "true",
3728
+ need_logo: "false",
3729
+ is_new_login: "1"
3730
+ });
3731
+ const qrcode = parseQrcode(response.body);
3732
+ if (!qrcode) return passportError("passportQrcode", response.body.message || `获取二维码失败: ${response.raw.slice(0, 200)}`);
3733
+ return createSuccessResponse({
3734
+ token: qrcode.token,
3735
+ content: qrcode.content,
3736
+ expire_time: qrcode.expireTime,
3737
+ expires_in: Math.max(0, qrcode.expireTime - Math.floor(Date.now() / 1e3)),
3738
+ cookie: response.cookie
3739
+ }, "获取成功", 200);
3740
+ });
3741
+ }
3742
+ /**
3743
+ * 查询抖音扫码登录二维码的状态
2660
3744
  *
2661
- * const result = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD' }, cookie)
3745
+ * 状态为 `confirmed` 时会自动跟随 SSO 跳转领取登录凭证,返回的 `cookie` 即完整登录态。
3746
+ * @param options - 二维码状态参数
3747
+ * @param options.token - `requestPassportQrcode` 返回的令牌
3748
+ * @param cookie - 会话 Cookie,必须是申请二维码时返回的那一份
3749
+ * @param requestConfig - 请求配置 (可选)
3750
+ * @returns 扫码状态与最新会话 cookie
3751
+ * @example
3752
+ * ```typescript
3753
+ * const status = await checkPassportQrcode({ token }, cookie)
3754
+ * // new 未扫码 / scanned 已扫待确认 / verify 需二次验证 / confirmed 登录成功 / expired 已过期
3755
+ * console.log(status.data.status)
2662
3756
  * ```
2663
3757
  */
2664
- const bilibiliFetcher = {
2665
- fetchVideoInfo,
2666
- fetchVideoStreamUrl,
2667
- fetchVideoDanmaku,
2668
- fetchComments,
2669
- fetchCommentReplies: fetchCommentReplies$1,
2670
- fetchUserCard,
2671
- fetchUserDynamicList,
2672
- fetchUserLiveStatus,
2673
- fetchUserSpaceInfo,
2674
- fetchUploaderTotalViews,
2675
- fetchDynamicDetail,
2676
- fetchDynamicCard,
2677
- fetchBangumiInfo,
2678
- fetchBangumiStreamUrl,
2679
- fetchLiveRoomInfo: fetchLiveRoomInfo$2,
2680
- fetchLiveRoomInitInfo,
2681
- fetchArticleContent,
2682
- fetchArticleCards,
2683
- fetchArticleInfo,
2684
- fetchArticleListInfo,
2685
- fetchLoginStatus,
2686
- requestLoginQrcode: requestLoginQrcode$1,
2687
- checkQrcodeStatus,
2688
- requestCaptchaFromVoucher,
2689
- validateCaptchaResult,
2690
- convertAvToBv,
2691
- convertBvToAv,
2692
- fetchEmojiList: fetchEmojiList$3
2693
- };
3758
+ async function checkPassportQrcode(options, cookie, requestConfig) {
3759
+ return run("passportQrcodeStatus", async () => {
3760
+ if (!options?.token) return passportError("passportQrcodeStatus", "缺少 token 参数");
3761
+ const client = new DouyinPassportClient(cookie, requestConfig);
3762
+ const response = await client.request("/passport/web/check_qrconnect/", {
3763
+ next: NEXT_URL,
3764
+ need_logo: "false",
3765
+ is_frontier: "true",
3766
+ token: options.token,
3767
+ is_new_login: "1",
3768
+ need_short_url: "true"
3769
+ });
3770
+ const result = parsePollResult(response.body);
3771
+ if (result.status === "verify") emitLogDebug(`[douyin passport] 触发二次验证,服务端原始响应: ${response.raw.slice(0, 1e3)}`);
3772
+ if (result.status === "confirmed" && result.redirectUrl) await client.followSsoRedirect(result.redirectUrl);
3773
+ const sessionCookie = result.status === "confirmed" ? client.cookies.toString() : client.cookies.serialize();
3774
+ return createSuccessResponse({
3775
+ ...result,
3776
+ cookie: sessionCookie,
3777
+ logged_in: client.cookies.isLoggedIn()
3778
+ }, "获取成功", 200);
3779
+ });
3780
+ }
3781
+ /**
3782
+ * 向账号绑定手机发送二次验证短信验证码
3783
+ *
3784
+ * 用于轮询返回 `status: 'verify'`(即 `error_code=2046` / `account_flow=verify`)的场景。
3785
+ * @param options - 发码参数
3786
+ * @param options.verify - 轮询返回的验证上下文
3787
+ * @param options.biz_trace_id - 追踪 ID (可选,不传自动生成)
3788
+ * @param cookie - 会话 Cookie
3789
+ * @param requestConfig - 请求配置 (可选)
3790
+ * @returns 脱敏手机号、重发等待秒数与追踪 ID
3791
+ */
3792
+ async function sendPassportVerifyCode(options, cookie, requestConfig) {
3793
+ return run("passportSendCode", async () => {
3794
+ if (!options?.verify?.encryptUid) return passportError("passportSendCode", "缺少 encrypt_uid,请从轮询响应中取得验证上下文");
3795
+ const bizTraceId = options.biz_trace_id ?? randomHex(8);
3796
+ const verifyWay = resolveVerifyWay(options.verify, options.verify_way);
3797
+ emitLogDebug(`[douyin passport] 发码使用的验证方式: ${verifyWay}`);
3798
+ const response = await new DouyinPassportClient(cookie, requestConfig).liteRequest("/passport/web/send_code/", buildVerifyBody(options.verify, verifyWay, { is6Digits: "1" }), bizTraceId);
3799
+ const result = parseSendCodeResult(response.body);
3800
+ if (!result.ok) emitLogDebug(`[douyin passport] 发码失败原文: ${response.raw.slice(0, 500)}`);
3801
+ return createSuccessResponse({
3802
+ ...result,
3803
+ cookie: response.cookie,
3804
+ biz_trace_id: bizTraceId,
3805
+ verify_way: verifyWay
3806
+ }, "获取成功", 200);
3807
+ });
3808
+ }
3809
+ /**
3810
+ * 提交二次验证的短信验证码
3811
+ * @param options - 验码参数
3812
+ * @param options.verify - 轮询返回的验证上下文
3813
+ * @param options.code - 用户收到的 6 位验证码明文
3814
+ * @param options.biz_trace_id - 必须与发码时用的是同一个
3815
+ * @param cookie - 会话 Cookie
3816
+ * @param requestConfig - 请求配置 (可选)
3817
+ * @returns 验证结果;`wrongCode` 为 true 表示验证码填错,可以让用户重试
3818
+ */
3819
+ async function validatePassportVerifyCode(options, cookie, requestConfig) {
3820
+ return run("passportValidateCode", async () => {
3821
+ if (!options?.verify?.encryptUid) return passportError("passportValidateCode", "缺少 encrypt_uid,请从轮询响应中取得验证上下文");
3822
+ if (!options.code) return passportError("passportValidateCode", "缺少 code,请填入收到的短信验证码");
3823
+ const response = await new DouyinPassportClient(cookie, requestConfig).liteRequest("/passport/web/validate_code/", buildVerifyBody(options.verify, resolveVerifyWay(options.verify, options.verify_way), { code: xor5Hex(options.code) }), options.biz_trace_id ?? randomHex(8));
3824
+ const result = parseValidateCodeResult(response.body);
3825
+ if (!result.ok) emitLogDebug(`[douyin passport] 验码失败原文: ${response.raw.slice(0, 500)}`);
3826
+ return createSuccessResponse({
3827
+ ...result,
3828
+ cookie: response.cookie
3829
+ }, "获取成功", 200);
3830
+ });
3831
+ }
2694
3832
  //#endregion
2695
3833
  //#region src/platform/defaultConfigs.ts
2696
3834
  /**
@@ -2826,34 +3964,6 @@ const getXiaohongshuDefaultConfig = (cookie) => {
2826
3964
  } };
2827
3965
  };
2828
3966
  //#endregion
2829
- //#region src/types/NetworksConfigType.ts
2830
- /** 快手平台API错误码 */
2831
- let kuaishouAPIErrorCode = /* @__PURE__ */ function(kuaishouAPIErrorCode) {
2832
- /** Cookie无效或已过期 */
2833
- kuaishouAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
2834
- /** 未知错误 */
2835
- kuaishouAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
2836
- return kuaishouAPIErrorCode;
2837
- }({});
2838
- /** 小红书平台API错误码 */
2839
- let xiaohongshuAPIErrorCode = /* @__PURE__ */ function(xiaohongshuAPIErrorCode) {
2840
- /** Cookie无效或已过期 */
2841
- xiaohongshuAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
2842
- /** 未知错误 */
2843
- xiaohongshuAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
2844
- /** 非法请求 */
2845
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ILLEGAL_REQUEST"] = 500] = "ILLEGAL_REQUEST";
2846
- /** 检测到帐号异常,请稍后重试 */
2847
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ACCOUNT_ABNORMAL"] = 300011] = "ACCOUNT_ABNORMAL";
2848
- /** 网络连接异常,请检查网络设置后重试 */
2849
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["NETWORK_ERROR"] = 300012] = "NETWORK_ERROR";
2850
- /** 访问频次异常,请勿频繁操作 */
2851
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["FREQUENCY_ERROR"] = 300013] = "FREQUENCY_ERROR";
2852
- /** 浏览器异常,请尝试更换浏览器后重试 */
2853
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["BROWSER_ERROR"] = 300015] = "BROWSER_ERROR";
2854
- return xiaohongshuAPIErrorCode;
2855
- }({});
2856
- //#endregion
2857
3967
  //#region src/platform/douyin/sign/a_bogus.ts
2858
3968
  var SM3 = class {
2859
3969
  reg;
@@ -3055,8 +4165,6 @@ function result_encrypt(long_str, num) {
3055
4165
  case 3:
3056
4166
  temp_int = long_int & 63;
3057
4167
  result += constant["str"].charAt(temp_int);
3058
- break;
3059
- default: break;
3060
4168
  }
3061
4169
  }
3062
4170
  return result;
@@ -3558,7 +4666,8 @@ var DouyinAPI = class {
3558
4666
  }
3559
4667
  /** 获取视频或图集数据 */
3560
4668
  getWorkDetail(data) {
3561
- return `https://www.douyin.com/aweme/v1/web/aweme/detail/?${buildQueryString({
4669
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/aweme/detail/";
4670
+ const params = {
3562
4671
  ...this.getBaseParams(),
3563
4672
  aweme_id: data.aweme_id,
3564
4673
  update_version_code: "170400",
@@ -3568,11 +4677,13 @@ var DouyinAPI = class {
3568
4677
  screen_height: "1310",
3569
4678
  round_trip_time: "150",
3570
4679
  webid: "7351848354471872041"
3571
- })}`;
4680
+ };
4681
+ return `${baseUrl}?${buildQueryString(params)}`;
3572
4682
  }
3573
4683
  /** 获取评论数据 */
3574
4684
  getComments(data) {
3575
- return `https://www.douyin.com/aweme/v1/web/comment/list/?${buildQueryString({
4685
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/comment/list/";
4686
+ const params = {
3576
4687
  ...this.getBaseParams(),
3577
4688
  aweme_id: data.aweme_id,
3578
4689
  cursor: data.cursor ?? 0,
@@ -3587,11 +4698,13 @@ var DouyinAPI = class {
3587
4698
  screen_width: "1552",
3588
4699
  screen_height: "970",
3589
4700
  round_trip_time: "50"
3590
- })}`;
4701
+ };
4702
+ return `${baseUrl}?${buildQueryString(params)}`;
3591
4703
  }
3592
4704
  /** 获取二级评论数据 */
3593
4705
  getCommentReplies(data) {
3594
- return `https://www-hj.douyin.com/aweme/v1/web/comment/list/reply/?${buildQueryString({
4706
+ const baseUrl = "https://www-hj.douyin.com/aweme/v1/web/comment/list/reply/";
4707
+ const params = {
3595
4708
  device_platform: "webapp",
3596
4709
  aid: "6383",
3597
4710
  channel: "channel_pc_web",
@@ -3629,11 +4742,13 @@ var DouyinAPI = class {
3629
4742
  webid: "7487210762873685515",
3630
4743
  verifyFp: fp,
3631
4744
  fp
3632
- })}`;
4745
+ };
4746
+ return `${baseUrl}?${buildQueryString(params)}`;
3633
4747
  }
3634
4748
  /** 获取动图数据 */
3635
4749
  getSlidesInfo(data) {
3636
- return `https://www.iesdouyin.com/web/api/v2/aweme/slidesinfo/?${buildQueryString({
4750
+ const baseUrl = "https://www.iesdouyin.com/web/api/v2/aweme/slidesinfo/";
4751
+ const params = {
3637
4752
  reflow_source: "reflow_page",
3638
4753
  web_id: "7326472315356857893",
3639
4754
  device_id: "7326472315356857893",
@@ -3642,7 +4757,8 @@ var DouyinAPI = class {
3642
4757
  msToken: douyinSign.Mstoken(116),
3643
4758
  verifyFp: fp,
3644
4759
  fp
3645
- })}`;
4760
+ };
4761
+ return `${baseUrl}?${buildQueryString(params)}`;
3646
4762
  }
3647
4763
  /** 获取表情数据 */
3648
4764
  getEmojiList() {
@@ -3650,7 +4766,8 @@ var DouyinAPI = class {
3650
4766
  }
3651
4767
  /** 获取用户主页视频数据 */
3652
4768
  getUserVideoList(data) {
3653
- return `https://www.douyin.com/aweme/v1/web/aweme/post/?${buildQueryString({
4769
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/aweme/post/";
4770
+ const params = {
3654
4771
  ...this.getBaseParams(),
3655
4772
  sec_user_id: data.sec_uid,
3656
4773
  max_cursor: data.max_cursor ?? "0",
@@ -3668,11 +4785,13 @@ var DouyinAPI = class {
3668
4785
  screen_height: "970",
3669
4786
  round_trip_time: "50",
3670
4787
  webid: "7338423850134226495"
3671
- })}`;
4788
+ };
4789
+ return `${baseUrl}?${buildQueryString(params)}`;
3672
4790
  }
3673
4791
  /** 获取用户喜欢列表数据 */
3674
4792
  getUserFavoriteList(data) {
3675
- return `https://www-hj.douyin.com/aweme/v1/web/aweme/favorite/?${buildQueryString({
4793
+ const baseUrl = "https://www-hj.douyin.com/aweme/v1/web/aweme/favorite/";
4794
+ const params = {
3676
4795
  ...this.getBaseParams(),
3677
4796
  sec_user_id: data.sec_uid,
3678
4797
  max_cursor: data.max_cursor ?? "0",
@@ -3691,11 +4810,13 @@ var DouyinAPI = class {
3691
4810
  screen_height: "1310",
3692
4811
  round_trip_time: "0",
3693
4812
  webid: "7487210762873685515"
3694
- })}`;
4813
+ };
4814
+ return `${baseUrl}?${buildQueryString(params)}`;
3695
4815
  }
3696
4816
  /** 获取用户推荐列表数据 */
3697
4817
  getUserRecommendList(data) {
3698
- return `https://www.douyin.com/aweme/v1/web/familiar/recommend/feed/?${buildQueryString({
4818
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/familiar/recommend/feed/";
4819
+ const params = {
3699
4820
  device_platform: "",
3700
4821
  aid: "6383",
3701
4822
  channel: "channel_pc_web",
@@ -3734,11 +4855,13 @@ var DouyinAPI = class {
3734
4855
  msToken: douyinSign.Mstoken(184),
3735
4856
  verifyFp: fp,
3736
4857
  fp
3737
- })}`;
4858
+ };
4859
+ return `${baseUrl}?${buildQueryString(params)}`;
3738
4860
  }
3739
4861
  /** 获取用户主页信息 */
3740
4862
  getUserProfile(data) {
3741
- return `https://www.douyin.com/aweme/v1/web/user/profile/other/?${buildQueryString({
4863
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/user/profile/other/";
4864
+ const params = {
3742
4865
  ...this.getBaseParams(),
3743
4866
  publish_video_strategy_type: "2",
3744
4867
  source: "channel_pc_web",
@@ -3750,11 +4873,13 @@ var DouyinAPI = class {
3750
4873
  screen_height: "970",
3751
4874
  round_trip_time: "0",
3752
4875
  webid: "7327957959955580467"
3753
- })}`;
4876
+ };
4877
+ return `${baseUrl}?${buildQueryString(params)}`;
3754
4878
  }
3755
4879
  /** 获取热点词数据 */
3756
4880
  getSuggestWords(data) {
3757
- return `https://www.douyin.com/aweme/v1/web/api/suggest_words/?${buildQueryString({
4881
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/api/suggest_words/";
4882
+ const params = {
3758
4883
  ...this.getBaseParams(),
3759
4884
  query: data.query,
3760
4885
  business_id: "30088",
@@ -3765,91 +4890,103 @@ var DouyinAPI = class {
3765
4890
  screen_height: "970",
3766
4891
  round_trip_time: "50",
3767
4892
  webid: "7327957959955580467"
3768
- })}`;
4893
+ };
4894
+ return `${baseUrl}?${buildQueryString(params)}`;
3769
4895
  }
3770
4896
  /** 获取搜索数据 */
3771
4897
  search(data) {
3772
4898
  const searchType = data.type ?? "general";
3773
4899
  const { verifyFp, fp, ...baseParamsWithoutFp } = this.getBaseParams();
3774
- if (searchType === "user") return `https://www.douyin.com/aweme/v1/web/discover/search/?${buildQueryString({
3775
- ...baseParamsWithoutFp,
3776
- count: data.number ?? 10,
3777
- disable_rs: "0",
3778
- from_group_id: "",
3779
- is_filter_search: "0",
3780
- keyword: data.query,
3781
- list_type: "single",
3782
- need_filter_settings: "1",
3783
- offset: "0",
3784
- pc_libra_divert: "Windows",
3785
- pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
3786
- query_correct_type: "1",
3787
- round_trip_time: "250",
3788
- screen_height: "1310",
3789
- screen_width: "2328",
3790
- search_channel: "aweme_user_web",
3791
- search_source: "switch_tab",
3792
- support_dash: "1",
3793
- support_h265: "1",
3794
- version_code: "170400",
3795
- version_name: "17.4.0",
3796
- webid: "7521399115230610959",
3797
- ...data.search_id && { search_id: data.search_id }
3798
- })}`;
3799
- else if (searchType === "video") return `https://www.douyin.com/aweme/v1/web/search/item/?${buildQueryString({
3800
- ...baseParamsWithoutFp,
3801
- count: data.number ?? 10,
3802
- disable_rs: "0",
3803
- enable_history: "1",
3804
- from_group_id: "",
3805
- is_filter_search: "0",
3806
- keyword: data.query,
3807
- list_type: "single",
3808
- need_filter_settings: "1",
3809
- offset: "0",
3810
- pc_libra_divert: "Windows",
3811
- pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
3812
- query_correct_type: "1",
3813
- round_trip_time: "50",
3814
- screen_height: "1310",
3815
- screen_width: "2328",
3816
- search_channel: "aweme_video_web",
3817
- search_source: "switch_tab",
3818
- support_dash: "1",
3819
- support_h265: "1",
3820
- version_code: "170400",
3821
- version_name: "17.4.0",
3822
- webid: "7521399115230610959",
3823
- ...data.search_id && { search_id: data.search_id }
3824
- })}`;
3825
- else return `https://www.douyin.com/aweme/v1/web/general/search/stream/?${buildQueryString({
3826
- ...baseParamsWithoutFp,
3827
- count: data.number ?? 10,
3828
- disable_rs: "0",
3829
- enable_history: "1",
3830
- is_filter_search: "0",
3831
- keyword: data.query,
3832
- list_type: "",
3833
- need_filter_settings: "1",
3834
- offset: "0",
3835
- pc_libra_divert: "Windows",
3836
- pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
3837
- query_correct_type: "1",
3838
- round_trip_time: "0",
3839
- screen_height: "1310",
3840
- screen_width: "2328",
3841
- search_channel: "aweme_general",
3842
- search_source: "normal_search",
3843
- support_dash: "1",
3844
- support_h265: "1",
3845
- version_code: "190600",
3846
- version_name: "19.6.0",
3847
- webid: "7521399115230610959"
3848
- })}`;
4900
+ if (searchType === "user") {
4901
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/discover/search/";
4902
+ const params = {
4903
+ ...baseParamsWithoutFp,
4904
+ count: data.number ?? 10,
4905
+ disable_rs: "0",
4906
+ from_group_id: "",
4907
+ is_filter_search: "0",
4908
+ keyword: data.query,
4909
+ list_type: "single",
4910
+ need_filter_settings: "1",
4911
+ offset: "0",
4912
+ pc_libra_divert: "Windows",
4913
+ pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
4914
+ query_correct_type: "1",
4915
+ round_trip_time: "250",
4916
+ screen_height: "1310",
4917
+ screen_width: "2328",
4918
+ search_channel: "aweme_user_web",
4919
+ search_source: "switch_tab",
4920
+ support_dash: "1",
4921
+ support_h265: "1",
4922
+ version_code: "170400",
4923
+ version_name: "17.4.0",
4924
+ webid: "7521399115230610959",
4925
+ ...data.search_id && { search_id: data.search_id }
4926
+ };
4927
+ return `${baseUrl}?${buildQueryString(params)}`;
4928
+ } else if (searchType === "video") {
4929
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/search/item/";
4930
+ const params = {
4931
+ ...baseParamsWithoutFp,
4932
+ count: data.number ?? 10,
4933
+ disable_rs: "0",
4934
+ enable_history: "1",
4935
+ from_group_id: "",
4936
+ is_filter_search: "0",
4937
+ keyword: data.query,
4938
+ list_type: "single",
4939
+ need_filter_settings: "1",
4940
+ offset: "0",
4941
+ pc_libra_divert: "Windows",
4942
+ pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
4943
+ query_correct_type: "1",
4944
+ round_trip_time: "50",
4945
+ screen_height: "1310",
4946
+ screen_width: "2328",
4947
+ search_channel: "aweme_video_web",
4948
+ search_source: "switch_tab",
4949
+ support_dash: "1",
4950
+ support_h265: "1",
4951
+ version_code: "170400",
4952
+ version_name: "17.4.0",
4953
+ webid: "7521399115230610959",
4954
+ ...data.search_id && { search_id: data.search_id }
4955
+ };
4956
+ return `${baseUrl}?${buildQueryString(params)}`;
4957
+ } else {
4958
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/general/search/stream/";
4959
+ const params = {
4960
+ ...baseParamsWithoutFp,
4961
+ count: data.number ?? 10,
4962
+ disable_rs: "0",
4963
+ enable_history: "1",
4964
+ is_filter_search: "0",
4965
+ keyword: data.query,
4966
+ list_type: "",
4967
+ need_filter_settings: "1",
4968
+ offset: "0",
4969
+ pc_libra_divert: "Windows",
4970
+ pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
4971
+ query_correct_type: "1",
4972
+ round_trip_time: "0",
4973
+ screen_height: "1310",
4974
+ screen_width: "2328",
4975
+ search_channel: "aweme_general",
4976
+ search_source: "normal_search",
4977
+ support_dash: "1",
4978
+ support_h265: "1",
4979
+ version_code: "190600",
4980
+ version_name: "19.6.0",
4981
+ webid: "7521399115230610959"
4982
+ };
4983
+ return `${baseUrl}?${buildQueryString(params)}`;
4984
+ }
3849
4985
  }
3850
4986
  /** 获取互动表情数据 */
3851
4987
  getDynamicEmojiList() {
3852
- return `https://www.douyin.com/aweme/v1/web/im/strategy/config?${buildQueryString({
4988
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/im/strategy/config";
4989
+ const params = {
3853
4990
  device_platform: "webapp",
3854
4991
  aid: "1128",
3855
4992
  channel: "channel_pc_web",
@@ -3881,11 +5018,13 @@ var DouyinAPI = class {
3881
5018
  msToken: douyinSign.Mstoken(116),
3882
5019
  verifyFp: fp,
3883
5020
  fp
3884
- })}`;
5021
+ };
5022
+ return `${baseUrl}?${buildQueryString(params)}`;
3885
5023
  }
3886
5024
  /** 获取背景音乐数据 */
3887
5025
  getMusicInfo(data) {
3888
- return `https://www.douyin.com/aweme/v1/web/music/detail/?${buildQueryString({
5026
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/music/detail/";
5027
+ const params = {
3889
5028
  device_platform: "webapp",
3890
5029
  aid: "6383",
3891
5030
  channel: "channel_pc_web",
@@ -3916,11 +5055,13 @@ var DouyinAPI = class {
3916
5055
  msToken: douyinSign.Mstoken(116),
3917
5056
  verifyFp: fp,
3918
5057
  fp
3919
- })}`;
5058
+ };
5059
+ return `${baseUrl}?${buildQueryString(params)}`;
3920
5060
  }
3921
5061
  /** 获取直播间信息 */
3922
5062
  getLiveRoomInfo(data) {
3923
- return `https://live.douyin.com/webcast/room/web/enter/?${buildQueryString({
5063
+ const baseUrl = "https://live.douyin.com/webcast/room/web/enter/";
5064
+ const params = {
3924
5065
  aid: "6383",
3925
5066
  app_name: "douyin_web",
3926
5067
  live_id: "1",
@@ -3943,18 +5084,22 @@ var DouyinAPI = class {
3943
5084
  msToken: douyinSign.Mstoken(116),
3944
5085
  verifyFp: fp,
3945
5086
  fp
3946
- })}`;
5087
+ };
5088
+ return `${baseUrl}?${buildQueryString(params)}`;
3947
5089
  }
3948
5090
  /** 申请登录二维码 */
3949
5091
  getLoginQrcode(data) {
3950
- return `https://sso.douyin.com/get_qrcode/?${buildQueryString({
5092
+ const baseUrl = "https://sso.douyin.com/get_qrcode/";
5093
+ const params = {
3951
5094
  verifyFp: data.verify_fp,
3952
5095
  fp: data.verify_fp
3953
- })}`;
5096
+ };
5097
+ return `${baseUrl}?${buildQueryString(params)}`;
3954
5098
  }
3955
5099
  /** 获取弹幕数据 */
3956
5100
  getDanmakuList(data) {
3957
- return `https://www-hj.douyin.com/aweme/v1/web/danmaku/get_v2/?${buildQueryString({
5101
+ const baseUrl = "https://www-hj.douyin.com/aweme/v1/web/danmaku/get_v2/";
5102
+ const params = {
3958
5103
  ...this.getBaseParams(),
3959
5104
  app_name: "aweme",
3960
5105
  format: "json",
@@ -3981,7 +5126,8 @@ var DouyinAPI = class {
3981
5126
  msToken: douyinSign.Mstoken(116),
3982
5127
  verifyFp: fp,
3983
5128
  fp
3984
- })}`;
5129
+ };
5130
+ return `${baseUrl}?${buildQueryString(params)}`;
3985
5131
  }
3986
5132
  };
3987
5133
  /**
@@ -4003,7 +5149,6 @@ const douyinApiUrls = new DouyinAPI();
4003
5149
  * 提供抖音各类数据的获取功能,包括视频、评论、用户等
4004
5150
  *
4005
5151
  * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
4006
- * 循环依赖链:DataFetchers → getdata → platform/douyin → DataFetchers
4007
5152
  *
4008
5153
  * @module platform/douyin/getdata
4009
5154
  */
@@ -4280,7 +5425,8 @@ const DouyinData = async (data, cookie, requestConfig) => {
4280
5425
  signType: null,
4281
5426
  processRawResponse: (raw) => {
4282
5427
  if (!isUserSearch && !isVideoSearch) {
4283
- const responses = filterSearchResponses(typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw]);
5428
+ const chunks = typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw];
5429
+ const responses = filterSearchResponses(chunks);
4284
5430
  if (responses.length === 0) return raw;
4285
5431
  const mergedData = [];
4286
5432
  let lastValid = {};
@@ -4659,7 +5805,8 @@ const filterSearchResponses = (objs) => {
4659
5805
  async function fetchDouyinInternal(methodType, options, config) {
4660
5806
  const startTime = Date.now();
4661
5807
  try {
4662
- const rawData = await DouyinData({ ...validateDouyinParams(methodType, options) }, config.cookie, config.requestConfig);
5808
+ const apiParams = { ...validateDouyinParams(methodType, options) };
5809
+ const rawData = await DouyinData(apiParams, config.cookie, config.requestConfig);
4663
5810
  const duration = Date.now() - startTime;
4664
5811
  if (rawData.data === "" || rawData.status_code !== 0) {
4665
5812
  emitApiError({
@@ -5140,6 +6287,10 @@ function createBoundDouyinFetcher(cookie, requestConfig) {
5140
6287
  * ```
5141
6288
  */
5142
6289
  const douyinFetcher = {
6290
+ requestPassportQrcode,
6291
+ checkPassportQrcode,
6292
+ sendPassportVerifyCode,
6293
+ validatePassportVerifyCode,
5143
6294
  fetchVideoWork: fetchVideoWork$1,
5144
6295
  fetchImageAlbumWork,
5145
6296
  fetchSlidesWork,
@@ -5267,11 +6418,13 @@ var API = class {
5267
6418
  * @returns 请求配置
5268
6419
  */
5269
6420
  profilePublic(data) {
6421
+ const count = "count" in data ? data.count ?? 12 : 12;
6422
+ const pcursor = "pcursor" in data ? data.pcursor ?? "" : "";
5270
6423
  return createKuaishouLiveApiRequest("profilePublic", "/live_api/profile/public", {
5271
6424
  caver: 2,
5272
- count: "count" in data ? data.count ?? 12 : 12,
6425
+ count,
5273
6426
  hasMore: true,
5274
- pcursor: "pcursor" in data ? data.pcursor ?? "" : "",
6427
+ pcursor,
5275
6428
  principalId: data.principalId,
5276
6429
  privacy: "public"
5277
6430
  }, { signPath: "/rest/k/feed/profile" });
@@ -5435,6 +6588,7 @@ var API = class {
5435
6588
  * @returns 请求配置
5436
6589
  */
5437
6590
  liveReco(gameId) {
6591
+ const normalizedGameId = Number(gameId) > 0 ? Number(gameId) : 1001;
5438
6592
  return createKuaishouLiveApiRequest("liveReco", "/live_api/liveroom/reco", {}, {
5439
6593
  method: "POST",
5440
6594
  requiresSign: false,
@@ -5444,7 +6598,7 @@ var API = class {
5444
6598
  followingWeight: 50
5445
6599
  },
5446
6600
  gameFavour: [{
5447
- gameId: Number(gameId) > 0 ? Number(gameId) : 1001,
6601
+ gameId: normalizedGameId,
5448
6602
  totalStayLength: 100
5449
6603
  }]
5450
6604
  }
@@ -5677,8 +6831,10 @@ const maskKuaishouHudrPayload = (payload) => {
5677
6831
  * @returns `HUDR_` 的完整结果及若干中间态,便于对拍与调试
5678
6832
  */
5679
6833
  const deriveKuaishouHudrBody = (context) => {
5680
- const maskedPayload = maskKuaishouHudrPayload(buildKuaishouHudrPayload(context));
5681
- const body = encodeBase64Url(new KuaishouChaChaCipher(KUAISHOU_HUDR_CHACHA_KEY, KUAISHOU_HUDR_CHACHA_NONCE).encrypt(maskedPayload));
6834
+ const payload = buildKuaishouHudrPayload(context);
6835
+ const maskedPayload = maskKuaishouHudrPayload(payload);
6836
+ const encrypted = new KuaishouChaChaCipher(KUAISHOU_HUDR_CHACHA_KEY, KUAISHOU_HUDR_CHACHA_NONCE).encrypt(maskedPayload);
6837
+ const body = encodeBase64Url(encrypted);
5682
6838
  return {
5683
6839
  body,
5684
6840
  full: `${KUAISHOU_HUDR_PREFIX}${body}`,
@@ -6190,7 +7346,9 @@ const KUAISHOU_HE_RANDOM_MAX = 0xffffffffffff;
6190
7346
  * @returns `$HE_` 载荷中的 4 字节 hash field hex
6191
7347
  */
6192
7348
  const deriveKuaishouHeHashFieldHex = (signInput, hudrBody) => {
6193
- return bytesToLowerHex(xorByteArrays(hexToSignedBytes(bytesToLowerHex(deriveKuaishouCts(deriveKuaishouB2sa(`${signInput}HUDR_${hudrBody}`))).slice(0, 8)), KUAISHOU_HE_INPUT_XOR_MASK));
7349
+ const hashInput = `${signInput}HUDR_${hudrBody}`;
7350
+ const digestHex = bytesToLowerHex(deriveKuaishouCts(deriveKuaishouB2sa(hashInput))).slice(0, 8);
7351
+ return bytesToLowerHex(xorByteArrays(hexToSignedBytes(digestHex), KUAISHOU_HE_INPUT_XOR_MASK));
6194
7352
  };
6195
7353
  /**
6196
7354
  * 推导快手签名中的 `$HE_` 段。
@@ -6530,7 +7688,6 @@ var kuaishouSign = class {
6530
7688
  * 快手数据获取模块
6531
7689
  *
6532
7690
  * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
6533
- * 循环依赖链:DataFetchers → getdata → platform/kuaishou → DataFetchers
6534
7691
  */
6535
7692
  const KUAISHOU_PROFILE_TAB_TYPE_MAP = {
6536
7693
  public: "public",
@@ -7212,7 +8369,8 @@ const KuaishouData = async (data, cookie, requestConfig) => {
7212
8369
  if (!liveDetailData) return liveRoomInfo;
7213
8370
  const userInfo = isErrorDetailLike(userInfoPayload) ? void 0 : userInfoPayload?.data?.userInfo;
7214
8371
  const sensitiveInfo = isErrorDetailLike(sensitivePayload) ? void 0 : sensitivePayload?.data?.sensitiveUserInfo;
7215
- const currentLiveRoomItem = mapLiveDetailToLiveRoomPlayItem(liveDetailData, mergeKuaishouLiveAuthor(liveDetailData?.author, userInfo, sensitiveInfo));
8372
+ const currentAuthor = mergeKuaishouLiveAuthor(liveDetailData?.author, userInfo, sensitiveInfo);
8373
+ const currentLiveRoomItem = mapLiveDetailToLiveRoomPlayItem(liveDetailData, currentAuthor);
7216
8374
  const liveStreamId = currentLiveRoomItem.liveStream?.id ?? currentLiveRoomItem.config?.liveStreamId;
7217
8375
  const currentGameId = liveDetailData?.gameInfo?.id ?? liveDetailData?.gameInfo?.gameId;
7218
8376
  const liveDetailWebsocketMeta = resolveKuaishouLiveDetailWebsocketMeta(liveDetailData);
@@ -7230,7 +8388,8 @@ const KuaishouData = async (data, cookie, requestConfig) => {
7230
8388
  shouldFetchRecommendList ? fetchKuaishouLiveApiPayload(data.methodType, kuaishouApiUrls.liveReco(currentGameId), refererPath, { allowResult2: true }) : Promise.resolve(null)
7231
8389
  ]);
7232
8390
  const resolvedRecommendList = !isErrorDetailLike(recoPayload) && Array.isArray(recoPayload?.data?.list) ? recoPayload.data.list : liveDetailRecommendList;
7233
- const nextPlayList = dedupeLiveRoomPlayList([currentLiveRoomItem, ...Array.isArray(resolvedRecommendList) ? resolvedRecommendList.map((item) => mapRecoItemToLiveRoomPlayItem(item)) : []]);
8391
+ const recoPlayList = Array.isArray(resolvedRecommendList) ? resolvedRecommendList.map((item) => mapRecoItemToLiveRoomPlayItem(item)) : [];
8392
+ const nextPlayList = dedupeLiveRoomPlayList([currentLiveRoomItem, ...recoPlayList]);
7234
8393
  return {
7235
8394
  ...liveRoomInfo,
7236
8395
  principalId: data.principalId,
@@ -7341,7 +8500,8 @@ const GlobalGetData$2 = async (type, options, config) => {
7341
8500
  async function fetchKuaishouInternal(methodType, options, config) {
7342
8501
  const startTime = Date.now();
7343
8502
  try {
7344
- const rawData = await KuaishouData({ ...validateKuaishouParams(methodType, options) }, config.cookie, config.requestConfig);
8503
+ const apiParams = { ...validateKuaishouParams(methodType, options) };
8504
+ const rawData = await KuaishouData(apiParams, config.cookie, config.requestConfig);
7345
8505
  const duration = Date.now() - startTime;
7346
8506
  if (rawData.code && Object.values(kuaishouAPIErrorCode).includes(rawData.code)) {
7347
8507
  emitApiError({
@@ -7600,18 +8760,19 @@ const XiaohongshuData = async (data, cookie, requestConfig) => {
7600
8760
  ...requestConfig?.headers ?? {}
7601
8761
  }
7602
8762
  };
8763
+ const userData = await GlobalGetData$1(data.methodType, {
8764
+ ...baseRequestConfig,
8765
+ url: xiaohongshuApiUrls.userProfile(data).Url,
8766
+ headers: {
8767
+ ...baseRequestConfig.headers,
8768
+ "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls.userProfile(data).apiPath, xiaohongshuSign.extractA1FromCookie(requestCookie), "xhs-pc-web"),
8769
+ "x-s-common": xiaohongshuSign.generateXSCommon(requestCookie),
8770
+ "x-t": xiaohongshuSign.generateXT()
8771
+ }
8772
+ });
7603
8773
  return {
7604
8774
  code: 0,
7605
- data: extractCreatorInfoFromHtml(await GlobalGetData$1(data.methodType, {
7606
- ...baseRequestConfig,
7607
- url: xiaohongshuApiUrls.userProfile(data).Url,
7608
- headers: {
7609
- ...baseRequestConfig.headers,
7610
- "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls.userProfile(data).apiPath, xiaohongshuSign.extractA1FromCookie(requestCookie), "xhs-pc-web"),
7611
- "x-s-common": xiaohongshuSign.generateXSCommon(requestCookie),
7612
- "x-t": xiaohongshuSign.generateXT()
7613
- }
7614
- })),
8775
+ data: extractCreatorInfoFromHtml(userData),
7615
8776
  msg: "success"
7616
8777
  };
7617
8778
  }
@@ -7725,7 +8886,8 @@ const sortTypeMapping = {
7725
8886
  async function fetchXiaohongshuInternal(methodType, options, config) {
7726
8887
  const startTime = Date.now();
7727
8888
  try {
7728
- const rawData = await XiaohongshuData({ ...validateXiaohongshuParams(methodType, options) }, config.cookie, config.requestConfig);
8889
+ const apiParams = { ...validateXiaohongshuParams(methodType, options) };
8890
+ const rawData = await XiaohongshuData(apiParams, config.cookie, config.requestConfig);
7729
8891
  const duration = Date.now() - startTime;
7730
8892
  if (rawData.code && Object.values(xiaohongshuAPIErrorCode).includes(rawData.code)) {
7731
8893
  emitApiError({
@@ -8149,106 +9311,6 @@ const getHeadersAndData = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
8149
9311
  };
8150
9312
  };
8151
9313
  //#endregion
8152
- //#region src/model/logger.ts
8153
- /**
8154
- * @deprecated v6 已废弃日志模块,请使用事件系统替代
8155
- * @see {@link ../events.ts} 使用 amagiEvents 监听日志事件
8156
- *
8157
- * 迁移示例:
8158
- * ```typescript
8159
- * import { amagiEvents } from '@ikenxuan/amagi'
8160
- *
8161
- * amagiEvents.on('log:info', (data) => console.log(data.message))
8162
- * amagiEvents.on('log:error', (data) => console.error(data.message))
8163
- * ```
8164
- */
8165
- /**
8166
- * @deprecated v6 已废弃,请使用事件系统替代
8167
- * 初始化 logger 配置 - 此函数现在为空操作
8168
- */
8169
- const initLogger = () => {};
8170
- /**
8171
- * @deprecated v6 已废弃,请使用事件系统替代
8172
- * 简化的日志类,仅发射事件,不再依赖 log4js
8173
- */
8174
- var SimpleLogger = class {
8175
- chalk;
8176
- red;
8177
- green;
8178
- yellow;
8179
- blue;
8180
- magenta;
8181
- cyan;
8182
- white;
8183
- gray;
8184
- constructor() {
8185
- this.chalk = new Chalk();
8186
- this.red = this.chalk.red;
8187
- this.green = this.chalk.green;
8188
- this.yellow = this.chalk.yellow;
8189
- this.blue = this.chalk.blue;
8190
- this.magenta = this.chalk.magenta;
8191
- this.cyan = this.chalk.cyan;
8192
- this.white = this.chalk.white;
8193
- this.gray = this.chalk.gray;
8194
- }
8195
- info(message, ...args) {
8196
- emitLog("info", String(message), ...args);
8197
- }
8198
- warn(message, ...args) {
8199
- emitLog("warn", String(message), ...args);
8200
- }
8201
- error(message, ...args) {
8202
- emitLog("error", String(message), ...args);
8203
- }
8204
- mark(message, ...args) {
8205
- emitLog("mark", String(message), ...args);
8206
- }
8207
- debug(message, ...args) {
8208
- emitLog("debug", String(message), ...args);
8209
- }
8210
- };
8211
- /**
8212
- * @deprecated v6 已废弃,请使用事件系统替代
8213
- */
8214
- const logger = new SimpleLogger();
8215
- /**
8216
- * @deprecated v6 已废弃,请使用事件系统替代
8217
- */
8218
- const httpLogger = new SimpleLogger();
8219
- /**
8220
- * @deprecated v6 已废弃,请使用事件系统监听 http:response 事件
8221
- * 创建一个日志中间件,用于记录特定请求的详细信息
8222
- * @param pathsToLog 指定需要记录日志的请求路径数组如果未提供,则记录所有请求的日志
8223
- * @returns
8224
- */
8225
- const logMiddleware = (pathsToLog) => {
8226
- return (req, res, next) => {
8227
- if (!pathsToLog || pathsToLog.some((path) => req.url.startsWith(path))) {
8228
- const startTime = Date.now();
8229
- const url = req.url;
8230
- const method = req.method;
8231
- const clientIP = req.headers["x-forwarded-for"] ?? req.socket.remoteAddress;
8232
- res.on("finish", () => {
8233
- const responseTime = Date.now() - startTime;
8234
- const statusCode = res.statusCode;
8235
- const requestSize = req.headers["content-length"] ?? "0";
8236
- const responseSize = res.get("content-length") ?? "0";
8237
- emitHttpResponse({
8238
- method,
8239
- url,
8240
- statusCode,
8241
- responseTime,
8242
- clientIP: String(clientIP),
8243
- requestSize: `${requestSize}B`,
8244
- responseSize: `${responseSize}B`
8245
- });
8246
- });
8247
- }
8248
- next();
8249
- };
8250
- };
8251
- //#endregion
8252
9314
  //#region src/platform/bilibili/qtparam.ts
8253
9315
  /**
8254
9316
  * 生成B站视频流请求参数
@@ -8567,7 +9629,6 @@ const wbi_sign = async (BASEURL, cookie) => {
8567
9629
  * 提供 B站各类数据的获取功能,包括视频、评论、用户、番剧等
8568
9630
  *
8569
9631
  * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
8570
- * 循环依赖链:DataFetchers → getdata → platform/bilibili → DataFetchers
8571
9632
  *
8572
9633
  * @module platform/bilibili/getdata
8573
9634
  */
@@ -8587,10 +9648,11 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8587
9648
  url: bilibiliApiUrls.getVideoInfo({ bvid: data.bvid })
8588
9649
  });
8589
9650
  case "videoStream": {
8590
- const sign = await qtparam(bilibiliApiUrls.getVideoStream({
9651
+ const baseUrl = bilibiliApiUrls.getVideoStream({
8591
9652
  avid: data.avid,
8592
9653
  cid: data.cid
8593
- }), baseRequestConfig.headers?.Cookie);
9654
+ });
9655
+ const sign = await qtparam(baseUrl, baseRequestConfig.headers?.Cookie);
8594
9656
  return await GlobalGetData(data.methodType, {
8595
9657
  ...baseRequestConfig,
8596
9658
  url: bilibiliApiUrls.getVideoStream({
@@ -8677,10 +9739,11 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8677
9739
  });
8678
9740
  }
8679
9741
  case "bangumiStream": {
8680
- const sign = await qtparam(bilibiliApiUrls.getBangumiStream({
9742
+ const baseUrl = bilibiliApiUrls.getBangumiStream({
8681
9743
  cid: data.cid,
8682
9744
  ep_id: data.ep_id.replace("ep", "")
8683
- }), baseRequestConfig.headers?.cookie);
9745
+ });
9746
+ const sign = await qtparam(baseUrl, baseRequestConfig.headers?.cookie);
8684
9747
  return await GlobalGetData(data.methodType, {
8685
9748
  ...baseRequestConfig,
8686
9749
  url: bilibiliApiUrls.getBangumiStream({
@@ -8716,12 +9779,6 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8716
9779
  url: bilibiliApiUrls.getDynamicDetail({ dynamic_id: data.dynamic_id })
8717
9780
  });
8718
9781
  }
8719
- case "dynamicCard": return {
8720
- code: -404,
8721
- message: "接口已停用:B站官方已于 `2025-08-09` 删除 dynamic_svr 接口,fetchDynamicCard 方法已废弃,调用讲返回错误信息",
8722
- ttl: 1,
8723
- data: null
8724
- };
8725
9782
  case "userCard": {
8726
9783
  const { host_mid } = data;
8727
9784
  return await GlobalGetData(data.methodType, {
@@ -8734,7 +9791,8 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8734
9791
  url: bilibiliApiUrls.getUserLiveStatus({ host_mid: data.host_mid })
8735
9792
  });
8736
9793
  case "userSpaceInfo": {
8737
- const wbiSignQuery = await wbi_sign(bilibiliApiUrls.getUserSpaceInfo({ host_mid: data.host_mid }), baseRequestConfig.headers?.cookie);
9794
+ const baseUrl = bilibiliApiUrls.getUserSpaceInfo({ host_mid: data.host_mid });
9795
+ const wbiSignQuery = await wbi_sign(baseUrl, baseRequestConfig.headers?.cookie);
8738
9796
  return await GlobalGetData(data.methodType, {
8739
9797
  ...baseRequestConfig,
8740
9798
  url: bilibiliApiUrls.getUserSpaceInfo({ host_mid: data.host_mid }) + wbiSignQuery
@@ -9085,10 +10143,11 @@ var ValidationError = class ValidationError extends Error {
9085
10143
  * @returns 验证错误实例
9086
10144
  */
9087
10145
  static fromZodError(zodError, requestPath) {
9088
- return new ValidationError("参数验证失败", zodError.issues.map((err) => ({
10146
+ const errors = zodError.issues.map((err) => ({
9089
10147
  field: err.path.join("."),
9090
10148
  message: err.message
9091
- })), requestPath);
10149
+ }));
10150
+ return new ValidationError("参数验证失败", errors, requestPath);
9092
10151
  }
9093
10152
  };
9094
10153
  /**
@@ -9112,7 +10171,10 @@ const handleError = (error, requestPath) => {
9112
10171
  platform: error.platform,
9113
10172
  requestPath
9114
10173
  };
9115
- if (error instanceof zod.ZodError) return handleError(ValidationError.fromZodError(error, requestPath), requestPath);
10174
+ if (error instanceof zod.ZodError) {
10175
+ const validationError = ValidationError.fromZodError(error, requestPath);
10176
+ return handleError(validationError, requestPath);
10177
+ }
9116
10178
  return {
9117
10179
  code: 500,
9118
10180
  message: error instanceof Error ? error.message : "未知错误",
@@ -9223,71 +10285,7 @@ const bilibiliUtils = {
9223
10285
  bv2av
9224
10286
  },
9225
10287
  danmaku: { parseDmSegMobileReply },
9226
- bilibiliApiUrls,
9227
- api: bilibili
9228
- };
9229
- //#endregion
9230
- //#region src/platform/douyin/DouyinApi.ts
9231
- /**
9232
- * 创建废弃的 API 存根函数
9233
- */
9234
- const createDeprecatedStub$2 = (methodName) => {
9235
- return (..._args) => {
9236
- checkDeprecation("getDouyinData");
9237
- throw new Error(`douyin.${methodName} 已废弃,请使用 douyinFetcher 替代`);
9238
- };
9239
- };
9240
- /**
9241
- * 封装了所有抖音相关的API请求,采用对象化的方式组织。
9242
- *
9243
- * @deprecated v6 已废弃,请使用 douyinFetcher 或 client.douyin.fetcher 替代
9244
- */
9245
- const douyin = {
9246
- /** @deprecated 请使用 douyinFetcher.fetchTextWork 替代 */
9247
- getTextWorkInfo: createDeprecatedStub$2("getTextWorkInfo"),
9248
- /** @deprecated 请使用 douyinFetcher.parseWork 替代 */
9249
- getWorkInfo: createDeprecatedStub$2("getWorkInfo"),
9250
- /** @deprecated 请使用 douyinFetcher.fetchVideoWork 替代 */
9251
- getVideoWorkInfo: createDeprecatedStub$2("getVideoWorkInfo"),
9252
- /** @deprecated 请使用 douyinFetcher.fetchImageAlbumWork 替代 */
9253
- getImageAlbumWorkInfo: createDeprecatedStub$2("getImageAlbumWorkInfo"),
9254
- /** @deprecated 请使用 douyinFetcher.fetchSlidesWork 替代 */
9255
- getSlidesWorkInfo: createDeprecatedStub$2("getSlidesWorkInfo"),
9256
- /** @deprecated 请使用 douyinFetcher.fetchComments 替代 */
9257
- getComments: createDeprecatedStub$2("getComments"),
9258
- /** @deprecated 请使用 douyinFetcher.fetchCommentReplies 替代 */
9259
- getCommentReplies: createDeprecatedStub$2("getCommentReplies"),
9260
- /** @deprecated 请使用 douyinFetcher.fetchUserProfile 替代 */
9261
- getUserProfile: createDeprecatedStub$2("getUserProfile"),
9262
- /** @deprecated 请使用 douyinFetcher.fetchEmojiList 替代 */
9263
- getEmojiList: createDeprecatedStub$2("getEmojiList"),
9264
- /** @deprecated 请使用 douyinFetcher.fetchDynamicEmojiList 替代 */
9265
- getEmojiProList: createDeprecatedStub$2("getEmojiProList"),
9266
- /** @deprecated 请使用 douyinFetcher.fetchUserVideoList 替代 */
9267
- getUserVideos: createDeprecatedStub$2("getUserVideos"),
9268
- /** @deprecated 请使用 douyinFetcher.fetchMusicInfo 替代 */
9269
- getMusicInfo: createDeprecatedStub$2("getMusicInfo"),
9270
- /** @deprecated 请使用 douyinFetcher.fetchSuggestWords 替代 */
9271
- getSuggestWords: createDeprecatedStub$2("getSuggestWords"),
9272
- /** @deprecated 请使用 douyinFetcher.searchContent 替代 */
9273
- search: createDeprecatedStub$2("search"),
9274
- /** @deprecated 请使用 douyinFetcher.fetchLiveRoomInfo 替代 */
9275
- getLiveRoomInfo: createDeprecatedStub$2("getLiveRoomInfo"),
9276
- /** @deprecated 请使用 douyinFetcher.fetchDanmakuList 替代 */
9277
- getDanmaku: createDeprecatedStub$2("getDanmaku"),
9278
- /** @deprecated 请使用 douyinFetcher 的具体方法替代 */
9279
- invoke: createDeprecatedStub$2("invoke")
9280
- };
9281
- /**
9282
- * 创建绑定了cookie的抖音API对象
9283
- *
9284
- * @deprecated v6 已废弃,请使用 createBoundDouyinFetcher 替代
9285
- */
9286
- const createBoundDouyinApi = (_cookie, _requestConfig) => {
9287
- return {
9288
- ...douyin,
9289
- getSearchData: createDeprecatedStub$2("getSearchData")
9290
- };
10288
+ bilibiliApiUrls
9291
10289
  };
9292
10290
  //#endregion
9293
10291
  //#region src/platform/douyin/routes.ts
@@ -9341,46 +10339,8 @@ const createDouyinRoutes = (cookie, requestConfig = getDouyinDefaultConfig(cooki
9341
10339
  /** 抖音相关功能模块 (工具集) */
9342
10340
  const douyinUtils = {
9343
10341
  sign: douyinSign,
9344
- douyinApiUrls,
9345
- api: douyin
9346
- };
9347
- //#endregion
9348
- //#region src/platform/kuaishou/KuaishouApi.ts
9349
- /**
9350
- * 创建废弃的 API 存根函数
9351
- */
9352
- const createDeprecatedStub$1 = (methodName) => {
9353
- return (..._args) => {
9354
- checkDeprecation("getKuaishouData");
9355
- throw new Error(`kuaishou.${methodName} 已废弃,请使用 kuaishouFetcher 替代`);
9356
- };
9357
- };
9358
- /**
9359
- * 快手相关 API 的命名空间。
9360
- *
9361
- * @deprecated v6 已废弃,请使用 kuaishouFetcher 或 client.kuaishou.fetcher 替代
9362
- */
9363
- const kuaishou = {
9364
- /** @deprecated 请使用 kuaishouFetcher.fetchVideoWork 替代 */
9365
- getWorkInfo: createDeprecatedStub$1("getWorkInfo"),
9366
- /** @deprecated 请使用 kuaishouFetcher.fetchWorkComments 替代 */
9367
- getComments: createDeprecatedStub$1("getComments"),
9368
- /** @deprecated 请使用 kuaishouFetcher.fetchUserProfile 替代 */
9369
- getUserProfile: createDeprecatedStub$1("getUserProfile"),
9370
- /** @deprecated 请使用 kuaishouFetcher.fetchUserWorkList 替代 */
9371
- getUserWorkList: createDeprecatedStub$1("getUserWorkList"),
9372
- /** @deprecated 请使用 kuaishouFetcher.fetchLiveRoomInfo 替代 */
9373
- getLiveRoomInfo: createDeprecatedStub$1("getLiveRoomInfo"),
9374
- /** @deprecated 请使用 kuaishouFetcher.fetchEmojiList 替代 */
9375
- getEmojiList: createDeprecatedStub$1("getEmojiList")
9376
- };
9377
- /**
9378
- * 创建绑定了cookie的快手API对象
9379
- *
9380
- * @deprecated v6 已废弃,请使用 createBoundKuaishouFetcher 替代
9381
- */
9382
- const createBoundKuaishouApi = (_cookie, _requestConfig) => {
9383
- return { ...kuaishou };
10342
+ passport: passport_exports,
10343
+ douyinApiUrls
9384
10344
  };
9385
10345
  //#endregion
9386
10346
  //#region src/platform/kuaishou/routes.ts
@@ -9434,48 +10394,7 @@ const createKuaishouRoutes = (cookie, requestConfig = getKuaishouDefaultConfig(c
9434
10394
  /** 快手相关功能模块 (工具集) */
9435
10395
  const kuaishouUtils = {
9436
10396
  sign: kuaishouSign,
9437
- kuaishouApiUrls,
9438
- api: kuaishou
9439
- };
9440
- //#endregion
9441
- //#region src/platform/xiaohongshu/XiaohongshuApi.ts
9442
- /**
9443
- * 创建废弃的 API 存根函数
9444
- */
9445
- const createDeprecatedStub = (methodName) => {
9446
- return (..._args) => {
9447
- checkDeprecation("getXiaohongshuData");
9448
- throw new Error(`xiaohongshu.${methodName} 已废弃,请使用 xiaohongshuFetcher 替代`);
9449
- };
9450
- };
9451
- /**
9452
- * 封装了所有小红书相关的API请求,采用对象化的方式组织。
9453
- *
9454
- * @deprecated v6 已废弃,请使用 xiaohongshuFetcher 或 client.xiaohongshu.fetcher 替代
9455
- */
9456
- const xiaohongshu = {
9457
- /** @deprecated 请使用 xiaohongshuFetcher.fetchHomeFeed 替代 */
9458
- getHomeFeed: createDeprecatedStub("getHomeFeed"),
9459
- /** @deprecated 请使用 xiaohongshuFetcher.fetchNoteDetail 替代 */
9460
- getNote: createDeprecatedStub("getNote"),
9461
- /** @deprecated 请使用 xiaohongshuFetcher.fetchNoteComments 替代 */
9462
- getComments: createDeprecatedStub("getComments"),
9463
- /** @deprecated 请使用 xiaohongshuFetcher.fetchUserProfile 替代 */
9464
- getUser: createDeprecatedStub("getUser"),
9465
- /** @deprecated 请使用 xiaohongshuFetcher.fetchUserNoteList 替代 */
9466
- getUserNotes: createDeprecatedStub("getUserNotes"),
9467
- /** @deprecated 请使用 xiaohongshuFetcher.searchNotes 替代 */
9468
- getSearchNotes: createDeprecatedStub("getSearchNotes"),
9469
- /** @deprecated 请使用 xiaohongshuFetcher.fetchEmojiList 替代 */
9470
- getEmojiList: createDeprecatedStub("getEmojiList")
9471
- };
9472
- /**
9473
- * 创建绑定了cookie的小红书API对象
9474
- *
9475
- * @deprecated v6 已废弃,请使用 createBoundXiaohongshuFetcher 替代
9476
- */
9477
- const createBoundXiaohongshuApi = (_cookie, _requestConfig) => {
9478
- return { ...xiaohongshu };
10397
+ kuaishouApiUrls
9479
10398
  };
9480
10399
  //#endregion
9481
10400
  //#region src/platform/xiaohongshu/routes.ts
@@ -9529,8 +10448,7 @@ const createXiaohongshuRoutes = (cookie, requestConfig = getXiaohongshuDefaultCo
9529
10448
  /** 小红书相关功能模块 (工具集) */
9530
10449
  const xiaohongshuUtils = {
9531
10450
  sign: xiaohongshuSign,
9532
- xiaohongshuApiUrls,
9533
- api: xiaohongshu
10451
+ xiaohongshuApiUrls
9534
10452
  };
9535
10453
  //#endregion
9536
10454
  //#region src/server/index.ts
@@ -9577,38 +10495,6 @@ const createAmagiClient = (options) => {
9577
10495
  });
9578
10496
  return app;
9579
10497
  };
9580
- /**
9581
- * @deprecated v6 已废弃,请使用 douyin.fetcher 替代
9582
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9583
- */
9584
- const getDouyinData = (..._args) => {
9585
- checkDeprecation("getDouyinData");
9586
- throw new Error("getDouyinData 已废弃");
9587
- };
9588
- /**
9589
- * @deprecated v6 已废弃,请使用 bilibili.fetcher 替代
9590
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9591
- */
9592
- const getBilibiliData = (..._args) => {
9593
- checkDeprecation("getBilibiliData");
9594
- throw new Error("getBilibiliData 已废弃");
9595
- };
9596
- /**
9597
- * @deprecated v6 已废弃,请使用 kuaishou.fetcher 替代
9598
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9599
- */
9600
- const getKuaishouData = (..._args) => {
9601
- checkDeprecation("getKuaishouData");
9602
- throw new Error("getKuaishouData 已废弃");
9603
- };
9604
- /**
9605
- * @deprecated v6 已废弃,请使用 xiaohongshu.fetcher 替代
9606
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9607
- */
9608
- const getXiaohongshuData = (..._args) => {
9609
- checkDeprecation("getXiaohongshuData");
9610
- throw new Error("getXiaohongshuData 已废弃");
9611
- };
9612
10498
  return {
9613
10499
  /** 启动本地HTTP服务 */
9614
10500
  startServer,
@@ -9626,39 +10512,23 @@ const createAmagiClient = (options) => {
9626
10512
  * @param listener - 事件处理函数 (只触发一次)
9627
10513
  */
9628
10514
  once: (event, listener) => amagiEvents.once(event, listener),
9629
- /** @deprecated v6 已废弃,请使用 douyin.fetcher 替代 */
9630
- getDouyinData,
9631
- /** @deprecated v6 已废弃,请使用 bilibili.fetcher 替代 */
9632
- getBilibiliData,
9633
- /** @deprecated v6 已废弃,请使用 kuaishou.fetcher 替代 */
9634
- getKuaishouData,
9635
- /** @deprecated v6 已废弃,请使用 xiaohongshu.fetcher 替代 */
9636
- getXiaohongshuData,
9637
10515
  douyin: {
9638
10516
  ...douyinUtils,
9639
- /** @deprecated 请使用 fetcher 替代 */
9640
- api: createBoundDouyinApi(douyinCookie, requestConfig),
9641
10517
  /** fetcher */
9642
10518
  fetcher: createBoundDouyinFetcher(douyinCookie, requestConfig)
9643
10519
  },
9644
10520
  bilibili: {
9645
10521
  ...bilibiliUtils,
9646
- /** @deprecated 请使用 fetcher 替代 */
9647
- api: createBoundBilibiliApi(bilibiliCookie, requestConfig),
9648
10522
  /** fetcher */
9649
10523
  fetcher: createBoundBilibiliFetcher(bilibiliCookie, requestConfig)
9650
10524
  },
9651
10525
  kuaishou: {
9652
10526
  ...kuaishouUtils,
9653
- /** @deprecated 请使用 fetcher 替代 */
9654
- api: createBoundKuaishouApi(kuaishouCookie, requestConfig),
9655
10527
  /** fetcher */
9656
10528
  fetcher: createBoundKuaishouFetcher(kuaishouCookie, requestConfig)
9657
10529
  },
9658
10530
  xiaohongshu: {
9659
10531
  ...xiaohongshuUtils,
9660
- /** @deprecated 请使用 fetcher 替代 */
9661
- api: createBoundXiaohongshuApi(xiaohongshuCookie, requestConfig),
9662
10532
  /** fetcher */
9663
10533
  fetcher: createBoundXiaohongshuFetcher(xiaohongshuCookie, requestConfig)
9664
10534
  }
@@ -9822,7 +10692,6 @@ const BilibiliInternalMethods = {
9822
10692
  USER_SPACE_INFO: "用户空间详细信息",
9823
10693
  USER_TOTAL_VIEWS: "获取UP主总播放量",
9824
10694
  DYNAMIC_DETAIL: "动态详情数据",
9825
- DYNAMIC_CARD: "动态卡片数据",
9826
10695
  BANGUMI_INFO: "番剧基本信息数据",
9827
10696
  BANGUMI_STREAM: "番剧下载信息数据",
9828
10697
  LIVE_ROOM_INFO: "直播间信息",
@@ -9853,7 +10722,6 @@ const BilibiliFetcherMethods = {
9853
10722
  USER_SPACE_INFO: "fetchUserSpaceInfo",
9854
10723
  USER_TOTAL_VIEWS: "fetchUploaderTotalViews",
9855
10724
  DYNAMIC_DETAIL: "fetchDynamicDetail",
9856
- DYNAMIC_CARD: "fetchDynamicCard",
9857
10725
  BANGUMI_INFO: "fetchBangumiInfo",
9858
10726
  BANGUMI_STREAM: "fetchBangumiStreamUrl",
9859
10727
  LIVE_ROOM_INFO: "fetchLiveRoomInfo",
@@ -9962,7 +10830,6 @@ const BilibiliMethodToFetcher = {
9962
10830
  [BilibiliInternalMethods.USER_SPACE_INFO]: BilibiliFetcherMethods.USER_SPACE_INFO,
9963
10831
  [BilibiliInternalMethods.USER_TOTAL_VIEWS]: BilibiliFetcherMethods.USER_TOTAL_VIEWS,
9964
10832
  [BilibiliInternalMethods.DYNAMIC_DETAIL]: BilibiliFetcherMethods.DYNAMIC_DETAIL,
9965
- [BilibiliInternalMethods.DYNAMIC_CARD]: BilibiliFetcherMethods.DYNAMIC_CARD,
9966
10833
  [BilibiliInternalMethods.BANGUMI_INFO]: BilibiliFetcherMethods.BANGUMI_INFO,
9967
10834
  [BilibiliInternalMethods.BANGUMI_STREAM]: BilibiliFetcherMethods.BANGUMI_STREAM,
9968
10835
  [BilibiliInternalMethods.LIVE_ROOM_INFO]: BilibiliFetcherMethods.LIVE_ROOM_INFO,
@@ -10094,7 +10961,6 @@ const BilibiliMethodMapping = {
10094
10961
  用户空间详细信息: "fetchUserSpaceInfo",
10095
10962
  获取UP主总播放量: "fetchUploaderTotalViews",
10096
10963
  动态详情数据: "fetchDynamicDetail",
10097
- 动态卡片数据: "fetchDynamicCard",
10098
10964
  番剧基本信息数据: "fetchBangumiInfo",
10099
10965
  番剧下载信息数据: "fetchBangumiStreamUrl",
10100
10966
  直播间信息: "fetchLiveRoomInfo",
@@ -10177,7 +11043,6 @@ const BilibiliApiRoutes = {
10177
11043
  userSpaceInfo: "/user/space",
10178
11044
  uploaderTotalViews: "/user/total-views",
10179
11045
  dynamicDetail: "/dynamic",
10180
- dynamicCard: "/dynamic/card",
10181
11046
  bangumiInfo: "/bangumi",
10182
11047
  bangumiStream: "/bangumi/stream",
10183
11048
  liveRoomInfo: "/live",
@@ -10247,14 +11112,10 @@ function getApiRoute(platform, methodType) {
10247
11112
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
10248
11113
  */
10249
11114
  const getVersion = () => {
10250
- return "6.4.0";
11115
+ return "6.6.0";
10251
11116
  };
10252
11117
  const VERSION = getVersion();
10253
11118
  /**
10254
- * @deprecated 请使用 createAmagiClient 替代
10255
- */
10256
- const amagiClient = createAmagiClient;
10257
- /**
10258
11119
  * 创建一个新的 amagi 客户端实例
10259
11120
  * 用于创建和初始化一个新的 amagi 客户端实例,支持通过 new 关键字或函数调用方式使用
10260
11121
  * @param options - cookies 配置选项,用于设置客户端的 cookies 相关参数
@@ -10274,10 +11135,6 @@ CreateAmagiApp.douyin = douyinUtils;
10274
11135
  CreateAmagiApp.bilibili = bilibiliUtils;
10275
11136
  CreateAmagiApp.kuaishou = kuaishouUtils;
10276
11137
  CreateAmagiApp.xiaohongshu = xiaohongshuUtils;
10277
- CreateAmagiApp.getDouyinData = getDouyinData;
10278
- CreateAmagiApp.getBilibiliData = getBilibiliData;
10279
- CreateAmagiApp.getKuaishouData = getKuaishouData;
10280
- CreateAmagiApp.getXiaohongshuData = getXiaohongshuData;
10281
11138
  CreateAmagiApp.events = amagiEvents;
10282
11139
  CreateAmagiApp.on = amagiEvents.on.bind(amagiEvents);
10283
11140
  CreateAmagiApp.once = amagiEvents.once.bind(amagiEvents);
@@ -10300,6 +11157,4 @@ const amagi = Client;
10300
11157
  * GPL-3.0 Licensed
10301
11158
  */
10302
11159
  //#endregion
10303
- export { AdditionalType, ApiError, BilibiliApiRoutes, BilibiliApplyCaptchaParamsSchema, BilibiliArticleCardParamsSchema, BilibiliArticleInfoParamsSchema, BilibiliArticleParamsSchema, BilibiliAv2BvParamsSchema, BilibiliBangumiInfoParamsSchema, BilibiliBangumiStreamParamsSchema, BilibiliBv2AvParamsSchema, BilibiliColumnInfoParamsSchema, BilibiliCommentParamsSchema, BilibiliCommentReplyParamsSchema, BilibiliDanmakuParamsSchema, BilibiliDynamicParamsSchema, BilibiliEmojiParamsSchema, BilibiliFetcherMethods, BilibiliInternalMethods, BilibiliLiveParamsSchema, BilibiliLoginParamsSchema, BilibiliMethodMapping, BilibiliMethodRoutes, BilibiliMethodToFetcher, BilibiliQrcodeParamsSchema, BilibiliQrcodeStatusParamsSchema, BilibiliUserParamsSchema, BilibiliValidateCaptchaParamsSchema, BilibiliValidationSchemas, BilibiliVideoDownloadParamsSchema, BilibiliVideoParamsSchema, CommentType, CreateApp, DouyinApiRoutes, DouyinCommentParamsSchema, DouyinCommentReplyParamsSchema, DouyinDanmakuParamsSchema, DouyinEmojiListParamsSchema, DouyinEmojiProParamsSchema, DouyinFetcherMethods, DouyinHotWordsParamsSchema, DouyinInternalMethods, DouyinLiveRoomParamsSchema, DouyinMethodMapping, DouyinMethodRoutes, DouyinMethodToFetcher, DouyinMusicParamsSchema, DouyinQrcodeParamsSchema, DouyinSearchParamsSchema, DouyinUserListParamsSchema, DouyinUserParamsSchema, DouyinValidationSchemas, DouyinWorkParamsSchema, DynamicType, KuaishouApiRoutes, KuaishouCommentParamsSchema, KuaishouEmojiParamsSchema, KuaishouFetcherMethods, KuaishouInternalMethods, KuaishouLiveRoomInfoParamsSchema, KuaishouMethodMapping, KuaishouMethodRoutes, KuaishouMethodToFetcher, KuaishouUserProfileParamsSchema, KuaishouUserWorkListParamsSchema, KuaishouValidationSchemas, KuaishouVideoParamsSchema, MajorType, MethodMaps, ValidationError, XiaohongshuApiRoutes, XiaohongshuFetcherMethods, XiaohongshuInternalMethods, XiaohongshuMethodMapping, XiaohongshuMethodRoutes, XiaohongshuMethodToFetcher, XiaohongshuValidationSchemas, amagi, amagiClient, amagiEvents, av2bv, bilibili, bilibiliApiUrls, bilibiliErrorCodeMap, bilibiliFetcher, bilibiliUtils, bv2av, createAmagiClient, createBilibiliRoutes, createBilibiliRoutes as registerBilibiliRoutes, createBoundBilibiliApi, createBoundBilibiliFetcher, createBoundDouyinApi, createBoundDouyinFetcher, createBoundKuaishouApi, createBoundKuaishouFetcher, createBoundXiaohongshuApi, createBoundXiaohongshuFetcher, createDouyinRoutes, createDouyinRoutes as registerDouyinRoutes, createErrorResponse, createKuaishouRoutes, createKuaishouRoutes as registerKuaishouRoutes, createSuccessResponse, createXiaohongshuRoutes, createXiaohongshuRoutes as registerXiaohongshuRoutes, Client as default, douyin, douyinApiUrls, douyinFetcher, douyinSign, douyinUtils, emitApiError, emitApiSuccess, emitHttpRequest, emitHttpResponse, emitLog, emitLogDebug, emitLogError, emitLogInfo, emitLogMark, emitLogWarn, emitNetworkError, emitNetworkRetry, fetchData, fetchResponse, getApiRoute, getBilibiliData, getDouyinData, getEnglishMethodName, getHeadersAndData, getKuaishouData, handleError, httpLogger, initLogger, isNetworkErrorResult, kuaishou, kuaishouApiUrls, kuaishouFetcher, kuaishouSign, kuaishouUtils, logMiddleware, logger, parseDmSegMobileReply, qtparam, toFetcherMethod, validateBilibiliParams, validateDouyinParams, validateKuaishouParams, validateXiaohongshuParams, wbi_sign, xiaohongshu, xiaohongshuApiUrls, xiaohongshuFetcher, xiaohongshuSign, xiaohongshuUtils };
10304
-
10305
- //# sourceMappingURL=index.mjs.map
11160
+ export { AdditionalType, ApiError, BilibiliApiRoutes, BilibiliApplyCaptchaParamsSchema, BilibiliArticleCardParamsSchema, BilibiliArticleInfoParamsSchema, BilibiliArticleParamsSchema, BilibiliAv2BvParamsSchema, BilibiliBangumiInfoParamsSchema, BilibiliBangumiStreamParamsSchema, BilibiliBv2AvParamsSchema, BilibiliColumnInfoParamsSchema, BilibiliCommentParamsSchema, BilibiliCommentReplyParamsSchema, BilibiliDanmakuParamsSchema, BilibiliDynamicParamsSchema, BilibiliEmojiParamsSchema, BilibiliFetcherMethods, BilibiliInternalMethods, BilibiliLiveParamsSchema, BilibiliLoginParamsSchema, BilibiliMethodMapping, BilibiliMethodRoutes, BilibiliMethodToFetcher, BilibiliQrcodeParamsSchema, BilibiliQrcodeStatusParamsSchema, BilibiliUserParamsSchema, BilibiliValidateCaptchaParamsSchema, BilibiliValidationSchemas, BilibiliVideoDownloadParamsSchema, BilibiliVideoParamsSchema, CommentType, CreateApp, DouyinApiRoutes, DouyinCommentParamsSchema, DouyinCommentReplyParamsSchema, DouyinDanmakuParamsSchema, DouyinEmojiListParamsSchema, DouyinEmojiProParamsSchema, DouyinFetcherMethods, DouyinHotWordsParamsSchema, DouyinInternalMethods, DouyinLiveRoomParamsSchema, DouyinMethodMapping, DouyinMethodRoutes, DouyinMethodToFetcher, DouyinMusicParamsSchema, DouyinQrcodeParamsSchema, DouyinSearchParamsSchema, DouyinUserListParamsSchema, DouyinUserParamsSchema, DouyinValidationSchemas, DouyinWorkParamsSchema, DynamicType, KuaishouApiRoutes, KuaishouCommentParamsSchema, KuaishouEmojiParamsSchema, KuaishouFetcherMethods, KuaishouInternalMethods, KuaishouLiveRoomInfoParamsSchema, KuaishouMethodMapping, KuaishouMethodRoutes, KuaishouMethodToFetcher, KuaishouUserProfileParamsSchema, KuaishouUserWorkListParamsSchema, KuaishouValidationSchemas, KuaishouVideoParamsSchema, MajorType, MethodMaps, ValidationError, XiaohongshuApiRoutes, XiaohongshuFetcherMethods, XiaohongshuInternalMethods, XiaohongshuMethodMapping, XiaohongshuMethodRoutes, XiaohongshuMethodToFetcher, XiaohongshuValidationSchemas, amagi, amagiEvents, av2bv, bilibiliApiUrls, bilibiliErrorCodeMap, bilibiliFetcher, bilibiliUtils, bv2av, checkPassportQrcode, createAmagiClient, createBilibiliRoutes, createBilibiliRoutes as registerBilibiliRoutes, createBoundBilibiliFetcher, createBoundDouyinFetcher, createBoundKuaishouFetcher, createBoundXiaohongshuFetcher, createDouyinRoutes, createDouyinRoutes as registerDouyinRoutes, createErrorResponse, createKuaishouRoutes, createKuaishouRoutes as registerKuaishouRoutes, createSuccessResponse, createXiaohongshuRoutes, createXiaohongshuRoutes as registerXiaohongshuRoutes, Client as default, douyinApiUrls, douyinFetcher, passport_exports as douyinPassport, douyinSign, douyinUtils, emitApiError, emitApiSuccess, emitHttpRequest, emitHttpResponse, emitLog, emitLogDebug, emitLogError, emitLogInfo, emitLogMark, emitLogWarn, emitNetworkError, emitNetworkRetry, fetchData, fetchResponse, getApiRoute, getEnglishMethodName, getHeadersAndData, handleError, isNetworkErrorResult, isSmsCodeVerifyWay, kuaishouApiUrls, kuaishouFetcher, kuaishouSign, kuaishouUtils, parseDmSegMobileReply, qtparam, requestPassportQrcode, sendPassportVerifyCode, toFetcherMethod, validateBilibiliParams, validateDouyinParams, validateKuaishouParams, validatePassportVerifyCode, validateXiaohongshuParams, wbi_sign, xiaohongshuApiUrls, xiaohongshuFetcher, xiaohongshuSign, xiaohongshuUtils };