@ikenxuan/amagi 5.12.0 → 5.12.3

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.
@@ -2,14 +2,27 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import url from "node:url";
4
4
  import { Chalk } from "chalk";
5
- import log4js from "log4js";
6
5
  import axios, { AxiosError } from "axios";
7
6
  import zod from "zod";
8
- import crypto from "node:crypto";
9
7
  import { Xhshow } from "@ikenxuan/xhshow-ts";
8
+ import crypto from "node:crypto";
10
9
  import express from "express";
11
10
 
12
11
  //#region src/model/logger.ts
12
+ /**
13
+ * 动态获取 log4js 库
14
+ * 优先使用 node-karin/log4js (Karin 环境)
15
+ * 失败则回退到 log4js (独立环境,通过别名映射到 @karinjs/log4js)
16
+ */
17
+ const getLog4js = async () => {
18
+ try {
19
+ return (await import("node-karin/log4js")).default;
20
+ } catch {
21
+ const lib = await import("log4js");
22
+ return lib.default || lib;
23
+ }
24
+ };
25
+ const log4jsPromise = getLog4js();
13
26
  /** 获取包的绝对路径 */
14
27
  const getPackageLogsPath = () => {
15
28
  const currentFileUrl = import.meta.url;
@@ -19,7 +32,11 @@ const getPackageLogsPath = () => {
19
32
  if (fs.existsSync(path.join(packageRoot, "package.json"))) break;
20
33
  packageRoot = path.dirname(packageRoot);
21
34
  }
22
- return path.join(packageRoot, "logs");
35
+ const logsDir = path.join(packageRoot, "logs");
36
+ try {
37
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
38
+ } catch (error) {}
39
+ return logsDir;
23
40
  };
24
41
  const logsPath = getPackageLogsPath();
25
42
  /** 获取日志级别,优先使用环境变量,默认为 info */
@@ -27,59 +44,65 @@ const getLogLevel = () => {
27
44
  return process.env.LOG_LEVEL ?? "info";
28
45
  };
29
46
  const currentLogLevel = getLogLevel();
30
- log4js.configure({
31
- appenders: {
32
- console: {
33
- type: "stdout",
34
- layout: {
35
- type: "pattern",
36
- pattern: "%[[amagi][%d{hh:mm:ss.SSS}][%4.4p]%] %m"
37
- }
38
- },
39
- command: {
40
- type: "dateFile",
41
- filename: path.join(logsPath, "application", "command"),
42
- pattern: "yyyy-MM-dd.log",
43
- numBackups: 15,
44
- alwaysIncludePattern: true,
45
- layout: {
46
- type: "pattern",
47
- pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
48
- }
49
- },
50
- httpConsole: {
51
- type: "stdout",
52
- layout: {
53
- type: "pattern",
54
- pattern: "%[[amagi][%d{hh:mm:ss.SSS}][HTTP]%] %m"
55
- }
56
- },
57
- httpRequest: {
58
- type: "dateFile",
59
- filename: path.join(logsPath, "http", "requests"),
60
- pattern: "yyyy-MM-dd.log",
61
- numBackups: 30,
62
- alwaysIncludePattern: true,
63
- layout: {
64
- type: "pattern",
65
- pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
66
- }
67
- }
68
- },
69
- categories: {
70
- default: {
71
- appenders: ["console", "command"],
72
- level: currentLogLevel
73
- },
74
- http: {
75
- appenders: ["httpConsole", "httpRequest"],
76
- level: "debug"
77
- }
78
- },
79
- pm2: true
80
- });
47
+ /** 初始化 logger 配置 */
48
+ const initLogger = () => {
49
+ log4jsPromise.then((log4js) => {
50
+ log4js.configure({
51
+ appenders: {
52
+ console: {
53
+ type: "stdout",
54
+ layout: {
55
+ type: "pattern",
56
+ pattern: "%[[amagi][%d{hh:mm:ss.SSS}][%4.4p]%] %m"
57
+ }
58
+ },
59
+ command: {
60
+ type: "dateFile",
61
+ filename: path.join(logsPath, "application", "command"),
62
+ pattern: "yyyy-MM-dd.log",
63
+ numBackups: 15,
64
+ alwaysIncludePattern: true,
65
+ layout: {
66
+ type: "pattern",
67
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
68
+ }
69
+ },
70
+ httpConsole: {
71
+ type: "stdout",
72
+ layout: {
73
+ type: "pattern",
74
+ pattern: "%[[amagi][%d{hh:mm:ss.SSS}][HTTP]%] %m"
75
+ }
76
+ },
77
+ httpRequest: {
78
+ type: "dateFile",
79
+ filename: path.join(logsPath, "http", "requests"),
80
+ pattern: "yyyy-MM-dd.log",
81
+ numBackups: 30,
82
+ alwaysIncludePattern: true,
83
+ layout: {
84
+ type: "pattern",
85
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
86
+ }
87
+ }
88
+ },
89
+ categories: {
90
+ default: {
91
+ appenders: ["console", "command"],
92
+ level: currentLogLevel
93
+ },
94
+ http: {
95
+ appenders: ["httpConsole", "httpRequest"],
96
+ level: "debug"
97
+ }
98
+ },
99
+ pm2: true
100
+ });
101
+ });
102
+ };
81
103
  var CustomLogger = class {
82
104
  logger;
105
+ queue = [];
83
106
  chalk;
84
107
  red;
85
108
  green;
@@ -90,7 +113,10 @@ var CustomLogger = class {
90
113
  white;
91
114
  gray;
92
115
  constructor(name) {
93
- this.logger = log4js.getLogger(name);
116
+ log4jsPromise.then((log4js) => {
117
+ this.logger = log4js.getLogger(name);
118
+ this.flush();
119
+ });
94
120
  this.chalk = new Chalk();
95
121
  this.red = this.chalk.red;
96
122
  this.green = this.chalk.green;
@@ -101,20 +127,31 @@ var CustomLogger = class {
101
127
  this.white = this.chalk.white;
102
128
  this.gray = this.chalk.gray;
103
129
  }
130
+ flush() {
131
+ if (!this.logger) return;
132
+ while (this.queue.length) {
133
+ const [method, args] = this.queue.shift();
134
+ this.logger[method](...args);
135
+ }
136
+ }
137
+ proxy(method, args) {
138
+ if (this.logger) this.logger[method](...args);
139
+ else this.queue.push([method, args]);
140
+ }
104
141
  info(message, ...args) {
105
- this.logger.info(message, ...args);
142
+ this.proxy("info", [message, ...args]);
106
143
  }
107
144
  warn(message, ...args) {
108
- this.logger.warn(message, ...args);
145
+ this.proxy("warn", [message, ...args]);
109
146
  }
110
147
  error(message, ...args) {
111
- this.logger.error(message, ...args);
148
+ this.proxy("error", [message, ...args]);
112
149
  }
113
150
  mark(message, ...args) {
114
- this.logger.mark(message, ...args);
151
+ this.proxy("mark", [message, ...args]);
115
152
  }
116
153
  debug(message, ...args) {
117
- this.logger.debug(message, ...args);
154
+ this.proxy("debug", [message, ...args]);
118
155
  }
119
156
  };
120
157
  const logger = new CustomLogger("default");
@@ -713,51 +750,26 @@ var xiaohongshuSign = class {
713
750
  return this.client.signXsPost(path$1, a1Cookie, clientType, body);
714
751
  }
715
752
  /**
716
- * 生成X-S签名(兼容旧接口)
717
- * @param url - 请求URL
718
- * @param body - 请求体
719
- * @param userAgent - User-Agent(暂未使用)
720
- * @param method - 请求方法,默认为 'POST'
721
- * @param a1Cookie - a1 cookie值
722
- * @returns X-S签名
723
- */
724
- static generateXS(url$1, body, userAgent, method = "POST", a1Cookie = "") {
725
- try {
726
- const urlObj = new URL(url$1);
727
- const path$1 = urlObj.pathname + urlObj.search;
728
- if (method.toUpperCase() === "GET") {
729
- const params = typeof body === "object" ? body : {};
730
- return this.generateXSGet(path$1, a1Cookie, "xhs-pc-web", params);
731
- } else {
732
- const requestBody = typeof body === "object" ? body : {};
733
- return this.generateXSPost(path$1, a1Cookie, "xhs-pc-web", requestBody);
734
- }
735
- } catch (error) {
736
- console.error("生成X-S签名失败:", error);
737
- throw new Error(`签名生成失败: ${error}`);
738
- }
739
- }
740
- /**
741
753
  * 生成X-S-Common参数
742
- * @param length - 长度
754
+ * @param cookies - cookie字符串
743
755
  * @returns Base64编码的随机字符串
744
756
  */
745
- static generateXSCommon(length = 945) {
746
- return crypto.randomBytes(length).toString("base64").replace(/=+$/, "");
757
+ static generateXSCommon(cookies) {
758
+ return this.client.signXsCommon(cookies);
747
759
  }
748
760
  /**
749
761
  * 生成X-T时间戳
750
762
  * @returns 当前时间戳字符串
751
763
  */
752
764
  static generateXT() {
753
- return Date.now().toString();
765
+ return this.client.getXT();
754
766
  }
755
767
  /**
756
768
  * 生成X-B3-Traceid
757
769
  * @returns 16位随机字符串
758
770
  */
759
771
  static generateXB3Traceid() {
760
- return Array.from({ length: 16 }, () => "abcdef0123456789"[Math.floor(Math.random() * 16)]).join("");
772
+ return this.client.getB3TraceId();
761
773
  }
762
774
  /**
763
775
  * 从cookie字符串中提取a1值
@@ -1243,7 +1255,8 @@ const fetchResponse = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
1243
1255
  * 判断结果是否为网络错误响应
1244
1256
  * @param result - 请求结果
1245
1257
  * @returns 是否为ErrorResult
1246
- * @description 通过检查 error 字段中的 amagiError 来区分网络错误和业务错误
1258
+ *
1259
+ * 通过检查 error 字段中的 amagiError 来区分网络错误和业务错误
1247
1260
  */
1248
1261
  const isNetworkErrorResult = (result) => {
1249
1262
  if (result === null || typeof result !== "object") return false;
@@ -3649,11 +3662,53 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3649
3662
  }
3650
3663
  case "评论数据": {
3651
3664
  const urlGenerator = (params) => douyinApiUrls$1.评论(params);
3652
- return await fetchPaginatedData(data$1.methodType, urlGenerator, data$1, 50, baseRequestConfig, signType);
3665
+ return await fetchPaginatedData({
3666
+ type: data$1.methodType,
3667
+ apiUrlGenerator: urlGenerator,
3668
+ params: {
3669
+ ...data$1,
3670
+ cursor: data$1.cursor ?? 0
3671
+ },
3672
+ maxPageSize: 50,
3673
+ requestConfig: baseRequestConfig,
3674
+ signType,
3675
+ extractList: (resp) => resp.comments ?? [],
3676
+ updateParams: (params, resp) => ({
3677
+ ...params,
3678
+ cursor: resp.cursor
3679
+ }),
3680
+ hasMore: (resp) => resp.has_more === 1,
3681
+ formatFinalResponse: (resp, list) => ({
3682
+ ...resp,
3683
+ comments: list,
3684
+ cursor: resp.cursor ?? list.length
3685
+ })
3686
+ });
3653
3687
  }
3654
3688
  case "指定评论回复数据": {
3655
3689
  const urlGenerator = (params) => douyinApiUrls$1.二级评论(params);
3656
- return await fetchPaginatedData(data$1.methodType, urlGenerator, data$1, 3, baseRequestConfig, "x_bogus");
3690
+ return await fetchPaginatedData({
3691
+ type: data$1.methodType,
3692
+ apiUrlGenerator: urlGenerator,
3693
+ params: {
3694
+ ...data$1,
3695
+ cursor: data$1.cursor ?? 0
3696
+ },
3697
+ maxPageSize: 3,
3698
+ requestConfig: baseRequestConfig,
3699
+ signType: "x_bogus",
3700
+ extractList: (resp) => resp.comments ?? [],
3701
+ updateParams: (params, resp) => ({
3702
+ ...params,
3703
+ cursor: resp.cursor
3704
+ }),
3705
+ hasMore: (resp) => resp.has_more === 1,
3706
+ formatFinalResponse: (resp, list) => ({
3707
+ ...resp,
3708
+ comments: list,
3709
+ cursor: resp.cursor ?? list.length
3710
+ })
3711
+ });
3657
3712
  }
3658
3713
  case "用户主页数据": {
3659
3714
  const url$1 = douyinApiUrls$1.用户主页信息({ sec_uid: data$1.sec_uid });
@@ -3705,10 +3760,6 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3705
3760
  });
3706
3761
  }
3707
3762
  case "搜索数据": {
3708
- let search_id = "";
3709
- const maxPageSize = 15;
3710
- let fetchedSearchList = [];
3711
- let tmpresp = null;
3712
3763
  const searchType = data$1.type ?? "综合";
3713
3764
  const refererUrl = searchType === "用户" ? `https://www.douyin.com/search/${encodeURIComponent(String(data$1.query))}?type=user` : searchType === "视频" ? `https://www.douyin.com/search/${encodeURIComponent(String(data$1.query))}?type=video` : `https://www.douyin.com/root/search/${encodeURIComponent(String(data$1.query))}`;
3714
3765
  const customConfig = {
@@ -3720,146 +3771,82 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3720
3771
  };
3721
3772
  const isUserSearch = searchType === "用户";
3722
3773
  const isVideoSearch = searchType === "视频";
3723
- let isFirstRequest = true;
3724
- while (fetchedSearchList.length < Number(data$1.number ?? 10)) {
3725
- const requestCount = Math.min(Number(data$1.number ?? 50) - fetchedSearchList.length, maxPageSize);
3726
- const url$1 = douyinApiUrls$1.搜索({
3774
+ return await fetchPaginatedData({
3775
+ type: data$1.methodType,
3776
+ apiUrlGenerator: (params) => douyinApiUrls$1.搜索(params),
3777
+ params: {
3727
3778
  query: data$1.query,
3728
3779
  type: data$1.type,
3729
- number: requestCount,
3730
- search_id: search_id === "" ? void 0 : search_id
3731
- });
3732
- const raw = await GlobalGetData$2(data$1.methodType, {
3733
- ...customConfig,
3734
- url: url$1
3735
- });
3736
- if (isUserSearch) {
3737
- if (!raw || typeof raw !== "object") {
3738
- const Err = {
3739
- errorDescription: "抖音用户搜索返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!",
3740
- requestType: data$1.methodType ?? "未知请求类型",
3741
- requestUrl: url$1
3742
- };
3743
- const warningMessage = `
3744
- 获取响应数据失败!原因:${logger.yellow("用户搜索返回无有效数据,疑似触发反爬机制")}
3745
- 请求类型:「${data$1.methodType}」
3746
- 搜索关键词:「${data$1.query}」
3747
- 请求URL:${url$1}
3748
- `;
3749
- !isFirstRequest && logger.warn(warningMessage);
3750
- return {
3751
- code: douoyinAPIErrorCode.COOKIE,
3752
- data: raw,
3753
- amagiError: Err,
3754
- amagiMessage: warningMessage
3755
- };
3756
- }
3757
- const userList = raw.user_list;
3758
- if (isFirstRequest && (!userList || userList.length === 0)) {
3759
- const Err = {
3760
- errorDescription: "抖音用户搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!",
3761
- requestType: data$1.methodType ?? "未知请求类型",
3762
- requestUrl: url$1
3763
- };
3764
- const warningMessage = `
3765
- 获取响应数据失败!原因:${logger.yellow("用户搜索接口第一次请求就返回空数组,你的抖音Cookie可能已经失效!")}
3766
- 请求类型:「${data$1.methodType}」
3767
- 搜索关键词:「${data$1.query}」
3768
- 请求URL:${url$1}
3769
- `;
3770
- logger.warn(warningMessage);
3771
- return {
3772
- data: raw,
3773
- amagiError: Err,
3774
- amagiMessage: warningMessage
3775
- };
3776
- }
3777
- if (Array.isArray(userList) && userList.length > 0) fetchedSearchList.push(...userList);
3778
- tmpresp = raw;
3779
- search_id = raw.rid ?? search_id;
3780
- if (typeof raw.has_more === "number" && raw.has_more === 0) break;
3781
- if (!userList || userList.length === 0) break;
3782
- } else if (isVideoSearch) {
3783
- if (!raw || typeof raw !== "object") {
3784
- const Err = {
3785
- errorDescription: "抖音视频搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效,你的抖音Cookie可能已经失效!",
3786
- requestType: data$1.methodType ?? "未知请求类型",
3787
- requestUrl: url$1
3788
- };
3789
- const warningMessage = `
3790
- 获取响应数据失败!原因:${logger.yellow("视频搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!")}
3791
- 请求类型:「${data$1.methodType}」
3792
- 搜索关键词:「${data$1.query}」
3793
- 请求URL:${url$1}
3794
- `;
3795
- !isFirstRequest && logger.warn(warningMessage);
3796
- return {
3797
- code: douoyinAPIErrorCode.COOKIE,
3798
- data: raw,
3799
- amagiError: Err,
3800
- amagiMessage: warningMessage
3801
- };
3802
- }
3803
- const videoList = raw.data;
3804
- if (isFirstRequest && (!videoList || videoList.length === 0)) {
3805
- const Err = {
3806
- errorDescription: "抖音视频搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!",
3807
- requestType: data$1.methodType ?? "未知请求类型",
3808
- requestUrl: url$1
3809
- };
3810
- const warningMessage = `
3811
- 获取响应数据失败!原因:${logger.yellow("视频搜索接口第一次请求就返回空数组,你的抖音Cookie可能已经失效!")}
3812
- 请求类型:「${data$1.methodType}」
3813
- 搜索关键词:「${data$1.query}」
3814
- 请求URL:${url$1}
3815
- `;
3816
- logger.warn(warningMessage);
3780
+ number: data$1.number ?? 10,
3781
+ search_id: ""
3782
+ },
3783
+ maxPageSize: 15,
3784
+ requestConfig: customConfig,
3785
+ signType: null,
3786
+ processRawResponse: (raw) => {
3787
+ if (!isUserSearch && !isVideoSearch) {
3788
+ const responses = filterSearchResponses(typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw]);
3789
+ if (responses.length === 0) return raw;
3790
+ const mergedData = [];
3791
+ let lastValid = {};
3792
+ for (const resp of responses) {
3793
+ if (Array.isArray(resp.data) && resp.data.length > 0) mergedData.push(...resp.data);
3794
+ lastValid = resp;
3795
+ }
3817
3796
  return {
3818
- data: raw,
3819
- amagiError: Err,
3820
- amagiMessage: warningMessage
3797
+ ...lastValid,
3798
+ data: mergedData
3821
3799
  };
3822
3800
  }
3823
- if (Array.isArray(videoList) && videoList.length > 0) fetchedSearchList.push(...videoList);
3824
- tmpresp = raw;
3825
- search_id = raw.log_pb?.impr_id ?? search_id;
3826
- if (typeof raw.has_more === "number" && raw.has_more === 0) break;
3827
- if (!videoList || videoList.length === 0) break;
3828
- } else {
3829
- const responses = filterSearchResponses(typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw]);
3830
- if (responses.length === 0) {
3831
- const Err = {
3832
- errorDescription: "抖音综合搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!",
3833
- requestType: data$1.methodType ?? "未知请求类型",
3834
- requestUrl: url$1
3835
- };
3801
+ return raw;
3802
+ },
3803
+ extractList: (resp) => {
3804
+ if (isUserSearch) return resp.user_list ?? [];
3805
+ return resp.data ?? [];
3806
+ },
3807
+ updateParams: (params, resp) => {
3808
+ let nextSearchId = params.search_id;
3809
+ if (isUserSearch) nextSearchId = resp.rid ?? nextSearchId;
3810
+ else nextSearchId = resp.log_pb?.impr_id ?? nextSearchId;
3811
+ return {
3812
+ ...params,
3813
+ search_id: nextSearchId
3814
+ };
3815
+ },
3816
+ hasMore: (resp) => {
3817
+ return resp.has_more !== 0;
3818
+ },
3819
+ validateFirstPage: (list, raw, url$1) => {
3820
+ const typeStr = isUserSearch ? "用户" : isVideoSearch ? "视频" : "综合";
3821
+ let isInvalidResponse = false;
3822
+ const rawAny = raw;
3823
+ if (!rawAny || typeof rawAny !== "object") isInvalidResponse = true;
3824
+ else if (isUserSearch && !rawAny.user_list) isInvalidResponse = true;
3825
+ else if (isVideoSearch && !rawAny.data) isInvalidResponse = true;
3826
+ else if (!isUserSearch && !isVideoSearch && !rawAny.data) isInvalidResponse = true;
3827
+ if (isInvalidResponse) {
3828
+ const desc = `抖音${typeStr}搜索返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!`;
3836
3829
  const warningMessage = `
3837
- 获取响应数据失败!原因:${logger.yellow("综合搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!")}
3830
+ 获取响应数据失败!原因:${logger.yellow(`${typeStr}搜索返回无有效数据,疑似触发反爬机制`)}
3838
3831
  请求类型:「${data$1.methodType}」
3839
3832
  搜索关键词:「${data$1.query}」
3840
3833
  请求URL:${url$1}
3841
3834
  `;
3842
- !isFirstRequest && logger.warn(warningMessage);
3843
3835
  return {
3844
3836
  code: douoyinAPIErrorCode.COOKIE,
3845
3837
  data: raw,
3846
- amagiError: Err,
3838
+ amagiError: {
3839
+ errorDescription: desc,
3840
+ requestType: data$1.methodType ?? "未知请求类型",
3841
+ requestUrl: url$1
3842
+ },
3847
3843
  amagiMessage: warningMessage
3848
3844
  };
3849
3845
  }
3850
- for (const resp of responses) {
3851
- if (Array.isArray(resp.data) && resp.data.length > 0) fetchedSearchList.push(...resp.data);
3852
- tmpresp = resp;
3853
- search_id = resp.log_pb?.impr_id ?? search_id;
3854
- }
3855
- if (isFirstRequest && fetchedSearchList.length === 0) {
3856
- const Err = {
3857
- errorDescription: "抖音综合搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!",
3858
- requestType: data$1.methodType ?? "未知请求类型",
3859
- requestUrl: url$1
3860
- };
3846
+ if (!list || list.length === 0) {
3847
+ const desc = `抖音${typeStr}搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!`;
3861
3848
  const warningMessage = `
3862
- 获取响应数据失败!原因:${logger.yellow("综合搜索索接第一次请求就返回空数组,你的抖音Cookie可能已经失效!")}
3849
+ 获取响应数据失败!原因:${logger.yellow(`${typeStr}搜索接口第一次请求就返回空数组,你的抖音Cookie可能已经失效!`)}
3863
3850
  请求类型:「${data$1.methodType}」
3864
3851
  搜索关键词:「${data$1.query}」
3865
3852
  请求URL:${url$1}
@@ -3867,25 +3854,27 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3867
3854
  logger.warn(warningMessage);
3868
3855
  return {
3869
3856
  data: raw,
3870
- amagiError: Err,
3857
+ amagiError: {
3858
+ errorDescription: desc,
3859
+ requestType: data$1.methodType ?? "未知请求类型",
3860
+ requestUrl: url$1
3861
+ },
3871
3862
  amagiMessage: warningMessage
3872
3863
  };
3873
3864
  }
3874
- if (tmpresp && typeof tmpresp.has_more === "number" && tmpresp.has_more === 0) break;
3865
+ return null;
3866
+ },
3867
+ formatFinalResponse: (resp, list) => {
3868
+ if (isUserSearch) return {
3869
+ ...resp,
3870
+ user_list: list
3871
+ };
3872
+ return {
3873
+ ...resp,
3874
+ data: list
3875
+ };
3875
3876
  }
3876
- isFirstRequest = false;
3877
- }
3878
- const slicedList = Number(data$1.number ?? 10) === 0 ? [] : fetchedSearchList.slice(0, Number(data$1.number ?? 10));
3879
- return isUserSearch ? {
3880
- ...tmpresp ?? {},
3881
- user_list: slicedList
3882
- } : isVideoSearch ? {
3883
- ...tmpresp ?? {},
3884
- data: slicedList
3885
- } : {
3886
- ...tmpresp ?? {},
3887
- data: slicedList
3888
- };
3877
+ });
3889
3878
  }
3890
3879
  case "动态表情数据": {
3891
3880
  const url$1 = douyinApiUrls$1.互动表情();
@@ -4021,40 +4010,48 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
4021
4010
  };
4022
4011
  /**
4023
4012
  * 通用的分页请求函数
4024
- * @param type - 请求类型
4025
- * @param apiUrlGenerator - 接口URL生成器
4026
- * @param params - 请求参数
4027
- * @param maxPageSize - 单次请求的最大数据量
4028
- * @param requestConfig - axios请求配置(外部配置优先)
4029
- * @param signType - 签名算法类型
4030
- * @returns 返回分页数据
4013
+ * 封装了循环分页请求、数据合并、错误处理和反爬检测逻辑
4014
+ *
4015
+ * @template T - 列表项类型
4016
+ * @template P - 参数类型
4017
+ * @template R - 返回值类型
4018
+ * @template RawResp - 原始响应类型
4019
+ * @param config - 分页请求配置对象
4020
+ * @returns Promise<R> 返回最终构造的数据对象
4031
4021
  */
4032
- const fetchPaginatedData = async (type, apiUrlGenerator, params, maxPageSize, requestConfig, signType = "a_bogus") => {
4033
- let cursor = params.cursor ?? 0;
4034
- let fetchedData = [];
4035
- let tmpresp = {};
4022
+ const fetchPaginatedData = async (config) => {
4023
+ const { type, apiUrlGenerator, params, maxPageSize, requestConfig, signType = "a_bogus", extractList, updateParams, hasMore, formatFinalResponse, processRawResponse, validateFirstPage } = config;
4024
+ let currentParams = { ...params };
4025
+ const fetchedData = [];
4026
+ let lastResponse = {};
4027
+ let isFirstRequest = true;
4036
4028
  const userAgent = requestConfig.headers?.["User-Agent"];
4037
- while (fetchedData.length < Number(params.number ?? maxPageSize)) {
4038
- const requestCount = Math.min(Number(params.number ?? maxPageSize) - fetchedData.length, maxPageSize);
4039
- const url$1 = apiUrlGenerator({
4040
- ...params,
4041
- number: requestCount,
4042
- cursor
4043
- });
4044
- const response = await GlobalGetData$2(type, {
4029
+ const targetNumber = Number(params.number ?? maxPageSize);
4030
+ while (fetchedData.length < targetNumber) {
4031
+ const remaining = targetNumber - fetchedData.length;
4032
+ currentParams.number = Math.min(remaining, maxPageSize);
4033
+ const url$1 = apiUrlGenerator(currentParams);
4034
+ const finalUrl = signType ? buildSignedUrl(url$1, signType, userAgent) : url$1;
4035
+ const raw = await GlobalGetData$2(type, {
4045
4036
  ...requestConfig,
4046
- url: buildSignedUrl(url$1, signType, userAgent)
4037
+ url: finalUrl
4047
4038
  });
4048
- fetchedData.push(...response.comments ?? response.data ?? []);
4049
- tmpresp = response;
4050
- if ((response.comments ?? response.data ?? []).length < requestCount) break;
4051
- cursor = response.cursor;
4039
+ const response = processRawResponse ? processRawResponse(raw) : raw;
4040
+ if (response && response.amagiError) return response;
4041
+ const list = extractList(response);
4042
+ if (isFirstRequest && validateFirstPage) {
4043
+ const error = validateFirstPage(list, response, finalUrl);
4044
+ if (error) return error;
4045
+ }
4046
+ if (Array.isArray(list) && list.length > 0) fetchedData.push(...list);
4047
+ lastResponse = response;
4048
+ if (!hasMore(response)) break;
4049
+ if (!list || list.length === 0) break;
4050
+ currentParams = updateParams(currentParams, response);
4051
+ isFirstRequest = false;
4052
4052
  }
4053
- return {
4054
- ...tmpresp,
4055
- comments: params.number === 0 ? [] : fetchedData.slice(0, Number(params.number ?? maxPageSize)),
4056
- cursor: params.number === 0 ? 0 : fetchedData.length
4057
- };
4053
+ const slicedData = targetNumber === 0 ? [] : fetchedData.slice(0, targetNumber);
4054
+ return formatFinalResponse(lastResponse, slicedData);
4058
4055
  };
4059
4056
  /**
4060
4057
  * 全局数据获取函数
@@ -4363,7 +4360,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4363
4360
  headers: {
4364
4361
  ...baseRequestConfig.headers,
4365
4362
  "x-s": xiaohongshuSign.generateXSPost(xiaohongshuApiUrls$1.首页推荐数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web", xiaohongshuApiUrls$1.首页推荐数据(data$1).Body),
4366
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4363
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4367
4364
  "x-t": xiaohongshuSign.generateXT()
4368
4365
  }
4369
4366
  });
@@ -4374,7 +4371,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4374
4371
  headers: {
4375
4372
  ...baseRequestConfig.headers,
4376
4373
  "x-s": xiaohongshuSign.generateXSPost(xiaohongshuApiUrls$1.单个笔记数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web", xiaohongshuApiUrls$1.单个笔记数据(data$1).Body),
4377
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4374
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4378
4375
  "x-t": xiaohongshuSign.generateXT()
4379
4376
  }
4380
4377
  });
@@ -4394,7 +4391,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4394
4391
  headers: {
4395
4392
  ...baseRequestConfig$1.headers,
4396
4393
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.评论数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4397
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4394
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4398
4395
  "x-t": xiaohongshuSign.generateXT()
4399
4396
  }
4400
4397
  });
@@ -4417,7 +4414,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4417
4414
  headers: {
4418
4415
  ...baseRequestConfig$1.headers,
4419
4416
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.用户数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4420
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4417
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4421
4418
  "x-t": xiaohongshuSign.generateXT()
4422
4419
  }
4423
4420
  })),
@@ -4432,7 +4429,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4432
4429
  ...baseRequestConfig.headers,
4433
4430
  "x-b3-traceid": xiaohongshuSign.generateXB3Traceid(),
4434
4431
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.用户笔记数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4435
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4432
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4436
4433
  "x-t": xiaohongshuSign.generateXT()
4437
4434
  }
4438
4435
  });
@@ -4452,7 +4449,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4452
4449
  headers: {
4453
4450
  ...baseRequestConfig$1.headers,
4454
4451
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.表情列表(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4455
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4452
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4456
4453
  "x-t": xiaohongshuSign.generateXT()
4457
4454
  }
4458
4455
  });
@@ -4464,7 +4461,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4464
4461
  headers: {
4465
4462
  ...baseRequestConfig.headers,
4466
4463
  "x-s": xiaohongshuSign.generateXSPost(xiaohongshuApiUrls$1.搜索笔记(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4467
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4464
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4468
4465
  "x-t": xiaohongshuSign.generateXT()
4469
4466
  }
4470
4467
  });
@@ -4956,6 +4953,7 @@ const createAmagiClient = (options) => {
4956
4953
  * @returns Express应用实例
4957
4954
  */
4958
4955
  const startServer = (port = 4567) => {
4956
+ initLogger();
4959
4957
  const app = express();
4960
4958
  app.use(express.json());
4961
4959
  app.use(express.urlencoded({ extended: true }));
@@ -5173,7 +5171,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType$1) {
5173
5171
 
5174
5172
  //#endregion
5175
5173
  //#region src/index.ts
5176
- const VERSION = "5.12.0";
5174
+ const VERSION = "5.12.3";
5177
5175
  /**
5178
5176
  * @deprecated 请使用 createAmagiClient 替代
5179
5177
  */
@@ -5209,4 +5207,5 @@ const Client = CreateApp;
5209
5207
  const amagi = Client;
5210
5208
 
5211
5209
  //#endregion
5212
- export { AdditionalType, ApiError, BilibiliApplyCaptchaParamsSchema, BilibiliArticleCardParamsSchema, BilibiliArticleInfoParamsSchema, BilibiliArticleParamsSchema, BilibiliAv2BvParamsSchema, BilibiliBangumiInfoParamsSchema, BilibiliBangumiStreamParamsSchema, BilibiliBv2AvParamsSchema, BilibiliColumnInfoParamsSchema, BilibiliCommentParamsSchema, BilibiliCommentReplyParamsSchema, BilibiliDynamicParamsSchema, BilibiliEmojiParamsSchema, BilibiliLiveParamsSchema, BilibiliLoginParamsSchema, BilibiliMethodRoutes, BilibiliQrcodeParamsSchema, BilibiliQrcodeStatusParamsSchema, BilibiliUserParamsSchema, BilibiliValidateCaptchaParamsSchema, 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 };
5210
+ export { AdditionalType, ApiError, BilibiliApplyCaptchaParamsSchema, BilibiliArticleCardParamsSchema, BilibiliArticleInfoParamsSchema, BilibiliArticleParamsSchema, BilibiliAv2BvParamsSchema, BilibiliBangumiInfoParamsSchema, BilibiliBangumiStreamParamsSchema, BilibiliBv2AvParamsSchema, BilibiliColumnInfoParamsSchema, BilibiliCommentParamsSchema, BilibiliCommentReplyParamsSchema, BilibiliDynamicParamsSchema, BilibiliEmojiParamsSchema, BilibiliLiveParamsSchema, BilibiliLoginParamsSchema, BilibiliMethodRoutes, BilibiliQrcodeParamsSchema, BilibiliQrcodeStatusParamsSchema, BilibiliUserParamsSchema, BilibiliValidateCaptchaParamsSchema, 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, initLogger, isNetworkErrorResult, kuaishou, kuaishouApiUrls, kuaishouUtils, logMiddleware, logger, qtparam, validateBilibiliParams, validateDouyinParams, validateKuaishouParams, validateXiaohongshuParams, wbi_sign, xiaohongshu, xiaohongshuApiUrls, xiaohongshuSign, xiaohongshuUtils };
5211
+ //# sourceMappingURL=index.js.map