arn-browser 0.0.3 → 0.0.5
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 +3 -2
- package/package.json +31 -47
- package/src/all_routes/routeWithSuperagent.d.ts +67 -0
- package/src/all_routes/routeWithSuperagent.js +322 -0
- package/src/human-cursor/HumanCursor.js +448 -0
- package/src/human-cursor/bezier.js +248 -0
- package/src/human-cursor/index.d.ts +154 -0
- package/src/human-cursor/index.js +9 -0
- package/src/human-cursor/randomizer.js +149 -0
- package/src/human-cursor/tweening.js +260 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +15 -0
- package/src/others/totp-generator.d.ts +15 -0
- package/src/others/totp-generator.js +86 -0
- package/src/utility/deleteDirectory.js +105 -0
- package/src/utility/launchBrowser.d.ts +248 -0
- package/src/utility/launchBrowser.js +899 -0
- package/src/utility/multilogin_token_manager.js +164 -0
- package/src/utility/playwright-helper.d.ts +61 -0
- package/src/utility/playwright-helper.js +129 -0
- package/src/utility/proxy-utility/custom-proxy.d.ts +93 -0
- package/src/utility/proxy-utility/custom-proxy.js +625 -0
- package/src/utility/proxy-utility/proxy-chain.d.ts +123 -0
- package/src/utility/proxy-utility/proxy-chain.js +337 -0
- package/src/utility/proxy-utility/proxy-helper.d.ts +91 -0
- package/src/utility/proxy-utility/proxy-helper.js +222 -0
- package/dist/__main__.d.ts +0 -2
- package/dist/__main__.js +0 -127
- package/dist/__version__.d.ts +0 -11
- package/dist/__version__.js +0 -16
- package/dist/addons.d.ts +0 -17
- package/dist/addons.js +0 -70
- package/dist/data-files/territoryInfo.xml +0 -2024
- package/dist/data-files/webgl_data.db +0 -0
- package/dist/exceptions.d.ts +0 -76
- package/dist/exceptions.js +0 -153
- package/dist/fingerprints.d.ts +0 -4
- package/dist/fingerprints.js +0 -82
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/ip.d.ts +0 -25
- package/dist/ip.js +0 -90
- package/dist/locale.d.ts +0 -26
- package/dist/locale.js +0 -280
- package/dist/mappings/browserforge.config.d.ts +0 -47
- package/dist/mappings/browserforge.config.js +0 -72
- package/dist/mappings/fonts.config.d.ts +0 -6
- package/dist/mappings/fonts.config.js +0 -822
- package/dist/mappings/warnings.config.d.ts +0 -16
- package/dist/mappings/warnings.config.js +0 -28
- package/dist/pkgman.d.ts +0 -62
- package/dist/pkgman.js +0 -347
- package/dist/server.d.ts +0 -6
- package/dist/server.js +0 -9
- package/dist/sync_api.d.ts +0 -7
- package/dist/sync_api.js +0 -27
- package/dist/utils.d.ts +0 -88
- package/dist/utils.js +0 -500
- package/dist/virtdisplay.d.ts +0 -20
- package/dist/virtdisplay.js +0 -123
- package/dist/warnings.d.ts +0 -4
- package/dist/warnings.js +0 -30
- package/dist/webgl/db-compat.d.ts +0 -9
- package/dist/webgl/db-compat.js +0 -44
- package/dist/webgl/sample.d.ts +0 -19
- package/dist/webgl/sample.js +0 -85
- /package/{LICENSE.md → LICENSE} +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HumanCursor - Human-like cursor movements for Playwright
|
|
3
|
+
* Drop-in replacement for Page with human cursor for click/fill/type/hover
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { HumanizeMouseTrajectory } from './bezier.js';
|
|
7
|
+
import { generateRandomCurveParameters, calculateAbsoluteOffset } from './randomizer.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Random integer between min and max (inclusive)
|
|
11
|
+
*/
|
|
12
|
+
function randInt(min, max) {
|
|
13
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sleep for specified milliseconds
|
|
18
|
+
*/
|
|
19
|
+
function sleep(ms) {
|
|
20
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a HumanLocator that wraps a Playwright Locator
|
|
25
|
+
* with human-like cursor movement for actions
|
|
26
|
+
*/
|
|
27
|
+
function createHumanLocator(cursor, locator) {
|
|
28
|
+
// Methods that need human cursor movement
|
|
29
|
+
const humanMethods = {
|
|
30
|
+
async click(options = {}) {
|
|
31
|
+
// If humanize is disabled, use native Playwright click
|
|
32
|
+
if (!cursor.config.humanize) {
|
|
33
|
+
return await locator.click(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await cursor._moveToLocator(locator, options);
|
|
37
|
+
const clicks = options.clickCount || 1;
|
|
38
|
+
const button = options.button || 'left';
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < clicks; i++) {
|
|
41
|
+
if (options.delay) {
|
|
42
|
+
await cursor._page.mouse.down({ button });
|
|
43
|
+
await sleep(options.delay);
|
|
44
|
+
await cursor._page.mouse.up({ button });
|
|
45
|
+
} else {
|
|
46
|
+
await cursor._page.mouse.click(
|
|
47
|
+
cursor.originCoordinates[0],
|
|
48
|
+
cursor.originCoordinates[1],
|
|
49
|
+
{ button }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (i < clicks - 1) {
|
|
53
|
+
await sleep(randInt(170, 280));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async dblclick(options = {}) {
|
|
59
|
+
if (!cursor.config.humanize) {
|
|
60
|
+
return await locator.dblclick(options);
|
|
61
|
+
}
|
|
62
|
+
await cursor._moveToLocator(locator, options);
|
|
63
|
+
await cursor._page.mouse.dblclick(
|
|
64
|
+
cursor.originCoordinates[0],
|
|
65
|
+
cursor.originCoordinates[1]
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async fill(value, options = {}) {
|
|
70
|
+
if (!cursor.config.humanize) {
|
|
71
|
+
return await locator.fill(value, options);
|
|
72
|
+
}
|
|
73
|
+
await this.click(options);
|
|
74
|
+
await cursor._page.keyboard.press('Control+A');
|
|
75
|
+
await sleep(randInt(50, 100));
|
|
76
|
+
await cursor._type(value, options);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async type(text, options = {}) {
|
|
80
|
+
if (!cursor.config.humanize) {
|
|
81
|
+
return await locator.type(text, options);
|
|
82
|
+
}
|
|
83
|
+
await this.click(options);
|
|
84
|
+
await cursor._type(text, options);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async hover(options = {}) {
|
|
88
|
+
if (!cursor.config.humanize) {
|
|
89
|
+
return await locator.hover(options);
|
|
90
|
+
}
|
|
91
|
+
await cursor._moveToLocator(locator, options);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async press(key, options = {}) {
|
|
95
|
+
if (!cursor.config.humanize) {
|
|
96
|
+
return await locator.press(key, options);
|
|
97
|
+
}
|
|
98
|
+
await this.click(options);
|
|
99
|
+
await cursor._page.keyboard.press(key);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Chainable methods that return new HumanLocators
|
|
103
|
+
filter(options) {
|
|
104
|
+
return createHumanLocator(cursor, locator.filter(options));
|
|
105
|
+
},
|
|
106
|
+
first() {
|
|
107
|
+
return createHumanLocator(cursor, locator.first());
|
|
108
|
+
},
|
|
109
|
+
last() {
|
|
110
|
+
return createHumanLocator(cursor, locator.last());
|
|
111
|
+
},
|
|
112
|
+
nth(index) {
|
|
113
|
+
return createHumanLocator(cursor, locator.nth(index));
|
|
114
|
+
},
|
|
115
|
+
locator(selector) {
|
|
116
|
+
return createHumanLocator(cursor, locator.locator(selector));
|
|
117
|
+
},
|
|
118
|
+
getByText(text, options) {
|
|
119
|
+
return createHumanLocator(cursor, locator.getByText(text, options));
|
|
120
|
+
},
|
|
121
|
+
getByRole(role, options) {
|
|
122
|
+
return createHumanLocator(cursor, locator.getByRole(role, options));
|
|
123
|
+
},
|
|
124
|
+
getByLabel(text, options) {
|
|
125
|
+
return createHumanLocator(cursor, locator.getByLabel(text, options));
|
|
126
|
+
},
|
|
127
|
+
getByPlaceholder(text, options) {
|
|
128
|
+
return createHumanLocator(cursor, locator.getByPlaceholder(text, options));
|
|
129
|
+
},
|
|
130
|
+
getByTestId(testId) {
|
|
131
|
+
return createHumanLocator(cursor, locator.getByTestId(testId));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Use Proxy to pass through all other methods to original locator
|
|
136
|
+
return new Proxy(locator, {
|
|
137
|
+
get(target, prop) {
|
|
138
|
+
// Human methods override
|
|
139
|
+
if (prop in humanMethods) {
|
|
140
|
+
return humanMethods[prop];
|
|
141
|
+
}
|
|
142
|
+
// Pass through to original locator
|
|
143
|
+
const value = target[prop];
|
|
144
|
+
if (typeof value === 'function') {
|
|
145
|
+
return value.bind(target);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create a HumanCursor that acts as a drop-in replacement for Page
|
|
154
|
+
* All page methods work, but click/fill/type/hover use human cursor
|
|
155
|
+
*
|
|
156
|
+
* @param {import('playwright').Page} page - Playwright Page object
|
|
157
|
+
* @param {Object} options - Configuration options
|
|
158
|
+
* @returns {import('playwright').Page & HumanCursorMethods}
|
|
159
|
+
*/
|
|
160
|
+
export function createCursor(page, options = {}) {
|
|
161
|
+
const config = {
|
|
162
|
+
humanize: options.humanize ?? true, // Enable human cursor by default
|
|
163
|
+
maxTime: options.maxTime ?? 1.5,
|
|
164
|
+
minTime: options.minTime ?? 0.5,
|
|
165
|
+
showCursor: options.showCursor ?? true // Show cursor by default
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Internal state
|
|
169
|
+
const cursor = {
|
|
170
|
+
_page: page,
|
|
171
|
+
originCoordinates: [0, 0],
|
|
172
|
+
config,
|
|
173
|
+
|
|
174
|
+
async _moveToLocator(locator, options = {}) {
|
|
175
|
+
await locator.scrollIntoViewIfNeeded();
|
|
176
|
+
const bounds = await locator.boundingBox();
|
|
177
|
+
|
|
178
|
+
if (!bounds) {
|
|
179
|
+
throw new Error('Element not found or not visible');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let xOffset, yOffset;
|
|
183
|
+
if (options.position) {
|
|
184
|
+
xOffset = options.position.x;
|
|
185
|
+
yOffset = options.position.y;
|
|
186
|
+
} else {
|
|
187
|
+
xOffset = bounds.width * (randInt(25, 75) / 100);
|
|
188
|
+
yOffset = bounds.height * (randInt(25, 75) / 100);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const destination = [bounds.x + xOffset, bounds.y + yOffset];
|
|
192
|
+
return await this._moveToPoint(destination, options);
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
async _moveToPoint(destination, options = {}) {
|
|
196
|
+
const viewport = page.viewportSize() || { width: 1280, height: 720 };
|
|
197
|
+
|
|
198
|
+
const params = generateRandomCurveParameters(
|
|
199
|
+
viewport,
|
|
200
|
+
this.originCoordinates,
|
|
201
|
+
destination
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (options.steady) {
|
|
205
|
+
params.offsetBoundaryX = 10;
|
|
206
|
+
params.offsetBoundaryY = 10;
|
|
207
|
+
params.distortionMean = 1.2;
|
|
208
|
+
params.distortionStdDev = 1.2;
|
|
209
|
+
params.distortionFrequency = 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const curve = new HumanizeMouseTrajectory(
|
|
213
|
+
this.originCoordinates,
|
|
214
|
+
destination,
|
|
215
|
+
{
|
|
216
|
+
offsetBoundaryX: params.offsetBoundaryX,
|
|
217
|
+
offsetBoundaryY: params.offsetBoundaryY,
|
|
218
|
+
knotsCount: params.knotsCount,
|
|
219
|
+
distortionMean: params.distortionMean,
|
|
220
|
+
distortionStdDev: params.distortionStdDev,
|
|
221
|
+
distortionFrequency: params.distortionFrequency,
|
|
222
|
+
tweening: params.tween,
|
|
223
|
+
targetPoints: params.targetPoints
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const distance = Math.sqrt(
|
|
228
|
+
Math.pow(destination[0] - this.originCoordinates[0], 2) +
|
|
229
|
+
Math.pow(destination[1] - this.originCoordinates[1], 2)
|
|
230
|
+
);
|
|
231
|
+
const totalTime = Math.min(
|
|
232
|
+
config.maxTime,
|
|
233
|
+
Math.max(config.minTime, distance / 1000)
|
|
234
|
+
) * 1000;
|
|
235
|
+
const baseDelay = totalTime / curve.points.length;
|
|
236
|
+
const speedMultiplier = options.moveSpeed || 1.0;
|
|
237
|
+
|
|
238
|
+
for (const point of curve.points) {
|
|
239
|
+
await page.mouse.move(point[0], point[1]);
|
|
240
|
+
if (baseDelay > 0) {
|
|
241
|
+
await sleep((baseDelay / speedMultiplier) + Math.random() * 2);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.originCoordinates = destination;
|
|
246
|
+
return destination;
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async _type(text, options = {}) {
|
|
250
|
+
const minDelay = options.minDelay ?? 50;
|
|
251
|
+
const maxDelay = options.maxDelay ?? 150;
|
|
252
|
+
|
|
253
|
+
for (const char of text) {
|
|
254
|
+
await page.keyboard.type(char);
|
|
255
|
+
await sleep(randInt(minDelay, maxDelay));
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
async showCursor() {
|
|
260
|
+
await page.evaluate(() => {
|
|
261
|
+
if (window.__humanCursorDot) return;
|
|
262
|
+
|
|
263
|
+
let cursor;
|
|
264
|
+
function updateCursor(event) {
|
|
265
|
+
const x = event.clientX;
|
|
266
|
+
const y = event.clientY;
|
|
267
|
+
|
|
268
|
+
if (!cursor) {
|
|
269
|
+
// Create cursor container
|
|
270
|
+
cursor = document.createElement('div');
|
|
271
|
+
cursor.id = '__human_cursor';
|
|
272
|
+
cursor.style.cssText = `
|
|
273
|
+
position: fixed;
|
|
274
|
+
pointer-events: none;
|
|
275
|
+
z-index: 2147483647;
|
|
276
|
+
transition: left 0.05s ease-out, top 0.05s ease-out;
|
|
277
|
+
`;
|
|
278
|
+
|
|
279
|
+
// Outer ring (animated)
|
|
280
|
+
const ring = document.createElement('div');
|
|
281
|
+
ring.style.cssText = `
|
|
282
|
+
width: 28px;
|
|
283
|
+
height: 28px;
|
|
284
|
+
border: 2px solid rgba(59, 130, 246, 0.6);
|
|
285
|
+
border-radius: 50%;
|
|
286
|
+
box-sizing: border-box;
|
|
287
|
+
position: absolute;
|
|
288
|
+
top: -14px;
|
|
289
|
+
left: -14px;
|
|
290
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
// Inner dot
|
|
294
|
+
const dot = document.createElement('div');
|
|
295
|
+
dot.style.cssText = `
|
|
296
|
+
width: 10px;
|
|
297
|
+
height: 10px;
|
|
298
|
+
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
|
299
|
+
border-radius: 50%;
|
|
300
|
+
position: absolute;
|
|
301
|
+
top: -5px;
|
|
302
|
+
left: -5px;
|
|
303
|
+
box-shadow: 0 0 12px rgba(59, 130, 246, 0.8), 0 0 4px rgba(255, 255, 255, 0.9);
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
// Click indicator
|
|
307
|
+
const clickRing = document.createElement('div');
|
|
308
|
+
clickRing.id = '__human_cursor_click';
|
|
309
|
+
clickRing.style.cssText = `
|
|
310
|
+
width: 36px;
|
|
311
|
+
height: 36px;
|
|
312
|
+
border: 2px solid rgba(239, 68, 68, 0.8);
|
|
313
|
+
border-radius: 50%;
|
|
314
|
+
box-sizing: border-box;
|
|
315
|
+
position: absolute;
|
|
316
|
+
top: -18px;
|
|
317
|
+
left: -18px;
|
|
318
|
+
opacity: 0;
|
|
319
|
+
transform: scale(0.5);
|
|
320
|
+
transition: opacity 0.15s, transform 0.15s;
|
|
321
|
+
`;
|
|
322
|
+
|
|
323
|
+
// Add pulse animation
|
|
324
|
+
const style = document.createElement('style');
|
|
325
|
+
style.textContent = `
|
|
326
|
+
@keyframes pulse {
|
|
327
|
+
0%, 100% { transform: scale(1); opacity: 0.6; }
|
|
328
|
+
50% { transform: scale(1.2); opacity: 0.3; }
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
document.head.appendChild(style);
|
|
332
|
+
|
|
333
|
+
cursor.appendChild(ring);
|
|
334
|
+
cursor.appendChild(dot);
|
|
335
|
+
cursor.appendChild(clickRing);
|
|
336
|
+
document.body.appendChild(cursor);
|
|
337
|
+
|
|
338
|
+
// Click effect
|
|
339
|
+
document.addEventListener('mousedown', () => {
|
|
340
|
+
const click = document.getElementById('__human_cursor_click');
|
|
341
|
+
if (click) {
|
|
342
|
+
click.style.opacity = '1';
|
|
343
|
+
click.style.transform = 'scale(1)';
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
document.addEventListener('mouseup', () => {
|
|
347
|
+
const click = document.getElementById('__human_cursor_click');
|
|
348
|
+
if (click) {
|
|
349
|
+
click.style.opacity = '0';
|
|
350
|
+
click.style.transform = 'scale(0.5)';
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
cursor.style.left = x + 'px';
|
|
356
|
+
cursor.style.top = y + 'px';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
document.addEventListener('mousemove', updateCursor);
|
|
360
|
+
window.__humanCursorDot = true;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Methods that return HumanLocators
|
|
366
|
+
const locatorMethods = {
|
|
367
|
+
locator(selector) {
|
|
368
|
+
return createHumanLocator(cursor, page.locator(selector));
|
|
369
|
+
},
|
|
370
|
+
getByText(text, opts) {
|
|
371
|
+
return createHumanLocator(cursor, page.getByText(text, opts));
|
|
372
|
+
},
|
|
373
|
+
getByRole(role, opts) {
|
|
374
|
+
return createHumanLocator(cursor, page.getByRole(role, opts));
|
|
375
|
+
},
|
|
376
|
+
getByLabel(text, opts) {
|
|
377
|
+
return createHumanLocator(cursor, page.getByLabel(text, opts));
|
|
378
|
+
},
|
|
379
|
+
getByPlaceholder(text, opts) {
|
|
380
|
+
return createHumanLocator(cursor, page.getByPlaceholder(text, opts));
|
|
381
|
+
},
|
|
382
|
+
getByAltText(text, opts) {
|
|
383
|
+
return createHumanLocator(cursor, page.getByAltText(text, opts));
|
|
384
|
+
},
|
|
385
|
+
getByTitle(text, opts) {
|
|
386
|
+
return createHumanLocator(cursor, page.getByTitle(text, opts));
|
|
387
|
+
},
|
|
388
|
+
getByTestId(testId) {
|
|
389
|
+
return createHumanLocator(cursor, page.getByTestId(testId));
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Wrap goto to auto-show cursor after navigation
|
|
394
|
+
const originalGoto = page.goto.bind(page);
|
|
395
|
+
const wrappedGoto = async (...args) => {
|
|
396
|
+
const result = await originalGoto(...args);
|
|
397
|
+
if (config.showCursor) {
|
|
398
|
+
await cursor.showCursor();
|
|
399
|
+
}
|
|
400
|
+
return result;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// Use Proxy to create drop-in page replacement
|
|
404
|
+
return new Proxy(page, {
|
|
405
|
+
get(target, prop) {
|
|
406
|
+
// Override goto to auto-show cursor
|
|
407
|
+
if (prop === 'goto') {
|
|
408
|
+
return wrappedGoto;
|
|
409
|
+
}
|
|
410
|
+
// Locator methods return HumanLocators
|
|
411
|
+
if (prop in locatorMethods) {
|
|
412
|
+
return locatorMethods[prop];
|
|
413
|
+
}
|
|
414
|
+
// Expose cursor utilities
|
|
415
|
+
if (prop === 'showCursor') {
|
|
416
|
+
return cursor.showCursor.bind(cursor);
|
|
417
|
+
}
|
|
418
|
+
if (prop === 'originCoordinates') {
|
|
419
|
+
return cursor.originCoordinates;
|
|
420
|
+
}
|
|
421
|
+
if (prop === 'setPosition') {
|
|
422
|
+
return (x, y) => { cursor.originCoordinates = [x, y]; };
|
|
423
|
+
}
|
|
424
|
+
// Pass through everything else to original page
|
|
425
|
+
const value = target[prop];
|
|
426
|
+
if (typeof value === 'function') {
|
|
427
|
+
return value.bind(target);
|
|
428
|
+
}
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Legacy class export for backwards compatibility
|
|
435
|
+
export class HumanCursor {
|
|
436
|
+
constructor(page, options = {}) {
|
|
437
|
+
this.page = page;
|
|
438
|
+
this.originCoordinates = [0, 0];
|
|
439
|
+
this.options = {
|
|
440
|
+
maxTime: options.maxTime ?? 1.5,
|
|
441
|
+
minTime: options.minTime ?? 0.5,
|
|
442
|
+
showCursor: options.showCursor ?? true // Show cursor by default
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Return proxy instead of class instance
|
|
446
|
+
return createCursor(page, options);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bezier curve calculator and human-like trajectory generator
|
|
3
|
+
* Ported from Python HumanCursor library
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { easeOutQuad } from './tweening.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calculates the binomial coefficient "n choose k"
|
|
10
|
+
* @param {number} n
|
|
11
|
+
* @param {number} k
|
|
12
|
+
* @returns {number}
|
|
13
|
+
*/
|
|
14
|
+
function binomial(n, k) {
|
|
15
|
+
let result = 1;
|
|
16
|
+
for (let i = 1; i <= k; i++) {
|
|
17
|
+
result = result * (n - i + 1) / i;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Calculate the i-th component of a Bernstein polynomial of degree n
|
|
24
|
+
* @param {number} x - Point on curve (0 to 1)
|
|
25
|
+
* @param {number} i - Index
|
|
26
|
+
* @param {number} n - Degree
|
|
27
|
+
* @returns {number}
|
|
28
|
+
*/
|
|
29
|
+
function bernsteinPolynomialPoint(x, i, n) {
|
|
30
|
+
return binomial(n, i) * Math.pow(x, i) * Math.pow(1 - x, n - i);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns a function that calculates a point on the Bezier curve
|
|
35
|
+
* @param {Array<[number, number]>} points - Control points
|
|
36
|
+
* @returns {function(number): [number, number]}
|
|
37
|
+
*/
|
|
38
|
+
function bernsteinPolynomial(points) {
|
|
39
|
+
return function (t) {
|
|
40
|
+
const n = points.length - 1;
|
|
41
|
+
let x = 0;
|
|
42
|
+
let y = 0;
|
|
43
|
+
for (let i = 0; i <= n; i++) {
|
|
44
|
+
const bern = bernsteinPolynomialPoint(t, i, n);
|
|
45
|
+
x += points[i][0] * bern;
|
|
46
|
+
y += points[i][1] * bern;
|
|
47
|
+
}
|
|
48
|
+
return [x, y];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Calculate n points on a Bezier curve
|
|
54
|
+
* @param {number} n - Number of points to generate
|
|
55
|
+
* @param {Array<[number, number]>} points - Control points
|
|
56
|
+
* @returns {Array<[number, number]>}
|
|
57
|
+
*/
|
|
58
|
+
function calculatePointsInCurve(n, points) {
|
|
59
|
+
const curvePoints = [];
|
|
60
|
+
const getBezierPoint = bernsteinPolynomial(points);
|
|
61
|
+
for (let i = 0; i < n; i++) {
|
|
62
|
+
const t = i / (n - 1);
|
|
63
|
+
curvePoints.push(getBezierPoint(t));
|
|
64
|
+
}
|
|
65
|
+
return curvePoints;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if a value is numeric
|
|
70
|
+
* @param {*} val
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
function isNumeric(val) {
|
|
74
|
+
return typeof val === 'number' && !isNaN(val);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if list contains valid points
|
|
79
|
+
* @param {Array} list
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
function isListOfPoints(list) {
|
|
83
|
+
if (!Array.isArray(list)) return false;
|
|
84
|
+
return list.every(p =>
|
|
85
|
+
Array.isArray(p) &&
|
|
86
|
+
p.length === 2 &&
|
|
87
|
+
isNumeric(p[0]) &&
|
|
88
|
+
isNumeric(p[1])
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Random integer between min and max (inclusive)
|
|
94
|
+
* @param {number} min
|
|
95
|
+
* @param {number} max
|
|
96
|
+
* @returns {number}
|
|
97
|
+
*/
|
|
98
|
+
function randInt(min, max) {
|
|
99
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Random float with normal distribution
|
|
104
|
+
* @param {number} mean
|
|
105
|
+
* @param {number} stdDev
|
|
106
|
+
* @returns {number}
|
|
107
|
+
*/
|
|
108
|
+
function randomNormal(mean, stdDev) {
|
|
109
|
+
// Box-Muller transform
|
|
110
|
+
const u1 = Math.random();
|
|
111
|
+
const u2 = Math.random();
|
|
112
|
+
const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
|
|
113
|
+
return z0 * stdDev + mean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generates human-like mouse trajectory using Bezier curves
|
|
118
|
+
*/
|
|
119
|
+
export class HumanizeMouseTrajectory {
|
|
120
|
+
/**
|
|
121
|
+
* @param {[number, number]} fromPoint - Starting point [x, y]
|
|
122
|
+
* @param {[number, number]} toPoint - Ending point [x, y]
|
|
123
|
+
* @param {Object} options - Curve generation options
|
|
124
|
+
*/
|
|
125
|
+
constructor(fromPoint, toPoint, options = {}) {
|
|
126
|
+
this.fromPoint = fromPoint;
|
|
127
|
+
this.toPoint = toPoint;
|
|
128
|
+
this.points = this.generateCurve(options);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generates the curve based on options
|
|
133
|
+
* @param {Object} options
|
|
134
|
+
* @returns {Array<[number, number]>}
|
|
135
|
+
*/
|
|
136
|
+
generateCurve(options) {
|
|
137
|
+
const offsetBoundaryX = options.offsetBoundaryX ?? 80;
|
|
138
|
+
const offsetBoundaryY = options.offsetBoundaryY ?? 80;
|
|
139
|
+
const leftBoundary = (options.leftBoundary ?? Math.min(this.fromPoint[0], this.toPoint[0])) - offsetBoundaryX;
|
|
140
|
+
const rightBoundary = (options.rightBoundary ?? Math.max(this.fromPoint[0], this.toPoint[0])) + offsetBoundaryX;
|
|
141
|
+
const downBoundary = (options.downBoundary ?? Math.min(this.fromPoint[1], this.toPoint[1])) - offsetBoundaryY;
|
|
142
|
+
const upBoundary = (options.upBoundary ?? Math.max(this.fromPoint[1], this.toPoint[1])) + offsetBoundaryY;
|
|
143
|
+
const knotsCount = options.knotsCount ?? 2;
|
|
144
|
+
const distortionMean = options.distortionMean ?? 1;
|
|
145
|
+
const distortionStdDev = options.distortionStdDev ?? 1;
|
|
146
|
+
const distortionFrequency = options.distortionFrequency ?? 0.5;
|
|
147
|
+
const tween = options.tweening ?? easeOutQuad;
|
|
148
|
+
const targetPoints = options.targetPoints ?? 100;
|
|
149
|
+
|
|
150
|
+
const internalKnots = this.generateInternalKnots(
|
|
151
|
+
leftBoundary, rightBoundary, downBoundary, upBoundary, knotsCount
|
|
152
|
+
);
|
|
153
|
+
let points = this.generatePoints(internalKnots);
|
|
154
|
+
points = this.distortPoints(points, distortionMean, distortionStdDev, distortionFrequency);
|
|
155
|
+
points = this.tweenPoints(points, tween, targetPoints);
|
|
156
|
+
return points;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generates random internal knots for the curve
|
|
161
|
+
* @param {number} lBoundary
|
|
162
|
+
* @param {number} rBoundary
|
|
163
|
+
* @param {number} dBoundary
|
|
164
|
+
* @param {number} uBoundary
|
|
165
|
+
* @param {number} knotsCount
|
|
166
|
+
* @returns {Array<[number, number]>}
|
|
167
|
+
*/
|
|
168
|
+
generateInternalKnots(lBoundary, rBoundary, dBoundary, uBoundary, knotsCount) {
|
|
169
|
+
if (knotsCount < 0) knotsCount = 0;
|
|
170
|
+
if (lBoundary > rBoundary) {
|
|
171
|
+
throw new Error('leftBoundary must be less than or equal to rightBoundary');
|
|
172
|
+
}
|
|
173
|
+
if (dBoundary > uBoundary) {
|
|
174
|
+
throw new Error('downBoundary must be less than or equal to upperBoundary');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const knots = [];
|
|
178
|
+
for (let i = 0; i < knotsCount; i++) {
|
|
179
|
+
const x = randInt(Math.floor(lBoundary), Math.floor(rBoundary));
|
|
180
|
+
const y = randInt(Math.floor(dBoundary), Math.floor(uBoundary));
|
|
181
|
+
knots.push([x, y]);
|
|
182
|
+
}
|
|
183
|
+
return knots;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generates points from Bezier curve
|
|
188
|
+
* @param {Array<[number, number]>} knots
|
|
189
|
+
* @returns {Array<[number, number]>}
|
|
190
|
+
*/
|
|
191
|
+
generatePoints(knots) {
|
|
192
|
+
const midPtsCnt = Math.max(
|
|
193
|
+
Math.abs(this.fromPoint[0] - this.toPoint[0]),
|
|
194
|
+
Math.abs(this.fromPoint[1] - this.toPoint[1]),
|
|
195
|
+
2
|
|
196
|
+
);
|
|
197
|
+
const allKnots = [this.fromPoint, ...knots, this.toPoint];
|
|
198
|
+
return calculatePointsInCurve(Math.floor(midPtsCnt), allKnots);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Distorts points to add human-like imperfections
|
|
203
|
+
* @param {Array<[number, number]>} points
|
|
204
|
+
* @param {number} distortionMean
|
|
205
|
+
* @param {number} distortionStdDev
|
|
206
|
+
* @param {number} distortionFrequency
|
|
207
|
+
* @returns {Array<[number, number]>}
|
|
208
|
+
*/
|
|
209
|
+
distortPoints(points, distortionMean, distortionStdDev, distortionFrequency) {
|
|
210
|
+
if (distortionFrequency < 0 || distortionFrequency > 1) {
|
|
211
|
+
throw new Error('distortionFrequency must be in range [0, 1]');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const distorted = [points[0]];
|
|
215
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
216
|
+
const [x, y] = points[i];
|
|
217
|
+
const delta = Math.random() < distortionFrequency
|
|
218
|
+
? randomNormal(distortionMean, distortionStdDev)
|
|
219
|
+
: 0;
|
|
220
|
+
distorted.push([x, y + delta]);
|
|
221
|
+
}
|
|
222
|
+
distorted.push(points[points.length - 1]);
|
|
223
|
+
return distorted;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Applies tweening to points
|
|
228
|
+
* @param {Array<[number, number]>} points
|
|
229
|
+
* @param {function(number): number} tween
|
|
230
|
+
* @param {number} targetPoints
|
|
231
|
+
* @returns {Array<[number, number]>}
|
|
232
|
+
*/
|
|
233
|
+
tweenPoints(points, tween, targetPoints) {
|
|
234
|
+
if (targetPoints < 2) {
|
|
235
|
+
throw new Error('targetPoints must be >= 2');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const result = [];
|
|
239
|
+
for (let i = 0; i < targetPoints; i++) {
|
|
240
|
+
const t = i / (targetPoints - 1);
|
|
241
|
+
const index = Math.floor(tween(t) * (points.length - 1));
|
|
242
|
+
result.push(points[index]);
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { calculatePointsInCurve, bernsteinPolynomial };
|