@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.
@@ -15,6 +15,78 @@ axios = require_rolldown_runtime.__toESM(axios, 1);
15
15
  let node_events = require("node:events");
16
16
  let _ikenxuan_xhshow_ts = require("@ikenxuan/xhshow-ts");
17
17
  //#region src/platforms/bilibili/api.ts
18
+ /**
19
+ * B站评论区类型代码(`comments` / `commentReplies` 的 `type` 参数)。
20
+ *
21
+ * 完整对照表(部分 oid 含义官方未明确,转写自 bilibili-API-collect):
22
+ *
23
+ * | 代码 | 评论区类型 | oid 的意义 |
24
+ * | --- | --- | --- |
25
+ * | 1 | 视频稿件 | 稿件 avid |
26
+ * | 2 | 话题 | 话题 id |
27
+ * | 4 | 活动 | 活动 id |
28
+ * | 5 | 小视频 | 小视频 id |
29
+ * | 6 | 小黑屋封禁信息 | 封禁公示 id |
30
+ * | 7 | 公告信息 | 公告 id |
31
+ * | 8 | 直播活动 | 直播间 id |
32
+ * | 9 | 活动稿件 | (?) |
33
+ * | 10 | 直播公告 | (?) |
34
+ * | 11 | 相簿(图片动态) | 相簿 id |
35
+ * | 12 | 专栏 | 专栏 cvid |
36
+ * | 13 | 票务 | (?) |
37
+ * | 14 | 音频 | 音频 auid |
38
+ * | 15 | 风纪委员会 | 众裁项目 id |
39
+ * | 16 | 点评 | (?) |
40
+ * | 17 | 动态(纯文字动态&分享) | 动态 id |
41
+ * | 18 | 播单 | (?) |
42
+ * | 19 | 音乐播单 | (?) |
43
+ * | 20 | 漫画 | (?) |
44
+ * | 21 | 漫画 | (?) |
45
+ * | 22 | 漫画 | 漫画 mcid |
46
+ * | 33 | 课程 | 课程 epid |
47
+ *
48
+ * @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
49
+ */
50
+ const commentTypeUnion = zod.default.union([
51
+ zod.default.literal(1),
52
+ zod.default.literal(2),
53
+ zod.default.literal(4),
54
+ zod.default.literal(5),
55
+ zod.default.literal(6),
56
+ zod.default.literal(7),
57
+ zod.default.literal(8),
58
+ zod.default.literal(9),
59
+ zod.default.literal(10),
60
+ zod.default.literal(11),
61
+ zod.default.literal(12),
62
+ zod.default.literal(13),
63
+ zod.default.literal(14),
64
+ zod.default.literal(15),
65
+ zod.default.literal(16),
66
+ zod.default.literal(17),
67
+ zod.default.literal(18),
68
+ zod.default.literal(19),
69
+ zod.default.literal(20),
70
+ zod.default.literal(21),
71
+ zod.default.literal(22),
72
+ zod.default.literal(33)
73
+ ], { error: "无效的评论区类型" });
74
+ /**
75
+ * `type` 参数的完整 schema:HTTP query 的字符串先 coerce 成数字,再 pipe 进合法代码
76
+ * 联合收窄 —— SDK 签名拿到的是 `CommentType` 而不是裸 `number`,非法代码报
77
+ * 「无效的评论区类型」。`zod.enum` 在 zod 4.6.5 不接受数字数组(values 恒空、恒报错),
78
+ * 所以用 literal 联合。
79
+ */
80
+ const commentTypeSchema = zod.default.coerce.number().pipe(commentTypeUnion);
81
+ /** `mode` 的合法值联合:0/1/2/3 之外(含 coerce 出的 NaN)统一报错 */
82
+ const commentModeUnion = zod.default.union([
83
+ zod.default.literal(0),
84
+ zod.default.literal(1),
85
+ zod.default.literal(2),
86
+ zod.default.literal(3)
87
+ ], { error: "排序方式只能是 0、1、2、3" });
88
+ /** `mode` 参数的完整 schema:coerce + 收窄到 `0 | 1 | 2 | 3` */
89
+ const commentModeSchema = zod.default.coerce.number().pipe(commentModeUnion);
18
90
  /** B站 API URL 构建类(所有方法只拼 URL,不发起请求) */
19
91
  var BilibiliAPI$1 = class {
20
92
  /** 获取登录基本信息 */
@@ -210,6 +282,36 @@ const stepsToSigner = (steps) => {
210
282
  * @param def - 端点声明
211
283
  */
212
284
  const defineEndpoint = (def) => def;
285
+ /**
286
+ * 标记一个端点参数为「内部参数」。
287
+ *
288
+ * 内部参数照常参与 zod 校验、`paginate.nextParams` 的写入与 URL 构造,但三个消费点
289
+ * 都看不见它:
290
+ * - **调用方签名**(fetcher / 静态 fetcher 的 options,见 `PublicParamsOf`)——对象
291
+ * 字面量里传入会被 excess property check 拒掉;
292
+ * - **openapi 的 query parameters**(`parametersOf` 经 `internalParamKeysOf` 过滤);
293
+ * - **文档参数表**(生成器读 openapi 产物,随上一条自动跟随)。
294
+ *
295
+ * 标记与字段声明焊在同一个表达式里(`pagination_str: internalParam(...)`),不存在
296
+ * 「写了字段忘了登记到某张清单」的漂移面 —— 这也是它不做成 EndpointDef 字段的理由:
297
+ * def 上已经够多键了,而且分离的清单必然漂移。运行时经 `.meta()` 挂进 zod 全局
298
+ * registry(文档生成器已在用 meta 传 examples,同一机制)。
299
+ */
300
+ const internalParam = (schema) => schema.meta({ amagiInternal: true });
301
+ /**
302
+ * 运行时判断一个 params 字段是否内部参数(openapi 生成器的过滤依据)。
303
+ *
304
+ * 读 `.meta()` 而不是别的运行时痕迹:meta 是 zod 官方的元数据通道,克隆与管道都保留。
305
+ */
306
+ const isInternalParam = (schema) => {
307
+ return (schema?.meta?.())?.amagiInternal === true;
308
+ };
309
+ /** 端点 params 里全部内部参数的键名(运行时)。非 object schema 返回空集 */
310
+ const internalParamKeysOf = (params) => {
311
+ const shape = params.shape;
312
+ if (shape === void 0) return /* @__PURE__ */ new Set();
313
+ return new Set(Object.keys(shape).filter((key) => isInternalParam(shape[key])));
314
+ };
213
315
  //#endregion
214
316
  //#region src/platforms/bilibili/endpoints/define.ts
215
317
  /**
@@ -514,45 +616,17 @@ const commentReplies$1 = defineBilibiliEndpoint({
514
616
  },
515
617
  params: zod.default.object({
516
618
  oid: zod.default.string().min(1, { error: "OID不能为空" }).describe("目标对象 ID,视频稿件填 avid"),
517
- type: zod.default.coerce.number().int().min(1).refine((val) => COMMENT_TYPES$1.includes(val), { error: "无效的评论区类型" }).describe("评论区类型,视频稿件填 1"),
619
+ type: commentTypeSchema.describe("评论区类型代码:1 视频稿件(oid=avid)、11 相簿/图片动态、12 专栏(cvid)、17 动态等;完整对照表见 bilibili-API-collect「评论区类型代码」"),
518
620
  root: zod.default.string().min(1, { error: "根评论ID不能为空" }).describe("根评论 ID,即要展开的一级评论"),
519
621
  number: zod.default.coerce.number().int().positive().default(20).optional().describe("该根评论下的回复条数,不翻页,默认 20")
520
622
  }),
521
623
  build: (p) => ({
522
624
  method: "GET",
523
- url: bilibiliApiUrls$1.getCommentReplies({
524
- ...p,
525
- type: p.type
526
- })
625
+ url: bilibiliApiUrls$1.getCommentReplies(p)
527
626
  }),
528
627
  retryOn: ["RISK_CONTROL"],
529
628
  response: type()
530
629
  });
531
- /** 评论区类型枚举 */
532
- const COMMENT_TYPES$1 = [
533
- 1,
534
- 2,
535
- 4,
536
- 5,
537
- 6,
538
- 7,
539
- 8,
540
- 9,
541
- 10,
542
- 11,
543
- 12,
544
- 13,
545
- 14,
546
- 15,
547
- 16,
548
- 17,
549
- 18,
550
- 19,
551
- 20,
552
- 21,
553
- 22,
554
- 33
555
- ];
556
630
  //#endregion
557
631
  //#region src/platforms/bilibili/endpoints/comments.ts
558
632
  /**
@@ -577,21 +651,17 @@ const comments$2 = defineBilibiliEndpoint({
577
651
  },
578
652
  params: zod.default.object({
579
653
  oid: zod.default.string().min(1, { error: "OID不能为空" }).describe("目标对象 ID,视频稿件填 avid"),
580
- type: zod.default.coerce.number().int().min(1).refine((val) => COMMENT_TYPES.includes(val), { error: "无效的评论区类型" }).describe("评论区类型,视频稿件填 1"),
654
+ type: commentTypeSchema.describe("评论区类型代码:1 视频稿件(oid=avid)、2 话题、11 相簿/图片动态、12 专栏(cvid)、14 音频(auid)、17 动态、33 课程(epid)等;完整对照表见 bilibili-API-collect「评论区类型代码」"),
581
655
  number: zod.default.coerce.number().int().positive().default(20).optional().describe("目标条数,自动翻页合并后去重,默认 20"),
582
- mode: zod.default.coerce.number().int().min(0).max(3).optional().describe("排序方式,默认 3"),
583
- pagination_str: zod.default.string().optional().describe("翻页游标,由端点接管,不用传"),
656
+ mode: commentModeSchema.optional().describe("排序方式:0 和 3 仅热度,1 按热度+按时间,2 仅时间;默认 3"),
657
+ pagination_str: internalParam(zod.default.string().optional().describe("翻页游标,由端点接管,不用传")),
584
658
  plat: zod.default.coerce.number().int().optional().describe("平台类型,默认 1"),
585
659
  seek_rpid: zod.default.string().optional().describe("定位到某条评论,默认空"),
586
660
  web_location: zod.default.string().optional().describe("web 位置参数,默认 1315875")
587
661
  }),
588
662
  build: (p) => ({
589
663
  method: "GET",
590
- url: bilibiliApiUrls$1.getComments({
591
- ...p,
592
- type: p.type,
593
- mode: p.mode
594
- })
664
+ url: bilibiliApiUrls$1.getComments(p)
595
665
  }),
596
666
  sign: [require_steps.wbi()],
597
667
  paginate: {
@@ -622,31 +692,6 @@ const comments$2 = defineBilibiliEndpoint({
622
692
  retryOn: ["RISK_CONTROL"],
623
693
  response: type()
624
694
  });
625
- /** 评论区类型枚举 */
626
- const COMMENT_TYPES = [
627
- 1,
628
- 2,
629
- 4,
630
- 5,
631
- 6,
632
- 7,
633
- 8,
634
- 9,
635
- 10,
636
- 11,
637
- 12,
638
- 13,
639
- 14,
640
- 15,
641
- 16,
642
- 17,
643
- 18,
644
- 19,
645
- 20,
646
- 21,
647
- 22,
648
- 33
649
- ];
650
695
  //#endregion
651
696
  //#region src/platforms/bilibili/endpoints/dynamicDetail.ts
652
697
  /**
@@ -1388,7 +1433,7 @@ var TypedEventEmitter = class extends node_events.EventEmitter {
1388
1433
  * @description 单例模式,所有模块共享同一个事件总线
1389
1434
  * @example
1390
1435
  * ```typescript
1391
- * import { amagiEvents } from '../model/events'
1436
+ * import { amagiEvents } from '@ikenxuan/amagi'
1392
1437
  *
1393
1438
  * // 监听 API 成功事件
1394
1439
  * amagiEvents.on('api:success', (data) => {
@@ -3786,7 +3831,15 @@ const SUCCESS_MESSAGE = "获取成功";
3786
3831
  * `T | undefined`)。数组回调里没有 `if` 可用,`filter` 又只认类型谓词
3787
3832
  * —— 这就是必须有守卫的场景:
3788
3833
  *
3789
- * ```ts
3834
+ * ```ts twoslash
3835
+ * import { isSuccess, type AmagiResult } from '@ikenxuan/amagi'
3836
+ *
3837
+ * declare const ids: string[]
3838
+ * declare const fetchOne: (id: string) => Promise<AmagiResult<Work>>
3839
+ * interface Work {
3840
+ * id: string
3841
+ * }
3842
+ *
3790
3843
  * const list: AmagiResult<Work>[] = await Promise.all(ids.map(fetchOne))
3791
3844
  * const works = list.filter(isSuccess).map((r) => r.data) // Work[]
3792
3845
  * ```
@@ -4112,6 +4165,37 @@ const resolveSigner = (decl, signers) => {
4112
4165
  return signer;
4113
4166
  };
4114
4167
  /**
4168
+ * 限额并发地跑异步任务,结果按 `items` 下标顺序收齐(单任务结局语义同
4169
+ * `Promise.allSettled`)。
4170
+ *
4171
+ * 派发顺序仍按 `items` 顺序(FIFO),只是同时在途的不超过 `limit`。单个任务的
4172
+ * 抛错经 `then` 的第二参落进对应下标、不惊动其它任务 —— 不写 `try/catch` 是因为
4173
+ * execute 的「唯一一处 catch」是源码级契约(execute.test.ts 有断言),这里与
4174
+ * `Promise.allSettled` 同构即可。
4175
+ */
4176
+ const settledWithConcurrency = async (items, limit, task) => {
4177
+ const queue = items.map((item, index) => ({
4178
+ item,
4179
+ index
4180
+ })).reverse();
4181
+ const settled = new Array(items.length);
4182
+ const worker = async () => {
4183
+ while (true) {
4184
+ const job = queue.pop();
4185
+ if (job === void 0) return;
4186
+ settled[job.index] = await task(job.item, job.index).then((value) => ({
4187
+ status: "fulfilled",
4188
+ value
4189
+ }), (reason) => ({
4190
+ status: "rejected",
4191
+ reason
4192
+ }));
4193
+ }
4194
+ };
4195
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
4196
+ return settled;
4197
+ };
4198
+ /**
4115
4199
  * 执行一条端点声明,产出信封。
4116
4200
  *
4117
4201
  * **永不 reject。** 唯一的例外是调用方自己传入的回调抛出(如会话的
@@ -4297,15 +4381,18 @@ const execute = async (def, input, options) => {
4297
4381
  const isMulti = signed.length > 1 || Array.isArray(built);
4298
4382
  const reason = isMulti ? "segment" : "initial";
4299
4383
  const tolerate = def.partial === "tolerate";
4384
+ const settled = await settledWithConcurrency(signed, 10, (spec, index) => runSpec(spec, reason, refreshFor(params, index)));
4300
4385
  let outcomes;
4301
4386
  if (tolerate) {
4302
- outcomes = (await Promise.allSettled(signed.map((spec, index) => runSpec(spec, reason, refreshFor(params, index))))).map((r) => r.status === "fulfilled" ? r.value : {
4387
+ outcomes = settled.map((r) => r.status === "fulfilled" ? r.value : {
4303
4388
  ok: false,
4304
4389
  error: classifyThrown(r.reason, "send")
4305
4390
  });
4306
4391
  if (outcomes.every((o) => !o.ok)) return failWith(outcomes[0].error);
4307
4392
  } else {
4308
- outcomes = await Promise.all(signed.map((spec, index) => runSpec(spec, reason, refreshFor(params, index))));
4393
+ const rejected = settled.find((r) => r.status === "rejected");
4394
+ if (rejected) throw rejected.reason;
4395
+ outcomes = settled.map((r) => r.value);
4309
4396
  const firstFailure = outcomes.find((o) => !o.ok);
4310
4397
  if (firstFailure) return failWith(firstFailure.error);
4311
4398
  }
@@ -5254,7 +5341,7 @@ const commentReplies = defineDouyinEndpoint({
5254
5341
  aweme_id: zod.default.string().min(1, { error: "作品ID不能为空" }).describe("作品 ID"),
5255
5342
  comment_id: zod.default.string().min(1, { error: "评论ID不能为空" }).describe("要取回复的评论 ID"),
5256
5343
  number: zod.default.coerce.number().int().min(1).optional().describe("目标条数,默认 3"),
5257
- cursor: zod.default.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传")
5344
+ cursor: internalParam(zod.default.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传"))
5258
5345
  }),
5259
5346
  build: (p) => ({
5260
5347
  method: "GET",
@@ -5298,7 +5385,7 @@ const comments$1 = defineDouyinEndpoint({
5298
5385
  params: zod.default.object({
5299
5386
  aweme_id: zod.default.string().min(1, { error: "作品ID不能为空" }).describe("作品 ID"),
5300
5387
  number: zod.default.coerce.number().int().min(1).optional().describe("目标条数,默认 50"),
5301
- cursor: zod.default.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传")
5388
+ cursor: internalParam(zod.default.coerce.number().int().min(0).optional().describe("翻页游标,一般不用传"))
5302
5389
  }),
5303
5390
  build: (p) => ({
5304
5391
  method: "GET",
@@ -5326,9 +5413,21 @@ const comments$1 = defineDouyinEndpoint({
5326
5413
  //#endregion
5327
5414
  //#region src/platforms/douyin/endpoints/danmakuList.ts
5328
5415
  /**
5416
+ * duration 的硬上限(毫秒,7 天)。
5417
+ *
5418
+ * 纯保险丝:分段并发已由执行器的限额池兜住,真实的超长作品(实测有 27 小时级、
5419
+ * 弹幕正常的直播回放)不受影响。挡的是天文数字的 `duration`(如 `9e15`,能通过
5420
+ * `.int()` 校验)—— `build` 的切段是同步 while 循环,那种值会在任何请求发出之前
5421
+ * 把事件循环卡死、把分段数组撑爆。真实作品离 7 天很远,这个值给足余量。
5422
+ *
5423
+ * 导出是因为校验层的平行 schema(validation/douyin.ts,v6 门面入口)要用同一个数。
5424
+ */
5425
+ const DANMAKU_MAX_DURATION_MS = 6048e5;
5426
+ /**
5329
5427
  * 弹幕列表(分段并发 + 合并排序 + `partial: 'tolerate'`)。
5330
5428
  *
5331
- * 总时长 ≤ 32000ms 单段直取;超过则按 32000ms 切成多段并发请求,
5429
+ * 总时长 ≤ 32000ms 单段直取;超过则按 32000ms 切成多段,经执行器的限额并发池
5430
+ * 发出(`SEGMENT_CONCURRENCY`,完成一个补一个),
5332
5431
  * **单段失败容忍**(失败段返回 null,其余段照常合并),最后按 `offset_time`
5333
5432
  * 升序合并,元信息(`extra` / `log_pb` / `status_code`)取第一段。
5334
5433
  *
@@ -5347,7 +5446,7 @@ const danmakuList$1 = defineDouyinEndpoint({
5347
5446
  aweme_id: zod.default.string().min(1, { error: "作品ID不能为空" }).describe("作品 ID"),
5348
5447
  start_time: zod.default.coerce.number().int().min(0).optional().describe("区间起点(毫秒),默认 0"),
5349
5448
  end_time: zod.default.coerce.number().int().min(0).optional().describe("区间终点(毫秒),默认取总时长"),
5350
- duration: zod.default.coerce.number().int().min(0).describe("作品总时长(毫秒),也是默认终点")
5449
+ duration: zod.default.coerce.number().int().min(0).max(DANMAKU_MAX_DURATION_MS, { error: "作品总时长不能超过 7 天" }).describe("作品总时长(毫秒),也是默认终点")
5351
5450
  }).refine((data) => data.end_time === void 0 || data.end_time <= data.duration, {
5352
5451
  error: "获取弹幕区间的结束时间不能超过视频总时长",
5353
5452
  path: ["end_time"]
@@ -5652,7 +5751,7 @@ const liveRoomInfo$1 = defineDouyinEndpoint({
5652
5751
  },
5653
5752
  params: zod.default.object({
5654
5753
  web_rid: zod.default.string().min(1, { error: "直播间ID不能为空" }).describe("直播间短号(链接里那段)"),
5655
- room_id: zod.default.string().optional().describe("内部透传,一般不用传")
5754
+ room_id: internalParam(zod.default.string().optional().describe("内部透传,一般不用传"))
5656
5755
  }),
5657
5756
  build: (p, ctx) => ({
5658
5757
  method: "GET",
@@ -5887,15 +5986,14 @@ const search = defineDouyinEndpoint({
5887
5986
  "video"
5888
5987
  ]).default("general").optional().describe("搜索类型,默认综合搜索"),
5889
5988
  number: zod.default.coerce.number().int().min(1).optional().describe("目标条数,默认 15"),
5890
- search_id: zod.default.string().optional().describe("翻页游标,一般不用传"),
5891
- offset: zod.default.coerce.number().int().min(0).optional().describe("翻页偏移,一般不用传")
5989
+ search_id: internalParam(zod.default.string().optional().describe("翻页游标,一般不用传")),
5990
+ offset: internalParam(zod.default.coerce.number().int().min(0).optional().describe("翻页偏移,一般不用传"))
5892
5991
  }),
5893
5992
  build: (p, ctx) => {
5894
5993
  const searchType = p.type ?? "general";
5895
5994
  return {
5896
5995
  method: "GET",
5897
5996
  url: douyinApiUrls$1.search({
5898
- keyword: p.query,
5899
5997
  query: p.query,
5900
5998
  type: searchType,
5901
5999
  number: p.number,
@@ -6067,7 +6165,7 @@ const userFavoriteList = defineDouyinEndpoint({
6067
6165
  params: zod.default.object({
6068
6166
  sec_uid: zod.default.string().min(1, { error: "用户ID不能为空" }).describe("用户 sec_uid,主页链接里那段"),
6069
6167
  number: zod.default.coerce.number().int().min(1).optional().describe("目标条数,默认 18"),
6070
- max_cursor: zod.default.string().optional().describe("翻页游标,一般不用传")
6168
+ max_cursor: internalParam(zod.default.string().optional().describe("翻页游标,一般不用传"))
6071
6169
  }),
6072
6170
  build: (p, ctx) => ({
6073
6171
  method: "GET",
@@ -6143,7 +6241,7 @@ const userRecommendList = defineDouyinEndpoint({
6143
6241
  params: zod.default.object({
6144
6242
  sec_uid: zod.default.string().min(1, { error: "用户ID不能为空" }).describe("用户 sec_uid,主页链接里那段"),
6145
6243
  number: zod.default.coerce.number().int().min(1).optional().describe("目标条数,默认 18"),
6146
- max_cursor: zod.default.string().optional().describe("翻页游标,一般不用传")
6244
+ max_cursor: internalParam(zod.default.string().optional().describe("翻页游标,一般不用传"))
6147
6245
  }),
6148
6246
  build: (p, ctx) => ({
6149
6247
  method: "GET",
@@ -6190,7 +6288,7 @@ const userVideoList = defineDouyinEndpoint({
6190
6288
  params: zod.default.object({
6191
6289
  sec_uid: zod.default.string().min(1, { error: "用户ID不能为空" }).describe("用户 sec_uid,主页链接里那段"),
6192
6290
  number: zod.default.coerce.number().int().min(1).optional().describe("目标条数,默认 18"),
6193
- max_cursor: zod.default.string().optional().describe("翻页游标,一般不用传")
6291
+ max_cursor: internalParam(zod.default.string().optional().describe("翻页游标,一般不用传"))
6194
6292
  }),
6195
6293
  build: (p, ctx) => ({
6196
6294
  method: "GET",
@@ -8574,9 +8672,10 @@ const DANMAKU_SCAN_STEP_MS = 6e4;
8574
8672
  /**
8575
8673
  * 扫描范围上限(毫秒,1 小时)。
8576
8674
  *
8577
- * 多窗口是 `Promise.all` 并发发出的,范围直接决定并发请求数
8578
- * (`ceil(范围 / 60000)`)。不设上限时一个手输的 `duration` 就能让一次调用打出上千个
8579
- * 请求,所以在参数层挡住:1 小时 → 最多 60 个窗口,与 `userProfile` 的 12 个同量级。
8675
+ * 多窗口经执行器的限额并发池发出(见 execute 的 `SEGMENT_CONCURRENCY`),范围决定
8676
+ * 的是**总**请求数(`ceil(范围 / 60000)`)。不设上限时一个手输的 `duration` 就能让
8677
+ * 一次调用打出上千个请求,所以在参数层挡住:1 小时 → 最多 60 个窗口,与
8678
+ * `userProfile` 的 12 个同量级。
8580
8679
  */
8581
8680
  const DANMAKU_MAX_RANGE_MS = 36e5;
8582
8681
  /**
@@ -9807,6 +9906,7 @@ const noteComments = defineXiaohongshuEndpoint({
9807
9906
  description: "取笔记评论列表。`number` 指定目标条数,翻页由端点自动完成。"
9808
9907
  },
9809
9908
  params: zod.default.object({
9909
+ cursor: internalParam(zod.default.string().optional().describe("分页游标,由端点接管,不用传")),
9810
9910
  note_id: zod.default.string().min(1, { error: "note_id 不能为空" }).describe("笔记 ID;从笔记分享链接里取"),
9811
9911
  xsec_token: zod.default.string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发"),
9812
9912
  number: zod.default.coerce.number().int().min(1).max(500).optional().describe("目标条数,默认一页")
@@ -51219,7 +51319,8 @@ const parametersOf = (def) => {
51219
51319
  unrepresentable: "any"
51220
51320
  });
51221
51321
  const required = new Set(json.required ?? []);
51222
- return Object.entries(json.properties ?? {}).map(([name, schema]) => {
51322
+ const internalKeys = internalParamKeysOf(def.params);
51323
+ return Object.entries(json.properties ?? {}).filter(([name]) => !internalKeys.has(name)).map(([name, schema]) => {
51223
51324
  const { description, ...rest } = schema;
51224
51325
  return {
51225
51326
  name,
@@ -51528,7 +51629,7 @@ const buildOpenApiSpec = (options = {}) => {
51528
51629
  openapi: "3.1.0",
51529
51630
  info: {
51530
51631
  title: "amagi HTTP API",
51531
- version: options.version ?? "7.0.0-beta.6",
51632
+ version: options.version ?? "7.0.0-beta.7",
51532
51633
  description: [
51533
51634
  "由端点注册表派生的规范 —— 手写的那份已经漂移,这份不会(`gen-openapi --check` 进 CI)。",
51534
51635
  "",
@@ -52081,6 +52182,7 @@ const createStaticFetcher = (platform, registry) => {
52081
52182
  * ```typescript
52082
52183
  * import { bilibiliFetcher } from '@ikenxuan/amagi'
52083
52184
  *
52185
+ * const cookie = 'SESSDATA=xxx; bili_jct=yyy'
52084
52186
  * const result = await bilibiliFetcher.fetchVideoInfo({ bvid: 'BV1xx411c7mD' }, cookie)
52085
52187
  * ```
52086
52188
  */
@@ -52290,6 +52392,7 @@ async function validatePassportVerifyCode(options, cookie, requestConfig) {
52290
52392
  * ```typescript
52291
52393
  * import { douyinFetcher } from '@ikenxuan/amagi'
52292
52394
  *
52395
+ * const cookie = 'ttwid=xxx'
52293
52396
  * const result = await douyinFetcher.fetchVideoWork({ aweme_id: '7123456789' }, cookie)
52294
52397
  * ```
52295
52398
  */
@@ -52307,6 +52410,8 @@ const douyinFetcher = {
52307
52410
  * @returns 绑定了 Cookie 的 Fetcher 对象,调用时无需传递 cookie
52308
52411
  * @example
52309
52412
  * ```typescript
52413
+ * import { createBoundDouyinFetcher } from '@ikenxuan/amagi'
52414
+ *
52310
52415
  * const fetcher = createBoundDouyinFetcher('your_cookie')
52311
52416
  * const result = await fetcher.fetchVideoWork({ aweme_id: '7123456789' })
52312
52417
  * ```
@@ -52328,6 +52433,7 @@ const createBoundDouyinFetcher = (cookie, requestConfig) => createFetcherFromReg
52328
52433
  * ```typescript
52329
52434
  * import { kuaishouFetcher } from '@ikenxuan/amagi'
52330
52435
  *
52436
+ * const cookie = 'did=xxx'
52331
52437
  * const result = await kuaishouFetcher.fetchVideoWork({ photoId: '3x123456789' }, cookie)
52332
52438
  * ```
52333
52439
  */
@@ -52350,6 +52456,7 @@ const createBoundKuaishouFetcher = (cookie, requestConfig) => createFetcherFromR
52350
52456
  * ```typescript
52351
52457
  * import { xiaohongshuFetcher } from '@ikenxuan/amagi'
52352
52458
  *
52459
+ * const cookie = 'a1=xxx; web_session=xxx'
52353
52460
  * const result = await xiaohongshuFetcher.fetchNoteDetail({ note_id: 'n1', xsec_token: 'tk' }, cookie)
52354
52461
  * ```
52355
52462
  */
@@ -52744,7 +52851,7 @@ const DouyinValidationSchemas = {
52744
52851
  aweme_id: zod.default.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
52745
52852
  start_time: zod.default.coerce.number({ error: "开始时间必须是数字" }).int({ error: "开始时间必须是整数" }).min(0, { error: "开始时间不能小于0" }).optional(),
52746
52853
  end_time: zod.default.coerce.number({ error: "结束时间必须是数字" }).int({ error: "结束时间必须是整数" }).min(0, { error: "结束时间不能小于0" }).optional(),
52747
- duration: zod.default.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" })
52854
+ duration: zod.default.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" }).max(DANMAKU_MAX_DURATION_MS, { error: "作品总时长不能超过 7 天" })
52748
52855
  }).refine((data) => {
52749
52856
  if (data.end_time !== void 0) return data.end_time <= data.duration;
52750
52857
  return true;
@@ -54292,7 +54399,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType) {
54292
54399
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
54293
54400
  */
54294
54401
  const getVersion = () => {
54295
- return "7.0.0-beta.6";
54402
+ return "7.0.0-beta.7";
54296
54403
  };
54297
54404
  const VERSION = getVersion();
54298
54405
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikenxuan/amagi",
3
- "version": "7.0.0-beta.6",
3
+ "version": "7.0.0-beta.7",
4
4
  "description": "抖音、B站的 web 端相关数据接口基于 Node.js 的实现",
5
5
  "keywords": [
6
6
  "api",
@@ -74,14 +74,14 @@
74
74
  "zod": "4.6.5"
75
75
  },
76
76
  "devDependencies": {
77
- "@ikenxuan/amagi-response-types": "0.1.0",
78
- "@ikenxuan/amagi-typegen": "0.1.0",
79
77
  "@types/express": "5.0.6",
80
78
  "sort-package-json": "4.0.0",
81
79
  "ts-json-schema-generator": "2.9.0",
82
- "tsdown": "^0.23.0"
80
+ "tsdown": "^0.23.0",
81
+ "@ikenxuan/amagi-response-types": "0.1.0",
82
+ "@ikenxuan/amagi-typegen": "0.1.0"
83
83
  },
84
- "timestamp": "2026-09-29T22:13:19.509Z",
84
+ "timestamp": "2026-10-02T19:47:49.892Z",
85
85
  "scripts": {
86
86
  "build": "tsdown",
87
87
  "dev": "cross-env LOG_LEVEL=debug tsx watch src/dev.ts",