@lmcc-dev/mult-fetch-mcp-server 1.0.3 → 1.2.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.
- package/README.md +11 -9
- package/README.zh.md +19 -10
- package/dist/i18n-test-report.json +8 -0
- package/dist/i18n-unused-keys-report.json +8 -0
- package/dist/src/client.js +75 -62
- package/dist/src/index.js +0 -0
- package/dist/src/lib/BrowserFetcher.js +12 -12
- package/dist/src/lib/NodeFetcher.js +165 -106
- package/dist/src/lib/fetch.js +40 -0
- package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
- package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
- package/dist/src/lib/fetchers/common/types.js +41 -0
- package/dist/src/lib/fetchers/common/utils.js +141 -0
- package/dist/src/lib/fetchers/index.js +62 -0
- package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
- package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
- package/dist/src/lib/i18n/index.js +105 -17
- package/dist/src/lib/i18n/keys/base.js +41 -0
- package/dist/src/lib/i18n/keys/browser.js +91 -0
- package/dist/src/lib/i18n/keys/client.js +68 -0
- package/dist/src/lib/i18n/keys/errors.js +49 -0
- package/dist/src/lib/i18n/keys/factory.js +46 -0
- package/dist/src/lib/i18n/keys/fetcher.js +87 -0
- package/dist/src/lib/i18n/keys/index.js +16 -0
- package/dist/src/lib/i18n/keys/node.js +69 -0
- package/dist/src/lib/i18n/keys/prompts.js +79 -0
- package/dist/src/lib/i18n/keys/resources.js +53 -0
- package/dist/src/lib/i18n/keys/server.js +67 -0
- package/dist/src/lib/i18n/keys/tools.js +39 -0
- package/dist/src/lib/i18n/keys.js +504 -0
- package/dist/src/lib/i18n/locales/en/browser.js +79 -55
- package/dist/src/lib/i18n/locales/en/client.js +57 -33
- package/dist/src/lib/i18n/locales/en/errors.js +36 -12
- package/dist/src/lib/i18n/locales/en/factory.js +38 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +67 -60
- package/dist/src/lib/i18n/locales/en/index.js +9 -7
- package/dist/src/lib/i18n/locales/en/node.js +57 -35
- package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
- package/dist/src/lib/i18n/locales/en/resources.js +52 -40
- package/dist/src/lib/i18n/locales/en/server.js +49 -24
- package/dist/src/lib/i18n/locales/en/tools.js +27 -19
- package/dist/src/lib/i18n/locales/zh/browser.js +79 -56
- package/dist/src/lib/i18n/locales/zh/client.js +57 -32
- package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
- package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -60
- package/dist/src/lib/i18n/locales/zh/index.js +9 -7
- package/dist/src/lib/i18n/locales/zh/node.js +61 -36
- package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
- package/dist/src/lib/i18n/locales/zh/resources.js +51 -39
- package/dist/src/lib/i18n/locales/zh/server.js +51 -25
- package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
- package/dist/src/lib/i18n/logger.js +90 -19
- package/dist/src/lib/logger.js +135 -14
- package/dist/src/lib/server/browser.js +12 -18
- package/dist/src/lib/server/fetcher.js +13 -34
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +1 -1
- package/dist/src/lib/server/resources.js +1 -1
- package/dist/src/lib/server/tools.js +404 -308
- package/dist/src/lib/utils/ErrorHandler.js +125 -0
- package/dist/src/lib/utils/errorDetection.js +41 -0
- package/dist/src/lib/utils/errors.js +279 -0
- package/dist/src/mcp-server.js +0 -0
- package/dist/src/test-i18n.js +139 -0
- package/dist/tests/BrowserFetcher.test.js +951 -0
- package/dist/tests/NodeFetcher.test.js +263 -0
- package/dist/tests/client.test.js +211 -0
- package/dist/tests/fetch.test.js +70 -0
- package/dist/tests/fetchers/Fetcher.test.js +108 -0
- package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
- package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
- package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
- package/dist/tests/i18n-missing-keys.js +364 -0
- package/dist/tests/i18n-remove-unused-keys.js +236 -0
- package/dist/tests/i18n-test-report.json +2004 -0
- package/dist/tests/i18n-unused-keys.js +425 -0
- package/dist/tests/i18n.test.js +178 -0
- package/dist/tests/logger.test.js +100 -0
- package/dist/tests/mcp-server.test.js +113 -0
- package/dist/tests/server/browser.test.js +201 -0
- package/dist/tests/server/fetcher.test.js +173 -0
- package/dist/tests/server/index.test.js +122 -0
- package/dist/tests/server/prompts.test.js +246 -0
- package/dist/tests/server/resources.test.js +395 -0
- package/dist/tests/server/tools.test.js +223 -0
- package/dist/tests/setup.js +36 -0
- package/dist/tests/src/lib/i18n/index.js +108 -0
- package/dist/tests/src/lib/i18n/keys/base.js +47 -0
- package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
- package/dist/tests/src/lib/i18n/keys/client.js +70 -0
- package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
- package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
- package/dist/tests/src/lib/i18n/keys/index.js +31 -0
- package/dist/tests/src/lib/i18n/keys/node.js +56 -0
- package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
- package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
- package/dist/tests/src/lib/i18n/keys/server.js +64 -0
- package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
- package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
- package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
- package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
- package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
- package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
- package/dist/tests/src/lib/i18n/logger.js +114 -0
- package/dist/tests/src/lib/logger.js +181 -0
- package/dist/tests/test-direct-client.js +0 -0
- package/dist/tests/test-i18n.js +530 -0
- package/dist/tests/test-mcp-methods.js +1 -1
- package/dist/tests/test-mcp.js +0 -0
- package/dist/tests/test-mini4k.js +0 -0
- package/dist/tests/tests/test-i18n.js +588 -0
- package/dist/tests/types.test.js +57 -0
- package/dist/tests/utils/ErrorHandler.test.js +144 -0
- package/dist/vitest.config.js +29 -0
- package/package.json +32 -19
- package/dist/src/cli-language.js +0 -78
- package/dist/src/lib/example.js +0 -93
- package/dist/src/lib/server/logger.js +0 -23
|
@@ -55,16 +55,16 @@ export class NodeFetcher {
|
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
57
|
// 检查环境变量 (Check environment variables)
|
|
58
|
-
log('
|
|
58
|
+
log('node.checkingProxyEnv', true, {}, COMPONENTS.NODE_FETCH);
|
|
59
59
|
const envVars = ['https_proxy', 'HTTPS_PROXY', 'http_proxy', 'HTTP_PROXY'];
|
|
60
60
|
for (const envVar of envVars) {
|
|
61
|
-
log('
|
|
61
|
+
log('node.envVarValue', true, {
|
|
62
62
|
envVar,
|
|
63
63
|
value: process.env[envVar]
|
|
64
64
|
}, COMPONENTS.NODE_FETCH);
|
|
65
65
|
if (process.env[envVar]) {
|
|
66
66
|
const proxyUrl = process.env[envVar];
|
|
67
|
-
log('
|
|
67
|
+
log('node.foundSystemProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
|
|
68
68
|
return proxyUrl;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -72,22 +72,22 @@ export class NodeFetcher {
|
|
|
72
72
|
try {
|
|
73
73
|
const platform = process.platform;
|
|
74
74
|
let proxyUrl;
|
|
75
|
-
log('
|
|
75
|
+
log('node.checkingSystemEnvVars', true, { platform }, COMPONENTS.NODE_FETCH);
|
|
76
76
|
if (platform === 'win32') {
|
|
77
77
|
// Windows系统 - 使用set命令 (Windows - use set command)
|
|
78
78
|
try {
|
|
79
79
|
// 使用set命令获取代理环境变量 (Use set command to get proxy environment variables)
|
|
80
80
|
const setOutput = execSync('set http_proxy & set https_proxy & set HTTP_PROXY & set HTTPS_PROXY').toString();
|
|
81
|
-
log('
|
|
81
|
+
log('node.windowsEnvVars', true, { output: setOutput.trim() }, COMPONENTS.NODE_FETCH);
|
|
82
82
|
// 解析输出找到代理设置 (Parse output to find proxy settings)
|
|
83
83
|
const proxyMatch = setOutput.match(/(?:http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY)=(https?:\/\/[^=\r\n]+)/i);
|
|
84
84
|
if (proxyMatch && proxyMatch[1]) {
|
|
85
85
|
proxyUrl = proxyMatch[1].trim();
|
|
86
|
-
log('
|
|
86
|
+
log('node.foundWindowsEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
catch (winError) {
|
|
90
|
-
log('
|
|
90
|
+
log('node.errorGettingWindowsEnvVars', true, { error: String(winError) }, COMPONENTS.NODE_FETCH);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
@@ -95,38 +95,168 @@ export class NodeFetcher {
|
|
|
95
95
|
try {
|
|
96
96
|
// 使用env命令获取所有环境变量 (Use env command to get all environment variables)
|
|
97
97
|
const envOutput = execSync('env').toString();
|
|
98
|
-
log('
|
|
98
|
+
log('node.unixEnvVars', true, { output: envOutput.length > 200 ? envOutput.substring(0, 200) + '...' : envOutput }, COMPONENTS.NODE_FETCH);
|
|
99
99
|
// 解析输出找到代理设置 (Parse output to find proxy settings)
|
|
100
100
|
const proxyMatch = envOutput.match(/(?:http_proxy|https_proxy|HTTP_PROXY|HTTPS_PROXY)=(https?:\/\/[^=\n]+)/i);
|
|
101
101
|
if (proxyMatch && proxyMatch[1]) {
|
|
102
102
|
proxyUrl = proxyMatch[1].trim();
|
|
103
|
-
log('
|
|
103
|
+
log('node.foundUnixEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
catch (unixError) {
|
|
107
|
-
log('
|
|
107
|
+
log('node.errorGettingUnixEnvVars', true, { error: String(unixError) }, COMPONENTS.NODE_FETCH);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
if (proxyUrl) {
|
|
111
111
|
return proxyUrl;
|
|
112
112
|
}
|
|
113
113
|
// 如果没有找到代理,记录日志 (If no proxy is found, log a message)
|
|
114
|
-
log('
|
|
114
|
+
log('node.noSystemProxyFound', true, {}, COMPONENTS.NODE_FETCH);
|
|
115
115
|
}
|
|
116
116
|
catch (error) {
|
|
117
|
-
log('
|
|
117
|
+
log('node.errorGettingSystemEnvVars', true, { error: String(error) }, COMPONENTS.NODE_FETCH);
|
|
118
118
|
}
|
|
119
119
|
// 检查NO_PROXY环境变量 (Check NO_PROXY environment variable)
|
|
120
120
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
121
121
|
if (noProxy) {
|
|
122
|
-
log('
|
|
122
|
+
log('node.foundNoProxy', true, { noProxy }, COMPONENTS.NODE_FETCH);
|
|
123
123
|
}
|
|
124
124
|
return undefined;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
*
|
|
128
|
-
* @param
|
|
129
|
-
* @
|
|
127
|
+
* 处理请求延迟 (Handle request delay)
|
|
128
|
+
* @param noDelay 是否禁用延迟 (Whether to disable delay)
|
|
129
|
+
* @param isRedirect 是否是重定向请求 (Whether it's a redirect request)
|
|
130
|
+
* @param debug 是否启用调试模式 (Whether debug mode is enabled)
|
|
131
|
+
*/
|
|
132
|
+
static async handleDelay(noDelay, isRedirect, debug) {
|
|
133
|
+
if (!noDelay && isRedirect) {
|
|
134
|
+
await this.randomDelay();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 准备请求头 (Prepare request headers)
|
|
139
|
+
* @param headers 原始请求头 (Original headers)
|
|
140
|
+
* @param debug 是否启用调试模式 (Whether debug mode is enabled)
|
|
141
|
+
* @returns 处理后的请求头 (Processed headers)
|
|
142
|
+
*/
|
|
143
|
+
static prepareHeaders(headers = {}, debug) {
|
|
144
|
+
const requestHeaders = { ...headers };
|
|
145
|
+
// 添加随机User-Agent (Add random User-Agent)
|
|
146
|
+
if (!requestHeaders['User-Agent']) {
|
|
147
|
+
const userAgent = this.getRandomUserAgent();
|
|
148
|
+
requestHeaders['User-Agent'] = userAgent;
|
|
149
|
+
log('node.usingUserAgent', debug, { userAgent }, COMPONENTS.NODE_FETCH);
|
|
150
|
+
}
|
|
151
|
+
return requestHeaders;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 设置代理 (Set proxy)
|
|
155
|
+
* @param url 请求URL (Request URL)
|
|
156
|
+
* @param proxy 代理URL (Proxy URL)
|
|
157
|
+
* @param debug 是否启用调试模式 (Whether debug mode is enabled)
|
|
158
|
+
* @returns 代理Agent (Proxy agent)
|
|
159
|
+
*/
|
|
160
|
+
static setupProxy(url, proxy, debug) {
|
|
161
|
+
if (!proxy)
|
|
162
|
+
return undefined;
|
|
163
|
+
let agent = undefined;
|
|
164
|
+
if (url.startsWith('https://')) {
|
|
165
|
+
agent = new HttpsProxyAgent(proxy);
|
|
166
|
+
log('node.usingHttpsProxy', debug, {}, COMPONENTS.NODE_FETCH);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
agent = new HttpProxyAgent(proxy);
|
|
170
|
+
log('node.usingHttpProxy', debug, {}, COMPONENTS.NODE_FETCH);
|
|
171
|
+
}
|
|
172
|
+
return agent;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 处理重定向URL (Handle redirect URL)
|
|
176
|
+
* @param location 重定向位置 (Redirect location)
|
|
177
|
+
* @param currentUrl 当前URL (Current URL)
|
|
178
|
+
* @param debug 是否启用调试模式 (Whether debug mode is enabled)
|
|
179
|
+
* @returns 完整的重定向URL (Complete redirect URL)
|
|
180
|
+
*/
|
|
181
|
+
static buildRedirectUrl(location, currentUrl, debug) {
|
|
182
|
+
let redirectUrl = location;
|
|
183
|
+
if (location.startsWith('/')) {
|
|
184
|
+
const urlObj = new URL(currentUrl);
|
|
185
|
+
redirectUrl = `${urlObj.protocol}//${urlObj.host}${location}`;
|
|
186
|
+
}
|
|
187
|
+
else if (!location.startsWith('http')) {
|
|
188
|
+
redirectUrl = new URL(location, currentUrl).toString();
|
|
189
|
+
}
|
|
190
|
+
log('node.constructedFullRedirectUrl', debug, { redirectUrl }, COMPONENTS.NODE_FETCH);
|
|
191
|
+
return redirectUrl;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 处理错误响应 (Handle error response)
|
|
195
|
+
* @param response 响应对象 (Response object)
|
|
196
|
+
* @param debug 是否启用调试模式 (Whether debug mode is enabled)
|
|
197
|
+
* @returns Promise<never> 抛出错误 (Throws error)
|
|
198
|
+
*/
|
|
199
|
+
static async handleErrorResponse(response, debug) {
|
|
200
|
+
log('node.errorResponse', debug, { status: response.status, statusText: response.statusText }, COMPONENTS.NODE_FETCH);
|
|
201
|
+
// 尝试读取错误响应体 (Try to read error response body)
|
|
202
|
+
let errorText = '';
|
|
203
|
+
try {
|
|
204
|
+
errorText = await response.text();
|
|
205
|
+
log('node.errorResponseBody', debug, { body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') }, COMPONENTS.NODE_FETCH);
|
|
206
|
+
}
|
|
207
|
+
catch (textError) {
|
|
208
|
+
log('node.errorReadingBody', debug, { error: String(textError) }, COMPONENTS.NODE_FETCH);
|
|
209
|
+
}
|
|
210
|
+
// 创建错误对象 (Create error object)
|
|
211
|
+
const error = new Error(`HTTP Error ${response.status}: ${response.statusText}`);
|
|
212
|
+
error.status = response.status;
|
|
213
|
+
error.statusText = response.statusText;
|
|
214
|
+
error.body = errorText;
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 处理请求错误 (Handle request error)
|
|
219
|
+
* @param error 错误对象 (Error object)
|
|
220
|
+
* @param fetchStart 请求开始时间 (Request start time)
|
|
221
|
+
* @param timeout 超时时间 (Timeout)
|
|
222
|
+
* @param redirectCount 重定向计数 (Redirect count)
|
|
223
|
+
* @param maxRedirects 最大重定向次数 (Maximum redirects)
|
|
224
|
+
* @param debug 是否启用调试模式 (Whether debug mode is enabled)
|
|
225
|
+
* @returns Promise<never> 抛出错误 (Throws error)
|
|
226
|
+
*/
|
|
227
|
+
static handleRequestError(error, fetchStart, timeout, redirectCount, maxRedirects, debug) {
|
|
228
|
+
// 记录错误 (Log error)
|
|
229
|
+
log('node.fetchError', debug, { error: String(error) }, COMPONENTS.NODE_FETCH);
|
|
230
|
+
// 处理超时错误 (Handle timeout error)
|
|
231
|
+
if (error.name === 'AbortError') {
|
|
232
|
+
log('node.requestAborted', debug, { duration: Date.now() - fetchStart }, COMPONENTS.NODE_FETCH);
|
|
233
|
+
const timeoutError = new Error(`Request timeout after ${timeout}ms`);
|
|
234
|
+
timeoutError.code = 'ETIMEDOUT';
|
|
235
|
+
timeoutError.timeout = timeout;
|
|
236
|
+
throw timeoutError;
|
|
237
|
+
}
|
|
238
|
+
// 处理网络错误 (Handle network error)
|
|
239
|
+
if (error.code) {
|
|
240
|
+
log('node.networkError', debug, { code: error.code || error.name }, COMPONENTS.NODE_FETCH);
|
|
241
|
+
const networkError = new Error(`Network error: ${error.code || error.message}`);
|
|
242
|
+
networkError.code = error.code;
|
|
243
|
+
networkError.originalError = error;
|
|
244
|
+
throw networkError;
|
|
245
|
+
}
|
|
246
|
+
// 处理重定向错误 (Handle redirect error)
|
|
247
|
+
if (redirectCount >= maxRedirects) {
|
|
248
|
+
log('node.tooManyRedirects', debug, { redirects: maxRedirects }, COMPONENTS.NODE_FETCH);
|
|
249
|
+
const redirectError = new Error(`Too many redirects (${maxRedirects})`);
|
|
250
|
+
redirectError.code = 'EMAXREDIRECTS';
|
|
251
|
+
redirectError.redirects = redirectCount;
|
|
252
|
+
throw redirectError;
|
|
253
|
+
}
|
|
254
|
+
// 重新抛出其他错误 (Rethrow other errors)
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 执行带重定向处理的HTTP请求 (Execute HTTP request with redirect handling)
|
|
259
|
+
* 支持自动处理重定向、超时和代理 (Supports automatic handling of redirects, timeouts, and proxies)
|
|
130
260
|
*/
|
|
131
261
|
static async _fetchWithRedirects({ url, headers = {}, proxy, noDelay, timeout = 30000, // 默认30秒超时
|
|
132
262
|
maxRedirects = 10, // 最大重定向次数
|
|
@@ -152,20 +282,12 @@ export class NodeFetcher {
|
|
|
152
282
|
// 处理重定向循环 (Handle redirect loop)
|
|
153
283
|
while (redirectCount < maxRedirects) {
|
|
154
284
|
log('node.fetchingUrl', debug, { url: currentUrl, redirect: redirectCount }, COMPONENTS.NODE_FETCH);
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
await this.randomDelay();
|
|
158
|
-
}
|
|
285
|
+
// 处理延迟 (Handle delay)
|
|
286
|
+
await this.handleDelay(noDelay, redirectCount > 0, debug);
|
|
159
287
|
// 准备请求头 (Prepare request headers)
|
|
160
|
-
const requestHeaders =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// 添加随机User-Agent (Add random User-Agent)
|
|
164
|
-
if (!requestHeaders['User-Agent']) {
|
|
165
|
-
const userAgent = this.getRandomUserAgent();
|
|
166
|
-
requestHeaders['User-Agent'] = userAgent;
|
|
167
|
-
log('node.usingUserAgent', debug, { userAgent }, COMPONENTS.NODE_FETCH);
|
|
168
|
-
}
|
|
288
|
+
const requestHeaders = this.prepareHeaders(headers, debug);
|
|
289
|
+
// 设置代理 (Set proxy)
|
|
290
|
+
const agent = this.setupProxy(currentUrl, finalProxy, debug);
|
|
169
291
|
// 准备请求选项 (Prepare request options)
|
|
170
292
|
const fetchOptions = {
|
|
171
293
|
method: 'GET',
|
|
@@ -173,17 +295,7 @@ export class NodeFetcher {
|
|
|
173
295
|
timeout,
|
|
174
296
|
signal: controller.signal,
|
|
175
297
|
};
|
|
176
|
-
|
|
177
|
-
let agent = undefined;
|
|
178
|
-
if (finalProxy) {
|
|
179
|
-
if (currentUrl.startsWith('https://')) {
|
|
180
|
-
agent = new HttpsProxyAgent(finalProxy);
|
|
181
|
-
log('node.usingHttpsProxy', debug, {}, COMPONENTS.NODE_FETCH);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
agent = new HttpProxyAgent(finalProxy);
|
|
185
|
-
log('node.usingHttpProxy', debug, {}, COMPONENTS.NODE_FETCH);
|
|
186
|
-
}
|
|
298
|
+
if (agent) {
|
|
187
299
|
fetchOptions.agent = agent;
|
|
188
300
|
}
|
|
189
301
|
// 记录请求详情 (Log request details)
|
|
@@ -209,17 +321,7 @@ export class NodeFetcher {
|
|
|
209
321
|
const location = response.headers.get('location');
|
|
210
322
|
log('node.redirectingTo', debug, { location }, COMPONENTS.NODE_FETCH);
|
|
211
323
|
// 构建完整的重定向URL (Build complete redirect URL)
|
|
212
|
-
|
|
213
|
-
if (location.startsWith('/')) {
|
|
214
|
-
const urlObj = new URL(currentUrl);
|
|
215
|
-
redirectUrl = `${urlObj.protocol}//${urlObj.host}${location}`;
|
|
216
|
-
}
|
|
217
|
-
else if (!location.startsWith('http')) {
|
|
218
|
-
redirectUrl = new URL(location, currentUrl).toString();
|
|
219
|
-
}
|
|
220
|
-
log('node.constructedFullRedirectUrl', debug, { redirectUrl }, COMPONENTS.NODE_FETCH);
|
|
221
|
-
// 更新当前URL为重定向URL (Update current URL to redirect URL)
|
|
222
|
-
currentUrl = redirectUrl;
|
|
324
|
+
currentUrl = this.buildRedirectUrl(location, currentUrl, debug);
|
|
223
325
|
continue;
|
|
224
326
|
}
|
|
225
327
|
// 如果响应成功,返回响应 (If response is successful, return response)
|
|
@@ -229,58 +331,15 @@ export class NodeFetcher {
|
|
|
229
331
|
}
|
|
230
332
|
else {
|
|
231
333
|
// 处理错误响应 (Handle error response)
|
|
232
|
-
|
|
233
|
-
// 尝试读取错误响应体 (Try to read error response body)
|
|
234
|
-
let errorText = '';
|
|
235
|
-
try {
|
|
236
|
-
errorText = await response.text();
|
|
237
|
-
log('node.errorResponseBody', debug, { body: errorText.substring(0, 200) + (errorText.length > 200 ? '...' : '') }, COMPONENTS.NODE_FETCH);
|
|
238
|
-
}
|
|
239
|
-
catch (textError) {
|
|
240
|
-
log('node.errorReadingBody', debug, { error: String(textError) }, COMPONENTS.NODE_FETCH);
|
|
241
|
-
}
|
|
242
|
-
// 创建错误对象 (Create error object)
|
|
243
|
-
const error = new Error(`HTTP Error ${response.status}: ${response.statusText}`);
|
|
244
|
-
error.status = response.status;
|
|
245
|
-
error.statusText = response.statusText;
|
|
246
|
-
error.body = errorText;
|
|
247
|
-
throw error;
|
|
334
|
+
return await this.handleErrorResponse(response, debug);
|
|
248
335
|
}
|
|
249
336
|
}
|
|
250
337
|
// 如果达到最大重定向次数,抛出错误 (If maximum redirects reached, throw error)
|
|
251
338
|
throw new Error(`Too many redirects (${maxRedirects})`);
|
|
252
339
|
}
|
|
253
340
|
catch (error) {
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
// 记录错误 (Log error)
|
|
257
|
-
log('node.fetchError', debug, { error: String(error) }, COMPONENTS.NODE_FETCH);
|
|
258
|
-
// 处理超时错误 (Handle timeout error)
|
|
259
|
-
if (error.name === 'AbortError') {
|
|
260
|
-
log('node.requestAborted', debug, { duration: Date.now() - fetchStart }, COMPONENTS.NODE_FETCH);
|
|
261
|
-
const timeoutError = new Error(`Request timeout after ${timeout}ms`);
|
|
262
|
-
timeoutError.code = 'ETIMEDOUT';
|
|
263
|
-
timeoutError.timeout = timeout;
|
|
264
|
-
throw timeoutError;
|
|
265
|
-
}
|
|
266
|
-
// 处理网络错误 (Handle network error)
|
|
267
|
-
if (error.code) {
|
|
268
|
-
log('node.networkError', debug, { code: error.code || error.name }, COMPONENTS.NODE_FETCH);
|
|
269
|
-
const networkError = new Error(`Network error: ${error.code || error.message}`);
|
|
270
|
-
networkError.code = error.code;
|
|
271
|
-
networkError.originalError = error;
|
|
272
|
-
throw networkError;
|
|
273
|
-
}
|
|
274
|
-
// 处理重定向错误 (Handle redirect error)
|
|
275
|
-
if (redirectCount >= maxRedirects) {
|
|
276
|
-
log('node.tooManyRedirects', debug, { redirects: maxRedirects }, COMPONENTS.NODE_FETCH);
|
|
277
|
-
const redirectError = new Error(`Too many redirects (${maxRedirects})`);
|
|
278
|
-
redirectError.code = 'EMAXREDIRECTS';
|
|
279
|
-
redirectError.redirects = redirectCount;
|
|
280
|
-
throw redirectError;
|
|
281
|
-
}
|
|
282
|
-
// 重新抛出其他错误 (Rethrow other errors)
|
|
283
|
-
throw error;
|
|
341
|
+
// 处理请求错误 (Handle request error)
|
|
342
|
+
this.handleRequestError(error, fetchStart, timeout, redirectCount, maxRedirects, debug);
|
|
284
343
|
}
|
|
285
344
|
finally {
|
|
286
345
|
// 清除超时定时器 (Clear timeout timer)
|
|
@@ -373,14 +432,14 @@ export class NodeFetcher {
|
|
|
373
432
|
*/
|
|
374
433
|
static async txt(requestPayload) {
|
|
375
434
|
const { debug = false } = requestPayload;
|
|
376
|
-
log('
|
|
435
|
+
log('node.startingTxtFetch', debug, {}, COMPONENTS.NODE_FETCH);
|
|
377
436
|
try {
|
|
378
437
|
// 执行请求 (Execute request)
|
|
379
438
|
const response = await this._fetchWithRedirects(requestPayload);
|
|
380
439
|
// 读取响应文本 (Read response text)
|
|
381
|
-
log('
|
|
440
|
+
log('node.readingText', debug, {}, COMPONENTS.NODE_FETCH);
|
|
382
441
|
const text = await response.text();
|
|
383
|
-
log('
|
|
442
|
+
log('node.textContentLength', debug, { length: text.length }, COMPONENTS.NODE_FETCH);
|
|
384
443
|
// 返回纯文本内容 (Return plain text content)
|
|
385
444
|
return {
|
|
386
445
|
text,
|
|
@@ -401,21 +460,21 @@ export class NodeFetcher {
|
|
|
401
460
|
*/
|
|
402
461
|
static async markdown(requestPayload) {
|
|
403
462
|
const { debug = false } = requestPayload;
|
|
404
|
-
log('
|
|
463
|
+
log('node.startingMarkdownFetch', debug, {}, COMPONENTS.NODE_FETCH);
|
|
405
464
|
try {
|
|
406
465
|
// 执行请求 (Execute request)
|
|
407
466
|
const response = await this._fetchWithRedirects(requestPayload);
|
|
408
467
|
// 读取响应文本 (Read response text)
|
|
409
|
-
log('
|
|
468
|
+
log('node.readingText', debug, {}, COMPONENTS.NODE_FETCH);
|
|
410
469
|
const html = await response.text();
|
|
411
|
-
log('
|
|
470
|
+
log('node.htmlContentLength', debug, { length: html.length }, COMPONENTS.NODE_FETCH);
|
|
412
471
|
// 创建Turndown服务 (Create Turndown service)
|
|
413
|
-
log('
|
|
472
|
+
log('node.creatingTurndown', debug, {}, COMPONENTS.NODE_FETCH);
|
|
414
473
|
const turndownService = new TurndownService();
|
|
415
474
|
// 将HTML转换为Markdown (Convert HTML to Markdown)
|
|
416
|
-
log('
|
|
475
|
+
log('node.convertingToMarkdown', debug, {}, COMPONENTS.NODE_FETCH);
|
|
417
476
|
const markdown = turndownService.turndown(html);
|
|
418
|
-
log('
|
|
477
|
+
log('node.markdownContentLength', debug, { length: markdown.length }, COMPONENTS.NODE_FETCH);
|
|
419
478
|
// 返回Markdown内容 (Return Markdown content)
|
|
420
479
|
return {
|
|
421
480
|
markdown,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import { Fetcher } from './fetchers/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* 获取HTML内容 (Get HTML content)
|
|
9
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
10
|
+
* @returns HTML内容 (HTML content)
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchHtml(requestPayload) {
|
|
13
|
+
return await Fetcher.html(requestPayload);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 获取JSON内容 (Get JSON content)
|
|
17
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
18
|
+
* @returns JSON内容 (JSON content)
|
|
19
|
+
*/
|
|
20
|
+
export async function fetchJson(requestPayload) {
|
|
21
|
+
return await Fetcher.json(requestPayload);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 获取纯文本内容 (Get plain text content)
|
|
25
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
26
|
+
* @returns 纯文本内容 (Plain text content)
|
|
27
|
+
*/
|
|
28
|
+
export async function fetchTxt(requestPayload) {
|
|
29
|
+
return await Fetcher.txt(requestPayload);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 获取Markdown内容 (Get Markdown content)
|
|
33
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
34
|
+
* @returns Markdown内容 (Markdown content)
|
|
35
|
+
*/
|
|
36
|
+
export async function fetchMarkdown(requestPayload) {
|
|
37
|
+
return await Fetcher.markdown(requestPayload);
|
|
38
|
+
}
|
|
39
|
+
// 导出所有类型和类 (Export all types and classes)
|
|
40
|
+
export * from './fetchers/index.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import { BrowserFetcher } from './browser/BrowserFetcher.js';
|
|
7
|
+
import { NodeFetcher } from './node/NodeFetcher.js';
|
|
8
|
+
import { log, COMPONENTS } from '../logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* 抓取器工厂类 (Fetcher factory class)
|
|
11
|
+
* 根据请求参数创建合适的抓取器实例 (Create appropriate fetcher instance based on request parameters)
|
|
12
|
+
*/
|
|
13
|
+
export class FetcherFactory {
|
|
14
|
+
/**
|
|
15
|
+
* 创建抓取器实例 (Create fetcher instance)
|
|
16
|
+
* @param requestPayload 请求参数 (Request parameters)
|
|
17
|
+
* @returns 抓取器实例 (Fetcher instance)
|
|
18
|
+
*/
|
|
19
|
+
static createFetcher(requestPayload) {
|
|
20
|
+
const { useBrowser = false, useNodeFetch = false, autoDetectMode = true, debug = false } = requestPayload;
|
|
21
|
+
// 如果明确指定使用浏览器模式 (If browser mode is explicitly specified)
|
|
22
|
+
if (useBrowser) {
|
|
23
|
+
log('factory.using_browser_mode', debug, {}, COMPONENTS.FETCHER_FACTORY);
|
|
24
|
+
return new BrowserFetcher();
|
|
25
|
+
}
|
|
26
|
+
// 如果明确指定使用Node模式 (If Node mode is explicitly specified)
|
|
27
|
+
if (useNodeFetch) {
|
|
28
|
+
log('factory.using_node_mode', debug, {}, COMPONENTS.FETCHER_FACTORY);
|
|
29
|
+
return new NodeFetcher();
|
|
30
|
+
}
|
|
31
|
+
// 如果启用自动检测模式 (If auto-detect mode is enabled)
|
|
32
|
+
if (autoDetectMode) {
|
|
33
|
+
// 这里可以添加更复杂的逻辑来决定使用哪种模式
|
|
34
|
+
// 例如,根据URL特征、请求头等判断
|
|
35
|
+
// 目前简单地默认使用Node模式,因为它更轻量级
|
|
36
|
+
log('factory.auto_detecting_mode', debug, { defaultToNode: true }, COMPONENTS.FETCHER_FACTORY);
|
|
37
|
+
return new NodeFetcher();
|
|
38
|
+
}
|
|
39
|
+
// 默认使用Node模式 (Default to Node mode)
|
|
40
|
+
log('factory.defaultingToNodeMode', debug, {}, COMPONENTS.FETCHER_FACTORY);
|
|
41
|
+
return new NodeFetcher();
|
|
42
|
+
}
|
|
43
|
+
}
|