@ikenxuan/amagi 7.0.0-beta.6 → 7.0.0-beta.7

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.
@@ -9,6 +9,78 @@ import axios, { AxiosError, AxiosHeaders, mergeConfig } from "axios";
9
9
  import { EventEmitter } from "node:events";
10
10
  import { FingerprintGenerator, Xhshow } from "@ikenxuan/xhshow-ts";
11
11
  //#region src/platforms/bilibili/api.ts
12
+ /**
13
+ * B站评论区类型代码(`comments` / `commentReplies` 的 `type` 参数)。
14
+ *
15
+ * 完整对照表(部分 oid 含义官方未明确,转写自 bilibili-API-collect):
16
+ *
17
+ * | 代码 | 评论区类型 | oid 的意义 |
18
+ * | --- | --- | --- |
19
+ * | 1 | 视频稿件 | 稿件 avid |
20
+ * | 2 | 话题 | 话题 id |
21
+ * | 4 | 活动 | 活动 id |
22
+ * | 5 | 小视频 | 小视频 id |
23
+ * | 6 | 小黑屋封禁信息 | 封禁公示 id |
24
+ * | 7 | 公告信息 | 公告 id |
25
+ * | 8 | 直播活动 | 直播间 id |
26
+ * | 9 | 活动稿件 | (?) |
27
+ * | 10 | 直播公告 | (?) |
28
+ * | 11 | 相簿(图片动态) | 相簿 id |
29
+ * | 12 | 专栏 | 专栏 cvid |
30
+ * | 13 | 票务 | (?) |
31
+ * | 14 | 音频 | 音频 auid |
32
+ * | 15 | 风纪委员会 | 众裁项目 id |
33
+ * | 16 | 点评 | (?) |
34
+ * | 17 | 动态(纯文字动态&分享) | 动态 id |
35
+ * | 18 | 播单 | (?) |
36
+ * | 19 | 音乐播单 | (?) |
37
+ * | 20 | 漫画 | (?) |
38
+ * | 21 | 漫画 | (?) |
39
+ * | 22 | 漫画 | 漫画 mcid |
40
+ * | 33 | 课程 | 课程 epid |
41
+ *
42
+ * @see https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/readme.md#%E8%AF%84%E8%AE%BA%E5%8C%BA%E7%B1%BB%E5%9E%8B%E4%BB%A3%E7%A0%81
43
+ */
44
+ const commentTypeUnion = zod.union([
45
+ zod.literal(1),
46
+ zod.literal(2),
47
+ zod.literal(4),
48
+ zod.literal(5),
49
+ zod.literal(6),
50
+ zod.literal(7),
51
+ zod.literal(8),
52
+ zod.literal(9),
53
+ zod.literal(10),
54
+ zod.literal(11),
55
+ zod.literal(12),
56
+ zod.literal(13),
57
+ zod.literal(14),
58
+ zod.literal(15),
59
+ zod.literal(16),
60
+ zod.literal(17),
61
+ zod.literal(18),
62
+ zod.literal(19),
63
+ zod.literal(20),
64
+ zod.literal(21),
65
+ zod.literal(22),
66
+ zod.literal(33)
67
+ ], { error: "无效的评论区类型" });
68
+ /**
69
+ * `type` 参数的完整 schema:HTTP query 的字符串先 coerce 成数字,再 pipe 进合法代码
70
+ * 联合收窄 —— SDK 签名拿到的是 `CommentType` 而不是裸 `number`,非法代码报
71
+ * 「无效的评论区类型」。`zod.enum` 在 zod 4.6.5 不接受数字数组(values 恒空、恒报错),
72
+ * 所以用 literal 联合。
73
+ */
74
+ const commentTypeSchema = zod.coerce.number().pipe(commentTypeUnion);
75
+ /** `mode` 的合法值联合:0/1/2/3 之外(含 coerce 出的 NaN)统一报错 */
76
+ const commentModeUnion = zod.union([
77
+ zod.literal(0),
78
+ zod.literal(1),
79
+ zod.literal(2),
80
+ zod.literal(3)
81
+ ], { error: "排序方式只能是 0、1、2、3" });
82
+ /** `mode` 参数的完整 schema:coerce + 收窄到 `0 | 1 | 2 | 3` */
83
+ const commentModeSchema = zod.coerce.number().pipe(commentModeUnion);
12
84
  /** B站 API URL 构建类(所有方法只拼 URL,不发起请求) */
13
85
  var BilibiliAPI$1 = class {
14
86
  /** 获取登录基本信息 */
@@ -204,6 +276,36 @@ const stepsToSigner = (steps) => {
204
276
  * @param def - 端点声明
205
277
  */
206
278
  const defineEndpoint = (def) => def;
279
+ /**
280
+ * 标记一个端点参数为「内部参数」。
281
+ *
282
+ * 内部参数照常参与 zod 校验、`paginate.nextParams` 的写入与 URL 构造,但三个消费点
283
+ * 都看不见它:
284
+ * - **调用方签名**(fetcher / 静态 fetcher 的 options,见 `PublicParamsOf`)——对象
285
+ * 字面量里传入会被 excess property check 拒掉;
286
+ * - **openapi 的 query parameters**(`parametersOf` 经 `internalParamKeysOf` 过滤);
287
+ * - **文档参数表**(生成器读 openapi 产物,随上一条自动跟随)。
288
+ *
289
+ * 标记与字段声明焊在同一个表达式里(`pagination_str: internalParam(...)`),不存在
290
+ * 「写了字段忘了登记到某张清单」的漂移面 —— 这也是它不做成 EndpointDef 字段的理由:
291
+ * def 上已经够多键了,而且分离的清单必然漂移。运行时经 `.meta()` 挂进 zod 全局
292
+ * registry(文档生成器已在用 meta 传 examples,同一机制)。
293
+ */
294
+ const internalParam = (schema) => schema.meta({ amagiInternal: true });
295
+ /**
296
+ * 运行时判断一个 params 字段是否内部参数(openapi 生成器的过滤依据)。
297
+ *
298
+ * 读 `.meta()` 而不是别的运行时痕迹:meta 是 zod 官方的元数据通道,克隆与管道都保留。
299
+ */
300
+ const isInternalParam = (schema) => {
301
+ return (schema?.meta?.())?.amagiInternal === true;
302
+ };
303
+ /** 端点 params 里全部内部参数的键名(运行时)。非 object schema 返回空集 */
304
+ const internalParamKeysOf = (params) => {
305
+ const shape = params.shape;
306
+ if (shape === void 0) return /* @__PURE__ */ new Set();
307
+ return new Set(Object.keys(shape).filter((key) => isInternalParam(shape[key])));
308
+ };
207
309
  //#endregion
208
310
  //#region src/platforms/bilibili/endpoints/define.ts
209
311
  /**
@@ -508,45 +610,17 @@ const commentReplies$1 = defineBilibiliEndpoint({
508
610
  },
509
611
  params: zod.object({
510
612
  oid: zod.string().min(1, { error: "OID不能为空" }).describe("目标对象 ID,视频稿件填 avid"),
511
- type: zod.coerce.number().int().min(1).refine((val) => COMMENT_TYPES$1.includes(val), { error: "无效的评论区类型" }).describe("评论区类型,视频稿件填 1"),
613
+ type: commentTypeSchema.describe("评论区类型代码:1 视频稿件(oid=avid)、11 相簿/图片动态、12 专栏(cvid)、17 动态等;完整对照表见 bilibili-API-collect「评论区类型代码」"),
512
614
  root: zod.string().min(1, { error: "根评论ID不能为空" }).describe("根评论 ID,即要展开的一级评论"),
513
615
  number: zod.coerce.number().int().positive().default(20).optional().describe("该根评论下的回复条数,不翻页,默认 20")
514
616
  }),
515
617
  build: (p) => ({
516
618
  method: "GET",
517
- url: bilibiliApiUrls$1.getCommentReplies({
518
- ...p,
519
- type: p.type
520
- })
619
+ url: bilibiliApiUrls$1.getCommentReplies(p)
521
620
  }),
522
621
  retryOn: ["RISK_CONTROL"],
523
622
  response: type()
524
623
  });
525
- /** 评论区类型枚举 */
526
- const COMMENT_TYPES$1 = [
527
- 1,
528
- 2,
529
- 4,
530
- 5,
531
- 6,
532
- 7,
533
- 8,
534
- 9,
535
- 10,
536
- 11,
537
- 12,
538
- 13,
539
- 14,
540
- 15,
541
- 16,
542
- 17,
543
- 18,
544
- 19,
545
- 20,
546
- 21,
547
- 22,
548
- 33
549
- ];
550
624
  //#endregion
551
625
  //#region src/platforms/bilibili/endpoints/comments.ts
552
626
  /**
@@ -571,21 +645,17 @@ const comments$2 = defineBilibiliEndpoint({
571
645
  },
572
646
  params: zod.object({
573
647
  oid: zod.string().min(1, { error: "OID不能为空" }).describe("目标对象 ID,视频稿件填 avid"),
574
- type: zod.coerce.number().int().min(1).refine((val) => COMMENT_TYPES.includes(val), { error: "无效的评论区类型" }).describe("评论区类型,视频稿件填 1"),
648
+ type: commentTypeSchema.describe("评论区类型代码:1 视频稿件(oid=avid)、2 话题、11 相簿/图片动态、12 专栏(cvid)、14 音频(auid)、17 动态、33 课程(epid)等;完整对照表见 bilibili-API-collect「评论区类型代码」"),
575
649
  number: zod.coerce.number().int().positive().default(20).optional().describe("目标条数,自动翻页合并后去重,默认 20"),
576
- mode: zod.coerce.number().int().min(0).max(3).optional().describe("排序方式,默认 3"),
577
- pagination_str: zod.string().optional().describe("翻页游标,由端点接管,不用传"),
650
+ mode: commentModeSchema.optional().describe("排序方式:0 和 3 仅热度,1 按热度+按时间,2 仅时间;默认 3"),
651
+ pagination_str: internalParam(zod.string().optional().describe("翻页游标,由端点接管,不用传")),
578
652
  plat: zod.coerce.number().int().optional().describe("平台类型,默认 1"),
579
653
  seek_rpid: zod.string().optional().describe("定位到某条评论,默认空"),
580
654
  web_location: zod.string().optional().describe("web 位置参数,默认 1315875")
581
655
  }),
582
656
  build: (p) => ({
583
657
  method: "GET",
584
- url: bilibiliApiUrls$1.getComments({
585
- ...p,
586
- type: p.type,
587
- mode: p.mode
588
- })
658
+ url: bilibiliApiUrls$1.getComments(p)
589
659
  }),
590
660
  sign: [wbi()],
591
661
  paginate: {
@@ -616,31 +686,6 @@ const comments$2 = defineBilibiliEndpoint({
616
686
  retryOn: ["RISK_CONTROL"],
617
687
  response: type()
618
688
  });
619
- /** 评论区类型枚举 */
620
- const COMMENT_TYPES = [
621
- 1,
622
- 2,
623
- 4,
624
- 5,
625
- 6,
626
- 7,
627
- 8,
628
- 9,
629
- 10,
630
- 11,
631
- 12,
632
- 13,
633
- 14,
634
- 15,
635
- 16,
636
- 17,
637
- 18,
638
- 19,
639
- 20,
640
- 21,
641
- 22,
642
- 33
643
- ];
644
689
  //#endregion
645
690
  //#region src/platforms/bilibili/endpoints/dynamicDetail.ts
646
691
  /**
@@ -1382,7 +1427,7 @@ var TypedEventEmitter = class extends EventEmitter {
1382
1427
  * @description 单例模式,所有模块共享同一个事件总线
1383
1428
  * @example
1384
1429
  * ```typescript
1385
- * import { amagiEvents } from '../model/events'
1430
+ * import { amagiEvents } from '@ikenxuan/amagi'
1386
1431
  *
1387
1432
  * // 监听 API 成功事件
1388
1433
  * amagiEvents.on('api:success', (data) => {
@@ -3780,7 +3825,15 @@ const SUCCESS_MESSAGE = "获取成功";
3780
3825
  * `T | undefined`)。数组回调里没有 `if` 可用,`filter` 又只认类型谓词
3781
3826
  * —— 这就是必须有守卫的场景:
3782
3827
  *
3783
- * ```ts
3828
+ * ```ts twoslash
3829
+ * import { isSuccess, type AmagiResult } from '@ikenxuan/amagi'
3830
+ *
3831
+ * declare const ids: string[]
3832
+ * declare const fetchOne: (id: string) => Promise<AmagiResult<Work>>
3833
+ * interface Work {
3834
+ * id: string
3835
+ * }
3836
+ *
3784
3837
  * const list: AmagiResult<Work>[] = await Promise.all(ids.map(fetchOne))
3785
3838
  * const works = list.filter(isSuccess).map((r) => r.data) // Work[]
3786
3839
  * ```
@@ -4106,6 +4159,37 @@ const resolveSigner = (decl, signers) => {
4106
4159
  return signer;
4107
4160
  };
4108
4161
  /**
4162
+ * 限额并发地跑异步任务,结果按 `items` 下标顺序收齐(单任务结局语义同
4163
+ * `Promise.allSettled`)。
4164
+ *
4165
+ * 派发顺序仍按 `items` 顺序(FIFO),只是同时在途的不超过 `limit`。单个任务的
4166
+ * 抛错经 `then` 的第二参落进对应下标、不惊动其它任务 —— 不写 `try/catch` 是因为
4167
+ * execute 的「唯一一处 catch」是源码级契约(execute.test.ts 有断言),这里与
4168
+ * `Promise.allSettled` 同构即可。
4169
+ */
4170
+ const settledWithConcurrency = async (items, limit, task) => {
4171
+ const queue = items.map((item, index) => ({
4172
+ item,
4173
+ index
4174
+ })).reverse();
4175
+ const settled = new Array(items.length);
4176
+ const worker = async () => {
4177
+ while (true) {
4178
+ const job = queue.pop();
4179
+ if (job === void 0) return;
4180
+ settled[job.index] = await task(job.item, job.index).then((value) => ({
4181
+ status: "fulfilled",
4182
+ value
4183
+ }), (reason) => ({
4184
+ status: "rejected",
4185
+ reason
4186
+ }));
4187
+ }
4188
+ };
4189
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
4190
+ return settled;
4191
+ };
4192
+ /**
4109
4193
  * 执行一条端点声明,产出信封。
4110
4194
  *
4111
4195
  * **永不 reject。** 唯一的例外是调用方自己传入的回调抛出(如会话的
@@ -4291,15 +4375,18 @@ const execute = async (def, input, options) => {
4291
4375
  const isMulti = signed.length > 1 || Array.isArray(built);
4292
4376
  const reason = isMulti ? "segment" : "initial";
4293
4377
  const tolerate = def.partial === "tolerate";
4378
+ const settled = await settledWithConcurrency(signed, 10, (spec, index) => runSpec(spec, reason, refreshFor(params, index)));
4294
4379
  let outcomes;
4295
4380
  if (tolerate) {
4296
- outcomes = (await Promise.allSettled(signed.map((spec, index) => runSpec(spec, reason, refreshFor(params, index))))).map((r) => r.status === "fulfilled" ? r.value : {
4381
+ outcomes = settled.map((r) => r.status === "fulfilled" ? r.value : {
4297
4382
  ok: false,
4298
4383
  error: classifyThrown(r.reason, "send")
4299
4384
  });
4300
4385
  if (outcomes.every((o) => !o.ok)) return failWith(outcomes[0].error);
4301
4386
  } else {
4302
- outcomes = await Promise.all(signed.map((spec, index) => runSpec(spec, reason, refreshFor(params, index))));
4387
+ const rejected = settled.find((r) => r.status === "rejected");
4388
+ if (rejected) throw rejected.reason;
4389
+ outcomes = settled.map((r) => r.value);
4303
4390
  const firstFailure = outcomes.find((o) => !o.ok);
4304
4391
  if (firstFailure) return failWith(firstFailure.error);
4305
4392
  }
@@ -5248,7 +5335,7 @@ const commentReplies = defineDouyinEndpoint({
5248
5335
  aweme_id: zod.string().min(1, { error: "作品ID不能为空" }).describe("作品 ID"),
5249
5336
  comment_id: zod.string().min(1, { error: "评论ID不能为空" }).describe("要取回复的评论 ID"),
5250
5337
  number: zod.coerce.number().int().min(1).optional().describe("目标条数,默认 3"),
5251
- cursor: zod.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传")
5338
+ cursor: internalParam(zod.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传"))
5252
5339
  }),
5253
5340
  build: (p) => ({
5254
5341
  method: "GET",
@@ -5292,7 +5379,7 @@ const comments$1 = defineDouyinEndpoint({
5292
5379
  params: zod.object({
5293
5380
  aweme_id: zod.string().min(1, { error: "作品ID不能为空" }).describe("作品 ID"),
5294
5381
  number: zod.coerce.number().int().min(1).optional().describe("目标条数,默认 50"),
5295
- cursor: zod.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传")
5382
+ cursor: internalParam(zod.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传"))
5296
5383
  }),
5297
5384
  build: (p) => ({
5298
5385
  method: "GET",
@@ -5320,9 +5407,21 @@ const comments$1 = defineDouyinEndpoint({
5320
5407
  //#endregion
5321
5408
  //#region src/platforms/douyin/endpoints/danmakuList.ts
5322
5409
  /**
5410
+ * duration 的硬上限(毫秒,7 天)。
5411
+ *
5412
+ * 纯保险丝:分段并发已由执行器的限额池兜住,真实的超长作品(实测有 27 小时级、
5413
+ * 弹幕正常的直播回放)不受影响。挡的是天文数字的 `duration`(如 `9e15`,能通过
5414
+ * `.int()` 校验)—— `build` 的切段是同步 while 循环,那种值会在任何请求发出之前
5415
+ * 把事件循环卡死、把分段数组撑爆。真实作品离 7 天很远,这个值给足余量。
5416
+ *
5417
+ * 导出是因为校验层的平行 schema(validation/douyin.ts,v6 门面入口)要用同一个数。
5418
+ */
5419
+ const DANMAKU_MAX_DURATION_MS = 6048e5;
5420
+ /**
5323
5421
  * 弹幕列表(分段并发 + 合并排序 + `partial: 'tolerate'`)。
5324
5422
  *
5325
- * 总时长 ≤ 32000ms 单段直取;超过则按 32000ms 切成多段并发请求,
5423
+ * 总时长 ≤ 32000ms 单段直取;超过则按 32000ms 切成多段,经执行器的限额并发池
5424
+ * 发出(`SEGMENT_CONCURRENCY`,完成一个补一个),
5326
5425
  * **单段失败容忍**(失败段返回 null,其余段照常合并),最后按 `offset_time`
5327
5426
  * 升序合并,元信息(`extra` / `log_pb` / `status_code`)取第一段。
5328
5427
  *
@@ -5341,7 +5440,7 @@ const danmakuList$1 = defineDouyinEndpoint({
5341
5440
  aweme_id: zod.string().min(1, { error: "作品ID不能为空" }).describe("作品 ID"),
5342
5441
  start_time: zod.coerce.number().int().min(0).optional().describe("区间起点(毫秒),默认 0"),
5343
5442
  end_time: zod.coerce.number().int().min(0).optional().describe("区间终点(毫秒),默认取总时长"),
5344
- duration: zod.coerce.number().int().min(0).describe("作品总时长(毫秒),也是默认终点")
5443
+ duration: zod.coerce.number().int().min(0).max(DANMAKU_MAX_DURATION_MS, { error: "作品总时长不能超过 7 天" }).describe("作品总时长(毫秒),也是默认终点")
5345
5444
  }).refine((data) => data.end_time === void 0 || data.end_time <= data.duration, {
5346
5445
  error: "获取弹幕区间的结束时间不能超过视频总时长",
5347
5446
  path: ["end_time"]
@@ -5646,7 +5745,7 @@ const liveRoomInfo$1 = defineDouyinEndpoint({
5646
5745
  },
5647
5746
  params: zod.object({
5648
5747
  web_rid: zod.string().min(1, { error: "直播间ID不能为空" }).describe("直播间短号(链接里那段)"),
5649
- room_id: zod.string().optional().describe("内部透传,一般不用传")
5748
+ room_id: internalParam(zod.string().optional().describe("内部透传,一般不用传"))
5650
5749
  }),
5651
5750
  build: (p, ctx) => ({
5652
5751
  method: "GET",
@@ -5881,15 +5980,14 @@ const search = defineDouyinEndpoint({
5881
5980
  "video"
5882
5981
  ]).default("general").optional().describe("搜索类型,默认综合搜索"),
5883
5982
  number: zod.coerce.number().int().min(1).optional().describe("目标条数,默认 15"),
5884
- search_id: zod.string().optional().describe("翻页游标,一般不用传"),
5885
- offset: zod.coerce.number().int().min(0).optional().describe("翻页偏移,一般不用传")
5983
+ search_id: internalParam(zod.string().optional().describe("翻页游标,一般不用传")),
5984
+ offset: internalParam(zod.coerce.number().int().min(0).optional().describe("翻页偏移,一般不用传"))
5886
5985
  }),
5887
5986
  build: (p, ctx) => {
5888
5987
  const searchType = p.type ?? "general";
5889
5988
  return {
5890
5989
  method: "GET",
5891
5990
  url: douyinApiUrls$1.search({
5892
- keyword: p.query,
5893
5991
  query: p.query,
5894
5992
  type: searchType,
5895
5993
  number: p.number,
@@ -6061,7 +6159,7 @@ const userFavoriteList = defineDouyinEndpoint({
6061
6159
  params: zod.object({
6062
6160
  sec_uid: zod.string().min(1, { error: "用户ID不能为空" }).describe("用户 sec_uid,主页链接里那段"),
6063
6161
  number: zod.coerce.number().int().min(1).optional().describe("目标条数,默认 18"),
6064
- max_cursor: zod.string().optional().describe("翻页游标,一般不用传")
6162
+ max_cursor: internalParam(zod.string().optional().describe("翻页游标,一般不用传"))
6065
6163
  }),
6066
6164
  build: (p, ctx) => ({
6067
6165
  method: "GET",
@@ -6137,7 +6235,7 @@ const userRecommendList = defineDouyinEndpoint({
6137
6235
  params: zod.object({
6138
6236
  sec_uid: zod.string().min(1, { error: "用户ID不能为空" }).describe("用户 sec_uid,主页链接里那段"),
6139
6237
  number: zod.coerce.number().int().min(1).optional().describe("目标条数,默认 18"),
6140
- max_cursor: zod.string().optional().describe("翻页游标,一般不用传")
6238
+ max_cursor: internalParam(zod.string().optional().describe("翻页游标,一般不用传"))
6141
6239
  }),
6142
6240
  build: (p, ctx) => ({
6143
6241
  method: "GET",
@@ -6184,7 +6282,7 @@ const userVideoList = defineDouyinEndpoint({
6184
6282
  params: zod.object({
6185
6283
  sec_uid: zod.string().min(1, { error: "用户ID不能为空" }).describe("用户 sec_uid,主页链接里那段"),
6186
6284
  number: zod.coerce.number().int().min(1).optional().describe("目标条数,默认 18"),
6187
- max_cursor: zod.string().optional().describe("翻页游标,一般不用传")
6285
+ max_cursor: internalParam(zod.string().optional().describe("翻页游标,一般不用传"))
6188
6286
  }),
6189
6287
  build: (p, ctx) => ({
6190
6288
  method: "GET",
@@ -8568,9 +8666,10 @@ const DANMAKU_SCAN_STEP_MS = 6e4;
8568
8666
  /**
8569
8667
  * 扫描范围上限(毫秒,1 小时)。
8570
8668
  *
8571
- * 多窗口是 `Promise.all` 并发发出的,范围直接决定并发请求数
8572
- * (`ceil(范围 / 60000)`)。不设上限时一个手输的 `duration` 就能让一次调用打出上千个
8573
- * 请求,所以在参数层挡住:1 小时 → 最多 60 个窗口,与 `userProfile` 的 12 个同量级。
8669
+ * 多窗口经执行器的限额并发池发出(见 execute 的 `SEGMENT_CONCURRENCY`),范围决定
8670
+ * 的是**总**请求数(`ceil(范围 / 60000)`)。不设上限时一个手输的 `duration` 就能让
8671
+ * 一次调用打出上千个请求,所以在参数层挡住:1 小时 → 最多 60 个窗口,与
8672
+ * `userProfile` 的 12 个同量级。
8574
8673
  */
8575
8674
  const DANMAKU_MAX_RANGE_MS = 36e5;
8576
8675
  /**
@@ -9801,6 +9900,7 @@ const noteComments = defineXiaohongshuEndpoint({
9801
9900
  description: "取笔记评论列表。`number` 指定目标条数,翻页由端点自动完成。"
9802
9901
  },
9803
9902
  params: zod.object({
9903
+ cursor: internalParam(zod.string().optional().describe("分页游标,由端点接管,不用传")),
9804
9904
  note_id: zod.string().min(1, { error: "note_id 不能为空" }).describe("笔记 ID;从笔记分享链接里取"),
9805
9905
  xsec_token: zod.string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发"),
9806
9906
  number: zod.coerce.number().int().min(1).max(500).optional().describe("目标条数,默认一页")
@@ -51213,7 +51313,8 @@ const parametersOf = (def) => {
51213
51313
  unrepresentable: "any"
51214
51314
  });
51215
51315
  const required = new Set(json.required ?? []);
51216
- return Object.entries(json.properties ?? {}).map(([name, schema]) => {
51316
+ const internalKeys = internalParamKeysOf(def.params);
51317
+ return Object.entries(json.properties ?? {}).filter(([name]) => !internalKeys.has(name)).map(([name, schema]) => {
51217
51318
  const { description, ...rest } = schema;
51218
51319
  return {
51219
51320
  name,
@@ -51522,7 +51623,7 @@ const buildOpenApiSpec = (options = {}) => {
51522
51623
  openapi: "3.1.0",
51523
51624
  info: {
51524
51625
  title: "amagi HTTP API",
51525
- version: options.version ?? "7.0.0-beta.6",
51626
+ version: options.version ?? "7.0.0-beta.7",
51526
51627
  description: [
51527
51628
  "由端点注册表派生的规范 —— 手写的那份已经漂移,这份不会(`gen-openapi --check` 进 CI)。",
51528
51629
  "",
@@ -52075,6 +52176,7 @@ const createStaticFetcher = (platform, registry) => {
52075
52176
  * ```typescript
52076
52177
  * import { bilibiliFetcher } from '@ikenxuan/amagi'
52077
52178
  *
52179
+ * const cookie = 'SESSDATA=xxx; bili_jct=yyy'
52078
52180
  * const result = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD' }, cookie)
52079
52181
  * ```
52080
52182
  */
@@ -52284,6 +52386,7 @@ async function validatePassportVerifyCode(options, cookie, requestConfig) {
52284
52386
  * ```typescript
52285
52387
  * import { douyinFetcher } from '@ikenxuan/amagi'
52286
52388
  *
52389
+ * const cookie = 'ttwid=xxx'
52287
52390
  * const result = await douyinFetcher.fetchVideoWork({ aweme_id: '7123456789' }, cookie)
52288
52391
  * ```
52289
52392
  */
@@ -52301,6 +52404,8 @@ const douyinFetcher = {
52301
52404
  * @returns 绑定了 Cookie 的 Fetcher 对象,调用时无需传递 cookie
52302
52405
  * @example
52303
52406
  * ```typescript
52407
+ * import { createBoundDouyinFetcher } from '@ikenxuan/amagi'
52408
+ *
52304
52409
  * const fetcher = createBoundDouyinFetcher('your_cookie')
52305
52410
  * const result = await fetcher.fetchVideoWork({ aweme_id: '7123456789' })
52306
52411
  * ```
@@ -52322,6 +52427,7 @@ const createBoundDouyinFetcher = (cookie, requestConfig) => createFetcherFromReg
52322
52427
  * ```typescript
52323
52428
  * import { kuaishouFetcher } from '@ikenxuan/amagi'
52324
52429
  *
52430
+ * const cookie = 'did=xxx'
52325
52431
  * const result = await kuaishouFetcher.fetchVideoWork({ photoId: '3x123456789' }, cookie)
52326
52432
  * ```
52327
52433
  */
@@ -52344,6 +52450,7 @@ const createBoundKuaishouFetcher = (cookie, requestConfig) => createFetcherFromR
52344
52450
  * ```typescript
52345
52451
  * import { xiaohongshuFetcher } from '@ikenxuan/amagi'
52346
52452
  *
52453
+ * const cookie = 'a1=xxx; web_session=xxx'
52347
52454
  * const result = await xiaohongshuFetcher.fetchNoteDetail({ note_id: 'n1', xsec_token: 'tk' }, cookie)
52348
52455
  * ```
52349
52456
  */
@@ -52738,7 +52845,7 @@ const DouyinValidationSchemas = {
52738
52845
  aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
52739
52846
  start_time: zod.coerce.number({ error: "开始时间必须是数字" }).int({ error: "开始时间必须是整数" }).min(0, { error: "开始时间不能小于0" }).optional(),
52740
52847
  end_time: zod.coerce.number({ error: "结束时间必须是数字" }).int({ error: "结束时间必须是整数" }).min(0, { error: "结束时间不能小于0" }).optional(),
52741
- duration: zod.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" })
52848
+ duration: zod.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" }).max(DANMAKU_MAX_DURATION_MS, { error: "作品总时长不能超过 7 天" })
52742
52849
  }).refine((data) => {
52743
52850
  if (data.end_time !== void 0) return data.end_time <= data.duration;
52744
52851
  return true;
@@ -54286,7 +54393,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType) {
54286
54393
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
54287
54394
  */
54288
54395
  const getVersion = () => {
54289
- return "7.0.0-beta.6";
54396
+ return "7.0.0-beta.7";
54290
54397
  };
54291
54398
  const VERSION = getVersion();
54292
54399
  /**