crawlforge-mcp-server 3.0.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/CLAUDE.md +315 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/package.json +115 -0
- package/server.js +1963 -0
- package/setup.js +112 -0
- package/src/constants/config.js +615 -0
- package/src/core/ActionExecutor.js +1104 -0
- package/src/core/AlertNotificationSystem.js +601 -0
- package/src/core/AuthManager.js +315 -0
- package/src/core/ChangeTracker.js +2306 -0
- package/src/core/JobManager.js +687 -0
- package/src/core/LLMsTxtAnalyzer.js +753 -0
- package/src/core/LocalizationManager.js +1615 -0
- package/src/core/PerformanceManager.js +828 -0
- package/src/core/ResearchOrchestrator.js +1327 -0
- package/src/core/SnapshotManager.js +1037 -0
- package/src/core/StealthBrowserManager.js +1795 -0
- package/src/core/WebhookDispatcher.js +745 -0
- package/src/core/analysis/ContentAnalyzer.js +749 -0
- package/src/core/analysis/LinkAnalyzer.js +972 -0
- package/src/core/cache/CacheManager.js +821 -0
- package/src/core/connections/ConnectionPool.js +553 -0
- package/src/core/crawlers/BFSCrawler.js +845 -0
- package/src/core/integrations/PerformanceIntegration.js +377 -0
- package/src/core/llm/AnthropicProvider.js +135 -0
- package/src/core/llm/LLMManager.js +415 -0
- package/src/core/llm/LLMProvider.js +97 -0
- package/src/core/llm/OpenAIProvider.js +127 -0
- package/src/core/processing/BrowserProcessor.js +986 -0
- package/src/core/processing/ContentProcessor.js +505 -0
- package/src/core/processing/PDFProcessor.js +448 -0
- package/src/core/processing/StreamProcessor.js +673 -0
- package/src/core/queue/QueueManager.js +98 -0
- package/src/core/workers/WorkerPool.js +585 -0
- package/src/core/workers/worker.js +743 -0
- package/src/monitoring/healthCheck.js +600 -0
- package/src/monitoring/metrics.js +761 -0
- package/src/optimization/wave3-optimizations.js +932 -0
- package/src/security/security-patches.js +120 -0
- package/src/security/security-tests.js +355 -0
- package/src/security/wave3-security.js +652 -0
- package/src/tools/advanced/BatchScrapeTool.js +1089 -0
- package/src/tools/advanced/ScrapeWithActionsTool.js +669 -0
- package/src/tools/crawl/crawlDeep.js +449 -0
- package/src/tools/crawl/mapSite.js +400 -0
- package/src/tools/extract/analyzeContent.js +624 -0
- package/src/tools/extract/extractContent.js +329 -0
- package/src/tools/extract/processDocument.js +503 -0
- package/src/tools/extract/summarizeContent.js +376 -0
- package/src/tools/llmstxt/generateLLMsTxt.js +570 -0
- package/src/tools/research/deepResearch.js +706 -0
- package/src/tools/search/adapters/duckduckgoSearch.js +398 -0
- package/src/tools/search/adapters/googleSearch.js +236 -0
- package/src/tools/search/adapters/searchProviderFactory.js +96 -0
- package/src/tools/search/queryExpander.js +543 -0
- package/src/tools/search/ranking/ResultDeduplicator.js +676 -0
- package/src/tools/search/ranking/ResultRanker.js +497 -0
- package/src/tools/search/searchWeb.js +482 -0
- package/src/tools/tracking/trackChanges.js +1355 -0
- package/src/utils/CircuitBreaker.js +515 -0
- package/src/utils/ErrorHandlingConfig.js +342 -0
- package/src/utils/HumanBehaviorSimulator.js +569 -0
- package/src/utils/Logger.js +568 -0
- package/src/utils/MemoryMonitor.js +173 -0
- package/src/utils/RetryManager.js +386 -0
- package/src/utils/contentUtils.js +588 -0
- package/src/utils/domainFilter.js +612 -0
- package/src/utils/inputValidation.js +766 -0
- package/src/utils/rateLimiter.js +196 -0
- package/src/utils/robotsChecker.js +91 -0
- package/src/utils/securityMiddleware.js +416 -0
- package/src/utils/sitemapParser.js +678 -0
- package/src/utils/ssrfProtection.js +640 -0
- package/src/utils/urlNormalizer.js +168 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HumanBehaviorSimulator - Simulate human-like interactions
|
|
3
|
+
* Features: natural mouse movements, typing patterns, scroll behavior, idle periods
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
const BehaviorConfigSchema = z.object({
|
|
9
|
+
mouseMovements: z.object({
|
|
10
|
+
enabled: z.boolean().default(true),
|
|
11
|
+
speed: z.enum(['slow', 'normal', 'fast']).default('normal'),
|
|
12
|
+
accuracy: z.number().min(0.1).max(1.0).default(0.8), // 0.1 = very inaccurate, 1.0 = perfect
|
|
13
|
+
naturalCurves: z.boolean().default(true),
|
|
14
|
+
randomMicroMovements: z.boolean().default(true)
|
|
15
|
+
}).default({}),
|
|
16
|
+
|
|
17
|
+
typing: z.object({
|
|
18
|
+
enabled: z.boolean().default(true),
|
|
19
|
+
speed: z.enum(['slow', 'normal', 'fast']).default('normal'),
|
|
20
|
+
variability: z.number().min(0).max(1).default(0.3), // Typing speed variation
|
|
21
|
+
mistakes: z.object({
|
|
22
|
+
enabled: z.boolean().default(true),
|
|
23
|
+
frequency: z.number().min(0).max(0.1).default(0.02), // 2% mistake rate
|
|
24
|
+
correctionDelay: z.number().min(100).max(2000).default(500)
|
|
25
|
+
}).default({})
|
|
26
|
+
}).default({}),
|
|
27
|
+
|
|
28
|
+
scrolling: z.object({
|
|
29
|
+
enabled: z.boolean().default(true),
|
|
30
|
+
naturalAcceleration: z.boolean().default(true),
|
|
31
|
+
randomPauses: z.boolean().default(true),
|
|
32
|
+
scrollBackProbability: z.number().min(0).max(1).default(0.1)
|
|
33
|
+
}).default({}),
|
|
34
|
+
|
|
35
|
+
interactions: z.object({
|
|
36
|
+
hoverBeforeClick: z.boolean().default(true),
|
|
37
|
+
clickDelay: z.object({
|
|
38
|
+
min: z.number().default(100),
|
|
39
|
+
max: z.number().default(300)
|
|
40
|
+
}).default({}),
|
|
41
|
+
focusBlurSimulation: z.boolean().default(true),
|
|
42
|
+
idlePeriods: z.object({
|
|
43
|
+
enabled: z.boolean().default(true),
|
|
44
|
+
frequency: z.number().min(0).max(1).default(0.1), // 10% chance
|
|
45
|
+
minDuration: z.number().default(1000),
|
|
46
|
+
maxDuration: z.number().default(5000)
|
|
47
|
+
}).default({})
|
|
48
|
+
}).default({})
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export class HumanBehaviorSimulator {
|
|
52
|
+
constructor(config = {}) {
|
|
53
|
+
this.config = BehaviorConfigSchema.parse(config);
|
|
54
|
+
|
|
55
|
+
// Speed multipliers for different speeds
|
|
56
|
+
this.speedMultipliers = {
|
|
57
|
+
slow: 1.5,
|
|
58
|
+
normal: 1.0,
|
|
59
|
+
fast: 0.7
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Base typing speeds (characters per minute)
|
|
63
|
+
this.typingSpeeds = {
|
|
64
|
+
slow: 200,
|
|
65
|
+
normal: 350,
|
|
66
|
+
fast: 500
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Common typing patterns and mistakes
|
|
70
|
+
this.commonMistakes = {
|
|
71
|
+
'e': ['r', 'w'],
|
|
72
|
+
'r': ['e', 't'],
|
|
73
|
+
't': ['r', 'y'],
|
|
74
|
+
'y': ['t', 'u'],
|
|
75
|
+
'u': ['y', 'i'],
|
|
76
|
+
'i': ['u', 'o'],
|
|
77
|
+
'o': ['i', 'p'],
|
|
78
|
+
'a': ['s', 'q'],
|
|
79
|
+
's': ['a', 'd'],
|
|
80
|
+
'd': ['s', 'f'],
|
|
81
|
+
'f': ['d', 'g'],
|
|
82
|
+
'g': ['f', 'h'],
|
|
83
|
+
'h': ['g', 'j'],
|
|
84
|
+
'j': ['h', 'k'],
|
|
85
|
+
'k': ['j', 'l'],
|
|
86
|
+
'l': ['k', ';'],
|
|
87
|
+
'n': ['b', 'm'],
|
|
88
|
+
'm': ['n', ',']
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this.stats = {
|
|
92
|
+
mouseMovements: 0,
|
|
93
|
+
typingActions: 0,
|
|
94
|
+
scrollActions: 0,
|
|
95
|
+
idlePeriods: 0,
|
|
96
|
+
mistakesSimulated: 0,
|
|
97
|
+
totalInteractions: 0
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Simulate human-like mouse movement to target coordinates
|
|
103
|
+
*/
|
|
104
|
+
async simulateMouseMovement(page, fromX, fromY, toX, toY) {
|
|
105
|
+
if (!this.config.mouseMovements.enabled) {
|
|
106
|
+
await page.mouse.move(toX, toY);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const path = this.generateMousePath(fromX, fromY, toX, toY);
|
|
111
|
+
const speed = this.speedMultipliers[this.config.mouseMovements.speed];
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < path.length; i++) {
|
|
114
|
+
const point = path[i];
|
|
115
|
+
await page.mouse.move(point.x, point.y);
|
|
116
|
+
|
|
117
|
+
// Add realistic delay between movements
|
|
118
|
+
const delay = (5 + Math.random() * 5) * speed;
|
|
119
|
+
await this.delay(delay);
|
|
120
|
+
|
|
121
|
+
// Occasionally add micro-movements
|
|
122
|
+
if (this.config.mouseMovements.randomMicroMovements && Math.random() < 0.1) {
|
|
123
|
+
const microX = point.x + (Math.random() - 0.5) * 2;
|
|
124
|
+
const microY = point.y + (Math.random() - 0.5) * 2;
|
|
125
|
+
await page.mouse.move(microX, microY);
|
|
126
|
+
await this.delay(10 + Math.random() * 20);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.stats.mouseMovements++;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate natural mouse movement path using Bezier curves
|
|
135
|
+
*/
|
|
136
|
+
generateMousePath(fromX, fromY, toX, toY) {
|
|
137
|
+
const points = [];
|
|
138
|
+
const distance = Math.sqrt(Math.pow(toX - fromX, 2) + Math.pow(toY - fromY, 2));
|
|
139
|
+
const steps = Math.max(10, Math.min(100, Math.floor(distance / 5)));
|
|
140
|
+
|
|
141
|
+
if (!this.config.mouseMovements.naturalCurves || distance < 50) {
|
|
142
|
+
// Simple linear movement for short distances
|
|
143
|
+
for (let i = 0; i <= steps; i++) {
|
|
144
|
+
const t = i / steps;
|
|
145
|
+
points.push({
|
|
146
|
+
x: fromX + (toX - fromX) * t,
|
|
147
|
+
y: fromY + (toY - fromY) * t
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// Bezier curve for natural movement
|
|
152
|
+
const controlPoints = this.generateBezierControlPoints(fromX, fromY, toX, toY);
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i <= steps; i++) {
|
|
155
|
+
const t = i / steps;
|
|
156
|
+
const point = this.calculateBezierPoint(t,
|
|
157
|
+
{ x: fromX, y: fromY },
|
|
158
|
+
controlPoints.cp1,
|
|
159
|
+
controlPoints.cp2,
|
|
160
|
+
{ x: toX, y: toY }
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Add accuracy variation
|
|
164
|
+
const accuracy = this.config.mouseMovements.accuracy;
|
|
165
|
+
const deviation = (1 - accuracy) * 10;
|
|
166
|
+
|
|
167
|
+
points.push({
|
|
168
|
+
x: point.x + (Math.random() - 0.5) * deviation,
|
|
169
|
+
y: point.y + (Math.random() - 0.5) * deviation
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return points;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generate control points for Bezier curve
|
|
179
|
+
*/
|
|
180
|
+
generateBezierControlPoints(x1, y1, x2, y2) {
|
|
181
|
+
const midX = (x1 + x2) / 2;
|
|
182
|
+
const midY = (y1 + y2) / 2;
|
|
183
|
+
|
|
184
|
+
// Add some randomness to make the curve more natural
|
|
185
|
+
const curvature = Math.random() * 100 + 50;
|
|
186
|
+
const direction = Math.random() < 0.5 ? 1 : -1;
|
|
187
|
+
|
|
188
|
+
const cp1 = {
|
|
189
|
+
x: midX + direction * curvature * Math.random(),
|
|
190
|
+
y: midY + direction * curvature * Math.random()
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const cp2 = {
|
|
194
|
+
x: midX - direction * curvature * Math.random(),
|
|
195
|
+
y: midY - direction * curvature * Math.random()
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return { cp1, cp2 };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Calculate point on cubic Bezier curve
|
|
203
|
+
*/
|
|
204
|
+
calculateBezierPoint(t, p0, p1, p2, p3) {
|
|
205
|
+
const u = 1 - t;
|
|
206
|
+
const tt = t * t;
|
|
207
|
+
const uu = u * u;
|
|
208
|
+
const uuu = uu * u;
|
|
209
|
+
const ttt = tt * t;
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
x: uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x,
|
|
213
|
+
y: uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Simulate human-like typing with realistic delays and mistakes
|
|
219
|
+
*/
|
|
220
|
+
async simulateTyping(page, selector, text) {
|
|
221
|
+
if (!this.config.typing.enabled) {
|
|
222
|
+
await page.type(selector, text);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const element = await page.waitForSelector(selector);
|
|
227
|
+
await element.focus();
|
|
228
|
+
|
|
229
|
+
// Calculate base typing speed
|
|
230
|
+
const baseSpeed = this.typingSpeeds[this.config.typing.speed];
|
|
231
|
+
const baseDelay = 60000 / baseSpeed; // Convert CPM to milliseconds per character
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < text.length; i++) {
|
|
234
|
+
const char = text[i];
|
|
235
|
+
|
|
236
|
+
// Simulate typing mistake
|
|
237
|
+
if (this.config.typing.mistakes.enabled && Math.random() < this.config.typing.mistakes.frequency) {
|
|
238
|
+
await this.simulateTypingMistake(element, char);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Calculate realistic delay for this character
|
|
243
|
+
const delay = this.calculateTypingDelay(char, i, text, baseDelay);
|
|
244
|
+
|
|
245
|
+
// Type the character
|
|
246
|
+
await element.type(char);
|
|
247
|
+
await this.delay(delay);
|
|
248
|
+
|
|
249
|
+
// Occasionally add brief pauses (like thinking)
|
|
250
|
+
if (Math.random() < 0.05) {
|
|
251
|
+
await this.delay(200 + Math.random() * 800);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.stats.typingActions++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Simulate a typing mistake and correction
|
|
260
|
+
*/
|
|
261
|
+
async simulateTypingMistake(element, correctChar) {
|
|
262
|
+
// Type wrong character
|
|
263
|
+
const mistakes = this.commonMistakes[correctChar.toLowerCase()] || [correctChar];
|
|
264
|
+
const wrongChar = mistakes[Math.floor(Math.random() * mistakes.length)];
|
|
265
|
+
|
|
266
|
+
await element.type(wrongChar);
|
|
267
|
+
await this.delay(this.config.typing.mistakes.correctionDelay + Math.random() * 500);
|
|
268
|
+
|
|
269
|
+
// Correct the mistake
|
|
270
|
+
await element.press('Backspace');
|
|
271
|
+
await this.delay(100 + Math.random() * 200);
|
|
272
|
+
await element.type(correctChar);
|
|
273
|
+
|
|
274
|
+
this.stats.mistakesSimulated++;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Calculate realistic typing delay based on character and context
|
|
279
|
+
*/
|
|
280
|
+
calculateTypingDelay(char, index, text, baseDelay) {
|
|
281
|
+
let delay = baseDelay;
|
|
282
|
+
|
|
283
|
+
// Add variability
|
|
284
|
+
const variability = this.config.typing.variability;
|
|
285
|
+
delay += (Math.random() - 0.5) * delay * variability;
|
|
286
|
+
|
|
287
|
+
// Longer delays for certain characters
|
|
288
|
+
if (char === ' ') {
|
|
289
|
+
delay *= 1.2; // Space takes slightly longer
|
|
290
|
+
} else if (char.match(/[.!?]/)) {
|
|
291
|
+
delay *= 1.5; // Punctuation takes longer
|
|
292
|
+
} else if (char.match(/[A-Z]/)) {
|
|
293
|
+
delay *= 1.3; // Capital letters take longer (shift key)
|
|
294
|
+
} else if (char.match(/[0-9]/)) {
|
|
295
|
+
delay *= 1.4; // Numbers take longer
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Longer delay after punctuation (thinking pause)
|
|
299
|
+
if (index > 0 && text[index - 1].match(/[.!?]/)) {
|
|
300
|
+
delay *= 2;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Shorter delay for repeated characters
|
|
304
|
+
if (index > 0 && text[index - 1] === char) {
|
|
305
|
+
delay *= 0.8;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return Math.max(50, delay); // Minimum 50ms delay
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Simulate human-like clicking with hover and delay
|
|
313
|
+
*/
|
|
314
|
+
async simulateClick(page, selector, options = {}) {
|
|
315
|
+
const element = await page.waitForSelector(selector);
|
|
316
|
+
const boundingBox = await element.boundingBox();
|
|
317
|
+
|
|
318
|
+
if (!boundingBox) {
|
|
319
|
+
throw new Error('Element not visible for clicking');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Calculate click position with slight randomness
|
|
323
|
+
const clickX = boundingBox.x + boundingBox.width * (0.3 + Math.random() * 0.4);
|
|
324
|
+
const clickY = boundingBox.y + boundingBox.height * (0.3 + Math.random() * 0.4);
|
|
325
|
+
|
|
326
|
+
// Get current mouse position (approximate)
|
|
327
|
+
const currentX = boundingBox.x - 50 + Math.random() * 100;
|
|
328
|
+
const currentY = boundingBox.y - 50 + Math.random() * 100;
|
|
329
|
+
|
|
330
|
+
// Simulate hover before click
|
|
331
|
+
if (this.config.interactions.hoverBeforeClick) {
|
|
332
|
+
await this.simulateMouseMovement(page, currentX, currentY, clickX, clickY);
|
|
333
|
+
await this.delay(100 + Math.random() * 200); // Brief hover
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Add click delay
|
|
337
|
+
const clickDelay = this.config.interactions.clickDelay.min +
|
|
338
|
+
Math.random() * (this.config.interactions.clickDelay.max - this.config.interactions.clickDelay.min);
|
|
339
|
+
await this.delay(clickDelay);
|
|
340
|
+
|
|
341
|
+
// Perform the click
|
|
342
|
+
await page.mouse.click(clickX, clickY, options);
|
|
343
|
+
|
|
344
|
+
this.stats.totalInteractions++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Simulate human-like scrolling with natural acceleration
|
|
349
|
+
*/
|
|
350
|
+
async simulateScroll(page, options = {}) {
|
|
351
|
+
if (!this.config.scrolling.enabled) {
|
|
352
|
+
await page.mouse.wheel(0, options.deltaY || 100);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const {
|
|
357
|
+
direction = 'down',
|
|
358
|
+
distance = 300,
|
|
359
|
+
duration = 1000,
|
|
360
|
+
target = null
|
|
361
|
+
} = options;
|
|
362
|
+
|
|
363
|
+
if (target) {
|
|
364
|
+
// Scroll to specific element
|
|
365
|
+
await page.evaluate((sel) => {
|
|
366
|
+
const element = document.querySelector(sel);
|
|
367
|
+
if (element) {
|
|
368
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
369
|
+
}
|
|
370
|
+
}, target);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Calculate scroll parameters
|
|
375
|
+
const steps = Math.max(10, Math.min(50, duration / 50));
|
|
376
|
+
const stepDistance = distance / steps;
|
|
377
|
+
const stepDelay = duration / steps;
|
|
378
|
+
|
|
379
|
+
let deltaY = direction === 'up' ? -stepDistance : stepDistance;
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < steps; i++) {
|
|
382
|
+
// Apply natural acceleration curve
|
|
383
|
+
let acceleration = 1;
|
|
384
|
+
if (this.config.scrolling.naturalAcceleration) {
|
|
385
|
+
const progress = i / steps;
|
|
386
|
+
// Ease-in-out curve
|
|
387
|
+
acceleration = progress < 0.5
|
|
388
|
+
? 2 * progress * progress
|
|
389
|
+
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const currentDelta = deltaY * acceleration;
|
|
393
|
+
await page.mouse.wheel(0, currentDelta);
|
|
394
|
+
|
|
395
|
+
// Variable delay between scroll steps
|
|
396
|
+
const delay = stepDelay * (0.8 + Math.random() * 0.4);
|
|
397
|
+
await this.delay(delay);
|
|
398
|
+
|
|
399
|
+
// Random pauses during scrolling
|
|
400
|
+
if (this.config.scrolling.randomPauses && Math.random() < 0.1) {
|
|
401
|
+
await this.delay(200 + Math.random() * 500);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Occasionally scroll back slightly (human behavior)
|
|
406
|
+
if (this.config.scrolling.scrollBackProbability > 0 &&
|
|
407
|
+
Math.random() < this.config.scrolling.scrollBackProbability) {
|
|
408
|
+
await this.delay(500 + Math.random() * 1000);
|
|
409
|
+
await page.mouse.wheel(0, -deltaY * 0.3);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.stats.scrollActions++;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Simulate focus and blur events
|
|
417
|
+
*/
|
|
418
|
+
async simulateFocusBlur(page, selector) {
|
|
419
|
+
if (!this.config.interactions.focusBlurSimulation) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const element = await page.waitForSelector(selector);
|
|
424
|
+
|
|
425
|
+
// Brief unfocus/refocus to simulate human attention
|
|
426
|
+
if (Math.random() < 0.3) {
|
|
427
|
+
await page.evaluate(() => document.activeElement?.blur());
|
|
428
|
+
await this.delay(100 + Math.random() * 300);
|
|
429
|
+
await element.focus();
|
|
430
|
+
await this.delay(50 + Math.random() * 150);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Simulate idle periods (human pauses)
|
|
436
|
+
*/
|
|
437
|
+
async simulateIdlePeriod() {
|
|
438
|
+
if (!this.config.interactions.idlePeriods.enabled ||
|
|
439
|
+
Math.random() > this.config.interactions.idlePeriods.frequency) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const minDuration = this.config.interactions.idlePeriods.minDuration;
|
|
444
|
+
const maxDuration = this.config.interactions.idlePeriods.maxDuration;
|
|
445
|
+
const duration = minDuration + Math.random() * (maxDuration - minDuration);
|
|
446
|
+
|
|
447
|
+
await this.delay(duration);
|
|
448
|
+
this.stats.idlePeriods++;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Simulate reading time based on content
|
|
453
|
+
*/
|
|
454
|
+
async simulateReadingTime(page, selector = null) {
|
|
455
|
+
let textLength = 0;
|
|
456
|
+
|
|
457
|
+
if (selector) {
|
|
458
|
+
textLength = await page.evaluate((sel) => {
|
|
459
|
+
const element = document.querySelector(sel);
|
|
460
|
+
return element ? element.textContent.length : 0;
|
|
461
|
+
}, selector);
|
|
462
|
+
} else {
|
|
463
|
+
textLength = await page.evaluate(() => document.body.textContent.length);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Average reading speed: 250 words per minute
|
|
467
|
+
// Average word length: 5 characters
|
|
468
|
+
const wordsCount = textLength / 5;
|
|
469
|
+
const readingTime = (wordsCount / 250) * 60 * 1000; // Convert to milliseconds
|
|
470
|
+
|
|
471
|
+
// Add randomness (50% to 150% of calculated time)
|
|
472
|
+
const actualReadingTime = readingTime * (0.5 + Math.random());
|
|
473
|
+
|
|
474
|
+
// Cap reading time at 30 seconds for practical purposes
|
|
475
|
+
const finalTime = Math.min(actualReadingTime, 30000);
|
|
476
|
+
|
|
477
|
+
await this.delay(finalTime);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Simulate form filling behavior
|
|
482
|
+
*/
|
|
483
|
+
async simulateFormFilling(page, formData) {
|
|
484
|
+
for (const [selector, value] of Object.entries(formData)) {
|
|
485
|
+
// Focus on field
|
|
486
|
+
await this.simulateFocusBlur(page, selector);
|
|
487
|
+
|
|
488
|
+
// Possible idle period before typing
|
|
489
|
+
await this.simulateIdlePeriod();
|
|
490
|
+
|
|
491
|
+
// Type with human-like behavior
|
|
492
|
+
await this.simulateTyping(page, selector, value);
|
|
493
|
+
|
|
494
|
+
// Brief pause after typing
|
|
495
|
+
await this.delay(200 + Math.random() * 500);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Generate realistic viewport interactions
|
|
501
|
+
*/
|
|
502
|
+
async simulateViewportInteraction(page, action) {
|
|
503
|
+
switch (action.type) {
|
|
504
|
+
case 'resize':
|
|
505
|
+
const newSize = action.size || { width: 1280 + Math.random() * 640, height: 720 + Math.random() * 360 };
|
|
506
|
+
await page.setViewportSize(newSize);
|
|
507
|
+
break;
|
|
508
|
+
|
|
509
|
+
case 'zoom':
|
|
510
|
+
const zoomLevel = action.level || (0.8 + Math.random() * 0.4); // 80% to 120%
|
|
511
|
+
await page.evaluate((zoom) => {
|
|
512
|
+
document.body.style.zoom = zoom;
|
|
513
|
+
}, zoomLevel);
|
|
514
|
+
break;
|
|
515
|
+
|
|
516
|
+
case 'fullscreen':
|
|
517
|
+
// Simulate fullscreen behavior
|
|
518
|
+
await page.keyboard.press('F11');
|
|
519
|
+
await this.delay(1000);
|
|
520
|
+
if (action.exit) {
|
|
521
|
+
await page.keyboard.press('F11');
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Utility delay function with slight randomness
|
|
529
|
+
*/
|
|
530
|
+
delay(ms, randomness = 0.1) {
|
|
531
|
+
const variation = ms * randomness;
|
|
532
|
+
const actualDelay = ms + (Math.random() - 0.5) * variation;
|
|
533
|
+
return new Promise(resolve => setTimeout(resolve, Math.max(0, actualDelay)));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get behavior statistics
|
|
538
|
+
*/
|
|
539
|
+
getStats() {
|
|
540
|
+
return {
|
|
541
|
+
...this.stats,
|
|
542
|
+
configuration: this.config,
|
|
543
|
+
timestamp: Date.now()
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Reset statistics
|
|
549
|
+
*/
|
|
550
|
+
resetStats() {
|
|
551
|
+
this.stats = {
|
|
552
|
+
mouseMovements: 0,
|
|
553
|
+
typingActions: 0,
|
|
554
|
+
scrollActions: 0,
|
|
555
|
+
idlePeriods: 0,
|
|
556
|
+
mistakesSimulated: 0,
|
|
557
|
+
totalInteractions: 0
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Update configuration
|
|
563
|
+
*/
|
|
564
|
+
updateConfig(newConfig) {
|
|
565
|
+
this.config = BehaviorConfigSchema.parse({ ...this.config, ...newConfig });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export default HumanBehaviorSimulator;
|