recker 1.0.79 → 1.0.80-next.70ea84d
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/browser/browser/mini.d.ts +2 -2
- package/dist/browser/browser/recker-mini.d.ts +8 -8
- package/dist/browser/browser/recker.d.ts +11 -8
- package/dist/browser/browser/recker.js +54 -6
- package/dist/browser/core/client.d.ts +15 -10
- package/dist/browser/core/client.js +54 -38
- package/dist/browser/index.iife.min.js +129 -130
- package/dist/browser/index.min.js +129 -130
- package/dist/browser/index.mini.iife.js +5697 -636
- package/dist/browser/index.mini.iife.min.js +47 -48
- package/dist/browser/index.mini.min.js +47 -48
- package/dist/browser/index.mini.umd.js +5697 -636
- package/dist/browser/index.mini.umd.min.js +47 -48
- package/dist/browser/index.umd.min.js +129 -130
- package/dist/browser/mini.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.js +6 -28
- package/dist/browser/recker-mini.d.ts +8 -8
- package/dist/browser/recker.d.ts +11 -8
- package/dist/browser/recker.js +54 -6
- package/dist/browser/scrape/document.js +2 -2
- package/dist/browser/scrape/element.js +7 -1
- package/dist/browser/scrape/parser/nodes/html.js +1 -1
- package/dist/browser/scrape/spider.d.ts +52 -0
- package/dist/browser/scrape/spider.js +620 -38
- package/dist/browser/scrape/types.d.ts +2 -0
- package/dist/browser/search/google.d.ts +26 -1
- package/dist/browser/search/google.js +427 -45
- package/dist/browser/seo/analyzer.d.ts +1 -0
- package/dist/browser/seo/analyzer.js +144 -1
- package/dist/browser/seo/index.d.ts +1 -1
- package/dist/browser/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/browser/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/browser/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/browser/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/browser/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/browser/seo/keyword-campaign-shared.js +59 -0
- package/dist/browser/seo/keyword-campaign.d.ts +4 -107
- package/dist/browser/seo/keyword-campaign.js +2 -380
- package/dist/browser/seo/keywords.js +5 -22
- package/dist/browser/seo/types.d.ts +19 -0
- package/dist/browser/transport/curl.d.ts +5 -1
- package/dist/browser/transport/curl.js +207 -50
- package/dist/browser/transport/undici.d.ts +4 -3
- package/dist/browser/transport/undici.js +123 -49
- package/dist/browser/types/index.d.ts +9 -3
- package/dist/browser/utils/binary-manager.js +26 -3
- package/dist/browser/utils/block-detector.d.ts +8 -0
- package/dist/browser/utils/block-detector.js +542 -7
- package/dist/cli/commands/hls-runner.js +5 -4
- package/dist/cli/commands/live-runner.js +5 -4
- package/dist/cli/commands/loadtest-runner.js +3 -2
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +105 -0
- package/dist/cli/commands/seo-runner.js +9 -7
- package/dist/cli/commands/seo.js +140 -1
- package/dist/cli/commands/serve.js +75 -131
- package/dist/cli/commands/server-runner.js +59 -82
- package/dist/cli/commands/spider-runner.d.ts +37 -1
- package/dist/cli/commands/spider-runner.js +134 -10
- package/dist/cli/commands/spider.d.ts +18 -1
- package/dist/cli/commands/spider.js +457 -27
- package/dist/cli/events/handlers/cli.js +30 -1
- package/dist/cli/events/handlers/tui.js +26 -0
- package/dist/cli/events/types.d.ts +27 -0
- package/dist/cli/handler.d.ts +2 -12
- package/dist/cli/handler.js +20 -15
- package/dist/cli/handlers/protocols.js +39 -12
- package/dist/cli/handlers/search.d.ts +2 -0
- package/dist/cli/handlers/search.js +171 -0
- package/dist/cli/handlers/seo-analyze.d.ts +1 -0
- package/dist/cli/handlers/seo-analyze.js +666 -0
- package/dist/cli/handlers/seo-robots.d.ts +1 -0
- package/dist/cli/handlers/seo-robots.js +76 -0
- package/dist/cli/handlers/seo-serp.d.ts +54 -0
- package/dist/cli/handlers/seo-serp.js +243 -0
- package/dist/cli/handlers/seo-sitemap.d.ts +1 -0
- package/dist/cli/handlers/seo-sitemap.js +55 -0
- package/dist/cli/handlers/seo-spider.d.ts +1 -0
- package/dist/cli/handlers/seo-spider.js +334 -0
- package/dist/cli/handlers/seo.d.ts +8 -4
- package/dist/cli/handlers/seo.js +294 -442
- package/dist/cli/handlers/spider.js +94 -17
- package/dist/cli/handlers/streaming.js +5 -1
- package/dist/cli/index.js +11 -2
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +15 -4
- package/dist/cli/tui/app.js +6 -1
- package/dist/cli/tui/components/rich-response.d.ts +72 -0
- package/dist/cli/tui/components/rich-response.js +117 -0
- package/dist/cli/tui/executor-commands/background.js +30 -24
- package/dist/cli/tui/executor-commands/testing.js +3 -2
- package/dist/cli/tui/hooks/useDomains.d.ts +17 -0
- package/dist/cli/tui/hooks/useHelp.js +15 -2
- package/dist/cli/tui/job-manager.d.ts +4 -4
- package/dist/cli/tui/job-manager.js +5 -1
- package/dist/cli/tui/spider-tui.d.ts +63 -0
- package/dist/cli/tui/spider-tui.js +120 -2
- package/dist/cli/types.d.ts +12 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils/option-helpers.d.ts +10 -0
- package/dist/cli/utils/option-helpers.js +63 -0
- package/dist/cli/utils/score-color.d.ts +11 -0
- package/dist/cli/utils/score-color.js +11 -0
- package/dist/cli/utils/serp-campaign.d.ts +53 -0
- package/dist/cli/utils/serp-campaign.js +53 -0
- package/dist/cli/utils/serp-config.d.ts +26 -0
- package/dist/cli/utils/serp-config.js +125 -0
- package/dist/core/client.d.ts +15 -10
- package/dist/core/client.js +54 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +35 -34
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +20 -2
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/profiles.js +5 -1
- package/dist/mcp/prompts/index.js +8 -4
- package/dist/mcp/resources/index.js +46 -23
- package/dist/mcp/server.js +9 -6
- package/dist/mcp/tools/protocols.js +9 -2
- package/dist/mcp/tools/registry.js +13 -3
- package/dist/mcp/tools/seo.js +427 -2
- package/dist/mcp/types.d.ts +5 -1
- package/dist/plugins/proxy-rotator.d.ts +2 -2
- package/dist/plugins/proxy-rotator.js +6 -28
- package/dist/raffel/client.d.ts +38 -0
- package/dist/raffel/client.js +282 -0
- package/dist/raffel/index.d.ts +2 -0
- package/dist/raffel/index.js +2 -0
- package/dist/raffel/types.d.ts +40 -0
- package/dist/raffel/types.js +14 -0
- package/dist/recker.d.ts +13 -7
- package/dist/recker.js +58 -6
- package/dist/scrape/document.js +2 -2
- package/dist/scrape/element.js +7 -1
- package/dist/scrape/parser/nodes/html.js +1 -1
- package/dist/scrape/spider.d.ts +52 -0
- package/dist/scrape/spider.js +620 -38
- package/dist/scrape/types.d.ts +2 -0
- package/dist/search/google.d.ts +26 -1
- package/dist/search/google.js +427 -45
- package/dist/search/index.d.ts +1 -1
- package/dist/seo/analyzer.d.ts +1 -0
- package/dist/seo/analyzer.js +144 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/seo/keyword-campaign-shared.js +59 -0
- package/dist/seo/keyword-campaign.d.ts +4 -107
- package/dist/seo/keyword-campaign.js +2 -380
- package/dist/seo/keywords.js +5 -22
- package/dist/seo/types.d.ts +19 -0
- package/dist/template/index.d.ts +1 -1
- package/dist/template/types.d.ts +0 -2
- package/dist/testing/index.d.ts +1 -22
- package/dist/testing/index.js +1 -11
- package/dist/transport/curl.d.ts +5 -1
- package/dist/transport/curl.js +207 -50
- package/dist/transport/undici.d.ts +4 -3
- package/dist/transport/undici.js +123 -49
- package/dist/types/index.d.ts +9 -3
- package/dist/utils/binary-manager.js +26 -3
- package/dist/utils/block-detector.d.ts +8 -0
- package/dist/utils/block-detector.js +542 -7
- package/dist/version.js +1 -1
- package/package.json +12 -1
- package/dist/testing/mock-dns-server.d.ts +0 -69
- package/dist/testing/mock-dns-server.js +0 -269
- package/dist/testing/mock-ftp-server.d.ts +0 -89
- package/dist/testing/mock-ftp-server.js +0 -562
- package/dist/testing/mock-hls-server.d.ts +0 -80
- package/dist/testing/mock-hls-server.js +0 -381
- package/dist/testing/mock-http-server.d.ts +0 -124
- package/dist/testing/mock-http-server.js +0 -343
- package/dist/testing/mock-proxy-server.d.ts +0 -108
- package/dist/testing/mock-proxy-server.js +0 -615
- package/dist/testing/mock-sse-server.d.ts +0 -76
- package/dist/testing/mock-sse-server.js +0 -291
- package/dist/testing/mock-telnet-server.d.ts +0 -59
- package/dist/testing/mock-telnet-server.js +0 -274
- package/dist/testing/mock-udp-server.d.ts +0 -43
- package/dist/testing/mock-udp-server.js +0 -188
- package/dist/testing/mock-websocket-server.d.ts +0 -76
- package/dist/testing/mock-websocket-server.js +0 -334
- package/dist/testing/mock-whois-server.d.ts +0 -56
- package/dist/testing/mock-whois-server.js +0 -234
- package/dist/testing/proxy-certs.d.ts +0 -19
- package/dist/testing/proxy-certs.js +0 -208
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CommandEmitter } from '../events/emitter.js';
|
|
2
|
+
import { AbortError, ValidationError } from '../../core/errors.js';
|
|
2
3
|
const DEFAULT_PORTS = {
|
|
3
4
|
http: 3000,
|
|
4
5
|
webhook: 3000,
|
|
@@ -56,7 +57,7 @@ export class ServerRunner extends CommandEmitter {
|
|
|
56
57
|
this.emitStart(`${type} server`, { port, host });
|
|
57
58
|
try {
|
|
58
59
|
if (signal?.aborted) {
|
|
59
|
-
throw new
|
|
60
|
+
throw new AbortError('Server start aborted');
|
|
60
61
|
}
|
|
61
62
|
const abortHandler = () => {
|
|
62
63
|
this.aborted = true;
|
|
@@ -104,7 +105,7 @@ export class ServerRunner extends CommandEmitter {
|
|
|
104
105
|
return result;
|
|
105
106
|
}
|
|
106
107
|
catch (err) {
|
|
107
|
-
if (signal?.aborted || this.aborted || err
|
|
108
|
+
if (signal?.aborted || this.aborted || err instanceof AbortError) {
|
|
108
109
|
this.emitError('Server stopped by user', { code: 'ABORT' });
|
|
109
110
|
return {
|
|
110
111
|
type,
|
|
@@ -145,121 +146,99 @@ export class ServerRunner extends CommandEmitter {
|
|
|
145
146
|
async createServer(type, options) {
|
|
146
147
|
switch (type) {
|
|
147
148
|
case 'http': {
|
|
148
|
-
const {
|
|
149
|
-
const server = await
|
|
149
|
+
const { createMockHttpServer } = await import('../../testing/index.js');
|
|
150
|
+
const server = await createMockHttpServer({
|
|
150
151
|
port: options.port,
|
|
151
152
|
host: options.host,
|
|
152
153
|
delay: options.delay,
|
|
153
154
|
cors: options.cors !== false,
|
|
154
155
|
});
|
|
155
156
|
if (options.echo) {
|
|
156
|
-
|
|
157
|
+
const echoHandler = (req) => ({
|
|
157
158
|
status: 200,
|
|
158
159
|
body: { method: req.method, path: req.path, query: req.query, headers: req.headers, body: req.body },
|
|
159
|
-
})
|
|
160
|
+
});
|
|
161
|
+
server.get('/*', echoHandler);
|
|
162
|
+
server.post('/*', echoHandler);
|
|
163
|
+
server.put('/*', echoHandler);
|
|
164
|
+
server.patch('/*', echoHandler);
|
|
165
|
+
server.delete('/*', echoHandler);
|
|
160
166
|
}
|
|
161
167
|
this.attachHttpEvents(server);
|
|
162
168
|
return server;
|
|
163
169
|
}
|
|
164
170
|
case 'webhook': {
|
|
165
|
-
const {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
const { createMockHttpServer } = await import('../../testing/index.js');
|
|
172
|
+
const webhookStatus = options.status || 204;
|
|
173
|
+
const webhookHandler = () => ({ status: webhookStatus });
|
|
174
|
+
const server = await createMockHttpServer({ port: options.port, host: options.host });
|
|
175
|
+
server.get('/*', webhookHandler);
|
|
176
|
+
server.post('/*', webhookHandler);
|
|
177
|
+
server.put('/*', webhookHandler);
|
|
178
|
+
server.patch('/*', webhookHandler);
|
|
179
|
+
server.delete('/*', webhookHandler);
|
|
172
180
|
this.attachHttpEvents(server);
|
|
173
181
|
return server;
|
|
174
182
|
}
|
|
175
183
|
case 'websocket': {
|
|
176
|
-
const {
|
|
177
|
-
const server = await
|
|
184
|
+
const { createMockWebSocketServer } = await import('../../testing/index.js');
|
|
185
|
+
const server = await createMockWebSocketServer({
|
|
178
186
|
port: options.port,
|
|
179
187
|
host: options.host,
|
|
180
|
-
echo: options.echo !== false,
|
|
181
|
-
delay: options.delay,
|
|
182
188
|
});
|
|
189
|
+
if (options.echo === false) {
|
|
190
|
+
server.setMessageHandler(() => undefined);
|
|
191
|
+
}
|
|
183
192
|
this.attachWebSocketEvents(server);
|
|
184
193
|
return server;
|
|
185
194
|
}
|
|
186
195
|
case 'sse': {
|
|
187
|
-
const {
|
|
188
|
-
const server = await
|
|
196
|
+
const { createMockSSEServer } = await import('../../testing/index.js');
|
|
197
|
+
const server = await createMockSSEServer({
|
|
189
198
|
port: options.port,
|
|
190
199
|
host: options.host,
|
|
191
200
|
path: options.path || '/events',
|
|
192
201
|
});
|
|
193
202
|
if (options.heartbeat) {
|
|
194
|
-
server.startPeriodicEvents('heartbeat', options.heartbeat);
|
|
203
|
+
server.startPeriodicEvents('heartbeat', options.heartbeat, () => ({ data: 'heartbeat', event: 'heartbeat' }));
|
|
195
204
|
}
|
|
196
205
|
this.attachSSEEvents(server);
|
|
197
206
|
return server;
|
|
198
207
|
}
|
|
199
208
|
case 'hls': {
|
|
200
|
-
const {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
baseUrl,
|
|
209
|
+
const { createMockHlsServer } = await import('../../testing/index.js');
|
|
210
|
+
const hlsServer = await createMockHlsServer({
|
|
211
|
+
port: options.port,
|
|
212
|
+
host: options.host,
|
|
205
213
|
mode: options.mode || 'vod',
|
|
206
214
|
segmentCount: options.segments || 10,
|
|
207
215
|
segmentDuration: options.duration || 6,
|
|
208
|
-
multiQuality: true,
|
|
209
|
-
});
|
|
210
|
-
const httpServer = http.createServer(async (req, res) => {
|
|
211
|
-
const url = `${baseUrl}${req.url}`;
|
|
212
|
-
this.stats.totalRequests++;
|
|
213
|
-
this.emit('data', {
|
|
214
|
-
type: 'data',
|
|
215
|
-
category: 'server:request',
|
|
216
|
-
timestamp: Date.now(),
|
|
217
|
-
command: 'server',
|
|
218
|
-
level: 'debug',
|
|
219
|
-
payload: { method: req.method, path: req.url },
|
|
220
|
-
});
|
|
221
|
-
try {
|
|
222
|
-
const response = await hlsServer.transport.dispatch({ url, method: req.method || 'GET' });
|
|
223
|
-
res.statusCode = response.status;
|
|
224
|
-
response.headers.forEach((value, key) => res.setHeader(key, value));
|
|
225
|
-
const body = await response.arrayBuffer();
|
|
226
|
-
res.end(Buffer.from(body));
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
res.statusCode = 404;
|
|
230
|
-
res.end('Not Found');
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
await new Promise((resolve) => {
|
|
234
|
-
httpServer.listen(options.port, options.host, () => resolve());
|
|
235
216
|
});
|
|
236
217
|
return {
|
|
237
|
-
port:
|
|
218
|
+
port: hlsServer.port,
|
|
238
219
|
address: options.host,
|
|
239
|
-
url: hlsServer.
|
|
240
|
-
isRunning:
|
|
241
|
-
stop:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
},
|
|
245
|
-
on: () => this,
|
|
246
|
-
emit: () => false,
|
|
220
|
+
url: hlsServer.masterUrl,
|
|
221
|
+
isRunning: hlsServer.isRunning,
|
|
222
|
+
stop: () => hlsServer.stop(),
|
|
223
|
+
on: hlsServer.on.bind(hlsServer),
|
|
224
|
+
emit: hlsServer.emit.bind(hlsServer),
|
|
247
225
|
};
|
|
248
226
|
}
|
|
249
227
|
case 'udp': {
|
|
250
|
-
const {
|
|
251
|
-
const server =
|
|
228
|
+
const { createMockUdpServer } = await import('../../testing/index.js');
|
|
229
|
+
const server = await createMockUdpServer({
|
|
252
230
|
port: options.port,
|
|
253
231
|
host: options.host,
|
|
254
|
-
echo: options.echo !== false,
|
|
255
232
|
});
|
|
256
|
-
|
|
233
|
+
if (options.echo === false) {
|
|
234
|
+
server.setMessageHandler(() => undefined);
|
|
235
|
+
}
|
|
257
236
|
this.attachUDPEvents(server);
|
|
258
237
|
return server;
|
|
259
238
|
}
|
|
260
239
|
case 'dns': {
|
|
261
|
-
const {
|
|
262
|
-
const server = await
|
|
240
|
+
const { createMockDnsServer } = await import('../../testing/index.js');
|
|
241
|
+
const server = await createMockDnsServer({
|
|
263
242
|
port: options.port,
|
|
264
243
|
host: options.host,
|
|
265
244
|
delay: options.delay,
|
|
@@ -268,41 +247,39 @@ export class ServerRunner extends CommandEmitter {
|
|
|
268
247
|
return server;
|
|
269
248
|
}
|
|
270
249
|
case 'whois': {
|
|
271
|
-
const {
|
|
272
|
-
const server = await
|
|
250
|
+
const { createMockWhoisServer } = await import('../../testing/index.js');
|
|
251
|
+
const server = await createMockWhoisServer({
|
|
273
252
|
port: options.port,
|
|
274
253
|
host: options.host,
|
|
275
|
-
|
|
254
|
+
responseDelay: options.delay,
|
|
276
255
|
});
|
|
277
256
|
this.attachWhoisEvents(server);
|
|
278
257
|
return server;
|
|
279
258
|
}
|
|
280
259
|
case 'telnet': {
|
|
281
|
-
const {
|
|
282
|
-
const server = await
|
|
260
|
+
const { createMockTelnetServer } = await import('../../testing/index.js');
|
|
261
|
+
const server = await createMockTelnetServer({
|
|
283
262
|
port: options.port,
|
|
284
263
|
host: options.host,
|
|
285
|
-
|
|
286
|
-
delay: options.delay,
|
|
264
|
+
responseDelay: options.delay,
|
|
287
265
|
});
|
|
288
266
|
this.attachTelnetEvents(server);
|
|
289
267
|
return server;
|
|
290
268
|
}
|
|
291
269
|
case 'ftp': {
|
|
292
|
-
const {
|
|
293
|
-
const server = await
|
|
270
|
+
const { createMockFtpServer } = await import('../../testing/index.js');
|
|
271
|
+
const server = await createMockFtpServer({
|
|
294
272
|
port: options.port,
|
|
295
273
|
host: options.host,
|
|
296
|
-
username: options.username || 'user',
|
|
297
|
-
password: options.password || 'pass',
|
|
298
|
-
anonymous: options.anonymous !== false,
|
|
299
|
-
delay: options.delay,
|
|
300
274
|
});
|
|
301
275
|
this.attachFTPEvents(server);
|
|
302
276
|
return server;
|
|
303
277
|
}
|
|
304
278
|
default:
|
|
305
|
-
throw new
|
|
279
|
+
throw new ValidationError(`Unknown server type: ${type}`, {
|
|
280
|
+
field: 'serverType',
|
|
281
|
+
value: type,
|
|
282
|
+
});
|
|
306
283
|
}
|
|
307
284
|
}
|
|
308
285
|
async waitForStop(signal) {
|
|
@@ -441,7 +418,7 @@ export class ServerRunner extends CommandEmitter {
|
|
|
441
418
|
command: 'server',
|
|
442
419
|
level: 'debug',
|
|
443
420
|
payload: {
|
|
444
|
-
domain: query.
|
|
421
|
+
domain: query.name,
|
|
445
422
|
recordType: query.type,
|
|
446
423
|
},
|
|
447
424
|
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { CommandEmitter } from '../events/emitter.js';
|
|
2
|
+
import type { SeoReport } from '../../seo/types.js';
|
|
3
|
+
import type { SpiderPageResult, SpiderTransport } from '../../scrape/spider.js';
|
|
4
|
+
import type { SpiderFocusMode } from '../utils/option-helpers.js';
|
|
2
5
|
export interface SpiderRunnerOptions {
|
|
3
6
|
depth?: number;
|
|
4
7
|
limit?: number;
|
|
@@ -6,7 +9,19 @@ export interface SpiderRunnerOptions {
|
|
|
6
9
|
robots?: boolean;
|
|
7
10
|
seo?: boolean;
|
|
8
11
|
useSitemap?: boolean;
|
|
9
|
-
|
|
12
|
+
transport?: SpiderTransport;
|
|
13
|
+
preferCurlFirst?: boolean;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
delay?: number;
|
|
16
|
+
maxRetryAttempts?: number;
|
|
17
|
+
baseRetryDelayMs?: number;
|
|
18
|
+
maxRetryDelayMs?: number;
|
|
19
|
+
retryBackoffMultiplier?: number;
|
|
20
|
+
retryJitterMs?: number;
|
|
21
|
+
maxDomainBlockStrikes?: number;
|
|
22
|
+
rotateUserAgent?: boolean;
|
|
23
|
+
randomizeHeaders?: boolean;
|
|
24
|
+
focus?: SpiderFocusMode;
|
|
10
25
|
signal?: AbortSignal;
|
|
11
26
|
extract?: string[];
|
|
12
27
|
include?: string[];
|
|
@@ -17,6 +32,19 @@ export interface SpiderRunnerResult {
|
|
|
17
32
|
pagesVisited: number;
|
|
18
33
|
duration: number;
|
|
19
34
|
errors: number;
|
|
35
|
+
security?: {
|
|
36
|
+
pages: number;
|
|
37
|
+
blockedPages: number;
|
|
38
|
+
captchaPages: number;
|
|
39
|
+
captchaProviders: Record<string, number>;
|
|
40
|
+
attempts: number;
|
|
41
|
+
retries: number;
|
|
42
|
+
transportUsage: Record<Exclude<SpiderTransport, 'auto'>, number>;
|
|
43
|
+
avgAttempts: number;
|
|
44
|
+
avgTtfbMs?: number;
|
|
45
|
+
avgTotalMs?: number;
|
|
46
|
+
avgDownloadMs?: number;
|
|
47
|
+
};
|
|
20
48
|
internalLinks: number;
|
|
21
49
|
externalLinks: number;
|
|
22
50
|
images: number;
|
|
@@ -29,10 +57,18 @@ export interface SpiderRunnerResult {
|
|
|
29
57
|
depth: number;
|
|
30
58
|
links: number;
|
|
31
59
|
duration: number;
|
|
60
|
+
security?: SpiderPageResult['security'];
|
|
61
|
+
timings?: SpiderPageResult['timings'];
|
|
62
|
+
resources?: {
|
|
63
|
+
images: number;
|
|
64
|
+
scripts: number;
|
|
65
|
+
stylesheets: number;
|
|
66
|
+
};
|
|
32
67
|
seoScore?: number;
|
|
33
68
|
seoGrade?: string;
|
|
34
69
|
seoErrors?: number;
|
|
35
70
|
seoWarnings?: number;
|
|
71
|
+
seoReport?: SeoReport;
|
|
36
72
|
extracted?: Record<string, unknown>;
|
|
37
73
|
}>;
|
|
38
74
|
extraction?: {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CommandEmitter } from '../events/emitter.js';
|
|
2
|
+
import { AbortError } from '../../core/errors.js';
|
|
2
3
|
export class SpiderRunner extends CommandEmitter {
|
|
3
4
|
constructor() {
|
|
4
5
|
super('spider');
|
|
5
6
|
}
|
|
6
7
|
async run(url, options = {}) {
|
|
7
|
-
const { depth = 5, limit = 100, concurrency = 5, robots = true, seo = false, focus = 'all', signal, extract, include, exclude, } = options;
|
|
8
|
+
const { depth = 5, limit = 100, concurrency = 5, robots = true, seo = false, focus = 'all', signal, extract, include, exclude, transport, preferCurlFirst, timeout, delay, maxRetryAttempts, baseRetryDelayMs, maxRetryDelayMs, retryBackoffMultiplier, retryJitterMs, maxDomainBlockStrikes, rotateUserAgent, randomizeHeaders, } = options;
|
|
8
9
|
let startUrl = url;
|
|
9
10
|
if (!startUrl.startsWith('http')) {
|
|
10
11
|
startUrl = `https://${startUrl}`;
|
|
@@ -17,9 +18,67 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
17
18
|
let scripts = 0;
|
|
18
19
|
let stylesheets = 0;
|
|
19
20
|
let errorCount = 0;
|
|
21
|
+
let blockedPages = 0;
|
|
22
|
+
let captchaPages = 0;
|
|
23
|
+
let captchaProviders = {};
|
|
24
|
+
let attemptsTotal = 0;
|
|
25
|
+
let retriesTotal = 0;
|
|
26
|
+
let transportUsage = { undici: 0, curl: 0 };
|
|
27
|
+
let timingSum = { ttfb: 0, total: 0, download: 0 };
|
|
28
|
+
let timingCount = 0;
|
|
29
|
+
const collectPageMetrics = (page) => {
|
|
30
|
+
const resources = page.resources;
|
|
31
|
+
if (resources) {
|
|
32
|
+
images += resources.images || 0;
|
|
33
|
+
scripts += resources.scripts || 0;
|
|
34
|
+
stylesheets += resources.stylesheets || 0;
|
|
35
|
+
}
|
|
36
|
+
if (page.security) {
|
|
37
|
+
if (page.security.blocked)
|
|
38
|
+
blockedPages += 1;
|
|
39
|
+
if (page.security.captchaDetected)
|
|
40
|
+
captchaPages += 1;
|
|
41
|
+
if (page.security.captchaDetected) {
|
|
42
|
+
const provider = (page.security.captchaProvider || '').trim().toLowerCase();
|
|
43
|
+
const key = provider.length > 0 ? provider : 'unknown';
|
|
44
|
+
captchaProviders[key] = (captchaProviders[key] || 0) + 1;
|
|
45
|
+
}
|
|
46
|
+
attemptsTotal += Number(page.security.attempts ?? 1);
|
|
47
|
+
retriesTotal += Number(page.security.retryCount ?? 0);
|
|
48
|
+
const transport = page.security.transport;
|
|
49
|
+
if (transport && transport !== 'auto') {
|
|
50
|
+
transportUsage[transport] += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
attemptsTotal += 1;
|
|
55
|
+
}
|
|
56
|
+
const ttfb = page.timings?.ttfb;
|
|
57
|
+
const total = page.timings?.total;
|
|
58
|
+
const download = page.timings?.download;
|
|
59
|
+
if (typeof ttfb === 'number' || typeof total === 'number' || typeof download === 'number') {
|
|
60
|
+
timingSum.ttfb += typeof ttfb === 'number' ? ttfb : 0;
|
|
61
|
+
timingSum.total += typeof total === 'number' ? total : 0;
|
|
62
|
+
timingSum.download += typeof download === 'number' ? download : 0;
|
|
63
|
+
timingCount += 1;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const getSecuritySummary = () => ({
|
|
67
|
+
pages: pages.length,
|
|
68
|
+
blockedPages,
|
|
69
|
+
captchaPages,
|
|
70
|
+
captchaProviders,
|
|
71
|
+
attempts: attemptsTotal,
|
|
72
|
+
retries: retriesTotal,
|
|
73
|
+
transportUsage,
|
|
74
|
+
avgAttempts: pages.length > 0 ? Number((attemptsTotal / pages.length).toFixed(2)) : 0,
|
|
75
|
+
avgTtfbMs: timingCount > 0 ? Math.round(timingSum.ttfb / timingCount) : undefined,
|
|
76
|
+
avgTotalMs: timingCount > 0 ? Math.round(timingSum.total / timingCount) : undefined,
|
|
77
|
+
avgDownloadMs: timingCount > 0 ? Math.round(timingSum.download / timingCount) : undefined,
|
|
78
|
+
});
|
|
20
79
|
try {
|
|
21
80
|
if (seo) {
|
|
22
|
-
return await this.runSeoSpider(startUrl, options, pages);
|
|
81
|
+
return await this.runSeoSpider(startUrl, options, pages, collectPageMetrics, getSecuritySummary);
|
|
23
82
|
}
|
|
24
83
|
const { Spider } = await import('../../scrape/spider.js');
|
|
25
84
|
const extractionByPage = {};
|
|
@@ -28,10 +87,23 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
28
87
|
maxPages: limit,
|
|
29
88
|
concurrency,
|
|
30
89
|
respectRobotsTxt: robots,
|
|
90
|
+
transport,
|
|
91
|
+
preferCurlFirst,
|
|
92
|
+
timeout,
|
|
93
|
+
delay,
|
|
94
|
+
maxRetryAttempts,
|
|
95
|
+
baseRetryDelayMs,
|
|
96
|
+
maxRetryDelayMs,
|
|
97
|
+
retryBackoffMultiplier,
|
|
98
|
+
retryJitterMs,
|
|
99
|
+
maxDomainBlockStrikes,
|
|
100
|
+
rotateUserAgent,
|
|
101
|
+
randomizeHeaders,
|
|
31
102
|
extract,
|
|
32
103
|
include: include?.map(p => new RegExp(p)),
|
|
33
104
|
exclude: exclude?.map(p => new RegExp(p)),
|
|
34
105
|
onPage: (page) => {
|
|
106
|
+
collectPageMetrics(page);
|
|
35
107
|
pages.push({
|
|
36
108
|
url: page.url,
|
|
37
109
|
status: page.status,
|
|
@@ -39,6 +111,9 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
39
111
|
depth: page.depth,
|
|
40
112
|
links: page.links?.length || 0,
|
|
41
113
|
duration: page.duration,
|
|
114
|
+
security: page.security,
|
|
115
|
+
timings: page.timings,
|
|
116
|
+
resources: page.resources,
|
|
42
117
|
extracted: page.extracted,
|
|
43
118
|
});
|
|
44
119
|
if (page.extracted && Object.keys(page.extracted).length > 0) {
|
|
@@ -70,9 +145,12 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
70
145
|
title: page.title,
|
|
71
146
|
links: page.links?.length || 0,
|
|
72
147
|
duration: page.duration,
|
|
148
|
+
security: page.security,
|
|
149
|
+
timings: page.timings,
|
|
150
|
+
resources: page.resources,
|
|
73
151
|
});
|
|
74
152
|
if (signal?.aborted) {
|
|
75
|
-
throw new
|
|
153
|
+
throw new AbortError('Spider crawl aborted');
|
|
76
154
|
}
|
|
77
155
|
},
|
|
78
156
|
onProgress: (progress) => {
|
|
@@ -121,6 +199,7 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
121
199
|
images,
|
|
122
200
|
scripts,
|
|
123
201
|
stylesheets,
|
|
202
|
+
security: getSecuritySummary(),
|
|
124
203
|
pages,
|
|
125
204
|
extraction,
|
|
126
205
|
};
|
|
@@ -134,24 +213,25 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
134
213
|
images,
|
|
135
214
|
scripts,
|
|
136
215
|
stylesheets,
|
|
216
|
+
security: getSecuritySummary(),
|
|
137
217
|
});
|
|
138
218
|
return summary;
|
|
139
219
|
}
|
|
140
220
|
catch (err) {
|
|
141
|
-
if (signal?.aborted || err
|
|
221
|
+
if (signal?.aborted || err instanceof AbortError) {
|
|
142
222
|
this.emitError('Spider stopped by user', { code: 'ABORT' });
|
|
143
223
|
}
|
|
144
224
|
else {
|
|
145
|
-
this.emitError(err.message
|
|
146
|
-
stack: err.stack,
|
|
225
|
+
this.emitError(err instanceof Error ? err.message : 'Spider failed', {
|
|
226
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
147
227
|
context: startUrl,
|
|
148
228
|
});
|
|
149
229
|
}
|
|
150
230
|
throw err;
|
|
151
231
|
}
|
|
152
232
|
}
|
|
153
|
-
async runSeoSpider(url, options, pages) {
|
|
154
|
-
const { depth = 5, limit = 100, concurrency = 5, robots = true, focus = 'all', signal, useSitemap = true } = options;
|
|
233
|
+
async runSeoSpider(url, options, pages, collectPageMetrics, getSecuritySummary) {
|
|
234
|
+
const { depth = 5, limit = 100, concurrency = 5, robots = true, focus = 'all', signal, useSitemap = true, transport, preferCurlFirst, timeout, delay, maxRetryAttempts, baseRetryDelayMs, maxRetryDelayMs, retryBackoffMultiplier, retryJitterMs, maxDomainBlockStrikes, rotateUserAgent, randomizeHeaders, } = options;
|
|
155
235
|
const focusCategories = {
|
|
156
236
|
links: ['links'],
|
|
157
237
|
duplicates: ['title', 'meta', 'content'],
|
|
@@ -171,7 +251,18 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
171
251
|
maxPages: limit,
|
|
172
252
|
concurrency,
|
|
173
253
|
sameDomain: true,
|
|
174
|
-
delay
|
|
254
|
+
delay,
|
|
255
|
+
timeout,
|
|
256
|
+
transport,
|
|
257
|
+
preferCurlFirst,
|
|
258
|
+
maxRetryAttempts,
|
|
259
|
+
baseRetryDelayMs,
|
|
260
|
+
maxRetryDelayMs,
|
|
261
|
+
retryBackoffMultiplier,
|
|
262
|
+
retryJitterMs,
|
|
263
|
+
maxDomainBlockStrikes,
|
|
264
|
+
rotateUserAgent,
|
|
265
|
+
randomizeHeaders,
|
|
175
266
|
seo: true,
|
|
176
267
|
respectRobotsTxt: robots,
|
|
177
268
|
useSitemap,
|
|
@@ -186,7 +277,7 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
186
277
|
depth: progress.depth,
|
|
187
278
|
});
|
|
188
279
|
if (signal?.aborted) {
|
|
189
|
-
throw new
|
|
280
|
+
throw new AbortError('Spider crawl aborted');
|
|
190
281
|
}
|
|
191
282
|
},
|
|
192
283
|
});
|
|
@@ -200,11 +291,16 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
200
291
|
depth: page.depth,
|
|
201
292
|
links: page.links?.length || 0,
|
|
202
293
|
duration: page.duration,
|
|
294
|
+
security: page.security,
|
|
295
|
+
timings: page.timings,
|
|
296
|
+
resources: page.resources,
|
|
203
297
|
seoScore: seoReport?.score,
|
|
204
298
|
seoGrade: seoReport?.grade,
|
|
205
299
|
seoErrors: seoReport?.summary?.errors,
|
|
206
300
|
seoWarnings: seoReport?.summary?.warnings,
|
|
301
|
+
seoReport,
|
|
207
302
|
});
|
|
303
|
+
collectPageMetrics(page);
|
|
208
304
|
if (page.links) {
|
|
209
305
|
const pageHost = new URL(page.url).hostname;
|
|
210
306
|
for (const link of page.links) {
|
|
@@ -231,6 +327,9 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
231
327
|
title: page.title,
|
|
232
328
|
links: page.links?.length || 0,
|
|
233
329
|
duration: page.duration,
|
|
330
|
+
security: page.security,
|
|
331
|
+
timings: page.timings,
|
|
332
|
+
resources: page.resources,
|
|
234
333
|
});
|
|
235
334
|
}
|
|
236
335
|
const summary = {
|
|
@@ -243,6 +342,7 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
243
342
|
images,
|
|
244
343
|
scripts,
|
|
245
344
|
stylesheets,
|
|
345
|
+
security: getSecuritySummary(),
|
|
246
346
|
pages,
|
|
247
347
|
seo: result.summary ? {
|
|
248
348
|
avgScore: result.summary.avgScore,
|
|
@@ -273,6 +373,10 @@ export class SpiderRunner extends CommandEmitter {
|
|
|
273
373
|
errors: result.errors?.length || 0,
|
|
274
374
|
internalLinks,
|
|
275
375
|
externalLinks,
|
|
376
|
+
images,
|
|
377
|
+
scripts,
|
|
378
|
+
stylesheets,
|
|
379
|
+
security: getSecuritySummary(),
|
|
276
380
|
});
|
|
277
381
|
return summary;
|
|
278
382
|
}
|
|
@@ -340,6 +444,10 @@ export async function runSpiderWithEvents(url, options = {}) {
|
|
|
340
444
|
concurrency: spiderOptions.concurrency ?? 5,
|
|
341
445
|
seo: spiderOptions.seo ?? false,
|
|
342
446
|
extract: spiderOptions.extract,
|
|
447
|
+
transport: spiderOptions.transport,
|
|
448
|
+
preferCurlFirst: spiderOptions.preferCurlFirst,
|
|
449
|
+
timeout: spiderOptions.timeout,
|
|
450
|
+
delay: spiderOptions.delay,
|
|
343
451
|
},
|
|
344
452
|
});
|
|
345
453
|
runner.on('spider:page', (event) => {
|
|
@@ -351,6 +459,12 @@ export async function runSpiderWithEvents(url, options = {}) {
|
|
|
351
459
|
title: event.title,
|
|
352
460
|
links: event.links,
|
|
353
461
|
duration: event.duration,
|
|
462
|
+
blocked: event.security?.blocked,
|
|
463
|
+
blockedByCaptcha: event.security?.blockedByCaptcha,
|
|
464
|
+
captchaDetected: event.security?.captchaDetected,
|
|
465
|
+
transport: event.security?.transport,
|
|
466
|
+
timing: event.timings,
|
|
467
|
+
resources: event.resources,
|
|
354
468
|
});
|
|
355
469
|
});
|
|
356
470
|
}
|
|
@@ -369,10 +483,16 @@ export async function runSpiderWithEvents(url, options = {}) {
|
|
|
369
483
|
depth: page.depth,
|
|
370
484
|
links: page.links,
|
|
371
485
|
duration: page.duration,
|
|
486
|
+
security: page.security,
|
|
487
|
+
timings: page.timings,
|
|
488
|
+
resources: page.resources,
|
|
372
489
|
seoScore: page.seoScore,
|
|
373
490
|
seoGrade: page.seoGrade,
|
|
374
491
|
seoErrors: page.seoErrors,
|
|
375
492
|
seoWarnings: page.seoWarnings,
|
|
493
|
+
attempts: page.security?.attempts,
|
|
494
|
+
retryCount: page.security?.retryCount,
|
|
495
|
+
retryAfterMs: page.security?.retryAfterMs,
|
|
376
496
|
extracted: page.extracted,
|
|
377
497
|
});
|
|
378
498
|
}
|
|
@@ -386,6 +506,10 @@ export async function runSpiderWithEvents(url, options = {}) {
|
|
|
386
506
|
internalLinks: result.internalLinks,
|
|
387
507
|
externalLinks: result.externalLinks,
|
|
388
508
|
seo: result.seo,
|
|
509
|
+
security: result.security,
|
|
510
|
+
images: result.images,
|
|
511
|
+
scripts: result.scripts,
|
|
512
|
+
stylesheets: result.stylesheets,
|
|
389
513
|
extraction: result.extraction,
|
|
390
514
|
});
|
|
391
515
|
await jsonlWriter.close();
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { RekCommand as Command } from '../router.js';
|
|
2
|
+
import { parseSpiderSerpConfig } from '../utils/serp-config.js';
|
|
3
|
+
import { type SpiderTransport } from '../utils/option-helpers.js';
|
|
4
|
+
type SpiderFocusMode = 'all' | 'links' | 'duplicates' | 'security' | 'ai' | 'resources';
|
|
2
5
|
export interface SpiderOptions {
|
|
3
6
|
url: string;
|
|
4
7
|
depth?: number;
|
|
5
8
|
limit?: number;
|
|
6
9
|
concurrency?: number;
|
|
7
10
|
output?: string;
|
|
8
|
-
focus?:
|
|
11
|
+
focus?: SpiderFocusMode;
|
|
9
12
|
seo?: boolean;
|
|
10
13
|
robots?: boolean;
|
|
11
14
|
json?: boolean;
|
|
@@ -15,6 +18,20 @@ export interface SpiderOptions {
|
|
|
15
18
|
exclude?: string | string[];
|
|
16
19
|
noSitemap?: boolean;
|
|
17
20
|
disableTui?: boolean;
|
|
21
|
+
transport?: SpiderTransport;
|
|
22
|
+
preferCurlFirst?: boolean;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
delay?: number;
|
|
25
|
+
maxRetryAttempts?: number;
|
|
26
|
+
baseRetryDelayMs?: number;
|
|
27
|
+
maxRetryDelayMs?: number;
|
|
28
|
+
retryBackoffMultiplier?: number;
|
|
29
|
+
retryJitterMs?: number;
|
|
30
|
+
maxDomainBlockStrikes?: number;
|
|
31
|
+
rotateUserAgent?: boolean;
|
|
32
|
+
randomizeHeaders?: boolean;
|
|
33
|
+
serpConfig?: ReturnType<typeof parseSpiderSerpConfig>;
|
|
18
34
|
}
|
|
19
35
|
export declare function runSpider(opts: SpiderOptions): Promise<void>;
|
|
20
36
|
export declare function registerSpiderCommand(program: Command): void;
|
|
37
|
+
export {};
|