@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.
@@ -7,19 +7,31 @@ node_path = require_chunk.__toESM(node_path);
7
7
  let node_url = require("node:url");
8
8
  node_url = require_chunk.__toESM(node_url);
9
9
  let chalk = require("chalk");
10
- let log4js = require("log4js");
11
- log4js = require_chunk.__toESM(log4js);
12
10
  let axios = require("axios");
13
11
  axios = require_chunk.__toESM(axios);
14
12
  let zod = require("zod");
15
13
  zod = require_chunk.__toESM(zod);
14
+ let __ikenxuan_xhshow_ts = require("@ikenxuan/xhshow-ts");
16
15
  let node_crypto = require("node:crypto");
17
16
  node_crypto = require_chunk.__toESM(node_crypto);
18
- let __ikenxuan_xhshow_ts = require("@ikenxuan/xhshow-ts");
19
17
  let express = require("express");
20
18
  express = require_chunk.__toESM(express);
21
19
 
22
20
  //#region src/model/logger.ts
21
+ /**
22
+ * 动态获取 log4js 库
23
+ * 优先使用 node-karin/log4js (Karin 环境)
24
+ * 失败则回退到 log4js (独立环境,通过别名映射到 @karinjs/log4js)
25
+ */
26
+ const getLog4js = async () => {
27
+ try {
28
+ return (await import("node-karin/log4js")).default;
29
+ } catch {
30
+ const lib = await import("log4js");
31
+ return lib.default || lib;
32
+ }
33
+ };
34
+ const log4jsPromise = getLog4js();
23
35
  /** 获取包的绝对路径 */
24
36
  const getPackageLogsPath = () => {
25
37
  const currentFileUrl = require("url").pathToFileURL(__filename).href;
@@ -29,7 +41,11 @@ const getPackageLogsPath = () => {
29
41
  if (node_fs.default.existsSync(node_path.default.join(packageRoot, "package.json"))) break;
30
42
  packageRoot = node_path.default.dirname(packageRoot);
31
43
  }
32
- return node_path.default.join(packageRoot, "logs");
44
+ const logsDir = node_path.default.join(packageRoot, "logs");
45
+ try {
46
+ if (!node_fs.default.existsSync(logsDir)) node_fs.default.mkdirSync(logsDir, { recursive: true });
47
+ } catch (error) {}
48
+ return logsDir;
33
49
  };
34
50
  const logsPath = getPackageLogsPath();
35
51
  /** 获取日志级别,优先使用环境变量,默认为 info */
@@ -37,59 +53,65 @@ const getLogLevel = () => {
37
53
  return process.env.LOG_LEVEL ?? "info";
38
54
  };
39
55
  const currentLogLevel = getLogLevel();
40
- log4js.default.configure({
41
- appenders: {
42
- console: {
43
- type: "stdout",
44
- layout: {
45
- type: "pattern",
46
- pattern: "%[[amagi][%d{hh:mm:ss.SSS}][%4.4p]%] %m"
47
- }
48
- },
49
- command: {
50
- type: "dateFile",
51
- filename: node_path.default.join(logsPath, "application", "command"),
52
- pattern: "yyyy-MM-dd.log",
53
- numBackups: 15,
54
- alwaysIncludePattern: true,
55
- layout: {
56
- type: "pattern",
57
- pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
58
- }
59
- },
60
- httpConsole: {
61
- type: "stdout",
62
- layout: {
63
- type: "pattern",
64
- pattern: "%[[amagi][%d{hh:mm:ss.SSS}][HTTP]%] %m"
65
- }
66
- },
67
- httpRequest: {
68
- type: "dateFile",
69
- filename: node_path.default.join(logsPath, "http", "requests"),
70
- pattern: "yyyy-MM-dd.log",
71
- numBackups: 30,
72
- alwaysIncludePattern: true,
73
- layout: {
74
- type: "pattern",
75
- pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
76
- }
77
- }
78
- },
79
- categories: {
80
- default: {
81
- appenders: ["console", "command"],
82
- level: currentLogLevel
83
- },
84
- http: {
85
- appenders: ["httpConsole", "httpRequest"],
86
- level: "debug"
87
- }
88
- },
89
- pm2: true
90
- });
56
+ /** 初始化 logger 配置 */
57
+ const initLogger = () => {
58
+ log4jsPromise.then((log4js) => {
59
+ log4js.configure({
60
+ appenders: {
61
+ console: {
62
+ type: "stdout",
63
+ layout: {
64
+ type: "pattern",
65
+ pattern: "%[[amagi][%d{hh:mm:ss.SSS}][%4.4p]%] %m"
66
+ }
67
+ },
68
+ command: {
69
+ type: "dateFile",
70
+ filename: node_path.default.join(logsPath, "application", "command"),
71
+ pattern: "yyyy-MM-dd.log",
72
+ numBackups: 15,
73
+ alwaysIncludePattern: true,
74
+ layout: {
75
+ type: "pattern",
76
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
77
+ }
78
+ },
79
+ httpConsole: {
80
+ type: "stdout",
81
+ layout: {
82
+ type: "pattern",
83
+ pattern: "%[[amagi][%d{hh:mm:ss.SSS}][HTTP]%] %m"
84
+ }
85
+ },
86
+ httpRequest: {
87
+ type: "dateFile",
88
+ filename: node_path.default.join(logsPath, "http", "requests"),
89
+ pattern: "yyyy-MM-dd.log",
90
+ numBackups: 30,
91
+ alwaysIncludePattern: true,
92
+ layout: {
93
+ type: "pattern",
94
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
95
+ }
96
+ }
97
+ },
98
+ categories: {
99
+ default: {
100
+ appenders: ["console", "command"],
101
+ level: currentLogLevel
102
+ },
103
+ http: {
104
+ appenders: ["httpConsole", "httpRequest"],
105
+ level: "debug"
106
+ }
107
+ },
108
+ pm2: true
109
+ });
110
+ });
111
+ };
91
112
  var CustomLogger = class {
92
113
  logger;
114
+ queue = [];
93
115
  chalk;
94
116
  red;
95
117
  green;
@@ -100,7 +122,10 @@ var CustomLogger = class {
100
122
  white;
101
123
  gray;
102
124
  constructor(name) {
103
- this.logger = log4js.default.getLogger(name);
125
+ log4jsPromise.then((log4js) => {
126
+ this.logger = log4js.getLogger(name);
127
+ this.flush();
128
+ });
104
129
  this.chalk = new chalk.Chalk();
105
130
  this.red = this.chalk.red;
106
131
  this.green = this.chalk.green;
@@ -111,20 +136,31 @@ var CustomLogger = class {
111
136
  this.white = this.chalk.white;
112
137
  this.gray = this.chalk.gray;
113
138
  }
139
+ flush() {
140
+ if (!this.logger) return;
141
+ while (this.queue.length) {
142
+ const [method, args] = this.queue.shift();
143
+ this.logger[method](...args);
144
+ }
145
+ }
146
+ proxy(method, args) {
147
+ if (this.logger) this.logger[method](...args);
148
+ else this.queue.push([method, args]);
149
+ }
114
150
  info(message, ...args) {
115
- this.logger.info(message, ...args);
151
+ this.proxy("info", [message, ...args]);
116
152
  }
117
153
  warn(message, ...args) {
118
- this.logger.warn(message, ...args);
154
+ this.proxy("warn", [message, ...args]);
119
155
  }
120
156
  error(message, ...args) {
121
- this.logger.error(message, ...args);
157
+ this.proxy("error", [message, ...args]);
122
158
  }
123
159
  mark(message, ...args) {
124
- this.logger.mark(message, ...args);
160
+ this.proxy("mark", [message, ...args]);
125
161
  }
126
162
  debug(message, ...args) {
127
- this.logger.debug(message, ...args);
163
+ this.proxy("debug", [message, ...args]);
128
164
  }
129
165
  };
130
166
  const logger = new CustomLogger("default");
@@ -723,51 +759,26 @@ var xiaohongshuSign = class {
723
759
  return this.client.signXsPost(path$1, a1Cookie, clientType, body);
724
760
  }
725
761
  /**
726
- * 生成X-S签名(兼容旧接口)
727
- * @param url - 请求URL
728
- * @param body - 请求体
729
- * @param userAgent - User-Agent(暂未使用)
730
- * @param method - 请求方法,默认为 'POST'
731
- * @param a1Cookie - a1 cookie值
732
- * @returns X-S签名
733
- */
734
- static generateXS(url$1, body, userAgent, method = "POST", a1Cookie = "") {
735
- try {
736
- const urlObj = new URL(url$1);
737
- const path$1 = urlObj.pathname + urlObj.search;
738
- if (method.toUpperCase() === "GET") {
739
- const params = typeof body === "object" ? body : {};
740
- return this.generateXSGet(path$1, a1Cookie, "xhs-pc-web", params);
741
- } else {
742
- const requestBody = typeof body === "object" ? body : {};
743
- return this.generateXSPost(path$1, a1Cookie, "xhs-pc-web", requestBody);
744
- }
745
- } catch (error) {
746
- console.error("生成X-S签名失败:", error);
747
- throw new Error(`签名生成失败: ${error}`);
748
- }
749
- }
750
- /**
751
762
  * 生成X-S-Common参数
752
- * @param length - 长度
763
+ * @param cookies - cookie字符串
753
764
  * @returns Base64编码的随机字符串
754
765
  */
755
- static generateXSCommon(length = 945) {
756
- return node_crypto.default.randomBytes(length).toString("base64").replace(/=+$/, "");
766
+ static generateXSCommon(cookies) {
767
+ return this.client.signXsCommon(cookies);
757
768
  }
758
769
  /**
759
770
  * 生成X-T时间戳
760
771
  * @returns 当前时间戳字符串
761
772
  */
762
773
  static generateXT() {
763
- return Date.now().toString();
774
+ return this.client.getXT();
764
775
  }
765
776
  /**
766
777
  * 生成X-B3-Traceid
767
778
  * @returns 16位随机字符串
768
779
  */
769
780
  static generateXB3Traceid() {
770
- return Array.from({ length: 16 }, () => "abcdef0123456789"[Math.floor(Math.random() * 16)]).join("");
781
+ return this.client.getB3TraceId();
771
782
  }
772
783
  /**
773
784
  * 从cookie字符串中提取a1值
@@ -1253,7 +1264,8 @@ const fetchResponse = async (config, maxRetries = DEFAULT_MAX_RETRIES) => {
1253
1264
  * 判断结果是否为网络错误响应
1254
1265
  * @param result - 请求结果
1255
1266
  * @returns 是否为ErrorResult
1256
- * @description 通过检查 error 字段中的 amagiError 来区分网络错误和业务错误
1267
+ *
1268
+ * 通过检查 error 字段中的 amagiError 来区分网络错误和业务错误
1257
1269
  */
1258
1270
  const isNetworkErrorResult = (result) => {
1259
1271
  if (result === null || typeof result !== "object") return false;
@@ -3659,11 +3671,53 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3659
3671
  }
3660
3672
  case "评论数据": {
3661
3673
  const urlGenerator = (params) => douyinApiUrls$1.评论(params);
3662
- return await fetchPaginatedData(data$1.methodType, urlGenerator, data$1, 50, baseRequestConfig, signType);
3674
+ return await fetchPaginatedData({
3675
+ type: data$1.methodType,
3676
+ apiUrlGenerator: urlGenerator,
3677
+ params: {
3678
+ ...data$1,
3679
+ cursor: data$1.cursor ?? 0
3680
+ },
3681
+ maxPageSize: 50,
3682
+ requestConfig: baseRequestConfig,
3683
+ signType,
3684
+ extractList: (resp) => resp.comments ?? [],
3685
+ updateParams: (params, resp) => ({
3686
+ ...params,
3687
+ cursor: resp.cursor
3688
+ }),
3689
+ hasMore: (resp) => resp.has_more === 1,
3690
+ formatFinalResponse: (resp, list) => ({
3691
+ ...resp,
3692
+ comments: list,
3693
+ cursor: resp.cursor ?? list.length
3694
+ })
3695
+ });
3663
3696
  }
3664
3697
  case "指定评论回复数据": {
3665
3698
  const urlGenerator = (params) => douyinApiUrls$1.二级评论(params);
3666
- return await fetchPaginatedData(data$1.methodType, urlGenerator, data$1, 3, baseRequestConfig, "x_bogus");
3699
+ return await fetchPaginatedData({
3700
+ type: data$1.methodType,
3701
+ apiUrlGenerator: urlGenerator,
3702
+ params: {
3703
+ ...data$1,
3704
+ cursor: data$1.cursor ?? 0
3705
+ },
3706
+ maxPageSize: 3,
3707
+ requestConfig: baseRequestConfig,
3708
+ signType: "x_bogus",
3709
+ extractList: (resp) => resp.comments ?? [],
3710
+ updateParams: (params, resp) => ({
3711
+ ...params,
3712
+ cursor: resp.cursor
3713
+ }),
3714
+ hasMore: (resp) => resp.has_more === 1,
3715
+ formatFinalResponse: (resp, list) => ({
3716
+ ...resp,
3717
+ comments: list,
3718
+ cursor: resp.cursor ?? list.length
3719
+ })
3720
+ });
3667
3721
  }
3668
3722
  case "用户主页数据": {
3669
3723
  const url$1 = douyinApiUrls$1.用户主页信息({ sec_uid: data$1.sec_uid });
@@ -3715,10 +3769,6 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3715
3769
  });
3716
3770
  }
3717
3771
  case "搜索数据": {
3718
- let search_id = "";
3719
- const maxPageSize = 15;
3720
- let fetchedSearchList = [];
3721
- let tmpresp = null;
3722
3772
  const searchType = data$1.type ?? "综合";
3723
3773
  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))}`;
3724
3774
  const customConfig = {
@@ -3730,146 +3780,82 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3730
3780
  };
3731
3781
  const isUserSearch = searchType === "用户";
3732
3782
  const isVideoSearch = searchType === "视频";
3733
- let isFirstRequest = true;
3734
- while (fetchedSearchList.length < Number(data$1.number ?? 10)) {
3735
- const requestCount = Math.min(Number(data$1.number ?? 50) - fetchedSearchList.length, maxPageSize);
3736
- const url$1 = douyinApiUrls$1.搜索({
3783
+ return await fetchPaginatedData({
3784
+ type: data$1.methodType,
3785
+ apiUrlGenerator: (params) => douyinApiUrls$1.搜索(params),
3786
+ params: {
3737
3787
  query: data$1.query,
3738
3788
  type: data$1.type,
3739
- number: requestCount,
3740
- search_id: search_id === "" ? void 0 : search_id
3741
- });
3742
- const raw = await GlobalGetData$2(data$1.methodType, {
3743
- ...customConfig,
3744
- url: url$1
3745
- });
3746
- if (isUserSearch) {
3747
- if (!raw || typeof raw !== "object") {
3748
- const Err = {
3749
- errorDescription: "抖音用户搜索返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!",
3750
- requestType: data$1.methodType ?? "未知请求类型",
3751
- requestUrl: url$1
3752
- };
3753
- const warningMessage = `
3754
- 获取响应数据失败!原因:${logger.yellow("用户搜索返回无有效数据,疑似触发反爬机制")}
3755
- 请求类型:「${data$1.methodType}」
3756
- 搜索关键词:「${data$1.query}」
3757
- 请求URL:${url$1}
3758
- `;
3759
- !isFirstRequest && logger.warn(warningMessage);
3760
- return {
3761
- code: douoyinAPIErrorCode.COOKIE,
3762
- data: raw,
3763
- amagiError: Err,
3764
- amagiMessage: warningMessage
3765
- };
3766
- }
3767
- const userList = raw.user_list;
3768
- if (isFirstRequest && (!userList || userList.length === 0)) {
3769
- const Err = {
3770
- errorDescription: "抖音用户搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!",
3771
- requestType: data$1.methodType ?? "未知请求类型",
3772
- requestUrl: url$1
3773
- };
3774
- const warningMessage = `
3775
- 获取响应数据失败!原因:${logger.yellow("用户搜索接口第一次请求就返回空数组,你的抖音Cookie可能已经失效!")}
3776
- 请求类型:「${data$1.methodType}」
3777
- 搜索关键词:「${data$1.query}」
3778
- 请求URL:${url$1}
3779
- `;
3780
- logger.warn(warningMessage);
3781
- return {
3782
- data: raw,
3783
- amagiError: Err,
3784
- amagiMessage: warningMessage
3785
- };
3786
- }
3787
- if (Array.isArray(userList) && userList.length > 0) fetchedSearchList.push(...userList);
3788
- tmpresp = raw;
3789
- search_id = raw.rid ?? search_id;
3790
- if (typeof raw.has_more === "number" && raw.has_more === 0) break;
3791
- if (!userList || userList.length === 0) break;
3792
- } else if (isVideoSearch) {
3793
- if (!raw || typeof raw !== "object") {
3794
- const Err = {
3795
- errorDescription: "抖音视频搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效,你的抖音Cookie可能已经失效!",
3796
- requestType: data$1.methodType ?? "未知请求类型",
3797
- requestUrl: url$1
3798
- };
3799
- const warningMessage = `
3800
- 获取响应数据失败!原因:${logger.yellow("视频搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!")}
3801
- 请求类型:「${data$1.methodType}」
3802
- 搜索关键词:「${data$1.query}」
3803
- 请求URL:${url$1}
3804
- `;
3805
- !isFirstRequest && logger.warn(warningMessage);
3806
- return {
3807
- code: douoyinAPIErrorCode.COOKIE,
3808
- data: raw,
3809
- amagiError: Err,
3810
- amagiMessage: warningMessage
3811
- };
3812
- }
3813
- const videoList = raw.data;
3814
- if (isFirstRequest && (!videoList || videoList.length === 0)) {
3815
- const Err = {
3816
- errorDescription: "抖音视频搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!",
3817
- requestType: data$1.methodType ?? "未知请求类型",
3818
- requestUrl: url$1
3819
- };
3820
- const warningMessage = `
3821
- 获取响应数据失败!原因:${logger.yellow("视频搜索接口第一次请求就返回空数组,你的抖音Cookie可能已经失效!")}
3822
- 请求类型:「${data$1.methodType}」
3823
- 搜索关键词:「${data$1.query}」
3824
- 请求URL:${url$1}
3825
- `;
3826
- logger.warn(warningMessage);
3789
+ number: data$1.number ?? 10,
3790
+ search_id: ""
3791
+ },
3792
+ maxPageSize: 15,
3793
+ requestConfig: customConfig,
3794
+ signType: null,
3795
+ processRawResponse: (raw) => {
3796
+ if (!isUserSearch && !isVideoSearch) {
3797
+ const responses = filterSearchResponses(typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw]);
3798
+ if (responses.length === 0) return raw;
3799
+ const mergedData = [];
3800
+ let lastValid = {};
3801
+ for (const resp of responses) {
3802
+ if (Array.isArray(resp.data) && resp.data.length > 0) mergedData.push(...resp.data);
3803
+ lastValid = resp;
3804
+ }
3827
3805
  return {
3828
- data: raw,
3829
- amagiError: Err,
3830
- amagiMessage: warningMessage
3806
+ ...lastValid,
3807
+ data: mergedData
3831
3808
  };
3832
3809
  }
3833
- if (Array.isArray(videoList) && videoList.length > 0) fetchedSearchList.push(...videoList);
3834
- tmpresp = raw;
3835
- search_id = raw.log_pb?.impr_id ?? search_id;
3836
- if (typeof raw.has_more === "number" && raw.has_more === 0) break;
3837
- if (!videoList || videoList.length === 0) break;
3838
- } else {
3839
- const responses = filterSearchResponses(typeof raw === "string" ? parseDouyinMultiJson(raw) : [raw]);
3840
- if (responses.length === 0) {
3841
- const Err = {
3842
- errorDescription: "抖音综合搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!",
3843
- requestType: data$1.methodType ?? "未知请求类型",
3844
- requestUrl: url$1
3845
- };
3810
+ return raw;
3811
+ },
3812
+ extractList: (resp) => {
3813
+ if (isUserSearch) return resp.user_list ?? [];
3814
+ return resp.data ?? [];
3815
+ },
3816
+ updateParams: (params, resp) => {
3817
+ let nextSearchId = params.search_id;
3818
+ if (isUserSearch) nextSearchId = resp.rid ?? nextSearchId;
3819
+ else nextSearchId = resp.log_pb?.impr_id ?? nextSearchId;
3820
+ return {
3821
+ ...params,
3822
+ search_id: nextSearchId
3823
+ };
3824
+ },
3825
+ hasMore: (resp) => {
3826
+ return resp.has_more !== 0;
3827
+ },
3828
+ validateFirstPage: (list, raw, url$1) => {
3829
+ const typeStr = isUserSearch ? "用户" : isVideoSearch ? "视频" : "综合";
3830
+ let isInvalidResponse = false;
3831
+ const rawAny = raw;
3832
+ if (!rawAny || typeof rawAny !== "object") isInvalidResponse = true;
3833
+ else if (isUserSearch && !rawAny.user_list) isInvalidResponse = true;
3834
+ else if (isVideoSearch && !rawAny.data) isInvalidResponse = true;
3835
+ else if (!isUserSearch && !isVideoSearch && !rawAny.data) isInvalidResponse = true;
3836
+ if (isInvalidResponse) {
3837
+ const desc = `抖音${typeStr}搜索返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!`;
3846
3838
  const warningMessage = `
3847
- 获取响应数据失败!原因:${logger.yellow("综合搜索接口返回无有效数据,疑似触发反爬机制,你的抖音Cookie可能已经失效!")}
3839
+ 获取响应数据失败!原因:${logger.yellow(`${typeStr}搜索返回无有效数据,疑似触发反爬机制`)}
3848
3840
  请求类型:「${data$1.methodType}」
3849
3841
  搜索关键词:「${data$1.query}」
3850
3842
  请求URL:${url$1}
3851
3843
  `;
3852
- !isFirstRequest && logger.warn(warningMessage);
3853
3844
  return {
3854
3845
  code: douoyinAPIErrorCode.COOKIE,
3855
3846
  data: raw,
3856
- amagiError: Err,
3847
+ amagiError: {
3848
+ errorDescription: desc,
3849
+ requestType: data$1.methodType ?? "未知请求类型",
3850
+ requestUrl: url$1
3851
+ },
3857
3852
  amagiMessage: warningMessage
3858
3853
  };
3859
3854
  }
3860
- for (const resp of responses) {
3861
- if (Array.isArray(resp.data) && resp.data.length > 0) fetchedSearchList.push(...resp.data);
3862
- tmpresp = resp;
3863
- search_id = resp.log_pb?.impr_id ?? search_id;
3864
- }
3865
- if (isFirstRequest && fetchedSearchList.length === 0) {
3866
- const Err = {
3867
- errorDescription: "抖音综合搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!",
3868
- requestType: data$1.methodType ?? "未知请求类型",
3869
- requestUrl: url$1
3870
- };
3855
+ if (!list || list.length === 0) {
3856
+ const desc = `抖音${typeStr}搜索接口第一次请求就返回空数组,可能该关键词无搜索结果或触发风控限制,你的抖音Cookie可能已经失效!`;
3871
3857
  const warningMessage = `
3872
- 获取响应数据失败!原因:${logger.yellow("综合搜索索接第一次请求就返回空数组,你的抖音Cookie可能已经失效!")}
3858
+ 获取响应数据失败!原因:${logger.yellow(`${typeStr}搜索接口第一次请求就返回空数组,你的抖音Cookie可能已经失效!`)}
3873
3859
  请求类型:「${data$1.methodType}」
3874
3860
  搜索关键词:「${data$1.query}」
3875
3861
  请求URL:${url$1}
@@ -3877,25 +3863,27 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
3877
3863
  logger.warn(warningMessage);
3878
3864
  return {
3879
3865
  data: raw,
3880
- amagiError: Err,
3866
+ amagiError: {
3867
+ errorDescription: desc,
3868
+ requestType: data$1.methodType ?? "未知请求类型",
3869
+ requestUrl: url$1
3870
+ },
3881
3871
  amagiMessage: warningMessage
3882
3872
  };
3883
3873
  }
3884
- if (tmpresp && typeof tmpresp.has_more === "number" && tmpresp.has_more === 0) break;
3874
+ return null;
3875
+ },
3876
+ formatFinalResponse: (resp, list) => {
3877
+ if (isUserSearch) return {
3878
+ ...resp,
3879
+ user_list: list
3880
+ };
3881
+ return {
3882
+ ...resp,
3883
+ data: list
3884
+ };
3885
3885
  }
3886
- isFirstRequest = false;
3887
- }
3888
- const slicedList = Number(data$1.number ?? 10) === 0 ? [] : fetchedSearchList.slice(0, Number(data$1.number ?? 10));
3889
- return isUserSearch ? {
3890
- ...tmpresp ?? {},
3891
- user_list: slicedList
3892
- } : isVideoSearch ? {
3893
- ...tmpresp ?? {},
3894
- data: slicedList
3895
- } : {
3896
- ...tmpresp ?? {},
3897
- data: slicedList
3898
- };
3886
+ });
3899
3887
  }
3900
3888
  case "动态表情数据": {
3901
3889
  const url$1 = douyinApiUrls$1.互动表情();
@@ -4031,40 +4019,48 @@ const DouyinData = async (data$1, cookie, requestConfig) => {
4031
4019
  };
4032
4020
  /**
4033
4021
  * 通用的分页请求函数
4034
- * @param type - 请求类型
4035
- * @param apiUrlGenerator - 接口URL生成器
4036
- * @param params - 请求参数
4037
- * @param maxPageSize - 单次请求的最大数据量
4038
- * @param requestConfig - axios请求配置(外部配置优先)
4039
- * @param signType - 签名算法类型
4040
- * @returns 返回分页数据
4022
+ * 封装了循环分页请求、数据合并、错误处理和反爬检测逻辑
4023
+ *
4024
+ * @template T - 列表项类型
4025
+ * @template P - 参数类型
4026
+ * @template R - 返回值类型
4027
+ * @template RawResp - 原始响应类型
4028
+ * @param config - 分页请求配置对象
4029
+ * @returns Promise<R> 返回最终构造的数据对象
4041
4030
  */
4042
- const fetchPaginatedData = async (type, apiUrlGenerator, params, maxPageSize, requestConfig, signType = "a_bogus") => {
4043
- let cursor = params.cursor ?? 0;
4044
- let fetchedData = [];
4045
- let tmpresp = {};
4031
+ const fetchPaginatedData = async (config) => {
4032
+ const { type, apiUrlGenerator, params, maxPageSize, requestConfig, signType = "a_bogus", extractList, updateParams, hasMore, formatFinalResponse, processRawResponse, validateFirstPage } = config;
4033
+ let currentParams = { ...params };
4034
+ const fetchedData = [];
4035
+ let lastResponse = {};
4036
+ let isFirstRequest = true;
4046
4037
  const userAgent = requestConfig.headers?.["User-Agent"];
4047
- while (fetchedData.length < Number(params.number ?? maxPageSize)) {
4048
- const requestCount = Math.min(Number(params.number ?? maxPageSize) - fetchedData.length, maxPageSize);
4049
- const url$1 = apiUrlGenerator({
4050
- ...params,
4051
- number: requestCount,
4052
- cursor
4053
- });
4054
- const response = await GlobalGetData$2(type, {
4038
+ const targetNumber = Number(params.number ?? maxPageSize);
4039
+ while (fetchedData.length < targetNumber) {
4040
+ const remaining = targetNumber - fetchedData.length;
4041
+ currentParams.number = Math.min(remaining, maxPageSize);
4042
+ const url$1 = apiUrlGenerator(currentParams);
4043
+ const finalUrl = signType ? buildSignedUrl(url$1, signType, userAgent) : url$1;
4044
+ const raw = await GlobalGetData$2(type, {
4055
4045
  ...requestConfig,
4056
- url: buildSignedUrl(url$1, signType, userAgent)
4046
+ url: finalUrl
4057
4047
  });
4058
- fetchedData.push(...response.comments ?? response.data ?? []);
4059
- tmpresp = response;
4060
- if ((response.comments ?? response.data ?? []).length < requestCount) break;
4061
- cursor = response.cursor;
4048
+ const response = processRawResponse ? processRawResponse(raw) : raw;
4049
+ if (response && response.amagiError) return response;
4050
+ const list = extractList(response);
4051
+ if (isFirstRequest && validateFirstPage) {
4052
+ const error = validateFirstPage(list, response, finalUrl);
4053
+ if (error) return error;
4054
+ }
4055
+ if (Array.isArray(list) && list.length > 0) fetchedData.push(...list);
4056
+ lastResponse = response;
4057
+ if (!hasMore(response)) break;
4058
+ if (!list || list.length === 0) break;
4059
+ currentParams = updateParams(currentParams, response);
4060
+ isFirstRequest = false;
4062
4061
  }
4063
- return {
4064
- ...tmpresp,
4065
- comments: params.number === 0 ? [] : fetchedData.slice(0, Number(params.number ?? maxPageSize)),
4066
- cursor: params.number === 0 ? 0 : fetchedData.length
4067
- };
4062
+ const slicedData = targetNumber === 0 ? [] : fetchedData.slice(0, targetNumber);
4063
+ return formatFinalResponse(lastResponse, slicedData);
4068
4064
  };
4069
4065
  /**
4070
4066
  * 全局数据获取函数
@@ -4373,7 +4369,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4373
4369
  headers: {
4374
4370
  ...baseRequestConfig.headers,
4375
4371
  "x-s": xiaohongshuSign.generateXSPost(xiaohongshuApiUrls$1.首页推荐数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web", xiaohongshuApiUrls$1.首页推荐数据(data$1).Body),
4376
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4372
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4377
4373
  "x-t": xiaohongshuSign.generateXT()
4378
4374
  }
4379
4375
  });
@@ -4384,7 +4380,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4384
4380
  headers: {
4385
4381
  ...baseRequestConfig.headers,
4386
4382
  "x-s": xiaohongshuSign.generateXSPost(xiaohongshuApiUrls$1.单个笔记数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web", xiaohongshuApiUrls$1.单个笔记数据(data$1).Body),
4387
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4383
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4388
4384
  "x-t": xiaohongshuSign.generateXT()
4389
4385
  }
4390
4386
  });
@@ -4404,7 +4400,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4404
4400
  headers: {
4405
4401
  ...baseRequestConfig$1.headers,
4406
4402
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.评论数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4407
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4403
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4408
4404
  "x-t": xiaohongshuSign.generateXT()
4409
4405
  }
4410
4406
  });
@@ -4427,7 +4423,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4427
4423
  headers: {
4428
4424
  ...baseRequestConfig$1.headers,
4429
4425
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.用户数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4430
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4426
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4431
4427
  "x-t": xiaohongshuSign.generateXT()
4432
4428
  }
4433
4429
  })),
@@ -4442,7 +4438,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4442
4438
  ...baseRequestConfig.headers,
4443
4439
  "x-b3-traceid": xiaohongshuSign.generateXB3Traceid(),
4444
4440
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.用户笔记数据(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4445
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4441
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4446
4442
  "x-t": xiaohongshuSign.generateXT()
4447
4443
  }
4448
4444
  });
@@ -4462,7 +4458,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4462
4458
  headers: {
4463
4459
  ...baseRequestConfig$1.headers,
4464
4460
  "x-s": xiaohongshuSign.generateXSGet(xiaohongshuApiUrls$1.表情列表(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4465
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4461
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4466
4462
  "x-t": xiaohongshuSign.generateXT()
4467
4463
  }
4468
4464
  });
@@ -4474,7 +4470,7 @@ const XiaohongshuData = async (data$1, cookie, requestConfig) => {
4474
4470
  headers: {
4475
4471
  ...baseRequestConfig.headers,
4476
4472
  "x-s": xiaohongshuSign.generateXSPost(xiaohongshuApiUrls$1.搜索笔记(data$1).apiPath, xiaohongshuSign.extractA1FromCookie(cookie ?? ""), "xhs-pc-web"),
4477
- "x-s-common": xiaohongshuSign.generateXSCommon(),
4473
+ "x-s-common": xiaohongshuSign.generateXSCommon(cookie ?? ""),
4478
4474
  "x-t": xiaohongshuSign.generateXT()
4479
4475
  }
4480
4476
  });
@@ -4966,6 +4962,7 @@ const createAmagiClient = (options) => {
4966
4962
  * @returns Express应用实例
4967
4963
  */
4968
4964
  const startServer = (port = 4567) => {
4965
+ initLogger();
4969
4966
  const app = (0, express.default)();
4970
4967
  app.use(express.default.json());
4971
4968
  app.use(express.default.urlencoded({ extended: true }));
@@ -5183,7 +5180,7 @@ let DynamicType = /* @__PURE__ */ function(DynamicType$1) {
5183
5180
 
5184
5181
  //#endregion
5185
5182
  //#region src/index.ts
5186
- const VERSION = "5.12.0";
5183
+ const VERSION = "5.12.3";
5187
5184
  /**
5188
5185
  * @deprecated 请使用 createAmagiClient 替代
5189
5186
  */
@@ -5302,6 +5299,7 @@ exports.getHeadersAndData = getHeadersAndData;
5302
5299
  exports.getKuaishouData = getKuaishouData;
5303
5300
  exports.handleError = handleError;
5304
5301
  exports.httpLogger = httpLogger;
5302
+ exports.initLogger = initLogger;
5305
5303
  exports.isNetworkErrorResult = isNetworkErrorResult;
5306
5304
  exports.kuaishou = kuaishou;
5307
5305
  exports.kuaishouApiUrls = kuaishouApiUrls;