@ikenxuan/amagi 5.10.0 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,1499 @@ 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
+ */
1227
+ const isNetworkErrorResult = (result) => {
1228
+ return result !== null && typeof result === "object" && "success" in result && result.success === false;
1229
+ };
1230
+ /**
1231
+ * 获取响应头和数据(带自动重试)
1232
+ * @param config - axios请求配置
1233
+ * @param maxRetries - 最大重试次数,默认3次
1234
+ * @returns 包含headers和data的对象,或错误结果
1235
+ */
1236
+ const getHeadersAndData = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
1237
+ const response = await fetchResponse(config, maxRetries);
1238
+ if ("success" in response && response.success === false) return response;
1239
+ return {
1240
+ headers: normalizeHeaders(response.headers),
1241
+ data: response.data
1242
+ };
1392
1243
  };
1393
1244
 
1394
1245
  //#endregion
1395
- //#region src/platform/xiaohongshu/API.ts
1246
+ //#region src/platform/defaultConfigs.ts
1396
1247
  /**
1397
- * 搜索排序类型枚举
1248
+ * 根据User-Agent生成对应的Sec-Ch-Ua值
1249
+ * @param userAgent - 用户代理字符串
1250
+ * @returns 对应的Sec-Ch-Ua值
1398
1251
  */
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
- }({});
1252
+ const generateSecChUa = (userAgent) => {
1253
+ const chromeMatch = userAgent.match(/Chrome\/(\d+)/);
1254
+ const chromeVersion = chromeMatch ? chromeMatch[1] : "125";
1255
+ return `"Not)A;Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`;
1256
+ };
1257
+ /**
1258
+ * 抖音平台默认请求配置
1259
+ * @param cookie - 用户Cookie
1260
+ * @param requestConfig - 外部请求配置(优先级最高)
1261
+ * @returns 合并后的请求配置
1262
+ */
1263
+ const getDouyinDefaultConfig = (cookie, requestConfig) => {
1264
+ 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";
1265
+ finalUserAgent = finalUserAgent.replace(/\s+Edg\/[\d\.]+/g, "");
1266
+ const defHeaders = {
1267
+ Accept: "application/json, text/plain, */*",
1268
+ "Accept-Encoding": "gzip, deflate, br, zstd",
1269
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1270
+ Cookie: cookie ? cookie.replace(/\s+/g, "") : "",
1271
+ Priority: "u=1, i",
1272
+ Referer: "https://www.douyin.com/",
1273
+ "Sec-Ch-Ua": generateSecChUa(finalUserAgent),
1274
+ "Sec-Ch-Ua-Mobile": "?0",
1275
+ "Sec-Ch-Ua-Platform": "\"Windows\"",
1276
+ "Sec-Fetch-Dest": "empty",
1277
+ "Sec-Fetch-Mode": "cors",
1278
+ "Sec-Fetch-Site": "same-origin",
1279
+ "User-Agent": finalUserAgent
1280
+ };
1281
+ return {
1282
+ method: "GET",
1283
+ timeout: 1e4,
1284
+ ...requestConfig,
1285
+ headers: {
1286
+ ...defHeaders,
1287
+ ...requestConfig?.headers ?? {}
1288
+ }
1289
+ };
1290
+ };
1414
1291
  /**
1415
- * 搜索笔记类型枚举
1292
+ * B站平台默认请求配置
1293
+ * @param cookie - 用户Cookie
1294
+ * @param requestConfig - 外部请求配置(优先级最高)
1295
+ * @returns 合并后的请求配置
1416
1296
  */
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
- }({});
1297
+ const getBilibiliDefaultConfig = (cookie, requestConfig) => {
1298
+ const defHeaders = {
1299
+ Accept: "application/json, text/plain, */*",
1300
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1301
+ "Accept-Encoding": "gzip, deflate, br",
1302
+ Origin: "https://www.bilibili.com",
1303
+ Referer: "https://www.bilibili.com/",
1304
+ Priority: "u=1, i",
1305
+ "Sec-Ch-Ua": "\"Microsoft Edge\";v=\"141\", \"Chromium\";v=\"141\", \"Not_A Brand\";v=\"24\"",
1306
+ "Sec-Ch-Ua-Mobile": "?0",
1307
+ "Sec-Ch-Ua-Platform": "\"Windows\"",
1308
+ "Sec-Fetch-Dest": "empty",
1309
+ "Sec-Fetch-Mode": "cors",
1310
+ "Sec-Fetch-Site": "same-site",
1311
+ Cookie: cookie ? cookie.replace(/\s+/g, "") : ""
1312
+ };
1313
+ return {
1314
+ method: "GET",
1315
+ timeout: 1e4,
1316
+ ...requestConfig,
1317
+ headers: {
1318
+ ...defHeaders,
1319
+ ...requestConfig?.headers ?? {}
1320
+ }
1321
+ };
1322
+ };
1432
1323
  /**
1433
- * 构建查询字符串
1434
- * @param params - 参数对象
1435
- * @returns 查询字符串
1324
+ * 快手平台默认请求配置
1325
+ * @param cookie - 用户Cookie
1326
+ * @param requestConfig - 外部请求配置(优先级最高)
1327
+ * @returns 合并后的请求配置
1436
1328
  */
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("&");
1329
+ const getKuaishouDefaultConfig = (cookie, requestConfig) => {
1330
+ const defHeaders = {
1331
+ Referer: "https://www.kuaishou.com/new-reco",
1332
+ Origin: "https://www.kuaishou.com",
1333
+ Accept: "application/json, text/plain, */*",
1334
+ "Accept-Encoding": "gzip, deflate, br, zstd",
1335
+ "Content-Type": "application/json",
1336
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1337
+ Priority: "u=0, i",
1338
+ "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",
1339
+ Cookie: cookie ? cookie.replace(/\s+/g, "") : ""
1340
+ };
1341
+ return {
1342
+ method: "POST",
1343
+ timeout: 1e4,
1344
+ ...requestConfig,
1345
+ headers: {
1346
+ ...defHeaders,
1347
+ ...requestConfig?.headers ?? {}
1348
+ }
1349
+ };
1439
1350
  };
1440
1351
  /**
1441
- * 小红书API地址配置
1352
+ * 获取小红书默认配置
1353
+ * @param cookie - 用户Cookie
1354
+ * @returns 小红书请求配置
1442
1355
  */
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
- };
1356
+ const getXiaohongshuDefaultConfig = (cookie) => {
1357
+ return { headers: {
1358
+ accept: "application/json, text/plain, */*",
1359
+ "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
1360
+ "cache-control": "no-cache",
1361
+ "content-type": "application/json;charset=UTF-8",
1362
+ pragma: "no-cache",
1363
+ priority: "u=1, i",
1364
+ referer: "https://www.xiaohongshu.com/",
1365
+ "sec-ch-ua": "\"Microsoft Edge\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
1366
+ "sec-ch-ua-mobile": "?0",
1367
+ "sec-ch-ua-platform": "\"Windows\"",
1368
+ "sec-fetch-dest": "empty",
1369
+ "sec-fetch-mode": "cors",
1370
+ "sec-fetch-site": "same-site",
1371
+ "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",
1372
+ cookie: cookie ?? ""
1373
+ } };
1374
+ };
1375
+
1376
+ //#endregion
1377
+ //#region src/platform/bilibili/API.ts
1378
+ var BiLiBiLiAPI = class {
1379
+ 登录基本信息() {
1380
+ return "https://api.bilibili.com/x/web-interface/nav";
1381
+ }
1382
+ 视频详细信息(data$1) {
1383
+ return `https://api.bilibili.com/x/web-interface/view?bvid=${data$1.bvid}`;
1384
+ }
1385
+ 视频流信息(data$1) {
1386
+ return `https://api.bilibili.com/x/player/playurl?avid=${data$1.avid}&cid=${data$1.cid}`;
1387
+ }
1388
+ /** 评论区类型,type参数详见 [评论区类型代码](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/readme.md#评论区类型代码) */
1389
+ 评论区明细(data$1) {
1390
+ const params = new URLSearchParams({
1391
+ oid: data$1.oid.toString(),
1392
+ type: data$1.type.toString(),
1393
+ mode: (data$1.mode ?? 3).toString(),
1394
+ plat: "1",
1395
+ seek_rpid: "",
1396
+ web_location: "1315875"
1397
+ });
1398
+ if (data$1.pagination_str) params.append("pagination_str", JSON.stringify({ offset: data$1.pagination_str }));
1399
+ else params.append("pagination_str", JSON.stringify({ offset: "" }));
1400
+ return `https://api.bilibili.com/x/v2/reply/wbi/main?${params.toString()}`;
1401
+ }
1402
+ 评论区状态(data$1) {
1403
+ return `https://api.bilibili.com/x/v2/reply/subject/description?type=${data$1.type}&oid=${data$1.oid}`;
1404
+ }
1405
+ /** 指定评论的回复 */
1406
+ 指定评论的回复(data$1) {
1407
+ 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}`;
1408
+ }
1409
+ 表情列表() {
1410
+ return "https://api.bilibili.com/x/emote/user/panel/web?business=reply&web_location=0.0";
1411
+ }
1412
+ 番剧明细(data$1) {
1413
+ if (data$1.ep_id) return `https://api.bilibili.com/pgc/view/web/season?ep_id=${data$1.ep_id}`;
1414
+ else if (data$1.season_id) return `https://api.bilibili.com/pgc/view/web/season?season_id=${data$1.season_id}`;
1415
+ else throw new Error("拟造接口地址出错,缺少 ep_id 或 season_id 参数");
1416
+ }
1417
+ 番剧视频流信息(data$1) {
1418
+ return `https://api.bilibili.com/pgc/player/web/playurl?cid=${data$1.cid}&ep_id=${data$1.ep_id}`;
1419
+ }
1420
+ 用户空间动态(data$1) {
1421
+ 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`;
1422
+ }
1423
+ 动态详情(data$1) {
1424
+ 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`;
1425
+ }
1426
+ 动态卡片信息(data$1) {
1427
+ return `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=${data$1.dynamic_id}`;
1428
+ }
1429
+ 用户名片信息(data$1) {
1430
+ return `https://api.bilibili.com/x/web-interface/card?mid=${data$1.host_mid}&photo=true`;
1431
+ }
1432
+ 直播间信息(data$1) {
1433
+ return `https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${data$1.room_id}`;
1434
+ }
1435
+ 直播间初始化信息(data$1) {
1436
+ return `https://api.live.bilibili.com/room/v1/Room/room_init?id=${data$1.room_id}`;
1437
+ }
1438
+ 申请二维码() {
1439
+ return "https://passport.bilibili.com/x/passport-login/web/qrcode/generate";
1440
+ }
1441
+ 二维码状态(data$1) {
1442
+ return `https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${data$1.qrcode_key}`;
1443
+ }
1444
+ 获取UP主总播放量(data$1) {
1445
+ return `https://api.bilibili.com/x/space/upstat?mid=${data$1.host_mid}`;
1446
+ }
1447
+ 专栏正文内容(data$1) {
1448
+ return `https://api.bilibili.com/x/article/view?id=${data$1.id}`;
1449
+ }
1450
+ 专栏显示卡片信息(data$1) {
1451
+ return `https://api.bilibili.com/x/article/cards?ids=${Array.isArray(data$1.ids) ? data$1.ids.join(",") : data$1.ids}`;
1452
+ }
1453
+ 专栏文章基本信息(data$1) {
1454
+ return `https://api.bilibili.com/x/article/viewinfo?id=${data$1.id}`;
1455
+ }
1456
+ 文集基本信息(data$1) {
1457
+ return `https://api.bilibili.com/x/article/list/web/articles?id=${data$1.id}`;
1541
1458
  }
1542
1459
  };
1460
+ /** 该类下的所有方法只会返回拼接好参数后的 Url 地址,需要手动请求该地址以获取数据 */
1461
+ const bilibiliApiUrls = new BiLiBiLiAPI();
1462
+
1463
+ //#endregion
1464
+ //#region src/platform/bilibili/BilibiliApi.ts
1543
1465
  /**
1544
- * 创建小红书API URLs实例
1545
- * @returns 小红书API URLs对象
1466
+ * 创建B站API方法的通用工厂函数
1467
+ * @template T - B站方法类型键名
1468
+ * @param methodType - 方法类型
1469
+ * @returns 返回配置好的API方法
1546
1470
  */
1547
- const createXiaohongshuApiUrls = () => {
1548
- return xiaohongshuApiUrls;
1471
+ const createBilibiliApiMethod = (methodType) => {
1472
+ return async (options, cookie) => {
1473
+ return await getBilibiliData(methodType, options, cookie);
1474
+ };
1549
1475
  };
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
1476
  /**
1556
- * 小红书首页推荐数据参数验证模式
1477
+ * 创建绑定cookie的B站API方法工厂函数
1478
+ * @template T - B站方法类型键名
1479
+ * @param methodType - 方法类型
1480
+ * @param cookie - 绑定的cookie
1481
+ * @returns 返回绑定了cookie的API方法
1557
1482
  */
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
- });
1483
+ const createBoundBilibiliApiMethod = (methodType, cookie) => {
1484
+ return async (options) => {
1485
+ return await getBilibiliData(methodType, options, cookie);
1486
+ };
1487
+ };
1567
1488
  /**
1568
- * 小红书单个笔记数据参数验证模式
1489
+ * B站相关 API 的命名空间。
1490
+ *
1491
+ * 部分接口可能不需要 Cookie 但建议传递有效的用户 Cookie,以获取更多数据。
1492
+ *
1493
+ * 提供了一系列方法,用于与B站相关的 API 进行交互。
1494
+ *
1495
+ * 每个方法都接受参数和 Cookie,返回 Promise,解析为统一格式的API响应。
1569
1496
  */
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
- });
1497
+ const bilibili = {
1498
+ getVideoInfo: createBilibiliApiMethod("单个视频作品数据"),
1499
+ getVideoStream: createBilibiliApiMethod("单个视频下载信息数据"),
1500
+ getComments: createBilibiliApiMethod("评论数据"),
1501
+ getCommentReply: createBilibiliApiMethod("指定评论的回复"),
1502
+ getUserProfile: createBilibiliApiMethod("用户主页数据"),
1503
+ getUserDynamic: createBilibiliApiMethod("用户主页动态列表数据"),
1504
+ getEmojiList: createBilibiliApiMethod("Emoji数据"),
1505
+ getBangumiInfo: createBilibiliApiMethod("番剧基本信息数据"),
1506
+ getBangumiStream: createBilibiliApiMethod("番剧下载信息数据"),
1507
+ getDynamicInfo: createBilibiliApiMethod("动态详情数据"),
1508
+ getDynamicCard: createBilibiliApiMethod("动态卡片数据"),
1509
+ getLiveRoomDetail: createBilibiliApiMethod("直播间信息"),
1510
+ getLiveRoomInitInfo: createBilibiliApiMethod("直播间初始化信息"),
1511
+ getLoginBasicInfo: createBilibiliApiMethod("登录基本信息"),
1512
+ getLoginQrcode: createBilibiliApiMethod("申请二维码"),
1513
+ checkQrcodeStatus: createBilibiliApiMethod("二维码状态"),
1514
+ getUserTotalPlayCount: createBilibiliApiMethod("获取UP主总播放量"),
1515
+ convertAvToBv: createBilibiliApiMethod("AV转BV"),
1516
+ convertBvToAv: createBilibiliApiMethod("BV转AV"),
1517
+ getArticleContent: createBilibiliApiMethod("专栏正文内容"),
1518
+ getArticleCard: createBilibiliApiMethod("专栏显示卡片信息"),
1519
+ getArticleInfo: createBilibiliApiMethod("专栏文章基本信息"),
1520
+ getColumnInfo: createBilibiliApiMethod("文集基本信息")
1521
+ };
1575
1522
  /**
1576
- * 小红书评论数据参数验证模式
1523
+ * 创建绑定了cookie的B站API对象
1524
+ * @param cookie - 要绑定的cookie(可选)
1525
+ * @returns 绑定了cookie的B站API对象,调用时不需要再传递cookie
1577
1526
  */
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
- });
1527
+ const createBoundBilibiliApi = (cookie, requestConfig) => {
1528
+ return {
1529
+ getVideoInfo: createBoundBilibiliApiMethod("单个视频作品数据", cookie),
1530
+ getVideoStream: createBoundBilibiliApiMethod("单个视频下载信息数据", cookie),
1531
+ getComments: createBoundBilibiliApiMethod("评论数据", cookie),
1532
+ getCommentReply: createBoundBilibiliApiMethod("指定评论的回复", cookie),
1533
+ getUserProfile: createBoundBilibiliApiMethod("用户主页数据", cookie),
1534
+ getUserDynamic: createBoundBilibiliApiMethod("用户主页动态列表数据", cookie),
1535
+ getEmojiList: createBoundBilibiliApiMethod("Emoji数据", cookie),
1536
+ getBangumiInfo: createBoundBilibiliApiMethod("番剧基本信息数据", cookie),
1537
+ getBangumiStream: createBoundBilibiliApiMethod("番剧下载信息数据", cookie),
1538
+ getDynamicInfo: createBoundBilibiliApiMethod("动态详情数据", cookie),
1539
+ getDynamicCard: createBoundBilibiliApiMethod("动态卡片数据", cookie),
1540
+ getLiveRoomDetail: createBoundBilibiliApiMethod("直播间信息", cookie),
1541
+ getLiveRoomInitInfo: createBoundBilibiliApiMethod("直播间初始化信息", cookie),
1542
+ getLoginBasicInfo: createBoundBilibiliApiMethod("登录基本信息", cookie),
1543
+ getLoginQrcode: createBoundBilibiliApiMethod("申请二维码", cookie),
1544
+ checkQrcodeStatus: createBoundBilibiliApiMethod("二维码状态", cookie),
1545
+ getUserTotalPlayCount: createBoundBilibiliApiMethod("获取UP主总播放量", cookie),
1546
+ convertAvToBv: createBoundBilibiliApiMethod("AV转BV", cookie),
1547
+ convertBvToAv: createBoundBilibiliApiMethod("BV转AV", cookie),
1548
+ getArticleContent: createBoundBilibiliApiMethod("专栏正文内容", cookie),
1549
+ getArticleCard: createBoundBilibiliApiMethod("专栏显示卡片信息", cookie),
1550
+ getArticleInfo: createBoundBilibiliApiMethod("专栏文章基本信息", cookie),
1551
+ getColumnInfo: createBoundBilibiliApiMethod("文集基本信息", cookie)
1552
+ };
1553
+ };
1554
+
1555
+ //#endregion
1556
+ //#region src/platform/bilibili/sign/bv2av.ts
1557
+ const XOR_CODE = 23442827791579n;
1558
+ const MASK_CODE = 2251799813685247n;
1559
+ const MAX_AID = 1n << 51n;
1560
+ const BASE = 58n;
1561
+ const data = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf";
1584
1562
  /**
1585
- * 小红书用户数据参数验证模式
1563
+ * av号转bv号
1564
+ * @param aid av号
1565
+ * @returns
1586
1566
  */
1587
- const UserParamsSchema = zod.object({
1588
- methodType: zod.literal("用户数据", { error: "方法类型必须是\"用户数据\"" }),
1589
- user_id: zod.string({ error: "user_id必须是字符串" })
1590
- });
1567
+ const av2bv = (aid) => {
1568
+ const bytes = [
1569
+ "B",
1570
+ "V",
1571
+ "1",
1572
+ "0",
1573
+ "0",
1574
+ "0",
1575
+ "0",
1576
+ "0",
1577
+ "0",
1578
+ "0",
1579
+ "0",
1580
+ "0"
1581
+ ];
1582
+ let bvIndex = bytes.length - 1;
1583
+ let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
1584
+ while (tmp > 0) {
1585
+ bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
1586
+ tmp = tmp / BASE;
1587
+ bvIndex -= 1;
1588
+ }
1589
+ [bytes[3], bytes[9]] = [bytes[9], bytes[3]];
1590
+ [bytes[4], bytes[7]] = [bytes[7], bytes[4]];
1591
+ return bytes.join("");
1592
+ };
1593
+ /**
1594
+ * bv号转av号
1595
+ * @param bvid bv号
1596
+ * @returns
1597
+ */
1598
+ const bv2av = (bvid) => {
1599
+ const bvidArr = Array.from(bvid);
1600
+ [bvidArr[3], bvidArr[9]] = [bvidArr[9], bvidArr[3]];
1601
+ [bvidArr[4], bvidArr[7]] = [bvidArr[7], bvidArr[4]];
1602
+ bvidArr.splice(0, 3);
1603
+ const tmp = bvidArr.reduce((pre, bvidChar) => pre * BASE + BigInt(data.indexOf(bvidChar)), 0n);
1604
+ return Number(tmp & MASK_CODE ^ XOR_CODE);
1605
+ };
1606
+
1607
+ //#endregion
1608
+ //#region src/platform/bilibili/sign/wbi.ts
1609
+ /**
1610
+ * 混合密钥编码表,用于对 imgKey 和 subKey 进行字符顺序打乱编码
1611
+ */
1612
+ const mixinKeyEncTab = [
1613
+ 46,
1614
+ 47,
1615
+ 18,
1616
+ 2,
1617
+ 53,
1618
+ 8,
1619
+ 23,
1620
+ 32,
1621
+ 15,
1622
+ 50,
1623
+ 10,
1624
+ 31,
1625
+ 58,
1626
+ 3,
1627
+ 45,
1628
+ 35,
1629
+ 27,
1630
+ 43,
1631
+ 5,
1632
+ 49,
1633
+ 33,
1634
+ 9,
1635
+ 42,
1636
+ 19,
1637
+ 29,
1638
+ 28,
1639
+ 14,
1640
+ 39,
1641
+ 12,
1642
+ 38,
1643
+ 41,
1644
+ 13,
1645
+ 37,
1646
+ 48,
1647
+ 7,
1648
+ 16,
1649
+ 24,
1650
+ 55,
1651
+ 40,
1652
+ 61,
1653
+ 26,
1654
+ 17,
1655
+ 0,
1656
+ 1,
1657
+ 60,
1658
+ 51,
1659
+ 30,
1660
+ 4,
1661
+ 22,
1662
+ 25,
1663
+ 54,
1664
+ 21,
1665
+ 56,
1666
+ 59,
1667
+ 6,
1668
+ 63,
1669
+ 57,
1670
+ 62,
1671
+ 11,
1672
+ 36,
1673
+ 20,
1674
+ 34,
1675
+ 44,
1676
+ 52
1677
+ ];
1591
1678
  /**
1592
- * 小红书用户笔记数据参数验证模式
1679
+ * 对 imgKey 和 subKey 进行字符顺序打乱编码
1680
+ * @param orig - 原始字符串数组,通常是 img_key + sub_key 的字符数组
1681
+ * @returns 返回经过编码表打乱后的32位字符串
1593
1682
  */
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: "方法类型必须是\"表情列表\"" }) });
1683
+ const getMixinKey = (orig) => mixinKeyEncTab.map((n) => orig[n]).join("").slice(0, 32);
1601
1684
  /**
1602
- * 小红书搜索笔记参数验证模式
1685
+ * 为请求参数进行 WBI 签名
1686
+ * @param params - 请求参数对象,键值对形式
1687
+ * @param img_key - 图片密钥
1688
+ * @param sub_key - 子密钥
1689
+ * @returns 返回包含时间戳和签名的查询字符串
1603
1690
  */
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
- });
1691
+ const encWbi = (params, img_key, sub_key) => {
1692
+ const mixin_key = getMixinKey(img_key + sub_key);
1693
+ const curr_time = Math.round(Date.now() / 1e3);
1694
+ const chr_filter = /[!'()*]/g;
1695
+ Object.assign(params, { wts: curr_time });
1696
+ const query = Object.keys(params).sort().map((key) => {
1697
+ const value = params[key].toString().replace(chr_filter, "");
1698
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
1699
+ }).join("&");
1700
+ return `&wts=${curr_time}&w_rid=${crypto.createHash("md5").update(query + mixin_key).digest("hex")}`;
1701
+ };
1612
1702
  /**
1613
- * 小红书验证模式映射
1703
+ * 获取最新的 img_key 和 sub_key
1704
+ * @param cookie - 有效的用户 Cookie 字符串
1705
+ * @returns 返回包含 img_key 和 sub_key 的对象
1706
+ * @throws 当网络请求失败或响应格式不正确时抛出错误
1614
1707
  */
1615
- const XiaohongshuValidationSchemas = {
1616
- 首页推荐数据: HomeFeedParamsSchema,
1617
- 单个笔记数据: NoteParamsSchema,
1618
- 评论数据: CommentParamsSchema,
1619
- 用户数据: UserParamsSchema,
1620
- 用户笔记数据: UserNoteParamsSchema,
1621
- 表情列表: EmojiListParamsSchema,
1622
- 搜索笔记: SearchNoteParamsSchema
1708
+ const getWbiKeys = async (cookie) => {
1709
+ const { data: { wbi_img: { img_url, sub_url } } } = (await axios("https://api.bilibili.com/x/web-interface/nav", { headers: { Cookie: cookie } })).data;
1710
+ return {
1711
+ img_key: img_url.slice(img_url.lastIndexOf("/") + 1, img_url.lastIndexOf(".")),
1712
+ sub_key: sub_url.slice(sub_url.lastIndexOf("/") + 1, sub_url.lastIndexOf("."))
1713
+ };
1623
1714
  };
1624
1715
  /**
1625
- * 小红书方法类型
1716
+ * 对请求链接进行 WBI 签名
1717
+ * @param BASEURL - 完整的请求地址,可以是字符串或 URL 对象
1718
+ * @param cookie - 有效的用户 Cookie 字符串
1719
+ * @returns 返回包含 WBI 签名的查询字符串
1720
+ * @throws 当获取 WBI 密钥失败或 URL 解析失败时抛出错误
1626
1721
  */
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"
1722
+ const wbi_sign = async (BASEURL, cookie) => {
1723
+ const web_keys = await getWbiKeys(cookie);
1724
+ const url$1 = new URL(BASEURL);
1725
+ const params = {};
1726
+ for (const [key, value] of url$1.searchParams.entries()) params[key] = value;
1727
+ return encWbi(params, web_keys.img_key, web_keys.sub_key);
1635
1728
  };
1636
1729
 
1637
1730
  //#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
- };
1731
+ //#region src/utils/errors.ts
1669
1732
  /**
1670
- * 验证快手参数
1671
- * @param methodType - 快手方法类型
1672
- * @param params - 待验证的参数
1673
- * @returns 验证后的参数,符合原始API期望的类型
1733
+ * API错误类
1674
1734
  */
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
- });
1735
+ var ApiError = class extends Error {
1736
+ code;
1737
+ platform;
1738
+ /**
1739
+ * 构造API错误
1740
+ * @param message - 错误消息
1741
+ * @param code - 错误代码
1742
+ * @param platform - 平台名称
1743
+ */
1744
+ constructor(message, code = 500, platform = "unknown") {
1745
+ super(message);
1746
+ this.name = "ApiError";
1747
+ this.code = code;
1748
+ this.platform = platform;
1749
+ }
1683
1750
  };
1684
1751
  /**
1685
- * 验证小红书参数
1686
- * @param methodType - 小红书方法类型
1687
- * @param params - 待验证的参数
1688
- * @returns 验证后的参数
1752
+ * 参数验证错误类
1689
1753
  */
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
- });
1754
+ var ValidationError = class ValidationError extends Error {
1755
+ errors;
1756
+ requestPath;
1757
+ /**
1758
+ * 构造参数验证错误
1759
+ * @param message - 错误消息
1760
+ * @param errors - 详细错误信息
1761
+ * @param requestPath - HTTP请求路径
1762
+ */
1763
+ constructor(message, errors, requestPath) {
1764
+ super(message);
1765
+ this.name = "ValidationError";
1766
+ this.errors = errors;
1767
+ this.requestPath = requestPath;
1768
+ }
1769
+ /**
1770
+ * 从Zod错误创建验证错误
1771
+ * @param zodError - Zod验证错误
1772
+ * @param requestPath - HTTP请求路径
1773
+ * @returns 验证错误实例
1774
+ */
1775
+ static fromZodError(zodError, requestPath) {
1776
+ return new ValidationError("参数验证失败", zodError.issues.map((err) => ({
1777
+ field: err.path.join("."),
1778
+ message: err.message
1779
+ })), requestPath);
1780
+ }
1698
1781
  };
1699
1782
  /**
1700
- * 创建成功响应格式
1701
- * @param data - 响应数据
1702
- * @param message - 响应消息(可选)
1703
- * @param code - 响应状态码(可选,默认200)
1704
- * @returns 格式化的成功API响应对象
1783
+ * 处理错误并返回统一格式
1784
+ * @param error - 错误对象
1785
+ * @param requestPath - HTTP请求路径(可选)
1786
+ * @returns 统一的错误响应格式
1705
1787
  */
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
1788
+ const handleError = (error, requestPath) => {
1789
+ if (error instanceof ValidationError) return {
1790
+ code: 400,
1791
+ message: error.message,
1792
+ data: null,
1793
+ errors: error.errors,
1794
+ requestPath: error.requestPath ?? requestPath
1713
1795
  };
1714
- };
1715
- /**
1716
- * 创建失败响应格式
1717
- * @param error - 错误信息
1718
- * @param message - 详细错误消息(可选)
1719
- * @param code - 错误状态码(可选,默认500)
1720
- * @returns 格式化的错误响应对象
1721
- */
1722
- const createErrorResponse = (error, message, code = 500) => {
1796
+ if (error instanceof ApiError) return {
1797
+ code: error.code,
1798
+ message: error.message,
1799
+ data: null,
1800
+ platform: error.platform,
1801
+ requestPath
1802
+ };
1803
+ if (error instanceof zod.ZodError) return handleError(ValidationError.fromZodError(error, requestPath), requestPath);
1723
1804
  return {
1724
- success: false,
1725
- error,
1726
- message,
1727
- code,
1728
- data: void 0
1805
+ code: 500,
1806
+ message: error instanceof Error ? error.message : "未知错误",
1807
+ data: null,
1808
+ requestPath
1729
1809
  };
1730
1810
  };
1731
1811
 
@@ -2078,6 +2158,18 @@ const fetchBilibili = async (data$1, cookie, requestConfig) => {
2078
2158
  ...baseRequestConfig,
2079
2159
  url: bilibiliApiUrls.二维码状态({ qrcode_key: data$1.qrcode_key })
2080
2160
  });
2161
+ if (isNetworkErrorResult(result)) {
2162
+ const networkError = new Error(result.error.amagiError.errorDescription);
2163
+ Object.assign(networkError, {
2164
+ code: result.error.code,
2165
+ data: null,
2166
+ amagiError: {
2167
+ ...result.error.amagiError,
2168
+ requestType: data$1.methodType
2169
+ }
2170
+ });
2171
+ throw networkError;
2172
+ }
2081
2173
  if (result.data.code !== 0) {
2082
2174
  const Err = {
2083
2175
  errorDescription: `获取响应数据失败!原因:${bilibiliErrorCodeMap[String(result.data.code)] || result.data.message || "未知错误"}!`,
@@ -2159,6 +2251,18 @@ const GlobalGetData$3 = async (type, options, retryCount = 0) => {
2159
2251
  let warningMessage = "";
2160
2252
  try {
2161
2253
  const result = await fetchData(options);
2254
+ if (isNetworkErrorResult(result)) {
2255
+ const networkError = new Error(result.error.amagiError.errorDescription);
2256
+ Object.assign(networkError, {
2257
+ code: result.error.code,
2258
+ data: null,
2259
+ amagiError: {
2260
+ ...result.error.amagiError,
2261
+ requestType: type
2262
+ }
2263
+ });
2264
+ throw networkError;
2265
+ }
2162
2266
  if (!result || result === "") {
2163
2267
  const Err = {
2164
2268
  errorDescription: "获取响应数据失败!接口返回内容为空,你的B站ck可能已经失效!",
@@ -3892,6 +3996,18 @@ const GlobalGetData$2 = async (type, config) => {
3892
3996
  let warningMessage = "";
3893
3997
  try {
3894
3998
  const result = await fetchData(config);
3999
+ if (isNetworkErrorResult(result)) {
4000
+ const networkError = new Error(result.error.amagiError.errorDescription);
4001
+ Object.assign(networkError, {
4002
+ code: result.error.code,
4003
+ data: null,
4004
+ amagiError: {
4005
+ ...result.error.amagiError,
4006
+ requestType: type
4007
+ }
4008
+ });
4009
+ throw networkError;
4010
+ }
3895
4011
  if (!result || result === "") {
3896
4012
  const Err = {
3897
4013
  errorDescription: "获取响应数据失败!接口返回内容为空,你的抖音ck可能已经失效!",
@@ -4095,6 +4211,18 @@ const GlobalGetData$1 = async (type, options) => {
4095
4211
  let warningMessage = "";
4096
4212
  try {
4097
4213
  const result = await fetchData(options);
4214
+ if (isNetworkErrorResult(result)) {
4215
+ const networkError = new Error(result.error.amagiError.errorDescription);
4216
+ Object.assign(networkError, {
4217
+ code: result.error.code,
4218
+ data: null,
4219
+ amagiError: {
4220
+ ...result.error.amagiError,
4221
+ requestType: type
4222
+ }
4223
+ });
4224
+ throw networkError;
4225
+ }
4098
4226
  if (result === "" || !result || result.result === 2) {
4099
4227
  const Err = {
4100
4228
  errorDescription: "获取响应数据失败!接口返回内容为空!",
@@ -4279,6 +4407,18 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4279
4407
  const GlobalGetData = async (methodType, config) => {
4280
4408
  try {
4281
4409
  const response = await fetchData(config);
4410
+ if (isNetworkErrorResult(response)) {
4411
+ const networkError = new Error(response.error.amagiError.errorDescription);
4412
+ Object.assign(networkError, {
4413
+ code: response.error.code,
4414
+ data: null,
4415
+ amagiError: {
4416
+ ...response.error.amagiError,
4417
+ requestType: methodType
4418
+ }
4419
+ });
4420
+ throw networkError;
4421
+ }
4282
4422
  if (typeof response === "string" && response.includes("<html>")) return response;
4283
4423
  if (response.code !== 0) throw new Error(`API请求失败: ${response.data?.msg ?? response.msg ?? "未知错误"}, code: ${response.code}`);
4284
4424
  return response;
@@ -4960,7 +5100,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType$1) {
4960
5100
 
4961
5101
  //#endregion
4962
5102
  //#region src/index.ts
4963
- const VERSION = "5.10.0";
5103
+ const VERSION = "5.11.0";
4964
5104
  /**
4965
5105
  * @deprecated 请使用 createAmagiClient 替代
4966
5106
  */
@@ -4996,4 +5136,4 @@ const Client = CreateApp;
4996
5136
  const amagi = Client;
4997
5137
 
4998
5138
  //#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 };
5139
+ 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 };