@ikenxuan/amagi 7.0.0-beta.3 → 7.0.0-beta.5

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.
@@ -176,15 +176,25 @@ const bilibiliApiUrls$1 = new BilibiliAPI$1();
176
176
  */
177
177
  const type = () => ({});
178
178
  /**
179
- * 声明一个端点。
180
- *
181
- * 运行时就是恒等函数,全部价值在类型推导:`TParams` 从 `params` 推出,
182
- * `TData` 从 `response` / `normalize` / `compute` 推出。
179
+ * 声明一个端点。运行时是恒等函数(原样返回 `def`),全部价值在类型推导:
180
+ * `TParams` 从 `params` 推、`TData` 从 `response` / `compute` 推。
181
+ * 平台一般用各自的 `defineXxxEndpoint`(把签名器名收窄),不直接用这个。
183
182
  * @param def - 端点声明
184
- * @returns 原样返回 `def`,但带上推导好的具体类型
185
183
  */
186
184
  const defineEndpoint = (def) => def;
187
185
  //#endregion
186
+ //#region src/platforms/bilibili/endpoints/define.ts
187
+ /**
188
+ * B站端点声明。
189
+ *
190
+ * 就是 {@link defineEndpoint} 绑定了 B站签名器名联合 {@link BilibiliSignerName}:
191
+ * `sign` 的字符串分支被收窄成 `'wbi' | 'qtparam'`,写错名字编译期即报错,不必等
192
+ * 运行时查表失败。运行时仍是恒等函数,其余行为与通用 `defineEndpoint` 完全一致。
193
+ * @param def - 端点声明(`sign` 的名字受 B站签名器表约束)
194
+ * @returns 原样返回 `def`,带上推导好的具体类型
195
+ */
196
+ const defineBilibiliEndpoint = (def) => defineEndpoint(def);
197
+ //#endregion
188
198
  //#region src/platforms/bilibili/endpoints/articleCards.ts
189
199
  /**
190
200
  * 专栏显示卡片信息(单请求)。
@@ -192,7 +202,7 @@ const defineEndpoint = (def) => def;
192
202
  * 与旧版一致:`getArticleCards` GET,无签名。
193
203
  * `ids` 可传字符串或字符串数组。
194
204
  */
195
- const articleCards = defineEndpoint({
205
+ const articleCards = defineBilibiliEndpoint({
196
206
  name: "bilibili.articleCards",
197
207
  route: "/fetch_article_card",
198
208
  doc: {
@@ -214,7 +224,7 @@ const articleCards = defineEndpoint({
214
224
  *
215
225
  * 与旧版一致:`getArticleContent` GET,无签名。
216
226
  */
217
- const articleContent = defineEndpoint({
227
+ const articleContent = defineBilibiliEndpoint({
218
228
  name: "bilibili.articleContent",
219
229
  route: "/fetch_article_content",
220
230
  doc: {
@@ -236,7 +246,7 @@ const articleContent = defineEndpoint({
236
246
  *
237
247
  * 与旧版一致:`getArticleInfo` GET,无签名。
238
248
  */
239
- const articleInfo = defineEndpoint({
249
+ const articleInfo = defineBilibiliEndpoint({
240
250
  name: "bilibili.articleInfo",
241
251
  route: "/fetch_article_info",
242
252
  doc: {
@@ -258,7 +268,7 @@ const articleInfo = defineEndpoint({
258
268
  *
259
269
  * 与旧版一致:`getArticleListInfo` GET,无签名。
260
270
  */
261
- const articleListInfo = defineEndpoint({
271
+ const articleListInfo = defineBilibiliEndpoint({
262
272
  name: "bilibili.articleListInfo",
263
273
  route: "/fetch_column_info",
264
274
  doc: {
@@ -334,7 +344,7 @@ const bv2av = (bvid) => {
334
344
  *
335
345
  * 返回形状:`{ bvid }`。
336
346
  */
337
- const avToBv = defineEndpoint({
347
+ const avToBv = defineBilibiliEndpoint({
338
348
  name: "bilibili.avToBv",
339
349
  route: "/av_to_bv",
340
350
  doc: {
@@ -353,7 +363,7 @@ const avToBv = defineEndpoint({
353
363
  * 与旧版一致:`getBangumiInfo` GET,无签名。
354
364
  * `ep_id` 与 `season_id` 至少传其一,两者都要求非空串(`ep_id` 优先)。
355
365
  */
356
- const bangumiInfo = defineEndpoint({
366
+ const bangumiInfo = defineBilibiliEndpoint({
357
367
  name: "bilibili.bangumiInfo",
358
368
  route: "/fetch_bangumi_video_info",
359
369
  doc: {
@@ -387,7 +397,7 @@ const bangumiInfo = defineEndpoint({
387
397
  * 与旧版一致:`getBangumiStream` GET + qtparam 签名,
388
398
  * `ep_id` 去掉 `ep` 前缀。
389
399
  */
390
- const bangumiStream = defineEndpoint({
400
+ const bangumiStream = defineBilibiliEndpoint({
391
401
  name: "bilibili.bangumiStream",
392
402
  route: "/fetch_bangumi_video_playurl",
393
403
  doc: {
@@ -418,7 +428,7 @@ const bangumiStream = defineEndpoint({
418
428
  *
419
429
  * 返回 `{ aid: number }`,不带 `av` 前缀。
420
430
  */
421
- const bvToAv = defineEndpoint({
431
+ const bvToAv = defineBilibiliEndpoint({
422
432
  name: "bilibili.bvToAv",
423
433
  route: "/bv_to_av",
424
434
  doc: {
@@ -437,7 +447,7 @@ const bvToAv = defineEndpoint({
437
447
  * 与旧版一致:`getCaptchaFromVoucher` POST,
438
448
  * body 为 `{ csrf?, v_voucher }`,无签名。
439
449
  */
440
- const captchaFromVoucher = defineEndpoint({
450
+ const captchaFromVoucher = defineBilibiliEndpoint({
441
451
  name: "bilibili.captchaFromVoucher",
442
452
  route: "/apply_captcha",
443
453
  doc: {
@@ -467,7 +477,7 @@ const captchaFromVoucher = defineEndpoint({
467
477
  *
468
478
  * 与旧版一致:`getCommentReplies` GET,无签名。
469
479
  */
470
- const commentReplies$1 = defineEndpoint({
480
+ const commentReplies$1 = defineBilibiliEndpoint({
471
481
  name: "bilibili.commentReplies",
472
482
  route: "/fetch_comment_reply",
473
483
  doc: {
@@ -530,7 +540,7 @@ const COMMENT_TYPES$1 = [
530
540
  * 按 `rpid` 去重 + 截断到 `number`。
531
541
  * - 未开放评论区由主接口返回非 0 的 `code`,由 judge 判失败。
532
542
  */
533
- const comments$2 = defineEndpoint({
543
+ const comments$2 = defineBilibiliEndpoint({
534
544
  name: "bilibili.comments",
535
545
  route: "/fetch_work_comments",
536
546
  doc: {
@@ -569,20 +579,18 @@ const comments$2 = defineEndpoint({
569
579
  ...params,
570
580
  pagination_str: next ?? params.pagination_str
571
581
  };
582
+ },
583
+ merge: ({ lastPage, items }, params) => {
584
+ const sliced = Array.from(new Map(items.map((item) => [item.rpid, item])).values()).slice(0, params.number ?? 20);
585
+ return {
586
+ ...lastPage,
587
+ data: {
588
+ ...lastPage.data,
589
+ replies: sliced
590
+ }
591
+ };
572
592
  }
573
593
  },
574
- normalize: (decoded, params) => {
575
- const { lastPage, items } = decoded;
576
- const page = lastPage;
577
- const sliced = Array.from(new Map(items.map((item) => [item.rpid, item])).values()).slice(0, params.number ?? 20);
578
- return {
579
- ...page ?? {},
580
- data: {
581
- ...page?.data ?? {},
582
- replies: sliced
583
- }
584
- };
585
- },
586
594
  retryOn: ["RISK_CONTROL"],
587
595
  response: type()
588
596
  });
@@ -618,7 +626,7 @@ const COMMENT_TYPES = [
618
626
  *
619
627
  * 与旧版一致:`getDynamicDetail` GET,无签名。
620
628
  */
621
- const dynamicDetail = defineEndpoint({
629
+ const dynamicDetail = defineBilibiliEndpoint({
622
630
  name: "bilibili.dynamicDetail",
623
631
  route: "/fetch_dynamic_info",
624
632
  doc: {
@@ -641,7 +649,7 @@ const dynamicDetail = defineEndpoint({
641
649
  * 与旧版一致:`getEmojiList` GET,无签名。
642
650
  * 它是网络请求(表情面板接口),不是本地计算。
643
651
  */
644
- const emojiList$4 = defineEndpoint({
652
+ const emojiList$4 = defineBilibiliEndpoint({
645
653
  name: "bilibili.emojiList",
646
654
  route: "/fetch_emoji_list",
647
655
  doc: {
@@ -663,7 +671,7 @@ const emojiList$4 = defineEndpoint({
663
671
  *
664
672
  * 与旧版一致:`getLiveRoomInfo` GET,无签名。
665
673
  */
666
- const liveRoomInfo$2 = defineEndpoint({
674
+ const liveRoomInfo$2 = defineBilibiliEndpoint({
667
675
  name: "bilibili.liveRoomInfo",
668
676
  route: "/fetch_live_room_detail",
669
677
  doc: {
@@ -685,7 +693,7 @@ const liveRoomInfo$2 = defineEndpoint({
685
693
  *
686
694
  * 与旧版一致:`getLiveRoomInit` GET,无签名。
687
695
  */
688
- const liveRoomInit = defineEndpoint({
696
+ const liveRoomInit = defineBilibiliEndpoint({
689
697
  name: "bilibili.liveRoomInit",
690
698
  route: "/fetch_liveroom_def",
691
699
  doc: {
@@ -707,7 +715,7 @@ const liveRoomInit = defineEndpoint({
707
715
  *
708
716
  * 与旧版一致:`getLoginQrcode` GET,无签名。
709
717
  */
710
- const loginQrcode$1 = defineEndpoint({
718
+ const loginQrcode$1 = defineBilibiliEndpoint({
711
719
  name: "bilibili.loginQrcode",
712
720
  route: "/new_login_qrcode",
713
721
  doc: {
@@ -729,7 +737,7 @@ const loginQrcode$1 = defineEndpoint({
729
737
  *
730
738
  * 与旧版一致:`getLoginStatus` GET,无签名。
731
739
  */
732
- const loginStatus = defineEndpoint({
740
+ const loginStatus = defineBilibiliEndpoint({
733
741
  name: "bilibili.loginStatus",
734
742
  route: "/login_basic_info",
735
743
  doc: {
@@ -752,7 +760,7 @@ const loginStatus = defineEndpoint({
752
760
  * 与旧版一致:`getQrcodeStatus` GET,无签名。
753
761
  * **返回形状**:只返回平台响应体(`code` / `data` / `message`),不透出响应 headers。
754
762
  */
755
- const qrcodeStatus = defineEndpoint({
763
+ const qrcodeStatus = defineBilibiliEndpoint({
756
764
  name: "bilibili.qrcodeStatus",
757
765
  route: "/check_qrcode",
758
766
  doc: {
@@ -774,7 +782,7 @@ const qrcodeStatus = defineEndpoint({
774
782
  *
775
783
  * 与旧版一致:`getUploaderTotalViews` GET,无签名。
776
784
  */
777
- const uploaderTotalViews = defineEndpoint({
785
+ const uploaderTotalViews = defineBilibiliEndpoint({
778
786
  name: "bilibili.uploaderTotalViews",
779
787
  route: "/fetch_user_full_view",
780
788
  doc: {
@@ -796,7 +804,7 @@ const uploaderTotalViews = defineEndpoint({
796
804
  *
797
805
  * 与旧版一致:`getUserCard` GET,无签名。
798
806
  */
799
- const userCard = defineEndpoint({
807
+ const userCard = defineBilibiliEndpoint({
800
808
  name: "bilibili.userCard",
801
809
  route: "/fetch_user_profile",
802
810
  doc: {
@@ -927,7 +935,7 @@ var AmagiHeaders = class AmagiHeaders {
927
935
  * `Referer: https://space.bilibili.com/{host_mid}/dynamic`
928
936
  * (调用方显式传了 Referer 则不覆盖)。
929
937
  */
930
- const userDynamicList = defineEndpoint({
938
+ const userDynamicList = defineBilibiliEndpoint({
931
939
  name: "bilibili.userDynamicList",
932
940
  route: "/fetch_user_dynamic",
933
941
  doc: {
@@ -956,7 +964,7 @@ const userDynamicList = defineEndpoint({
956
964
  *
957
965
  * 与旧版一致:`getUserLiveStatus` GET,无签名。
958
966
  */
959
- const userLiveStatus = defineEndpoint({
967
+ const userLiveStatus = defineBilibiliEndpoint({
960
968
  name: "bilibili.userLiveStatus",
961
969
  route: "/fetch_user_live_status",
962
970
  doc: {
@@ -978,7 +986,7 @@ const userLiveStatus = defineEndpoint({
978
986
  *
979
987
  * 与旧版一致:`getUserSpaceInfo` GET + wbi 签名。
980
988
  */
981
- const userSpaceInfo = defineEndpoint({
989
+ const userSpaceInfo = defineBilibiliEndpoint({
982
990
  name: "bilibili.userSpaceInfo",
983
991
  route: "/fetch_user_space_info",
984
992
  doc: {
@@ -1002,7 +1010,7 @@ const userSpaceInfo = defineEndpoint({
1002
1010
  * 与旧版一致:`validateCaptcha` POST,
1003
1011
  * body 为 `{ challenge, token, validate, seccode, csrf? }`,无签名。
1004
1012
  */
1005
- const validateCaptcha = defineEndpoint({
1013
+ const validateCaptcha = defineBilibiliEndpoint({
1006
1014
  name: "bilibili.validateCaptcha",
1007
1015
  route: "/validate_captcha",
1008
1016
  doc: {
@@ -1139,7 +1147,7 @@ function parseDmSegMobileReply(data) {
1139
1147
  * judge 恒成功:二进制响应没有 `code` 字段,交给 decode 解析;
1140
1148
  * 解析失败由 execute 归因为 `parse` / `DECODE_FAILED`。
1141
1149
  */
1142
- const videoDanmaku = defineEndpoint({
1150
+ const videoDanmaku = defineBilibiliEndpoint({
1143
1151
  name: "bilibili.videoDanmaku",
1144
1152
  route: "/fetch_danmaku",
1145
1153
  doc: {
@@ -1169,7 +1177,7 @@ const videoDanmaku = defineEndpoint({
1169
1177
  * 共 27 条路由。
1170
1178
  */
1171
1179
  const bilibiliRegistry = {
1172
- videoInfo: defineEndpoint({
1180
+ videoInfo: defineBilibiliEndpoint({
1173
1181
  name: "bilibili.videoInfo",
1174
1182
  route: "/fetch_one_video",
1175
1183
  doc: {
@@ -1184,7 +1192,7 @@ const bilibiliRegistry = {
1184
1192
  retryOn: ["RISK_CONTROL"],
1185
1193
  response: type()
1186
1194
  }),
1187
- videoStream: defineEndpoint({
1195
+ videoStream: defineBilibiliEndpoint({
1188
1196
  name: "bilibili.videoStream",
1189
1197
  route: "/fetch_video_playurl",
1190
1198
  doc: {
@@ -3522,7 +3530,12 @@ const xBogusSigner = (spec, ctx) => {
3522
3530
  url: url.toString()
3523
3531
  }, ctx.cookie);
3524
3532
  };
3525
- /** 平台签名器表,交给 runtime 的 `signers` 查名 */
3533
+ /**
3534
+ * 平台签名器表,交给 runtime 的 `signers` 查名。
3535
+ *
3536
+ * 用 `satisfies` 而非显式 `: Record<string, SignFn>` 返回:后者会把键联合抹成宽
3537
+ * `string`,而 {@link DouyinSignerName} 要靠 `keyof` 从这张表推导出精确的名字联合。
3538
+ */
3526
3539
  const createDouyinSigners = () => ({
3527
3540
  "a-bogus": aBogusSigner,
3528
3541
  "x-bogus": xBogusSigner
@@ -5466,7 +5479,12 @@ const xhsGetXywTraceSigner = async (spec, ctx) => {
5466
5479
  headers: headers.toJSON()
5467
5480
  };
5468
5481
  };
5469
- /** 平台签名器表,交给 runtime 的 `signers` 查名 */
5482
+ /**
5483
+ * 平台签名器表,交给 runtime 的 `signers` 查名。
5484
+ *
5485
+ * 用 `satisfies` 而非显式 `: Record<string, SignFn>` 返回:后者会把键联合抹成宽
5486
+ * `string`,而 {@link XiaohongshuSignerName} 要靠 `keyof` 从这张表推导精确名字联合。
5487
+ */
5470
5488
  const createXiaohongshuSigners = () => ({
5471
5489
  "xhs-post": xhsPostSigner,
5472
5490
  "xhs-get": xhsGetSigner,
@@ -6383,7 +6401,10 @@ const runPaginated = async (def, params, runPage) => {
6383
6401
  const limitKey = def.limitParam ?? "number";
6384
6402
  const countKey = def.countParam ?? limitKey;
6385
6403
  const target = resolveTarget(params[limitKey], def.maxPageSize);
6386
- const pages = [];
6404
+ if (target === 0) return {
6405
+ ok: true,
6406
+ value: { empty: true }
6407
+ };
6387
6408
  const collected = [];
6388
6409
  let current = params;
6389
6410
  let lastPage;
@@ -6400,7 +6421,6 @@ const runPaginated = async (def, params, runPage) => {
6400
6421
  error: outcome.error
6401
6422
  };
6402
6423
  const page = outcome.value;
6403
- pages.push(page);
6404
6424
  lastPage = page;
6405
6425
  const list = def.items(page);
6406
6426
  if (Array.isArray(list) && list.length > 0) collected.push(...list);
@@ -6412,9 +6432,9 @@ const runPaginated = async (def, params, runPage) => {
6412
6432
  return {
6413
6433
  ok: true,
6414
6434
  value: {
6435
+ empty: false,
6415
6436
  lastPage,
6416
- pages,
6417
- items: target === 0 ? [] : collected.slice(0, target)
6437
+ items: collected.slice(0, target)
6418
6438
  }
6419
6439
  };
6420
6440
  };
@@ -6766,7 +6786,10 @@ const execute = async (def, input, options) => {
6766
6786
  });
6767
6787
  if (!paged.ok) return failWith(paged.error);
6768
6788
  stage = "normalize";
6769
- return succeed(def.normalize ? def.normalize(paged.value, params) : paged.value);
6789
+ const outcome = paged.value;
6790
+ if (outcome.empty) return succeed(void 0);
6791
+ const merge = def.paginate.merge;
6792
+ return succeed(merge ? merge(outcome, params) : outcome);
6770
6793
  }
6771
6794
  stage = "build";
6772
6795
  const built = def.build(params, ctx);
@@ -6790,6 +6813,15 @@ const execute = async (def, input, options) => {
6790
6813
  if (firstFailure) return failWith(firstFailure.error);
6791
6814
  }
6792
6815
  stage = "normalize";
6816
+ if (isMulti && def.aggregate) {
6817
+ const parts = outcomes.map((o) => o.ok ? o.value : void 0);
6818
+ const head = parts.find((p) => p !== void 0);
6819
+ const aggregate = def.aggregate;
6820
+ return succeed(aggregate({
6821
+ parts,
6822
+ head
6823
+ }, params));
6824
+ }
6793
6825
  const decodedValue = isMulti ? outcomes.map((o) => o.ok ? o.value : void 0) : outcomes[0].value;
6794
6826
  return succeed(def.normalize ? def.normalize(decodedValue, params) : decodedValue);
6795
6827
  } catch (cause) {
@@ -7703,6 +7735,19 @@ var DouyinAPI$1 = class {
7703
7735
  /** 默认的 DouyinAPI 实例(使用默认浏览器版本 125.0.0.0) */
7704
7736
  const douyinApiUrls$1 = new DouyinAPI$1();
7705
7737
  //#endregion
7738
+ //#region src/platforms/douyin/endpoints/define.ts
7739
+ /**
7740
+ * 抖音端点声明。
7741
+ *
7742
+ * 就是 {@link defineEndpoint} 绑定了抖音签名器名联合 {@link DouyinSignerName}:
7743
+ * `sign` 的字符串分支被收窄成 `'a-bogus' | 'x-bogus'`,写错名字(如 `'a-bogas'`)
7744
+ * 编译期即报错,不必等运行时查表失败。运行时仍是恒等函数,其余行为与通用
7745
+ * `defineEndpoint` 完全一致。
7746
+ * @param def - 端点声明(`sign` 的名字受抖音签名器表约束)
7747
+ * @returns 原样返回 `def`,带上推导好的具体类型
7748
+ */
7749
+ const defineDouyinEndpoint = (def) => defineEndpoint(def);
7750
+ //#endregion
7706
7751
  //#region src/platforms/douyin/endpoints/commentReplies.ts
7707
7752
  /**
7708
7753
  * 二级评论(声明式翻页,maxPageSize 3,签名用 `x_bogus`)。
@@ -7710,7 +7755,7 @@ const douyinApiUrls$1 = new DouyinAPI$1();
7710
7755
  * 与旧版一致:`getCommentReplies` GET,签名器是 `x_bogus`,游标是 `cursor`
7711
7756
  * (`has_more === 1` 继续),最终形状 `{ ...最后一页, comments, cursor }`。
7712
7757
  */
7713
- const commentReplies = defineEndpoint({
7758
+ const commentReplies = defineDouyinEndpoint({
7714
7759
  name: "douyin.commentReplies",
7715
7760
  route: "/fetch_video_comment_replies",
7716
7761
  doc: {
@@ -7737,16 +7782,12 @@ const commentReplies = defineEndpoint({
7737
7782
  nextParams: (params, page) => ({
7738
7783
  ...params,
7739
7784
  cursor: page.cursor
7740
- })
7741
- },
7742
- normalize: (decoded) => {
7743
- const { lastPage, items } = decoded;
7744
- const page = lastPage;
7745
- return {
7746
- ...page ?? {},
7785
+ }),
7786
+ merge: ({ lastPage, items }) => ({
7787
+ ...lastPage,
7747
7788
  comments: items,
7748
- cursor: page?.cursor ?? items.length
7749
- };
7789
+ cursor: lastPage.cursor ?? items.length
7790
+ })
7750
7791
  },
7751
7792
  response: type()
7752
7793
  });
@@ -7759,7 +7800,7 @@ const commentReplies = defineEndpoint({
7759
7800
  * `cursor`(`has_more === 1` 继续),最终形状 `{ ...最后一页, comments, cursor }`
7760
7801
  * (`cursor: resp.cursor ?? list.length`)。
7761
7802
  */
7762
- const comments$1 = defineEndpoint({
7803
+ const comments$1 = defineDouyinEndpoint({
7763
7804
  name: "douyin.comments",
7764
7805
  route: "/fetch_work_comments",
7765
7806
  doc: {
@@ -7785,16 +7826,12 @@ const comments$1 = defineEndpoint({
7785
7826
  nextParams: (params, page) => ({
7786
7827
  ...params,
7787
7828
  cursor: page.cursor
7788
- })
7789
- },
7790
- normalize: (decoded) => {
7791
- const { lastPage, items } = decoded;
7792
- const page = lastPage;
7793
- return {
7794
- ...page ?? {},
7829
+ }),
7830
+ merge: ({ lastPage, items }) => ({
7831
+ ...lastPage,
7795
7832
  comments: items,
7796
- cursor: page?.cursor ?? items.length
7797
- };
7833
+ cursor: lastPage.cursor ?? items.length
7834
+ })
7798
7835
  },
7799
7836
  response: type()
7800
7837
  });
@@ -7811,7 +7848,7 @@ const comments$1 = defineEndpoint({
7811
7848
  * 整体(execute 的 tolerate:**全部分片都失败时仍返回失败信封**),
7812
7849
  * `normalize` 负责合并与排序。
7813
7850
  */
7814
- const danmakuList$1 = defineEndpoint({
7851
+ const danmakuList$1 = defineDouyinEndpoint({
7815
7852
  name: "douyin.danmakuList",
7816
7853
  route: "/fetch_work_danmaku",
7817
7854
  doc: {
@@ -7863,33 +7900,14 @@ const danmakuList$1 = defineEndpoint({
7863
7900
  retryOn: ["ANTIBOT_PAGE"],
7864
7901
  retryFresh: true,
7865
7902
  partial: "tolerate",
7866
- normalize: (decoded, params) => {
7867
- const parts = decoded;
7868
- const startTime = params.start_time ?? 0;
7869
- const endTime = params.end_time ?? params.duration;
7870
- const merged = [];
7871
- let finalExtra = null;
7872
- let finalLogPb = null;
7873
- let finalStatusCode = 0;
7874
- parts.forEach((segmentData, index) => {
7875
- if (segmentData && Array.isArray(segmentData.danmaku_list)) {
7876
- merged.push(...segmentData.danmaku_list);
7877
- if (index === 0) {
7878
- finalExtra = segmentData.extra;
7879
- finalLogPb = segmentData.log_pb;
7880
- finalStatusCode = segmentData.status_code ?? 0;
7881
- }
7882
- }
7883
- });
7884
- merged.sort((a, b) => (a.offset_time ?? 0) - (b.offset_time ?? 0));
7903
+ aggregate: ({ parts, head }, params) => {
7904
+ const merged = parts.flatMap((seg) => seg?.danmaku_list ?? []).sort((a, b) => a.offset_time - b.offset_time);
7885
7905
  return {
7906
+ ...head,
7886
7907
  danmaku_list: merged,
7887
- start_time: startTime,
7888
- end_time: endTime,
7889
- total: merged.length,
7890
- status_code: finalStatusCode,
7891
- extra: finalExtra,
7892
- log_pb: finalLogPb
7908
+ start_time: params.start_time ?? 0,
7909
+ end_time: params.end_time ?? params.duration,
7910
+ total: merged.length
7893
7911
  };
7894
7912
  },
7895
7913
  response: type()
@@ -7901,7 +7919,7 @@ const danmakuList$1 = defineEndpoint({
7901
7919
  *
7902
7920
  * 与旧版一致:`getDynamicEmojiList` GET + a_bogus 签名。
7903
7921
  */
7904
- const dynamicEmojiList = defineEndpoint({
7922
+ const dynamicEmojiList = defineDouyinEndpoint({
7905
7923
  name: "douyin.dynamicEmojiList",
7906
7924
  route: "/fetch_emoji_pro_list",
7907
7925
  doc: {
@@ -7925,7 +7943,7 @@ const dynamicEmojiList = defineEndpoint({
7925
7943
  *
7926
7944
  * 与旧版一致:`getEmojiList` GET,**不带签名参数**。
7927
7945
  */
7928
- const emojiList$3 = defineEndpoint({
7946
+ const emojiList$3 = defineDouyinEndpoint({
7929
7947
  name: "douyin.emojiList",
7930
7948
  route: "/fetch_emoji_list",
7931
7949
  doc: {
@@ -7959,7 +7977,7 @@ const emojiList$3 = defineEndpoint({
7959
7977
  * 超时是「本次调用」的属性而不是端点的属性)。
7960
7978
 
7961
7979
  */
7962
- const emojiResourceMeta = defineEndpoint({
7980
+ const emojiResourceMeta = defineDouyinEndpoint({
7963
7981
  name: "douyin.emojiResourceMeta",
7964
7982
  route: "/fetch_emoji_resource_meta",
7965
7983
  doc: {
@@ -7994,7 +8012,7 @@ const emojiResourceMeta = defineEndpoint({
7994
8012
  * 接口的 `has_more` 形状还没有样本可依。
7995
8013
 
7996
8014
  */
7997
- const guestMusicAwemeList = defineEndpoint({
8015
+ const guestMusicAwemeList = defineDouyinEndpoint({
7998
8016
  name: "douyin.guestMusicAwemeList",
7999
8017
  route: "/fetch_guest_music_aweme_list",
8000
8018
  doc: {
@@ -8025,7 +8043,7 @@ const guestMusicAwemeList = defineEndpoint({
8025
8043
  * `extra.extract_item_id` 就是创建这条原声的那个作品。哪条优先由调用方按场景排。
8026
8044
 
8027
8045
  */
8028
- const guestMusicInfo = defineEndpoint({
8046
+ const guestMusicInfo = defineDouyinEndpoint({
8029
8047
  name: "douyin.guestMusicInfo",
8030
8048
  route: "/fetch_guest_music_info",
8031
8049
  doc: {
@@ -8055,7 +8073,7 @@ const guestMusicInfo = defineEndpoint({
8055
8073
  * 「设备参数 × 会话」的交叉校验,这条接口本来不需要身份。
8056
8074
 
8057
8075
  */
8058
- const guestUserInfo = defineEndpoint({
8076
+ const guestUserInfo = defineDouyinEndpoint({
8059
8077
  name: "douyin.guestUserInfo",
8060
8078
  route: "/fetch_guest_user_info",
8061
8079
  doc: {
@@ -8078,7 +8096,7 @@ const guestUserInfo = defineEndpoint({
8078
8096
  *
8079
8097
  * 行为与旧版一致:`getWorkDetail` GET + a_bogus 签名。
8080
8098
  */
8081
- const imageAlbumWork = defineEndpoint({
8099
+ const imageAlbumWork = defineDouyinEndpoint({
8082
8100
  name: "douyin.imageAlbumWork",
8083
8101
  route: "/fetch_image_album_work",
8084
8102
  doc: {
@@ -8137,7 +8155,7 @@ const withDouyinReferer = (ctx, page) => {
8137
8155
  * 与旧版一致:`getLiveRoomInfo` GET + a_bogus 签名,
8138
8156
  * Referer 指向 `https://live.douyin.com/{web_rid}`。
8139
8157
  */
8140
- const liveRoomInfo$1 = defineEndpoint({
8158
+ const liveRoomInfo$1 = defineDouyinEndpoint({
8141
8159
  name: "douyin.liveRoomInfo",
8142
8160
  route: "/fetch_user_live_videos",
8143
8161
  doc: {
@@ -8172,7 +8190,7 @@ const liveRoomInfo$1 = defineEndpoint({
8172
8190
  * 映射条目 `DyLoginQrcode` 是本端点的**原始响应**,与 `DyPassportQrcode`
8173
8191
  * (登录状态机归一化后的形状,runtime/session 用)不是一回事。
8174
8192
  */
8175
- const loginQrcode = defineEndpoint({
8193
+ const loginQrcode = defineDouyinEndpoint({
8176
8194
  name: "douyin.loginQrcode",
8177
8195
  route: "/fetch_login_qrcode",
8178
8196
  doc: {
@@ -8196,7 +8214,7 @@ const loginQrcode = defineEndpoint({
8196
8214
  *
8197
8215
  * 与旧版一致:`getMusicInfo` GET + a_bogus 签名。
8198
8216
  */
8199
- const musicInfo = defineEndpoint({
8217
+ const musicInfo = defineDouyinEndpoint({
8200
8218
  name: "douyin.musicInfo",
8201
8219
  route: "/fetch_music_work",
8202
8220
  doc: {
@@ -8222,7 +8240,7 @@ const musicInfo = defineEndpoint({
8222
8240
  * 其余 4 个各占新路径。
8223
8241
  * 与旧版一致:`getWorkDetail` GET + a_bogus 签名,返回原始响应。
8224
8242
  */
8225
- const parseWork = defineEndpoint({
8243
+ const parseWork = defineDouyinEndpoint({
8226
8244
  name: "douyin.parseWork",
8227
8245
  route: "/fetch_one_work",
8228
8246
  doc: {
@@ -8366,7 +8384,7 @@ const SEARCH_TYPE_FIELD = "__search_type";
8366
8384
  *
8367
8385
  * 与旧版一致:**不签名**。
8368
8386
  */
8369
- const search = defineEndpoint({
8387
+ const search = defineDouyinEndpoint({
8370
8388
  name: "douyin.search",
8371
8389
  route: "/fetch_search_info",
8372
8390
  doc: {
@@ -8423,50 +8441,47 @@ const search = defineEndpoint({
8423
8441
  judge: searchJudge,
8424
8442
  paginate: {
8425
8443
  maxPageSize: 15,
8444
+ page: type(),
8426
8445
  items: (page) => {
8427
- const p = page;
8428
- if (Array.isArray(p.user_list)) return p.user_list;
8429
- return p.data ?? [];
8446
+ if (Array.isArray(page.user_list)) return page.user_list;
8447
+ return page.data ?? [];
8430
8448
  },
8431
8449
  hasMore: (page) => page.has_more !== 0,
8432
8450
  nextParams: (params, page) => {
8433
8451
  const p = page;
8434
8452
  const nextSearchId = (typeof p.rid === "string" && p.rid.length > 0 ? p.rid : void 0) ?? (typeof p.log_pb?.impr_id === "string" ? p.log_pb.impr_id : void 0) ?? params.search_id;
8435
8453
  const currentOffset = params.offset ?? 0;
8436
- const items = Array.isArray(page && page.user_list) ? page.user_list : page.data ?? [];
8454
+ const items = Array.isArray(page.user_list) ? page.user_list : page.data ?? [];
8437
8455
  const nextOffset = typeof p.cursor === "number" && p.cursor > currentOffset ? p.cursor : currentOffset + items.length;
8438
8456
  return {
8439
8457
  ...params,
8440
8458
  search_id: nextSearchId,
8441
8459
  offset: nextOffset
8442
8460
  };
8461
+ },
8462
+ merge: ({ lastPage, items }, params) => {
8463
+ /**
8464
+ * 判别值 = **响应形状** 与 **请求类型** 的组合,各管一半:
8465
+ *
8466
+ * - **user 只看结构**:成功路径上 user 必有 `user_list`、video / general 必有 `data`
8467
+ * (`searchJudge` 就是按这条判反爬的),所以 `user_list` 在不在是可靠信号。
8468
+ * - **general 与 video 之间没有可靠结构信号**,只能取 `params.type`。
8469
+ * **反爬包装的形态跟逻辑类型不是一回事,不能当判据** —— 分页最后一页是单个
8470
+ * 合法 JSON,会被解析成对象,按 wire body 形态判会把 general 误标成 video。
8471
+ * - 两者矛盾时(请求 user、回的却是 data 形状)按 `general` 记:「不是 user」比
8472
+ * 「请求说是 user」更硬。
8473
+ */
8474
+ const requested = params.type ?? "general";
8475
+ const searchType = Array.isArray(lastPage.user_list) ? "user" : requested === "user" ? "general" : requested;
8476
+ const out = {
8477
+ ...lastPage,
8478
+ [SEARCH_TYPE_FIELD]: searchType
8479
+ };
8480
+ if (Array.isArray(lastPage.user_list)) out.user_list = items;
8481
+ else out.data = items;
8482
+ return out;
8443
8483
  }
8444
8484
  },
8445
- normalize: (decoded, params) => {
8446
- const { lastPage, items } = decoded;
8447
- const page = lastPage;
8448
- /**
8449
- * 判别值 = **响应形状** 与 **请求类型** 的组合,各管一半:
8450
- *
8451
- * - **user 只看结构**:成功路径上 user 必有 `user_list`、video / general 必有 `data`
8452
- * (`searchJudge` 就是按这条判反爬的),所以 `user_list` 在不在是可靠信号。
8453
- * - **general 与 video 之间没有可靠结构信号**,只能取 `params.type`。
8454
- * **反爬包装的形态跟逻辑类型不是一回事,不能当判据** —— 分页最后一页是单个
8455
- * 合法 JSON,会被解析成对象,按 wire body 形态判会把 general 误标成 video。
8456
- * - 两者矛盾时(请求 user、回的却是 data 形状)按 `general` 记:「不是 user」比
8457
- * 「请求说是 user」更硬 —— 记成 user 会让下游收窄到「有 `user_list` 的那一支」,
8458
- * 而那恰恰是它没有的东西。
8459
- */
8460
- const requested = params.type ?? "general";
8461
- const searchType = Array.isArray(page?.user_list) ? "user" : requested === "user" ? "general" : requested;
8462
- const out = {
8463
- ...page ?? {},
8464
- [SEARCH_TYPE_FIELD]: searchType
8465
- };
8466
- if (Array.isArray(page?.user_list)) out.user_list = items;
8467
- else out.data = items;
8468
- return out;
8469
- },
8470
8485
  response: type()
8471
8486
  });
8472
8487
  //#endregion
@@ -8476,7 +8491,7 @@ const search = defineEndpoint({
8476
8491
  *
8477
8492
  * 行为与旧版一致:`getWorkDetail` GET + a_bogus 签名。
8478
8493
  */
8479
- const slidesWork = defineEndpoint({
8494
+ const slidesWork = defineDouyinEndpoint({
8480
8495
  name: "douyin.slidesWork",
8481
8496
  route: "/fetch_slides_work",
8482
8497
  doc: {
@@ -8501,7 +8516,7 @@ const slidesWork = defineEndpoint({
8501
8516
  * 与旧版一致:`getSuggestWords` GET + a_bogus 签名,
8502
8517
  * Referer 指向 `https://www.douyin.com/search/{query}`。
8503
8518
  */
8504
- const suggestWords = defineEndpoint({
8519
+ const suggestWords = defineDouyinEndpoint({
8505
8520
  name: "douyin.suggestWords",
8506
8521
  route: "/fetch_suggest_words",
8507
8522
  doc: {
@@ -8529,7 +8544,7 @@ const suggestWords = defineEndpoint({
8529
8544
  *
8530
8545
  * 行为与旧版一致:`getWorkDetail` GET + a_bogus 签名。
8531
8546
  */
8532
- const textWork = defineEndpoint({
8547
+ const textWork = defineDouyinEndpoint({
8533
8548
  name: "douyin.textWork",
8534
8549
  route: "/fetch_text_work",
8535
8550
  doc: {
@@ -8554,7 +8569,7 @@ const textWork = defineEndpoint({
8554
8569
  * 与旧版一致:`getUserFavoriteList` GET + a_bogus 签名,
8555
8570
  * 游标是 `max_cursor`(`has_more === 1` 继续),Referer 指向用户主页。
8556
8571
  */
8557
- const userFavoriteList = defineEndpoint({
8572
+ const userFavoriteList = defineDouyinEndpoint({
8558
8573
  name: "douyin.userFavoriteList",
8559
8574
  route: "/fetch_user_favorite_list",
8560
8575
  doc: {
@@ -8584,14 +8599,11 @@ const userFavoriteList = defineEndpoint({
8584
8599
  nextParams: (params, page) => ({
8585
8600
  ...params,
8586
8601
  max_cursor: page.max_cursor?.toString() ?? "0"
8587
- })
8588
- },
8589
- normalize: (decoded) => {
8590
- const { lastPage, items } = decoded;
8591
- return {
8592
- ...lastPage ?? {},
8602
+ }),
8603
+ merge: ({ lastPage, items }) => ({
8604
+ ...lastPage,
8593
8605
  aweme_list: items
8594
- };
8606
+ })
8595
8607
  },
8596
8608
  response: type()
8597
8609
  });
@@ -8603,7 +8615,7 @@ const userFavoriteList = defineEndpoint({
8603
8615
  * 与旧版一致:`getUserProfile` GET + a_bogus 签名,
8604
8616
  * Referer 指向 `https://www.douyin.com/user/{sec_uid}`(由 {@link withDouyinReferer} 注入)。
8605
8617
  */
8606
- const userProfile$3 = defineEndpoint({
8618
+ const userProfile$3 = defineDouyinEndpoint({
8607
8619
  name: "douyin.userProfile",
8608
8620
  route: "/fetch_user_info",
8609
8621
  doc: {
@@ -8633,7 +8645,7 @@ const userProfile$3 = defineEndpoint({
8633
8645
  * 签名,游标是 `max_cursor`。**`hasMore` 判的是 `has_more === true`(布尔)**,
8634
8646
  * 与 userVideoList / userFavoriteList 的 `=== 1` 不同。
8635
8647
  */
8636
- const userRecommendList = defineEndpoint({
8648
+ const userRecommendList = defineDouyinEndpoint({
8637
8649
  name: "douyin.userRecommendList",
8638
8650
  route: "/fetch_user_recommend_list",
8639
8651
  doc: {
@@ -8663,14 +8675,11 @@ const userRecommendList = defineEndpoint({
8663
8675
  nextParams: (params, page) => ({
8664
8676
  ...params,
8665
8677
  max_cursor: page.max_cursor?.toString() ?? "0"
8666
- })
8667
- },
8668
- normalize: (decoded) => {
8669
- const { lastPage, items } = decoded;
8670
- return {
8671
- ...lastPage ?? {},
8678
+ }),
8679
+ merge: ({ lastPage, items }) => ({
8680
+ ...lastPage,
8672
8681
  aweme_list: items
8673
- };
8682
+ })
8674
8683
  },
8675
8684
  response: type()
8676
8685
  });
@@ -8683,7 +8692,7 @@ const userRecommendList = defineEndpoint({
8683
8692
  * 游标是 `max_cursor`(字符串,`has_more === 1` 继续),Referer 指向用户主页。
8684
8693
  * 最终形状 `{ ...最后一页, aweme_list }`。
8685
8694
  */
8686
- const userVideoList = defineEndpoint({
8695
+ const userVideoList = defineDouyinEndpoint({
8687
8696
  name: "douyin.userVideoList",
8688
8697
  route: "/fetch_user_post_videos",
8689
8698
  doc: {
@@ -8713,14 +8722,11 @@ const userVideoList = defineEndpoint({
8713
8722
  nextParams: (params, page) => ({
8714
8723
  ...params,
8715
8724
  max_cursor: page.max_cursor?.toString() ?? "0"
8716
- })
8717
- },
8718
- normalize: (decoded) => {
8719
- const { lastPage, items } = decoded;
8720
- return {
8721
- ...lastPage ?? {},
8725
+ }),
8726
+ merge: ({ lastPage, items }) => ({
8727
+ ...lastPage,
8722
8728
  aweme_list: items
8723
- };
8729
+ })
8724
8730
  },
8725
8731
  response: type()
8726
8732
  });
@@ -8737,7 +8743,7 @@ const userVideoList = defineEndpoint({
8737
8743
  */
8738
8744
  const douyinRegistry = {
8739
8745
  parseWork,
8740
- videoWork: defineEndpoint({
8746
+ videoWork: defineDouyinEndpoint({
8741
8747
  name: "douyin.videoWork",
8742
8748
  route: "/fetch_video_work",
8743
8749
  doc: {
@@ -10985,6 +10991,18 @@ const kuaishouDidState = createKuaishouDidState();
10985
10991
  */
10986
10992
  const kuaishouDidPrepare = async (ctx) => ({ cookie: buildKuaishouDidCookie(kuaishouDidState.getDid(), ctx.cookie) });
10987
10993
  //#endregion
10994
+ //#region src/platforms/kuaishou/endpoints/define.ts
10995
+ /**
10996
+ * 快手端点声明。
10997
+ *
10998
+ * 就是 {@link defineEndpoint} 绑定了快手签名器名联合 {@link KuaishouSignerName}:
10999
+ * `sign` 的字符串分支被收窄成 `'hxfalcon'`,写错名字编译期即报错,不必等运行时
11000
+ * 查表失败。运行时仍是恒等函数,其余行为与通用 `defineEndpoint` 完全一致。
11001
+ * @param def - 端点声明(`sign` 的名字受快手签名器表约束)
11002
+ * @returns 原样返回 `def`,带上推导好的具体类型
11003
+ */
11004
+ const defineKuaishouEndpoint = (def) => defineEndpoint(def);
11005
+ //#endregion
10988
11006
  //#region src/platforms/kuaishou/endpoints/comments.ts
10989
11007
  /**
10990
11008
  * 获取作品评论(H5 `photo/comment/list`,POST + 声明式翻页)。
@@ -11004,7 +11022,7 @@ const kuaishouDidPrepare = async (ctx) => ({ cookie: buildKuaishouDidCookie(kuai
11004
11022
  * 翻页:调用方传 `number` 指定目标条数,`pcursor` 由 `paginate` 声明管理,
11005
11023
  * 不暴露为自由参数。
11006
11024
  */
11007
- const comments = defineEndpoint({
11025
+ const comments = defineKuaishouEndpoint({
11008
11026
  name: "kuaishou.comments",
11009
11027
  route: "/fetch_work_comments",
11010
11028
  doc: {
@@ -11037,14 +11055,11 @@ const comments = defineEndpoint({
11037
11055
  nextParams: (params, page) => ({
11038
11056
  ...params,
11039
11057
  pcursor: page.pcursor ?? ""
11040
- })
11041
- },
11042
- normalize: (decoded) => {
11043
- const { lastPage, items } = decoded;
11044
- return {
11045
- ...lastPage ?? {},
11058
+ }),
11059
+ merge: ({ lastPage, items }) => ({
11060
+ ...lastPage,
11046
11061
  rootComments: items
11047
- };
11062
+ })
11048
11063
  },
11049
11064
  response: type()
11050
11065
  });
@@ -11120,7 +11135,7 @@ const resolveScanRange = (p) => {
11120
11135
  * 同许可):https://github.com/OduckO —— `src/platform/kuaishou/danmaku.ts` 与
11121
11136
  * `TODO.md:140-164`。
11122
11137
  */
11123
- const danmakuList = defineEndpoint({
11138
+ const danmakuList = defineKuaishouEndpoint({
11124
11139
  name: "kuaishou.danmakuList",
11125
11140
  route: "/fetch_danmaku_list",
11126
11141
  doc: {
@@ -11182,39 +11197,38 @@ const danmakuList = defineEndpoint({
11182
11197
  retryOn: ["PLATFORM_UNAVAILABLE"],
11183
11198
  partial: "tolerate",
11184
11199
  /**
11185
- * 合并多个窗口:去重 → 按时间升序 → 放回最后一个窗口的原位。
11200
+ * 合并多个窗口:去重 → 按时间升序 → 写回整体扫描范围。
11201
+ *
11202
+ * 这不是「归一化」,只是把分段拿到的条目放回原位(与 `comments` 的跨页累积同语义),
11203
+ * 所以返回类型仍描述真实 GraphQL 形状;只有 `positionFromInclude` / `positionToExclude`
11204
+ * 被改写成本次**整体**扫描范围(单窗口时就等于那一窗的值)。
11186
11205
  *
11187
- * 这不是「归一化」,只是把分段拿到的条目放回它本来的位置(与 `comments` 的跨页累积
11188
- * 同一个语义),所以返回类型仍然描述真实的 GraphQL 形状。只有
11189
- * `positionFromInclude` / `positionToExclude` 被改写成本次**整体**扫描范围 ——
11190
- * 单窗口调用时它们就等于那一窗的值。
11191
- * @param decoded - 每个窗口 decode 之后的值(失败窗口是 `undefined`)
11206
+ * 走 typed 的 aggregate(而非 normalize):`parts` 已收窄成 `Array<…Response | undefined>`、
11207
+ * `head` 恒有值,去重 / 排序 / `...head` 全程无断言。
11208
+ * @param value - 各窗口累积值(`parts` 含失败窗口的 undefined,`head` 恒有值)
11192
11209
  * @param params - 校验后的参数
11193
11210
  * @returns 合并后的响应
11194
11211
  */
11195
- normalize: (decoded, params) => {
11196
- const windows = decoded;
11212
+ aggregate: ({ parts, head }, params) => {
11197
11213
  const { from, to } = resolveScanRange(params);
11198
11214
  const merged = [];
11199
11215
  const seen = /* @__PURE__ */ new Set();
11200
- for (const win of windows) {
11216
+ for (const win of parts) {
11201
11217
  const node = win?.data?.visionDanmaku;
11202
11218
  if (!node) continue;
11203
- for (const row of node.danmakus ?? []) {
11204
- const key = row.id === void 0 || row.id === null ? `${row.position ?? 0}:${row.body ?? ""}` : String(row.id);
11205
- if (seen.has(key)) continue;
11206
- seen.add(key);
11219
+ for (const row of node.danmakus) {
11220
+ if (seen.has(row.id)) continue;
11221
+ seen.add(row.id);
11207
11222
  merged.push(row);
11208
11223
  }
11209
11224
  }
11210
- merged.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
11211
- const firstBody = windows.find((win) => Boolean(win?.data?.visionDanmaku));
11225
+ merged.sort((a, b) => a.position - b.position);
11212
11226
  return {
11213
- ...firstBody ?? {},
11227
+ ...head,
11214
11228
  data: {
11215
- ...firstBody?.data ?? {},
11229
+ ...head.data,
11216
11230
  visionDanmaku: {
11217
- ...firstBody?.data?.visionDanmaku ?? {},
11231
+ ...head.data.visionDanmaku,
11218
11232
  positionFromInclude: from,
11219
11233
  positionToExclude: to,
11220
11234
  danmakus: merged
@@ -11236,7 +11250,7 @@ const danmakuList = defineEndpoint({
11236
11250
  * 也正因为它零凭证,它是「无 cookie 能不能取到数」的**基线探针** ——
11237
11251
  * 这条通而 videoWork 不通,问题就在签名或 did,不在网络环境。
11238
11252
  */
11239
- const emojiList$2 = defineEndpoint({
11253
+ const emojiList$2 = defineKuaishouEndpoint({
11240
11254
  name: "kuaishou.emojiList",
11241
11255
  route: "/fetch_emoji_list",
11242
11256
  doc: {
@@ -11267,7 +11281,7 @@ const emojiList$2 = defineEndpoint({
11267
11281
  * 返回结构直接包含 `liveStream` / `author` / `gameInfo` / `noticeList` 等。
11268
11282
  * 响应判定用快手 judge(`result=1` 才进 data)。
11269
11283
  */
11270
- const liveRoomInfo = defineEndpoint({
11284
+ const liveRoomInfo = defineKuaishouEndpoint({
11271
11285
  name: "kuaishou.liveRoomInfo",
11272
11286
  route: "/fetch_live_room_info",
11273
11287
  doc: {
@@ -11517,7 +11531,7 @@ const mapLiveDetailToUserProfileLiveInfo = (detailData, author) => {
11517
11531
  * 到空值 —— 这正是「部分失败」语义:build 返回 12 个请求并发发出,
11518
11532
  * 失败分片在 normalize 里留空,**全部分片都失败时仍返回失败信封**。
11519
11533
  */
11520
- const userProfile$2 = defineEndpoint({
11534
+ const userProfile$2 = defineKuaishouEndpoint({
11521
11535
  name: "kuaishou.userProfile",
11522
11536
  route: "/fetch_user_profile",
11523
11537
  doc: {
@@ -11617,7 +11631,7 @@ const userProfile$2 = defineEndpoint({
11617
11631
  * `profilePublic` 的查询参数(`userWorkList` 是 `profilePublic` 的
11618
11632
  * 领域化封装)。
11619
11633
  */
11620
- const userWorkList = defineEndpoint({
11634
+ const userWorkList = defineKuaishouEndpoint({
11621
11635
  name: "kuaishou.userWorkList",
11622
11636
  route: "/fetch_user_work_list",
11623
11637
  doc: {
@@ -11649,19 +11663,17 @@ const userWorkList = defineEndpoint({
11649
11663
  nextParams: (params, page) => ({
11650
11664
  ...params,
11651
11665
  pcursor: page.data?.pcursor ?? ""
11652
- })
11653
- },
11654
- normalize: (decoded, params) => {
11655
- const { lastPage, items } = decoded;
11656
- const page = lastPage;
11657
- const pcursor = page?.data?.pcursor;
11658
- return {
11659
- principalId: params.principalId,
11660
- list: items,
11661
- pcursor: typeof pcursor === "string" ? pcursor : "",
11662
- hasMore: typeof pcursor === "string" && pcursor.length > 0,
11663
- result: page?.data?.result ?? 1
11664
- };
11666
+ }),
11667
+ merge: ({ lastPage, items }, params) => {
11668
+ const pcursor = lastPage.data?.pcursor;
11669
+ return {
11670
+ principalId: params.principalId,
11671
+ list: items,
11672
+ pcursor: typeof pcursor === "string" ? pcursor : "",
11673
+ hasMore: typeof pcursor === "string" && pcursor.length > 0,
11674
+ result: lastPage.data?.result ?? 1
11675
+ };
11676
+ }
11665
11677
  },
11666
11678
  response: type()
11667
11679
  });
@@ -11682,7 +11694,7 @@ const userWorkList = defineEndpoint({
11682
11694
  * 第 8 条 `/fetch_danmaku_list` 是弹幕。它**完全免鉴权**(不签名、不要 cookie)。
11683
11695
  */
11684
11696
  const kuaishouRegistry = {
11685
- videoWork: defineEndpoint({
11697
+ videoWork: defineKuaishouEndpoint({
11686
11698
  name: "kuaishou.videoWork",
11687
11699
  route: "/fetch_one_work",
11688
11700
  doc: {
@@ -11702,7 +11714,7 @@ const kuaishouRegistry = {
11702
11714
  },
11703
11715
  response: type()
11704
11716
  }),
11705
- videoWorkFull: defineEndpoint({
11717
+ videoWorkFull: defineKuaishouEndpoint({
11706
11718
  name: "kuaishou.videoWorkFull",
11707
11719
  route: "/fetch_one_work_full",
11708
11720
  doc: {
@@ -12206,6 +12218,18 @@ const xiaohongshuApiUrls$1 = {
12206
12218
  searchNotes: searchNotes$1
12207
12219
  };
12208
12220
  //#endregion
12221
+ //#region src/platforms/xiaohongshu/endpoints/define.ts
12222
+ /**
12223
+ * 小红书端点声明。
12224
+ *
12225
+ * 就是 {@link defineEndpoint} 绑定了小红书签名器名联合 {@link XiaohongshuSignerName}:
12226
+ * `sign` 的字符串分支被收窄成小红书那几个 `xhs-*` 名字,写错名字编译期即报错,不必
12227
+ * 等运行时查表失败。运行时仍是恒等函数,其余行为与通用 `defineEndpoint` 完全一致。
12228
+ * @param def - 端点声明(`sign` 的名字受小红书签名器表约束)
12229
+ * @returns 原样返回 `def`,带上推导好的具体类型
12230
+ */
12231
+ const defineXiaohongshuEndpoint = (def) => defineEndpoint(def);
12232
+ //#endregion
12209
12233
  //#region src/platforms/xiaohongshu/endpoints/emojiList.ts
12210
12234
  /**
12211
12235
  * 表情列表(GET,无参数)。
@@ -12213,7 +12237,7 @@ const xiaohongshuApiUrls$1 = {
12213
12237
  * 没有业务参数 —— 用空对象 schema(`zod.object({})`),
12214
12238
  * fetcher 方法可以不传 options。
12215
12239
  */
12216
- const emojiList = defineEndpoint({
12240
+ const emojiList = defineXiaohongshuEndpoint({
12217
12241
  name: "xiaohongshu.emojiList",
12218
12242
  route: "/fetch_emoji_list",
12219
12243
  doc: {
@@ -12244,7 +12268,7 @@ const emojiList = defineEndpoint({
12244
12268
  * 进 trace)。已有 a1 直接跳过 —— guest cookie 流程有真实网络开销,
12245
12269
  * 不能每次调用都重跑。
12246
12270
  */
12247
- const homeFeed = defineEndpoint({
12271
+ const homeFeed = defineXiaohongshuEndpoint({
12248
12272
  name: "xiaohongshu.homeFeed",
12249
12273
  route: "/fetch_home_feed",
12250
12274
  doc: {
@@ -12287,7 +12311,7 @@ const homeFeed = defineEndpoint({
12287
12311
  * `data.has_more` 是 boolean,与抖音的 `has_more === 1` 不同 ——
12288
12312
  * 差异收敛在 `paginate` 声明里,管线不感知。
12289
12313
  */
12290
- const noteComments = defineEndpoint({
12314
+ const noteComments = defineXiaohongshuEndpoint({
12291
12315
  name: "xiaohongshu.noteComments",
12292
12316
  route: "/fetch_note_comments",
12293
12317
  doc: {
@@ -12316,18 +12340,14 @@ const noteComments = defineEndpoint({
12316
12340
  nextParams: (params, page) => ({
12317
12341
  ...params,
12318
12342
  cursor: page.data?.cursor ?? ""
12319
- })
12320
- },
12321
- normalize: (decoded) => {
12322
- const { lastPage, items } = decoded;
12323
- const page = lastPage;
12324
- return {
12325
- ...page ?? {},
12343
+ }),
12344
+ merge: ({ lastPage, items }) => ({
12345
+ ...lastPage,
12326
12346
  data: {
12327
- ...page?.data ?? {},
12347
+ ...lastPage.data,
12328
12348
  comments: items
12329
12349
  }
12330
- };
12350
+ })
12331
12351
  },
12332
12352
  response: type()
12333
12353
  });
@@ -12338,7 +12358,7 @@ const noteComments = defineEndpoint({
12338
12358
  *
12339
12359
  * `note_id` 与 `xsec_token` 都要求 `min(1)`:空字符串会发出一个必败的请求。
12340
12360
  */
12341
- const noteDetail = defineEndpoint({
12361
+ const noteDetail = defineXiaohongshuEndpoint({
12342
12362
  name: "xiaohongshu.noteDetail",
12343
12363
  route: "/fetch_one_note",
12344
12364
  doc: {
@@ -12369,7 +12389,7 @@ const noteDetail = defineEndpoint({
12369
12389
  * `search_id` 是每次搜索的随机 id:在 `build` 里显式生成并传给
12370
12390
  * `api.searchNotes(data, searchId)`(api 保持纯函数,随机源归 `sign/`)。
12371
12391
  */
12372
- const searchNotes = defineEndpoint({
12392
+ const searchNotes = defineXiaohongshuEndpoint({
12373
12393
  name: "xiaohongshu.searchNotes",
12374
12394
  route: "/fetch_search_notes",
12375
12395
  doc: {
@@ -12402,7 +12422,7 @@ const searchNotes = defineEndpoint({
12402
12422
  * XYS_ 签名会被平台以 HTTP 406 拒绝,因此用 `'xhs-get-xyw-trace'`
12403
12423
  * (XYW_ GET 签名 + traceid),而不是给所有 GET 都换。
12404
12424
  */
12405
- const userNoteList = defineEndpoint({
12425
+ const userNoteList = defineXiaohongshuEndpoint({
12406
12426
  name: "xiaohongshu.userNoteList",
12407
12427
  route: "/fetch_user_notes",
12408
12428
  doc: {
@@ -12441,7 +12461,7 @@ const xiaohongshuRegistry = {
12441
12461
  homeFeed,
12442
12462
  noteDetail,
12443
12463
  noteComments,
12444
- userProfile: defineEndpoint({
12464
+ userProfile: defineXiaohongshuEndpoint({
12445
12465
  name: "xiaohongshu.userProfile",
12446
12466
  route: "/fetch_user_profile",
12447
12467
  doc: {
@@ -54020,7 +54040,7 @@ const buildOpenApiSpec = (options = {}) => {
54020
54040
  openapi: "3.1.0",
54021
54041
  info: {
54022
54042
  title: "amagi HTTP API",
54023
- version: options.version ?? "7.0.0-beta.3",
54043
+ version: options.version ?? "7.0.0-beta.5",
54024
54044
  description: [
54025
54045
  "由端点注册表派生的规范 —— 手写的那份已经漂移,这份不会(`gen-openapi --check` 进 CI)。",
54026
54046
  "",
@@ -56843,7 +56863,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType) {
56843
56863
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
56844
56864
  */
56845
56865
  const getVersion = () => {
56846
- return "7.0.0-beta.3";
56866
+ return "7.0.0-beta.5";
56847
56867
  };
56848
56868
  const VERSION = getVersion();
56849
56869
  /**