@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.
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
5
+ const require_rolldown_runtime = require("../rolldown-runtime-C0BPl7ul.cjs");
6
6
  let node_url = require("node:url");
7
7
  node_url = require_rolldown_runtime.__toESM(node_url, 1);
8
8
  let node_events = require("node:events");
@@ -13,450 +13,11 @@ let node_crypto = require("node:crypto");
13
13
  node_crypto = require_rolldown_runtime.__toESM(node_crypto, 1);
14
14
  let axios = require("axios");
15
15
  axios = require_rolldown_runtime.__toESM(axios, 1);
16
- let chalk = require("chalk");
17
16
  let protobufjs = require("protobufjs");
18
17
  protobufjs = require_rolldown_runtime.__toESM(protobufjs, 1);
19
18
  let express = require("express");
20
19
  express = require_rolldown_runtime.__toESM(express, 1);
21
- //#region src/utils/deprecation.ts
22
- /**
23
- * 废弃 API 注册表
24
- * 存储所有已注册的废弃 API 配置
25
- */
26
- const deprecatedApis = /* @__PURE__ */ new Map();
27
- /**
28
- * 注册一个废弃的 API
29
- *
30
- * 将 API 添加到废弃注册表中,后续可通过 checkDeprecation 检查
31
- *
32
- * @param config - 废弃配置对象
33
- *
34
- * @example
35
- * ```typescript
36
- * registerDeprecatedApi({
37
- * name: 'getDouyinData',
38
- * deprecatedIn: '6.0.0',
39
- * removedIn: '7.0.0',
40
- * replacement: 'douyinFetcher',
41
- * throwError: true
42
- * })
43
- * ```
44
- */
45
- function registerDeprecatedApi(config) {
46
- deprecatedApis.set(config.name, config);
47
- }
48
- /**
49
- * 检查 API 是否已废弃并进行相应处理
50
- *
51
- * 如果 API 已注册为废弃,根据配置决定是打印警告还是抛出错误
52
- *
53
- * @param apiName - 要检查的 API 名称
54
- * @throws {DeprecatedApiError} 如果 API 已废弃且配置为抛出错误
55
- *
56
- * @example
57
- * ```typescript
58
- * // 在函数开头调用检查
59
- * function getDouyinData(options) {
60
- * checkDeprecation('getDouyinData')
61
- * // ...
62
- * }
63
- * ```
64
- */
65
- function checkDeprecation(apiName) {
66
- const config = deprecatedApis.get(apiName);
67
- if (!config) return;
68
- const message = buildDeprecationMessage(config);
69
- if (config.throwError) throw new DeprecatedApiError(message, config);
70
- else console.warn(message);
71
- }
72
- /**
73
- * 根据配置构建废弃提示消息
74
- *
75
- * @param config - 废弃配置
76
- * @returns 格式化的废弃提示消息字符串
77
- */
78
- function buildDeprecationMessage(config) {
79
- const lines = [`[DEPRECATED] "${config.name}" 已在 v${config.deprecatedIn} 版本废弃。`];
80
- if (config.replacement) lines.push(`请使用 "${config.replacement}" 替代。`);
81
- else lines.push("此接口已被上游删除,无法继续使用,无可用替代方案。");
82
- if (config.removedIn) lines.push(`此 API 将在 v${config.removedIn} 版本移除。`);
83
- if (config.migrationGuide) lines.push(`迁移指南: ${config.migrationGuide}`);
84
- return lines.join("\n");
85
- }
86
- /**
87
- * 废弃 API 调用错误类
88
- *
89
- * 当调用已废弃且配置为抛出错误的 API 时抛出此错误
90
- * 包含完整的废弃配置信息,便于调试和迁移
91
- */
92
- var DeprecatedApiError = class DeprecatedApiError extends Error {
93
- /** 废弃配置信息,包含替代方案等详细信息 */
94
- config;
95
- /**
96
- * 创建废弃 API 错误实例
97
- *
98
- * @param message - 错误消息
99
- * @param config - 废弃配置对象
100
- */
101
- constructor(message, config) {
102
- super(message);
103
- this.name = "DeprecatedApiError";
104
- this.config = config;
105
- Error.captureStackTrace?.(this, DeprecatedApiError);
106
- }
107
- };
108
- registerDeprecatedApi({
109
- name: "getDouyinData",
110
- deprecatedIn: "6.0.0",
111
- removedIn: "7.0.0",
112
- replacement: "douyinFetcher 或 client.douyin.fetcher",
113
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
114
- throwError: true
115
- });
116
- registerDeprecatedApi({
117
- name: "getBilibiliData",
118
- deprecatedIn: "6.0.0",
119
- removedIn: "7.0.0",
120
- replacement: "bilibiliFetcher 或 client.bilibili.fetcher",
121
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
122
- throwError: true
123
- });
124
- registerDeprecatedApi({
125
- name: "getKuaishouData",
126
- deprecatedIn: "6.0.0",
127
- removedIn: "7.0.0",
128
- replacement: "kuaishouFetcher 或 client.kuaishou.fetcher",
129
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
130
- throwError: true
131
- });
132
- registerDeprecatedApi({
133
- name: "getXiaohongshuData",
134
- deprecatedIn: "6.0.0",
135
- removedIn: "7.0.0",
136
- replacement: "xiaohongshuFetcher 或 client.xiaohongshu.fetcher",
137
- migrationGuide: "https://github.com/ikenxuan/amagi/blob/main/packages/core/MIGRATION-v6.md",
138
- throwError: true
139
- });
140
- [
141
- {
142
- name: "单个视频作品数据",
143
- replacement: "fetchVideoInfo"
144
- },
145
- {
146
- name: "单个视频下载信息数据",
147
- replacement: "fetchVideoStreamUrl"
148
- },
149
- {
150
- name: "评论数据",
151
- replacement: "fetchComments"
152
- },
153
- {
154
- name: "指定评论的回复",
155
- replacement: "fetchCommentReplies"
156
- },
157
- {
158
- name: "用户主页数据",
159
- replacement: "fetchUserCard"
160
- },
161
- {
162
- name: "用户主页动态列表数据",
163
- replacement: "fetchUserDynamicList"
164
- },
165
- {
166
- name: "用户空间详细信息",
167
- replacement: "fetchUserSpaceInfo"
168
- },
169
- {
170
- name: "获取UP主总播放量",
171
- replacement: "fetchUploaderTotalViews"
172
- },
173
- {
174
- name: "Emoji数据",
175
- replacement: "fetchEmojiList"
176
- },
177
- {
178
- name: "番剧基本信息数据",
179
- replacement: "fetchBangumiInfo"
180
- },
181
- {
182
- name: "番剧下载信息数据",
183
- replacement: "fetchBangumiStreamUrl"
184
- },
185
- {
186
- name: "动态详情数据",
187
- replacement: "fetchDynamicDetail"
188
- },
189
- {
190
- name: "直播间信息",
191
- replacement: "fetchLiveRoomInfo"
192
- },
193
- {
194
- name: "直播间初始化信息",
195
- replacement: "fetchLiveRoomInitInfo"
196
- },
197
- {
198
- name: "登录基本信息",
199
- replacement: "fetchLoginStatus"
200
- },
201
- {
202
- name: "申请二维码",
203
- replacement: "requestLoginQrcode"
204
- },
205
- {
206
- name: "二维码状态",
207
- replacement: "checkQrcodeStatus"
208
- },
209
- {
210
- name: "AV转BV",
211
- replacement: "convertAvToBv"
212
- },
213
- {
214
- name: "BV转AV",
215
- replacement: "convertBvToAv"
216
- },
217
- {
218
- name: "专栏正文内容",
219
- replacement: "fetchArticleContent"
220
- },
221
- {
222
- name: "专栏显示卡片信息",
223
- replacement: "fetchArticleCards"
224
- },
225
- {
226
- name: "专栏文章基本信息",
227
- replacement: "fetchArticleInfo"
228
- },
229
- {
230
- name: "文集基本信息",
231
- replacement: "fetchArticleListInfo"
232
- },
233
- {
234
- name: "实时弹幕",
235
- replacement: "fetchVideoDanmaku"
236
- },
237
- {
238
- name: "从_v_voucher_申请_captcha",
239
- replacement: "requestCaptchaFromVoucher"
240
- },
241
- {
242
- name: "验证验证码结果",
243
- replacement: "validateCaptchaResult"
244
- },
245
- {
246
- name: "视频作品数据",
247
- replacement: "fetchVideoWork"
248
- },
249
- {
250
- name: "图集作品数据",
251
- replacement: "fetchImageAlbumWork"
252
- },
253
- {
254
- name: "合辑作品数据",
255
- replacement: "fetchSlidesWork"
256
- },
257
- {
258
- name: "文字作品数据",
259
- replacement: "fetchTextWork"
260
- },
261
- {
262
- name: "聚合解析",
263
- replacement: "parseWork"
264
- },
265
- {
266
- name: "指定评论回复数据",
267
- replacement: "fetchCommentReplies"
268
- },
269
- {
270
- name: "用户主页视频列表数据",
271
- replacement: "fetchUserVideoList"
272
- },
273
- {
274
- name: "热点词数据",
275
- replacement: "fetchSuggestWords"
276
- },
277
- {
278
- name: "搜索数据",
279
- replacement: "searchContent"
280
- },
281
- {
282
- name: "音乐数据",
283
- replacement: "fetchMusicInfo"
284
- },
285
- {
286
- name: "直播间信息数据",
287
- replacement: "fetchLiveRoomInfo"
288
- },
289
- {
290
- name: "申请二维码数据",
291
- replacement: "requestLoginQrcode"
292
- },
293
- {
294
- name: "动态表情数据",
295
- replacement: "fetchDynamicEmojiList"
296
- },
297
- {
298
- name: "弹幕数据",
299
- replacement: "fetchDanmakuList"
300
- },
301
- {
302
- name: "单个视频作品数据",
303
- replacement: "fetchVideoWork"
304
- },
305
- {
306
- name: "首页推荐数据",
307
- replacement: "fetchHomeFeed"
308
- },
309
- {
310
- name: "单个笔记数据",
311
- replacement: "fetchNoteDetail"
312
- },
313
- {
314
- name: "用户数据",
315
- replacement: "fetchUserProfile"
316
- },
317
- {
318
- name: "用户笔记数据",
319
- replacement: "fetchUserNoteList"
320
- },
321
- {
322
- name: "表情列表",
323
- replacement: "fetchEmojiList"
324
- },
325
- {
326
- name: "搜索笔记",
327
- replacement: "searchNotes"
328
- }
329
- ].forEach(({ name, replacement }) => {
330
- registerDeprecatedApi({
331
- name: `methodType: '${name}'`,
332
- deprecatedIn: "6.0.0",
333
- removedIn: "7.0.0",
334
- replacement: `fetcher.${replacement}()`,
335
- throwError: true
336
- });
337
- });
338
- registerDeprecatedApi({
339
- name: `methodType: '动态卡片数据'`,
340
- deprecatedIn: "6.0.0",
341
- removedIn: "7.0.0",
342
- migrationGuide: "https://amagi-docs.vercel.app/docs/changelog/6.1.3",
343
- throwError: false
344
- });
345
- registerDeprecatedApi({
346
- name: "fetchDynamicCard",
347
- deprecatedIn: "6.1.3",
348
- removedIn: "7.0.0",
349
- migrationGuide: "https://amagi-docs.vercel.app/docs/changelog/6.1.3",
350
- throwError: false
351
- });
352
- //#endregion
353
- //#region src/model/DataFetchers.ts
354
- /**
355
- * 数据获取器模块 (已废弃)
356
- *
357
- * 此模块中的 getXXXData 函数已在 v6 版本废弃并移除
358
- * 请使用新的 fetcher API 替代
359
- *
360
- * @module model/DataFetchers
361
- * @deprecated v6 已废弃,请使用 fetcher API 替代
362
- */
363
- /**
364
- * 获取抖音数据
365
- *
366
- * @deprecated v6 已废弃,请使用 douyinFetcher 或 client.douyin.fetcher 替代
367
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
368
- *
369
- * @example
370
- * ```typescript
371
- * // 旧用法 (已废弃,会抛出错误)
372
- * const data = await getDouyinData('videoWork', { aweme_id: '123' }, cookie)
373
- *
374
- * // 新用法
375
- * import { douyinFetcher } from '@ikenxuan/amagi'
376
- * const data = await douyinFetcher.fetchVideoWork({ aweme_id: '123' }, cookie)
377
- *
378
- * // 或使用客户端实例
379
- * const client = createAmagiClient({ cookies: { douyin: cookie } })
380
- * const data = await client.douyin.fetcher.fetchVideoWork({ aweme_id: '123' })
381
- * ```
382
- */
383
- function getDouyinData(..._args) {
384
- checkDeprecation("getDouyinData");
385
- throw new Error("getDouyinData 已废弃");
386
- }
387
- /**
388
- * 获取B站数据
389
- *
390
- * @deprecated v6 已废弃,请使用 bilibiliFetcher 或 client.bilibili.fetcher 替代
391
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
392
- *
393
- * @example
394
- * ```typescript
395
- * // 旧用法 (已废弃,会抛出错误)
396
- * const data = await getBilibiliData('videoInfo', { bvid: 'BV123' }, cookie)
397
- *
398
- * // 新用法
399
- * import { bilibiliFetcher } from '@ikenxuan/amagi'
400
- * const data = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV123' }, cookie)
401
- *
402
- * // 或使用客户端实例
403
- * const client = createAmagiClient({ cookies: { bilibili: cookie } })
404
- * const data = await client.bilibili.fetcher.fetchVideoInfo({ bvid: 'BV123' })
405
- * ```
406
- */
407
- function getBilibiliData(..._args) {
408
- checkDeprecation("getBilibiliData");
409
- throw new Error("getBilibiliData 已废弃");
410
- }
411
- /**
412
- * 获取快手数据
413
- *
414
- * @deprecated v6 已废弃,请使用 kuaishouFetcher 或 client.kuaishou.fetcher 替代
415
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
416
- *
417
- * @example
418
- * ```typescript
419
- * // 旧用法 (已废弃,会抛出错误)
420
- * const data = await getKuaishouData('videoWork', { photoId: '123' }, cookie)
421
- *
422
- * // 新用法
423
- * import { kuaishouFetcher } from '@ikenxuan/amagi'
424
- * const data = await kuaishouFetcher.fetchVideoWork({ photoId: '123' }, cookie)
425
- *
426
- * // 或使用客户端实例
427
- * const client = createAmagiClient({ cookies: { kuaishou: cookie } })
428
- * const data = await client.kuaishou.fetcher.fetchVideoWork({ photoId: '123' })
429
- * ```
430
- */
431
- function getKuaishouData(..._args) {
432
- checkDeprecation("getKuaishouData");
433
- throw new Error("getKuaishouData 已废弃");
434
- }
435
- /**
436
- * 获取小红书数据
437
- *
438
- * @deprecated v6 已废弃,请使用 xiaohongshuFetcher 或 client.xiaohongshu.fetcher 替代
439
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
440
- *
441
- * @example
442
- * ```typescript
443
- * // 旧用法 (已废弃,会抛出错误)
444
- * const data = await getXiaohongshuData('noteDetail', { note_id: '123' }, cookie)
445
- *
446
- * // 新用法
447
- * import { xiaohongshuFetcher } from '@ikenxuan/amagi'
448
- * const data = await xiaohongshuFetcher.fetchNoteDetail({ note_id: '123' }, cookie)
449
- *
450
- * // 或使用客户端实例
451
- * const client = createAmagiClient({ cookies: { xiaohongshu: cookie } })
452
- * const data = await client.xiaohongshu.fetcher.fetchNoteDetail({ note_id: '123' })
453
- * ```
454
- */
455
- function getXiaohongshuData(..._args) {
456
- checkDeprecation("getXiaohongshuData");
457
- throw new Error("getXiaohongshuData 已废弃");
458
- }
459
- //#endregion
20
+ let chalk = require("chalk");
460
21
  //#region src/platform/bilibili/API.ts
461
22
  /**
462
23
  * B站 API URL 构建类
@@ -528,15 +89,6 @@ var BilibiliAPI = class {
528
89
  getDynamicDetail(data) {
529
90
  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`;
530
91
  }
531
- /**
532
- * 获取动态卡片信息
533
- *
534
- * @deprecated B站官方已于 `2025-08-09` 删除原 `dynamic_svr` 接口,该接口已停用。
535
- * 调用将返回错误信息,请使用 {@link getDynamicDetail} 替代。
536
- */
537
- getDynamicCard(data) {
538
- return this.getDynamicDetail(data);
539
- }
540
92
  /** 获取用户名片信息 */
541
93
  getUserCard(data) {
542
94
  return `https://api.bilibili.com/x/web-interface/card?mid=${data.host_mid}&photo=true`;
@@ -628,86 +180,6 @@ var BilibiliAPI = class {
628
180
  /** B站 API URL 构建器实例 */
629
181
  const bilibiliApiUrls = new BilibiliAPI();
630
182
  //#endregion
631
- //#region src/platform/bilibili/BilibiliApi.ts
632
- /**
633
- * 创建废弃的 API 存根函数
634
- */
635
- const createDeprecatedStub$3 = (methodName) => {
636
- return (..._args) => {
637
- checkDeprecation("getBilibiliData");
638
- throw new Error(`bilibili.${methodName} 已废弃,请使用 bilibiliFetcher 替代`);
639
- };
640
- };
641
- /**
642
- * B站相关 API 的命名空间。
643
- *
644
- * @deprecated v6 已废弃,请使用 bilibiliFetcher 或 client.bilibili.fetcher 替代
645
- */
646
- const bilibili = {
647
- /** @deprecated 请使用 bilibiliFetcher.fetchVideoInfo 替代 */
648
- getVideoInfo: createDeprecatedStub$3("getVideoInfo"),
649
- /** @deprecated 请使用 bilibiliFetcher.fetchVideoStreamUrl 替代 */
650
- getVideoStream: createDeprecatedStub$3("getVideoStream"),
651
- /** @deprecated 请使用 bilibiliFetcher.fetchComments 替代 */
652
- getComments: createDeprecatedStub$3("getComments"),
653
- /** @deprecated 请使用 bilibiliFetcher.fetchCommentReplies 替代 */
654
- getCommentReply: createDeprecatedStub$3("getCommentReply"),
655
- /** @deprecated 请使用 bilibiliFetcher.fetchUserCard 替代 */
656
- getUserProfile: createDeprecatedStub$3("getUserProfile"),
657
- /** @deprecated 请使用 bilibiliFetcher.fetchUserDynamicList 替代 */
658
- getUserDynamic: createDeprecatedStub$3("getUserDynamic"),
659
- /** @deprecated 请使用 bilibiliFetcher.fetchEmojiList 替代 */
660
- getEmojiList: createDeprecatedStub$3("getEmojiList"),
661
- /** @deprecated 请使用 bilibiliFetcher.fetchBangumiInfo 替代 */
662
- getBangumiInfo: createDeprecatedStub$3("getBangumiInfo"),
663
- /** @deprecated 请使用 bilibiliFetcher.fetchBangumiStreamUrl 替代 */
664
- getBangumiStream: createDeprecatedStub$3("getBangumiStream"),
665
- /** @deprecated 请使用 bilibiliFetcher.fetchDynamicDetail 替代 */
666
- getDynamicInfo: createDeprecatedStub$3("getDynamicInfo"),
667
- /** @deprecated 请使用 bilibiliFetcher.fetchDynamicCard 替代 */
668
- getDynamicCard: createDeprecatedStub$3("getDynamicCard"),
669
- /** @deprecated 请使用 bilibiliFetcher.fetchLiveRoomInfo 替代 */
670
- getLiveRoomDetail: createDeprecatedStub$3("getLiveRoomDetail"),
671
- /** @deprecated 请使用 bilibiliFetcher.fetchLiveRoomInitInfo 替代 */
672
- getLiveRoomInitInfo: createDeprecatedStub$3("getLiveRoomInitInfo"),
673
- /** @deprecated 请使用 bilibiliFetcher.fetchLoginStatus 替代 */
674
- getLoginBasicInfo: createDeprecatedStub$3("getLoginBasicInfo"),
675
- /** @deprecated 请使用 bilibiliFetcher.requestLoginQrcode 替代 */
676
- getLoginQrcode: createDeprecatedStub$3("getLoginQrcode"),
677
- /** @deprecated 请使用 bilibiliFetcher.checkQrcodeStatus 替代 */
678
- checkQrcodeStatus: createDeprecatedStub$3("checkQrcodeStatus"),
679
- /** @deprecated 请使用 bilibiliFetcher.fetchUploaderTotalViews 替代 */
680
- getUserTotalPlayCount: createDeprecatedStub$3("getUserTotalPlayCount"),
681
- /** @deprecated 请使用 bilibiliFetcher.convertAvToBv 替代 */
682
- convertAvToBv: createDeprecatedStub$3("convertAvToBv"),
683
- /** @deprecated 请使用 bilibiliFetcher.convertBvToAv 替代 */
684
- convertBvToAv: createDeprecatedStub$3("convertBvToAv"),
685
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleContent 替代 */
686
- getArticleContent: createDeprecatedStub$3("getArticleContent"),
687
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleCards 替代 */
688
- getArticleCard: createDeprecatedStub$3("getArticleCard"),
689
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleInfo 替代 */
690
- getArticleInfo: createDeprecatedStub$3("getArticleInfo"),
691
- /** @deprecated 请使用 bilibiliFetcher.fetchArticleListInfo 替代 */
692
- getColumnInfo: createDeprecatedStub$3("getColumnInfo"),
693
- /** @deprecated 请使用 bilibiliFetcher.fetchUserSpaceInfo 替代 */
694
- getUserProfileDetail: createDeprecatedStub$3("getUserProfileDetail"),
695
- /** @deprecated 请使用 bilibiliFetcher.requestCaptchaFromVoucher 替代 */
696
- applyVoucherCaptcha: createDeprecatedStub$3("applyVoucherCaptcha"),
697
- /** @deprecated 请使用 bilibiliFetcher.validateCaptchaResult 替代 */
698
- validateCaptcha: createDeprecatedStub$3("validateCaptcha"),
699
- /** @deprecated 请使用 bilibiliFetcher.fetchVideoDanmaku 替代 */
700
- getDanmaku: createDeprecatedStub$3("getDanmaku")
701
- };
702
- /**
703
- * 创建绑定了cookie的B站API对象
704
- *
705
- * @deprecated v6 已废弃,请使用 createBoundBilibiliFetcher 替代
706
- */
707
- const createBoundBilibiliApi = (_cookie, _requestConfig) => {
708
- return { ...bilibili };
709
- };
710
- //#endregion
711
183
  //#region src/model/events.ts
712
184
  /**
713
185
  * Amagi 事件系统
@@ -1020,7 +492,7 @@ const BilibiliBangumiStreamParamsSchema = zod.default.object({
1020
492
  });
1021
493
  /** 动态参数验证 */
1022
494
  const BilibiliDynamicParamsSchema = zod.default.object({
1023
- methodType: zod.default.enum(["dynamicDetail", "dynamicCard"], { error: "方法类型必须是\"dynamicDetail\"或\"dynamicCard\"" }),
495
+ methodType: zod.default.literal("dynamicDetail", { error: "方法类型必须是\"dynamicDetail\"" }),
1024
496
  dynamic_id: zod.default.string({ error: "动态ID必须是字符串" }).min(1, { error: "动态ID不能为空" })
1025
497
  });
1026
498
  /** 直播间参数验证 */
@@ -1102,7 +574,6 @@ const BilibiliValidationSchemas = {
1102
574
  bangumiInfo: BilibiliBangumiInfoParamsSchema,
1103
575
  bangumiStream: BilibiliBangumiStreamParamsSchema,
1104
576
  dynamicDetail: BilibiliDynamicParamsSchema,
1105
- dynamicCard: BilibiliDynamicParamsSchema,
1106
577
  liveRoomInfo: BilibiliLiveParamsSchema,
1107
578
  liveRoomInit: BilibiliLiveParamsSchema,
1108
579
  loginStatus: BilibiliLoginParamsSchema,
@@ -1133,7 +604,6 @@ const BilibiliMethodRoutes = {
1133
604
  bangumiInfo: "/fetch_bangumi_video_info",
1134
605
  bangumiStream: "/fetch_bangumi_video_playurl",
1135
606
  dynamicDetail: "/fetch_dynamic_info",
1136
- dynamicCard: "/fetch_dynamic_card",
1137
607
  liveRoomInfo: "/fetch_live_room_detail",
1138
608
  liveRoomInit: "/fetch_liveroom_def",
1139
609
  loginStatus: "/login_basic_info",
@@ -1710,18 +1180,20 @@ const xiaohongshuApiUrls = {
1710
1180
  * @returns 完整的接口URL
1711
1181
  */
1712
1182
  noteComments(data) {
1183
+ const baseUrl = "https://edith.xiaohongshu.com/api/sns/web/v2/comment/page";
1184
+ const params = {
1185
+ note_id: data.note_id,
1186
+ cursor: data.cursor ?? "",
1187
+ image_formats: [
1188
+ "jpg",
1189
+ "webp",
1190
+ "avif"
1191
+ ].join(","),
1192
+ xsec_token: data.xsec_token
1193
+ };
1713
1194
  return {
1714
1195
  apiPath: "/api/sns/web/v2/comment/page",
1715
- Url: `https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?${buildQueryString$1({
1716
- note_id: data.note_id,
1717
- cursor: data.cursor ?? "",
1718
- image_formats: [
1719
- "jpg",
1720
- "webp",
1721
- "avif"
1722
- ].join(","),
1723
- xsec_token: data.xsec_token
1724
- })}`
1196
+ Url: `${baseUrl}?${buildQueryString$1(params)}`
1725
1197
  };
1726
1198
  },
1727
1199
  /**
@@ -1741,19 +1213,21 @@ const xiaohongshuApiUrls = {
1741
1213
  * @returns 完整的接口URL
1742
1214
  */
1743
1215
  userNoteList(data) {
1216
+ const baseUrl = "https://edith.xiaohongshu.com/api/sns/web/v1/user_posted";
1217
+ const params = {
1218
+ user_id: data.user_id,
1219
+ cursor: data.cursor ?? "",
1220
+ num: data.num ?? 30,
1221
+ image_formats: [
1222
+ "jpg",
1223
+ "webp",
1224
+ "avif"
1225
+ ].join(","),
1226
+ xsec_source: "pc_feed"
1227
+ };
1744
1228
  return {
1745
1229
  apiPath: "/api/sns/web/v1/user_posted",
1746
- Url: `https://edith.xiaohongshu.com/api/sns/web/v1/user_posted?${buildQueryString$1({
1747
- user_id: data.user_id,
1748
- cursor: data.cursor ?? "",
1749
- num: data.num ?? 30,
1750
- image_formats: [
1751
- "jpg",
1752
- "webp",
1753
- "avif"
1754
- ].join(","),
1755
- xsec_source: "pc_feed"
1756
- })}`
1230
+ Url: `${baseUrl}?${buildQueryString$1(params)}`
1757
1231
  };
1758
1232
  },
1759
1233
  /**
@@ -1970,7 +1444,8 @@ const createErrorResponse = (error, message, code = 500, data) => {
1970
1444
  async function fetchBilibiliInternal(methodType, options, config) {
1971
1445
  const startTime = Date.now();
1972
1446
  try {
1973
- const rawData = await fetchBilibili({ ...validateBilibiliParams(methodType, options) }, config.cookie, config.requestConfig);
1447
+ const apiParams = { ...validateBilibiliParams(methodType, options) };
1448
+ const rawData = await fetchBilibili(apiParams, config.cookie, config.requestConfig);
1974
1449
  const duration = Date.now() - startTime;
1975
1450
  if (rawData.code !== 0) {
1976
1451
  const errorMessage = rawData.message || "B站数据获取失败";
@@ -1982,11 +1457,12 @@ async function fetchBilibiliInternal(methodType, options, config) {
1982
1457
  url: void 0,
1983
1458
  duration
1984
1459
  });
1985
- return createErrorResponse(rawData.amagiError ?? {
1460
+ const amagiError = rawData.amagiError ?? {
1986
1461
  errorDescription: errorMessage,
1987
1462
  requestType: methodType,
1988
1463
  requestUrl: void 0
1989
- }, errorMessage, rawData.code, rawData);
1464
+ };
1465
+ return createErrorResponse(amagiError, errorMessage, rawData.code, rawData);
1990
1466
  }
1991
1467
  const result = createSuccessResponse(rawData, "获取成功", 200);
1992
1468
  emitApiSuccess({
@@ -2293,31 +1769,6 @@ async function fetchDynamicDetail(options, cookie, requestConfig) {
2293
1769
  requestConfig
2294
1770
  });
2295
1771
  }
2296
- /**
2297
- * 获取B站动态卡片信息
2298
- *
2299
- * @deprecated v6.1.3 已废弃,B站官方已于 `2025-08-09` 删除原 `dynamic_svr` 接口。
2300
- * 调用将返回错误信息
2301
- * 计划于 v7.0.0 移除。
2302
- *
2303
- * @param options - 动态参数
2304
- * @param options.dynamic_id - 动态 ID
2305
- * @param cookie - B站 Cookie (可选)
2306
- * @param requestConfig - 请求配置 (可选)
2307
- * @returns 动态卡片数据(已停用,返回错误信息)
2308
- * @example
2309
- * ```typescript
2310
- * const result = await fetchDynamicCard({ dynamic_id: '123456789' }, cookie)
2311
- * // result.success === false,错误信息提示接口已停用
2312
- * ```
2313
- */
2314
- async function fetchDynamicCard(options, cookie, requestConfig) {
2315
- checkDeprecation("fetchDynamicCard");
2316
- return fetchBilibiliInternal("dynamicCard", options, {
2317
- cookie,
2318
- requestConfig
2319
- });
2320
- }
2321
1772
  //#endregion
2322
1773
  //#region src/model/fetchers/bilibili/live.ts
2323
1774
  /**
@@ -2622,86 +2073,1772 @@ const resolveBoundRequest = (boundCookie, base, override) => {
2622
2073
  * const strictResult = await fetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD', typeMode: 'strict' })
2623
2074
  * ```
2624
2075
  */
2625
- function createBoundBilibiliFetcher(cookie, requestConfig) {
2626
- const resolveRequest = (override) => resolveBoundRequest(cookie, requestConfig, override);
2627
- return {
2628
- fetchVideoInfo: (options, override) => fetchVideoInfo(options, ...resolveRequest(override)),
2629
- fetchVideoStreamUrl: (options, override) => fetchVideoStreamUrl(options, ...resolveRequest(override)),
2630
- fetchVideoDanmaku: (options, override) => fetchVideoDanmaku(options, ...resolveRequest(override)),
2631
- fetchComments: (options, override) => fetchComments(options, ...resolveRequest(override)),
2632
- fetchCommentReplies: (options, override) => fetchCommentReplies$1(options, ...resolveRequest(override)),
2633
- fetchUserCard: (options, override) => fetchUserCard(options, ...resolveRequest(override)),
2634
- fetchUserDynamicList: (options, override) => fetchUserDynamicList(options, ...resolveRequest(override)),
2635
- fetchUserLiveStatus: (options, override) => fetchUserLiveStatus(options, ...resolveRequest(override)),
2636
- fetchUserSpaceInfo: (options, override) => fetchUserSpaceInfo(options, ...resolveRequest(override)),
2637
- fetchUploaderTotalViews: (options, override) => fetchUploaderTotalViews(options, ...resolveRequest(override)),
2638
- fetchDynamicDetail: (options, override) => fetchDynamicDetail(options, ...resolveRequest(override)),
2639
- /** @deprecated v6.1.3 已废弃,调用将返回错误信息 */
2640
- fetchDynamicCard: (options, override) => fetchDynamicCard(options, ...resolveRequest(override)),
2641
- fetchBangumiInfo: (options, override) => fetchBangumiInfo(options, ...resolveRequest(override)),
2642
- fetchBangumiStreamUrl: (options, override) => fetchBangumiStreamUrl(options, ...resolveRequest(override)),
2643
- fetchLiveRoomInfo: (options, override) => fetchLiveRoomInfo$2(options, ...resolveRequest(override)),
2644
- fetchLiveRoomInitInfo: (options, override) => fetchLiveRoomInitInfo(options, ...resolveRequest(override)),
2645
- fetchArticleContent: (options, override) => fetchArticleContent(options, ...resolveRequest(override)),
2646
- fetchArticleCards: (options, override) => fetchArticleCards(options, ...resolveRequest(override)),
2647
- fetchArticleInfo: (options, override) => fetchArticleInfo(options, ...resolveRequest(override)),
2648
- fetchArticleListInfo: (options, override) => fetchArticleListInfo(options, ...resolveRequest(override)),
2649
- fetchLoginStatus: (options, override) => fetchLoginStatus(options, ...resolveRequest(override)),
2650
- requestLoginQrcode: (options, override) => requestLoginQrcode$1(options, ...resolveRequest(override)),
2651
- checkQrcodeStatus: (options, override) => checkQrcodeStatus(options, ...resolveRequest(override)),
2652
- requestCaptchaFromVoucher: (options, override) => requestCaptchaFromVoucher(options, ...resolveRequest(override)),
2653
- validateCaptchaResult: (options, override) => validateCaptchaResult(options, ...resolveRequest(override)),
2654
- convertAvToBv: (options, override) => convertAvToBv(options, ...resolveRequest(override)),
2655
- convertBvToAv: (options, override) => convertBvToAv(options, ...resolveRequest(override)),
2656
- fetchEmojiList: (options, override) => fetchEmojiList$3(options, ...resolveRequest(override))
2657
- };
2076
+ function createBoundBilibiliFetcher(cookie, requestConfig) {
2077
+ const resolveRequest = (override) => resolveBoundRequest(cookie, requestConfig, override);
2078
+ return {
2079
+ fetchVideoInfo: (options, override) => fetchVideoInfo(options, ...resolveRequest(override)),
2080
+ fetchVideoStreamUrl: (options, override) => fetchVideoStreamUrl(options, ...resolveRequest(override)),
2081
+ fetchVideoDanmaku: (options, override) => fetchVideoDanmaku(options, ...resolveRequest(override)),
2082
+ fetchComments: (options, override) => fetchComments(options, ...resolveRequest(override)),
2083
+ fetchCommentReplies: (options, override) => fetchCommentReplies$1(options, ...resolveRequest(override)),
2084
+ fetchUserCard: (options, override) => fetchUserCard(options, ...resolveRequest(override)),
2085
+ fetchUserDynamicList: (options, override) => fetchUserDynamicList(options, ...resolveRequest(override)),
2086
+ fetchUserLiveStatus: (options, override) => fetchUserLiveStatus(options, ...resolveRequest(override)),
2087
+ fetchUserSpaceInfo: (options, override) => fetchUserSpaceInfo(options, ...resolveRequest(override)),
2088
+ fetchUploaderTotalViews: (options, override) => fetchUploaderTotalViews(options, ...resolveRequest(override)),
2089
+ fetchDynamicDetail: (options, override) => fetchDynamicDetail(options, ...resolveRequest(override)),
2090
+ fetchBangumiInfo: (options, override) => fetchBangumiInfo(options, ...resolveRequest(override)),
2091
+ fetchBangumiStreamUrl: (options, override) => fetchBangumiStreamUrl(options, ...resolveRequest(override)),
2092
+ fetchLiveRoomInfo: (options, override) => fetchLiveRoomInfo$2(options, ...resolveRequest(override)),
2093
+ fetchLiveRoomInitInfo: (options, override) => fetchLiveRoomInitInfo(options, ...resolveRequest(override)),
2094
+ fetchArticleContent: (options, override) => fetchArticleContent(options, ...resolveRequest(override)),
2095
+ fetchArticleCards: (options, override) => fetchArticleCards(options, ...resolveRequest(override)),
2096
+ fetchArticleInfo: (options, override) => fetchArticleInfo(options, ...resolveRequest(override)),
2097
+ fetchArticleListInfo: (options, override) => fetchArticleListInfo(options, ...resolveRequest(override)),
2098
+ fetchLoginStatus: (options, override) => fetchLoginStatus(options, ...resolveRequest(override)),
2099
+ requestLoginQrcode: (options, override) => requestLoginQrcode$1(options, ...resolveRequest(override)),
2100
+ checkQrcodeStatus: (options, override) => checkQrcodeStatus(options, ...resolveRequest(override)),
2101
+ requestCaptchaFromVoucher: (options, override) => requestCaptchaFromVoucher(options, ...resolveRequest(override)),
2102
+ validateCaptchaResult: (options, override) => validateCaptchaResult(options, ...resolveRequest(override)),
2103
+ convertAvToBv: (options, override) => convertAvToBv(options, ...resolveRequest(override)),
2104
+ convertBvToAv: (options, override) => convertBvToAv(options, ...resolveRequest(override)),
2105
+ fetchEmojiList: (options, override) => fetchEmojiList$3(options, ...resolveRequest(override))
2106
+ };
2107
+ }
2108
+ //#endregion
2109
+ //#region src/model/fetchers/bilibili/index.ts
2110
+ /**
2111
+ * B站 Fetcher 模块入口
2112
+ * @module fetchers/bilibili
2113
+ */
2114
+ /**
2115
+ * B站数据获取器
2116
+ * 包含所有 B站 API 方法,调用时需要传递 cookie
2117
+ * @example
2118
+ * ```typescript
2119
+ * import { bilibiliFetcher } from '@ikenxuan/amagi'
2120
+ *
2121
+ * const result = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD' }, cookie)
2122
+ * ```
2123
+ */
2124
+ const bilibiliFetcher = {
2125
+ fetchVideoInfo,
2126
+ fetchVideoStreamUrl,
2127
+ fetchVideoDanmaku,
2128
+ fetchComments,
2129
+ fetchCommentReplies: fetchCommentReplies$1,
2130
+ fetchUserCard,
2131
+ fetchUserDynamicList,
2132
+ fetchUserLiveStatus,
2133
+ fetchUserSpaceInfo,
2134
+ fetchUploaderTotalViews,
2135
+ fetchDynamicDetail,
2136
+ fetchBangumiInfo,
2137
+ fetchBangumiStreamUrl,
2138
+ fetchLiveRoomInfo: fetchLiveRoomInfo$2,
2139
+ fetchLiveRoomInitInfo,
2140
+ fetchArticleContent,
2141
+ fetchArticleCards,
2142
+ fetchArticleInfo,
2143
+ fetchArticleListInfo,
2144
+ fetchLoginStatus,
2145
+ requestLoginQrcode: requestLoginQrcode$1,
2146
+ checkQrcodeStatus,
2147
+ requestCaptchaFromVoucher,
2148
+ validateCaptchaResult,
2149
+ convertAvToBv,
2150
+ convertBvToAv,
2151
+ fetchEmojiList: fetchEmojiList$3
2152
+ };
2153
+ //#endregion
2154
+ //#region src/platform/douyin/passport/sm3.ts
2155
+ /**
2156
+ * SM3 摘要(GM/T 0004-2012),抖音 bdms 签名链使用的变体
2157
+ *
2158
+ * 与标准实现的唯一差异:字符串按 `charCodeAt` 逐字符取字节(非 UTF-8 编码),
2159
+ * 与浏览器里 bdms 的 `strToBytes` 行为一致。签名输入均为 ASCII,实际不会踩到多字节分支,
2160
+ * 但仍保留该分支以保证与浏览器实现逐位一致。
2161
+ */
2162
+ /** 循环左移 32 位 */
2163
+ const rotl = (x, n) => {
2164
+ const shift = n % 32;
2165
+ return (x << shift | x >>> 32 - shift) >>> 0;
2166
+ };
2167
+ /** 轮常量 Tj */
2168
+ const tj = (j) => j < 16 ? 2043430169 : 2055708042;
2169
+ /** 布尔函数 FFj */
2170
+ const ff = (j, x, y, z) => j < 16 ? (x ^ y ^ z) >>> 0 : (x & y | x & z | y & z) >>> 0;
2171
+ /** 布尔函数 GGj */
2172
+ const gg = (j, x, y, z) => j < 16 ? (x ^ y ^ z) >>> 0 : (x & y | ~x & z) >>> 0;
2173
+ /** 初始向量 IV */
2174
+ const IV = [
2175
+ 1937774191,
2176
+ 1226093241,
2177
+ 388252375,
2178
+ 3666478592,
2179
+ 2842636476,
2180
+ 372324522,
2181
+ 3817729613,
2182
+ 2969243214
2183
+ ];
2184
+ /** 字符串转字节数组:逐字符 charCodeAt,大于一字节时按大端拆分 */
2185
+ const strToBytes = (input) => {
2186
+ const bytes = [];
2187
+ for (let i = 0; i < input.length; i++) {
2188
+ let code = input.charCodeAt(i);
2189
+ const chunk = [];
2190
+ do {
2191
+ chunk.push(code & 255);
2192
+ code >>= 8;
2193
+ } while (code);
2194
+ bytes.push(...chunk.reverse());
2195
+ }
2196
+ return bytes;
2197
+ };
2198
+ /** 消息扩展:生成 W[0..67] 与 W'[0..63](后者放在 68 之后) */
2199
+ const expand = (block) => {
2200
+ const w = new Array(132);
2201
+ 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;
2202
+ for (let j = 16; j < 68; j++) {
2203
+ let x = w[j - 16] ^ w[j - 9] ^ rotl(w[j - 3], 15);
2204
+ x = x ^ rotl(x, 15) ^ rotl(x, 23);
2205
+ w[j] = (x ^ rotl(w[j - 13], 7) ^ w[j - 6]) >>> 0;
2206
+ }
2207
+ for (let j = 0; j < 64; j++) w[j + 68] = (w[j] ^ w[j + 4]) >>> 0;
2208
+ return w;
2209
+ };
2210
+ /** SM3 压缩函数:就地更新寄存器 */
2211
+ const compress = (reg, block) => {
2212
+ const w = expand(block);
2213
+ const r = reg.slice();
2214
+ for (let j = 0; j < 64; j++) {
2215
+ let ss1 = rotl(r[0], 12) + r[4] + rotl(tj(j), j) & 4294967295;
2216
+ ss1 = rotl(ss1 >>> 0, 7);
2217
+ const ss2 = (ss1 ^ rotl(r[0], 12)) >>> 0;
2218
+ const tt1 = ff(j, r[0], r[1], r[2]) + r[3] + ss2 + w[j + 68] >>> 0;
2219
+ const tt2 = gg(j, r[4], r[5], r[6]) + r[7] + ss1 + w[j] >>> 0;
2220
+ r[3] = r[2];
2221
+ r[2] = rotl(r[1], 9);
2222
+ r[1] = r[0];
2223
+ r[0] = tt1;
2224
+ r[7] = r[6];
2225
+ r[6] = rotl(r[5], 19);
2226
+ r[5] = r[4];
2227
+ r[4] = (tt2 ^ rotl(tt2, 9) ^ rotl(tt2, 17)) >>> 0;
2228
+ }
2229
+ for (let i = 0; i < 8; i++) reg[i] = (reg[i] ^ r[i]) >>> 0;
2230
+ };
2231
+ /** 按 SM3 规则填充消息(0x80 + 0 + 64 位比特长度) */
2232
+ const pad = (bytes) => {
2233
+ const padded = bytes.slice();
2234
+ const bitLength = bytes.length * 8;
2235
+ padded.push(128);
2236
+ while (padded.length % 64 !== 56) padded.push(0);
2237
+ const high = Math.floor(bitLength / 4294967296);
2238
+ for (let i = 0; i < 4; i++) padded.push(high >>> (3 - i) * 8 & 255);
2239
+ for (let i = 0; i < 4; i++) padded.push(bitLength >>> (3 - i) * 8 & 255);
2240
+ return padded;
2241
+ };
2242
+ /**
2243
+ * 计算 SM3 摘要
2244
+ * @param message 待摘要的字符串或字节数组
2245
+ * @returns 32 字节摘要
2246
+ */
2247
+ const sm3 = (message) => {
2248
+ const bytes = typeof message === "string" ? strToBytes(message) : message;
2249
+ const reg = IV.slice();
2250
+ const padded = pad(bytes);
2251
+ for (let i = 0; i < padded.length; i += 64) compress(reg, padded.slice(i, i + 64));
2252
+ const digest = new Array(32);
2253
+ for (let i = 0; i < 8; i++) {
2254
+ digest[i * 4] = reg[i] >>> 24 & 255;
2255
+ digest[i * 4 + 1] = reg[i] >>> 16 & 255;
2256
+ digest[i * 4 + 2] = reg[i] >>> 8 & 255;
2257
+ digest[i * 4 + 3] = reg[i] & 255;
2258
+ }
2259
+ return digest;
2260
+ };
2261
+ /**
2262
+ * 连续两次 SM3(bdms 对 URL 与盐值的处理方式)
2263
+ * @param message 待摘要的字符串或字节数组
2264
+ * @returns 32 字节摘要
2265
+ */
2266
+ const sm3Twice = (message) => sm3(sm3(message));
2267
+ /**
2268
+ * 十六进制摘要,仅用于测试与排查
2269
+ * @param message 待摘要的字符串或字节数组
2270
+ */
2271
+ const sm3Hex = (message) => sm3(message).map((byte) => byte.toString(16).padStart(2, "0")).join("");
2272
+ //#endregion
2273
+ //#region src/platform/douyin/passport/aBogus.ts
2274
+ /**
2275
+ * a_bogus 签名(bdms 1.0.1.19 形态,passport 登录接口使用)
2276
+ *
2277
+ * 抖音同时在线着多个 a_bogus 版本:`@ikenxuan/amagi` 里带的是 web 数据接口用的旧版
2278
+ * (盐值 `cus`、pageId 6241),而 login.douyin.com 的 passport SDK 用的是本文件实现的
2279
+ * 新版(盐值 `dhzx`、pageId 7571、sdkVersion 1.0.1.19-fix.01)。两者互不通用,
2280
+ * 所以这里单独实现一份,不复用 amagi 的签名。
2281
+ *
2282
+ * 算法为社区公开的逆向结论(见 PR 说明中的参考链接),此处按 kkk 的代码风格重写:
2283
+ * - SM3 为抖音变体(连续两次摘要),见 ./sm3
2284
+ * - RC4 为变体:S 盒递减初始化 + `j = (j * S[i] + j + K[i]) % 256`
2285
+ * - Base64 使用自定义字符表,UA 段用 s3、最终结果用 s4
2286
+ * - `random()` 保留了原实现里 `Math.random`(函数对象而非调用)参与位运算的行为,
2287
+ * 该表达式恒为 NaN → 位运算结果恒为 0,这里直接写成常量而不是照抄错误代码
2288
+ */
2289
+ /** RC4 / S 盒长度 */
2290
+ const BOX_SIZE = 256;
2291
+ /** bdms 1.0.1.19 的盐值 */
2292
+ const SALT = "dhzx";
2293
+ /** bdms SDK 版本号,同时也是 passport 通用参数里的 p_bd */
2294
+ const BDMS_SDK_VERSION = "1.0.1.19-fix.01";
2295
+ /** 版本号基准时间戳(bdms 内部按 14 天为一档计数) */
2296
+ const VERSION_EPOCH = 17218368e5;
2297
+ /** 模块加载时刻,等价于浏览器里的「进入页面时间」 */
2298
+ const ENTER_PAGE_TS = Date.now();
2299
+ /** 自定义 Base64 字符表 */
2300
+ const BASE64_TABLES = {
2301
+ s0: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
2302
+ s1: "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=",
2303
+ s2: "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=",
2304
+ s3: "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe",
2305
+ s4: "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"
2306
+ };
2307
+ /**
2308
+ * 浏览器环境快照。服务器上没有真实窗口,这里给出一组常见的桌面分辨率组合;
2309
+ * 该值只影响指纹内容本身,不需要与任何真实设备对应。
2310
+ */
2311
+ const BROWSER_ENV = {
2312
+ innerWidth: 2048,
2313
+ innerHeight: 960,
2314
+ outerWidth: 2554,
2315
+ outerHeight: 1386,
2316
+ availWidth: 2560,
2317
+ availHeight: 1392,
2318
+ sizeWidth: 2560,
2319
+ sizeHeight: 1440,
2320
+ platform: "Win32"
2321
+ };
2322
+ /** RC4 密钥:bdms 用 `[1 / 256, 1 % 256, 14 % 256]` 造出 "\x00\x01\x0e" */
2323
+ const rc4Key = String.fromCharCode(...[
2324
+ 1 / BOX_SIZE,
2325
+ 1 % BOX_SIZE,
2326
+ 14 % BOX_SIZE
2327
+ ]);
2328
+ /**
2329
+ * 变体 RC4
2330
+ * @param key 密钥
2331
+ * @param text 明文(按 charCode 处理)
2332
+ */
2333
+ const rc4 = (key, text) => {
2334
+ const s = new Uint8Array(BOX_SIZE);
2335
+ const k = new Uint8Array(BOX_SIZE);
2336
+ for (let i = 0; i < BOX_SIZE; i++) {
2337
+ s[i] = 255 - i;
2338
+ k[i] = key.charCodeAt(i % key.length);
2339
+ }
2340
+ let j = 0;
2341
+ for (let i = 0; i < BOX_SIZE; i++) {
2342
+ j = (j * s[i] + j + k[i]) % BOX_SIZE;
2343
+ [s[i], s[j]] = [s[j], s[i]];
2344
+ }
2345
+ let x = 0;
2346
+ let y = 0;
2347
+ let cipher = "";
2348
+ for (let n = 0; n < text.length; n++) {
2349
+ x = (x + 1) % BOX_SIZE;
2350
+ y = (y + s[x]) % BOX_SIZE;
2351
+ [s[x], s[y]] = [s[y], s[x]];
2352
+ cipher += String.fromCharCode(text.charCodeAt(n) ^ s[(s[x] + s[y]) % BOX_SIZE]);
2353
+ }
2354
+ return cipher;
2355
+ };
2356
+ /**
2357
+ * 自定义字符表 Base64
2358
+ * @param input 明文(按 charCode 取低 8 位)
2359
+ * @param table 字符表名(s0 ~ s4)
2360
+ */
2361
+ const base64 = (input, table) => {
2362
+ const alphabet = BASE64_TABLES[table];
2363
+ let output = "";
2364
+ let i = 0;
2365
+ while (i < input.length) {
2366
+ const c1 = input.charCodeAt(i++);
2367
+ const c2 = input.charCodeAt(i++);
2368
+ const c3 = input.charCodeAt(i++);
2369
+ const chunk = (c1 & 255) << 16 | (Number.isNaN(c2) ? 0 : (c2 & 255) << 8) | (Number.isNaN(c3) ? 0 : c3 & 255);
2370
+ output += alphabet.charAt(chunk >> 18 & 63);
2371
+ output += alphabet.charAt(chunk >> 12 & 63);
2372
+ output += Number.isNaN(c2) ? "=" : alphabet.charAt(chunk >> 6 & 63);
2373
+ output += Number.isNaN(c3) ? "=" : alphabet.charAt(chunk & 63);
2374
+ }
2375
+ return output;
2376
+ };
2377
+ /**
2378
+ * 生成 4 字节随机混淆段
2379
+ * @param seed 两字节的基准值
2380
+ * @param flag 0 = 完全随机;1 = 高位固定为 0;2 = 低位受限、高位固定为 178
2381
+ */
2382
+ const mix = (seed, flag) => {
2383
+ const r = Math.random() * 65535 | 0;
2384
+ let low = r & 255;
2385
+ let high = r >> 8 & 255;
2386
+ if (flag === 1) high = 0;
2387
+ if (flag === 2) {
2388
+ low = Math.random() * 240 >> 0;
2389
+ if (low > 109) low += low % 2 + 1;
2390
+ high = 178;
2391
+ }
2392
+ return [
2393
+ low & 170 | seed[0] & 85,
2394
+ low & 85 | seed[0] & 170,
2395
+ high & 170 | seed[1] & 85,
2396
+ high & 85 | seed[1] & 170
2397
+ ];
2398
+ };
2399
+ /** SDK 版本号拆成数字数组,非纯数字段(如 `19-fix`)取 0 */
2400
+ const versionSegments = (version) => version.split(".").map((segment) => ~~Number(segment));
2401
+ /** 每 3 字节扩成 4 字节,掺入随机位 */
2402
+ const spread = (bytes) => {
2403
+ const masks = [
2404
+ 145,
2405
+ 110,
2406
+ 66,
2407
+ 189,
2408
+ 44,
2409
+ 211
2410
+ ];
2411
+ const out = [];
2412
+ for (let i = 0; i < bytes.length; i += 3) {
2413
+ if (i + 2 >= bytes.length) {
2414
+ out.push(bytes[i]);
2415
+ if (bytes[i + 1] !== void 0) out.push(bytes[i + 1]);
2416
+ continue;
2417
+ }
2418
+ const noise = Math.random() * 1e3 & 255;
2419
+ 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]);
2420
+ }
2421
+ return out;
2422
+ };
2423
+ /**
2424
+ * 生成 a_bogus
2425
+ * @param query 除 a_bogus 之外的完整查询串(未加 `?`,保持实际发送顺序)
2426
+ * @param userAgent 与请求头一致的 UA
2427
+ * @returns a_bogus 参数值(未做 URL 编码)
2428
+ */
2429
+ const aBogus = (query, userAgent) => {
2430
+ const salted = query.endsWith(SALT) ? query : query + SALT;
2431
+ const queryDigest = sm3Twice(salted);
2432
+ const saltDigest = sm3Twice(SALT);
2433
+ const uaEncoded = base64(rc4(rc4Key, userAgent), "s3");
2434
+ const uaDigest = sm3(uaEncoded);
2435
+ const now = Date.now();
2436
+ const ink = now - 1;
2437
+ /** 指纹字节表,下标沿用 bdms 内部编号,便于与逆向资料对照 */
2438
+ const b = {};
2439
+ b[24] = 41;
2440
+ b[26] = (now - VERSION_EPOCH) / 1e3 / 60 / 60 / 24 / 14 >> 0;
2441
+ b[27] = 6;
2442
+ b[28] = now - ENTER_PAGE_TS + 3 & 255;
2443
+ b[29] = now & 255;
2444
+ b[30] = now >> 8 & 255;
2445
+ b[31] = now >> 16 & 255;
2446
+ b[32] = now >> 24 & 255;
2447
+ b[33] = now / 2 ** 32 & 255;
2448
+ b[34] = now / 2 ** 40 & 255;
2449
+ b[35] = 1;
2450
+ b[36] = 0;
2451
+ b[38] = 129;
2452
+ b[39] = 0;
2453
+ b[40] = 0;
2454
+ b[41] = 0;
2455
+ b[42] = 0;
2456
+ b[43] = 0;
2457
+ b[44] = 14;
2458
+ b[45] = 0;
2459
+ b[46] = 0;
2460
+ b[47] = 0;
2461
+ b[48] = queryDigest[9];
2462
+ b[49] = queryDigest[18];
2463
+ b[51] = queryDigest[3];
2464
+ b[52] = saltDigest[10];
2465
+ b[53] = saltDigest[19];
2466
+ b[55] = saltDigest[4];
2467
+ b[56] = uaDigest[11];
2468
+ b[57] = uaDigest[21];
2469
+ b[59] = uaDigest[5];
2470
+ b[60] = ink & 255;
2471
+ b[61] = ink >> 8 & 255;
2472
+ b[62] = ink >> 16 & 255;
2473
+ b[63] = ink >> 24 & 255;
2474
+ b[64] = ink / 2 ** 32 & 255;
2475
+ b[65] = ink / 2 ** 40 & 255;
2476
+ b[66] = 3;
2477
+ b[67] = 147;
2478
+ b[68] = 29;
2479
+ b[69] = 0;
2480
+ b[70] = 0;
2481
+ b[71] = 239;
2482
+ b[72] = 24;
2483
+ b[73] = 0;
2484
+ b[74] = 0;
2485
+ const envSnapshot = Object.values(BROWSER_ENV).join("|");
2486
+ const envBytes = Array.from(envSnapshot, (char) => char.charCodeAt(0));
2487
+ b[79] = envBytes.length & 255;
2488
+ b[80] = envBytes.length >> 8 & 255;
2489
+ const tail = `${now + 3 & 255},`;
2490
+ const tailBytes = Array.from(tail, (char) => char.charCodeAt(0));
2491
+ b[84] = tailBytes.length & 255;
2492
+ b[85] = tailBytes.length >> 8 & 255;
2493
+ const version = versionSegments(BDMS_SDK_VERSION);
2494
+ const noise = mix([version[0], version[1]], 0).concat(mix([version[0], version[1]], 2));
2495
+ const checksum = [
2496
+ 24,
2497
+ 26,
2498
+ 27,
2499
+ 28,
2500
+ 29,
2501
+ 30,
2502
+ 31,
2503
+ 32,
2504
+ 33,
2505
+ 34,
2506
+ 35,
2507
+ 36,
2508
+ 38,
2509
+ 39,
2510
+ 40,
2511
+ 41,
2512
+ 42,
2513
+ 43,
2514
+ 44,
2515
+ 45,
2516
+ 46,
2517
+ 47,
2518
+ 48,
2519
+ 49,
2520
+ 51,
2521
+ 52,
2522
+ 53,
2523
+ 55,
2524
+ 56,
2525
+ 57,
2526
+ 59,
2527
+ 60,
2528
+ 61,
2529
+ 62,
2530
+ 63,
2531
+ 64,
2532
+ 65,
2533
+ 66,
2534
+ 67,
2535
+ 68,
2536
+ 69,
2537
+ 70,
2538
+ 71,
2539
+ 72,
2540
+ 73,
2541
+ 74,
2542
+ 79,
2543
+ 80,
2544
+ 84,
2545
+ 85
2546
+ ].reduce((acc, key) => acc ^ b[key], noise.reduce((acc, value) => acc ^ value, 0));
2547
+ /** 打乱后的字节顺序,与 bdms 内部的 tlist 一致 */
2548
+ const shuffled = [
2549
+ 34,
2550
+ 44,
2551
+ 56,
2552
+ 61,
2553
+ 73,
2554
+ 29,
2555
+ 70,
2556
+ 45,
2557
+ 35,
2558
+ 49,
2559
+ 38,
2560
+ 66,
2561
+ 51,
2562
+ 68,
2563
+ 28,
2564
+ 48,
2565
+ 64,
2566
+ 47,
2567
+ 30,
2568
+ 71,
2569
+ 26,
2570
+ 55,
2571
+ 31,
2572
+ 69,
2573
+ 59,
2574
+ 40,
2575
+ 62,
2576
+ 63,
2577
+ 27,
2578
+ 72,
2579
+ 41,
2580
+ 74,
2581
+ 57,
2582
+ 52,
2583
+ 42,
2584
+ 39,
2585
+ 33,
2586
+ 67,
2587
+ 53,
2588
+ 43,
2589
+ 65,
2590
+ 46,
2591
+ 36,
2592
+ 24,
2593
+ 60,
2594
+ 32,
2595
+ 79,
2596
+ 80,
2597
+ 84,
2598
+ 85
2599
+ ].map((key) => b[key]);
2600
+ const payload = spread(shuffled.concat(envBytes, tailBytes, [checksum]));
2601
+ const prefix = String.fromCharCode(...mix([3, 82], 1));
2602
+ const encrypted = rc4(String.fromCharCode(211), String.fromCharCode(...noise.concat(payload)));
2603
+ return base64(prefix + encrypted, "s4");
2604
+ };
2605
+ //#endregion
2606
+ //#region src/platform/douyin/passport/cookieJar.ts
2607
+ /**
2608
+ * 登录流程用的轻量 CookieJar
2609
+ *
2610
+ * 登录过程会跨 `www.douyin.com` / `login.douyin.com` / `ttwid.bytedance.com` 三个域,
2611
+ * 且同一个 cookie 名会被多次下发(例如 `ttwid` 在换取可信指纹后会被替换、
2612
+ * `sessionid` 在二次验证通过后会被升级)。这里只做一件事:**按下发顺序覆盖同名 cookie**,
2613
+ * 保证最终拿到的永远是最后一次下发的值,同时正确处理服务端的删除指令。
2614
+ *
2615
+ * 不做域/路径隔离:整个登录流程都在抖音自己的域下,隔离反而会漏掉跨子域下发的凭证。
2616
+ *
2617
+ * 另外承载一小部分**本地会话状态**(见 `INTERNAL_PREFIX`):passport 的几个接口对外是
2618
+ * 无状态的,会话全靠 cookie 串在调用之间传递,而 bd-ticket-guard 需要在多次调用之间
2619
+ * 记住自己生成的密钥与服务端签发的票据。这些条目以 `__amagi_` 开头,
2620
+ * `toString()` 不会把它们放进 Cookie 请求头,只有 `serialize()` 才会带上。
2621
+ */
2622
+ /** 本地会话状态的 cookie 名前缀,这些条目永远不会发给服务端 */
2623
+ const INTERNAL_PREFIX = "__amagi_";
2624
+ /** 是否为本地会话状态条目 */
2625
+ const isInternal = (name) => name.startsWith(INTERNAL_PREFIX);
2626
+ /** 判断一条 Set-Cookie 是否表示「删除该 cookie」 */
2627
+ const isDeletion = (value, attributes) => {
2628
+ if (value === "") return true;
2629
+ for (const attribute of attributes) {
2630
+ const [rawName, ...rest] = attribute.split("=");
2631
+ const name = rawName.trim().toLowerCase();
2632
+ const rawValue = rest.join("=").trim();
2633
+ if (name === "max-age") {
2634
+ const maxAge = Number(rawValue);
2635
+ if (Number.isFinite(maxAge) && maxAge <= 0) return true;
2636
+ }
2637
+ if (name === "expires") {
2638
+ const expires = Date.parse(rawValue);
2639
+ if (Number.isFinite(expires) && expires <= Date.now()) return true;
2640
+ }
2641
+ }
2642
+ return false;
2643
+ };
2644
+ var CookieJar = class {
2645
+ /** Map 保留插入顺序,重复 set 只更新值、不改变位置 */
2646
+ cookies = /* @__PURE__ */ new Map();
2647
+ /**
2648
+ * @param initial 初始 cookie 串,形如 `a=1; b=2`
2649
+ */
2650
+ constructor(initial) {
2651
+ if (initial) this.merge(initial);
2652
+ }
2653
+ /** 当前持有的 cookie 数量 */
2654
+ get size() {
2655
+ return this.cookies.size;
2656
+ }
2657
+ /**
2658
+ * 写入一条 cookie
2659
+ * @param name cookie 名
2660
+ * @param value cookie 值
2661
+ */
2662
+ set(name, value) {
2663
+ this.cookies.set(name, value);
2664
+ return this;
2665
+ }
2666
+ /**
2667
+ * 读取一条 cookie
2668
+ * @param name cookie 名
2669
+ */
2670
+ get(name) {
2671
+ return this.cookies.get(name);
2672
+ }
2673
+ /**
2674
+ * 是否持有某条 cookie
2675
+ * @param name cookie 名
2676
+ */
2677
+ has(name) {
2678
+ return this.cookies.has(name);
2679
+ }
2680
+ /**
2681
+ * 合并一段 `name=value; name=value` 形式的 cookie 串
2682
+ * @param cookieString cookie 串,空值直接忽略
2683
+ */
2684
+ merge(cookieString) {
2685
+ if (!cookieString) return this;
2686
+ for (const pair of cookieString.split(";")) {
2687
+ const index = pair.indexOf("=");
2688
+ if (index <= 0) continue;
2689
+ const name = pair.slice(0, index).trim();
2690
+ const value = pair.slice(index + 1).trim();
2691
+ if (name && value) this.cookies.set(name, value);
2692
+ }
2693
+ return this;
2694
+ }
2695
+ /**
2696
+ * 应用响应的 Set-Cookie 头
2697
+ * @param setCookies 单条或多条 Set-Cookie 原始值
2698
+ */
2699
+ applySetCookie(setCookies) {
2700
+ if (!setCookies) return this;
2701
+ for (const line of Array.isArray(setCookies) ? setCookies : [setCookies]) {
2702
+ const [pair, ...attributes] = line.split(";");
2703
+ const index = pair.indexOf("=");
2704
+ if (index <= 0) continue;
2705
+ const name = pair.slice(0, index).trim();
2706
+ const value = pair.slice(index + 1).trim();
2707
+ if (!name) continue;
2708
+ if (isDeletion(value, attributes)) {
2709
+ this.cookies.delete(name);
2710
+ continue;
2711
+ }
2712
+ this.cookies.set(name, value);
2713
+ }
2714
+ return this;
2715
+ }
2716
+ /**
2717
+ * 是否已经拿到登录态凭证(`ttwid` 是匿名设备指纹,不算登录)
2718
+ */
2719
+ isLoggedIn() {
2720
+ return this.has("sessionid") || this.has("sessionid_ss") || this.has("sid_guard");
2721
+ }
2722
+ /** 序列化为可直接放进 Cookie 请求头的字符串,不含本地会话状态 */
2723
+ toString() {
2724
+ return [...this.cookies].filter(([name]) => !isInternal(name)).map(([name, value]) => `${name}=${value}`).join("; ");
2725
+ }
2726
+ /**
2727
+ * 序列化为在两次调用之间传递的会话串,包含本地会话状态
2728
+ *
2729
+ * 登录流程内部用这个;最终落库的登录凭证用 `toString()`,避免把本地密钥写进配置。
2730
+ */
2731
+ serialize() {
2732
+ return [...this.cookies].map(([name, value]) => `${name}=${value}`).join("; ");
2733
+ }
2734
+ /** 导出为普通对象,便于断言与日志 */
2735
+ toJSON() {
2736
+ return Object.fromEntries(this.cookies);
2737
+ }
2738
+ };
2739
+ //#endregion
2740
+ //#region src/platform/douyin/passport/params.ts
2741
+ /**
2742
+ * passport 登录 SDK 的参数与签名构造
2743
+ *
2744
+ * login.douyin.com 的接口一共要过四道签名:
2745
+ * - `p_no`:SDK 版本相关参数排序后取 sha256
2746
+ * - `sign`:`排序后的前 10 个 query 参数 & 排序后的 body 参数 & app_key` 取 sha256
2747
+ * - `qs`:参与 sign 的参数名列表逐字节异或 5 后转十六进制
2748
+ * - `x-tt-passport-aid-sign` 请求头:以 appKey 为消息、当日 UTC 正午时间戳为密钥做 HMAC 派生
2749
+ *
2750
+ * 这些常量都是 SDK 自身的版本号与固定 app_key,属于协议的一部分,不含任何设备/账号信息。
2751
+ */
2752
+ /** passport 登录 SDK 的 app_key */
2753
+ const APP_KEY = "163e7ce78d58971a41f5b969996d85c2";
2754
+ /** 抖音 web 的 aid */
2755
+ const PASSPORT_AID = "6383";
2756
+ /** 登录接口域名 */
2757
+ const LOGIN_HOST = "login.douyin.com";
2758
+ /** 抖音主站域名 */
2759
+ const WEB_HOST = "www.douyin.com";
2760
+ /** 登录 SDK(normal 形态)版本号 */
2761
+ const JSSDK_VERSION = "3.1.3";
2762
+ /** 验证页 SDK(lite 形态)版本号 */
2763
+ const LITE_JSSDK_VERSION = "5.1.2";
2764
+ /** 验证页使用的 authn SDK 版本号 */
2765
+ const LITE_AUTHN_VERSION = "1.0.0.420-web";
2766
+ /** 各子 SDK 版本号,参与 p_no 计算 */
2767
+ const SDK_VERSIONS = {
2768
+ pVer: "1.1.3",
2769
+ pZt: "3.3.14",
2770
+ pUi: "2.1.9-alpha.6",
2771
+ pCa: "4.0.17",
2772
+ pCaReal: "1.0.0.874"
2773
+ };
2774
+ /** 与 aBogus 中 BROWSER_ENV 对应的窗口尺寸,用于生成 account_sdk_source_info */
2775
+ const ENV_VIEWPORT = {
2776
+ innerWidth: 2048,
2777
+ innerHeight: 960,
2778
+ outerWidth: 2554,
2779
+ outerHeight: 1386
2780
+ };
2781
+ const sha256Hex = (input) => node_crypto.default.createHash("sha256").update(input, "utf8").digest("hex");
2782
+ const hmacSha256 = (key, message) => node_crypto.default.createHmac("sha256", Buffer.from(key)).update(Buffer.from(message)).digest();
2783
+ const hexToBytes = (hex) => Uint8Array.from(hex.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []);
2784
+ /**
2785
+ * 逐字节异或 5 后转十六进制,SDK 用它编码参数名列表、验证码与密码
2786
+ * @param input 明文
2787
+ */
2788
+ const xor5Hex = (input) => Array.from(Buffer.from(input, "utf8"), (byte) => (byte ^ 5).toString(16).padStart(2, "0")).join("");
2789
+ /** 随机十六进制串,用于 biz_trace_id 一类的追踪 ID */
2790
+ const randomHex = (length) => node_crypto.default.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
2791
+ /** 当日 UTC 12:00 的秒级时间戳,aid-sign 以此为密钥基准 */
2792
+ const utcNoonTimestamp = (now = /* @__PURE__ */ new Date()) => Math.floor(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 12, 0, 0, 0) / 1e3);
2793
+ /**
2794
+ * 按 SDK 规则序列化参数:键名排序后拼成 `k=v&k=v`
2795
+ * @param params 参数对象
2796
+ * @param limit 大于等于 0 时只取排序后的前 limit 个键
2797
+ */
2798
+ const serializeSorted = (params, limit = -1) => {
2799
+ const keys = Object.keys(params).sort();
2800
+ if (limit >= 0) keys.splice(limit);
2801
+ return {
2802
+ text: keys.map((key) => `${key}=${typeof params[key] === "object" ? JSON.stringify(params[key]) : params[key]}`).join("&"),
2803
+ keys
2804
+ };
2805
+ };
2806
+ /**
2807
+ * 计算 sign 与 qs
2808
+ * @param params query 参数(仅排序后的前 10 个参与签名)
2809
+ * @param data body 参数,GET 请求传空对象
2810
+ */
2811
+ const makeSignAndQs = (params, data = {}) => {
2812
+ const { text: paramsText, keys } = serializeSorted(params, 10);
2813
+ const { text: dataText } = serializeSorted(data);
2814
+ return {
2815
+ sign: sha256Hex(`${paramsText}&${dataText}&app_key=${APP_KEY}`),
2816
+ qs: xor5Hex(keys.join(","))
2817
+ };
2818
+ };
2819
+ /**
2820
+ * 计算 p_no
2821
+ * @param pTs 毫秒时间戳,与 query 里的 p_ts 保持一致
2822
+ */
2823
+ const makePNo = (pTs) => {
2824
+ const parts = {
2825
+ passport_jssdk_version: JSSDK_VERSION,
2826
+ p_bd: BDMS_SDK_VERSION,
2827
+ p_ca: SDK_VERSIONS.pCa,
2828
+ p_ts: pTs,
2829
+ p_ver: SDK_VERSIONS.pVer,
2830
+ p_zt: SDK_VERSIONS.pZt
2831
+ };
2832
+ return sha256Hex(Object.keys(parts).sort().map((key) => `${key}=${parts[key]}`).join("&"));
2833
+ };
2834
+ /**
2835
+ * HKDF 风格的密钥派生,aid-sign 内部使用
2836
+ * @param keyHex 初始密钥(十六进制)
2837
+ * @param length 输出长度
2838
+ */
2839
+ const deriveKey = (keyHex, length) => {
2840
+ const output = [];
2841
+ let previous = "";
2842
+ let counter = 0;
2843
+ while (output.length < length) {
2844
+ counter++;
2845
+ const message = Uint8Array.from([...hexToBytes(previous), counter]);
2846
+ previous = hmacSha256(hexToBytes(keyHex), message).toString("hex");
2847
+ output.push(...hexToBytes(previous));
2848
+ }
2849
+ return Uint8Array.from(output.slice(0, length));
2850
+ };
2851
+ /**
2852
+ * 计算 `x-tt-passport-aid-sign` 请求头
2853
+ * @param urlPath 接口路径,如 `/passport/web/get_qrcode/`
2854
+ * @param timestamp 当日 UTC 正午时间戳(秒),默认取当前
2855
+ */
2856
+ const makeAidSign = (urlPath, timestamp = utcNoonTimestamp()) => {
2857
+ const encoder = new TextEncoder();
2858
+ const ts = String(timestamp);
2859
+ const seed = hmacSha256(encoder.encode(ts), encoder.encode(APP_KEY)).toString("hex");
2860
+ const key = deriveKey(seed, 32);
2861
+ return hmacSha256(key, encoder.encode(`aid=${PASSPORT_AID}&path=${urlPath}&ts=${ts}`)).toString("hex");
2862
+ };
2863
+ /**
2864
+ * 生成 account_sdk_source_info:SDK 采集的浏览器环境快照,异或 5 后转十六进制。
2865
+ *
2866
+ * 上游参考实现内联的是作者本机抓包值(含显卡型号、堆内存占用、带 query 的个人主页 URL),
2867
+ * 不适合进仓库,这里换成一份等价形态的通用快照。
2868
+ *
2869
+ * 实测服务端在 `get_qrcode` 阶段不校验该字段内容(删掉、置空、填垃圾值都同样返回
2870
+ * `error_code: 0`),保留它只是为了与 SDK 的真实请求形态一致。
2871
+ */
2872
+ const makeAccountSdkSourceInfo = () => xor5Hex(JSON.stringify({
2873
+ hardwareConcurrency: 8,
2874
+ webdriver: false,
2875
+ chromedriver: false,
2876
+ shelldriver: false,
2877
+ plugins: 5,
2878
+ innerHeight: ENV_VIEWPORT.innerHeight,
2879
+ innerWidth: ENV_VIEWPORT.innerWidth,
2880
+ outerHeight: ENV_VIEWPORT.outerHeight,
2881
+ outerWidth: ENV_VIEWPORT.outerWidth,
2882
+ webgl: {
2883
+ vendor: "Google Inc. (Intel)",
2884
+ renderer: "ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)"
2885
+ },
2886
+ performance: {
2887
+ timeOrigin: Date.now(),
2888
+ navigationTiming: {
2889
+ entryType: "navigation",
2890
+ initiatorType: "navigation",
2891
+ name: `https://${WEB_HOST}/`,
2892
+ renderBlockingStatus: "non-blocking"
2893
+ }
2894
+ },
2895
+ browser: {
2896
+ bit_protocol: "false",
2897
+ bit_helper: false
2898
+ }
2899
+ }));
2900
+ /**
2901
+ * 构造 passport 登录 SDK 的通用 query 参数
2902
+ *
2903
+ * 参数顺序即实际发送顺序:SDK 各拦截器依次注入,`request_host` 在这里先编码一次,
2904
+ * 拼 URL 时会再编码一次,所以线上抓包看到的是双重编码。
2905
+ * @param extra 业务参数(GET 时并入 query)
2906
+ */
2907
+ const makeCommonParams = (extra = {}) => {
2908
+ const pTs = String(Date.now());
2909
+ return {
2910
+ passport_jssdk_version: JSSDK_VERSION,
2911
+ passport_jssdk_type: "normal",
2912
+ is_from_ttaccountsdk: "1",
2913
+ aid: PASSPORT_AID,
2914
+ language: "zh",
2915
+ account_app_language: "zh-CN",
2916
+ ts: String(utcNoonTimestamp()),
2917
+ ...Object.fromEntries(Object.entries(extra).map(([key, value]) => [key, String(value)])),
2918
+ is_from_iesaccountsaas: "1",
2919
+ p_ui: SDK_VERSIONS.pUi,
2920
+ p_ca: SDK_VERSIONS.pCa,
2921
+ p_ca_real: SDK_VERSIONS.pCaReal,
2922
+ account_sdk_source: "web",
2923
+ account_sdk_source_info: makeAccountSdkSourceInfo(),
2924
+ p_js_v: JSSDK_VERSION,
2925
+ p_js_t: "pro",
2926
+ p_zt: SDK_VERSIONS.pZt,
2927
+ p_ver: SDK_VERSIONS.pVer,
2928
+ p_ver_real: "0",
2929
+ request_host: encodeURIComponent(`https://${WEB_HOST}`),
2930
+ p_bd: BDMS_SDK_VERSION,
2931
+ p_ts: pTs,
2932
+ p_no: makePNo(pTs),
2933
+ biz_trace_id: randomHex(8),
2934
+ device_platform: "web_app"
2935
+ };
2936
+ };
2937
+ /**
2938
+ * 构造验证页 SDK(lite 形态)的固定 query 参数
2939
+ * @param bizTraceId 业务追踪 ID
2940
+ */
2941
+ const makeLiteParams = (bizTraceId) => ({
2942
+ passport_jssdk_version: LITE_JSSDK_VERSION,
2943
+ passport_jssdk_type: "lite",
2944
+ is_from_ttaccountsdk: "1",
2945
+ aid: PASSPORT_AID,
2946
+ language: "zh",
2947
+ account_app_language: "zh-CN",
2948
+ new_authn_sdk_version: LITE_AUTHN_VERSION,
2949
+ biz_trace_id: bizTraceId
2950
+ });
2951
+ /**
2952
+ * 按插入顺序序列化为查询串(不排序,`request_host` 因此产生二次编码)
2953
+ * @param params 参数对象
2954
+ */
2955
+ const serializeQuery = (params) => Object.entries(params).map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`).join("&");
2956
+ //#endregion
2957
+ //#region src/platform/douyin/passport/ticketGuard.ts
2958
+ /**
2959
+ * bd-ticket-guard 设备票据
2960
+ *
2961
+ * 抖音的设备真实性风控。常见的说法是必须从浏览器 localStorage 里导出一对密钥才能用,
2962
+ * 但那只适用于「已登录之后」的接口 —— **登录流程本身就是票据的签发入口**:
2963
+ *
2964
+ * 1. 本地生成一对 P-256 密钥
2965
+ * 2. 把公钥写进 `bd_ticket_guard_client_data` cookie,在取二维码**之前**交给服务端
2966
+ * 3. 服务端在后续响应的 `bd-ticket-guard-server-data` 头里签发 `{ticket, ts_sign, client_cert}`
2967
+ * 4. 之后每个请求用 `ECDH(自己的私钥, client_cert 里的服务端公钥)` 派生的密钥
2968
+ * 对 `ticket=…&path=…&timestamp=…` 做 HMAC,放进 `bd-ticket-guard-client-data` 头
2969
+ *
2970
+ * 全程不需要浏览器,票据是服务端真实签发给我们自己这把密钥的,没有任何伪造数据。
2971
+ * 密钥与票据以 `__amagi_` 前缀存在 CookieJar 里,只在调用之间传递,不会发给服务端。
2972
+ */
2973
+ /** 交给服务端的公钥所在的 cookie */
2974
+ const CLIENT_DATA_COOKIE = "bd_ticket_guard_client_data";
2975
+ /** 声明 web 域版本的 cookie */
2976
+ const CLIENT_WEB_DOMAIN_COOKIE = "bd_ticket_guard_client_web_domain";
2977
+ /** 服务端也可能把签发结果放在这条 cookie 里 */
2978
+ const SERVER_DATA_COOKIE = "bd_ticket_guard_server_data";
2979
+ /** 服务端签发结果所在的响应头 */
2980
+ const SERVER_DATA_HEADER = "bd-ticket-guard-server-data";
2981
+ /** 本地会话状态:PKCS#8 私钥(base64) */
2982
+ const KEY_ENTRY = `${INTERNAL_PREFIX}tg_key`;
2983
+ /** 本地会话状态:服务端签发的票据 */
2984
+ const TICKET_ENTRY = `${INTERNAL_PREFIX}tg_ticket`;
2985
+ /** 本地会话状态:票据的时间戳签名 */
2986
+ const TS_SIGN_ENTRY = `${INTERNAL_PREFIX}tg_ts_sign`;
2987
+ /** 本地会话状态:ECDH 派生出的 HMAC 密钥(hex) */
2988
+ const ECDH_ENTRY = `${INTERNAL_PREFIX}tg_ecdh`;
2989
+ /** SDK 声明的 ticket-guard 版本 */
2990
+ const GUARD_VERSION = "2";
2991
+ /** SDK 声明的迭代版本 */
2992
+ const ITERATION_VERSION = "1";
2993
+ /** P-256 公钥的 SubjectPublicKeyInfo 前缀,其后紧跟 65 字节未压缩点 */
2994
+ const P256_SPKI_PREFIX = Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex");
2995
+ /** 被签名内容的字段列表,需与实际拼接顺序一致 */
2996
+ const REQ_CONTENT = "ticket,path,timestamp";
2997
+ /** 紧凑 JSON,与浏览器的 `JSON.stringify` 一致 */
2998
+ const compactJson = (value) => JSON.stringify(value);
2999
+ /**
3000
+ * 从 PEM 证书或 `pub.<base64>` 里取出服务端公钥
3001
+ * @param clientCert 服务端下发的 client_cert
3002
+ */
3003
+ const readServerPublicKey = (clientCert) => {
3004
+ if (clientCert.startsWith("pub.")) {
3005
+ const point = Buffer.from(clientCert.slice(4), "base64");
3006
+ const body = point.length === 65 ? point.subarray(1) : point;
3007
+ const spki = Buffer.concat([
3008
+ P256_SPKI_PREFIX,
3009
+ Buffer.from([4]),
3010
+ body
3011
+ ]);
3012
+ return node_crypto.default.createPublicKey({
3013
+ key: spki,
3014
+ format: "der",
3015
+ type: "spki"
3016
+ });
3017
+ }
3018
+ return new node_crypto.default.X509Certificate(clientCert).publicKey;
3019
+ };
3020
+ /**
3021
+ * bd-ticket-guard 会话
3022
+ *
3023
+ * 状态全部读写自传入的 CookieJar,因此与 passport 的无状态调用形态天然兼容。
3024
+ */
3025
+ var TicketGuard = class {
3026
+ jar;
3027
+ /**
3028
+ * @param jar 当前会话 cookie
3029
+ */
3030
+ constructor(jar) {
3031
+ this.jar = jar;
3032
+ }
3033
+ /** 本会话的私钥,缺失时生成一把并写回 CookieJar */
3034
+ get privateKey() {
3035
+ const stored = this.jar.get(KEY_ENTRY);
3036
+ if (stored) return node_crypto.default.createPrivateKey({
3037
+ key: Buffer.from(stored, "base64"),
3038
+ format: "der",
3039
+ type: "pkcs8"
3040
+ });
3041
+ const { privateKey } = node_crypto.default.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
3042
+ const der = privateKey.export({
3043
+ format: "der",
3044
+ type: "pkcs8"
3045
+ });
3046
+ this.jar.set(KEY_ENTRY, der.toString("base64"));
3047
+ return privateKey;
3048
+ }
3049
+ /** 未压缩格式的公钥(base64),即 `bd-ticket-guard-ree-public-key` */
3050
+ get reePublicKey() {
3051
+ const spki = node_crypto.default.createPublicKey(this.privateKey).export({
3052
+ format: "der",
3053
+ type: "spki"
3054
+ });
3055
+ return spki.subarray(spki.length - 65).toString("base64");
3056
+ }
3057
+ /** 已签发的票据,未签发时为 undefined */
3058
+ get state() {
3059
+ const ticket = this.jar.get(TICKET_ENTRY);
3060
+ const tsSign = this.jar.get(TS_SIGN_ENTRY);
3061
+ const ecdh = this.jar.get(ECDH_ENTRY);
3062
+ if (!ticket || !tsSign || !ecdh) return void 0;
3063
+ return {
3064
+ ticket,
3065
+ tsSign,
3066
+ ecdhKey: Buffer.from(ecdh, "hex")
3067
+ };
3068
+ }
3069
+ /**
3070
+ * 在首个 passport 请求之前把公钥交给服务端
3071
+ *
3072
+ * 缺了这一步扫码依然能成功,但服务端不会签发票据,后续请求也就无从携带。
3073
+ */
3074
+ publishPublicKey() {
3075
+ const payload = compactJson({
3076
+ "bd-ticket-guard-version": Number(GUARD_VERSION),
3077
+ "bd-ticket-guard-iteration-version": Number(ITERATION_VERSION),
3078
+ "bd-ticket-guard-ree-public-key": this.reePublicKey,
3079
+ "bd-ticket-guard-web-version": Number(GUARD_VERSION)
3080
+ });
3081
+ this.jar.set(CLIENT_DATA_COOKIE, encodeURIComponent(Buffer.from(payload, "utf8").toString("base64")));
3082
+ this.jar.set(CLIENT_WEB_DOMAIN_COOKIE, GUARD_VERSION);
3083
+ }
3084
+ /**
3085
+ * 消化响应里可能带回的票据签发结果
3086
+ * @param headers 响应头
3087
+ * @returns 是否收到了新票据
3088
+ */
3089
+ applyServerData(headers) {
3090
+ const fromHeader = headers[SERVER_DATA_HEADER];
3091
+ const raw = typeof fromHeader === "string" && fromHeader ? fromHeader : this.jar.get(SERVER_DATA_COOKIE);
3092
+ if (!raw) return false;
3093
+ let info;
3094
+ try {
3095
+ info = JSON.parse(Buffer.from(decodeURIComponent(raw), "base64").toString("utf8"));
3096
+ } catch {
3097
+ return false;
3098
+ }
3099
+ if (!info.ticket || !info.ts_sign || !info.client_cert) return false;
3100
+ let ecdhKey;
3101
+ try {
3102
+ ecdhKey = this.deriveEcdhKey(info.client_cert);
3103
+ } catch {
3104
+ return false;
3105
+ }
3106
+ this.jar.set(TICKET_ENTRY, info.ticket);
3107
+ this.jar.set(TS_SIGN_ENTRY, info.ts_sign);
3108
+ this.jar.set(ECDH_ENTRY, ecdhKey.toString("hex"));
3109
+ return true;
3110
+ }
3111
+ /**
3112
+ * 生成本次请求的 bd-ticket-guard 请求头
3113
+ *
3114
+ * 尚未拿到票据时只声明公钥,让服务端有机会签发;拿到之后带完整签名。
3115
+ * @param path 请求路径,不含 query
3116
+ * @param timestamp 秒级时间戳,默认取当前
3117
+ */
3118
+ headers(path, timestamp = Math.floor(Date.now() / 1e3)) {
3119
+ const base = {
3120
+ "bd-ticket-guard-version": GUARD_VERSION,
3121
+ "bd-ticket-guard-iteration-version": ITERATION_VERSION,
3122
+ "bd-ticket-guard-ree-public-key": this.reePublicKey
3123
+ };
3124
+ const state = this.state;
3125
+ if (!state) return base;
3126
+ const signed = `ticket=${state.ticket}&path=${path}&timestamp=${timestamp}`;
3127
+ const clientData = compactJson({
3128
+ ts_sign: state.tsSign,
3129
+ req_content: REQ_CONTENT,
3130
+ req_sign: node_crypto.default.createHmac("sha256", state.ecdhKey).update(signed, "utf8").digest("base64"),
3131
+ timestamp
3132
+ });
3133
+ return {
3134
+ ...base,
3135
+ "bd-ticket-guard-client-data": Buffer.from(clientData, "utf8").toString("base64"),
3136
+ "bd-ticket-guard-web-version": state.tsSign.startsWith("ts.1") ? "1" : GUARD_VERSION,
3137
+ "bd-ticket-guard-web-sign-type": "1"
3138
+ };
3139
+ }
3140
+ /**
3141
+ * ECDH + HKDF-SHA256 派生 HMAC 密钥
3142
+ * @param clientCert 服务端下发的证书或裸公钥
3143
+ */
3144
+ deriveEcdhKey(clientCert) {
3145
+ const shared = node_crypto.default.diffieHellman({
3146
+ privateKey: this.privateKey,
3147
+ publicKey: readServerPublicKey(clientCert)
3148
+ });
3149
+ return Buffer.from(node_crypto.default.hkdfSync("sha256", shared, Buffer.alloc(32), Buffer.alloc(0), 32));
3150
+ }
3151
+ };
3152
+ //#endregion
3153
+ //#region src/platform/douyin/passport/client.ts
3154
+ /**
3155
+ * passport 登录的 HTTP 客户端
3156
+ *
3157
+ * 走 amagi 自己的 `fetchResponse`(axios),因此代理、超时、重试与网络事件与其它接口一致。
3158
+ *
3159
+ * 客户端本身是无状态的:会话状态(`msToken`、`passport_csrf_token`)都以 cookie 形式
3160
+ * 随调用方传入的 cookie 串进出,`x-tt-passport-verify-portrait` 则由 `ttwid` 派生,
3161
+ * 因此同一份 cookie 在整个登录过程中会得到稳定的 portrait,调用方无需额外保存任何东西。
3162
+ */
3163
+ /** 与签名里的浏览器环境保持一致的 UA */
3164
+ 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";
3165
+ /** 单次请求默认超时 */
3166
+ const DEFAULT_TIMEOUT = 15e3;
3167
+ /** SSO 跳转最多跟随的次数 */
3168
+ const MAX_REDIRECT_HOPS = 5;
3169
+ /** 浏览器客户端提示头,服务端会与 UA 交叉校验 */
3170
+ const CLIENT_HINTS = {
3171
+ "sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Chromium\";v=\"142\"",
3172
+ "sec-ch-ua-mobile": "?0",
3173
+ "sec-ch-ua-platform": "\"Windows\""
3174
+ };
3175
+ /** 安全解析 JSON,失败返回空对象 */
3176
+ const parseJson = (text) => {
3177
+ try {
3178
+ return JSON.parse(text);
3179
+ } catch {
3180
+ return {};
3181
+ }
3182
+ };
3183
+ /**
3184
+ * 由 ttwid 派生出稳定的 verify portrait
3185
+ *
3186
+ * 浏览器里这个值在一次登录页生命周期内固定不变。这里用 cookie 中已有的设备指纹推导,
3187
+ * 既保证同一会话内多次调用得到同一个值,又不需要调用方额外携带状态。
3188
+ * @param jar 当前会话 cookie
3189
+ */
3190
+ const deriveVerifyPortrait = (jar) => {
3191
+ const seed = jar.get("ttwid") ?? jar.get("__ac_nonce") ?? "douyin-passport";
3192
+ const hex = node_crypto.default.createHash("sha256").update(seed).digest("hex");
3193
+ return `${[
3194
+ hex.slice(0, 8),
3195
+ hex.slice(8, 12),
3196
+ `4${hex.slice(13, 16)}`,
3197
+ `8${hex.slice(17, 20)}`,
3198
+ hex.slice(20, 32)
3199
+ ].join("-")}.login`;
3200
+ };
3201
+ var DouyinPassportClient = class {
3202
+ requestConfig;
3203
+ /** 会话 cookie */
3204
+ cookies;
3205
+ /** bd-ticket-guard 设备票据,状态随 cookie 一起流转 */
3206
+ ticketGuard;
3207
+ /**
3208
+ * @param cookie 已有的会话 cookie 串
3209
+ * @param requestConfig amagi 的请求配置(代理、超时、额外请求头)
3210
+ */
3211
+ constructor(cookie, requestConfig) {
3212
+ this.requestConfig = requestConfig;
3213
+ this.cookies = new CookieJar(cookie);
3214
+ this.ticketGuard = new TicketGuard(this.cookies);
3215
+ }
3216
+ /** CSRF token:优先用服务端下发的,缺失时本地生成并同步写进 cookie(双提交校验) */
3217
+ get csrfToken() {
3218
+ const fromCookie = this.cookies.get("passport_csrf_token");
3219
+ if (fromCookie) return fromCookie;
3220
+ const generated = randomHex(32);
3221
+ this.cookies.set("passport_csrf_token", generated);
3222
+ this.cookies.set("passport_csrf_token_default", generated);
3223
+ return generated;
3224
+ }
3225
+ /**
3226
+ * 初始化登录环境指纹
3227
+ *
3228
+ * 依次请求抖音首页拿 `__ac_nonce`、再向 ttwid 服务注册拿 `ttwid`。两步都是匿名的,
3229
+ * 任意机器、任意系统都能跑;失败不抛错,只会让后续更容易命中风控。
3230
+ */
3231
+ async bootstrap() {
3232
+ this.ticketGuard.publishPublicKey();
3233
+ if (this.cookies.has("ttwid") && this.cookies.has("__ac_nonce")) return;
3234
+ await this.send({
3235
+ method: "GET",
3236
+ url: `https://${WEB_HOST}/`,
3237
+ headers: {
3238
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
3239
+ ...CLIENT_HINTS
3240
+ }
3241
+ });
3242
+ await this.send({
3243
+ method: "POST",
3244
+ url: "https://ttwid.bytedance.com/ttwid/register/",
3245
+ headers: {
3246
+ "Content-Type": "application/json",
3247
+ Origin: `https://${WEB_HOST}`,
3248
+ Referer: `https://${WEB_HOST}/`
3249
+ },
3250
+ data: JSON.stringify({
3251
+ aid: 6383,
3252
+ service: WEB_HOST
3253
+ })
3254
+ });
3255
+ emitLogDebug(`[douyin passport] 环境指纹就绪: ttwid=${this.cookies.has("ttwid")}, ac_nonce=${this.cookies.has("__ac_nonce")}`);
3256
+ }
3257
+ /**
3258
+ * 请求 login.douyin.com 的 passport 接口(四重签名 + a_bogus 形态)
3259
+ * @param path 接口路径,如 `/passport/web/get_qrcode/`
3260
+ * @param params 业务参数,并入 query
3261
+ */
3262
+ async request(path, params = {}) {
3263
+ const common = makeCommonParams(params);
3264
+ const { sign, qs } = makeSignAndQs(common, {});
3265
+ const query = {
3266
+ ...common,
3267
+ sign,
3268
+ qs
3269
+ };
3270
+ const msToken = this.cookies.get("msToken");
3271
+ if (msToken) query.msToken = msToken;
3272
+ const queryString = serializeQuery(query);
3273
+ const url = `https://${LOGIN_HOST}${path}?${queryString}&a_bogus=${encodeURIComponent(aBogus(queryString, PASSPORT_USER_AGENT))}`;
3274
+ return this.send({
3275
+ method: "GET",
3276
+ url,
3277
+ headers: {
3278
+ Accept: "application/json, text/javascript",
3279
+ "Content-Type": "application/x-www-form-urlencoded",
3280
+ Referer: `https://${WEB_HOST}/`,
3281
+ "x-tt-passport-aid-sign": makeAidSign(path),
3282
+ "x-tt-passport-csrf-token": this.csrfToken,
3283
+ "x-tt-passport-verify-portrait": deriveVerifyPortrait(this.cookies),
3284
+ "x-tt-passport-trace-id": String(common.biz_trace_id),
3285
+ ...this.ticketGuard.headers(path),
3286
+ ...CLIENT_HINTS
3287
+ }
3288
+ });
3289
+ }
3290
+ /**
3291
+ * 请求 www.douyin.com 的验证页接口(lite 形态:固定 query + 表单 body,无签名)
3292
+ * @param path 接口路径,如 `/passport/web/send_code/`
3293
+ * @param params 业务参数,进 body
3294
+ * @param bizTraceId 业务追踪 ID,同一次验证流程内保持一致
3295
+ */
3296
+ async liteRequest(path, params, bizTraceId) {
3297
+ return this.send({
3298
+ method: "POST",
3299
+ url: `https://${WEB_HOST}${path}?${serializeQuery(makeLiteParams(bizTraceId))}`,
3300
+ headers: {
3301
+ Accept: "application/json, text/javascript",
3302
+ "Content-Type": "application/x-www-form-urlencoded",
3303
+ Origin: `https://${WEB_HOST}`,
3304
+ Referer: `https://${WEB_HOST}/`,
3305
+ "x-tt-passport-aid-sign": makeAidSign(path),
3306
+ "x-tt-passport-csrf-token": this.csrfToken,
3307
+ "x-tt-passport-verify-portrait": deriveVerifyPortrait(this.cookies),
3308
+ "x-tt-passport-trace-id": bizTraceId,
3309
+ ...this.ticketGuard.headers(path),
3310
+ ...CLIENT_HINTS
3311
+ },
3312
+ data: serializeQuery(params)
3313
+ });
3314
+ }
3315
+ /**
3316
+ * 跟随扫码确认后下发的 SSO 跳转链,把最终的登录凭证收进 CookieJar
3317
+ * @param redirectUrl `check_qrconnect` 返回的 redirect_url
3318
+ * @returns 是否拿到登录态 cookie
3319
+ */
3320
+ async followSsoRedirect(redirectUrl) {
3321
+ let current = redirectUrl;
3322
+ for (let hop = 0; hop < MAX_REDIRECT_HOPS; hop++) {
3323
+ const response = await this.send({
3324
+ method: "GET",
3325
+ url: current,
3326
+ maxRedirects: 0,
3327
+ headers: {
3328
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
3329
+ Referer: `https://${LOGIN_HOST}/`,
3330
+ ...CLIENT_HINTS
3331
+ }
3332
+ });
3333
+ const location = response.location;
3334
+ if (!location || response.status < 300 || response.status >= 400) break;
3335
+ current = new URL(location, current).toString();
3336
+ }
3337
+ return this.cookies.isLoggedIn();
3338
+ }
3339
+ /** 实际发请求:合并 cookie、消化 Set-Cookie 与 msToken */
3340
+ async send(config) {
3341
+ const cookie = this.cookies.toString();
3342
+ const response = await fetchResponse({
3343
+ timeout: this.requestConfig?.timeout ?? DEFAULT_TIMEOUT,
3344
+ proxy: this.requestConfig?.proxy,
3345
+ ...config,
3346
+ responseType: "text",
3347
+ maxRedirects: config.maxRedirects ?? 5,
3348
+ headers: {
3349
+ "User-Agent": PASSPORT_USER_AGENT,
3350
+ ...config.headers,
3351
+ ...cookie ? { Cookie: cookie } : {}
3352
+ }
3353
+ });
3354
+ if (isNetworkErrorResult(response)) throw new Error(response.error.amagiError.errorDescription);
3355
+ const axiosResponse = response;
3356
+ this.cookies.applySetCookie(axiosResponse.headers["set-cookie"]);
3357
+ const refreshed = axiosResponse.headers["x-ms-token"];
3358
+ if (typeof refreshed === "string" && refreshed) this.cookies.set("msToken", refreshed);
3359
+ if (this.ticketGuard.applyServerData(axiosResponse.headers)) emitLogDebug("[douyin passport] 已获得 bd-ticket-guard 票据");
3360
+ const raw = typeof axiosResponse.data === "string" ? axiosResponse.data : JSON.stringify(axiosResponse.data);
3361
+ return {
3362
+ status: axiosResponse.status,
3363
+ raw,
3364
+ body: parseJson(raw),
3365
+ cookie: this.cookies.serialize(),
3366
+ location: axiosResponse.headers.location
3367
+ };
3368
+ }
3369
+ };
3370
+ //#endregion
3371
+ //#region src/platform/douyin/passport/parser.ts
3372
+ /** 触发账号二次验证的错误码 */
3373
+ const ERROR_SECOND_VERIFY = 2046;
3374
+ /** 验证码错误 */
3375
+ const ERROR_WRONG_CODE = 1202;
3376
+ /** 发码过于频繁 */
3377
+ const ERROR_RATE_LIMITED = 1206;
3378
+ /** 命中风控 / 设备环境异常 */
3379
+ const RISK_ERROR_CODES = /* @__PURE__ */ new Set([2156, 4031]);
3380
+ /**
3381
+ * 轮询过于频繁
3382
+ *
3383
+ * 描述文案是「访问太频繁」,但设备指纹不完整时服务端也用这个码兜底,
3384
+ * 属于可重试的瞬时状态,退避后继续轮询即可,不能当致命错误。
3385
+ */
3386
+ const ERROR_POLL_BUSY = 7;
3387
+ /** 命中限频后的退避倍率 */
3388
+ const BUSY_BACKOFF = 2;
3389
+ /** 轮询间隔下限与默认值,服务端偶尔会给 0 */
3390
+ const MIN_INTERVAL = 1e3;
3391
+ const DEFAULT_INTERVAL = 3e3;
3392
+ /** 验证会话票据字段,需原样透传 */
3393
+ const STD_KEYS = [
3394
+ "std_verify_flow_id",
3395
+ "std_verify_scene",
3396
+ "std_verify_template",
3397
+ "std_verify_token",
3398
+ "std_verify_type",
3399
+ "std_verify_way"
3400
+ ];
3401
+ const asString = (value) => typeof value === "string" ? value : "";
3402
+ const asNumber = (value) => {
3403
+ const parsed = typeof value === "number" ? value : Number(value);
3404
+ return Number.isFinite(parsed) ? parsed : void 0;
3405
+ };
3406
+ /** 从响应体的多个可能位置取错误码 */
3407
+ const readErrorCode = (payload) => asNumber(payload.data?.error_code) ?? asNumber(payload.error_code);
3408
+ /** 取一段人类可读的错误描述 */
3409
+ const readMessage = (payload) => asString(payload.data?.description) || asString(payload.description) || asString(payload.message) || "";
3410
+ /**
3411
+ * 解析 `get_qrcode` 响应
3412
+ * @param payload 服务端响应体
3413
+ * @returns 二维码信息,缺少 token 时返回 null
3414
+ */
3415
+ const parseQrcode = (payload) => {
3416
+ const data = payload.data ?? {};
3417
+ const token = asString(data.token);
3418
+ if (!token) return null;
3419
+ return {
3420
+ token,
3421
+ content: asString(data.qrcode_index_url) || token,
3422
+ expireTime: asNumber(data.expire_time) ?? 0
3423
+ };
3424
+ };
3425
+ /** 解析服务端下发的可选验证方式 */
3426
+ const parseVerifyWays = (raw) => {
3427
+ if (!Array.isArray(raw)) return [];
3428
+ return raw.map((item) => {
3429
+ const way = item;
3430
+ return {
3431
+ verifyWay: asString(way?.verify_way),
3432
+ mobile: asString(way?.mobile) || void 0
3433
+ };
3434
+ }).filter((way) => way.verifyWay !== "");
3435
+ };
3436
+ /**
3437
+ * 从轮询响应里提取二次验证上下文
3438
+ * @param data 轮询响应的 data 段
3439
+ */
3440
+ const parseVerifyContext = (data) => {
3441
+ const stdParams = {};
3442
+ for (const key of STD_KEYS) {
3443
+ const value = asString(data[key]);
3444
+ if (value) stdParams[key] = value;
3445
+ }
3446
+ return {
3447
+ encryptUid: asString(data.encrypt_uid),
3448
+ verifyTicket: asString(data.verify_ticket),
3449
+ stdParams,
3450
+ copywritingKey: asString(data.copywriting_key) || "qr_connect",
3451
+ diversionTag: asString(data.ies_safety_diversion_tag) || "mfa",
3452
+ newVerifyFlow: asString(data.new_verify_flow),
3453
+ verifyWays: parseVerifyWays(data.verify_ways)
3454
+ };
3455
+ };
3456
+ /**
3457
+ * 解析 `check_qrconnect` 响应为状态机可消费的结果
3458
+ * @param payload 服务端响应体
3459
+ */
3460
+ const parsePollResult = (payload) => {
3461
+ const data = payload.data ?? {};
3462
+ const rawInterval = asNumber(data.interval) ?? 0;
3463
+ const interval = rawInterval >= MIN_INTERVAL ? rawInterval : DEFAULT_INTERVAL;
3464
+ const status = asString(data.status);
3465
+ const errorCode = readErrorCode(payload);
3466
+ if (errorCode === ERROR_SECOND_VERIFY || asString(data.account_flow) === "verify") return {
3467
+ status: "verify",
3468
+ interval,
3469
+ verify: parseVerifyContext(data)
3470
+ };
3471
+ if (errorCode !== void 0 && RISK_ERROR_CODES.has(errorCode)) return {
3472
+ status: "risk",
3473
+ interval,
3474
+ message: readMessage(payload) || `error_code=${errorCode}`
3475
+ };
3476
+ if (errorCode === ERROR_POLL_BUSY) return {
3477
+ status: "busy",
3478
+ interval: interval * BUSY_BACKOFF,
3479
+ message: readMessage(payload) || "轮询过于频繁"
3480
+ };
3481
+ switch (status) {
3482
+ case "new":
3483
+ case "scanned":
3484
+ case "expired": return {
3485
+ status,
3486
+ interval
3487
+ };
3488
+ case "confirmed": return {
3489
+ status: "confirmed",
3490
+ interval,
3491
+ redirectUrl: asString(data.redirect_url) || (Array.isArray(data.redirect_urls) ? asString(data.redirect_urls[0]) : "")
3492
+ };
3493
+ default: return {
3494
+ status: "unknown",
3495
+ interval,
3496
+ message: readMessage(payload) || (status ? `status=${status}` : `error_code=${errorCode ?? "unknown"}`)
3497
+ };
3498
+ }
3499
+ };
3500
+ /**
3501
+ * 解析 `send_code` 响应
3502
+ * @param payload 服务端响应体
3503
+ */
3504
+ const parseSendCodeResult = (payload) => {
3505
+ const data = payload.data ?? {};
3506
+ const errorCode = readErrorCode(payload);
3507
+ const retryAfter = asNumber(data.retry_time) ?? 60;
3508
+ const mobile = asString(data.mobile);
3509
+ if (errorCode === 0 || errorCode === void 0 && payload.message === "success") return {
3510
+ ok: true,
3511
+ mobile,
3512
+ retryAfter,
3513
+ message: ""
3514
+ };
3515
+ return {
3516
+ ok: false,
3517
+ mobile,
3518
+ retryAfter,
3519
+ errorCode,
3520
+ message: readMessage(payload) || (errorCode === ERROR_RATE_LIMITED ? "短信发送过于频繁" : `发码失败 error_code=${errorCode ?? "unknown"}`)
3521
+ };
3522
+ };
3523
+ /**
3524
+ * 解析 `validate_code` 响应
3525
+ * @param payload 服务端响应体
3526
+ */
3527
+ const parseValidateCodeResult = (payload) => {
3528
+ const data = payload.data ?? {};
3529
+ const errorCode = readErrorCode(payload);
3530
+ if (errorCode === 0 || asString(data.ticket) || errorCode === void 0 && payload.message === "success") return {
3531
+ ok: true,
3532
+ wrongCode: false,
3533
+ message: ""
3534
+ };
3535
+ return {
3536
+ ok: false,
3537
+ wrongCode: errorCode === ERROR_WRONG_CODE,
3538
+ errorCode,
3539
+ message: readMessage(payload) || (errorCode === ERROR_WRONG_CODE ? "验证码错误" : `验证失败 error_code=${errorCode ?? "unknown"}`)
3540
+ };
3541
+ };
3542
+ //#endregion
3543
+ //#region src/platform/douyin/passport/index.ts
3544
+ var passport_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
3545
+ BDMS_SDK_VERSION: () => BDMS_SDK_VERSION,
3546
+ CookieJar: () => CookieJar,
3547
+ DouyinPassportClient: () => DouyinPassportClient,
3548
+ INTERNAL_PREFIX: () => INTERNAL_PREFIX,
3549
+ PASSPORT_USER_AGENT: () => PASSPORT_USER_AGENT,
3550
+ TicketGuard: () => TicketGuard,
3551
+ aBogus: () => aBogus,
3552
+ makeAidSign: () => makeAidSign,
3553
+ makeSignAndQs: () => makeSignAndQs,
3554
+ parsePollResult: () => parsePollResult,
3555
+ parseQrcode: () => parseQrcode,
3556
+ parseSendCodeResult: () => parseSendCodeResult,
3557
+ parseValidateCodeResult: () => parseValidateCodeResult,
3558
+ randomHex: () => randomHex,
3559
+ sm3: () => sm3,
3560
+ sm3Hex: () => sm3Hex,
3561
+ sm3Twice: () => sm3Twice,
3562
+ utcNoonTimestamp: () => utcNoonTimestamp,
3563
+ xor5Hex: () => xor5Hex
3564
+ });
3565
+ //#endregion
3566
+ //#region src/types/NetworksConfigType.ts
3567
+ /** 快手平台API错误码 */
3568
+ let kuaishouAPIErrorCode = /* @__PURE__ */ function(kuaishouAPIErrorCode) {
3569
+ /** Cookie无效或已过期 */
3570
+ kuaishouAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
3571
+ /** 未知错误 */
3572
+ kuaishouAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
3573
+ return kuaishouAPIErrorCode;
3574
+ }({});
3575
+ /** 小红书平台API错误码 */
3576
+ let xiaohongshuAPIErrorCode = /* @__PURE__ */ function(xiaohongshuAPIErrorCode) {
3577
+ /** Cookie无效或已过期 */
3578
+ xiaohongshuAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
3579
+ /** 未知错误 */
3580
+ xiaohongshuAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
3581
+ /** 非法请求 */
3582
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ILLEGAL_REQUEST"] = 500] = "ILLEGAL_REQUEST";
3583
+ /** 检测到帐号异常,请稍后重试 */
3584
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ACCOUNT_ABNORMAL"] = 300011] = "ACCOUNT_ABNORMAL";
3585
+ /** 网络连接异常,请检查网络设置后重试 */
3586
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["NETWORK_ERROR"] = 300012] = "NETWORK_ERROR";
3587
+ /** 访问频次异常,请勿频繁操作 */
3588
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["FREQUENCY_ERROR"] = 300013] = "FREQUENCY_ERROR";
3589
+ /** 浏览器异常,请尝试更换浏览器后重试 */
3590
+ xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["BROWSER_ERROR"] = 300015] = "BROWSER_ERROR";
3591
+ return xiaohongshuAPIErrorCode;
3592
+ }({});
3593
+ //#endregion
3594
+ //#region src/model/fetchers/douyin/auth.ts
3595
+ /**
3596
+ * 抖音登录认证相关 API(passport 扫码登录)
3597
+ *
3598
+ * 与其它 fetcher 的差别:这几个接口不走 `DouyinData` 的 URL 拼装 + a_bogus 流水线,
3599
+ * 因为 passport 体系有自己的三重 query 签名、独立的 a_bogus 形态与双重编码规则。
3600
+ * 对外形态保持一致:同样是 `(options, cookie?, requestConfig?) => Result<T>`,
3601
+ * 同样发 `apiSuccess` / `apiError` 事件,同样复用 amagi 的代理、超时与重试。
3602
+ *
3603
+ * 这几个方法都是无状态的:会话状态全部装在 cookie 串里,调用方拿到返回的 `cookie`
3604
+ * 后在下一次调用时传回来即可。轮询循环由调用方维护。
3605
+ *
3606
+ * @module fetchers/douyin/auth
3607
+ */
3608
+ /** 短信验证码的验证方式标识,服务端未给出可用方式时的兜底值 */
3609
+ const SMS_VERIFY_WAY = "mobile_sms_verify";
3610
+ /**
3611
+ * 可以用「收 6 位验证码」这套流程走完的验证方式
3612
+ *
3613
+ * 除官方常见的 `mobile_sms_verify`,账号被判定需要辅助验证时会给出
3614
+ * `assist_mobile_sms_verify`,两者都是下行短信收码,走同一对
3615
+ * `send_code` / `validate_code` 接口,区别只在 `std_verify_way` 的取值。
3616
+ * 上行短信(`*_up_sms_verify`)要求用户从手机发短信出去,是另一套接口,不在此列。
3617
+ */
3618
+ const SMS_CODE_WAY_PATTERN = /^(assist_)?mobile_sms_verify$/;
3619
+ /**
3620
+ * 判断某个验证方式能否用短信验证码流程完成
3621
+ * @param verifyWay 服务端下发的 verify_way
3622
+ */
3623
+ const isSmsCodeVerifyWay = (verifyWay) => SMS_CODE_WAY_PATTERN.test(verifyWay);
3624
+ /**
3625
+ * 选出本次要用的 std_verify_way
3626
+ *
3627
+ * 优先用调用方指定的;否则从服务端给出的可选方式里挑一个能收码的;都没有才回退到默认值。
3628
+ * 之前这里写死 `mobile_sms_verify`,遇到辅助验证的账号会因为 way 对不上而失败。
3629
+ * @param verify 轮询下发的验证上下文
3630
+ * @param requested 调用方显式指定的验证方式
3631
+ */
3632
+ const resolveVerifyWay = (verify, requested) => requested ?? verify.verifyWays.find((way) => isSmsCodeVerifyWay(way.verifyWay))?.verifyWay ?? verify.stdParams.std_verify_way ?? SMS_VERIFY_WAY;
3633
+ /** 短信验证码的 act_type */
3634
+ const SMS_ACT_TYPE = "3737";
3635
+ /** 验证页 SDK 版本,随表单一起提交 */
3636
+ const AUTHN_VERSION = "1.0.0.420-web";
3637
+ /** 抖音 web 的 aid */
3638
+ const AID = "6383";
3639
+ /** 扫码成功后的跳转地址 */
3640
+ const NEXT_URL = "https://www.douyin.com";
3641
+ /**
3642
+ * 发码与验码共用的表单字段
3643
+ *
3644
+ * 字段顺序与「空值也要占位」的行为对齐官方验证页 SDK 的抓包形态:
3645
+ * `verify_ticket` / `new_verify_flow` / `std_verify_flow_id` / `std_verify_token`
3646
+ * 即使为空也必须出现,缺字段会被判为伪造请求。
3647
+ * @param verify 轮询下发的验证上下文
3648
+ * @param verifyWay 本次使用的验证方式,原样进 `std_verify_way`
3649
+ * @param tail 追加在 std_verify_way 之后的字段(发码是 is6Digits,验码是 code)
3650
+ */
3651
+ const buildVerifyBody = (verify, verifyWay, tail) => ({
3652
+ mix_mode: "1",
3653
+ type: SMS_ACT_TYPE,
3654
+ encrypt_uid: verify.encryptUid,
3655
+ verify_ticket: verify.verifyTicket,
3656
+ copywriting_key: verify.copywritingKey,
3657
+ ies_safety_diversion_tag: verify.diversionTag,
3658
+ new_verify_flow: verify.newVerifyFlow,
3659
+ std_verify_flow_id: verify.stdParams.std_verify_flow_id ?? "",
3660
+ std_verify_scene: verify.stdParams.std_verify_scene ?? "account_login",
3661
+ std_verify_template: verify.stdParams.std_verify_template ?? "ato_web",
3662
+ std_verify_token: verify.stdParams.std_verify_token ?? "",
3663
+ std_verify_type: verify.stdParams.std_verify_type ?? "MFA",
3664
+ std_verify_way: verifyWay,
3665
+ ...tail,
3666
+ aid: AID,
3667
+ new_authn_sdk_version: AUTHN_VERSION
3668
+ });
3669
+ /**
3670
+ * 构造 passport 侧的业务错误响应
3671
+ * @param methodType 方法名,进 amagiError.requestType
3672
+ * @param message 错误描述
3673
+ */
3674
+ const passportError = (methodType, message) => createErrorResponse({
3675
+ code: "UNKNOWN_ERROR",
3676
+ data: null,
3677
+ amagiError: {
3678
+ errorDescription: message,
3679
+ requestType: methodType,
3680
+ requestUrl: `https://login.douyin.com/passport/`
3681
+ },
3682
+ amagiMessage: message
3683
+ }, message);
3684
+ /** 统一包一层事件上报与异常兜底 */
3685
+ const run = async (methodType, task) => {
3686
+ const startTime = Date.now();
3687
+ try {
3688
+ const result = await task();
3689
+ const duration = Date.now() - startTime;
3690
+ if (result.code === 200) emitApiSuccess({
3691
+ platform: "douyin",
3692
+ methodType,
3693
+ response: result,
3694
+ statusCode: 200,
3695
+ duration
3696
+ });
3697
+ else emitApiError({
3698
+ platform: "douyin",
3699
+ methodType,
3700
+ errorCode: result.code,
3701
+ errorMessage: result.message,
3702
+ duration
3703
+ });
3704
+ return result;
3705
+ } catch (error) {
3706
+ const duration = Date.now() - startTime;
3707
+ const errorMessage = error instanceof Error ? error.message : "未知错误";
3708
+ emitApiError({
3709
+ platform: "douyin",
3710
+ methodType,
3711
+ errorMessage,
3712
+ duration
3713
+ });
3714
+ throw new Error(`抖音登录请求失败: ${errorMessage}`);
3715
+ }
3716
+ };
3717
+ /**
3718
+ * 申请抖音扫码登录二维码
3719
+ *
3720
+ * 首次调用会自动完成环境指纹初始化(`__ac_nonce` + `ttwid`),无需额外准备。
3721
+ * @param options - 请求选项 (可选)
3722
+ * @param cookie - 已有的会话 Cookie (可选,续用同一会话时传入)
3723
+ * @param requestConfig - 请求配置 (可选)
3724
+ * @returns 二维码令牌、内容与会话 cookie
3725
+ * @example
3726
+ * ```typescript
3727
+ * const qrcode = await requestPassportQrcode()
3728
+ * console.log(qrcode.data.content) // 拿去生成二维码图片
3729
+ * ```
3730
+ */
3731
+ async function requestPassportQrcode(options, cookie, requestConfig) {
3732
+ return run("passportQrcode", async () => {
3733
+ const client = new DouyinPassportClient(cookie, requestConfig);
3734
+ await client.bootstrap();
3735
+ const response = await client.request("/passport/web/get_qrcode/", {
3736
+ next: NEXT_URL,
3737
+ need_short_url: "true",
3738
+ need_logo: "false",
3739
+ is_new_login: "1"
3740
+ });
3741
+ const qrcode = parseQrcode(response.body);
3742
+ if (!qrcode) return passportError("passportQrcode", response.body.message || `获取二维码失败: ${response.raw.slice(0, 200)}`);
3743
+ return createSuccessResponse({
3744
+ token: qrcode.token,
3745
+ content: qrcode.content,
3746
+ expire_time: qrcode.expireTime,
3747
+ expires_in: Math.max(0, qrcode.expireTime - Math.floor(Date.now() / 1e3)),
3748
+ cookie: response.cookie
3749
+ }, "获取成功", 200);
3750
+ });
2658
3751
  }
2659
- //#endregion
2660
- //#region src/model/fetchers/bilibili/index.ts
2661
- /**
2662
- * B站 Fetcher 模块入口
2663
- * @module fetchers/bilibili
2664
- */
2665
3752
  /**
2666
- * B站数据获取器
2667
- * 包含所有 B站 API 方法,调用时需要传递 cookie
3753
+ * 查询抖音扫码登录二维码的状态
3754
+ *
3755
+ * 状态为 `confirmed` 时会自动跟随 SSO 跳转领取登录凭证,返回的 `cookie` 即完整登录态。
3756
+ * @param options - 二维码状态参数
3757
+ * @param options.token - `requestPassportQrcode` 返回的令牌
3758
+ * @param cookie - 会话 Cookie,必须是申请二维码时返回的那一份
3759
+ * @param requestConfig - 请求配置 (可选)
3760
+ * @returns 扫码状态与最新会话 cookie
2668
3761
  * @example
2669
3762
  * ```typescript
2670
- * import { bilibiliFetcher } from '@ikenxuan/amagi'
2671
- *
2672
- * const result = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD' }, cookie)
3763
+ * const status = await checkPassportQrcode({ token }, cookie)
3764
+ * // new 未扫码 / scanned 已扫待确认 / verify 需二次验证 / confirmed 登录成功 / expired 已过期
3765
+ * console.log(status.data.status)
2673
3766
  * ```
2674
3767
  */
2675
- const bilibiliFetcher = {
2676
- fetchVideoInfo,
2677
- fetchVideoStreamUrl,
2678
- fetchVideoDanmaku,
2679
- fetchComments,
2680
- fetchCommentReplies: fetchCommentReplies$1,
2681
- fetchUserCard,
2682
- fetchUserDynamicList,
2683
- fetchUserLiveStatus,
2684
- fetchUserSpaceInfo,
2685
- fetchUploaderTotalViews,
2686
- fetchDynamicDetail,
2687
- fetchDynamicCard,
2688
- fetchBangumiInfo,
2689
- fetchBangumiStreamUrl,
2690
- fetchLiveRoomInfo: fetchLiveRoomInfo$2,
2691
- fetchLiveRoomInitInfo,
2692
- fetchArticleContent,
2693
- fetchArticleCards,
2694
- fetchArticleInfo,
2695
- fetchArticleListInfo,
2696
- fetchLoginStatus,
2697
- requestLoginQrcode: requestLoginQrcode$1,
2698
- checkQrcodeStatus,
2699
- requestCaptchaFromVoucher,
2700
- validateCaptchaResult,
2701
- convertAvToBv,
2702
- convertBvToAv,
2703
- fetchEmojiList: fetchEmojiList$3
2704
- };
3768
+ async function checkPassportQrcode(options, cookie, requestConfig) {
3769
+ return run("passportQrcodeStatus", async () => {
3770
+ if (!options?.token) return passportError("passportQrcodeStatus", "缺少 token 参数");
3771
+ const client = new DouyinPassportClient(cookie, requestConfig);
3772
+ const response = await client.request("/passport/web/check_qrconnect/", {
3773
+ next: NEXT_URL,
3774
+ need_logo: "false",
3775
+ is_frontier: "true",
3776
+ token: options.token,
3777
+ is_new_login: "1",
3778
+ need_short_url: "true"
3779
+ });
3780
+ const result = parsePollResult(response.body);
3781
+ if (result.status === "verify") emitLogDebug(`[douyin passport] 触发二次验证,服务端原始响应: ${response.raw.slice(0, 1e3)}`);
3782
+ if (result.status === "confirmed" && result.redirectUrl) await client.followSsoRedirect(result.redirectUrl);
3783
+ const sessionCookie = result.status === "confirmed" ? client.cookies.toString() : client.cookies.serialize();
3784
+ return createSuccessResponse({
3785
+ ...result,
3786
+ cookie: sessionCookie,
3787
+ logged_in: client.cookies.isLoggedIn()
3788
+ }, "获取成功", 200);
3789
+ });
3790
+ }
3791
+ /**
3792
+ * 向账号绑定手机发送二次验证短信验证码
3793
+ *
3794
+ * 用于轮询返回 `status: 'verify'`(即 `error_code=2046` / `account_flow=verify`)的场景。
3795
+ * @param options - 发码参数
3796
+ * @param options.verify - 轮询返回的验证上下文
3797
+ * @param options.biz_trace_id - 追踪 ID (可选,不传自动生成)
3798
+ * @param cookie - 会话 Cookie
3799
+ * @param requestConfig - 请求配置 (可选)
3800
+ * @returns 脱敏手机号、重发等待秒数与追踪 ID
3801
+ */
3802
+ async function sendPassportVerifyCode(options, cookie, requestConfig) {
3803
+ return run("passportSendCode", async () => {
3804
+ if (!options?.verify?.encryptUid) return passportError("passportSendCode", "缺少 encrypt_uid,请从轮询响应中取得验证上下文");
3805
+ const bizTraceId = options.biz_trace_id ?? randomHex(8);
3806
+ const verifyWay = resolveVerifyWay(options.verify, options.verify_way);
3807
+ emitLogDebug(`[douyin passport] 发码使用的验证方式: ${verifyWay}`);
3808
+ const response = await new DouyinPassportClient(cookie, requestConfig).liteRequest("/passport/web/send_code/", buildVerifyBody(options.verify, verifyWay, { is6Digits: "1" }), bizTraceId);
3809
+ const result = parseSendCodeResult(response.body);
3810
+ if (!result.ok) emitLogDebug(`[douyin passport] 发码失败原文: ${response.raw.slice(0, 500)}`);
3811
+ return createSuccessResponse({
3812
+ ...result,
3813
+ cookie: response.cookie,
3814
+ biz_trace_id: bizTraceId,
3815
+ verify_way: verifyWay
3816
+ }, "获取成功", 200);
3817
+ });
3818
+ }
3819
+ /**
3820
+ * 提交二次验证的短信验证码
3821
+ * @param options - 验码参数
3822
+ * @param options.verify - 轮询返回的验证上下文
3823
+ * @param options.code - 用户收到的 6 位验证码明文
3824
+ * @param options.biz_trace_id - 必须与发码时用的是同一个
3825
+ * @param cookie - 会话 Cookie
3826
+ * @param requestConfig - 请求配置 (可选)
3827
+ * @returns 验证结果;`wrongCode` 为 true 表示验证码填错,可以让用户重试
3828
+ */
3829
+ async function validatePassportVerifyCode(options, cookie, requestConfig) {
3830
+ return run("passportValidateCode", async () => {
3831
+ if (!options?.verify?.encryptUid) return passportError("passportValidateCode", "缺少 encrypt_uid,请从轮询响应中取得验证上下文");
3832
+ if (!options.code) return passportError("passportValidateCode", "缺少 code,请填入收到的短信验证码");
3833
+ 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));
3834
+ const result = parseValidateCodeResult(response.body);
3835
+ if (!result.ok) emitLogDebug(`[douyin passport] 验码失败原文: ${response.raw.slice(0, 500)}`);
3836
+ return createSuccessResponse({
3837
+ ...result,
3838
+ cookie: response.cookie
3839
+ }, "获取成功", 200);
3840
+ });
3841
+ }
2705
3842
  //#endregion
2706
3843
  //#region src/platform/defaultConfigs.ts
2707
3844
  /**
@@ -2837,34 +3974,6 @@ const getXiaohongshuDefaultConfig = (cookie) => {
2837
3974
  } };
2838
3975
  };
2839
3976
  //#endregion
2840
- //#region src/types/NetworksConfigType.ts
2841
- /** 快手平台API错误码 */
2842
- let kuaishouAPIErrorCode = /* @__PURE__ */ function(kuaishouAPIErrorCode) {
2843
- /** Cookie无效或已过期 */
2844
- kuaishouAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
2845
- /** 未知错误 */
2846
- kuaishouAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
2847
- return kuaishouAPIErrorCode;
2848
- }({});
2849
- /** 小红书平台API错误码 */
2850
- let xiaohongshuAPIErrorCode = /* @__PURE__ */ function(xiaohongshuAPIErrorCode) {
2851
- /** Cookie无效或已过期 */
2852
- xiaohongshuAPIErrorCode["COOKIE"] = "INVALID_COOKIE";
2853
- /** 未知错误 */
2854
- xiaohongshuAPIErrorCode["UNKNOWN"] = "UNKNOWN_ERROR";
2855
- /** 非法请求 */
2856
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ILLEGAL_REQUEST"] = 500] = "ILLEGAL_REQUEST";
2857
- /** 检测到帐号异常,请稍后重试 */
2858
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["ACCOUNT_ABNORMAL"] = 300011] = "ACCOUNT_ABNORMAL";
2859
- /** 网络连接异常,请检查网络设置后重试 */
2860
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["NETWORK_ERROR"] = 300012] = "NETWORK_ERROR";
2861
- /** 访问频次异常,请勿频繁操作 */
2862
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["FREQUENCY_ERROR"] = 300013] = "FREQUENCY_ERROR";
2863
- /** 浏览器异常,请尝试更换浏览器后重试 */
2864
- xiaohongshuAPIErrorCode[xiaohongshuAPIErrorCode["BROWSER_ERROR"] = 300015] = "BROWSER_ERROR";
2865
- return xiaohongshuAPIErrorCode;
2866
- }({});
2867
- //#endregion
2868
3977
  //#region src/platform/douyin/sign/a_bogus.ts
2869
3978
  var SM3 = class {
2870
3979
  reg;
@@ -3066,8 +4175,6 @@ function result_encrypt(long_str, num) {
3066
4175
  case 3:
3067
4176
  temp_int = long_int & 63;
3068
4177
  result += constant["str"].charAt(temp_int);
3069
- break;
3070
- default: break;
3071
4178
  }
3072
4179
  }
3073
4180
  return result;
@@ -3569,7 +4676,8 @@ var DouyinAPI = class {
3569
4676
  }
3570
4677
  /** 获取视频或图集数据 */
3571
4678
  getWorkDetail(data) {
3572
- return `https://www.douyin.com/aweme/v1/web/aweme/detail/?${buildQueryString({
4679
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/aweme/detail/";
4680
+ const params = {
3573
4681
  ...this.getBaseParams(),
3574
4682
  aweme_id: data.aweme_id,
3575
4683
  update_version_code: "170400",
@@ -3579,11 +4687,13 @@ var DouyinAPI = class {
3579
4687
  screen_height: "1310",
3580
4688
  round_trip_time: "150",
3581
4689
  webid: "7351848354471872041"
3582
- })}`;
4690
+ };
4691
+ return `${baseUrl}?${buildQueryString(params)}`;
3583
4692
  }
3584
4693
  /** 获取评论数据 */
3585
4694
  getComments(data) {
3586
- return `https://www.douyin.com/aweme/v1/web/comment/list/?${buildQueryString({
4695
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/comment/list/";
4696
+ const params = {
3587
4697
  ...this.getBaseParams(),
3588
4698
  aweme_id: data.aweme_id,
3589
4699
  cursor: data.cursor ?? 0,
@@ -3598,11 +4708,13 @@ var DouyinAPI = class {
3598
4708
  screen_width: "1552",
3599
4709
  screen_height: "970",
3600
4710
  round_trip_time: "50"
3601
- })}`;
4711
+ };
4712
+ return `${baseUrl}?${buildQueryString(params)}`;
3602
4713
  }
3603
4714
  /** 获取二级评论数据 */
3604
4715
  getCommentReplies(data) {
3605
- return `https://www-hj.douyin.com/aweme/v1/web/comment/list/reply/?${buildQueryString({
4716
+ const baseUrl = "https://www-hj.douyin.com/aweme/v1/web/comment/list/reply/";
4717
+ const params = {
3606
4718
  device_platform: "webapp",
3607
4719
  aid: "6383",
3608
4720
  channel: "channel_pc_web",
@@ -3640,11 +4752,13 @@ var DouyinAPI = class {
3640
4752
  webid: "7487210762873685515",
3641
4753
  verifyFp: fp,
3642
4754
  fp
3643
- })}`;
4755
+ };
4756
+ return `${baseUrl}?${buildQueryString(params)}`;
3644
4757
  }
3645
4758
  /** 获取动图数据 */
3646
4759
  getSlidesInfo(data) {
3647
- return `https://www.iesdouyin.com/web/api/v2/aweme/slidesinfo/?${buildQueryString({
4760
+ const baseUrl = "https://www.iesdouyin.com/web/api/v2/aweme/slidesinfo/";
4761
+ const params = {
3648
4762
  reflow_source: "reflow_page",
3649
4763
  web_id: "7326472315356857893",
3650
4764
  device_id: "7326472315356857893",
@@ -3653,7 +4767,8 @@ var DouyinAPI = class {
3653
4767
  msToken: douyinSign.Mstoken(116),
3654
4768
  verifyFp: fp,
3655
4769
  fp
3656
- })}`;
4770
+ };
4771
+ return `${baseUrl}?${buildQueryString(params)}`;
3657
4772
  }
3658
4773
  /** 获取表情数据 */
3659
4774
  getEmojiList() {
@@ -3661,7 +4776,8 @@ var DouyinAPI = class {
3661
4776
  }
3662
4777
  /** 获取用户主页视频数据 */
3663
4778
  getUserVideoList(data) {
3664
- return `https://www.douyin.com/aweme/v1/web/aweme/post/?${buildQueryString({
4779
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/aweme/post/";
4780
+ const params = {
3665
4781
  ...this.getBaseParams(),
3666
4782
  sec_user_id: data.sec_uid,
3667
4783
  max_cursor: data.max_cursor ?? "0",
@@ -3679,11 +4795,13 @@ var DouyinAPI = class {
3679
4795
  screen_height: "970",
3680
4796
  round_trip_time: "50",
3681
4797
  webid: "7338423850134226495"
3682
- })}`;
4798
+ };
4799
+ return `${baseUrl}?${buildQueryString(params)}`;
3683
4800
  }
3684
4801
  /** 获取用户喜欢列表数据 */
3685
4802
  getUserFavoriteList(data) {
3686
- return `https://www-hj.douyin.com/aweme/v1/web/aweme/favorite/?${buildQueryString({
4803
+ const baseUrl = "https://www-hj.douyin.com/aweme/v1/web/aweme/favorite/";
4804
+ const params = {
3687
4805
  ...this.getBaseParams(),
3688
4806
  sec_user_id: data.sec_uid,
3689
4807
  max_cursor: data.max_cursor ?? "0",
@@ -3702,11 +4820,13 @@ var DouyinAPI = class {
3702
4820
  screen_height: "1310",
3703
4821
  round_trip_time: "0",
3704
4822
  webid: "7487210762873685515"
3705
- })}`;
4823
+ };
4824
+ return `${baseUrl}?${buildQueryString(params)}`;
3706
4825
  }
3707
4826
  /** 获取用户推荐列表数据 */
3708
4827
  getUserRecommendList(data) {
3709
- return `https://www.douyin.com/aweme/v1/web/familiar/recommend/feed/?${buildQueryString({
4828
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/familiar/recommend/feed/";
4829
+ const params = {
3710
4830
  device_platform: "",
3711
4831
  aid: "6383",
3712
4832
  channel: "channel_pc_web",
@@ -3745,11 +4865,13 @@ var DouyinAPI = class {
3745
4865
  msToken: douyinSign.Mstoken(184),
3746
4866
  verifyFp: fp,
3747
4867
  fp
3748
- })}`;
4868
+ };
4869
+ return `${baseUrl}?${buildQueryString(params)}`;
3749
4870
  }
3750
4871
  /** 获取用户主页信息 */
3751
4872
  getUserProfile(data) {
3752
- return `https://www.douyin.com/aweme/v1/web/user/profile/other/?${buildQueryString({
4873
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/user/profile/other/";
4874
+ const params = {
3753
4875
  ...this.getBaseParams(),
3754
4876
  publish_video_strategy_type: "2",
3755
4877
  source: "channel_pc_web",
@@ -3761,11 +4883,13 @@ var DouyinAPI = class {
3761
4883
  screen_height: "970",
3762
4884
  round_trip_time: "0",
3763
4885
  webid: "7327957959955580467"
3764
- })}`;
4886
+ };
4887
+ return `${baseUrl}?${buildQueryString(params)}`;
3765
4888
  }
3766
4889
  /** 获取热点词数据 */
3767
4890
  getSuggestWords(data) {
3768
- return `https://www.douyin.com/aweme/v1/web/api/suggest_words/?${buildQueryString({
4891
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/api/suggest_words/";
4892
+ const params = {
3769
4893
  ...this.getBaseParams(),
3770
4894
  query: data.query,
3771
4895
  business_id: "30088",
@@ -3776,91 +4900,103 @@ var DouyinAPI = class {
3776
4900
  screen_height: "970",
3777
4901
  round_trip_time: "50",
3778
4902
  webid: "7327957959955580467"
3779
- })}`;
4903
+ };
4904
+ return `${baseUrl}?${buildQueryString(params)}`;
3780
4905
  }
3781
4906
  /** 获取搜索数据 */
3782
4907
  search(data) {
3783
4908
  const searchType = data.type ?? "general";
3784
4909
  const { verifyFp, fp, ...baseParamsWithoutFp } = this.getBaseParams();
3785
- if (searchType === "user") return `https://www.douyin.com/aweme/v1/web/discover/search/?${buildQueryString({
3786
- ...baseParamsWithoutFp,
3787
- count: data.number ?? 10,
3788
- disable_rs: "0",
3789
- from_group_id: "",
3790
- is_filter_search: "0",
3791
- keyword: data.query,
3792
- list_type: "single",
3793
- need_filter_settings: "1",
3794
- offset: "0",
3795
- pc_libra_divert: "Windows",
3796
- pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
3797
- query_correct_type: "1",
3798
- round_trip_time: "250",
3799
- screen_height: "1310",
3800
- screen_width: "2328",
3801
- search_channel: "aweme_user_web",
3802
- search_source: "switch_tab",
3803
- support_dash: "1",
3804
- support_h265: "1",
3805
- version_code: "170400",
3806
- version_name: "17.4.0",
3807
- webid: "7521399115230610959",
3808
- ...data.search_id && { search_id: data.search_id }
3809
- })}`;
3810
- else if (searchType === "video") return `https://www.douyin.com/aweme/v1/web/search/item/?${buildQueryString({
3811
- ...baseParamsWithoutFp,
3812
- count: data.number ?? 10,
3813
- disable_rs: "0",
3814
- enable_history: "1",
3815
- from_group_id: "",
3816
- is_filter_search: "0",
3817
- keyword: data.query,
3818
- list_type: "single",
3819
- need_filter_settings: "1",
3820
- offset: "0",
3821
- pc_libra_divert: "Windows",
3822
- pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
3823
- query_correct_type: "1",
3824
- round_trip_time: "50",
3825
- screen_height: "1310",
3826
- screen_width: "2328",
3827
- search_channel: "aweme_video_web",
3828
- search_source: "switch_tab",
3829
- support_dash: "1",
3830
- support_h265: "1",
3831
- version_code: "170400",
3832
- version_name: "17.4.0",
3833
- webid: "7521399115230610959",
3834
- ...data.search_id && { search_id: data.search_id }
3835
- })}`;
3836
- else return `https://www.douyin.com/aweme/v1/web/general/search/stream/?${buildQueryString({
3837
- ...baseParamsWithoutFp,
3838
- count: data.number ?? 10,
3839
- disable_rs: "0",
3840
- enable_history: "1",
3841
- is_filter_search: "0",
3842
- keyword: data.query,
3843
- list_type: "",
3844
- need_filter_settings: "1",
3845
- offset: "0",
3846
- pc_libra_divert: "Windows",
3847
- pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
3848
- query_correct_type: "1",
3849
- round_trip_time: "0",
3850
- screen_height: "1310",
3851
- screen_width: "2328",
3852
- search_channel: "aweme_general",
3853
- search_source: "normal_search",
3854
- support_dash: "1",
3855
- support_h265: "1",
3856
- version_code: "190600",
3857
- version_name: "19.6.0",
3858
- webid: "7521399115230610959"
3859
- })}`;
4910
+ if (searchType === "user") {
4911
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/discover/search/";
4912
+ const params = {
4913
+ ...baseParamsWithoutFp,
4914
+ count: data.number ?? 10,
4915
+ disable_rs: "0",
4916
+ from_group_id: "",
4917
+ is_filter_search: "0",
4918
+ keyword: data.query,
4919
+ list_type: "single",
4920
+ need_filter_settings: "1",
4921
+ offset: "0",
4922
+ pc_libra_divert: "Windows",
4923
+ pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
4924
+ query_correct_type: "1",
4925
+ round_trip_time: "250",
4926
+ screen_height: "1310",
4927
+ screen_width: "2328",
4928
+ search_channel: "aweme_user_web",
4929
+ search_source: "switch_tab",
4930
+ support_dash: "1",
4931
+ support_h265: "1",
4932
+ version_code: "170400",
4933
+ version_name: "17.4.0",
4934
+ webid: "7521399115230610959",
4935
+ ...data.search_id && { search_id: data.search_id }
4936
+ };
4937
+ return `${baseUrl}?${buildQueryString(params)}`;
4938
+ } else if (searchType === "video") {
4939
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/search/item/";
4940
+ const params = {
4941
+ ...baseParamsWithoutFp,
4942
+ count: data.number ?? 10,
4943
+ disable_rs: "0",
4944
+ enable_history: "1",
4945
+ from_group_id: "",
4946
+ is_filter_search: "0",
4947
+ keyword: data.query,
4948
+ list_type: "single",
4949
+ need_filter_settings: "1",
4950
+ offset: "0",
4951
+ pc_libra_divert: "Windows",
4952
+ pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
4953
+ query_correct_type: "1",
4954
+ round_trip_time: "50",
4955
+ screen_height: "1310",
4956
+ screen_width: "2328",
4957
+ search_channel: "aweme_video_web",
4958
+ search_source: "switch_tab",
4959
+ support_dash: "1",
4960
+ support_h265: "1",
4961
+ version_code: "170400",
4962
+ version_name: "17.4.0",
4963
+ webid: "7521399115230610959",
4964
+ ...data.search_id && { search_id: data.search_id }
4965
+ };
4966
+ return `${baseUrl}?${buildQueryString(params)}`;
4967
+ } else {
4968
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/general/search/stream/";
4969
+ const params = {
4970
+ ...baseParamsWithoutFp,
4971
+ count: data.number ?? 10,
4972
+ disable_rs: "0",
4973
+ enable_history: "1",
4974
+ is_filter_search: "0",
4975
+ keyword: data.query,
4976
+ list_type: "",
4977
+ need_filter_settings: "1",
4978
+ offset: "0",
4979
+ pc_libra_divert: "Windows",
4980
+ pc_search_top_1_params: "{\"enable_ai_search_top_1\":1}",
4981
+ query_correct_type: "1",
4982
+ round_trip_time: "0",
4983
+ screen_height: "1310",
4984
+ screen_width: "2328",
4985
+ search_channel: "aweme_general",
4986
+ search_source: "normal_search",
4987
+ support_dash: "1",
4988
+ support_h265: "1",
4989
+ version_code: "190600",
4990
+ version_name: "19.6.0",
4991
+ webid: "7521399115230610959"
4992
+ };
4993
+ return `${baseUrl}?${buildQueryString(params)}`;
4994
+ }
3860
4995
  }
3861
4996
  /** 获取互动表情数据 */
3862
4997
  getDynamicEmojiList() {
3863
- return `https://www.douyin.com/aweme/v1/web/im/strategy/config?${buildQueryString({
4998
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/im/strategy/config";
4999
+ const params = {
3864
5000
  device_platform: "webapp",
3865
5001
  aid: "1128",
3866
5002
  channel: "channel_pc_web",
@@ -3892,11 +5028,13 @@ var DouyinAPI = class {
3892
5028
  msToken: douyinSign.Mstoken(116),
3893
5029
  verifyFp: fp,
3894
5030
  fp
3895
- })}`;
5031
+ };
5032
+ return `${baseUrl}?${buildQueryString(params)}`;
3896
5033
  }
3897
5034
  /** 获取背景音乐数据 */
3898
5035
  getMusicInfo(data) {
3899
- return `https://www.douyin.com/aweme/v1/web/music/detail/?${buildQueryString({
5036
+ const baseUrl = "https://www.douyin.com/aweme/v1/web/music/detail/";
5037
+ const params = {
3900
5038
  device_platform: "webapp",
3901
5039
  aid: "6383",
3902
5040
  channel: "channel_pc_web",
@@ -3927,11 +5065,13 @@ var DouyinAPI = class {
3927
5065
  msToken: douyinSign.Mstoken(116),
3928
5066
  verifyFp: fp,
3929
5067
  fp
3930
- })}`;
5068
+ };
5069
+ return `${baseUrl}?${buildQueryString(params)}`;
3931
5070
  }
3932
5071
  /** 获取直播间信息 */
3933
5072
  getLiveRoomInfo(data) {
3934
- return `https://live.douyin.com/webcast/room/web/enter/?${buildQueryString({
5073
+ const baseUrl = "https://live.douyin.com/webcast/room/web/enter/";
5074
+ const params = {
3935
5075
  aid: "6383",
3936
5076
  app_name: "douyin_web",
3937
5077
  live_id: "1",
@@ -3954,18 +5094,22 @@ var DouyinAPI = class {
3954
5094
  msToken: douyinSign.Mstoken(116),
3955
5095
  verifyFp: fp,
3956
5096
  fp
3957
- })}`;
5097
+ };
5098
+ return `${baseUrl}?${buildQueryString(params)}`;
3958
5099
  }
3959
5100
  /** 申请登录二维码 */
3960
5101
  getLoginQrcode(data) {
3961
- return `https://sso.douyin.com/get_qrcode/?${buildQueryString({
5102
+ const baseUrl = "https://sso.douyin.com/get_qrcode/";
5103
+ const params = {
3962
5104
  verifyFp: data.verify_fp,
3963
5105
  fp: data.verify_fp
3964
- })}`;
5106
+ };
5107
+ return `${baseUrl}?${buildQueryString(params)}`;
3965
5108
  }
3966
5109
  /** 获取弹幕数据 */
3967
5110
  getDanmakuList(data) {
3968
- return `https://www-hj.douyin.com/aweme/v1/web/danmaku/get_v2/?${buildQueryString({
5111
+ const baseUrl = "https://www-hj.douyin.com/aweme/v1/web/danmaku/get_v2/";
5112
+ const params = {
3969
5113
  ...this.getBaseParams(),
3970
5114
  app_name: "aweme",
3971
5115
  format: "json",
@@ -3992,7 +5136,8 @@ var DouyinAPI = class {
3992
5136
  msToken: douyinSign.Mstoken(116),
3993
5137
  verifyFp: fp,
3994
5138
  fp
3995
- })}`;
5139
+ };
5140
+ return `${baseUrl}?${buildQueryString(params)}`;
3996
5141
  }
3997
5142
  };
3998
5143
  /**
@@ -4014,7 +5159,6 @@ const douyinApiUrls = new DouyinAPI();
4014
5159
  * 提供抖音各类数据的获取功能,包括视频、评论、用户等
4015
5160
  *
4016
5161
  * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
4017
- * 循环依赖链:DataFetchers → getdata → platform/douyin → DataFetchers
4018
5162
  *
4019
5163
  * @module platform/douyin/getdata
4020
5164
  */
@@ -4291,7 +5435,8 @@ const DouyinData = async (data, cookie, requestConfig) => {
4291
5435
  signType: null,
4292
5436
  processRawResponse: (raw) => {
4293
5437
  if (!isUserSearch && !isVideoSearch) {
4294
- const responses = filterSearchResponses(typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw]);
5438
+ const chunks = typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw];
5439
+ const responses = filterSearchResponses(chunks);
4295
5440
  if (responses.length === 0) return raw;
4296
5441
  const mergedData = [];
4297
5442
  let lastValid = {};
@@ -4670,7 +5815,8 @@ const filterSearchResponses = (objs) => {
4670
5815
  async function fetchDouyinInternal(methodType, options, config) {
4671
5816
  const startTime = Date.now();
4672
5817
  try {
4673
- const rawData = await DouyinData({ ...validateDouyinParams(methodType, options) }, config.cookie, config.requestConfig);
5818
+ const apiParams = { ...validateDouyinParams(methodType, options) };
5819
+ const rawData = await DouyinData(apiParams, config.cookie, config.requestConfig);
4674
5820
  const duration = Date.now() - startTime;
4675
5821
  if (rawData.data === "" || rawData.status_code !== 0) {
4676
5822
  emitApiError({
@@ -5151,6 +6297,10 @@ function createBoundDouyinFetcher(cookie, requestConfig) {
5151
6297
  * ```
5152
6298
  */
5153
6299
  const douyinFetcher = {
6300
+ requestPassportQrcode,
6301
+ checkPassportQrcode,
6302
+ sendPassportVerifyCode,
6303
+ validatePassportVerifyCode,
5154
6304
  fetchVideoWork: fetchVideoWork$1,
5155
6305
  fetchImageAlbumWork,
5156
6306
  fetchSlidesWork,
@@ -5278,11 +6428,13 @@ var API = class {
5278
6428
  * @returns 请求配置
5279
6429
  */
5280
6430
  profilePublic(data) {
6431
+ const count = "count" in data ? data.count ?? 12 : 12;
6432
+ const pcursor = "pcursor" in data ? data.pcursor ?? "" : "";
5281
6433
  return createKuaishouLiveApiRequest("profilePublic", "/live_api/profile/public", {
5282
6434
  caver: 2,
5283
- count: "count" in data ? data.count ?? 12 : 12,
6435
+ count,
5284
6436
  hasMore: true,
5285
- pcursor: "pcursor" in data ? data.pcursor ?? "" : "",
6437
+ pcursor,
5286
6438
  principalId: data.principalId,
5287
6439
  privacy: "public"
5288
6440
  }, { signPath: "/rest/k/feed/profile" });
@@ -5446,6 +6598,7 @@ var API = class {
5446
6598
  * @returns 请求配置
5447
6599
  */
5448
6600
  liveReco(gameId) {
6601
+ const normalizedGameId = Number(gameId) > 0 ? Number(gameId) : 1001;
5449
6602
  return createKuaishouLiveApiRequest("liveReco", "/live_api/liveroom/reco", {}, {
5450
6603
  method: "POST",
5451
6604
  requiresSign: false,
@@ -5455,7 +6608,7 @@ var API = class {
5455
6608
  followingWeight: 50
5456
6609
  },
5457
6610
  gameFavour: [{
5458
- gameId: Number(gameId) > 0 ? Number(gameId) : 1001,
6611
+ gameId: normalizedGameId,
5459
6612
  totalStayLength: 100
5460
6613
  }]
5461
6614
  }
@@ -5688,8 +6841,10 @@ const maskKuaishouHudrPayload = (payload) => {
5688
6841
  * @returns `HUDR_` 的完整结果及若干中间态,便于对拍与调试
5689
6842
  */
5690
6843
  const deriveKuaishouHudrBody = (context) => {
5691
- const maskedPayload = maskKuaishouHudrPayload(buildKuaishouHudrPayload(context));
5692
- const body = encodeBase64Url(new KuaishouChaChaCipher(KUAISHOU_HUDR_CHACHA_KEY, KUAISHOU_HUDR_CHACHA_NONCE).encrypt(maskedPayload));
6844
+ const payload = buildKuaishouHudrPayload(context);
6845
+ const maskedPayload = maskKuaishouHudrPayload(payload);
6846
+ const encrypted = new KuaishouChaChaCipher(KUAISHOU_HUDR_CHACHA_KEY, KUAISHOU_HUDR_CHACHA_NONCE).encrypt(maskedPayload);
6847
+ const body = encodeBase64Url(encrypted);
5693
6848
  return {
5694
6849
  body,
5695
6850
  full: `${KUAISHOU_HUDR_PREFIX}${body}`,
@@ -6201,7 +7356,9 @@ const KUAISHOU_HE_RANDOM_MAX = 0xffffffffffff;
6201
7356
  * @returns `$HE_` 载荷中的 4 字节 hash field hex
6202
7357
  */
6203
7358
  const deriveKuaishouHeHashFieldHex = (signInput, hudrBody) => {
6204
- return bytesToLowerHex(xorByteArrays(hexToSignedBytes(bytesToLowerHex(deriveKuaishouCts(deriveKuaishouB2sa(`${signInput}HUDR_${hudrBody}`))).slice(0, 8)), KUAISHOU_HE_INPUT_XOR_MASK));
7359
+ const hashInput = `${signInput}HUDR_${hudrBody}`;
7360
+ const digestHex = bytesToLowerHex(deriveKuaishouCts(deriveKuaishouB2sa(hashInput))).slice(0, 8);
7361
+ return bytesToLowerHex(xorByteArrays(hexToSignedBytes(digestHex), KUAISHOU_HE_INPUT_XOR_MASK));
6205
7362
  };
6206
7363
  /**
6207
7364
  * 推导快手签名中的 `$HE_` 段。
@@ -6541,7 +7698,6 @@ var kuaishouSign = class {
6541
7698
  * 快手数据获取模块
6542
7699
  *
6543
7700
  * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
6544
- * 循环依赖链:DataFetchers → getdata → platform/kuaishou → DataFetchers
6545
7701
  */
6546
7702
  const KUAISHOU_PROFILE_TAB_TYPE_MAP = {
6547
7703
  public: "public",
@@ -7223,7 +8379,8 @@ const KuaishouData = async (data, cookie, requestConfig) => {
7223
8379
  if (!liveDetailData) return liveRoomInfo;
7224
8380
  const userInfo = isErrorDetailLike(userInfoPayload) ? void 0 : userInfoPayload?.data?.userInfo;
7225
8381
  const sensitiveInfo = isErrorDetailLike(sensitivePayload) ? void 0 : sensitivePayload?.data?.sensitiveUserInfo;
7226
- const currentLiveRoomItem = mapLiveDetailToLiveRoomPlayItem(liveDetailData, mergeKuaishouLiveAuthor(liveDetailData?.author, userInfo, sensitiveInfo));
8382
+ const currentAuthor = mergeKuaishouLiveAuthor(liveDetailData?.author, userInfo, sensitiveInfo);
8383
+ const currentLiveRoomItem = mapLiveDetailToLiveRoomPlayItem(liveDetailData, currentAuthor);
7227
8384
  const liveStreamId = currentLiveRoomItem.liveStream?.id ?? currentLiveRoomItem.config?.liveStreamId;
7228
8385
  const currentGameId = liveDetailData?.gameInfo?.id ?? liveDetailData?.gameInfo?.gameId;
7229
8386
  const liveDetailWebsocketMeta = resolveKuaishouLiveDetailWebsocketMeta(liveDetailData);
@@ -7241,7 +8398,8 @@ const KuaishouData = async (data, cookie, requestConfig) => {
7241
8398
  shouldFetchRecommendList ? fetchKuaishouLiveApiPayload(data.methodType, kuaishouApiUrls.liveReco(currentGameId), refererPath, { allowResult2: true }) : Promise.resolve(null)
7242
8399
  ]);
7243
8400
  const resolvedRecommendList = !isErrorDetailLike(recoPayload) && Array.isArray(recoPayload?.data?.list) ? recoPayload.data.list : liveDetailRecommendList;
7244
- const nextPlayList = dedupeLiveRoomPlayList([currentLiveRoomItem, ...Array.isArray(resolvedRecommendList) ? resolvedRecommendList.map((item) => mapRecoItemToLiveRoomPlayItem(item)) : []]);
8401
+ const recoPlayList = Array.isArray(resolvedRecommendList) ? resolvedRecommendList.map((item) => mapRecoItemToLiveRoomPlayItem(item)) : [];
8402
+ const nextPlayList = dedupeLiveRoomPlayList([currentLiveRoomItem, ...recoPlayList]);
7245
8403
  return {
7246
8404
  ...liveRoomInfo,
7247
8405
  principalId: data.principalId,
@@ -7352,7 +8510,8 @@ const GlobalGetData$2 = async (type, options, config) => {
7352
8510
  async function fetchKuaishouInternal(methodType, options, config) {
7353
8511
  const startTime = Date.now();
7354
8512
  try {
7355
- const rawData = await KuaishouData({ ...validateKuaishouParams(methodType, options) }, config.cookie, config.requestConfig);
8513
+ const apiParams = { ...validateKuaishouParams(methodType, options) };
8514
+ const rawData = await KuaishouData(apiParams, config.cookie, config.requestConfig);
7356
8515
  const duration = Date.now() - startTime;
7357
8516
  if (rawData.code && Object.values(kuaishouAPIErrorCode).includes(rawData.code)) {
7358
8517
  emitApiError({
@@ -7611,18 +8770,19 @@ const XiaohongshuData = async (data, cookie, requestConfig) => {
7611
8770
  ...requestConfig?.headers ?? {}
7612
8771
  }
7613
8772
  };
8773
+ const userData = await GlobalGetData$1(data.methodType, {
8774
+ ...baseRequestConfig,
8775
+ url: xiaohongshuApiUrls.userProfile(data).Url,
8776
+ headers: {
8777
+ ...baseRequestConfig.headers,
8778
+ "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls.userProfile(data).apiPath, xiaohongshuSign.extractA1FromCookie(requestCookie), "xhs-pc-web"),
8779
+ "x-s-common": xiaohongshuSign.generateXSCommon(requestCookie),
8780
+ "x-t": xiaohongshuSign.generateXT()
8781
+ }
8782
+ });
7614
8783
  return {
7615
8784
  code: 0,
7616
- data: extractCreatorInfoFromHtml(await GlobalGetData$1(data.methodType, {
7617
- ...baseRequestConfig,
7618
- url: xiaohongshuApiUrls.userProfile(data).Url,
7619
- headers: {
7620
- ...baseRequestConfig.headers,
7621
- "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls.userProfile(data).apiPath, xiaohongshuSign.extractA1FromCookie(requestCookie), "xhs-pc-web"),
7622
- "x-s-common": xiaohongshuSign.generateXSCommon(requestCookie),
7623
- "x-t": xiaohongshuSign.generateXT()
7624
- }
7625
- })),
8785
+ data: extractCreatorInfoFromHtml(userData),
7626
8786
  msg: "success"
7627
8787
  };
7628
8788
  }
@@ -7736,7 +8896,8 @@ const sortTypeMapping = {
7736
8896
  async function fetchXiaohongshuInternal(methodType, options, config) {
7737
8897
  const startTime = Date.now();
7738
8898
  try {
7739
- const rawData = await XiaohongshuData({ ...validateXiaohongshuParams(methodType, options) }, config.cookie, config.requestConfig);
8899
+ const apiParams = { ...validateXiaohongshuParams(methodType, options) };
8900
+ const rawData = await XiaohongshuData(apiParams, config.cookie, config.requestConfig);
7740
8901
  const duration = Date.now() - startTime;
7741
8902
  if (rawData.code && Object.values(xiaohongshuAPIErrorCode).includes(rawData.code)) {
7742
8903
  emitApiError({
@@ -8160,106 +9321,6 @@ const getHeadersAndData = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
8160
9321
  };
8161
9322
  };
8162
9323
  //#endregion
8163
- //#region src/model/logger.ts
8164
- /**
8165
- * @deprecated v6 已废弃日志模块,请使用事件系统替代
8166
- * @see {@link ../events.ts} 使用 amagiEvents 监听日志事件
8167
- *
8168
- * 迁移示例:
8169
- * ```typescript
8170
- * import { amagiEvents } from '@ikenxuan/amagi'
8171
- *
8172
- * amagiEvents.on('log:info', (data) => console.log(data.message))
8173
- * amagiEvents.on('log:error', (data) => console.error(data.message))
8174
- * ```
8175
- */
8176
- /**
8177
- * @deprecated v6 已废弃,请使用事件系统替代
8178
- * 初始化 logger 配置 - 此函数现在为空操作
8179
- */
8180
- const initLogger = () => {};
8181
- /**
8182
- * @deprecated v6 已废弃,请使用事件系统替代
8183
- * 简化的日志类,仅发射事件,不再依赖 log4js
8184
- */
8185
- var SimpleLogger = class {
8186
- chalk;
8187
- red;
8188
- green;
8189
- yellow;
8190
- blue;
8191
- magenta;
8192
- cyan;
8193
- white;
8194
- gray;
8195
- constructor() {
8196
- this.chalk = new chalk.Chalk();
8197
- this.red = this.chalk.red;
8198
- this.green = this.chalk.green;
8199
- this.yellow = this.chalk.yellow;
8200
- this.blue = this.chalk.blue;
8201
- this.magenta = this.chalk.magenta;
8202
- this.cyan = this.chalk.cyan;
8203
- this.white = this.chalk.white;
8204
- this.gray = this.chalk.gray;
8205
- }
8206
- info(message, ...args) {
8207
- emitLog("info", String(message), ...args);
8208
- }
8209
- warn(message, ...args) {
8210
- emitLog("warn", String(message), ...args);
8211
- }
8212
- error(message, ...args) {
8213
- emitLog("error", String(message), ...args);
8214
- }
8215
- mark(message, ...args) {
8216
- emitLog("mark", String(message), ...args);
8217
- }
8218
- debug(message, ...args) {
8219
- emitLog("debug", String(message), ...args);
8220
- }
8221
- };
8222
- /**
8223
- * @deprecated v6 已废弃,请使用事件系统替代
8224
- */
8225
- const logger = new SimpleLogger();
8226
- /**
8227
- * @deprecated v6 已废弃,请使用事件系统替代
8228
- */
8229
- const httpLogger = new SimpleLogger();
8230
- /**
8231
- * @deprecated v6 已废弃,请使用事件系统监听 http:response 事件
8232
- * 创建一个日志中间件,用于记录特定请求的详细信息
8233
- * @param pathsToLog 指定需要记录日志的请求路径数组如果未提供,则记录所有请求的日志
8234
- * @returns
8235
- */
8236
- const logMiddleware = (pathsToLog) => {
8237
- return (req, res, next) => {
8238
- if (!pathsToLog || pathsToLog.some((path) => req.url.startsWith(path))) {
8239
- const startTime = Date.now();
8240
- const url = req.url;
8241
- const method = req.method;
8242
- const clientIP = req.headers["x-forwarded-for"] ?? req.socket.remoteAddress;
8243
- res.on("finish", () => {
8244
- const responseTime = Date.now() - startTime;
8245
- const statusCode = res.statusCode;
8246
- const requestSize = req.headers["content-length"] ?? "0";
8247
- const responseSize = res.get("content-length") ?? "0";
8248
- emitHttpResponse({
8249
- method,
8250
- url,
8251
- statusCode,
8252
- responseTime,
8253
- clientIP: String(clientIP),
8254
- requestSize: `${requestSize}B`,
8255
- responseSize: `${responseSize}B`
8256
- });
8257
- });
8258
- }
8259
- next();
8260
- };
8261
- };
8262
- //#endregion
8263
9324
  //#region src/platform/bilibili/qtparam.ts
8264
9325
  /**
8265
9326
  * 生成B站视频流请求参数
@@ -8578,7 +9639,6 @@ const wbi_sign = async (BASEURL, cookie) => {
8578
9639
  * 提供 B站各类数据的获取功能,包括视频、评论、用户、番剧等
8579
9640
  *
8580
9641
  * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
8581
- * 循环依赖链:DataFetchers → getdata → platform/bilibili → DataFetchers
8582
9642
  *
8583
9643
  * @module platform/bilibili/getdata
8584
9644
  */
@@ -8598,10 +9658,11 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8598
9658
  url: bilibiliApiUrls.getVideoInfo({ bvid: data.bvid })
8599
9659
  });
8600
9660
  case "videoStream": {
8601
- const sign = await qtparam(bilibiliApiUrls.getVideoStream({
9661
+ const baseUrl = bilibiliApiUrls.getVideoStream({
8602
9662
  avid: data.avid,
8603
9663
  cid: data.cid
8604
- }), baseRequestConfig.headers?.Cookie);
9664
+ });
9665
+ const sign = await qtparam(baseUrl, baseRequestConfig.headers?.Cookie);
8605
9666
  return await GlobalGetData(data.methodType, {
8606
9667
  ...baseRequestConfig,
8607
9668
  url: bilibiliApiUrls.getVideoStream({
@@ -8688,10 +9749,11 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8688
9749
  });
8689
9750
  }
8690
9751
  case "bangumiStream": {
8691
- const sign = await qtparam(bilibiliApiUrls.getBangumiStream({
9752
+ const baseUrl = bilibiliApiUrls.getBangumiStream({
8692
9753
  cid: data.cid,
8693
9754
  ep_id: data.ep_id.replace("ep", "")
8694
- }), baseRequestConfig.headers?.cookie);
9755
+ });
9756
+ const sign = await qtparam(baseUrl, baseRequestConfig.headers?.cookie);
8695
9757
  return await GlobalGetData(data.methodType, {
8696
9758
  ...baseRequestConfig,
8697
9759
  url: bilibiliApiUrls.getBangumiStream({
@@ -8727,12 +9789,6 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8727
9789
  url: bilibiliApiUrls.getDynamicDetail({ dynamic_id: data.dynamic_id })
8728
9790
  });
8729
9791
  }
8730
- case "dynamicCard": return {
8731
- code: -404,
8732
- message: "接口已停用:B站官方已于 `2025-08-09` 删除 dynamic_svr 接口,fetchDynamicCard 方法已废弃,调用讲返回错误信息",
8733
- ttl: 1,
8734
- data: null
8735
- };
8736
9792
  case "userCard": {
8737
9793
  const { host_mid } = data;
8738
9794
  return await GlobalGetData(data.methodType, {
@@ -8745,7 +9801,8 @@ const fetchBilibili = async (data, cookie, requestConfig) => {
8745
9801
  url: bilibiliApiUrls.getUserLiveStatus({ host_mid: data.host_mid })
8746
9802
  });
8747
9803
  case "userSpaceInfo": {
8748
- const wbiSignQuery = await wbi_sign(bilibiliApiUrls.getUserSpaceInfo({ host_mid: data.host_mid }), baseRequestConfig.headers?.cookie);
9804
+ const baseUrl = bilibiliApiUrls.getUserSpaceInfo({ host_mid: data.host_mid });
9805
+ const wbiSignQuery = await wbi_sign(baseUrl, baseRequestConfig.headers?.cookie);
8749
9806
  return await GlobalGetData(data.methodType, {
8750
9807
  ...baseRequestConfig,
8751
9808
  url: bilibiliApiUrls.getUserSpaceInfo({ host_mid: data.host_mid }) + wbiSignQuery
@@ -9096,10 +10153,11 @@ var ValidationError = class ValidationError extends Error {
9096
10153
  * @returns 验证错误实例
9097
10154
  */
9098
10155
  static fromZodError(zodError, requestPath) {
9099
- return new ValidationError("参数验证失败", zodError.issues.map((err) => ({
10156
+ const errors = zodError.issues.map((err) => ({
9100
10157
  field: err.path.join("."),
9101
10158
  message: err.message
9102
- })), requestPath);
10159
+ }));
10160
+ return new ValidationError("参数验证失败", errors, requestPath);
9103
10161
  }
9104
10162
  };
9105
10163
  /**
@@ -9123,7 +10181,10 @@ const handleError = (error, requestPath) => {
9123
10181
  platform: error.platform,
9124
10182
  requestPath
9125
10183
  };
9126
- if (error instanceof zod.default.ZodError) return handleError(ValidationError.fromZodError(error, requestPath), requestPath);
10184
+ if (error instanceof zod.default.ZodError) {
10185
+ const validationError = ValidationError.fromZodError(error, requestPath);
10186
+ return handleError(validationError, requestPath);
10187
+ }
9127
10188
  return {
9128
10189
  code: 500,
9129
10190
  message: error instanceof Error ? error.message : "未知错误",
@@ -9234,71 +10295,7 @@ const bilibiliUtils = {
9234
10295
  bv2av
9235
10296
  },
9236
10297
  danmaku: { parseDmSegMobileReply },
9237
- bilibiliApiUrls,
9238
- api: bilibili
9239
- };
9240
- //#endregion
9241
- //#region src/platform/douyin/DouyinApi.ts
9242
- /**
9243
- * 创建废弃的 API 存根函数
9244
- */
9245
- const createDeprecatedStub$2 = (methodName) => {
9246
- return (..._args) => {
9247
- checkDeprecation("getDouyinData");
9248
- throw new Error(`douyin.${methodName} 已废弃,请使用 douyinFetcher 替代`);
9249
- };
9250
- };
9251
- /**
9252
- * 封装了所有抖音相关的API请求,采用对象化的方式组织。
9253
- *
9254
- * @deprecated v6 已废弃,请使用 douyinFetcher 或 client.douyin.fetcher 替代
9255
- */
9256
- const douyin = {
9257
- /** @deprecated 请使用 douyinFetcher.fetchTextWork 替代 */
9258
- getTextWorkInfo: createDeprecatedStub$2("getTextWorkInfo"),
9259
- /** @deprecated 请使用 douyinFetcher.parseWork 替代 */
9260
- getWorkInfo: createDeprecatedStub$2("getWorkInfo"),
9261
- /** @deprecated 请使用 douyinFetcher.fetchVideoWork 替代 */
9262
- getVideoWorkInfo: createDeprecatedStub$2("getVideoWorkInfo"),
9263
- /** @deprecated 请使用 douyinFetcher.fetchImageAlbumWork 替代 */
9264
- getImageAlbumWorkInfo: createDeprecatedStub$2("getImageAlbumWorkInfo"),
9265
- /** @deprecated 请使用 douyinFetcher.fetchSlidesWork 替代 */
9266
- getSlidesWorkInfo: createDeprecatedStub$2("getSlidesWorkInfo"),
9267
- /** @deprecated 请使用 douyinFetcher.fetchComments 替代 */
9268
- getComments: createDeprecatedStub$2("getComments"),
9269
- /** @deprecated 请使用 douyinFetcher.fetchCommentReplies 替代 */
9270
- getCommentReplies: createDeprecatedStub$2("getCommentReplies"),
9271
- /** @deprecated 请使用 douyinFetcher.fetchUserProfile 替代 */
9272
- getUserProfile: createDeprecatedStub$2("getUserProfile"),
9273
- /** @deprecated 请使用 douyinFetcher.fetchEmojiList 替代 */
9274
- getEmojiList: createDeprecatedStub$2("getEmojiList"),
9275
- /** @deprecated 请使用 douyinFetcher.fetchDynamicEmojiList 替代 */
9276
- getEmojiProList: createDeprecatedStub$2("getEmojiProList"),
9277
- /** @deprecated 请使用 douyinFetcher.fetchUserVideoList 替代 */
9278
- getUserVideos: createDeprecatedStub$2("getUserVideos"),
9279
- /** @deprecated 请使用 douyinFetcher.fetchMusicInfo 替代 */
9280
- getMusicInfo: createDeprecatedStub$2("getMusicInfo"),
9281
- /** @deprecated 请使用 douyinFetcher.fetchSuggestWords 替代 */
9282
- getSuggestWords: createDeprecatedStub$2("getSuggestWords"),
9283
- /** @deprecated 请使用 douyinFetcher.searchContent 替代 */
9284
- search: createDeprecatedStub$2("search"),
9285
- /** @deprecated 请使用 douyinFetcher.fetchLiveRoomInfo 替代 */
9286
- getLiveRoomInfo: createDeprecatedStub$2("getLiveRoomInfo"),
9287
- /** @deprecated 请使用 douyinFetcher.fetchDanmakuList 替代 */
9288
- getDanmaku: createDeprecatedStub$2("getDanmaku"),
9289
- /** @deprecated 请使用 douyinFetcher 的具体方法替代 */
9290
- invoke: createDeprecatedStub$2("invoke")
9291
- };
9292
- /**
9293
- * 创建绑定了cookie的抖音API对象
9294
- *
9295
- * @deprecated v6 已废弃,请使用 createBoundDouyinFetcher 替代
9296
- */
9297
- const createBoundDouyinApi = (_cookie, _requestConfig) => {
9298
- return {
9299
- ...douyin,
9300
- getSearchData: createDeprecatedStub$2("getSearchData")
9301
- };
10298
+ bilibiliApiUrls
9302
10299
  };
9303
10300
  //#endregion
9304
10301
  //#region src/platform/douyin/routes.ts
@@ -9352,46 +10349,8 @@ const createDouyinRoutes = (cookie, requestConfig = getDouyinDefaultConfig(cooki
9352
10349
  /** 抖音相关功能模块 (工具集) */
9353
10350
  const douyinUtils = {
9354
10351
  sign: douyinSign,
9355
- douyinApiUrls,
9356
- api: douyin
9357
- };
9358
- //#endregion
9359
- //#region src/platform/kuaishou/KuaishouApi.ts
9360
- /**
9361
- * 创建废弃的 API 存根函数
9362
- */
9363
- const createDeprecatedStub$1 = (methodName) => {
9364
- return (..._args) => {
9365
- checkDeprecation("getKuaishouData");
9366
- throw new Error(`kuaishou.${methodName} 已废弃,请使用 kuaishouFetcher 替代`);
9367
- };
9368
- };
9369
- /**
9370
- * 快手相关 API 的命名空间。
9371
- *
9372
- * @deprecated v6 已废弃,请使用 kuaishouFetcher 或 client.kuaishou.fetcher 替代
9373
- */
9374
- const kuaishou = {
9375
- /** @deprecated 请使用 kuaishouFetcher.fetchVideoWork 替代 */
9376
- getWorkInfo: createDeprecatedStub$1("getWorkInfo"),
9377
- /** @deprecated 请使用 kuaishouFetcher.fetchWorkComments 替代 */
9378
- getComments: createDeprecatedStub$1("getComments"),
9379
- /** @deprecated 请使用 kuaishouFetcher.fetchUserProfile 替代 */
9380
- getUserProfile: createDeprecatedStub$1("getUserProfile"),
9381
- /** @deprecated 请使用 kuaishouFetcher.fetchUserWorkList 替代 */
9382
- getUserWorkList: createDeprecatedStub$1("getUserWorkList"),
9383
- /** @deprecated 请使用 kuaishouFetcher.fetchLiveRoomInfo 替代 */
9384
- getLiveRoomInfo: createDeprecatedStub$1("getLiveRoomInfo"),
9385
- /** @deprecated 请使用 kuaishouFetcher.fetchEmojiList 替代 */
9386
- getEmojiList: createDeprecatedStub$1("getEmojiList")
9387
- };
9388
- /**
9389
- * 创建绑定了cookie的快手API对象
9390
- *
9391
- * @deprecated v6 已废弃,请使用 createBoundKuaishouFetcher 替代
9392
- */
9393
- const createBoundKuaishouApi = (_cookie, _requestConfig) => {
9394
- return { ...kuaishou };
10352
+ passport: passport_exports,
10353
+ douyinApiUrls
9395
10354
  };
9396
10355
  //#endregion
9397
10356
  //#region src/platform/kuaishou/routes.ts
@@ -9445,48 +10404,7 @@ const createKuaishouRoutes = (cookie, requestConfig = getKuaishouDefaultConfig(c
9445
10404
  /** 快手相关功能模块 (工具集) */
9446
10405
  const kuaishouUtils = {
9447
10406
  sign: kuaishouSign,
9448
- kuaishouApiUrls,
9449
- api: kuaishou
9450
- };
9451
- //#endregion
9452
- //#region src/platform/xiaohongshu/XiaohongshuApi.ts
9453
- /**
9454
- * 创建废弃的 API 存根函数
9455
- */
9456
- const createDeprecatedStub = (methodName) => {
9457
- return (..._args) => {
9458
- checkDeprecation("getXiaohongshuData");
9459
- throw new Error(`xiaohongshu.${methodName} 已废弃,请使用 xiaohongshuFetcher 替代`);
9460
- };
9461
- };
9462
- /**
9463
- * 封装了所有小红书相关的API请求,采用对象化的方式组织。
9464
- *
9465
- * @deprecated v6 已废弃,请使用 xiaohongshuFetcher 或 client.xiaohongshu.fetcher 替代
9466
- */
9467
- const xiaohongshu = {
9468
- /** @deprecated 请使用 xiaohongshuFetcher.fetchHomeFeed 替代 */
9469
- getHomeFeed: createDeprecatedStub("getHomeFeed"),
9470
- /** @deprecated 请使用 xiaohongshuFetcher.fetchNoteDetail 替代 */
9471
- getNote: createDeprecatedStub("getNote"),
9472
- /** @deprecated 请使用 xiaohongshuFetcher.fetchNoteComments 替代 */
9473
- getComments: createDeprecatedStub("getComments"),
9474
- /** @deprecated 请使用 xiaohongshuFetcher.fetchUserProfile 替代 */
9475
- getUser: createDeprecatedStub("getUser"),
9476
- /** @deprecated 请使用 xiaohongshuFetcher.fetchUserNoteList 替代 */
9477
- getUserNotes: createDeprecatedStub("getUserNotes"),
9478
- /** @deprecated 请使用 xiaohongshuFetcher.searchNotes 替代 */
9479
- getSearchNotes: createDeprecatedStub("getSearchNotes"),
9480
- /** @deprecated 请使用 xiaohongshuFetcher.fetchEmojiList 替代 */
9481
- getEmojiList: createDeprecatedStub("getEmojiList")
9482
- };
9483
- /**
9484
- * 创建绑定了cookie的小红书API对象
9485
- *
9486
- * @deprecated v6 已废弃,请使用 createBoundXiaohongshuFetcher 替代
9487
- */
9488
- const createBoundXiaohongshuApi = (_cookie, _requestConfig) => {
9489
- return { ...xiaohongshu };
10407
+ kuaishouApiUrls
9490
10408
  };
9491
10409
  //#endregion
9492
10410
  //#region src/platform/xiaohongshu/routes.ts
@@ -9540,8 +10458,7 @@ const createXiaohongshuRoutes = (cookie, requestConfig = getXiaohongshuDefaultCo
9540
10458
  /** 小红书相关功能模块 (工具集) */
9541
10459
  const xiaohongshuUtils = {
9542
10460
  sign: xiaohongshuSign,
9543
- xiaohongshuApiUrls,
9544
- api: xiaohongshu
10461
+ xiaohongshuApiUrls
9545
10462
  };
9546
10463
  //#endregion
9547
10464
  //#region src/server/index.ts
@@ -9588,38 +10505,6 @@ const createAmagiClient = (options) => {
9588
10505
  });
9589
10506
  return app;
9590
10507
  };
9591
- /**
9592
- * @deprecated v6 已废弃,请使用 douyin.fetcher 替代
9593
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9594
- */
9595
- const getDouyinData = (..._args) => {
9596
- checkDeprecation("getDouyinData");
9597
- throw new Error("getDouyinData 已废弃");
9598
- };
9599
- /**
9600
- * @deprecated v6 已废弃,请使用 bilibili.fetcher 替代
9601
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9602
- */
9603
- const getBilibiliData = (..._args) => {
9604
- checkDeprecation("getBilibiliData");
9605
- throw new Error("getBilibiliData 已废弃");
9606
- };
9607
- /**
9608
- * @deprecated v6 已废弃,请使用 kuaishou.fetcher 替代
9609
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9610
- */
9611
- const getKuaishouData = (..._args) => {
9612
- checkDeprecation("getKuaishouData");
9613
- throw new Error("getKuaishouData 已废弃");
9614
- };
9615
- /**
9616
- * @deprecated v6 已废弃,请使用 xiaohongshu.fetcher 替代
9617
- * @throws {DeprecatedApiError} 调用时抛出废弃错误
9618
- */
9619
- const getXiaohongshuData = (..._args) => {
9620
- checkDeprecation("getXiaohongshuData");
9621
- throw new Error("getXiaohongshuData 已废弃");
9622
- };
9623
10508
  return {
9624
10509
  /** 启动本地HTTP服务 */
9625
10510
  startServer,
@@ -9637,39 +10522,23 @@ const createAmagiClient = (options) => {
9637
10522
  * @param listener - 事件处理函数 (只触发一次)
9638
10523
  */
9639
10524
  once: (event, listener) => amagiEvents.once(event, listener),
9640
- /** @deprecated v6 已废弃,请使用 douyin.fetcher 替代 */
9641
- getDouyinData,
9642
- /** @deprecated v6 已废弃,请使用 bilibili.fetcher 替代 */
9643
- getBilibiliData,
9644
- /** @deprecated v6 已废弃,请使用 kuaishou.fetcher 替代 */
9645
- getKuaishouData,
9646
- /** @deprecated v6 已废弃,请使用 xiaohongshu.fetcher 替代 */
9647
- getXiaohongshuData,
9648
10525
  douyin: {
9649
10526
  ...douyinUtils,
9650
- /** @deprecated 请使用 fetcher 替代 */
9651
- api: createBoundDouyinApi(douyinCookie, requestConfig),
9652
10527
  /** fetcher */
9653
10528
  fetcher: createBoundDouyinFetcher(douyinCookie, requestConfig)
9654
10529
  },
9655
10530
  bilibili: {
9656
10531
  ...bilibiliUtils,
9657
- /** @deprecated 请使用 fetcher 替代 */
9658
- api: createBoundBilibiliApi(bilibiliCookie, requestConfig),
9659
10532
  /** fetcher */
9660
10533
  fetcher: createBoundBilibiliFetcher(bilibiliCookie, requestConfig)
9661
10534
  },
9662
10535
  kuaishou: {
9663
10536
  ...kuaishouUtils,
9664
- /** @deprecated 请使用 fetcher 替代 */
9665
- api: createBoundKuaishouApi(kuaishouCookie, requestConfig),
9666
10537
  /** fetcher */
9667
10538
  fetcher: createBoundKuaishouFetcher(kuaishouCookie, requestConfig)
9668
10539
  },
9669
10540
  xiaohongshu: {
9670
10541
  ...xiaohongshuUtils,
9671
- /** @deprecated 请使用 fetcher 替代 */
9672
- api: createBoundXiaohongshuApi(xiaohongshuCookie, requestConfig),
9673
10542
  /** fetcher */
9674
10543
  fetcher: createBoundXiaohongshuFetcher(xiaohongshuCookie, requestConfig)
9675
10544
  }
@@ -9833,7 +10702,6 @@ const BilibiliInternalMethods = {
9833
10702
  USER_SPACE_INFO: "用户空间详细信息",
9834
10703
  USER_TOTAL_VIEWS: "获取UP主总播放量",
9835
10704
  DYNAMIC_DETAIL: "动态详情数据",
9836
- DYNAMIC_CARD: "动态卡片数据",
9837
10705
  BANGUMI_INFO: "番剧基本信息数据",
9838
10706
  BANGUMI_STREAM: "番剧下载信息数据",
9839
10707
  LIVE_ROOM_INFO: "直播间信息",
@@ -9864,7 +10732,6 @@ const BilibiliFetcherMethods = {
9864
10732
  USER_SPACE_INFO: "fetchUserSpaceInfo",
9865
10733
  USER_TOTAL_VIEWS: "fetchUploaderTotalViews",
9866
10734
  DYNAMIC_DETAIL: "fetchDynamicDetail",
9867
- DYNAMIC_CARD: "fetchDynamicCard",
9868
10735
  BANGUMI_INFO: "fetchBangumiInfo",
9869
10736
  BANGUMI_STREAM: "fetchBangumiStreamUrl",
9870
10737
  LIVE_ROOM_INFO: "fetchLiveRoomInfo",
@@ -9973,7 +10840,6 @@ const BilibiliMethodToFetcher = {
9973
10840
  [BilibiliInternalMethods.USER_SPACE_INFO]: BilibiliFetcherMethods.USER_SPACE_INFO,
9974
10841
  [BilibiliInternalMethods.USER_TOTAL_VIEWS]: BilibiliFetcherMethods.USER_TOTAL_VIEWS,
9975
10842
  [BilibiliInternalMethods.DYNAMIC_DETAIL]: BilibiliFetcherMethods.DYNAMIC_DETAIL,
9976
- [BilibiliInternalMethods.DYNAMIC_CARD]: BilibiliFetcherMethods.DYNAMIC_CARD,
9977
10843
  [BilibiliInternalMethods.BANGUMI_INFO]: BilibiliFetcherMethods.BANGUMI_INFO,
9978
10844
  [BilibiliInternalMethods.BANGUMI_STREAM]: BilibiliFetcherMethods.BANGUMI_STREAM,
9979
10845
  [BilibiliInternalMethods.LIVE_ROOM_INFO]: BilibiliFetcherMethods.LIVE_ROOM_INFO,
@@ -10105,7 +10971,6 @@ const BilibiliMethodMapping = {
10105
10971
  用户空间详细信息: "fetchUserSpaceInfo",
10106
10972
  获取UP主总播放量: "fetchUploaderTotalViews",
10107
10973
  动态详情数据: "fetchDynamicDetail",
10108
- 动态卡片数据: "fetchDynamicCard",
10109
10974
  番剧基本信息数据: "fetchBangumiInfo",
10110
10975
  番剧下载信息数据: "fetchBangumiStreamUrl",
10111
10976
  直播间信息: "fetchLiveRoomInfo",
@@ -10188,7 +11053,6 @@ const BilibiliApiRoutes = {
10188
11053
  userSpaceInfo: "/user/space",
10189
11054
  uploaderTotalViews: "/user/total-views",
10190
11055
  dynamicDetail: "/dynamic",
10191
- dynamicCard: "/dynamic/card",
10192
11056
  bangumiInfo: "/bangumi",
10193
11057
  bangumiStream: "/bangumi/stream",
10194
11058
  liveRoomInfo: "/live",
@@ -10258,14 +11122,10 @@ function getApiRoute(platform, methodType) {
10258
11122
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
10259
11123
  */
10260
11124
  const getVersion = () => {
10261
- return "6.4.0";
11125
+ return "6.6.0";
10262
11126
  };
10263
11127
  const VERSION = getVersion();
10264
11128
  /**
10265
- * @deprecated 请使用 createAmagiClient 替代
10266
- */
10267
- const amagiClient = createAmagiClient;
10268
- /**
10269
11129
  * 创建一个新的 amagi 客户端实例
10270
11130
  * 用于创建和初始化一个新的 amagi 客户端实例,支持通过 new 关键字或函数调用方式使用
10271
11131
  * @param options - cookies 配置选项,用于设置客户端的 cookies 相关参数
@@ -10285,10 +11145,6 @@ CreateAmagiApp.douyin = douyinUtils;
10285
11145
  CreateAmagiApp.bilibili = bilibiliUtils;
10286
11146
  CreateAmagiApp.kuaishou = kuaishouUtils;
10287
11147
  CreateAmagiApp.xiaohongshu = xiaohongshuUtils;
10288
- CreateAmagiApp.getDouyinData = getDouyinData;
10289
- CreateAmagiApp.getBilibiliData = getBilibiliData;
10290
- CreateAmagiApp.getKuaishouData = getKuaishouData;
10291
- CreateAmagiApp.getXiaohongshuData = getXiaohongshuData;
10292
11148
  CreateAmagiApp.events = amagiEvents;
10293
11149
  CreateAmagiApp.on = amagiEvents.on.bind(amagiEvents);
10294
11150
  CreateAmagiApp.once = amagiEvents.once.bind(amagiEvents);
@@ -10389,24 +11245,19 @@ exports.XiaohongshuMethodRoutes = XiaohongshuMethodRoutes;
10389
11245
  exports.XiaohongshuMethodToFetcher = XiaohongshuMethodToFetcher;
10390
11246
  exports.XiaohongshuValidationSchemas = XiaohongshuValidationSchemas;
10391
11247
  exports.amagi = amagi;
10392
- exports.amagiClient = amagiClient;
10393
11248
  exports.amagiEvents = amagiEvents;
10394
11249
  exports.av2bv = av2bv;
10395
- exports.bilibili = bilibili;
10396
11250
  exports.bilibiliApiUrls = bilibiliApiUrls;
10397
11251
  exports.bilibiliErrorCodeMap = bilibiliErrorCodeMap;
10398
11252
  exports.bilibiliFetcher = bilibiliFetcher;
10399
11253
  exports.bilibiliUtils = bilibiliUtils;
10400
11254
  exports.bv2av = bv2av;
11255
+ exports.checkPassportQrcode = checkPassportQrcode;
10401
11256
  exports.createAmagiClient = createAmagiClient;
10402
11257
  exports.createBilibiliRoutes = createBilibiliRoutes;
10403
- exports.createBoundBilibiliApi = createBoundBilibiliApi;
10404
11258
  exports.createBoundBilibiliFetcher = createBoundBilibiliFetcher;
10405
- exports.createBoundDouyinApi = createBoundDouyinApi;
10406
11259
  exports.createBoundDouyinFetcher = createBoundDouyinFetcher;
10407
- exports.createBoundKuaishouApi = createBoundKuaishouApi;
10408
11260
  exports.createBoundKuaishouFetcher = createBoundKuaishouFetcher;
10409
- exports.createBoundXiaohongshuApi = createBoundXiaohongshuApi;
10410
11261
  exports.createBoundXiaohongshuFetcher = createBoundXiaohongshuFetcher;
10411
11262
  exports.createDouyinRoutes = createDouyinRoutes;
10412
11263
  exports.createErrorResponse = createErrorResponse;
@@ -10414,9 +11265,14 @@ exports.createKuaishouRoutes = createKuaishouRoutes;
10414
11265
  exports.createSuccessResponse = createSuccessResponse;
10415
11266
  exports.createXiaohongshuRoutes = createXiaohongshuRoutes;
10416
11267
  exports.default = Client;
10417
- exports.douyin = douyin;
10418
11268
  exports.douyinApiUrls = douyinApiUrls;
10419
11269
  exports.douyinFetcher = douyinFetcher;
11270
+ Object.defineProperty(exports, "douyinPassport", {
11271
+ enumerable: true,
11272
+ get: function() {
11273
+ return passport_exports;
11274
+ }
11275
+ });
10420
11276
  exports.douyinSign = douyinSign;
10421
11277
  exports.douyinUtils = douyinUtils;
10422
11278
  exports.emitApiError = emitApiError;
@@ -10434,35 +11290,30 @@ exports.emitNetworkRetry = emitNetworkRetry;
10434
11290
  exports.fetchData = fetchData;
10435
11291
  exports.fetchResponse = fetchResponse;
10436
11292
  exports.getApiRoute = getApiRoute;
10437
- exports.getBilibiliData = getBilibiliData;
10438
- exports.getDouyinData = getDouyinData;
10439
11293
  exports.getEnglishMethodName = getEnglishMethodName;
10440
11294
  exports.getHeadersAndData = getHeadersAndData;
10441
- exports.getKuaishouData = getKuaishouData;
10442
11295
  exports.handleError = handleError;
10443
- exports.httpLogger = httpLogger;
10444
- exports.initLogger = initLogger;
10445
11296
  exports.isNetworkErrorResult = isNetworkErrorResult;
10446
- exports.kuaishou = kuaishou;
11297
+ exports.isSmsCodeVerifyWay = isSmsCodeVerifyWay;
10447
11298
  exports.kuaishouApiUrls = kuaishouApiUrls;
10448
11299
  exports.kuaishouFetcher = kuaishouFetcher;
10449
11300
  exports.kuaishouSign = kuaishouSign;
10450
11301
  exports.kuaishouUtils = kuaishouUtils;
10451
- exports.logMiddleware = logMiddleware;
10452
- exports.logger = logger;
10453
11302
  exports.parseDmSegMobileReply = parseDmSegMobileReply;
10454
11303
  exports.qtparam = qtparam;
10455
11304
  exports.registerBilibiliRoutes = createBilibiliRoutes;
10456
11305
  exports.registerDouyinRoutes = createDouyinRoutes;
10457
11306
  exports.registerKuaishouRoutes = createKuaishouRoutes;
10458
11307
  exports.registerXiaohongshuRoutes = createXiaohongshuRoutes;
11308
+ exports.requestPassportQrcode = requestPassportQrcode;
11309
+ exports.sendPassportVerifyCode = sendPassportVerifyCode;
10459
11310
  exports.toFetcherMethod = toFetcherMethod;
10460
11311
  exports.validateBilibiliParams = validateBilibiliParams;
10461
11312
  exports.validateDouyinParams = validateDouyinParams;
10462
11313
  exports.validateKuaishouParams = validateKuaishouParams;
11314
+ exports.validatePassportVerifyCode = validatePassportVerifyCode;
10463
11315
  exports.validateXiaohongshuParams = validateXiaohongshuParams;
10464
11316
  exports.wbi_sign = wbi_sign;
10465
- exports.xiaohongshu = xiaohongshu;
10466
11317
  exports.xiaohongshuApiUrls = xiaohongshuApiUrls;
10467
11318
  exports.xiaohongshuFetcher = xiaohongshuFetcher;
10468
11319
  exports.xiaohongshuSign = xiaohongshuSign;