@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.
- package/dist/default/index.cjs +271 -273
- package/dist/default/index.d.cts +83 -86
- package/dist/default/index.d.cts.map +1 -0
- package/dist/default/index.d.ts +83 -86
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +272 -273
- package/dist/default/index.js.map +1 -0
- package/package.json +16 -16
package/dist/default/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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.
|
|
151
|
+
this.proxy("info", [message, ...args]);
|
|
116
152
|
}
|
|
117
153
|
warn(message, ...args) {
|
|
118
|
-
this.
|
|
154
|
+
this.proxy("warn", [message, ...args]);
|
|
119
155
|
}
|
|
120
156
|
error(message, ...args) {
|
|
121
|
-
this.
|
|
157
|
+
this.proxy("error", [message, ...args]);
|
|
122
158
|
}
|
|
123
159
|
mark(message, ...args) {
|
|
124
|
-
this.
|
|
160
|
+
this.proxy("mark", [message, ...args]);
|
|
125
161
|
}
|
|
126
162
|
debug(message, ...args) {
|
|
127
|
-
this.
|
|
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
|
|
763
|
+
* @param cookies - cookie字符串
|
|
753
764
|
* @returns Base64编码的随机字符串
|
|
754
765
|
*/
|
|
755
|
-
static generateXSCommon(
|
|
756
|
-
return
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
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:
|
|
3740
|
-
search_id:
|
|
3741
|
-
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
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
|
-
|
|
3829
|
-
|
|
3830
|
-
amagiMessage: warningMessage
|
|
3806
|
+
...lastValid,
|
|
3807
|
+
data: mergedData
|
|
3831
3808
|
};
|
|
3832
3809
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
if (
|
|
3837
|
-
|
|
3838
|
-
}
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
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(
|
|
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:
|
|
3847
|
+
amagiError: {
|
|
3848
|
+
errorDescription: desc,
|
|
3849
|
+
requestType: data$1.methodType ?? "未知请求类型",
|
|
3850
|
+
requestUrl: url$1
|
|
3851
|
+
},
|
|
3857
3852
|
amagiMessage: warningMessage
|
|
3858
3853
|
};
|
|
3859
3854
|
}
|
|
3860
|
-
|
|
3861
|
-
|
|
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(
|
|
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:
|
|
3866
|
+
amagiError: {
|
|
3867
|
+
errorDescription: desc,
|
|
3868
|
+
requestType: data$1.methodType ?? "未知请求类型",
|
|
3869
|
+
requestUrl: url$1
|
|
3870
|
+
},
|
|
3881
3871
|
amagiMessage: warningMessage
|
|
3882
3872
|
};
|
|
3883
3873
|
}
|
|
3884
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
4035
|
-
*
|
|
4036
|
-
* @
|
|
4037
|
-
* @
|
|
4038
|
-
* @
|
|
4039
|
-
* @
|
|
4040
|
-
* @
|
|
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 (
|
|
4043
|
-
|
|
4044
|
-
let
|
|
4045
|
-
|
|
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
|
-
|
|
4048
|
-
|
|
4049
|
-
const
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
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:
|
|
4046
|
+
url: finalUrl
|
|
4057
4047
|
});
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
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
|
-
|
|
4064
|
-
|
|
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.
|
|
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;
|