@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.
Files changed (136) hide show
  1. package/README.md +11 -9
  2. package/README.zh.md +19 -10
  3. package/dist/i18n-test-report.json +8 -0
  4. package/dist/i18n-unused-keys-report.json +8 -0
  5. package/dist/src/client.js +75 -62
  6. package/dist/src/index.js +0 -0
  7. package/dist/src/lib/BrowserFetcher.js +12 -12
  8. package/dist/src/lib/NodeFetcher.js +165 -106
  9. package/dist/src/lib/fetch.js +40 -0
  10. package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
  11. package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
  12. package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
  13. package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
  14. package/dist/src/lib/fetchers/common/types.js +41 -0
  15. package/dist/src/lib/fetchers/common/utils.js +141 -0
  16. package/dist/src/lib/fetchers/index.js +62 -0
  17. package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
  18. package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
  19. package/dist/src/lib/i18n/index.js +105 -17
  20. package/dist/src/lib/i18n/keys/base.js +41 -0
  21. package/dist/src/lib/i18n/keys/browser.js +91 -0
  22. package/dist/src/lib/i18n/keys/client.js +68 -0
  23. package/dist/src/lib/i18n/keys/errors.js +49 -0
  24. package/dist/src/lib/i18n/keys/factory.js +46 -0
  25. package/dist/src/lib/i18n/keys/fetcher.js +87 -0
  26. package/dist/src/lib/i18n/keys/index.js +16 -0
  27. package/dist/src/lib/i18n/keys/node.js +69 -0
  28. package/dist/src/lib/i18n/keys/prompts.js +79 -0
  29. package/dist/src/lib/i18n/keys/resources.js +53 -0
  30. package/dist/src/lib/i18n/keys/server.js +67 -0
  31. package/dist/src/lib/i18n/keys/tools.js +39 -0
  32. package/dist/src/lib/i18n/keys.js +504 -0
  33. package/dist/src/lib/i18n/locales/en/browser.js +79 -55
  34. package/dist/src/lib/i18n/locales/en/client.js +57 -33
  35. package/dist/src/lib/i18n/locales/en/errors.js +36 -12
  36. package/dist/src/lib/i18n/locales/en/factory.js +38 -0
  37. package/dist/src/lib/i18n/locales/en/fetcher.js +67 -60
  38. package/dist/src/lib/i18n/locales/en/index.js +9 -7
  39. package/dist/src/lib/i18n/locales/en/node.js +57 -35
  40. package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
  41. package/dist/src/lib/i18n/locales/en/resources.js +52 -40
  42. package/dist/src/lib/i18n/locales/en/server.js +49 -24
  43. package/dist/src/lib/i18n/locales/en/tools.js +27 -19
  44. package/dist/src/lib/i18n/locales/zh/browser.js +79 -56
  45. package/dist/src/lib/i18n/locales/zh/client.js +57 -32
  46. package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
  47. package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
  48. package/dist/src/lib/i18n/locales/zh/fetcher.js +67 -60
  49. package/dist/src/lib/i18n/locales/zh/index.js +9 -7
  50. package/dist/src/lib/i18n/locales/zh/node.js +61 -36
  51. package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
  52. package/dist/src/lib/i18n/locales/zh/resources.js +51 -39
  53. package/dist/src/lib/i18n/locales/zh/server.js +51 -25
  54. package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
  55. package/dist/src/lib/i18n/logger.js +90 -19
  56. package/dist/src/lib/logger.js +135 -14
  57. package/dist/src/lib/server/browser.js +12 -18
  58. package/dist/src/lib/server/fetcher.js +13 -34
  59. package/dist/src/lib/server/index.js +2 -2
  60. package/dist/src/lib/server/prompts.js +1 -1
  61. package/dist/src/lib/server/resources.js +1 -1
  62. package/dist/src/lib/server/tools.js +404 -308
  63. package/dist/src/lib/utils/ErrorHandler.js +125 -0
  64. package/dist/src/lib/utils/errorDetection.js +41 -0
  65. package/dist/src/lib/utils/errors.js +279 -0
  66. package/dist/src/mcp-server.js +0 -0
  67. package/dist/src/test-i18n.js +139 -0
  68. package/dist/tests/BrowserFetcher.test.js +951 -0
  69. package/dist/tests/NodeFetcher.test.js +263 -0
  70. package/dist/tests/client.test.js +211 -0
  71. package/dist/tests/fetch.test.js +70 -0
  72. package/dist/tests/fetchers/Fetcher.test.js +108 -0
  73. package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
  74. package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
  75. package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
  76. package/dist/tests/i18n-missing-keys.js +364 -0
  77. package/dist/tests/i18n-remove-unused-keys.js +236 -0
  78. package/dist/tests/i18n-test-report.json +2004 -0
  79. package/dist/tests/i18n-unused-keys.js +425 -0
  80. package/dist/tests/i18n.test.js +178 -0
  81. package/dist/tests/logger.test.js +100 -0
  82. package/dist/tests/mcp-server.test.js +113 -0
  83. package/dist/tests/server/browser.test.js +201 -0
  84. package/dist/tests/server/fetcher.test.js +173 -0
  85. package/dist/tests/server/index.test.js +122 -0
  86. package/dist/tests/server/prompts.test.js +246 -0
  87. package/dist/tests/server/resources.test.js +395 -0
  88. package/dist/tests/server/tools.test.js +223 -0
  89. package/dist/tests/setup.js +36 -0
  90. package/dist/tests/src/lib/i18n/index.js +108 -0
  91. package/dist/tests/src/lib/i18n/keys/base.js +47 -0
  92. package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
  93. package/dist/tests/src/lib/i18n/keys/client.js +70 -0
  94. package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
  95. package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
  96. package/dist/tests/src/lib/i18n/keys/index.js +31 -0
  97. package/dist/tests/src/lib/i18n/keys/node.js +56 -0
  98. package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
  99. package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
  100. package/dist/tests/src/lib/i18n/keys/server.js +64 -0
  101. package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
  102. package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
  103. package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
  104. package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
  105. package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
  106. package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
  107. package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
  108. package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
  109. package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
  110. package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
  111. package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
  112. package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
  113. package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
  114. package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
  115. package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
  116. package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
  117. package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
  118. package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
  119. package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
  120. package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
  121. package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
  122. package/dist/tests/src/lib/i18n/logger.js +114 -0
  123. package/dist/tests/src/lib/logger.js +181 -0
  124. package/dist/tests/test-direct-client.js +0 -0
  125. package/dist/tests/test-i18n.js +530 -0
  126. package/dist/tests/test-mcp-methods.js +1 -1
  127. package/dist/tests/test-mcp.js +0 -0
  128. package/dist/tests/test-mini4k.js +0 -0
  129. package/dist/tests/tests/test-i18n.js +588 -0
  130. package/dist/tests/types.test.js +57 -0
  131. package/dist/tests/utils/ErrorHandler.test.js +144 -0
  132. package/dist/vitest.config.js +29 -0
  133. package/package.json +32 -19
  134. package/dist/src/cli-language.js +0 -78
  135. package/dist/src/lib/example.js +0 -93
  136. 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('fetcher.checkingProxyEnv', true, {}, COMPONENTS.NODE_FETCH);
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('fetcher.envVarValue', true, {
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('fetcher.foundSystemProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
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('fetcher.checkingSystemEnvVars', true, { platform }, COMPONENTS.NODE_FETCH);
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('fetcher.windowsEnvVars', true, { output: setOutput.trim() }, COMPONENTS.NODE_FETCH);
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('fetcher.foundWindowsEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
86
+ log('node.foundWindowsEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
87
87
  }
88
88
  }
89
89
  catch (winError) {
90
- log('fetcher.errorGettingWindowsEnvVars', true, { error: String(winError) }, COMPONENTS.NODE_FETCH);
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('fetcher.unixEnvVars', true, { output: envOutput.length > 200 ? envOutput.substring(0, 200) + '...' : envOutput }, COMPONENTS.NODE_FETCH);
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('fetcher.foundUnixEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
103
+ log('node.foundUnixEnvProxy', true, { proxy: proxyUrl }, COMPONENTS.NODE_FETCH);
104
104
  }
105
105
  }
106
106
  catch (unixError) {
107
- log('fetcher.errorGettingUnixEnvVars', true, { error: String(unixError) }, COMPONENTS.NODE_FETCH);
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('fetcher.noSystemProxyFound', true, {}, COMPONENTS.NODE_FETCH);
114
+ log('node.noSystemProxyFound', true, {}, COMPONENTS.NODE_FETCH);
115
115
  }
116
116
  catch (error) {
117
- log('fetcher.errorGettingSystemEnvVars', true, { error: String(error) }, COMPONENTS.NODE_FETCH);
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('fetcher.foundNoProxy', true, { noProxy }, COMPONENTS.NODE_FETCH);
122
+ log('node.foundNoProxy', true, { noProxy }, COMPONENTS.NODE_FETCH);
123
123
  }
124
124
  return undefined;
125
125
  }
126
126
  /**
127
- * 执行带重定向处理的HTTP请求 (Perform HTTP request with redirect handling)
128
- * @param requestPayload 请求参数 (Request parameters)
129
- * @returns 响应数据 (Response data)
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
- // 如果启用了随机延迟且不是第一个请求,则添加延迟 (Add delay if random delay is enabled and not the first request)
156
- if (!noDelay && redirectCount > 0) {
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
- ...headers
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
- // 设置代理 (Set proxy)
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
- let redirectUrl = location;
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
- log('node.errorResponse', debug, { status: response.status, statusText: response.statusText }, COMPONENTS.NODE_FETCH);
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
- // 清除超时定时器 (Clear timeout timer)
255
- clearTimeout(timeoutId);
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('fetcher.startingTxtFetch', debug, {}, COMPONENTS.NODE_FETCH);
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('fetcher.readingText', debug, {}, COMPONENTS.NODE_FETCH);
440
+ log('node.readingText', debug, {}, COMPONENTS.NODE_FETCH);
382
441
  const text = await response.text();
383
- log('fetcher.textContentLength', debug, { length: text.length }, COMPONENTS.NODE_FETCH);
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('fetcher.startingMarkdownFetch', debug, {}, COMPONENTS.NODE_FETCH);
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('fetcher.readingText', debug, {}, COMPONENTS.NODE_FETCH);
468
+ log('node.readingText', debug, {}, COMPONENTS.NODE_FETCH);
410
469
  const html = await response.text();
411
- log('fetcher.htmlContentLength', debug, { length: html.length }, COMPONENTS.NODE_FETCH);
470
+ log('node.htmlContentLength', debug, { length: html.length }, COMPONENTS.NODE_FETCH);
412
471
  // 创建Turndown服务 (Create Turndown service)
413
- log('fetcher.creatingTurndown', debug, {}, COMPONENTS.NODE_FETCH);
472
+ log('node.creatingTurndown', debug, {}, COMPONENTS.NODE_FETCH);
414
473
  const turndownService = new TurndownService();
415
474
  // 将HTML转换为Markdown (Convert HTML to Markdown)
416
- log('fetcher.convertingToMarkdown', debug, {}, COMPONENTS.NODE_FETCH);
475
+ log('node.convertingToMarkdown', debug, {}, COMPONENTS.NODE_FETCH);
417
476
  const markdown = turndownService.turndown(html);
418
- log('fetcher.markdownContentLength', debug, { length: markdown.length }, COMPONENTS.NODE_FETCH);
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
+ }