cloakbrowser 0.3.21 → 0.3.23
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/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/human/index.d.ts.map +1 -1
- package/dist/human/index.js +11 -2
- package/dist/human/index.js.map +1 -1
- package/dist/human-puppeteer/index.d.ts +63 -0
- package/dist/human-puppeteer/index.d.ts.map +1 -0
- package/dist/human-puppeteer/index.js +780 -0
- package/dist/human-puppeteer/index.js.map +1 -0
- package/dist/human-puppeteer/keyboard.d.ts +16 -0
- package/dist/human-puppeteer/keyboard.d.ts.map +1 -0
- package/dist/human-puppeteer/keyboard.js +157 -0
- package/dist/human-puppeteer/keyboard.js.map +1 -0
- package/dist/human-puppeteer/scroll.d.ts +26 -0
- package/dist/human-puppeteer/scroll.d.ts.map +1 -0
- package/dist/human-puppeteer/scroll.js +130 -0
- package/dist/human-puppeteer/scroll.js.map +1 -0
- package/dist/puppeteer.d.ts +7 -5
- package/dist/puppeteer.d.ts.map +1 -1
- package/dist/puppeteer.js +16 -8
- package/dist/puppeteer.js.map +1 -1
- package/package.json +5 -1
- package/dist/stealth-eval.d.ts +0 -40
- package/dist/stealth-eval.d.ts.map +0 -1
- package/dist/stealth-eval.js +0 -145
- package/dist/stealth-eval.js.map +0 -1
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-like behavioral layer for cloakbrowser — Puppeteer edition.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors Playwright humanize architecture, adapted for Puppeteer API.
|
|
5
|
+
*
|
|
6
|
+
* Patches ALL native Puppeteer interaction surfaces:
|
|
7
|
+
*
|
|
8
|
+
* PAGE-LEVEL:
|
|
9
|
+
* click (with clickCount support for dblclick), hover, type,
|
|
10
|
+
* select, focus, tap, goto
|
|
11
|
+
*
|
|
12
|
+
* MOUSE:
|
|
13
|
+
* move, click (with clickCount support for dblclick), wheel,
|
|
14
|
+
* dragAndDrop
|
|
15
|
+
*
|
|
16
|
+
* KEYBOARD:
|
|
17
|
+
* type, down, up, press, sendCharacter
|
|
18
|
+
*
|
|
19
|
+
* FRAME-LEVEL:
|
|
20
|
+
* click, hover, type, select, focus, tap
|
|
21
|
+
* + $, $$, waitForSelector (return patched ElementHandles)
|
|
22
|
+
*
|
|
23
|
+
* ELEMENTHANDLE-LEVEL (Puppeteer-specific, no Playwright equivalent):
|
|
24
|
+
* click (with clickCount), hover, type, press, tap, select,
|
|
25
|
+
* focus, drop, dragAndDrop
|
|
26
|
+
* + $, $$, waitForSelector (nested elements are also patched)
|
|
27
|
+
*
|
|
28
|
+
* BROWSER-LEVEL:
|
|
29
|
+
* newPage, createBrowserContext / createIncognitoBrowserContext,
|
|
30
|
+
* targetcreated event
|
|
31
|
+
*
|
|
32
|
+
* Stealth-aware:
|
|
33
|
+
* - isInputElement / isSelectorFocused use CDP Isolated Worlds
|
|
34
|
+
* - Shift symbol typing uses CDP Input.dispatchKeyEvent (isTrusted=true)
|
|
35
|
+
* - ElementHandle isInput check uses CDP DOM.describeNode (no JS execution)
|
|
36
|
+
* - Falls back to page.evaluate only when CDP session is unavailable
|
|
37
|
+
*
|
|
38
|
+
* Puppeteer-specific adaptations:
|
|
39
|
+
* - page.createCDPSession() instead of context.newCDPSession(page)
|
|
40
|
+
* - page.viewport() instead of page.viewportSize()
|
|
41
|
+
* - page.$(selector) instead of page.locator(selector)
|
|
42
|
+
* - keyboard.sendCharacter() mapped via RawKeyboard.insertText
|
|
43
|
+
* - mouse.wheel({deltaX, deltaY}) object form adapted to (dx, dy)
|
|
44
|
+
* - page.select() instead of page.selectOption()
|
|
45
|
+
* - ElementHandle prototype patching (Puppeteer-only)
|
|
46
|
+
* - No page.dblclick() — Puppeteer uses click({clickCount:2})
|
|
47
|
+
*/
|
|
48
|
+
import { rand, randRange, sleep } from '../human/config.js';
|
|
49
|
+
import { humanMove, humanClick, clickTarget, humanIdle } from '../human/mouse.js';
|
|
50
|
+
import { humanType } from './keyboard.js';
|
|
51
|
+
import { scrollToElement, smoothWheel } from './scroll.js';
|
|
52
|
+
export { resolveConfig } from '../human/config.js';
|
|
53
|
+
export { humanMove, humanClick, clickTarget, humanIdle } from '../human/mouse.js';
|
|
54
|
+
export { humanType } from './keyboard.js';
|
|
55
|
+
export { scrollToElement } from './scroll.js';
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// CDP Isolated World — stealth DOM evaluation (Puppeteer version)
|
|
58
|
+
// ============================================================================
|
|
59
|
+
class StealthEval {
|
|
60
|
+
cdp = null;
|
|
61
|
+
contextId = null;
|
|
62
|
+
page;
|
|
63
|
+
constructor(page) {
|
|
64
|
+
this.page = page;
|
|
65
|
+
}
|
|
66
|
+
async ensureCdp() {
|
|
67
|
+
if (!this.cdp) {
|
|
68
|
+
this.cdp = await this.page.createCDPSession();
|
|
69
|
+
}
|
|
70
|
+
return this.cdp;
|
|
71
|
+
}
|
|
72
|
+
async createWorld() {
|
|
73
|
+
const cdp = await this.ensureCdp();
|
|
74
|
+
const tree = await cdp.send('Page.getFrameTree');
|
|
75
|
+
const frameId = tree.frameTree.frame.id;
|
|
76
|
+
const result = await cdp.send('Page.createIsolatedWorld', {
|
|
77
|
+
frameId,
|
|
78
|
+
worldName: '',
|
|
79
|
+
grantUniveralAccess: true,
|
|
80
|
+
});
|
|
81
|
+
const ctxId = result.executionContextId;
|
|
82
|
+
this.contextId = ctxId;
|
|
83
|
+
return ctxId;
|
|
84
|
+
}
|
|
85
|
+
async evaluate(expression) {
|
|
86
|
+
if (this.contextId === null) {
|
|
87
|
+
await this.createWorld();
|
|
88
|
+
}
|
|
89
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
90
|
+
try {
|
|
91
|
+
const cdp = await this.ensureCdp();
|
|
92
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
93
|
+
expression,
|
|
94
|
+
contextId: this.contextId,
|
|
95
|
+
returnByValue: true,
|
|
96
|
+
});
|
|
97
|
+
if (result.exceptionDetails) {
|
|
98
|
+
if (attempt === 0) {
|
|
99
|
+
await this.createWorld();
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return result.result?.value;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
if (attempt === 0) {
|
|
108
|
+
this.contextId = null;
|
|
109
|
+
try {
|
|
110
|
+
await this.createWorld();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
invalidate() {
|
|
123
|
+
this.contextId = null;
|
|
124
|
+
}
|
|
125
|
+
async getCdpSession() {
|
|
126
|
+
return this.ensureCdp();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Cursor state
|
|
131
|
+
// ============================================================================
|
|
132
|
+
class CursorState {
|
|
133
|
+
x = 0;
|
|
134
|
+
y = 0;
|
|
135
|
+
initialized = false;
|
|
136
|
+
}
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Stealth DOM queries
|
|
139
|
+
// ============================================================================
|
|
140
|
+
async function isInputElement(stealth, page, selector) {
|
|
141
|
+
if (stealth) {
|
|
142
|
+
try {
|
|
143
|
+
const escaped = JSON.stringify(selector);
|
|
144
|
+
const result = await stealth.evaluate(`
|
|
145
|
+
(() => {
|
|
146
|
+
const el = document.querySelector(${escaped});
|
|
147
|
+
if (!el) return false;
|
|
148
|
+
const tag = el.tagName.toLowerCase();
|
|
149
|
+
return tag === 'input' || tag === 'textarea'
|
|
150
|
+
|| el.getAttribute('contenteditable') === 'true';
|
|
151
|
+
})()
|
|
152
|
+
`);
|
|
153
|
+
return !!result;
|
|
154
|
+
}
|
|
155
|
+
catch { /* fallthrough */ }
|
|
156
|
+
}
|
|
157
|
+
return page.evaluate((sel) => {
|
|
158
|
+
const el = document.querySelector(sel);
|
|
159
|
+
if (!el)
|
|
160
|
+
return false;
|
|
161
|
+
const tag = el.tagName.toLowerCase();
|
|
162
|
+
return tag === 'input' || tag === 'textarea'
|
|
163
|
+
|| el.getAttribute('contenteditable') === 'true';
|
|
164
|
+
}, selector).catch(() => false);
|
|
165
|
+
}
|
|
166
|
+
async function isSelectorFocused(stealth, page, selector) {
|
|
167
|
+
if (stealth) {
|
|
168
|
+
try {
|
|
169
|
+
const escaped = JSON.stringify(selector);
|
|
170
|
+
const result = await stealth.evaluate(`
|
|
171
|
+
(() => {
|
|
172
|
+
const el = document.querySelector(${escaped});
|
|
173
|
+
return el === document.activeElement;
|
|
174
|
+
})()
|
|
175
|
+
`);
|
|
176
|
+
return !!result;
|
|
177
|
+
}
|
|
178
|
+
catch { /* fallthrough */ }
|
|
179
|
+
}
|
|
180
|
+
return page.evaluate((sel) => {
|
|
181
|
+
const el = document.querySelector(sel);
|
|
182
|
+
return el === document.activeElement;
|
|
183
|
+
}, selector).catch(() => false);
|
|
184
|
+
}
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Stealth ElementHandle input check — uses CDP DOM.describeNode
|
|
187
|
+
// instead of el.evaluate() to avoid main-world JS execution.
|
|
188
|
+
// ============================================================================
|
|
189
|
+
async function isInputElementHandle(stealth, el) {
|
|
190
|
+
if (stealth) {
|
|
191
|
+
try {
|
|
192
|
+
const cdp = await stealth.getCdpSession();
|
|
193
|
+
const remoteObject = el.remoteObject?.();
|
|
194
|
+
if (remoteObject?.objectId) {
|
|
195
|
+
const { node } = await cdp.send('DOM.describeNode', {
|
|
196
|
+
objectId: remoteObject.objectId,
|
|
197
|
+
});
|
|
198
|
+
const tag = (node?.nodeName || '').toLowerCase();
|
|
199
|
+
if (tag === 'input' || tag === 'textarea')
|
|
200
|
+
return true;
|
|
201
|
+
const attrs = node?.attributes || [];
|
|
202
|
+
for (let i = 0; i < attrs.length; i += 2) {
|
|
203
|
+
if (attrs[i] === 'contenteditable' && attrs[i + 1] === 'true') {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch { /* fallthrough to el.evaluate */ }
|
|
211
|
+
}
|
|
212
|
+
return el.evaluate((node) => {
|
|
213
|
+
const tag = node.tagName?.toLowerCase();
|
|
214
|
+
return tag === 'input' || tag === 'textarea'
|
|
215
|
+
|| node.getAttribute?.('contenteditable') === 'true';
|
|
216
|
+
}).catch(() => false);
|
|
217
|
+
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Page-level patching
|
|
220
|
+
// ============================================================================
|
|
221
|
+
function patchPage(page, cfg, cursor) {
|
|
222
|
+
const originals = {
|
|
223
|
+
click: page.click.bind(page),
|
|
224
|
+
hover: page.hover.bind(page),
|
|
225
|
+
type: page.type.bind(page),
|
|
226
|
+
select: page.select.bind(page),
|
|
227
|
+
focus: page.focus.bind(page),
|
|
228
|
+
goto: page.goto.bind(page),
|
|
229
|
+
tap: page.tap.bind(page),
|
|
230
|
+
mouseMove: page.mouse.move.bind(page.mouse),
|
|
231
|
+
mouseClick: page.mouse.click.bind(page.mouse),
|
|
232
|
+
mouseDown: page.mouse.down.bind(page.mouse),
|
|
233
|
+
mouseUp: page.mouse.up.bind(page.mouse),
|
|
234
|
+
mouseWheel: page.mouse.wheel?.bind(page.mouse),
|
|
235
|
+
mouseDragAndDrop: page.mouse.dragAndDrop?.bind(page.mouse),
|
|
236
|
+
keyboardType: page.keyboard.type.bind(page.keyboard),
|
|
237
|
+
keyboardDown: page.keyboard.down.bind(page.keyboard),
|
|
238
|
+
keyboardUp: page.keyboard.up.bind(page.keyboard),
|
|
239
|
+
keyboardPress: page.keyboard.press.bind(page.keyboard),
|
|
240
|
+
keyboardSendCharacter: page.keyboard.sendCharacter.bind(page.keyboard),
|
|
241
|
+
};
|
|
242
|
+
page._original = originals;
|
|
243
|
+
page._humanCfg = cfg;
|
|
244
|
+
const stealth = new StealthEval(page);
|
|
245
|
+
page._stealth = stealth;
|
|
246
|
+
let cdpSession = null;
|
|
247
|
+
const ensureCdp = async () => {
|
|
248
|
+
if (!cdpSession) {
|
|
249
|
+
try {
|
|
250
|
+
cdpSession = await stealth.getCdpSession();
|
|
251
|
+
}
|
|
252
|
+
catch { }
|
|
253
|
+
}
|
|
254
|
+
return cdpSession;
|
|
255
|
+
};
|
|
256
|
+
const raw = {
|
|
257
|
+
move: originals.mouseMove,
|
|
258
|
+
down: originals.mouseDown,
|
|
259
|
+
up: originals.mouseUp,
|
|
260
|
+
wheel: async (deltaX, deltaY) => {
|
|
261
|
+
if (originals.mouseWheel) {
|
|
262
|
+
await originals.mouseWheel({ deltaX, deltaY });
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
const rawKb = {
|
|
267
|
+
down: originals.keyboardDown,
|
|
268
|
+
up: originals.keyboardUp,
|
|
269
|
+
type: originals.keyboardType,
|
|
270
|
+
insertText: originals.keyboardSendCharacter,
|
|
271
|
+
};
|
|
272
|
+
async function ensureCursorInit() {
|
|
273
|
+
if (!cursor.initialized) {
|
|
274
|
+
cursor.x = rand(cfg.initial_cursor_x[0], cfg.initial_cursor_x[1]);
|
|
275
|
+
cursor.y = rand(cfg.initial_cursor_y[0], cfg.initial_cursor_y[1]);
|
|
276
|
+
await originals.mouseMove(cursor.x, cursor.y);
|
|
277
|
+
cursor.initialized = true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// ==== goto ====
|
|
281
|
+
const humanGoto = async (url, options) => {
|
|
282
|
+
const response = await originals.goto(url, options);
|
|
283
|
+
stealth.invalidate();
|
|
284
|
+
patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
285
|
+
return response;
|
|
286
|
+
};
|
|
287
|
+
// ==== click (with clickCount support for dblclick) ====
|
|
288
|
+
const humanClickFn = async (selector, options) => {
|
|
289
|
+
await ensureCursorInit();
|
|
290
|
+
if (cfg.idle_between_actions) {
|
|
291
|
+
await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg);
|
|
292
|
+
}
|
|
293
|
+
const { box, cursorX, cursorY } = await scrollToElement(page, raw, selector, cursor.x, cursor.y, cfg);
|
|
294
|
+
cursor.x = cursorX;
|
|
295
|
+
cursor.y = cursorY;
|
|
296
|
+
const isInput = await isInputElement(stealth, page, selector);
|
|
297
|
+
const target = clickTarget(box, isInput, cfg);
|
|
298
|
+
await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
|
|
299
|
+
cursor.x = target.x;
|
|
300
|
+
cursor.y = target.y;
|
|
301
|
+
const clickCount = options?.clickCount ?? options?.count ?? 1;
|
|
302
|
+
if (clickCount >= 2) {
|
|
303
|
+
await humanClick(raw, isInput, cfg);
|
|
304
|
+
await sleep(rand(40, 90));
|
|
305
|
+
await raw.down({ clickCount: 2 });
|
|
306
|
+
await sleep(rand(30, 60));
|
|
307
|
+
await raw.up({ clickCount: 2 });
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
await humanClick(raw, isInput, cfg);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
// ==== hover ====
|
|
314
|
+
const humanHoverFn = async (selector, options) => {
|
|
315
|
+
await ensureCursorInit();
|
|
316
|
+
if (cfg.idle_between_actions) {
|
|
317
|
+
await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg);
|
|
318
|
+
}
|
|
319
|
+
const { box, cursorX, cursorY } = await scrollToElement(page, raw, selector, cursor.x, cursor.y, cfg);
|
|
320
|
+
cursor.x = cursorX;
|
|
321
|
+
cursor.y = cursorY;
|
|
322
|
+
const target = clickTarget(box, false, cfg);
|
|
323
|
+
await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
|
|
324
|
+
cursor.x = target.x;
|
|
325
|
+
cursor.y = target.y;
|
|
326
|
+
};
|
|
327
|
+
// ==== type ====
|
|
328
|
+
const humanTypeFn = async (selector, text, options) => {
|
|
329
|
+
await sleep(randRange(cfg.field_switch_delay));
|
|
330
|
+
await humanClickFn(selector);
|
|
331
|
+
await sleep(rand(100, 250));
|
|
332
|
+
const cdp = await ensureCdp();
|
|
333
|
+
await humanType(page, rawKb, text, cfg, cdp);
|
|
334
|
+
};
|
|
335
|
+
// ==== select ====
|
|
336
|
+
const humanSelectFn = async (selector, ...values) => {
|
|
337
|
+
await humanHoverFn(selector);
|
|
338
|
+
await sleep(rand(100, 300));
|
|
339
|
+
return originals.select(selector, ...values);
|
|
340
|
+
};
|
|
341
|
+
// ==== focus ====
|
|
342
|
+
const humanFocusFn = async (selector) => {
|
|
343
|
+
if (!await isSelectorFocused(stealth, page, selector)) {
|
|
344
|
+
await humanClickFn(selector);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
// ==== tap ====
|
|
348
|
+
const humanTapFn = async (selector, options) => {
|
|
349
|
+
await humanClickFn(selector, options);
|
|
350
|
+
};
|
|
351
|
+
// ============================================================
|
|
352
|
+
// Assign page-level patches
|
|
353
|
+
// ============================================================
|
|
354
|
+
page.goto = humanGoto;
|
|
355
|
+
page.click = humanClickFn;
|
|
356
|
+
page.hover = humanHoverFn;
|
|
357
|
+
page.type = humanTypeFn;
|
|
358
|
+
page.select = humanSelectFn;
|
|
359
|
+
page.focus = humanFocusFn;
|
|
360
|
+
page.tap = humanTapFn;
|
|
361
|
+
// ============================================================
|
|
362
|
+
// Mouse patches
|
|
363
|
+
// ============================================================
|
|
364
|
+
page.mouse.move = async (x, y, options) => {
|
|
365
|
+
await ensureCursorInit();
|
|
366
|
+
await humanMove(raw, cursor.x, cursor.y, x, y, cfg);
|
|
367
|
+
cursor.x = x;
|
|
368
|
+
cursor.y = y;
|
|
369
|
+
};
|
|
370
|
+
page.mouse.click = async (x, y, options) => {
|
|
371
|
+
await ensureCursorInit();
|
|
372
|
+
await humanMove(raw, cursor.x, cursor.y, x, y, cfg);
|
|
373
|
+
cursor.x = x;
|
|
374
|
+
cursor.y = y;
|
|
375
|
+
const clickCount = options?.clickCount ?? options?.count ?? 1;
|
|
376
|
+
if (clickCount >= 2) {
|
|
377
|
+
await humanClick(raw, false, cfg);
|
|
378
|
+
await sleep(rand(40, 90));
|
|
379
|
+
await raw.down({ clickCount: 2 });
|
|
380
|
+
await sleep(rand(30, 60));
|
|
381
|
+
await raw.up({ clickCount: 2 });
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
await humanClick(raw, false, cfg);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
if (originals.mouseWheel) {
|
|
388
|
+
page.mouse.wheel = async (options) => {
|
|
389
|
+
const dx = options?.deltaX ?? 0;
|
|
390
|
+
const dy = options?.deltaY ?? 0;
|
|
391
|
+
if (Math.abs(dy) > 0) {
|
|
392
|
+
await smoothWheel(raw, dy, cfg, 'y');
|
|
393
|
+
}
|
|
394
|
+
if (Math.abs(dx) > 0) {
|
|
395
|
+
await smoothWheel(raw, dx, cfg, 'x');
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (originals.mouseDragAndDrop) {
|
|
400
|
+
page.mouse.dragAndDrop = async (start, target, options) => {
|
|
401
|
+
await ensureCursorInit();
|
|
402
|
+
await humanMove(raw, cursor.x, cursor.y, start.x, start.y, cfg);
|
|
403
|
+
cursor.x = start.x;
|
|
404
|
+
cursor.y = start.y;
|
|
405
|
+
await sleep(rand(100, 200));
|
|
406
|
+
await originals.mouseDown();
|
|
407
|
+
await sleep(rand(80, 150));
|
|
408
|
+
await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
|
|
409
|
+
cursor.x = target.x;
|
|
410
|
+
cursor.y = target.y;
|
|
411
|
+
await sleep(rand(80, 150));
|
|
412
|
+
await originals.mouseUp();
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
// ============================================================
|
|
416
|
+
// Keyboard patches
|
|
417
|
+
// ============================================================
|
|
418
|
+
page.keyboard.type = async (text, options) => {
|
|
419
|
+
const cdp = await ensureCdp();
|
|
420
|
+
await humanType(page, rawKb, text, cfg, cdp);
|
|
421
|
+
};
|
|
422
|
+
page.keyboard.press = async (key, options) => {
|
|
423
|
+
await sleep(rand(20, 60));
|
|
424
|
+
await originals.keyboardDown(key);
|
|
425
|
+
await sleep(randRange(cfg.key_hold));
|
|
426
|
+
await originals.keyboardUp(key);
|
|
427
|
+
};
|
|
428
|
+
page.keyboard.down = async (key) => {
|
|
429
|
+
await sleep(rand(10, 30));
|
|
430
|
+
await originals.keyboardDown(key);
|
|
431
|
+
};
|
|
432
|
+
page.keyboard.up = async (key) => {
|
|
433
|
+
await sleep(rand(10, 30));
|
|
434
|
+
await originals.keyboardUp(key);
|
|
435
|
+
};
|
|
436
|
+
// ============================================================
|
|
437
|
+
// Store helpers for frame/element patching
|
|
438
|
+
// ============================================================
|
|
439
|
+
page._humanCursor = cursor;
|
|
440
|
+
page._humanRaw = raw;
|
|
441
|
+
page._humanRawKb = rawKb;
|
|
442
|
+
page._ensureCursorInit = ensureCursorInit;
|
|
443
|
+
// Initialize cursor
|
|
444
|
+
cursor.x = rand(cfg.initial_cursor_x[0], cfg.initial_cursor_x[1]);
|
|
445
|
+
cursor.y = rand(cfg.initial_cursor_y[0], cfg.initial_cursor_y[1]);
|
|
446
|
+
originals.mouseMove(cursor.x, cursor.y).then(() => {
|
|
447
|
+
cursor.initialized = true;
|
|
448
|
+
}).catch(() => { });
|
|
449
|
+
// Patch frames
|
|
450
|
+
patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
451
|
+
// Patch ElementHandle selectors
|
|
452
|
+
patchElementHandle(page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
453
|
+
}
|
|
454
|
+
// ============================================================================
|
|
455
|
+
// ElementHandle patching — PUPPETEER-SPECIFIC
|
|
456
|
+
// ============================================================================
|
|
457
|
+
function patchElementHandle(page, cfg, cursor, raw, rawKb, originals, stealth) {
|
|
458
|
+
const orig$ = page.$.bind(page);
|
|
459
|
+
const orig$$ = page.$$.bind(page);
|
|
460
|
+
const origWaitForSelector = page.waitForSelector.bind(page);
|
|
461
|
+
page.$ = async (selector) => {
|
|
462
|
+
const el = await orig$(selector);
|
|
463
|
+
if (el)
|
|
464
|
+
patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
465
|
+
return el;
|
|
466
|
+
};
|
|
467
|
+
page.$$ = async (selector) => {
|
|
468
|
+
const els = await orig$$(selector);
|
|
469
|
+
for (const el of els) {
|
|
470
|
+
patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
471
|
+
}
|
|
472
|
+
return els;
|
|
473
|
+
};
|
|
474
|
+
page.waitForSelector = async (selector, options) => {
|
|
475
|
+
const el = await origWaitForSelector(selector, options);
|
|
476
|
+
if (el)
|
|
477
|
+
patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
478
|
+
return el;
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth) {
|
|
482
|
+
if (el._humanPatched)
|
|
483
|
+
return;
|
|
484
|
+
el._humanPatched = true;
|
|
485
|
+
const origElClick = el.click.bind(el);
|
|
486
|
+
const origElHover = el.hover.bind(el);
|
|
487
|
+
const origElType = el.type.bind(el);
|
|
488
|
+
const origElPress = el.press?.bind(el);
|
|
489
|
+
const origElTap = el.tap?.bind(el);
|
|
490
|
+
const origElFocus = el.focus?.bind(el);
|
|
491
|
+
const origElDragAndDrop = el.dragAndDrop?.bind(el);
|
|
492
|
+
const origElSelect = el.select?.bind(el);
|
|
493
|
+
const origElDrop = el.drop?.bind(el);
|
|
494
|
+
// --- Nested selectors ---
|
|
495
|
+
const origEl$ = el.$.bind(el);
|
|
496
|
+
const origEl$$ = el.$$.bind(el);
|
|
497
|
+
const origElWaitForSelector = el.waitForSelector.bind(el);
|
|
498
|
+
el.$ = async (selector) => {
|
|
499
|
+
const child = await origEl$(selector);
|
|
500
|
+
if (child)
|
|
501
|
+
patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
502
|
+
return child;
|
|
503
|
+
};
|
|
504
|
+
el.$$ = async (selector) => {
|
|
505
|
+
const children = await origEl$$(selector);
|
|
506
|
+
for (const child of children) {
|
|
507
|
+
patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
508
|
+
}
|
|
509
|
+
return children;
|
|
510
|
+
};
|
|
511
|
+
el.waitForSelector = async (selector, options) => {
|
|
512
|
+
const child = await origElWaitForSelector(selector, options);
|
|
513
|
+
if (child)
|
|
514
|
+
patchSingleElementHandle(child, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
515
|
+
return child;
|
|
516
|
+
};
|
|
517
|
+
// --- Helper: get box and move cursor ---
|
|
518
|
+
const moveToElement = async () => {
|
|
519
|
+
await page._ensureCursorInit();
|
|
520
|
+
const box = await el.boundingBox();
|
|
521
|
+
if (!box)
|
|
522
|
+
return null;
|
|
523
|
+
const isInp = await isInputElementHandle(stealth, el);
|
|
524
|
+
const target = clickTarget(box, isInp, cfg);
|
|
525
|
+
if (cfg.idle_between_actions) {
|
|
526
|
+
await humanIdle(raw, rand(cfg.idle_between_duration[0], cfg.idle_between_duration[1]), cursor.x, cursor.y, cfg);
|
|
527
|
+
}
|
|
528
|
+
await humanMove(raw, cursor.x, cursor.y, target.x, target.y, cfg);
|
|
529
|
+
cursor.x = target.x;
|
|
530
|
+
cursor.y = target.y;
|
|
531
|
+
return { box, isInp };
|
|
532
|
+
};
|
|
533
|
+
// --- el.click() ---
|
|
534
|
+
el.click = async (options) => {
|
|
535
|
+
const info = await moveToElement();
|
|
536
|
+
if (!info)
|
|
537
|
+
return origElClick(options);
|
|
538
|
+
const clickCount = options?.clickCount ?? options?.count ?? 1;
|
|
539
|
+
if (clickCount >= 2) {
|
|
540
|
+
await humanClick(raw, info.isInp, cfg);
|
|
541
|
+
await sleep(rand(40, 90));
|
|
542
|
+
await raw.down({ clickCount: 2 });
|
|
543
|
+
await sleep(rand(30, 60));
|
|
544
|
+
await raw.up({ clickCount: 2 });
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
await humanClick(raw, info.isInp, cfg);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
// --- el.hover() ---
|
|
551
|
+
el.hover = async () => {
|
|
552
|
+
const info = await moveToElement();
|
|
553
|
+
if (!info)
|
|
554
|
+
return origElHover();
|
|
555
|
+
};
|
|
556
|
+
// --- el.type() ---
|
|
557
|
+
el.type = async (text, options) => {
|
|
558
|
+
const info = await moveToElement();
|
|
559
|
+
if (!info)
|
|
560
|
+
return origElType(text, options);
|
|
561
|
+
await humanClick(raw, info.isInp, cfg);
|
|
562
|
+
await sleep(rand(100, 250));
|
|
563
|
+
const cdp = await stealth.getCdpSession().catch(() => null);
|
|
564
|
+
await humanType(page, rawKb, text, cfg, cdp);
|
|
565
|
+
};
|
|
566
|
+
// --- el.press() ---
|
|
567
|
+
if (origElPress) {
|
|
568
|
+
el.press = async (key, options) => {
|
|
569
|
+
await sleep(rand(20, 60));
|
|
570
|
+
await originals.keyboardDown(key);
|
|
571
|
+
await sleep(randRange(cfg.key_hold));
|
|
572
|
+
await originals.keyboardUp(key);
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
// --- el.tap() ---
|
|
576
|
+
if (origElTap) {
|
|
577
|
+
el.tap = async () => {
|
|
578
|
+
const info = await moveToElement();
|
|
579
|
+
if (!info)
|
|
580
|
+
return origElTap();
|
|
581
|
+
await humanClick(raw, info.isInp, cfg);
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
// --- el.focus() ---
|
|
585
|
+
if (origElFocus) {
|
|
586
|
+
el.focus = async () => {
|
|
587
|
+
const info = await moveToElement();
|
|
588
|
+
if (!info)
|
|
589
|
+
return origElFocus();
|
|
590
|
+
await humanClick(raw, info.isInp, cfg);
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
// --- el.select() ---
|
|
594
|
+
if (origElSelect) {
|
|
595
|
+
el.select = async (...values) => {
|
|
596
|
+
const info = await moveToElement();
|
|
597
|
+
if (!info)
|
|
598
|
+
return origElSelect(...values);
|
|
599
|
+
await humanClick(raw, false, cfg);
|
|
600
|
+
await sleep(rand(100, 300));
|
|
601
|
+
return origElSelect(...values);
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
// --- el.drop() ---
|
|
605
|
+
if (origElDrop) {
|
|
606
|
+
el.drop = async (draggable, options) => {
|
|
607
|
+
const srcBox = await draggable.boundingBox();
|
|
608
|
+
const tgtBox = await el.boundingBox();
|
|
609
|
+
if (srcBox && tgtBox) {
|
|
610
|
+
const sx = srcBox.x + srcBox.width / 2;
|
|
611
|
+
const sy = srcBox.y + srcBox.height / 2;
|
|
612
|
+
const tx = tgtBox.x + tgtBox.width / 2;
|
|
613
|
+
const ty = tgtBox.y + tgtBox.height / 2;
|
|
614
|
+
await page._ensureCursorInit();
|
|
615
|
+
await humanMove(raw, cursor.x, cursor.y, sx, sy, cfg);
|
|
616
|
+
cursor.x = sx;
|
|
617
|
+
cursor.y = sy;
|
|
618
|
+
await sleep(rand(100, 200));
|
|
619
|
+
await originals.mouseDown();
|
|
620
|
+
await sleep(rand(80, 150));
|
|
621
|
+
await humanMove(raw, cursor.x, cursor.y, tx, ty, cfg);
|
|
622
|
+
cursor.x = tx;
|
|
623
|
+
cursor.y = ty;
|
|
624
|
+
await sleep(rand(80, 150));
|
|
625
|
+
await originals.mouseUp();
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
return origElDrop(draggable, options);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
// --- el.dragAndDrop() ---
|
|
633
|
+
if (origElDragAndDrop) {
|
|
634
|
+
el.dragAndDrop = async (targetEl, options) => {
|
|
635
|
+
const srcBox = await el.boundingBox();
|
|
636
|
+
const tgtBox = await targetEl.boundingBox();
|
|
637
|
+
if (srcBox && tgtBox) {
|
|
638
|
+
const sx = srcBox.x + srcBox.width / 2;
|
|
639
|
+
const sy = srcBox.y + srcBox.height / 2;
|
|
640
|
+
const tx = tgtBox.x + tgtBox.width / 2;
|
|
641
|
+
const ty = tgtBox.y + tgtBox.height / 2;
|
|
642
|
+
await page._ensureCursorInit();
|
|
643
|
+
await humanMove(raw, cursor.x, cursor.y, sx, sy, cfg);
|
|
644
|
+
cursor.x = sx;
|
|
645
|
+
cursor.y = sy;
|
|
646
|
+
await sleep(rand(100, 200));
|
|
647
|
+
await originals.mouseDown();
|
|
648
|
+
await sleep(rand(80, 150));
|
|
649
|
+
await humanMove(raw, cursor.x, cursor.y, tx, ty, cfg);
|
|
650
|
+
cursor.x = tx;
|
|
651
|
+
cursor.y = ty;
|
|
652
|
+
await sleep(rand(80, 150));
|
|
653
|
+
await originals.mouseUp();
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
return origElDragAndDrop(targetEl, options);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// ============================================================================
|
|
662
|
+
// Frame-level patching — native Puppeteer Frame methods only
|
|
663
|
+
// Puppeteer Frame has: click, hover, type, select, focus, tap
|
|
664
|
+
// ============================================================================
|
|
665
|
+
function patchFrames(page, cfg, cursor, raw, rawKb, originals, stealth) {
|
|
666
|
+
for (const frame of iterFrames(page)) {
|
|
667
|
+
patchSingleFrame(frame, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function patchSingleFrame(frame, page, cfg, cursor, raw, rawKb, originals, stealth) {
|
|
671
|
+
if (frame._humanPatched)
|
|
672
|
+
return;
|
|
673
|
+
frame._humanPatched = true;
|
|
674
|
+
const origFrameSelect = frame.select.bind(frame);
|
|
675
|
+
frame.click = async (selector, options) => {
|
|
676
|
+
await page.click(selector, options);
|
|
677
|
+
};
|
|
678
|
+
frame.hover = async (selector, options) => {
|
|
679
|
+
await page.hover(selector, options);
|
|
680
|
+
};
|
|
681
|
+
frame.type = async (selector, text, options) => {
|
|
682
|
+
await page.type(selector, text, options);
|
|
683
|
+
};
|
|
684
|
+
frame.select = async (selector, ...values) => {
|
|
685
|
+
await page.hover(selector);
|
|
686
|
+
await sleep(rand(100, 300));
|
|
687
|
+
return origFrameSelect(selector, ...values);
|
|
688
|
+
};
|
|
689
|
+
frame.focus = async (selector) => {
|
|
690
|
+
await page.focus(selector);
|
|
691
|
+
};
|
|
692
|
+
frame.tap = async (selector, options) => {
|
|
693
|
+
await page.click(selector, options);
|
|
694
|
+
};
|
|
695
|
+
// Patch frame.$() to return patched ElementHandles
|
|
696
|
+
const origFrame$ = frame.$.bind(frame);
|
|
697
|
+
const origFrame$$ = frame.$$.bind(frame);
|
|
698
|
+
const origFrameWaitForSelector = frame.waitForSelector.bind(frame);
|
|
699
|
+
frame.$ = async (selector) => {
|
|
700
|
+
const el = await origFrame$(selector);
|
|
701
|
+
if (el)
|
|
702
|
+
patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
703
|
+
return el;
|
|
704
|
+
};
|
|
705
|
+
frame.$$ = async (selector) => {
|
|
706
|
+
const els = await origFrame$$(selector);
|
|
707
|
+
for (const el of els) {
|
|
708
|
+
patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
709
|
+
}
|
|
710
|
+
return els;
|
|
711
|
+
};
|
|
712
|
+
frame.waitForSelector = async (selector, options) => {
|
|
713
|
+
const el = await origFrameWaitForSelector(selector, options);
|
|
714
|
+
if (el)
|
|
715
|
+
patchSingleElementHandle(el, page, cfg, cursor, raw, rawKb, originals, stealth);
|
|
716
|
+
return el;
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function* iterFrames(page) {
|
|
720
|
+
try {
|
|
721
|
+
const mainFrame = page.mainFrame();
|
|
722
|
+
yield mainFrame;
|
|
723
|
+
for (const child of mainFrame.childFrames()) {
|
|
724
|
+
yield child;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
catch { }
|
|
728
|
+
}
|
|
729
|
+
// ============================================================================
|
|
730
|
+
// Browser-level patching
|
|
731
|
+
// ============================================================================
|
|
732
|
+
export function patchBrowser(browser, cfg) {
|
|
733
|
+
browser.pages().then(pages => {
|
|
734
|
+
for (const page of pages) {
|
|
735
|
+
if (!page._original) {
|
|
736
|
+
patchPage(page, cfg, new CursorState());
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}).catch(() => { });
|
|
740
|
+
const origNewPage = browser.newPage.bind(browser);
|
|
741
|
+
browser.newPage = async () => {
|
|
742
|
+
const page = await origNewPage();
|
|
743
|
+
if (!page._original) {
|
|
744
|
+
patchPage(page, cfg, new CursorState());
|
|
745
|
+
}
|
|
746
|
+
return page;
|
|
747
|
+
};
|
|
748
|
+
// v21: createIncognitoBrowserContext
|
|
749
|
+
// v22+: createBrowserContext (renamed in puppeteer/puppeteer#11834)
|
|
750
|
+
for (const methodName of ['createBrowserContext', 'createIncognitoBrowserContext']) {
|
|
751
|
+
if (typeof browser[methodName] === 'function') {
|
|
752
|
+
const origCreateContext = browser[methodName].bind(browser);
|
|
753
|
+
browser[methodName] = async (options) => {
|
|
754
|
+
const context = await origCreateContext(options);
|
|
755
|
+
const origCtxNewPage = context.newPage.bind(context);
|
|
756
|
+
context.newPage = async () => {
|
|
757
|
+
const page = await origCtxNewPage();
|
|
758
|
+
if (!page._original) {
|
|
759
|
+
patchPage(page, cfg, new CursorState());
|
|
760
|
+
}
|
|
761
|
+
return page;
|
|
762
|
+
};
|
|
763
|
+
return context;
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
browser.on('targetcreated', async (target) => {
|
|
768
|
+
try {
|
|
769
|
+
if (target.type() === 'page') {
|
|
770
|
+
const page = await target.page();
|
|
771
|
+
if (page && !page._original) {
|
|
772
|
+
patchPage(page, cfg, new CursorState());
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch { }
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
export { patchPage };
|
|
780
|
+
//# sourceMappingURL=index.js.map
|