@ikenxuan/amagi 5.10.1 → 5.11.1

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.
@@ -4,8 +4,8 @@ import url from "node:url";
4
4
  import { Chalk } from "chalk";
5
5
  import log4js from "log4js";
6
6
  import axios, { AxiosError } from "axios";
7
- import crypto from "node:crypto";
8
7
  import zod from "zod";
8
+ import crypto from "node:crypto";
9
9
  import { Xhshow } from "@ikenxuan/xhshow-ts";
10
10
  import express from "express";
11
11
 
@@ -161,206 +161,6 @@ const logMiddleware = (pathsToLog) => {
161
161
  };
162
162
  };
163
163
 
164
- //#endregion
165
- //#region src/model/networks.ts
166
- /**
167
- * 清理User-Agent中的Edge标识,确保请求兼容性
168
- * @param userAgent - 原始User-Agent字符串
169
- * @returns 清理后的User-Agent字符串
170
- */
171
- const cleanUserAgent = (userAgent) => {
172
- return userAgent.replace(/\s+Edg\/[\d\.]+/g, "");
173
- };
174
- /**
175
- * 执行网络请求并返回数据
176
- * @param config - axios请求配置
177
- * @returns 响应数据
178
- */
179
- const fetchData = async (config) => {
180
- try {
181
- const cleanedConfig = { ...config };
182
- if (cleanedConfig.headers && cleanedConfig.headers["User-Agent"]) cleanedConfig.headers["User-Agent"] = cleanUserAgent(cleanedConfig.headers["User-Agent"]);
183
- return (await axios({
184
- ...cleanedConfig,
185
- validateStatus: () => true
186
- })).data;
187
- } catch (error) {
188
- if (error instanceof AxiosError) {
189
- logger.error("网络请求失败:", error.message);
190
- throw error;
191
- }
192
- throw error;
193
- }
194
- };
195
- const normalizeHeaders = (headers) => {
196
- if (headers && typeof headers.toJSON === "function") return headers.toJSON();
197
- return headers ?? {};
198
- };
199
- const fetchResponse = async (config) => {
200
- try {
201
- const cleanedConfig = { ...config };
202
- if (cleanedConfig.headers && cleanedConfig.headers["User-Agent"]) cleanedConfig.headers["User-Agent"] = cleanUserAgent(cleanedConfig.headers["User-Agent"]);
203
- return await axios({
204
- ...cleanedConfig,
205
- validateStatus: () => true
206
- });
207
- } catch (error) {
208
- if (error instanceof AxiosError) throw error;
209
- throw new Error("网络请求失败");
210
- }
211
- };
212
- /**
213
- * 获取响应头和数据
214
- * @param config - axios请求配置
215
- * @returns 包含headers和data的对象
216
- */
217
- const getHeadersAndData = async (config) => {
218
- try {
219
- const response = await fetchResponse(config);
220
- return {
221
- headers: normalizeHeaders(response.headers),
222
- data: response.data
223
- };
224
- } catch (error) {
225
- logger.error("获取响应头和数据失败:", error);
226
- return {
227
- headers: {},
228
- data: {}
229
- };
230
- }
231
- };
232
-
233
- //#endregion
234
- //#region src/platform/defaultConfigs.ts
235
- /**
236
- * 根据User-Agent生成对应的Sec-Ch-Ua值
237
- * @param userAgent - 用户代理字符串
238
- * @returns 对应的Sec-Ch-Ua值
239
- */
240
- const generateSecChUa = (userAgent) => {
241
- const chromeMatch = userAgent.match(/Chrome\/(\d+)/);
242
- const chromeVersion = chromeMatch ? chromeMatch[1] : "125";
243
- return `"Not)A;Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`;
244
- };
245
- /**
246
- * 抖音平台默认请求配置
247
- * @param cookie - 用户Cookie
248
- * @param requestConfig - 外部请求配置(优先级最高)
249
- * @returns 合并后的请求配置
250
- */
251
- const getDouyinDefaultConfig = (cookie, requestConfig) => {
252
- let finalUserAgent = requestConfig?.headers?.["User-Agent"] ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
253
- finalUserAgent = finalUserAgent.replace(/\s+Edg\/[\d\.]+/g, "");
254
- const defHeaders = {
255
- Accept: "application/json, text/plain, */*",
256
- "Accept-Encoding": "gzip, deflate, br, zstd",
257
- "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
258
- Cookie: cookie ? cookie.replace(/\s+/g, "") : "",
259
- Priority: "u=1, i",
260
- Referer: "https://www.douyin.com/",
261
- "Sec-Ch-Ua": generateSecChUa(finalUserAgent),
262
- "Sec-Ch-Ua-Mobile": "?0",
263
- "Sec-Ch-Ua-Platform": "\"Windows\"",
264
- "Sec-Fetch-Dest": "empty",
265
- "Sec-Fetch-Mode": "cors",
266
- "Sec-Fetch-Site": "same-origin",
267
- "User-Agent": finalUserAgent
268
- };
269
- return {
270
- method: "GET",
271
- timeout: 1e4,
272
- ...requestConfig,
273
- headers: {
274
- ...defHeaders,
275
- ...requestConfig?.headers ?? {}
276
- }
277
- };
278
- };
279
- /**
280
- * B站平台默认请求配置
281
- * @param cookie - 用户Cookie
282
- * @param requestConfig - 外部请求配置(优先级最高)
283
- * @returns 合并后的请求配置
284
- */
285
- const getBilibiliDefaultConfig = (cookie, requestConfig) => {
286
- const defHeaders = {
287
- Accept: "application/json, text/plain, */*",
288
- "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
289
- "Accept-Encoding": "gzip, deflate, br",
290
- Origin: "https://www.bilibili.com",
291
- Referer: "https://www.bilibili.com/",
292
- Priority: "u=1, i",
293
- "Sec-Ch-Ua": "\"Microsoft Edge\";v=\"141\", \"Chromium\";v=\"141\", \"Not_A Brand\";v=\"24\"",
294
- "Sec-Ch-Ua-Mobile": "?0",
295
- "Sec-Ch-Ua-Platform": "\"Windows\"",
296
- "Sec-Fetch-Dest": "empty",
297
- "Sec-Fetch-Mode": "cors",
298
- "Sec-Fetch-Site": "same-site",
299
- Cookie: cookie ? cookie.replace(/\s+/g, "") : ""
300
- };
301
- return {
302
- method: "GET",
303
- timeout: 1e4,
304
- ...requestConfig,
305
- headers: {
306
- ...defHeaders,
307
- ...requestConfig?.headers ?? {}
308
- }
309
- };
310
- };
311
- /**
312
- * 快手平台默认请求配置
313
- * @param cookie - 用户Cookie
314
- * @param requestConfig - 外部请求配置(优先级最高)
315
- * @returns 合并后的请求配置
316
- */
317
- const getKuaishouDefaultConfig = (cookie, requestConfig) => {
318
- const defHeaders = {
319
- Referer: "https://www.kuaishou.com/new-reco",
320
- Origin: "https://www.kuaishou.com",
321
- Accept: "application/json, text/plain, */*",
322
- "Accept-Encoding": "gzip, deflate, br, zstd",
323
- "Content-Type": "application/json",
324
- "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
325
- Priority: "u=0, i",
326
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
327
- Cookie: cookie ? cookie.replace(/\s+/g, "") : ""
328
- };
329
- return {
330
- method: "POST",
331
- timeout: 1e4,
332
- ...requestConfig,
333
- headers: {
334
- ...defHeaders,
335
- ...requestConfig?.headers ?? {}
336
- }
337
- };
338
- };
339
- /**
340
- * 获取小红书默认配置
341
- * @param cookie - 用户Cookie
342
- * @returns 小红书请求配置
343
- */
344
- const getXiaohongshuDefaultConfig = (cookie) => {
345
- return { headers: {
346
- accept: "application/json, text/plain, */*",
347
- "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
348
- "cache-control": "no-cache",
349
- "content-type": "application/json;charset=UTF-8",
350
- pragma: "no-cache",
351
- priority: "u=1, i",
352
- referer: "https://www.xiaohongshu.com/",
353
- "sec-ch-ua": "\"Microsoft Edge\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
354
- "sec-ch-ua-mobile": "?0",
355
- "sec-ch-ua-platform": "\"Windows\"",
356
- "sec-fetch-dest": "empty",
357
- "sec-fetch-mode": "cors",
358
- "sec-fetch-site": "same-site",
359
- "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0",
360
- cookie: cookie ?? ""
361
- } };
362
- };
363
-
364
164
  //#endregion
365
165
  //#region src/types/NetworksConfigType.ts
366
166
  /** 未知错误 */
@@ -513,1219 +313,1502 @@ let xiaohongshuAPIErrorCode = /* @__PURE__ */ function(xiaohongshuAPIErrorCode$1
513
313
  }({});
514
314
 
515
315
  //#endregion
516
- //#region src/platform/bilibili/API.ts
517
- var BiLiBiLiAPI = class {
518
- 登录基本信息() {
519
- return "https://api.bilibili.com/x/web-interface/nav";
520
- }
521
- 视频详细信息(data$1) {
522
- return `https://api.bilibili.com/x/web-interface/view?bvid=${data$1.bvid}`;
523
- }
524
- 视频流信息(data$1) {
525
- return `https://api.bilibili.com/x/player/playurl?avid=${data$1.avid}&cid=${data$1.cid}`;
526
- }
527
- /** 评论区类型,type参数详见 [评论区类型代码](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/readme.md#评论区类型代码) */
528
- 评论区明细(data$1) {
529
- const params = new URLSearchParams({
530
- oid: data$1.oid.toString(),
531
- type: data$1.type.toString(),
532
- mode: (data$1.mode ?? 3).toString(),
533
- plat: "1",
534
- seek_rpid: "",
535
- web_location: "1315875"
536
- });
537
- if (data$1.pagination_str) params.append("pagination_str", JSON.stringify({ offset: data$1.pagination_str }));
538
- else params.append("pagination_str", JSON.stringify({ offset: "" }));
539
- return `https://api.bilibili.com/x/v2/reply/wbi/main?${params.toString()}`;
540
- }
541
- 评论区状态(data$1) {
542
- return `https://api.bilibili.com/x/v2/reply/subject/description?type=${data$1.type}&oid=${data$1.oid}`;
543
- }
544
- /** 指定评论的回复 */
545
- 指定评论的回复(data$1) {
546
- return `https://api.bilibili.com/x/v2/reply/reply?type=${data$1.type}&oid=${data$1.oid}&root=${data$1.root}&ps=${data$1.number}`;
547
- }
548
- 表情列表() {
549
- return "https://api.bilibili.com/x/emote/user/panel/web?business=reply&web_location=0.0";
550
- }
551
- 番剧明细(data$1) {
552
- if (data$1.ep_id) return `https://api.bilibili.com/pgc/view/web/season?ep_id=${data$1.ep_id}`;
553
- else if (data$1.season_id) return `https://api.bilibili.com/pgc/view/web/season?season_id=${data$1.season_id}`;
554
- else throw new Error("拟造接口地址出错,缺少 ep_id 或 season_id 参数");
555
- }
556
- 番剧视频流信息(data$1) {
557
- return `https://api.bilibili.com/pgc/player/web/playurl?cid=${data$1.cid}&ep_id=${data$1.ep_id}`;
558
- }
559
- 用户空间动态(data$1) {
560
- return `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?host_mid=${data$1.host_mid}&dm_img_switch=0&&features=itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,forwardListHidden,decorationCard,commentsNewVersion,onlyfansAssetsV2,ugcDelete,onlyfansQaCard`;
561
- }
562
- 动态详情(data$1) {
563
- return `https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?id=${data$1.dynamic_id}&features=itemOpusStyle,opusBigCover,onlyfansVote,endFooterHidden,decorationCard,onlyfansAssetsV2,ugcDelete,onlyfansQaCard,editable,opusPrivateVisible,avatarAutoTheme`;
564
- }
565
- 动态卡片信息(data$1) {
566
- return `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=${data$1.dynamic_id}`;
567
- }
568
- 用户名片信息(data$1) {
569
- return `https://api.bilibili.com/x/web-interface/card?mid=${data$1.host_mid}&photo=true`;
570
- }
571
- 直播间信息(data$1) {
572
- return `https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${data$1.room_id}`;
573
- }
574
- 直播间初始化信息(data$1) {
575
- return `https://api.live.bilibili.com/room/v1/Room/room_init?id=${data$1.room_id}`;
576
- }
577
- 申请二维码() {
578
- return "https://passport.bilibili.com/x/passport-login/web/qrcode/generate";
579
- }
580
- 二维码状态(data$1) {
581
- return `https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${data$1.qrcode_key}`;
582
- }
583
- 获取UP主总播放量(data$1) {
584
- return `https://api.bilibili.com/x/space/upstat?mid=${data$1.host_mid}`;
585
- }
586
- 专栏正文内容(data$1) {
587
- return `https://api.bilibili.com/x/article/view?id=${data$1.id}`;
588
- }
589
- 专栏显示卡片信息(data$1) {
590
- return `https://api.bilibili.com/x/article/cards?ids=${Array.isArray(data$1.ids) ? data$1.ids.join(",") : data$1.ids}`;
591
- }
592
- 专栏文章基本信息(data$1) {
593
- return `https://api.bilibili.com/x/article/viewinfo?id=${data$1.id}`;
594
- }
595
- 文集基本信息(data$1) {
596
- return `https://api.bilibili.com/x/article/list/web/articles?id=${data$1.id}`;
316
+ //#region src/validation/utils.ts
317
+ function smartNumber(errorMessage, minValue = 1, isInteger = false) {
318
+ if (isInteger) return zod.coerce.number({ error: errorMessage }).int({ error: `${errorMessage.replace("不能为空", "")}必须是整数,不能包含小数` }).min(minValue, { error: `${errorMessage.replace("不能为空", "")}必须大于等于${minValue}` });
319
+ else return zod.coerce.number({ error: errorMessage }).min(minValue, { error: `${errorMessage.replace("不能为空", "")}必须大于等于${minValue}` });
320
+ }
321
+ /**
322
+ * 智能正整数转换器 - 专门用于正整数类型的转换
323
+ * @param errorMessage - 自定义错误信息
324
+ * @returns Zod正整数验证器
325
+ */
326
+ const smartPositiveInteger = (errorMessage) => {
327
+ return smartNumber(errorMessage, 1, true);
328
+ };
329
+ /**
330
+ * 从页面HTML中提取用户信息
331
+ * @param html - 包含用户页面HTML的字符串
332
+ * @returns 提取到的用户信息对象或null
333
+ */
334
+ const extractCreatorInfoFromHtml = (html) => {
335
+ const match = html.match(/<script>window\.__INITIAL_STATE__=(.+)<\/script>/m);
336
+ if (!match) return null;
337
+ try {
338
+ const jsonStr = match[1].replace(/:undefined/g, ":null");
339
+ return JSON.parse(jsonStr)?.user?.userPageData ?? null;
340
+ } catch (error) {
341
+ console.error("解析用户信息失败:", error);
342
+ return null;
597
343
  }
598
344
  };
599
- /** 该类下的所有方法只会返回拼接好参数后的 Url 地址,需要手动请求该地址以获取数据 */
600
- const bilibiliApiUrls = new BiLiBiLiAPI();
601
345
 
602
346
  //#endregion
603
- //#region src/platform/bilibili/BilibiliApi.ts
604
- /**
605
- * 创建B站API方法的通用工厂函数
606
- * @template T - B站方法类型键名
607
- * @param methodType - 方法类型
608
- * @returns 返回配置好的API方法
609
- */
610
- const createBilibiliApiMethod = (methodType) => {
611
- return async (options, cookie) => {
612
- return await getBilibiliData(methodType, options, cookie);
613
- };
347
+ //#region src/validation/bilibili.ts
348
+ const BilibiliVideoParamsSchema = zod.object({
349
+ methodType: zod.literal("单个视频作品数据", { error: "方法类型必须是\"单个视频作品数据\"" }),
350
+ bvid: zod.string({ error: "BVID必须是字符串" }).min(1, { error: "BVID不能为空" })
351
+ });
352
+ const BilibiliVideoDownloadParamsSchema = zod.object({
353
+ methodType: zod.literal("单个视频下载信息数据", { error: "方法类型必须是\"单个视频下载信息数据\"" }),
354
+ avid: smartNumber("AVID不能为空", 1, true),
355
+ cid: smartNumber("CID不能为空", 1, true)
356
+ });
357
+ const BilibiliCommentParamsSchema = zod.object({
358
+ methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
359
+ oid: zod.string({ error: "OID必须是字符串" }).min(1, { error: "OID不能为空" }),
360
+ type: smartNumber("评论类型不能为空", 1, true).refine((val) => [
361
+ 1,
362
+ 2,
363
+ 4,
364
+ 5,
365
+ 6,
366
+ 7,
367
+ 8,
368
+ 9,
369
+ 10,
370
+ 11,
371
+ 12,
372
+ 13,
373
+ 14,
374
+ 15,
375
+ 16,
376
+ 17,
377
+ 18,
378
+ 19,
379
+ 20,
380
+ 21,
381
+ 22,
382
+ 33
383
+ ].includes(val), { error: "无效的评论区类型" }),
384
+ number: zod.coerce.number({ error: "评论数量必须是数字" }).int({ error: "评论数量必须是整数" }).positive({ error: "评论数量必须是正数" }).default(20).optional(),
385
+ pn: zod.coerce.number({ error: "页码必须是数字" }).int({ error: "页码必须是整数" }).positive({ error: "页码必须是正数" }).default(1).optional()
386
+ });
387
+ const BilibiliCommentReplyParamsSchema = zod.object({
388
+ methodType: zod.literal("指定评论的回复", { error: "方法类型必须是\"指定评论的回复\"" }),
389
+ oid: zod.string({ error: "OID必须是字符串" }).min(1, { error: "OID不能为空" }),
390
+ type: smartNumber("评论类型不能为空", 1, true).refine((val) => [
391
+ 1,
392
+ 2,
393
+ 4,
394
+ 5,
395
+ 6,
396
+ 7,
397
+ 8,
398
+ 9,
399
+ 10,
400
+ 11,
401
+ 12,
402
+ 13,
403
+ 14,
404
+ 15,
405
+ 16,
406
+ 17,
407
+ 18,
408
+ 19,
409
+ 20,
410
+ 21,
411
+ 22,
412
+ 33
413
+ ].includes(val), { error: "无效的评论区类型" }),
414
+ root: zod.string({ error: "根评论ID必须是字符串" }).min(1, { error: "根评论ID不能为空" }),
415
+ number: zod.coerce.number({ error: "评论数量必须是数字" }).int({ error: "评论数量必须是整数" }).positive({ error: "评论数量必须是正数" }).default(20).optional(),
416
+ pn: zod.coerce.number({ error: "页码必须是数字" }).int({ error: "页码必须是整数" }).positive({ error: "页码必须是正数" }).default(1).optional()
417
+ });
418
+ const BilibiliUserParamsSchema = zod.object({
419
+ methodType: zod.enum([
420
+ "用户主页数据",
421
+ "用户主页动态列表数据",
422
+ "获取UP主总播放量"
423
+ ], { error: "方法类型必须是指定的枚举值之一" }),
424
+ host_mid: smartNumber("UP主UID不能为空", 1, true)
425
+ });
426
+ const BilibiliEmojiParamsSchema = zod.object({ methodType: zod.literal("Emoji数据", { error: "方法类型必须是\"Emoji数据\"" }) });
427
+ const BilibiliBangumiInfoParamsSchema = zod.object({
428
+ methodType: zod.literal("番剧基本信息数据", { error: "方法类型必须是\"番剧基本信息数据\"" }),
429
+ ep_id: zod.string({ error: "番剧EP ID必须是字符串" }).min(1, { error: "番剧EP ID不能为空" }).optional(),
430
+ season_id: zod.string({ error: "番剧季度ID必须是字符串" }).optional()
431
+ }).refine((data$1) => data$1.ep_id ?? data$1.season_id, {
432
+ error: "ep_id 和 season_id 至少需要提供一个",
433
+ path: ["ep_id"]
434
+ });
435
+ const BilibiliBangumiStreamParamsSchema = zod.object({
436
+ methodType: zod.literal("番剧下载信息数据", { error: "方法类型必须是\"番剧下载信息数据\"" }),
437
+ cid: smartNumber("CID不能为空", 1, true),
438
+ ep_id: zod.string({ error: "番剧EP ID必须是字符串" }).min(1, { error: "番剧EP ID不能为空" })
439
+ });
440
+ const BilibiliDynamicParamsSchema = zod.object({
441
+ methodType: zod.enum(["动态详情数据", "动态卡片数据"], { error: "方法类型必须是\"动态详情数据\"或\"动态卡片数据\"" }),
442
+ dynamic_id: zod.string({ error: "动态ID必须是字符串" }).min(1, { error: "动态ID不能为空" })
443
+ });
444
+ const BilibiliLiveParamsSchema = zod.object({
445
+ methodType: zod.enum(["直播间信息", "直播间初始化信息"], { error: "方法类型必须是\"直播间信息\"或\"直播间初始化信息\"" }),
446
+ room_id: zod.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" })
447
+ });
448
+ const BilibiliLoginParamsSchema = zod.object({ methodType: zod.literal("登录基本信息", { error: "方法类型必须是\"登录基本信息\"" }) });
449
+ const BilibiliQrcodeParamsSchema = zod.object({ methodType: zod.literal("申请二维码", { error: "方法类型必须是\"申请二维码\"" }) });
450
+ const BilibiliQrcodeStatusParamsSchema = zod.object({
451
+ methodType: zod.literal("二维码状态", { error: "方法类型必须是\"二维码状态\"" }),
452
+ qrcode_key: zod.string({ error: "二维码key必须是字符串" }).min(1, { error: "二维码key不能为空" })
453
+ });
454
+ const BilibiliAv2BvParamsSchema = zod.object({
455
+ methodType: zod.literal("AV转BV", { error: "方法类型必须是\"AV转BV\"" }),
456
+ avid: zod.coerce.number({ error: "AVID必须是数字" }).int({ error: "AVID必须是整数" }).positive({ error: "AVID必须是正数" })
457
+ });
458
+ const BilibiliBv2AvParamsSchema = zod.object({
459
+ methodType: zod.literal("BV转AV", { error: "方法类型必须是\"BV转AV\"" }),
460
+ bvid: zod.string({ error: "BVID必须是字符串" }).min(1, { error: "BVID不能为空" })
461
+ });
462
+ const BilibiliArticleParamsSchema = zod.object({
463
+ methodType: zod.literal("专栏正文内容", { error: "方法类型必须是\"专栏正文内容\"" }),
464
+ id: zod.string({ error: "专栏ID必须是字符串" }).min(1, { error: "专栏ID不能为空" })
465
+ });
466
+ const BilibiliArticleCardParamsSchema = zod.object({
467
+ methodType: zod.literal("专栏显示卡片信息", { error: "方法类型必须是\"专栏显示卡片信息\"" }),
468
+ ids: zod.union([zod.array(zod.string({ error: "被查询的 id 列表必须是字符串数组" })).min(1, { error: "被查询的 id 列表不能为空" }), zod.string({ error: "被查询的 id 列表必须是字符串" }).min(1, { error: "被查询的 id 列表不能为空" })])
469
+ });
470
+ const BilibiliArticleInfoParamsSchema = zod.object({
471
+ methodType: zod.literal("专栏文章基本信息", { error: "方法类型必须是\"专栏文章基本信息\"" }),
472
+ id: zod.string({ error: "专栏ID必须是字符串" }).min(1, { error: "专栏ID不能为空" })
473
+ });
474
+ const BilibiliColumnInfoParamsSchema = zod.object({
475
+ methodType: zod.literal("文集基本信息", { error: "方法类型必须是\"文集基本信息\"" }),
476
+ id: zod.string({ error: "文集ID必须是字符串" }).min(1, { error: "文集ID不能为空" })
477
+ });
478
+ const BilibiliValidationSchemas = {
479
+ 单个视频作品数据: BilibiliVideoParamsSchema,
480
+ 单个视频下载信息数据: BilibiliVideoDownloadParamsSchema,
481
+ 评论数据: BilibiliCommentParamsSchema,
482
+ 指定评论的回复: BilibiliCommentReplyParamsSchema,
483
+ 用户主页数据: BilibiliUserParamsSchema,
484
+ 用户主页动态列表数据: BilibiliUserParamsSchema,
485
+ Emoji数据: BilibiliEmojiParamsSchema,
486
+ 番剧基本信息数据: BilibiliBangumiInfoParamsSchema,
487
+ 番剧下载信息数据: BilibiliBangumiStreamParamsSchema,
488
+ 动态详情数据: BilibiliDynamicParamsSchema,
489
+ 动态卡片数据: BilibiliDynamicParamsSchema,
490
+ 直播间信息: BilibiliLiveParamsSchema,
491
+ 直播间初始化信息: BilibiliLiveParamsSchema,
492
+ 登录基本信息: BilibiliLoginParamsSchema,
493
+ 申请二维码: BilibiliQrcodeParamsSchema,
494
+ 二维码状态: BilibiliQrcodeStatusParamsSchema,
495
+ 获取UP主总播放量: BilibiliUserParamsSchema,
496
+ AV转BV: BilibiliAv2BvParamsSchema,
497
+ BV转AV: BilibiliBv2AvParamsSchema,
498
+ 专栏正文内容: BilibiliArticleParamsSchema,
499
+ 专栏显示卡片信息: BilibiliArticleCardParamsSchema,
500
+ 专栏文章基本信息: BilibiliArticleInfoParamsSchema,
501
+ 文集基本信息: BilibiliColumnInfoParamsSchema
614
502
  };
615
- /**
616
- * 创建绑定cookie的B站API方法工厂函数
617
- * @template T - B站方法类型键名
618
- * @param methodType - 方法类型
619
- * @param cookie - 绑定的cookie
620
- * @returns 返回绑定了cookie的API方法
621
- */
622
- const createBoundBilibiliApiMethod = (methodType, cookie) => {
623
- return async (options) => {
624
- return await getBilibiliData(methodType, options, cookie);
625
- };
503
+ const BilibiliMethodRoutes = {
504
+ 单个视频作品数据: "/fetch_one_video",
505
+ 单个视频下载信息数据: "/fetch_video_playurl",
506
+ 评论数据: "/fetch_work_comments",
507
+ 指定评论的回复: "/fetch_comment_reply",
508
+ 用户主页数据: "/fetch_user_profile",
509
+ 用户主页动态列表数据: "/fetch_user_dynamic",
510
+ Emoji数据: "/fetch_emoji_list",
511
+ 番剧基本信息数据: "/fetch_bangumi_video_info",
512
+ 番剧下载信息数据: "/fetch_bangumi_video_playurl",
513
+ 动态详情数据: "/fetch_dynamic_info",
514
+ 动态卡片数据: "/fetch_dynamic_card",
515
+ 直播间信息: "/fetch_live_room_detail",
516
+ 直播间初始化信息: "/fetch_liveroom_def",
517
+ 登录基本信息: "/login_basic_info",
518
+ 申请二维码: "/new_login_qrcode",
519
+ 二维码状态: "/check_qrcode",
520
+ 获取UP主总播放量: "/fetch_user_full_view",
521
+ AV转BV: "/av_to_bv",
522
+ BV转AV: "/bv_to_av",
523
+ 专栏正文内容: "/fetch_article_content",
524
+ 专栏显示卡片信息: "/fetch_article_card",
525
+ 专栏文章基本信息: "/fetch_article_info",
526
+ 文集基本信息: "/fetch_column_info"
626
527
  };
627
- /**
628
- * B站相关 API 的命名空间。
629
- *
630
- * 部分接口可能不需要 Cookie 但建议传递有效的用户 Cookie,以获取更多数据。
631
- *
632
- * 提供了一系列方法,用于与B站相关的 API 进行交互。
633
- *
634
- * 每个方法都接受参数和 Cookie,返回 Promise,解析为统一格式的API响应。
635
- */
636
- const bilibili = {
637
- getVideoInfo: createBilibiliApiMethod("单个视频作品数据"),
638
- getVideoStream: createBilibiliApiMethod("单个视频下载信息数据"),
639
- getComments: createBilibiliApiMethod("评论数据"),
640
- getCommentReply: createBilibiliApiMethod("指定评论的回复"),
641
- getUserProfile: createBilibiliApiMethod("用户主页数据"),
642
- getUserDynamic: createBilibiliApiMethod("用户主页动态列表数据"),
643
- getEmojiList: createBilibiliApiMethod("Emoji数据"),
644
- getBangumiInfo: createBilibiliApiMethod("番剧基本信息数据"),
645
- getBangumiStream: createBilibiliApiMethod("番剧下载信息数据"),
646
- getDynamicInfo: createBilibiliApiMethod("动态详情数据"),
647
- getDynamicCard: createBilibiliApiMethod("动态卡片数据"),
648
- getLiveRoomDetail: createBilibiliApiMethod("直播间信息"),
649
- getLiveRoomInitInfo: createBilibiliApiMethod("直播间初始化信息"),
650
- getLoginBasicInfo: createBilibiliApiMethod("登录基本信息"),
651
- getLoginQrcode: createBilibiliApiMethod("申请二维码"),
652
- checkQrcodeStatus: createBilibiliApiMethod("二维码状态"),
653
- getUserTotalPlayCount: createBilibiliApiMethod("获取UP主总播放量"),
654
- convertAvToBv: createBilibiliApiMethod("AV转BV"),
655
- convertBvToAv: createBilibiliApiMethod("BV转AV"),
656
- getArticleContent: createBilibiliApiMethod("专栏正文内容"),
657
- getArticleCard: createBilibiliApiMethod("专栏显示卡片信息"),
658
- getArticleInfo: createBilibiliApiMethod("专栏文章基本信息"),
659
- getColumnInfo: createBilibiliApiMethod("文集基本信息")
528
+
529
+ //#endregion
530
+ //#region src/validation/douyin.ts
531
+ const DouyinWorkParamsSchema = zod.object({
532
+ methodType: zod.enum([
533
+ "视频作品数据",
534
+ "图集作品数据",
535
+ "合辑作品数据",
536
+ "聚合解析"
537
+ ], { error: "方法类型必须是指定的枚举值之一" }),
538
+ aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" })
539
+ });
540
+ const DouyinCommentParamsSchema = zod.object({
541
+ methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
542
+ aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
543
+ number: smartPositiveInteger("评论数量必须是正整数").optional().default(50),
544
+ cursor: zod.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional()
545
+ });
546
+ const DouyinHotWordsParamsSchema = zod.object({
547
+ methodType: zod.literal("热点词数据", { error: "方法类型必须是\"热点词数据\"" }),
548
+ query: zod.string({ error: "搜索词必须是字符串" }).min(1, { error: "搜索词不能为空" })
549
+ });
550
+ const DouyinSearchParamsSchema = zod.object({
551
+ methodType: zod.literal("搜索数据", { error: "方法类型必须是\"搜索数据\"" }),
552
+ query: zod.string({ error: "搜索词必须是字符串" }).min(1, { error: "搜索词不能为空" }),
553
+ type: zod.enum([
554
+ "综合",
555
+ "用户",
556
+ "视频"
557
+ ], { error: "搜索类型必须是\"综合\"、\"用户\"或\"视频\"" }).optional().default("综合"),
558
+ number: smartPositiveInteger("搜索数量必须是正整数").optional().default(10),
559
+ search_id: zod.string({ error: "搜索ID必须是字符串" }).optional()
560
+ });
561
+ const DouyinCommentReplyParamsSchema = zod.object({
562
+ methodType: zod.literal("指定评论回复数据", { error: "方法类型必须是\"指定评论回复数据\"" }),
563
+ aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
564
+ comment_id: zod.string({ error: "评论ID必须z串" }).min(1, { error: "评论ID不能为空" }),
565
+ number: smartPositiveInteger("评论数量必须是正整数").optional().default(5),
566
+ cursor: zod.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional()
567
+ });
568
+ const DouyinUserParamsSchema = zod.object({
569
+ methodType: zod.enum(["用户主页数据", "用户主页视频列表数据"], { error: "方法类型必须是指定的枚举值之一" }),
570
+ sec_uid: zod.string({ error: "用户ID必须是字符串" }).min(1, { error: "用户ID不能为空" })
571
+ });
572
+ const DouyinMusicParamsSchema = zod.object({
573
+ methodType: zod.literal("音乐数据", { error: "方法类型必须是\"音乐数据\"" }),
574
+ music_id: zod.string({ error: "音乐ID必须是字符串" }).min(1, { error: "音乐ID不能为空" })
575
+ });
576
+ const DouyinLiveRoomParamsSchema = zod.object({
577
+ methodType: zod.literal("直播间信息数据", { error: "方法类型必须是\"直播间信息数据\"" }),
578
+ web_rid: zod.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" }),
579
+ room_id: zod.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" })
580
+ });
581
+ const DouyinQrcodeParamsSchema = zod.object({
582
+ methodType: zod.literal("申请二维码数据", { error: "方法类型必须是\"申请二维码数据\"" }),
583
+ verify_fp: zod.string({ error: "fp指纹必须是字符串" }).min(1, { error: "fp指纹不能为空" })
584
+ });
585
+ const DouyinEmojiListParamsSchema = zod.object({ methodType: zod.literal("Emoji数据", { error: "方法类型必须是\"Emoji数据\"" }) });
586
+ const DouyinEmojiProParamsSchema = zod.object({ methodType: zod.literal("动态表情数据", { error: "方法类型必须是\"动态表情数据\"" }) });
587
+ const DouyinDanmakuParamsSchema = zod.object({
588
+ methodType: zod.literal("弹幕数据", { error: "方法类型必须是\"弹幕数据\"" }),
589
+ aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
590
+ start_time: zod.coerce.number({ error: "开始时间必须是数字" }).int({ error: "开始时间必须是整数" }).min(0, { error: "开始时间不能小于0" }).optional(),
591
+ end_time: zod.coerce.number({ error: "结束时间必须是数字" }).int({ error: "结束时间必须是整数" }).min(0, { error: "结束时间不能小于0" }).optional(),
592
+ duration: zod.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" })
593
+ }).refine((data$1) => {
594
+ if (data$1.end_time !== void 0) return data$1.end_time <= data$1.duration;
595
+ return true;
596
+ }, {
597
+ error: "获取弹幕区间的结束时间不能超过视频总时长",
598
+ path: ["end_time"]
599
+ }).refine((data$1) => {
600
+ if (data$1.start_time !== void 0 && data$1.end_time !== void 0) return data$1.start_time < data$1.end_time;
601
+ return true;
602
+ }, {
603
+ error: "获取弹幕区间的开始时间必须小于结束时间",
604
+ path: ["start_time"]
605
+ });
606
+ const DouyinValidationSchemas = {
607
+ 文字作品数据: DouyinWorkParamsSchema,
608
+ 聚合解析: DouyinWorkParamsSchema,
609
+ 视频作品数据: DouyinWorkParamsSchema,
610
+ 图集作品数据: DouyinWorkParamsSchema,
611
+ 合辑作品数据: DouyinWorkParamsSchema,
612
+ 评论数据: DouyinCommentParamsSchema,
613
+ 用户主页数据: DouyinUserParamsSchema,
614
+ 用户主页视频列表数据: DouyinUserParamsSchema,
615
+ 热点词数据: DouyinHotWordsParamsSchema,
616
+ 搜索数据: DouyinSearchParamsSchema,
617
+ 音乐数据: DouyinMusicParamsSchema,
618
+ 直播间信息数据: DouyinLiveRoomParamsSchema,
619
+ 申请二维码数据: DouyinQrcodeParamsSchema,
620
+ Emoji数据: DouyinEmojiListParamsSchema,
621
+ 动态表情数据: DouyinEmojiProParamsSchema,
622
+ 指定评论回复数据: DouyinCommentReplyParamsSchema,
623
+ 弹幕数据: DouyinDanmakuParamsSchema
660
624
  };
661
- /**
662
- * 创建绑定了cookie的B站API对象
663
- * @param cookie - 要绑定的cookie(可选)
664
- * @returns 绑定了cookie的B站API对象,调用时不需要再传递cookie
665
- */
666
- const createBoundBilibiliApi = (cookie, requestConfig) => {
667
- return {
668
- getVideoInfo: createBoundBilibiliApiMethod("单个视频作品数据", cookie),
669
- getVideoStream: createBoundBilibiliApiMethod("单个视频下载信息数据", cookie),
670
- getComments: createBoundBilibiliApiMethod("评论数据", cookie),
671
- getCommentReply: createBoundBilibiliApiMethod("指定评论的回复", cookie),
672
- getUserProfile: createBoundBilibiliApiMethod("用户主页数据", cookie),
673
- getUserDynamic: createBoundBilibiliApiMethod("用户主页动态列表数据", cookie),
674
- getEmojiList: createBoundBilibiliApiMethod("Emoji数据", cookie),
675
- getBangumiInfo: createBoundBilibiliApiMethod("番剧基本信息数据", cookie),
676
- getBangumiStream: createBoundBilibiliApiMethod("番剧下载信息数据", cookie),
677
- getDynamicInfo: createBoundBilibiliApiMethod("动态详情数据", cookie),
678
- getDynamicCard: createBoundBilibiliApiMethod("动态卡片数据", cookie),
679
- getLiveRoomDetail: createBoundBilibiliApiMethod("直播间信息", cookie),
680
- getLiveRoomInitInfo: createBoundBilibiliApiMethod("直播间初始化信息", cookie),
681
- getLoginBasicInfo: createBoundBilibiliApiMethod("登录基本信息", cookie),
682
- getLoginQrcode: createBoundBilibiliApiMethod("申请二维码", cookie),
683
- checkQrcodeStatus: createBoundBilibiliApiMethod("二维码状态", cookie),
684
- getUserTotalPlayCount: createBoundBilibiliApiMethod("获取UP主总播放量", cookie),
685
- convertAvToBv: createBoundBilibiliApiMethod("AV转BV", cookie),
686
- convertBvToAv: createBoundBilibiliApiMethod("BV转AV", cookie),
687
- getArticleContent: createBoundBilibiliApiMethod("专栏正文内容", cookie),
688
- getArticleCard: createBoundBilibiliApiMethod("专栏显示卡片信息", cookie),
689
- getArticleInfo: createBoundBilibiliApiMethod("专栏文章基本信息", cookie),
690
- getColumnInfo: createBoundBilibiliApiMethod("文集基本信息", cookie)
691
- };
625
+ const DouyinMethodRoutes = {
626
+ 聚合解析: "/fetch_one_work",
627
+ 文字作品数据: "/fetch_one_work",
628
+ 视频作品数据: "/fetch_one_work",
629
+ 图集作品数据: "/fetch_one_work",
630
+ 合辑作品数据: "/fetch_one_work",
631
+ 评论数据: "/fetch_work_comments",
632
+ 用户主页数据: "/fetch_user_info",
633
+ 用户主页视频列表数据: "/fetch_user_post_videos",
634
+ 搜索数据: "/fetch_search_info",
635
+ 热点词数据: "/fetch_suggest_words",
636
+ 音乐数据: "/fetch_music_work",
637
+ Emoji数据: "/fetch_emoji_list",
638
+ 动态表情数据: "/fetch_emoji_pro_list",
639
+ 直播间信息数据: "/fetch_user_live_videos",
640
+ 指定评论回复数据: "/fetch_video_comment_replies",
641
+ 弹幕数据: "/fetch_work_danmaku"
692
642
  };
693
643
 
694
644
  //#endregion
695
- //#region src/platform/bilibili/sign/bv2av.ts
696
- const XOR_CODE = 23442827791579n;
697
- const MASK_CODE = 2251799813685247n;
698
- const MAX_AID = 1n << 51n;
699
- const BASE = 58n;
700
- const data = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf";
701
- /**
702
- * av号转bv号
703
- * @param aid av号
704
- * @returns
705
- */
706
- const av2bv = (aid) => {
707
- const bytes = [
708
- "B",
709
- "V",
710
- "1",
711
- "0",
712
- "0",
713
- "0",
714
- "0",
715
- "0",
716
- "0",
717
- "0",
718
- "0",
719
- "0"
720
- ];
721
- let bvIndex = bytes.length - 1;
722
- let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
723
- while (tmp > 0) {
724
- bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
725
- tmp = tmp / BASE;
726
- bvIndex -= 1;
727
- }
728
- [bytes[3], bytes[9]] = [bytes[9], bytes[3]];
729
- [bytes[4], bytes[7]] = [bytes[7], bytes[4]];
730
- return bytes.join("");
645
+ //#region src/validation/kuaishou.ts
646
+ const KuaishouVideoParamsSchema = zod.object({
647
+ methodType: zod.literal("单个视频作品数据", { error: "方法类型必须是\"单个视频作品数据\"" }),
648
+ photoId: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" })
649
+ });
650
+ const KuaishouCommentParamsSchema = zod.object({
651
+ methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
652
+ photoId: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" })
653
+ });
654
+ const KuaishouEmojiParamsSchema = zod.object({ methodType: zod.literal("Emoji数据", { error: "方法类型必须是\"Emoji数据\"" }) });
655
+ const KuaishouValidationSchemas = {
656
+ 单个视频作品数据: KuaishouVideoParamsSchema,
657
+ 评论数据: KuaishouCommentParamsSchema,
658
+ Emoji数据: KuaishouEmojiParamsSchema
731
659
  };
732
- /**
733
- * bv号转av号
734
- * @param bvid bv号
735
- * @returns
736
- */
737
- const bv2av = (bvid) => {
738
- const bvidArr = Array.from(bvid);
739
- [bvidArr[3], bvidArr[9]] = [bvidArr[9], bvidArr[3]];
740
- [bvidArr[4], bvidArr[7]] = [bvidArr[7], bvidArr[4]];
741
- bvidArr.splice(0, 3);
742
- const tmp = bvidArr.reduce((pre, bvidChar) => pre * BASE + BigInt(data.indexOf(bvidChar)), 0n);
743
- return Number(tmp & MASK_CODE ^ XOR_CODE);
660
+ const KuaishouMethodRoutes = {
661
+ 单个视频作品数据: "/fetch_one_work",
662
+ 评论数据: "/fetch_work_comments",
663
+ Emoji数据: "/fetch_emoji_list"
744
664
  };
745
665
 
746
666
  //#endregion
747
- //#region src/platform/bilibili/sign/wbi.ts
667
+ //#region src/platform/xiaohongshu/sign/index.ts
748
668
  /**
749
- * 混合密钥编码表,用于对 imgKey 和 subKey 进行字符顺序打乱编码
669
+ * 小红书签名算法类
750
670
  */
751
- const mixinKeyEncTab = [
752
- 46,
753
- 47,
754
- 18,
755
- 2,
756
- 53,
757
- 8,
758
- 23,
759
- 32,
760
- 15,
761
- 50,
762
- 10,
763
- 31,
764
- 58,
765
- 3,
766
- 45,
767
- 35,
768
- 27,
769
- 43,
770
- 5,
771
- 49,
772
- 33,
773
- 9,
774
- 42,
775
- 19,
776
- 29,
777
- 28,
778
- 14,
779
- 39,
780
- 12,
781
- 38,
782
- 41,
783
- 13,
784
- 37,
785
- 48,
786
- 7,
787
- 16,
788
- 24,
789
- 55,
790
- 40,
791
- 61,
792
- 26,
793
- 17,
794
- 0,
795
- 1,
796
- 60,
797
- 51,
798
- 30,
799
- 4,
800
- 22,
801
- 25,
802
- 54,
803
- 21,
804
- 56,
805
- 59,
806
- 6,
807
- 63,
808
- 57,
809
- 62,
810
- 11,
811
- 36,
812
- 20,
813
- 34,
814
- 44,
815
- 52
816
- ];
817
- /**
818
- * 对 imgKey 和 subKey 进行字符顺序打乱编码
819
- * @param orig - 原始字符串数组,通常是 img_key + sub_key 的字符数组
820
- * @returns 返回经过编码表打乱后的32位字符串
821
- */
822
- const getMixinKey = (orig) => mixinKeyEncTab.map((n) => orig[n]).join("").slice(0, 32);
823
- /**
824
- * 为请求参数进行 WBI 签名
825
- * @param params - 请求参数对象,键值对形式
826
- * @param img_key - 图片密钥
827
- * @param sub_key - 子密钥
828
- * @returns 返回包含时间戳和签名的查询字符串
829
- */
830
- const encWbi = (params, img_key, sub_key) => {
831
- const mixin_key = getMixinKey(img_key + sub_key);
832
- const curr_time = Math.round(Date.now() / 1e3);
833
- const chr_filter = /[!'()*]/g;
834
- Object.assign(params, { wts: curr_time });
835
- const query = Object.keys(params).sort().map((key) => {
836
- const value = params[key].toString().replace(chr_filter, "");
837
- return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
838
- }).join("&");
839
- return `&wts=${curr_time}&w_rid=${crypto.createHash("md5").update(query + mixin_key).digest("hex")}`;
840
- };
841
- /**
842
- * 获取最新的 img_key 和 sub_key
843
- * @param cookie - 有效的用户 Cookie 字符串
844
- * @returns 返回包含 img_key 和 sub_key 的对象
845
- * @throws 当网络请求失败或响应格式不正确时抛出错误
846
- */
847
- const getWbiKeys = async (cookie) => {
848
- const { data: { wbi_img: { img_url, sub_url } } } = (await axios("https://api.bilibili.com/x/web-interface/nav", { headers: { Cookie: cookie } })).data;
849
- return {
850
- img_key: img_url.slice(img_url.lastIndexOf("/") + 1, img_url.lastIndexOf(".")),
851
- sub_key: sub_url.slice(sub_url.lastIndexOf("/") + 1, sub_url.lastIndexOf("."))
852
- };
853
- };
854
- /**
855
- * 对请求链接进行 WBI 签名
856
- * @param BASEURL - 完整的请求地址,可以是字符串或 URL 对象
857
- * @param cookie - 有效的用户 Cookie 字符串
858
- * @returns 返回包含 WBI 签名的查询字符串
859
- * @throws 当获取 WBI 密钥失败或 URL 解析失败时抛出错误
860
- */
861
- const wbi_sign = async (BASEURL, cookie) => {
862
- const web_keys = await getWbiKeys(cookie);
863
- const url$1 = new URL(BASEURL);
864
- const params = {};
865
- for (const [key, value] of url$1.searchParams.entries()) params[key] = value;
866
- return encWbi(params, web_keys.img_key, web_keys.sub_key);
671
+ var xiaohongshuSign = class {
672
+ static client = new Xhshow();
673
+ /**
674
+ * 生成GET请求的X-S签名
675
+ * @param path - API路径
676
+ * @param a1Cookie - a1 cookie值
677
+ * @param clientType - 客户端类型,默认为 'xhs-pc-web'
678
+ * @param params - 查询参数对象
679
+ * @returns X-S签名
680
+ */
681
+ static generateXSGet(path$1, a1Cookie, clientType = "xhs-pc-web", params = {}) {
682
+ return this.client.signXsGet(path$1, a1Cookie, clientType, params);
683
+ }
684
+ /**
685
+ * 生成POST请求的X-S签名
686
+ * @param path - API路径
687
+ * @param a1Cookie - a1 cookie值
688
+ * @param clientType - 客户端类型,默认为 'xhs-pc-web'
689
+ * @param body - 请求体对象
690
+ * @returns X-S签名
691
+ */
692
+ static generateXSPost(path$1, a1Cookie, clientType = "xhs-pc-web", body = {}) {
693
+ return this.client.signXsPost(path$1, a1Cookie, clientType, body);
694
+ }
695
+ /**
696
+ * 生成X-S签名(兼容旧接口)
697
+ * @param url - 请求URL
698
+ * @param body - 请求体
699
+ * @param userAgent - User-Agent(暂未使用)
700
+ * @param method - 请求方法,默认为 'POST'
701
+ * @param a1Cookie - a1 cookie值
702
+ * @returns X-S签名
703
+ */
704
+ static generateXS(url$1, body, userAgent, method = "POST", a1Cookie = "") {
705
+ try {
706
+ const urlObj = new URL(url$1);
707
+ const path$1 = urlObj.pathname + urlObj.search;
708
+ if (method.toUpperCase() === "GET") {
709
+ const params = typeof body === "object" ? body : {};
710
+ return this.generateXSGet(path$1, a1Cookie, "xhs-pc-web", params);
711
+ } else {
712
+ const requestBody = typeof body === "object" ? body : {};
713
+ return this.generateXSPost(path$1, a1Cookie, "xhs-pc-web", requestBody);
714
+ }
715
+ } catch (error) {
716
+ console.error("生成X-S签名失败:", error);
717
+ throw new Error(`签名生成失败: ${error}`);
718
+ }
719
+ }
720
+ /**
721
+ * 生成X-S-Common参数
722
+ * @param length - 长度
723
+ * @returns Base64编码的随机字符串
724
+ */
725
+ static generateXSCommon(length = 945) {
726
+ return crypto.randomBytes(length).toString("base64").replace(/=+$/, "");
727
+ }
728
+ /**
729
+ * 生成X-T时间戳
730
+ * @returns 当前时间戳字符串
731
+ */
732
+ static generateXT() {
733
+ return Date.now().toString();
734
+ }
735
+ /**
736
+ * 生成X-B3-Traceid
737
+ * @returns 16位随机字符串
738
+ */
739
+ static generateXB3Traceid() {
740
+ return Array.from({ length: 16 }, () => "abcdef0123456789"[Math.floor(Math.random() * 16)]).join("");
741
+ }
742
+ /**
743
+ * 从cookie字符串中提取a1值
744
+ * @param cookieString - 完整的cookie字符串
745
+ * @returns a1 cookie值
746
+ */
747
+ static extractA1FromCookie(cookieString) {
748
+ const match = cookieString.match(/a1=([^;]+)/);
749
+ return match ? match[1] : "";
750
+ }
751
+ /**
752
+ * 生成搜索ID
753
+ * @returns 搜索ID字符串
754
+ */
755
+ static getSearchId = () => (BigInt(Date.now()) << 64n) + BigInt(Math.floor(Math.random() * 2147483646)).toString(36);
867
756
  };
868
757
 
869
758
  //#endregion
870
- //#region src/utils/errors.ts
759
+ //#region src/platform/xiaohongshu/API.ts
871
760
  /**
872
- * API错误类
761
+ * 搜索排序类型枚举
873
762
  */
874
- var ApiError = class extends Error {
875
- code;
876
- platform;
763
+ let SearchSortType = /* @__PURE__ */ function(SearchSortType$1) {
877
764
  /**
878
- * 构造API错误
879
- * @param message - 错误消息
880
- * @param code - 错误代码
881
- * @param platform - 平台名称
765
+ * 默认排序
882
766
  */
883
- constructor(message, code = 500, platform = "unknown") {
884
- super(message);
885
- this.name = "ApiError";
886
- this.code = code;
887
- this.platform = platform;
888
- }
889
- };
767
+ SearchSortType$1["GENERAL"] = "general";
768
+ /**
769
+ * 最受欢迎(按热度降序)
770
+ */
771
+ SearchSortType$1["MOST_POPULAR"] = "popularity_descending";
772
+ /**
773
+ * 最新发布(按时间降序)
774
+ */
775
+ SearchSortType$1["LATEST"] = "time_descending";
776
+ return SearchSortType$1;
777
+ }({});
890
778
  /**
891
- * 参数验证错误类
779
+ * 搜索笔记类型枚举
892
780
  */
893
- var ValidationError = class ValidationError extends Error {
894
- errors;
895
- requestPath;
781
+ let SearchNoteType = /* @__PURE__ */ function(SearchNoteType$1) {
896
782
  /**
897
- * 构造参数验证错误
898
- * @param message - 错误消息
899
- * @param errors - 详细错误信息
900
- * @param requestPath - HTTP请求路径
783
+ * 默认(全部类型)
901
784
  */
902
- constructor(message, errors, requestPath) {
903
- super(message);
904
- this.name = "ValidationError";
905
- this.errors = errors;
906
- this.requestPath = requestPath;
907
- }
785
+ SearchNoteType$1[SearchNoteType$1["ALL"] = 0] = "ALL";
908
786
  /**
909
- * 从Zod错误创建验证错误
910
- * @param zodError - Zod验证错误
911
- * @param requestPath - HTTP请求路径
912
- * @returns 验证错误实例
787
+ * 仅视频
913
788
  */
914
- static fromZodError(zodError, requestPath) {
915
- return new ValidationError("参数验证失败", zodError.issues.map((err) => ({
916
- field: err.path.join("."),
917
- message: err.message
918
- })), requestPath);
919
- }
789
+ SearchNoteType$1[SearchNoteType$1["VIDEO"] = 1] = "VIDEO";
790
+ /**
791
+ * 仅图片
792
+ */
793
+ SearchNoteType$1[SearchNoteType$1["IMAGE"] = 2] = "IMAGE";
794
+ return SearchNoteType$1;
795
+ }({});
796
+ /**
797
+ * 构建查询字符串
798
+ * @param params - 参数对象
799
+ * @returns 查询字符串
800
+ */
801
+ const buildQueryString$1 = (params) => {
802
+ return Object.entries(params).filter(([_, value]) => value !== void 0 && value !== null).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
920
803
  };
921
804
  /**
922
- * 处理错误并返回统一格式
923
- * @param error - 错误对象
924
- * @param requestPath - HTTP请求路径(可选)
925
- * @returns 统一的错误响应格式
805
+ * 小红书API地址配置
926
806
  */
927
- const handleError = (error, requestPath) => {
928
- if (error instanceof ValidationError) return {
929
- code: 400,
930
- message: error.message,
931
- data: null,
932
- errors: error.errors,
933
- requestPath: error.requestPath ?? requestPath
934
- };
935
- if (error instanceof ApiError) return {
936
- code: error.code,
937
- message: error.message,
938
- data: null,
939
- platform: error.platform,
940
- requestPath
941
- };
942
- if (error instanceof zod.ZodError) return handleError(ValidationError.fromZodError(error, requestPath), requestPath);
943
- return {
944
- code: 500,
945
- message: error instanceof Error ? error.message : "未知错误",
946
- data: null,
947
- requestPath
948
- };
949
- };
950
-
951
- //#endregion
952
- //#region src/validation/utils.ts
953
- function smartNumber(errorMessage, minValue = 1, isInteger = false) {
954
- if (isInteger) return zod.coerce.number({ error: errorMessage }).int({ error: `${errorMessage.replace("不能为空", "")}必须是整数,不能包含小数` }).min(minValue, { error: `${errorMessage.replace("不能为空", "")}必须大于等于${minValue}` });
955
- else return zod.coerce.number({ error: errorMessage }).min(minValue, { error: `${errorMessage.replace("不能为空", "")}必须大于等于${minValue}` });
956
- }
957
- /**
958
- * 智能正整数转换器 - 专门用于正整数类型的转换
959
- * @param errorMessage - 自定义错误信息
960
- * @returns Zod正整数验证器
961
- */
962
- const smartPositiveInteger = (errorMessage) => {
963
- return smartNumber(errorMessage, 1, true);
964
- };
965
- /**
966
- * 从页面HTML中提取用户信息
967
- * @param html - 包含用户页面HTML的字符串
968
- * @returns 提取到的用户信息对象或null
969
- */
970
- const extractCreatorInfoFromHtml = (html) => {
971
- const match = html.match(/<script>window\.__INITIAL_STATE__=(.+)<\/script>/m);
972
- if (!match) return null;
973
- try {
974
- const jsonStr = match[1].replace(/:undefined/g, ":null");
975
- return JSON.parse(jsonStr)?.user?.userPageData ?? null;
976
- } catch (error) {
977
- console.error("解析用户信息失败:", error);
978
- return null;
979
- }
980
- };
981
-
982
- //#endregion
983
- //#region src/validation/bilibili.ts
984
- const BilibiliVideoParamsSchema = zod.object({
985
- methodType: zod.literal("单个视频作品数据", { error: "方法类型必须是\"单个视频作品数据\"" }),
986
- bvid: zod.string({ error: "BVID必须是字符串" }).min(1, { error: "BVID不能为空" })
987
- });
988
- const BilibiliVideoDownloadParamsSchema = zod.object({
989
- methodType: zod.literal("单个视频下载信息数据", { error: "方法类型必须是\"单个视频下载信息数据\"" }),
990
- avid: smartNumber("AVID不能为空", 1, true),
991
- cid: smartNumber("CID不能为空", 1, true)
992
- });
993
- const BilibiliCommentParamsSchema = zod.object({
994
- methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
995
- oid: zod.string({ error: "OID必须是字符串" }).min(1, { error: "OID不能为空" }),
996
- type: smartNumber("评论类型不能为空", 1, true).refine((val) => [
997
- 1,
998
- 2,
999
- 4,
1000
- 5,
1001
- 6,
1002
- 7,
1003
- 8,
1004
- 9,
1005
- 10,
1006
- 11,
1007
- 12,
1008
- 13,
1009
- 14,
1010
- 15,
1011
- 16,
1012
- 17,
1013
- 18,
1014
- 19,
1015
- 20,
1016
- 21,
1017
- 22,
1018
- 33
1019
- ].includes(val), { error: "无效的评论区类型" }),
1020
- number: zod.coerce.number({ error: "评论数量必须是数字" }).int({ error: "评论数量必须是整数" }).positive({ error: "评论数量必须是正数" }).default(20).optional(),
1021
- pn: zod.coerce.number({ error: "页码必须是数字" }).int({ error: "页码必须是整数" }).positive({ error: "页码必须是正数" }).default(1).optional()
1022
- });
1023
- const BilibiliCommentReplyParamsSchema = zod.object({
1024
- methodType: zod.literal("指定评论的回复", { error: "方法类型必须是\"指定评论的回复\"" }),
1025
- oid: zod.string({ error: "OID必须是字符串" }).min(1, { error: "OID不能为空" }),
1026
- type: smartNumber("评论类型不能为空", 1, true).refine((val) => [
1027
- 1,
1028
- 2,
1029
- 4,
1030
- 5,
1031
- 6,
1032
- 7,
1033
- 8,
1034
- 9,
1035
- 10,
1036
- 11,
1037
- 12,
1038
- 13,
1039
- 14,
1040
- 15,
1041
- 16,
1042
- 17,
1043
- 18,
1044
- 19,
1045
- 20,
1046
- 21,
1047
- 22,
1048
- 33
1049
- ].includes(val), { error: "无效的评论区类型" }),
1050
- root: zod.string({ error: "根评论ID必须是字符串" }).min(1, { error: "根评论ID不能为空" }),
1051
- number: zod.coerce.number({ error: "评论数量必须是数字" }).int({ error: "评论数量必须是整数" }).positive({ error: "评论数量必须是正数" }).default(20).optional(),
1052
- pn: zod.coerce.number({ error: "页码必须是数字" }).int({ error: "页码必须是整数" }).positive({ error: "页码必须是正数" }).default(1).optional()
1053
- });
1054
- const BilibiliUserParamsSchema = zod.object({
1055
- methodType: zod.enum([
1056
- "用户主页数据",
1057
- "用户主页动态列表数据",
1058
- "获取UP主总播放量"
1059
- ], { error: "方法类型必须是指定的枚举值之一" }),
1060
- host_mid: smartNumber("UP主UID不能为空", 1, true)
1061
- });
1062
- const BilibiliEmojiParamsSchema = zod.object({ methodType: zod.literal("Emoji数据", { error: "方法类型必须是\"Emoji数据\"" }) });
1063
- const BilibiliBangumiInfoParamsSchema = zod.object({
1064
- methodType: zod.literal("番剧基本信息数据", { error: "方法类型必须是\"番剧基本信息数据\"" }),
1065
- ep_id: zod.string({ error: "番剧EP ID必须是字符串" }).min(1, { error: "番剧EP ID不能为空" }).optional(),
1066
- season_id: zod.string({ error: "番剧季度ID必须是字符串" }).optional()
1067
- }).refine((data$1) => data$1.ep_id ?? data$1.season_id, {
1068
- error: "ep_id 和 season_id 至少需要提供一个",
1069
- path: ["ep_id"]
1070
- });
1071
- const BilibiliBangumiStreamParamsSchema = zod.object({
1072
- methodType: zod.literal("番剧下载信息数据", { error: "方法类型必须是\"番剧下载信息数据\"" }),
1073
- cid: smartNumber("CID不能为空", 1, true),
1074
- ep_id: zod.string({ error: "番剧EP ID必须是字符串" }).min(1, { error: "番剧EP ID不能为空" })
1075
- });
1076
- const BilibiliDynamicParamsSchema = zod.object({
1077
- methodType: zod.enum(["动态详情数据", "动态卡片数据"], { error: "方法类型必须是\"动态详情数据\"或\"动态卡片数据\"" }),
1078
- dynamic_id: zod.string({ error: "动态ID必须是字符串" }).min(1, { error: "动态ID不能为空" })
1079
- });
1080
- const BilibiliLiveParamsSchema = zod.object({
1081
- methodType: zod.enum(["直播间信息", "直播间初始化信息"], { error: "方法类型必须是\"直播间信息\"或\"直播间初始化信息\"" }),
1082
- room_id: zod.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" })
1083
- });
1084
- const BilibiliLoginParamsSchema = zod.object({ methodType: zod.literal("登录基本信息", { error: "方法类型必须是\"登录基本信息\"" }) });
1085
- const BilibiliQrcodeParamsSchema = zod.object({ methodType: zod.literal("申请二维码", { error: "方法类型必须是\"申请二维码\"" }) });
1086
- const BilibiliQrcodeStatusParamsSchema = zod.object({
1087
- methodType: zod.literal("二维码状态", { error: "方法类型必须是\"二维码状态\"" }),
1088
- qrcode_key: zod.string({ error: "二维码key必须是字符串" }).min(1, { error: "二维码key不能为空" })
1089
- });
1090
- const BilibiliAv2BvParamsSchema = zod.object({
1091
- methodType: zod.literal("AV转BV", { error: "方法类型必须是\"AV转BV\"" }),
1092
- avid: zod.coerce.number({ error: "AVID必须是数字" }).int({ error: "AVID必须是整数" }).positive({ error: "AVID必须是正数" })
1093
- });
1094
- const BilibiliBv2AvParamsSchema = zod.object({
1095
- methodType: zod.literal("BV转AV", { error: "方法类型必须是\"BV转AV\"" }),
1096
- bvid: zod.string({ error: "BVID必须是字符串" }).min(1, { error: "BVID不能为空" })
1097
- });
1098
- const BilibiliArticleParamsSchema = zod.object({
1099
- methodType: zod.literal("专栏正文内容", { error: "方法类型必须是\"专栏正文内容\"" }),
1100
- id: zod.string({ error: "专栏ID必须是字符串" }).min(1, { error: "专栏ID不能为空" })
1101
- });
1102
- const BilibiliArticleCardParamsSchema = zod.object({
1103
- methodType: zod.literal("专栏显示卡片信息", { error: "方法类型必须是\"专栏显示卡片信息\"" }),
1104
- ids: zod.union([zod.array(zod.string({ error: "被查询的 id 列表必须是字符串数组" })).min(1, { error: "被查询的 id 列表不能为空" }), zod.string({ error: "被查询的 id 列表必须是字符串" }).min(1, { error: "被查询的 id 列表不能为空" })])
1105
- });
1106
- const BilibiliArticleInfoParamsSchema = zod.object({
1107
- methodType: zod.literal("专栏文章基本信息", { error: "方法类型必须是\"专栏文章基本信息\"" }),
1108
- id: zod.string({ error: "专栏ID必须是字符串" }).min(1, { error: "专栏ID不能为空" })
1109
- });
1110
- const BilibiliColumnInfoParamsSchema = zod.object({
1111
- methodType: zod.literal("文集基本信息", { error: "方法类型必须是\"文集基本信息\"" }),
1112
- id: zod.string({ error: "文集ID必须是字符串" }).min(1, { error: "文集ID不能为空" })
1113
- });
1114
- const BilibiliValidationSchemas = {
1115
- 单个视频作品数据: BilibiliVideoParamsSchema,
1116
- 单个视频下载信息数据: BilibiliVideoDownloadParamsSchema,
1117
- 评论数据: BilibiliCommentParamsSchema,
1118
- 指定评论的回复: BilibiliCommentReplyParamsSchema,
1119
- 用户主页数据: BilibiliUserParamsSchema,
1120
- 用户主页动态列表数据: BilibiliUserParamsSchema,
1121
- Emoji数据: BilibiliEmojiParamsSchema,
1122
- 番剧基本信息数据: BilibiliBangumiInfoParamsSchema,
1123
- 番剧下载信息数据: BilibiliBangumiStreamParamsSchema,
1124
- 动态详情数据: BilibiliDynamicParamsSchema,
1125
- 动态卡片数据: BilibiliDynamicParamsSchema,
1126
- 直播间信息: BilibiliLiveParamsSchema,
1127
- 直播间初始化信息: BilibiliLiveParamsSchema,
1128
- 登录基本信息: BilibiliLoginParamsSchema,
1129
- 申请二维码: BilibiliQrcodeParamsSchema,
1130
- 二维码状态: BilibiliQrcodeStatusParamsSchema,
1131
- 获取UP主总播放量: BilibiliUserParamsSchema,
1132
- AV转BV: BilibiliAv2BvParamsSchema,
1133
- BV转AV: BilibiliBv2AvParamsSchema,
1134
- 专栏正文内容: BilibiliArticleParamsSchema,
1135
- 专栏显示卡片信息: BilibiliArticleCardParamsSchema,
1136
- 专栏文章基本信息: BilibiliArticleInfoParamsSchema,
1137
- 文集基本信息: BilibiliColumnInfoParamsSchema
1138
- };
1139
- const BilibiliMethodRoutes = {
1140
- 单个视频作品数据: "/fetch_one_video",
1141
- 单个视频下载信息数据: "/fetch_video_playurl",
1142
- 评论数据: "/fetch_work_comments",
1143
- 指定评论的回复: "/fetch_comment_reply",
1144
- 用户主页数据: "/fetch_user_profile",
1145
- 用户主页动态列表数据: "/fetch_user_dynamic",
1146
- Emoji数据: "/fetch_emoji_list",
1147
- 番剧基本信息数据: "/fetch_bangumi_video_info",
1148
- 番剧下载信息数据: "/fetch_bangumi_video_playurl",
1149
- 动态详情数据: "/fetch_dynamic_info",
1150
- 动态卡片数据: "/fetch_dynamic_card",
1151
- 直播间信息: "/fetch_live_room_detail",
1152
- 直播间初始化信息: "/fetch_liveroom_def",
1153
- 登录基本信息: "/login_basic_info",
1154
- 申请二维码: "/new_login_qrcode",
1155
- 二维码状态: "/check_qrcode",
1156
- 获取UP主总播放量: "/fetch_user_full_view",
1157
- AV转BV: "/av_to_bv",
1158
- BV转AV: "/bv_to_av",
1159
- 专栏正文内容: "/fetch_article_content",
1160
- 专栏显示卡片信息: "/fetch_article_card",
1161
- 专栏文章基本信息: "/fetch_article_info",
1162
- 文集基本信息: "/fetch_column_info"
807
+ const xiaohongshuApiUrls = {
808
+ 首页推荐数据(data$1 = {}) {
809
+ return {
810
+ apiPath: "/api/sns/web/v1/homefeed",
811
+ Url: "https://edith.xiaohongshu.com/api/sns/web/v1/homefeed",
812
+ Body: {
813
+ cursor_score: data$1.cursor_score ?? "1.7599348899670024E9",
814
+ num: data$1.num ?? 33,
815
+ refresh_type: data$1.refresh_type ?? 3,
816
+ note_index: data$1.note_index ?? 33,
817
+ category: data$1.category ?? "homefeed_recommend",
818
+ search_key: data$1.search_key ?? "",
819
+ image_formats: [
820
+ "jpg",
821
+ "webp",
822
+ "avif"
823
+ ]
824
+ }
825
+ };
826
+ },
827
+ 单个笔记数据(data$1) {
828
+ return {
829
+ apiPath: "/api/sns/web/v1/feed",
830
+ Url: "https://edith.xiaohongshu.com/api/sns/web/v1/feed",
831
+ Body: {
832
+ source_note_id: data$1.note_id,
833
+ image_formats: [
834
+ "jpg",
835
+ "webp",
836
+ "avif"
837
+ ],
838
+ extra: { need_body_topic: "1" },
839
+ xsec_source: "pc_feed",
840
+ xsec_token: data$1.xsec_token
841
+ }
842
+ };
843
+ },
844
+ 评论数据(data$1) {
845
+ return {
846
+ apiPath: "/api/sns/web/v2/comment/page",
847
+ Url: `https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?${buildQueryString$1({
848
+ note_id: data$1.note_id,
849
+ cursor: data$1.cursor ?? "",
850
+ image_formats: [
851
+ "jpg",
852
+ "webp",
853
+ "avif"
854
+ ].join(","),
855
+ xsec_token: data$1.xsec_token
856
+ })}`
857
+ };
858
+ },
859
+ 用户数据(data$1) {
860
+ return {
861
+ apiPath: "/api/sns/web/v1/user/otherinfo",
862
+ Url: `https://www.xiaohongshu.com/user/profile/${data$1.user_id}`
863
+ };
864
+ },
865
+ 用户笔记数据(data$1) {
866
+ return {
867
+ apiPath: "/api/sns/web/v1/user_posted",
868
+ Url: `https://edith.xiaohongshu.com/api/sns/web/v1/user_posted?${buildQueryString$1({
869
+ user_id: data$1.user_id,
870
+ cursor: data$1.cursor ?? "",
871
+ num: data$1.num ?? 30,
872
+ image_formats: [
873
+ "jpg",
874
+ "webp",
875
+ "avif"
876
+ ].join(","),
877
+ xsec_source: "pc_feed"
878
+ })}`
879
+ };
880
+ },
881
+ 表情列表(data$1) {
882
+ return {
883
+ apiPath: "/api/im/redmoji/detail",
884
+ Url: "https://edith.xiaohongshu.com/api/im/redmoji/detail"
885
+ };
886
+ },
887
+ 搜索笔记(data$1) {
888
+ return {
889
+ apiPath: "/api/sns/web/v1/search/notes",
890
+ Body: {
891
+ keyword: data$1.keyword,
892
+ page: data$1.page ?? 1,
893
+ page_size: data$1.page_size ?? 20,
894
+ sort: SearchSortType.GENERAL,
895
+ note_type: SearchNoteType.ALL,
896
+ search_id: xiaohongshuSign.getSearchId(),
897
+ image_formats: [
898
+ "jpg",
899
+ "webp",
900
+ "avif"
901
+ ]
902
+ },
903
+ Url: "https://edith.xiaohongshu.com/api/sns/web/v1/search/notes"
904
+ };
905
+ }
906
+ };
907
+ /**
908
+ * 创建小红书API URLs实例
909
+ * @returns 小红书API URLs对象
910
+ */
911
+ const createXiaohongshuApiUrls = () => {
912
+ return xiaohongshuApiUrls;
1163
913
  };
1164
914
 
1165
915
  //#endregion
1166
- //#region src/validation/douyin.ts
1167
- const DouyinWorkParamsSchema = zod.object({
1168
- methodType: zod.enum([
1169
- "视频作品数据",
1170
- "图集作品数据",
1171
- "合辑作品数据",
1172
- "聚合解析"
1173
- ], { error: "方法类型必须是指定的枚举值之一" }),
1174
- aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" })
1175
- });
1176
- const DouyinCommentParamsSchema = zod.object({
1177
- methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
1178
- aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
1179
- number: smartPositiveInteger("评论数量必须是正整数").optional().default(50),
1180
- cursor: zod.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional()
1181
- });
1182
- const DouyinHotWordsParamsSchema = zod.object({
1183
- methodType: zod.literal("热点词数据", { error: "方法类型必须是\"热点词数据\"" }),
1184
- query: zod.string({ error: "搜索词必须是字符串" }).min(1, { error: "搜索词不能为空" })
1185
- });
1186
- const DouyinSearchParamsSchema = zod.object({
1187
- methodType: zod.literal("搜索数据", { error: "方法类型必须是\"搜索数据\"" }),
1188
- query: zod.string({ error: "搜索词必须是字符串" }).min(1, { error: "搜索词不能为空" }),
1189
- type: zod.enum([
1190
- "综合",
1191
- "用户",
1192
- "视频"
1193
- ], { error: "搜索类型必须是\"综合\"、\"用户\"或\"视频\"" }).optional().default("综合"),
1194
- number: smartPositiveInteger("搜索数量必须是正整数").optional().default(10),
1195
- search_id: zod.string({ error: "搜索ID必须是字符串" }).optional()
1196
- });
1197
- const DouyinCommentReplyParamsSchema = zod.object({
1198
- methodType: zod.literal("指定评论回复数据", { error: "方法类型必须是\"指定评论回复数据\"" }),
1199
- aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
1200
- comment_id: zod.string({ error: "评论ID必须z串" }).min(1, { error: "评论ID不能为空" }),
1201
- number: smartPositiveInteger("评论数量必须是正整数").optional().default(5),
1202
- cursor: zod.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional()
916
+ //#region src/validation/xiaohongshu.ts
917
+ const SearchSortTypeValues = Object.values(SearchSortType).filter((v) => typeof v === "string");
918
+ const SearchNoteTypeValues = Object.values(SearchNoteType).filter((v) => typeof v === "number");
919
+ /**
920
+ * 小红书首页推荐数据参数验证模式
921
+ */
922
+ const HomeFeedParamsSchema = zod.object({
923
+ methodType: zod.literal("首页推荐数据", { error: "方法类型必须是\"首页推荐数据\"" }),
924
+ cursor_score: zod.string({ error: "cursor_score必须是字符串" }).optional(),
925
+ num: zod.coerce.number({ error: "数量必须是数字" }).int({ error: "数量必须是整数" }).min(1, { error: "数量不能小于1" }).max(100, { error: "数量不能大于100" }).optional(),
926
+ refresh_type: zod.coerce.number({ error: "refresh_type必须是数字" }).int({ error: "refresh_type必须是整数" }).optional(),
927
+ note_index: zod.coerce.number({ error: "note_index必须是数字" }).int({ error: "note_index必须是整数" }).optional(),
928
+ category: zod.string({ error: "category必须是字符串" }).optional(),
929
+ search_key: zod.string({ error: "search_key必须是字符串" }).optional()
1203
930
  });
1204
- const DouyinUserParamsSchema = zod.object({
1205
- methodType: zod.enum(["用户主页数据", "用户主页视频列表数据"], { error: "方法类型必须是指定的枚举值之一" }),
1206
- sec_uid: zod.string({ error: "用户ID必须是字符串" }).min(1, { error: "用户ID不能为空" })
931
+ /**
932
+ * 小红书单个笔记数据参数验证模式
933
+ */
934
+ const NoteParamsSchema = zod.object({
935
+ methodType: zod.literal("单个笔记数据", { error: "方法类型必须是\"单个笔记数据\"" }),
936
+ note_id: zod.string({ error: "note_id必须是字符串" }),
937
+ xsec_token: zod.string({ error: "xsec_token必须是字符串" })
1207
938
  });
1208
- const DouyinMusicParamsSchema = zod.object({
1209
- methodType: zod.literal("音乐数据", { error: "方法类型必须是\"音乐数据\"" }),
1210
- music_id: zod.string({ error: "音乐ID必须是字符串" }).min(1, { error: "音乐ID不能为空" })
939
+ /**
940
+ * 小红书评论数据参数验证模式
941
+ */
942
+ const CommentParamsSchema = zod.object({
943
+ methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
944
+ note_id: zod.string({ error: "note_id必须是字符串" }),
945
+ cursor: zod.string({ error: "cursor必须是字符串" }).optional(),
946
+ xsec_token: zod.string({ error: "xsec_token必须是字符串" })
1211
947
  });
1212
- const DouyinLiveRoomParamsSchema = zod.object({
1213
- methodType: zod.literal("直播间信息数据", { error: "方法类型必须是\"直播间信息数据\"" }),
1214
- web_rid: zod.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" }),
1215
- room_id: zod.string({ error: "直播间ID必须是字符串" }).min(1, { error: "直播间ID不能为空" })
948
+ /**
949
+ * 小红书用户数据参数验证模式
950
+ */
951
+ const UserParamsSchema = zod.object({
952
+ methodType: zod.literal("用户数据", { error: "方法类型必须是\"用户数据\"" }),
953
+ user_id: zod.string({ error: "user_id必须是字符串" })
1216
954
  });
1217
- const DouyinQrcodeParamsSchema = zod.object({
1218
- methodType: zod.literal("申请二维码数据", { error: "方法类型必须是\"申请二维码数据\"" }),
1219
- verify_fp: zod.string({ error: "fp指纹必须是字符串" }).min(1, { error: "fp指纹不能为空" })
955
+ /**
956
+ * 小红书用户笔记数据参数验证模式
957
+ */
958
+ const UserNoteParamsSchema = zod.object({
959
+ methodType: zod.literal("用户笔记数据", { error: "方法类型必须是\"用户笔记数据\"" }),
960
+ user_id: zod.string({ error: "user_id必须是字符串" }),
961
+ cursor: zod.string({ error: "cursor必须是字符串" }).optional(),
962
+ num: zod.coerce.number({ error: "数量必须是数字" }).int({ error: "数量必须是整数" }).min(1, { error: "数量不能小于1" }).max(100, { error: "数量不能大于100" }).optional()
1220
963
  });
1221
- const DouyinEmojiListParamsSchema = zod.object({ methodType: zod.literal("Emoji数据", { error: "方法类型必须是\"Emoji数据\"" }) });
1222
- const DouyinEmojiProParamsSchema = zod.object({ methodType: zod.literal("动态表情数据", { error: "方法类型必须是\"动态表情数据\"" }) });
1223
- const DouyinDanmakuParamsSchema = zod.object({
1224
- methodType: zod.literal("弹幕数据", { error: "方法类型必须是\"弹幕数据\"" }),
1225
- aweme_id: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" }),
1226
- start_time: zod.coerce.number({ error: "开始时间必须是数字" }).int({ error: "开始时间必须是整数" }).min(0, { error: "开始时间不能小于0" }).optional(),
1227
- end_time: zod.coerce.number({ error: "结束时间必须是数字" }).int({ error: "结束时间必须是整数" }).min(0, { error: "结束时间不能小于0" }).optional(),
1228
- duration: zod.coerce.number({ error: "视频时长必须是数字" }).int({ error: "视频时长必须是整数" }).min(0, { error: "视频时长不能小于0" })
1229
- }).refine((data$1) => {
1230
- if (data$1.end_time !== void 0) return data$1.end_time <= data$1.duration;
1231
- return true;
1232
- }, {
1233
- error: "获取弹幕区间的结束时间不能超过视频总时长",
1234
- path: ["end_time"]
1235
- }).refine((data$1) => {
1236
- if (data$1.start_time !== void 0 && data$1.end_time !== void 0) return data$1.start_time < data$1.end_time;
1237
- return true;
1238
- }, {
1239
- error: "获取弹幕区间的开始时间必须小于结束时间",
1240
- path: ["start_time"]
964
+ const EmojiListParamsSchema = zod.object({ methodType: zod.literal("表情列表", { error: "方法类型必须是\"表情列表\"" }) });
965
+ /**
966
+ * 小红书搜索笔记参数验证模式
967
+ */
968
+ const SearchNoteParamsSchema = zod.object({
969
+ methodType: zod.literal("搜索笔记", { error: "方法类型必须是\"搜索笔记\"" }),
970
+ keyword: zod.string({ error: "keyword必须是字符串" }),
971
+ page: zod.coerce.number({ error: "page必须是数字" }).int({ error: "page必须是整数" }).min(1, { error: "page不能小于1" }).optional(),
972
+ page_size: zod.coerce.number({ error: "page_size必须是数字" }).int({ error: "page_size必须是整数" }).min(1, { error: "page_size不能小于1" }).max(100, { error: "page_size不能大于100" }).optional(),
973
+ sort: zod.enum(SearchSortTypeValues, { error: "排序类型不合法" }).optional(),
974
+ note_type: zod.coerce.number({ error: "笔记类型必须是数字" }).int({ error: "笔记类型必须是整数" }).refine((val) => SearchNoteTypeValues.includes(val), { message: "笔记类型不合法" }).optional()
1241
975
  });
1242
- const DouyinValidationSchemas = {
1243
- 文字作品数据: DouyinWorkParamsSchema,
1244
- 聚合解析: DouyinWorkParamsSchema,
1245
- 视频作品数据: DouyinWorkParamsSchema,
1246
- 图集作品数据: DouyinWorkParamsSchema,
1247
- 合辑作品数据: DouyinWorkParamsSchema,
1248
- 评论数据: DouyinCommentParamsSchema,
1249
- 用户主页数据: DouyinUserParamsSchema,
1250
- 用户主页视频列表数据: DouyinUserParamsSchema,
1251
- 热点词数据: DouyinHotWordsParamsSchema,
1252
- 搜索数据: DouyinSearchParamsSchema,
1253
- 音乐数据: DouyinMusicParamsSchema,
1254
- 直播间信息数据: DouyinLiveRoomParamsSchema,
1255
- 申请二维码数据: DouyinQrcodeParamsSchema,
1256
- Emoji数据: DouyinEmojiListParamsSchema,
1257
- 动态表情数据: DouyinEmojiProParamsSchema,
1258
- 指定评论回复数据: DouyinCommentReplyParamsSchema,
1259
- 弹幕数据: DouyinDanmakuParamsSchema
976
+ /**
977
+ * 小红书验证模式映射
978
+ */
979
+ const XiaohongshuValidationSchemas = {
980
+ 首页推荐数据: HomeFeedParamsSchema,
981
+ 单个笔记数据: NoteParamsSchema,
982
+ 评论数据: CommentParamsSchema,
983
+ 用户数据: UserParamsSchema,
984
+ 用户笔记数据: UserNoteParamsSchema,
985
+ 表情列表: EmojiListParamsSchema,
986
+ 搜索笔记: SearchNoteParamsSchema
987
+ };
988
+ /**
989
+ * 小红书方法类型
990
+ */
991
+ const XiaohongshuMethodRoutes = {
992
+ 首页推荐数据: "/fetch_home_feed",
993
+ 单个笔记数据: "/fetch_one_note",
994
+ 评论数据: "/fetch_note_comments",
995
+ 用户数据: "/fetch_user_profile",
996
+ 用户笔记数据: "/fetch_user_notes",
997
+ 表情列表: "/fetch_emoji_list",
998
+ 搜索笔记: "/fetch_search_notes"
999
+ };
1000
+
1001
+ //#endregion
1002
+ //#region src/validation/index.ts
1003
+ /**
1004
+ * 验证抖音参数
1005
+ * @param methodType - 抖音方法类型
1006
+ * @param params - 待验证的参数
1007
+ * @returns 验证后的参数,符合原始API期望的类型
1008
+ */
1009
+ const validateDouyinParams = (methodType, params) => {
1010
+ return DouyinValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1011
+ methodType,
1012
+ ...params
1013
+ } : {
1014
+ methodType,
1015
+ params
1016
+ });
1017
+ };
1018
+ /**
1019
+ * 验证哔哩哔哩参数
1020
+ * @param methodType - 哔哩哔哩方法类型
1021
+ * @param params - 待验证的参数
1022
+ * @returns 验证后的参数,符合原始API期望的类型
1023
+ */
1024
+ const validateBilibiliParams = (methodType, params) => {
1025
+ return BilibiliValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1026
+ methodType,
1027
+ ...params
1028
+ } : {
1029
+ methodType,
1030
+ params
1031
+ });
1032
+ };
1033
+ /**
1034
+ * 验证快手参数
1035
+ * @param methodType - 快手方法类型
1036
+ * @param params - 待验证的参数
1037
+ * @returns 验证后的参数,符合原始API期望的类型
1038
+ */
1039
+ const validateKuaishouParams = (methodType, params) => {
1040
+ return KuaishouValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1041
+ methodType,
1042
+ ...params
1043
+ } : {
1044
+ methodType,
1045
+ params
1046
+ });
1047
+ };
1048
+ /**
1049
+ * 验证小红书参数
1050
+ * @param methodType - 小红书方法类型
1051
+ * @param params - 待验证的参数
1052
+ * @returns 验证后的参数
1053
+ */
1054
+ const validateXiaohongshuParams = (methodType, params) => {
1055
+ return XiaohongshuValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1056
+ methodType,
1057
+ ...params
1058
+ } : {
1059
+ methodType,
1060
+ params
1061
+ });
1062
+ };
1063
+ /**
1064
+ * 创建成功响应格式
1065
+ * @param data - 响应数据
1066
+ * @param message - 响应消息(可选)
1067
+ * @param code - 响应状态码(可选,默认200)
1068
+ * @returns 格式化的成功API响应对象
1069
+ */
1070
+ const createSuccessResponse = (data$1, message, code = 200) => {
1071
+ return {
1072
+ success: true,
1073
+ data: data$1,
1074
+ message,
1075
+ code,
1076
+ error: void 0
1077
+ };
1260
1078
  };
1261
- const DouyinMethodRoutes = {
1262
- 聚合解析: "/fetch_one_work",
1263
- 文字作品数据: "/fetch_one_work",
1264
- 视频作品数据: "/fetch_one_work",
1265
- 图集作品数据: "/fetch_one_work",
1266
- 合辑作品数据: "/fetch_one_work",
1267
- 评论数据: "/fetch_work_comments",
1268
- 用户主页数据: "/fetch_user_info",
1269
- 用户主页视频列表数据: "/fetch_user_post_videos",
1270
- 搜索数据: "/fetch_search_info",
1271
- 热点词数据: "/fetch_suggest_words",
1272
- 音乐数据: "/fetch_music_work",
1273
- Emoji数据: "/fetch_emoji_list",
1274
- 动态表情数据: "/fetch_emoji_pro_list",
1275
- 直播间信息数据: "/fetch_user_live_videos",
1276
- 指定评论回复数据: "/fetch_video_comment_replies",
1277
- 弹幕数据: "/fetch_work_danmaku"
1079
+ /**
1080
+ * 创建失败响应格式
1081
+ * @param error - 错误信息
1082
+ * @param message - 详细错误消息(可选)
1083
+ * @param code - 错误状态码(可选,默认500)
1084
+ * @returns 格式化的错误响应对象
1085
+ */
1086
+ const createErrorResponse = (error, message, code = 500) => {
1087
+ return {
1088
+ success: false,
1089
+ error,
1090
+ message,
1091
+ code,
1092
+ data: void 0
1093
+ };
1278
1094
  };
1279
1095
 
1280
1096
  //#endregion
1281
- //#region src/validation/kuaishou.ts
1282
- const KuaishouVideoParamsSchema = zod.object({
1283
- methodType: zod.literal("单个视频作品数据", { error: "方法类型必须是\"单个视频作品数据\"" }),
1284
- photoId: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" })
1285
- });
1286
- const KuaishouCommentParamsSchema = zod.object({
1287
- methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
1288
- photoId: zod.string({ error: "视频ID必须是字符串" }).min(1, { error: "视频ID不能为空" })
1289
- });
1290
- const KuaishouEmojiParamsSchema = zod.object({ methodType: zod.literal("Emoji数据", { error: "方法类型必须是\"Emoji数据\"" }) });
1291
- const KuaishouValidationSchemas = {
1292
- 单个视频作品数据: KuaishouVideoParamsSchema,
1293
- 评论数据: KuaishouCommentParamsSchema,
1294
- Emoji数据: KuaishouEmojiParamsSchema
1097
+ //#region src/model/networks.ts
1098
+ /** 可恢复的错误代码列表 */
1099
+ const RECOVERABLE_ERROR_CODES = [
1100
+ "ECONNRESET",
1101
+ "ETIMEDOUT",
1102
+ "ECONNREFUSED",
1103
+ "ENOTFOUND",
1104
+ "ENETUNREACH",
1105
+ "EHOSTUNREACH",
1106
+ "EPIPE",
1107
+ "EAI_AGAIN",
1108
+ "ECONNABORTED"
1109
+ ];
1110
+ /** 默认最大重试次数 */
1111
+ const DEFAULT_MAX_RETRIES = 3;
1112
+ /** 重试延迟基数(毫秒) */
1113
+ const RETRY_DELAY_BASE = 1e3;
1114
+ /**
1115
+ * 判断错误是否可恢复
1116
+ * @param error - Axios错误对象
1117
+ * @returns 是否可恢复
1118
+ */
1119
+ const isRecoverableError = (error) => {
1120
+ return RECOVERABLE_ERROR_CODES.includes(error.code);
1295
1121
  };
1296
- const KuaishouMethodRoutes = {
1297
- 单个视频作品数据: "/fetch_one_work",
1298
- 评论数据: "/fetch_work_comments",
1299
- Emoji数据: "/fetch_emoji_list"
1122
+ /**
1123
+ * 延迟函数
1124
+ * @param ms - 延迟毫秒数
1125
+ */
1126
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1127
+ /**
1128
+ * 创建网络错误响应
1129
+ * @param error - 错误对象
1130
+ * @param retries - 已重试次数
1131
+ * @returns 符合Result类型的错误响应
1132
+ */
1133
+ const createNetworkErrorResult = (error, retries) => {
1134
+ const errorCode = error.code ?? "UNKNOWN";
1135
+ const message = `网络请求失败 [${errorCode}]: ${error.message} (已重试 ${retries} 次)`;
1136
+ return createErrorResponse({
1137
+ code: amagiAPIErrorCode.UNKNOWN,
1138
+ data: null,
1139
+ amagiError: {
1140
+ errorDescription: `${error.message} (已重试 ${retries} 次)`,
1141
+ requestType: error.config?.method?.toUpperCase() ?? "UNKNOWN",
1142
+ requestUrl: error.config?.url ?? "",
1143
+ responseCode: errorCode
1144
+ },
1145
+ amagiMessage: error.message
1146
+ }, message, 500);
1300
1147
  };
1301
-
1302
- //#endregion
1303
- //#region src/platform/xiaohongshu/sign/index.ts
1304
1148
  /**
1305
- * 小红书签名算法类
1149
+ * 清理User-Agent中的Edge标识,确保请求兼容性
1150
+ * @param userAgent - 原始User-Agent字符串
1151
+ * @returns 清理后的User-Agent字符串
1306
1152
  */
1307
- var xiaohongshuSign = class {
1308
- static client = new Xhshow();
1309
- /**
1310
- * 生成GET请求的X-S签名
1311
- * @param path - API路径
1312
- * @param a1Cookie - a1 cookie值
1313
- * @param clientType - 客户端类型,默认为 'xhs-pc-web'
1314
- * @param params - 查询参数对象
1315
- * @returns X-S签名
1316
- */
1317
- static generateXSGet(path$1, a1Cookie, clientType = "xhs-pc-web", params = {}) {
1318
- return this.client.signXsGet(path$1, a1Cookie, clientType, params);
1319
- }
1320
- /**
1321
- * 生成POST请求的X-S签名
1322
- * @param path - API路径
1323
- * @param a1Cookie - a1 cookie值
1324
- * @param clientType - 客户端类型,默认为 'xhs-pc-web'
1325
- * @param body - 请求体对象
1326
- * @returns X-S签名
1327
- */
1328
- static generateXSPost(path$1, a1Cookie, clientType = "xhs-pc-web", body = {}) {
1329
- return this.client.signXsPost(path$1, a1Cookie, clientType, body);
1330
- }
1331
- /**
1332
- * 生成X-S签名(兼容旧接口)
1333
- * @param url - 请求URL
1334
- * @param body - 请求体
1335
- * @param userAgent - User-Agent(暂未使用)
1336
- * @param method - 请求方法,默认为 'POST'
1337
- * @param a1Cookie - a1 cookie值
1338
- * @returns X-S签名
1339
- */
1340
- static generateXS(url$1, body, userAgent, method = "POST", a1Cookie = "") {
1341
- try {
1342
- const urlObj = new URL(url$1);
1343
- const path$1 = urlObj.pathname + urlObj.search;
1344
- if (method.toUpperCase() === "GET") {
1345
- const params = typeof body === "object" ? body : {};
1346
- return this.generateXSGet(path$1, a1Cookie, "xhs-pc-web", params);
1347
- } else {
1348
- const requestBody = typeof body === "object" ? body : {};
1349
- return this.generateXSPost(path$1, a1Cookie, "xhs-pc-web", requestBody);
1153
+ const cleanUserAgent = (userAgent) => {
1154
+ return userAgent.replace(/\s+Edg\/[\d\.]+/g, "");
1155
+ };
1156
+ /**
1157
+ * 执行网络请求并返回数据(带自动重试)
1158
+ * @param config - axios请求配置
1159
+ * @param maxRetries - 最大重试次数,默认3次
1160
+ * @returns 响应数据或错误结果
1161
+ */
1162
+ const fetchData = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
1163
+ const cleanedConfig = { ...config };
1164
+ if (cleanedConfig.headers && cleanedConfig.headers["User-Agent"]) cleanedConfig.headers["User-Agent"] = cleanUserAgent(cleanedConfig.headers["User-Agent"]);
1165
+ let lastError = null;
1166
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
1167
+ return (await axios({
1168
+ ...cleanedConfig,
1169
+ validateStatus: () => true
1170
+ })).data;
1171
+ } catch (error) {
1172
+ if (error instanceof AxiosError) {
1173
+ lastError = error;
1174
+ if (isRecoverableError(error) && attempt < maxRetries) {
1175
+ const delayMs = RETRY_DELAY_BASE * Math.pow(2, attempt);
1176
+ logger.warn(`网络请求失败 [${error.code}],${delayMs}ms 后进行第 ${attempt + 1} 次重试...`);
1177
+ await delay(delayMs);
1178
+ continue;
1350
1179
  }
1351
- } catch (error) {
1352
- console.error("生成X-S签名失败:", error);
1353
- throw new Error(`签名生成失败: ${error}`);
1180
+ logger.error("网络请求失败:", error.message);
1181
+ return createNetworkErrorResult(error, attempt);
1354
1182
  }
1183
+ throw error;
1355
1184
  }
1356
- /**
1357
- * 生成X-S-Common参数
1358
- * @param length - 长度
1359
- * @returns Base64编码的随机字符串
1360
- */
1361
- static generateXSCommon(length = 945) {
1362
- return crypto.randomBytes(length).toString("base64").replace(/=+$/, "");
1363
- }
1364
- /**
1365
- * 生成X-T时间戳
1366
- * @returns 当前时间戳字符串
1367
- */
1368
- static generateXT() {
1369
- return Date.now().toString();
1370
- }
1371
- /**
1372
- * 生成X-B3-Traceid
1373
- * @returns 16位随机字符串
1374
- */
1375
- static generateXB3Traceid() {
1376
- return Array.from({ length: 16 }, () => "abcdef0123456789"[Math.floor(Math.random() * 16)]).join("");
1377
- }
1378
- /**
1379
- * 从cookie字符串中提取a1值
1380
- * @param cookieString - 完整的cookie字符串
1381
- * @returns a1 cookie值
1382
- */
1383
- static extractA1FromCookie(cookieString) {
1384
- const match = cookieString.match(/a1=([^;]+)/);
1385
- return match ? match[1] : "";
1185
+ return createNetworkErrorResult(lastError, maxRetries);
1186
+ };
1187
+ const normalizeHeaders = (headers) => {
1188
+ if (headers && typeof headers.toJSON === "function") return headers.toJSON();
1189
+ return headers ?? {};
1190
+ };
1191
+ /**
1192
+ * 执行网络请求并返回完整响应(带自动重试)
1193
+ * @param config - axios请求配置
1194
+ * @param maxRetries - 最大重试次数,默认3次
1195
+ * @returns 完整响应或错误结果
1196
+ */
1197
+ const fetchResponse = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
1198
+ const cleanedConfig = { ...config };
1199
+ if (cleanedConfig.headers && cleanedConfig.headers["User-Agent"]) cleanedConfig.headers["User-Agent"] = cleanUserAgent(cleanedConfig.headers["User-Agent"]);
1200
+ let lastError = null;
1201
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
1202
+ return await axios({
1203
+ ...cleanedConfig,
1204
+ validateStatus: () => true
1205
+ });
1206
+ } catch (error) {
1207
+ if (error instanceof AxiosError) {
1208
+ lastError = error;
1209
+ if (isRecoverableError(error) && attempt < maxRetries) {
1210
+ const delayMs = RETRY_DELAY_BASE * Math.pow(2, attempt);
1211
+ logger.warn(`网络请求失败 [${error.code}],${delayMs}ms 后进行第 ${attempt + 1} 次重试...`);
1212
+ await delay(delayMs);
1213
+ continue;
1214
+ }
1215
+ logger.error("网络请求失败:", error.message);
1216
+ return createNetworkErrorResult(error, attempt);
1217
+ }
1218
+ throw error;
1386
1219
  }
1387
- /**
1388
- * 生成搜索ID
1389
- * @returns 搜索ID字符串
1390
- */
1391
- static getSearchId = () => (BigInt(Date.now()) << 64n) + BigInt(Math.floor(Math.random() * 2147483646)).toString(36);
1220
+ return createNetworkErrorResult(lastError, maxRetries);
1221
+ };
1222
+ /**
1223
+ * 判断结果是否为网络错误响应
1224
+ * @param result - 请求结果
1225
+ * @returns 是否为ErrorResult
1226
+ * @description 通过检查 error 字段中的 amagiError 来区分网络错误和业务错误
1227
+ */
1228
+ const isNetworkErrorResult = (result) => {
1229
+ if (result === null || typeof result !== "object") return false;
1230
+ const obj = result;
1231
+ return obj.success === false && obj.error !== null && typeof obj.error === "object" && "amagiError" in obj.error;
1232
+ };
1233
+ /**
1234
+ * 获取响应头和数据(带自动重试)
1235
+ * @param config - axios请求配置
1236
+ * @param maxRetries - 最大重试次数,默认3次
1237
+ * @returns 包含headers和data的对象,或错误结果
1238
+ */
1239
+ const getHeadersAndData = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
1240
+ const response = await fetchResponse(config, maxRetries);
1241
+ if ("success" in response && response.success === false) return response;
1242
+ return {
1243
+ headers: normalizeHeaders(response.headers),
1244
+ data: response.data
1245
+ };
1392
1246
  };
1393
1247
 
1394
1248
  //#endregion
1395
- //#region src/platform/xiaohongshu/API.ts
1249
+ //#region src/platform/defaultConfigs.ts
1396
1250
  /**
1397
- * 搜索排序类型枚举
1251
+ * 根据User-Agent生成对应的Sec-Ch-Ua值
1252
+ * @param userAgent - 用户代理字符串
1253
+ * @returns 对应的Sec-Ch-Ua值
1398
1254
  */
1399
- let SearchSortType = /* @__PURE__ */ function(SearchSortType$1) {
1400
- /**
1401
- * 默认排序
1402
- */
1403
- SearchSortType$1["GENERAL"] = "general";
1404
- /**
1405
- * 最受欢迎(按热度降序)
1406
- */
1407
- SearchSortType$1["MOST_POPULAR"] = "popularity_descending";
1408
- /**
1409
- * 最新发布(按时间降序)
1410
- */
1411
- SearchSortType$1["LATEST"] = "time_descending";
1412
- return SearchSortType$1;
1413
- }({});
1255
+ const generateSecChUa = (userAgent) => {
1256
+ const chromeMatch = userAgent.match(/Chrome\/(\d+)/);
1257
+ const chromeVersion = chromeMatch ? chromeMatch[1] : "125";
1258
+ return `"Not)A;Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`;
1259
+ };
1260
+ /**
1261
+ * 抖音平台默认请求配置
1262
+ * @param cookie - 用户Cookie
1263
+ * @param requestConfig - 外部请求配置(优先级最高)
1264
+ * @returns 合并后的请求配置
1265
+ */
1266
+ const getDouyinDefaultConfig = (cookie, requestConfig) => {
1267
+ let finalUserAgent = requestConfig?.headers?.["User-Agent"] ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
1268
+ finalUserAgent = finalUserAgent.replace(/\s+Edg\/[\d\.]+/g, "");
1269
+ const defHeaders = {
1270
+ Accept: "application/json, text/plain, */*",
1271
+ "Accept-Encoding": "gzip, deflate, br, zstd",
1272
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1273
+ Cookie: cookie ? cookie.replace(/\s+/g, "") : "",
1274
+ Priority: "u=1, i",
1275
+ Referer: "https://www.douyin.com/",
1276
+ "Sec-Ch-Ua": generateSecChUa(finalUserAgent),
1277
+ "Sec-Ch-Ua-Mobile": "?0",
1278
+ "Sec-Ch-Ua-Platform": "\"Windows\"",
1279
+ "Sec-Fetch-Dest": "empty",
1280
+ "Sec-Fetch-Mode": "cors",
1281
+ "Sec-Fetch-Site": "same-origin",
1282
+ "User-Agent": finalUserAgent
1283
+ };
1284
+ return {
1285
+ method: "GET",
1286
+ timeout: 1e4,
1287
+ ...requestConfig,
1288
+ headers: {
1289
+ ...defHeaders,
1290
+ ...requestConfig?.headers ?? {}
1291
+ }
1292
+ };
1293
+ };
1414
1294
  /**
1415
- * 搜索笔记类型枚举
1295
+ * B站平台默认请求配置
1296
+ * @param cookie - 用户Cookie
1297
+ * @param requestConfig - 外部请求配置(优先级最高)
1298
+ * @returns 合并后的请求配置
1416
1299
  */
1417
- let SearchNoteType = /* @__PURE__ */ function(SearchNoteType$1) {
1418
- /**
1419
- * 默认(全部类型)
1420
- */
1421
- SearchNoteType$1[SearchNoteType$1["ALL"] = 0] = "ALL";
1422
- /**
1423
- * 仅视频
1424
- */
1425
- SearchNoteType$1[SearchNoteType$1["VIDEO"] = 1] = "VIDEO";
1426
- /**
1427
- * 仅图片
1428
- */
1429
- SearchNoteType$1[SearchNoteType$1["IMAGE"] = 2] = "IMAGE";
1430
- return SearchNoteType$1;
1431
- }({});
1300
+ const getBilibiliDefaultConfig = (cookie, requestConfig) => {
1301
+ const defHeaders = {
1302
+ Accept: "application/json, text/plain, */*",
1303
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1304
+ "Accept-Encoding": "gzip, deflate, br",
1305
+ Origin: "https://www.bilibili.com",
1306
+ Referer: "https://www.bilibili.com/",
1307
+ Priority: "u=1, i",
1308
+ "Sec-Ch-Ua": "\"Microsoft Edge\";v=\"141\", \"Chromium\";v=\"141\", \"Not_A Brand\";v=\"24\"",
1309
+ "Sec-Ch-Ua-Mobile": "?0",
1310
+ "Sec-Ch-Ua-Platform": "\"Windows\"",
1311
+ "Sec-Fetch-Dest": "empty",
1312
+ "Sec-Fetch-Mode": "cors",
1313
+ "Sec-Fetch-Site": "same-site",
1314
+ Cookie: cookie ? cookie.replace(/\s+/g, "") : ""
1315
+ };
1316
+ return {
1317
+ method: "GET",
1318
+ timeout: 1e4,
1319
+ ...requestConfig,
1320
+ headers: {
1321
+ ...defHeaders,
1322
+ ...requestConfig?.headers ?? {}
1323
+ }
1324
+ };
1325
+ };
1432
1326
  /**
1433
- * 构建查询字符串
1434
- * @param params - 参数对象
1435
- * @returns 查询字符串
1327
+ * 快手平台默认请求配置
1328
+ * @param cookie - 用户Cookie
1329
+ * @param requestConfig - 外部请求配置(优先级最高)
1330
+ * @returns 合并后的请求配置
1436
1331
  */
1437
- const buildQueryString$1 = (params) => {
1438
- return Object.entries(params).filter(([_, value]) => value !== void 0 && value !== null).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
1332
+ const getKuaishouDefaultConfig = (cookie, requestConfig) => {
1333
+ const defHeaders = {
1334
+ Referer: "https://www.kuaishou.com/new-reco",
1335
+ Origin: "https://www.kuaishou.com",
1336
+ Accept: "application/json, text/plain, */*",
1337
+ "Accept-Encoding": "gzip, deflate, br, zstd",
1338
+ "Content-Type": "application/json",
1339
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1340
+ Priority: "u=0, i",
1341
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
1342
+ Cookie: cookie ? cookie.replace(/\s+/g, "") : ""
1343
+ };
1344
+ return {
1345
+ method: "POST",
1346
+ timeout: 1e4,
1347
+ ...requestConfig,
1348
+ headers: {
1349
+ ...defHeaders,
1350
+ ...requestConfig?.headers ?? {}
1351
+ }
1352
+ };
1439
1353
  };
1440
1354
  /**
1441
- * 小红书API地址配置
1355
+ * 获取小红书默认配置
1356
+ * @param cookie - 用户Cookie
1357
+ * @returns 小红书请求配置
1442
1358
  */
1443
- const xiaohongshuApiUrls = {
1444
- 首页推荐数据(data$1 = {}) {
1445
- return {
1446
- apiPath: "/api/sns/web/v1/homefeed",
1447
- Url: "https://edith.xiaohongshu.com/api/sns/web/v1/homefeed",
1448
- Body: {
1449
- cursor_score: data$1.cursor_score ?? "1.7599348899670024E9",
1450
- num: data$1.num ?? 33,
1451
- refresh_type: data$1.refresh_type ?? 3,
1452
- note_index: data$1.note_index ?? 33,
1453
- category: data$1.category ?? "homefeed_recommend",
1454
- search_key: data$1.search_key ?? "",
1455
- image_formats: [
1456
- "jpg",
1457
- "webp",
1458
- "avif"
1459
- ]
1460
- }
1461
- };
1462
- },
1463
- 单个笔记数据(data$1) {
1464
- return {
1465
- apiPath: "/api/sns/web/v1/feed",
1466
- Url: "https://edith.xiaohongshu.com/api/sns/web/v1/feed",
1467
- Body: {
1468
- source_note_id: data$1.note_id,
1469
- image_formats: [
1470
- "jpg",
1471
- "webp",
1472
- "avif"
1473
- ],
1474
- extra: { need_body_topic: "1" },
1475
- xsec_source: "pc_feed",
1476
- xsec_token: data$1.xsec_token
1477
- }
1478
- };
1479
- },
1480
- 评论数据(data$1) {
1481
- return {
1482
- apiPath: "/api/sns/web/v2/comment/page",
1483
- Url: `https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?${buildQueryString$1({
1484
- note_id: data$1.note_id,
1485
- cursor: data$1.cursor ?? "",
1486
- image_formats: [
1487
- "jpg",
1488
- "webp",
1489
- "avif"
1490
- ].join(","),
1491
- xsec_token: data$1.xsec_token
1492
- })}`
1493
- };
1494
- },
1495
- 用户数据(data$1) {
1496
- return {
1497
- apiPath: "/api/sns/web/v1/user/otherinfo",
1498
- Url: `https://www.xiaohongshu.com/user/profile/${data$1.user_id}`
1499
- };
1500
- },
1501
- 用户笔记数据(data$1) {
1502
- return {
1503
- apiPath: "/api/sns/web/v1/user_posted",
1504
- Url: `https://edith.xiaohongshu.com/api/sns/web/v1/user_posted?${buildQueryString$1({
1505
- user_id: data$1.user_id,
1506
- cursor: data$1.cursor ?? "",
1507
- num: data$1.num ?? 30,
1508
- image_formats: [
1509
- "jpg",
1510
- "webp",
1511
- "avif"
1512
- ].join(","),
1513
- xsec_source: "pc_feed"
1514
- })}`
1515
- };
1516
- },
1517
- 表情列表(data$1) {
1518
- return {
1519
- apiPath: "/api/im/redmoji/detail",
1520
- Url: "https://edith.xiaohongshu.com/api/im/redmoji/detail"
1521
- };
1522
- },
1523
- 搜索笔记(data$1) {
1524
- return {
1525
- apiPath: "/api/sns/web/v1/search/notes",
1526
- Body: {
1527
- keyword: data$1.keyword,
1528
- page: data$1.page ?? 1,
1529
- page_size: data$1.page_size ?? 20,
1530
- sort: SearchSortType.GENERAL,
1531
- note_type: SearchNoteType.ALL,
1532
- search_id: xiaohongshuSign.getSearchId(),
1533
- image_formats: [
1534
- "jpg",
1535
- "webp",
1536
- "avif"
1537
- ]
1538
- },
1539
- Url: "https://edith.xiaohongshu.com/api/sns/web/v1/search/notes"
1540
- };
1359
+ const getXiaohongshuDefaultConfig = (cookie) => {
1360
+ return { headers: {
1361
+ accept: "application/json, text/plain, */*",
1362
+ "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1363
+ "cache-control": "no-cache",
1364
+ "content-type": "application/json;charset=UTF-8",
1365
+ pragma: "no-cache",
1366
+ priority: "u=1, i",
1367
+ referer: "https://www.xiaohongshu.com/",
1368
+ "sec-ch-ua": "\"Microsoft Edge\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
1369
+ "sec-ch-ua-mobile": "?0",
1370
+ "sec-ch-ua-platform": "\"Windows\"",
1371
+ "sec-fetch-dest": "empty",
1372
+ "sec-fetch-mode": "cors",
1373
+ "sec-fetch-site": "same-site",
1374
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0",
1375
+ cookie: cookie ?? ""
1376
+ } };
1377
+ };
1378
+
1379
+ //#endregion
1380
+ //#region src/platform/bilibili/API.ts
1381
+ var BiLiBiLiAPI = class {
1382
+ 登录基本信息() {
1383
+ return "https://api.bilibili.com/x/web-interface/nav";
1384
+ }
1385
+ 视频详细信息(data$1) {
1386
+ return `https://api.bilibili.com/x/web-interface/view?bvid=${data$1.bvid}`;
1387
+ }
1388
+ 视频流信息(data$1) {
1389
+ return `https://api.bilibili.com/x/player/playurl?avid=${data$1.avid}&cid=${data$1.cid}`;
1390
+ }
1391
+ /** 评论区类型,type参数详见 [评论区类型代码](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/readme.md#评论区类型代码) */
1392
+ 评论区明细(data$1) {
1393
+ const params = new URLSearchParams({
1394
+ oid: data$1.oid.toString(),
1395
+ type: data$1.type.toString(),
1396
+ mode: (data$1.mode ?? 3).toString(),
1397
+ plat: "1",
1398
+ seek_rpid: "",
1399
+ web_location: "1315875"
1400
+ });
1401
+ if (data$1.pagination_str) params.append("pagination_str", JSON.stringify({ offset: data$1.pagination_str }));
1402
+ else params.append("pagination_str", JSON.stringify({ offset: "" }));
1403
+ return `https://api.bilibili.com/x/v2/reply/wbi/main?${params.toString()}`;
1404
+ }
1405
+ 评论区状态(data$1) {
1406
+ return `https://api.bilibili.com/x/v2/reply/subject/description?type=${data$1.type}&oid=${data$1.oid}`;
1407
+ }
1408
+ /** 指定评论的回复 */
1409
+ 指定评论的回复(data$1) {
1410
+ return `https://api.bilibili.com/x/v2/reply/reply?type=${data$1.type}&oid=${data$1.oid}&root=${data$1.root}&ps=${data$1.number}`;
1411
+ }
1412
+ 表情列表() {
1413
+ return "https://api.bilibili.com/x/emote/user/panel/web?business=reply&web_location=0.0";
1414
+ }
1415
+ 番剧明细(data$1) {
1416
+ if (data$1.ep_id) return `https://api.bilibili.com/pgc/view/web/season?ep_id=${data$1.ep_id}`;
1417
+ else if (data$1.season_id) return `https://api.bilibili.com/pgc/view/web/season?season_id=${data$1.season_id}`;
1418
+ else throw new Error("拟造接口地址出错,缺少 ep_id 或 season_id 参数");
1419
+ }
1420
+ 番剧视频流信息(data$1) {
1421
+ return `https://api.bilibili.com/pgc/player/web/playurl?cid=${data$1.cid}&ep_id=${data$1.ep_id}`;
1422
+ }
1423
+ 用户空间动态(data$1) {
1424
+ return `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?host_mid=${data$1.host_mid}&dm_img_switch=0&&features=itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,forwardListHidden,decorationCard,commentsNewVersion,onlyfansAssetsV2,ugcDelete,onlyfansQaCard`;
1425
+ }
1426
+ 动态详情(data$1) {
1427
+ return `https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?id=${data$1.dynamic_id}&features=itemOpusStyle,opusBigCover,onlyfansVote,endFooterHidden,decorationCard,onlyfansAssetsV2,ugcDelete,onlyfansQaCard,editable,opusPrivateVisible,avatarAutoTheme`;
1428
+ }
1429
+ 动态卡片信息(data$1) {
1430
+ return `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=${data$1.dynamic_id}`;
1431
+ }
1432
+ 用户名片信息(data$1) {
1433
+ return `https://api.bilibili.com/x/web-interface/card?mid=${data$1.host_mid}&photo=true`;
1434
+ }
1435
+ 直播间信息(data$1) {
1436
+ return `https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${data$1.room_id}`;
1437
+ }
1438
+ 直播间初始化信息(data$1) {
1439
+ return `https://api.live.bilibili.com/room/v1/Room/room_init?id=${data$1.room_id}`;
1440
+ }
1441
+ 申请二维码() {
1442
+ return "https://passport.bilibili.com/x/passport-login/web/qrcode/generate";
1443
+ }
1444
+ 二维码状态(data$1) {
1445
+ return `https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${data$1.qrcode_key}`;
1446
+ }
1447
+ 获取UP主总播放量(data$1) {
1448
+ return `https://api.bilibili.com/x/space/upstat?mid=${data$1.host_mid}`;
1449
+ }
1450
+ 专栏正文内容(data$1) {
1451
+ return `https://api.bilibili.com/x/article/view?id=${data$1.id}`;
1452
+ }
1453
+ 专栏显示卡片信息(data$1) {
1454
+ return `https://api.bilibili.com/x/article/cards?ids=${Array.isArray(data$1.ids) ? data$1.ids.join(",") : data$1.ids}`;
1455
+ }
1456
+ 专栏文章基本信息(data$1) {
1457
+ return `https://api.bilibili.com/x/article/viewinfo?id=${data$1.id}`;
1458
+ }
1459
+ 文集基本信息(data$1) {
1460
+ return `https://api.bilibili.com/x/article/list/web/articles?id=${data$1.id}`;
1541
1461
  }
1542
1462
  };
1463
+ /** 该类下的所有方法只会返回拼接好参数后的 Url 地址,需要手动请求该地址以获取数据 */
1464
+ const bilibiliApiUrls = new BiLiBiLiAPI();
1465
+
1466
+ //#endregion
1467
+ //#region src/platform/bilibili/BilibiliApi.ts
1543
1468
  /**
1544
- * 创建小红书API URLs实例
1545
- * @returns 小红书API URLs对象
1469
+ * 创建B站API方法的通用工厂函数
1470
+ * @template T - B站方法类型键名
1471
+ * @param methodType - 方法类型
1472
+ * @returns 返回配置好的API方法
1546
1473
  */
1547
- const createXiaohongshuApiUrls = () => {
1548
- return xiaohongshuApiUrls;
1474
+ const createBilibiliApiMethod = (methodType) => {
1475
+ return async (options, cookie) => {
1476
+ return await getBilibiliData(methodType, options, cookie);
1477
+ };
1549
1478
  };
1550
-
1551
- //#endregion
1552
- //#region src/validation/xiaohongshu.ts
1553
- const SearchSortTypeValues = Object.values(SearchSortType).filter((v) => typeof v === "string");
1554
- const SearchNoteTypeValues = Object.values(SearchNoteType).filter((v) => typeof v === "number");
1555
1479
  /**
1556
- * 小红书首页推荐数据参数验证模式
1480
+ * 创建绑定cookie的B站API方法工厂函数
1481
+ * @template T - B站方法类型键名
1482
+ * @param methodType - 方法类型
1483
+ * @param cookie - 绑定的cookie
1484
+ * @returns 返回绑定了cookie的API方法
1557
1485
  */
1558
- const HomeFeedParamsSchema = zod.object({
1559
- methodType: zod.literal("首页推荐数据", { error: "方法类型必须是\"首页推荐数据\"" }),
1560
- cursor_score: zod.string({ error: "cursor_score必须是字符串" }).optional(),
1561
- num: zod.coerce.number({ error: "数量必须是数字" }).int({ error: "数量必须是整数" }).min(1, { error: "数量不能小于1" }).max(100, { error: "数量不能大于100" }).optional(),
1562
- refresh_type: zod.coerce.number({ error: "refresh_type必须是数字" }).int({ error: "refresh_type必须是整数" }).optional(),
1563
- note_index: zod.coerce.number({ error: "note_index必须是数字" }).int({ error: "note_index必须是整数" }).optional(),
1564
- category: zod.string({ error: "category必须是字符串" }).optional(),
1565
- search_key: zod.string({ error: "search_key必须是字符串" }).optional()
1566
- });
1486
+ const createBoundBilibiliApiMethod = (methodType, cookie) => {
1487
+ return async (options) => {
1488
+ return await getBilibiliData(methodType, options, cookie);
1489
+ };
1490
+ };
1567
1491
  /**
1568
- * 小红书单个笔记数据参数验证模式
1492
+ * B站相关 API 的命名空间。
1493
+ *
1494
+ * 部分接口可能不需要 Cookie 但建议传递有效的用户 Cookie,以获取更多数据。
1495
+ *
1496
+ * 提供了一系列方法,用于与B站相关的 API 进行交互。
1497
+ *
1498
+ * 每个方法都接受参数和 Cookie,返回 Promise,解析为统一格式的API响应。
1569
1499
  */
1570
- const NoteParamsSchema = zod.object({
1571
- methodType: zod.literal("单个笔记数据", { error: "方法类型必须是\"单个笔记数据\"" }),
1572
- note_id: zod.string({ error: "note_id必须是字符串" }),
1573
- xsec_token: zod.string({ error: "xsec_token必须是字符串" })
1574
- });
1500
+ const bilibili = {
1501
+ getVideoInfo: createBilibiliApiMethod("单个视频作品数据"),
1502
+ getVideoStream: createBilibiliApiMethod("单个视频下载信息数据"),
1503
+ getComments: createBilibiliApiMethod("评论数据"),
1504
+ getCommentReply: createBilibiliApiMethod("指定评论的回复"),
1505
+ getUserProfile: createBilibiliApiMethod("用户主页数据"),
1506
+ getUserDynamic: createBilibiliApiMethod("用户主页动态列表数据"),
1507
+ getEmojiList: createBilibiliApiMethod("Emoji数据"),
1508
+ getBangumiInfo: createBilibiliApiMethod("番剧基本信息数据"),
1509
+ getBangumiStream: createBilibiliApiMethod("番剧下载信息数据"),
1510
+ getDynamicInfo: createBilibiliApiMethod("动态详情数据"),
1511
+ getDynamicCard: createBilibiliApiMethod("动态卡片数据"),
1512
+ getLiveRoomDetail: createBilibiliApiMethod("直播间信息"),
1513
+ getLiveRoomInitInfo: createBilibiliApiMethod("直播间初始化信息"),
1514
+ getLoginBasicInfo: createBilibiliApiMethod("登录基本信息"),
1515
+ getLoginQrcode: createBilibiliApiMethod("申请二维码"),
1516
+ checkQrcodeStatus: createBilibiliApiMethod("二维码状态"),
1517
+ getUserTotalPlayCount: createBilibiliApiMethod("获取UP主总播放量"),
1518
+ convertAvToBv: createBilibiliApiMethod("AV转BV"),
1519
+ convertBvToAv: createBilibiliApiMethod("BV转AV"),
1520
+ getArticleContent: createBilibiliApiMethod("专栏正文内容"),
1521
+ getArticleCard: createBilibiliApiMethod("专栏显示卡片信息"),
1522
+ getArticleInfo: createBilibiliApiMethod("专栏文章基本信息"),
1523
+ getColumnInfo: createBilibiliApiMethod("文集基本信息")
1524
+ };
1575
1525
  /**
1576
- * 小红书评论数据参数验证模式
1526
+ * 创建绑定了cookie的B站API对象
1527
+ * @param cookie - 要绑定的cookie(可选)
1528
+ * @returns 绑定了cookie的B站API对象,调用时不需要再传递cookie
1577
1529
  */
1578
- const CommentParamsSchema = zod.object({
1579
- methodType: zod.literal("评论数据", { error: "方法类型必须是\"评论数据\"" }),
1580
- note_id: zod.string({ error: "note_id必须是字符串" }),
1581
- cursor: zod.string({ error: "cursor必须是字符串" }).optional(),
1582
- xsec_token: zod.string({ error: "xsec_token必须是字符串" })
1583
- });
1530
+ const createBoundBilibiliApi = (cookie, requestConfig) => {
1531
+ return {
1532
+ getVideoInfo: createBoundBilibiliApiMethod("单个视频作品数据", cookie),
1533
+ getVideoStream: createBoundBilibiliApiMethod("单个视频下载信息数据", cookie),
1534
+ getComments: createBoundBilibiliApiMethod("评论数据", cookie),
1535
+ getCommentReply: createBoundBilibiliApiMethod("指定评论的回复", cookie),
1536
+ getUserProfile: createBoundBilibiliApiMethod("用户主页数据", cookie),
1537
+ getUserDynamic: createBoundBilibiliApiMethod("用户主页动态列表数据", cookie),
1538
+ getEmojiList: createBoundBilibiliApiMethod("Emoji数据", cookie),
1539
+ getBangumiInfo: createBoundBilibiliApiMethod("番剧基本信息数据", cookie),
1540
+ getBangumiStream: createBoundBilibiliApiMethod("番剧下载信息数据", cookie),
1541
+ getDynamicInfo: createBoundBilibiliApiMethod("动态详情数据", cookie),
1542
+ getDynamicCard: createBoundBilibiliApiMethod("动态卡片数据", cookie),
1543
+ getLiveRoomDetail: createBoundBilibiliApiMethod("直播间信息", cookie),
1544
+ getLiveRoomInitInfo: createBoundBilibiliApiMethod("直播间初始化信息", cookie),
1545
+ getLoginBasicInfo: createBoundBilibiliApiMethod("登录基本信息", cookie),
1546
+ getLoginQrcode: createBoundBilibiliApiMethod("申请二维码", cookie),
1547
+ checkQrcodeStatus: createBoundBilibiliApiMethod("二维码状态", cookie),
1548
+ getUserTotalPlayCount: createBoundBilibiliApiMethod("获取UP主总播放量", cookie),
1549
+ convertAvToBv: createBoundBilibiliApiMethod("AV转BV", cookie),
1550
+ convertBvToAv: createBoundBilibiliApiMethod("BV转AV", cookie),
1551
+ getArticleContent: createBoundBilibiliApiMethod("专栏正文内容", cookie),
1552
+ getArticleCard: createBoundBilibiliApiMethod("专栏显示卡片信息", cookie),
1553
+ getArticleInfo: createBoundBilibiliApiMethod("专栏文章基本信息", cookie),
1554
+ getColumnInfo: createBoundBilibiliApiMethod("文集基本信息", cookie)
1555
+ };
1556
+ };
1557
+
1558
+ //#endregion
1559
+ //#region src/platform/bilibili/sign/bv2av.ts
1560
+ const XOR_CODE = 23442827791579n;
1561
+ const MASK_CODE = 2251799813685247n;
1562
+ const MAX_AID = 1n << 51n;
1563
+ const BASE = 58n;
1564
+ const data = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf";
1584
1565
  /**
1585
- * 小红书用户数据参数验证模式
1566
+ * av号转bv号
1567
+ * @param aid av号
1568
+ * @returns
1586
1569
  */
1587
- const UserParamsSchema = zod.object({
1588
- methodType: zod.literal("用户数据", { error: "方法类型必须是\"用户数据\"" }),
1589
- user_id: zod.string({ error: "user_id必须是字符串" })
1590
- });
1570
+ const av2bv = (aid) => {
1571
+ const bytes = [
1572
+ "B",
1573
+ "V",
1574
+ "1",
1575
+ "0",
1576
+ "0",
1577
+ "0",
1578
+ "0",
1579
+ "0",
1580
+ "0",
1581
+ "0",
1582
+ "0",
1583
+ "0"
1584
+ ];
1585
+ let bvIndex = bytes.length - 1;
1586
+ let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
1587
+ while (tmp > 0) {
1588
+ bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
1589
+ tmp = tmp / BASE;
1590
+ bvIndex -= 1;
1591
+ }
1592
+ [bytes[3], bytes[9]] = [bytes[9], bytes[3]];
1593
+ [bytes[4], bytes[7]] = [bytes[7], bytes[4]];
1594
+ return bytes.join("");
1595
+ };
1596
+ /**
1597
+ * bv号转av号
1598
+ * @param bvid bv号
1599
+ * @returns
1600
+ */
1601
+ const bv2av = (bvid) => {
1602
+ const bvidArr = Array.from(bvid);
1603
+ [bvidArr[3], bvidArr[9]] = [bvidArr[9], bvidArr[3]];
1604
+ [bvidArr[4], bvidArr[7]] = [bvidArr[7], bvidArr[4]];
1605
+ bvidArr.splice(0, 3);
1606
+ const tmp = bvidArr.reduce((pre, bvidChar) => pre * BASE + BigInt(data.indexOf(bvidChar)), 0n);
1607
+ return Number(tmp & MASK_CODE ^ XOR_CODE);
1608
+ };
1609
+
1610
+ //#endregion
1611
+ //#region src/platform/bilibili/sign/wbi.ts
1612
+ /**
1613
+ * 混合密钥编码表,用于对 imgKey 和 subKey 进行字符顺序打乱编码
1614
+ */
1615
+ const mixinKeyEncTab = [
1616
+ 46,
1617
+ 47,
1618
+ 18,
1619
+ 2,
1620
+ 53,
1621
+ 8,
1622
+ 23,
1623
+ 32,
1624
+ 15,
1625
+ 50,
1626
+ 10,
1627
+ 31,
1628
+ 58,
1629
+ 3,
1630
+ 45,
1631
+ 35,
1632
+ 27,
1633
+ 43,
1634
+ 5,
1635
+ 49,
1636
+ 33,
1637
+ 9,
1638
+ 42,
1639
+ 19,
1640
+ 29,
1641
+ 28,
1642
+ 14,
1643
+ 39,
1644
+ 12,
1645
+ 38,
1646
+ 41,
1647
+ 13,
1648
+ 37,
1649
+ 48,
1650
+ 7,
1651
+ 16,
1652
+ 24,
1653
+ 55,
1654
+ 40,
1655
+ 61,
1656
+ 26,
1657
+ 17,
1658
+ 0,
1659
+ 1,
1660
+ 60,
1661
+ 51,
1662
+ 30,
1663
+ 4,
1664
+ 22,
1665
+ 25,
1666
+ 54,
1667
+ 21,
1668
+ 56,
1669
+ 59,
1670
+ 6,
1671
+ 63,
1672
+ 57,
1673
+ 62,
1674
+ 11,
1675
+ 36,
1676
+ 20,
1677
+ 34,
1678
+ 44,
1679
+ 52
1680
+ ];
1591
1681
  /**
1592
- * 小红书用户笔记数据参数验证模式
1682
+ * 对 imgKey 和 subKey 进行字符顺序打乱编码
1683
+ * @param orig - 原始字符串数组,通常是 img_key + sub_key 的字符数组
1684
+ * @returns 返回经过编码表打乱后的32位字符串
1593
1685
  */
1594
- const UserNoteParamsSchema = zod.object({
1595
- methodType: zod.literal("用户笔记数据", { error: "方法类型必须是\"用户笔记数据\"" }),
1596
- user_id: zod.string({ error: "user_id必须是字符串" }),
1597
- cursor: zod.string({ error: "cursor必须是字符串" }).optional(),
1598
- num: zod.coerce.number({ error: "数量必须是数字" }).int({ error: "数量必须是整数" }).min(1, { error: "数量不能小于1" }).max(100, { error: "数量不能大于100" }).optional()
1599
- });
1600
- const EmojiListParamsSchema = zod.object({ methodType: zod.literal("表情列表", { error: "方法类型必须是\"表情列表\"" }) });
1686
+ const getMixinKey = (orig) => mixinKeyEncTab.map((n) => orig[n]).join("").slice(0, 32);
1601
1687
  /**
1602
- * 小红书搜索笔记参数验证模式
1688
+ * 为请求参数进行 WBI 签名
1689
+ * @param params - 请求参数对象,键值对形式
1690
+ * @param img_key - 图片密钥
1691
+ * @param sub_key - 子密钥
1692
+ * @returns 返回包含时间戳和签名的查询字符串
1603
1693
  */
1604
- const SearchNoteParamsSchema = zod.object({
1605
- methodType: zod.literal("搜索笔记", { error: "方法类型必须是\"搜索笔记\"" }),
1606
- keyword: zod.string({ error: "keyword必须是字符串" }),
1607
- page: zod.coerce.number({ error: "page必须是数字" }).int({ error: "page必须是整数" }).min(1, { error: "page不能小于1" }).optional(),
1608
- page_size: zod.coerce.number({ error: "page_size必须是数字" }).int({ error: "page_size必须是整数" }).min(1, { error: "page_size不能小于1" }).max(100, { error: "page_size不能大于100" }).optional(),
1609
- sort: zod.enum(SearchSortTypeValues, { error: "排序类型不合法" }).optional(),
1610
- note_type: zod.coerce.number({ error: "笔记类型必须是数字" }).int({ error: "笔记类型必须是整数" }).refine((val) => SearchNoteTypeValues.includes(val), { message: "笔记类型不合法" }).optional()
1611
- });
1694
+ const encWbi = (params, img_key, sub_key) => {
1695
+ const mixin_key = getMixinKey(img_key + sub_key);
1696
+ const curr_time = Math.round(Date.now() / 1e3);
1697
+ const chr_filter = /[!'()*]/g;
1698
+ Object.assign(params, { wts: curr_time });
1699
+ const query = Object.keys(params).sort().map((key) => {
1700
+ const value = params[key].toString().replace(chr_filter, "");
1701
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
1702
+ }).join("&");
1703
+ return `&wts=${curr_time}&w_rid=${crypto.createHash("md5").update(query + mixin_key).digest("hex")}`;
1704
+ };
1612
1705
  /**
1613
- * 小红书验证模式映射
1706
+ * 获取最新的 img_key 和 sub_key
1707
+ * @param cookie - 有效的用户 Cookie 字符串
1708
+ * @returns 返回包含 img_key 和 sub_key 的对象
1709
+ * @throws 当网络请求失败或响应格式不正确时抛出错误
1614
1710
  */
1615
- const XiaohongshuValidationSchemas = {
1616
- 首页推荐数据: HomeFeedParamsSchema,
1617
- 单个笔记数据: NoteParamsSchema,
1618
- 评论数据: CommentParamsSchema,
1619
- 用户数据: UserParamsSchema,
1620
- 用户笔记数据: UserNoteParamsSchema,
1621
- 表情列表: EmojiListParamsSchema,
1622
- 搜索笔记: SearchNoteParamsSchema
1711
+ const getWbiKeys = async (cookie) => {
1712
+ const { data: { wbi_img: { img_url, sub_url } } } = (await axios("https://api.bilibili.com/x/web-interface/nav", { headers: { Cookie: cookie } })).data;
1713
+ return {
1714
+ img_key: img_url.slice(img_url.lastIndexOf("/") + 1, img_url.lastIndexOf(".")),
1715
+ sub_key: sub_url.slice(sub_url.lastIndexOf("/") + 1, sub_url.lastIndexOf("."))
1716
+ };
1623
1717
  };
1624
1718
  /**
1625
- * 小红书方法类型
1719
+ * 对请求链接进行 WBI 签名
1720
+ * @param BASEURL - 完整的请求地址,可以是字符串或 URL 对象
1721
+ * @param cookie - 有效的用户 Cookie 字符串
1722
+ * @returns 返回包含 WBI 签名的查询字符串
1723
+ * @throws 当获取 WBI 密钥失败或 URL 解析失败时抛出错误
1626
1724
  */
1627
- const XiaohongshuMethodRoutes = {
1628
- 首页推荐数据: "/fetch_home_feed",
1629
- 单个笔记数据: "/fetch_one_note",
1630
- 评论数据: "/fetch_note_comments",
1631
- 用户数据: "/fetch_user_profile",
1632
- 用户笔记数据: "/fetch_user_notes",
1633
- 表情列表: "/fetch_emoji_list",
1634
- 搜索笔记: "/fetch_search_notes"
1725
+ const wbi_sign = async (BASEURL, cookie) => {
1726
+ const web_keys = await getWbiKeys(cookie);
1727
+ const url$1 = new URL(BASEURL);
1728
+ const params = {};
1729
+ for (const [key, value] of url$1.searchParams.entries()) params[key] = value;
1730
+ return encWbi(params, web_keys.img_key, web_keys.sub_key);
1635
1731
  };
1636
1732
 
1637
1733
  //#endregion
1638
- //#region src/validation/index.ts
1639
- /**
1640
- * 验证抖音参数
1641
- * @param methodType - 抖音方法类型
1642
- * @param params - 待验证的参数
1643
- * @returns 验证后的参数,符合原始API期望的类型
1644
- */
1645
- const validateDouyinParams = (methodType, params) => {
1646
- return DouyinValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1647
- methodType,
1648
- ...params
1649
- } : {
1650
- methodType,
1651
- params
1652
- });
1653
- };
1654
- /**
1655
- * 验证哔哩哔哩参数
1656
- * @param methodType - 哔哩哔哩方法类型
1657
- * @param params - 待验证的参数
1658
- * @returns 验证后的参数,符合原始API期望的类型
1659
- */
1660
- const validateBilibiliParams = (methodType, params) => {
1661
- return BilibiliValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1662
- methodType,
1663
- ...params
1664
- } : {
1665
- methodType,
1666
- params
1667
- });
1668
- };
1734
+ //#region src/utils/errors.ts
1669
1735
  /**
1670
- * 验证快手参数
1671
- * @param methodType - 快手方法类型
1672
- * @param params - 待验证的参数
1673
- * @returns 验证后的参数,符合原始API期望的类型
1736
+ * API错误类
1674
1737
  */
1675
- const validateKuaishouParams = (methodType, params) => {
1676
- return KuaishouValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1677
- methodType,
1678
- ...params
1679
- } : {
1680
- methodType,
1681
- params
1682
- });
1738
+ var ApiError = class extends Error {
1739
+ code;
1740
+ platform;
1741
+ /**
1742
+ * 构造API错误
1743
+ * @param message - 错误消息
1744
+ * @param code - 错误代码
1745
+ * @param platform - 平台名称
1746
+ */
1747
+ constructor(message, code = 500, platform = "unknown") {
1748
+ super(message);
1749
+ this.name = "ApiError";
1750
+ this.code = code;
1751
+ this.platform = platform;
1752
+ }
1683
1753
  };
1684
1754
  /**
1685
- * 验证小红书参数
1686
- * @param methodType - 小红书方法类型
1687
- * @param params - 待验证的参数
1688
- * @returns 验证后的参数
1755
+ * 参数验证错误类
1689
1756
  */
1690
- const validateXiaohongshuParams = (methodType, params) => {
1691
- return XiaohongshuValidationSchemas[methodType].parse(typeof params === "object" && params !== null ? {
1692
- methodType,
1693
- ...params
1694
- } : {
1695
- methodType,
1696
- params
1697
- });
1757
+ var ValidationError = class ValidationError extends Error {
1758
+ errors;
1759
+ requestPath;
1760
+ /**
1761
+ * 构造参数验证错误
1762
+ * @param message - 错误消息
1763
+ * @param errors - 详细错误信息
1764
+ * @param requestPath - HTTP请求路径
1765
+ */
1766
+ constructor(message, errors, requestPath) {
1767
+ super(message);
1768
+ this.name = "ValidationError";
1769
+ this.errors = errors;
1770
+ this.requestPath = requestPath;
1771
+ }
1772
+ /**
1773
+ * 从Zod错误创建验证错误
1774
+ * @param zodError - Zod验证错误
1775
+ * @param requestPath - HTTP请求路径
1776
+ * @returns 验证错误实例
1777
+ */
1778
+ static fromZodError(zodError, requestPath) {
1779
+ return new ValidationError("参数验证失败", zodError.issues.map((err) => ({
1780
+ field: err.path.join("."),
1781
+ message: err.message
1782
+ })), requestPath);
1783
+ }
1698
1784
  };
1699
1785
  /**
1700
- * 创建成功响应格式
1701
- * @param data - 响应数据
1702
- * @param message - 响应消息(可选)
1703
- * @param code - 响应状态码(可选,默认200)
1704
- * @returns 格式化的成功API响应对象
1786
+ * 处理错误并返回统一格式
1787
+ * @param error - 错误对象
1788
+ * @param requestPath - HTTP请求路径(可选)
1789
+ * @returns 统一的错误响应格式
1705
1790
  */
1706
- const createSuccessResponse = (data$1, message, code = 200) => {
1707
- return {
1708
- success: true,
1709
- data: data$1,
1710
- message,
1711
- code,
1712
- error: void 0
1791
+ const handleError = (error, requestPath) => {
1792
+ if (error instanceof ValidationError) return {
1793
+ code: 400,
1794
+ message: error.message,
1795
+ data: null,
1796
+ errors: error.errors,
1797
+ requestPath: error.requestPath ?? requestPath
1713
1798
  };
1714
- };
1715
- /**
1716
- * 创建失败响应格式
1717
- * @param error - 错误信息
1718
- * @param message - 详细错误消息(可选)
1719
- * @param code - 错误状态码(可选,默认500)
1720
- * @returns 格式化的错误响应对象
1721
- */
1722
- const createErrorResponse = (error, message, code = 500) => {
1799
+ if (error instanceof ApiError) return {
1800
+ code: error.code,
1801
+ message: error.message,
1802
+ data: null,
1803
+ platform: error.platform,
1804
+ requestPath
1805
+ };
1806
+ if (error instanceof zod.ZodError) return handleError(ValidationError.fromZodError(error, requestPath), requestPath);
1723
1807
  return {
1724
- success: false,
1725
- error,
1726
- message,
1727
- code,
1728
- data: void 0
1808
+ code: 500,
1809
+ message: error instanceof Error ? error.message : "未知错误",
1810
+ data: null,
1811
+ requestPath
1729
1812
  };
1730
1813
  };
1731
1814
 
@@ -2078,6 +2161,18 @@ const fetchBilibili = async (data$1, cookie, requestConfig) => {
2078
2161
  ...baseRequestConfig,
2079
2162
  url: bilibiliApiUrls.二维码状态({ qrcode_key: data$1.qrcode_key })
2080
2163
  });
2164
+ if (isNetworkErrorResult(result)) {
2165
+ const networkError = new Error(result.error.amagiError.errorDescription);
2166
+ Object.assign(networkError, {
2167
+ code: result.error.code,
2168
+ data: null,
2169
+ amagiError: {
2170
+ ...result.error.amagiError,
2171
+ requestType: data$1.methodType
2172
+ }
2173
+ });
2174
+ throw networkError;
2175
+ }
2081
2176
  if (result.data.code !== 0) {
2082
2177
  const Err = {
2083
2178
  errorDescription: `获取响应数据失败!原因:${bilibiliErrorCodeMap[String(result.data.code)] || result.data.message || "未知错误"}!`,
@@ -2159,6 +2254,18 @@ const GlobalGetData$3 = async (type, options, retryCount = 0) => {
2159
2254
  let warningMessage = "";
2160
2255
  try {
2161
2256
  const result = await fetchData(options);
2257
+ if (isNetworkErrorResult(result)) {
2258
+ const networkError = new Error(result.error.amagiError.errorDescription);
2259
+ Object.assign(networkError, {
2260
+ code: result.error.code,
2261
+ data: null,
2262
+ amagiError: {
2263
+ ...result.error.amagiError,
2264
+ requestType: type
2265
+ }
2266
+ });
2267
+ throw networkError;
2268
+ }
2162
2269
  if (!result || result === "") {
2163
2270
  const Err = {
2164
2271
  errorDescription: "获取响应数据失败!接口返回内容为空,你的B站ck可能已经失效!",
@@ -3892,6 +3999,18 @@ const GlobalGetData$2 = async (type, config) => {
3892
3999
  let warningMessage = "";
3893
4000
  try {
3894
4001
  const result = await fetchData(config);
4002
+ if (isNetworkErrorResult(result)) {
4003
+ const networkError = new Error(result.error.amagiError.errorDescription);
4004
+ Object.assign(networkError, {
4005
+ code: result.error.code,
4006
+ data: null,
4007
+ amagiError: {
4008
+ ...result.error.amagiError,
4009
+ requestType: type
4010
+ }
4011
+ });
4012
+ throw networkError;
4013
+ }
3895
4014
  if (!result || result === "") {
3896
4015
  const Err = {
3897
4016
  errorDescription: "获取响应数据失败!接口返回内容为空,你的抖音ck可能已经失效!",
@@ -4095,6 +4214,18 @@ const GlobalGetData$1 = async (type, options) => {
4095
4214
  let warningMessage = "";
4096
4215
  try {
4097
4216
  const result = await fetchData(options);
4217
+ if (isNetworkErrorResult(result)) {
4218
+ const networkError = new Error(result.error.amagiError.errorDescription);
4219
+ Object.assign(networkError, {
4220
+ code: result.error.code,
4221
+ data: null,
4222
+ amagiError: {
4223
+ ...result.error.amagiError,
4224
+ requestType: type
4225
+ }
4226
+ });
4227
+ throw networkError;
4228
+ }
4098
4229
  if (result === "" || !result || result.result === 2) {
4099
4230
  const Err = {
4100
4231
  errorDescription: "获取响应数据失败!接口返回内容为空!",
@@ -4279,6 +4410,18 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4279
4410
  const GlobalGetData = async (methodType, config) => {
4280
4411
  try {
4281
4412
  const response = await fetchData(config);
4413
+ if (isNetworkErrorResult(response)) {
4414
+ const networkError = new Error(response.error.amagiError.errorDescription);
4415
+ Object.assign(networkError, {
4416
+ code: response.error.code,
4417
+ data: null,
4418
+ amagiError: {
4419
+ ...response.error.amagiError,
4420
+ requestType: methodType
4421
+ }
4422
+ });
4423
+ throw networkError;
4424
+ }
4282
4425
  if (typeof response === "string" && response.includes("<html>")) return response;
4283
4426
  if (response.code !== 0) throw new Error(`API请求失败: ${response.data?.msg ?? response.msg ?? "未知错误"}, code: ${response.code}`);
4284
4427
  return response;
@@ -4960,7 +5103,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType$1) {
4960
5103
 
4961
5104
  //#endregion
4962
5105
  //#region src/index.ts
4963
- const VERSION = "5.10.1";
5106
+ const VERSION = "5.11.1";
4964
5107
  /**
4965
5108
  * @deprecated 请使用 createAmagiClient 替代
4966
5109
  */
@@ -4996,4 +5139,4 @@ const Client = CreateApp;
4996
5139
  const amagi = Client;
4997
5140
 
4998
5141
  //#endregion
4999
- export { AdditionalType, ApiError, BilibiliArticleCardParamsSchema, BilibiliArticleInfoParamsSchema, BilibiliArticleParamsSchema, BilibiliAv2BvParamsSchema, BilibiliBangumiInfoParamsSchema, BilibiliBangumiStreamParamsSchema, BilibiliBv2AvParamsSchema, BilibiliColumnInfoParamsSchema, BilibiliCommentParamsSchema, BilibiliCommentReplyParamsSchema, BilibiliDynamicParamsSchema, BilibiliEmojiParamsSchema, BilibiliLiveParamsSchema, BilibiliLoginParamsSchema, BilibiliMethodRoutes, BilibiliQrcodeParamsSchema, BilibiliQrcodeStatusParamsSchema, BilibiliUserParamsSchema, BilibiliValidationSchemas, BilibiliVideoDownloadParamsSchema, BilibiliVideoParamsSchema, CommentType, CreateApp, DouyinCommentParamsSchema, DouyinCommentReplyParamsSchema, DouyinDanmakuParamsSchema, DouyinEmojiListParamsSchema, DouyinEmojiProParamsSchema, DouyinHotWordsParamsSchema, DouyinLiveRoomParamsSchema, DouyinMethodRoutes, DouyinMusicParamsSchema, DouyinQrcodeParamsSchema, DouyinSearchParamsSchema, DouyinUserParamsSchema, DouyinValidationSchemas, DouyinWorkParamsSchema, DynamicType, KuaishouCommentParamsSchema, KuaishouEmojiParamsSchema, KuaishouMethodRoutes, KuaishouValidationSchemas, KuaishouVideoParamsSchema, MajorType, ValidationError, XiaohongshuMethodRoutes, XiaohongshuValidationSchemas, amagi, amagiClient, av2bv, bilibili, bilibiliApiUrls, bilibiliErrorCodeMap, bilibiliUtils, bv2av, createAmagiClient, createBilibiliRoutes, createBilibiliRoutes as registerBilibiliRoutes, createBoundBilibiliApi, createBoundDouyinApi, createBoundKuaishouApi, createBoundXiaohongshuApi, createDouyinRoutes, createDouyinRoutes as registerDouyinRoutes, createErrorResponse, createKuaishouRoutes, createKuaishouRoutes as registerKuaishouRoutes, createSuccessResponse, createXiaohongshuRoutes, createXiaohongshuRoutes as registerXiaohongshuRoutes, Client as default, douyin, douyinApiUrls, douyinSign, douyinUtils, fetchData, fetchResponse, getBilibiliData, getDouyinData, getHeadersAndData, getKuaishouData, handleError, httpLogger, kuaishou, kuaishouApiUrls, kuaishouUtils, logMiddleware, logger, qtparam, validateBilibiliParams, validateDouyinParams, validateKuaishouParams, validateXiaohongshuParams, wbi_sign, xiaohongshu, xiaohongshuApiUrls, xiaohongshuSign, xiaohongshuUtils };
5142
+ export { AdditionalType, ApiError, BilibiliArticleCardParamsSchema, BilibiliArticleInfoParamsSchema, BilibiliArticleParamsSchema, BilibiliAv2BvParamsSchema, BilibiliBangumiInfoParamsSchema, BilibiliBangumiStreamParamsSchema, BilibiliBv2AvParamsSchema, BilibiliColumnInfoParamsSchema, BilibiliCommentParamsSchema, BilibiliCommentReplyParamsSchema, BilibiliDynamicParamsSchema, BilibiliEmojiParamsSchema, BilibiliLiveParamsSchema, BilibiliLoginParamsSchema, BilibiliMethodRoutes, BilibiliQrcodeParamsSchema, BilibiliQrcodeStatusParamsSchema, BilibiliUserParamsSchema, BilibiliValidationSchemas, BilibiliVideoDownloadParamsSchema, BilibiliVideoParamsSchema, CommentType, CreateApp, DouyinCommentParamsSchema, DouyinCommentReplyParamsSchema, DouyinDanmakuParamsSchema, DouyinEmojiListParamsSchema, DouyinEmojiProParamsSchema, DouyinHotWordsParamsSchema, DouyinLiveRoomParamsSchema, DouyinMethodRoutes, DouyinMusicParamsSchema, DouyinQrcodeParamsSchema, DouyinSearchParamsSchema, DouyinUserParamsSchema, DouyinValidationSchemas, DouyinWorkParamsSchema, DynamicType, KuaishouCommentParamsSchema, KuaishouEmojiParamsSchema, KuaishouMethodRoutes, KuaishouValidationSchemas, KuaishouVideoParamsSchema, MajorType, ValidationError, XiaohongshuMethodRoutes, XiaohongshuValidationSchemas, amagi, amagiClient, av2bv, bilibili, bilibiliApiUrls, bilibiliErrorCodeMap, bilibiliUtils, bv2av, createAmagiClient, createBilibiliRoutes, createBilibiliRoutes as registerBilibiliRoutes, createBoundBilibiliApi, createBoundDouyinApi, createBoundKuaishouApi, createBoundXiaohongshuApi, createDouyinRoutes, createDouyinRoutes as registerDouyinRoutes, createErrorResponse, createKuaishouRoutes, createKuaishouRoutes as registerKuaishouRoutes, createSuccessResponse, createXiaohongshuRoutes, createXiaohongshuRoutes as registerXiaohongshuRoutes, Client as default, douyin, douyinApiUrls, douyinSign, douyinUtils, fetchData, fetchResponse, getBilibiliData, getDouyinData, getHeadersAndData, getKuaishouData, handleError, httpLogger, isNetworkErrorResult, kuaishou, kuaishouApiUrls, kuaishouUtils, logMiddleware, logger, qtparam, validateBilibiliParams, validateDouyinParams, validateKuaishouParams, validateXiaohongshuParams, wbi_sign, xiaohongshu, xiaohongshuApiUrls, xiaohongshuSign, xiaohongshuUtils };