surfagent 1.0.6 → 1.0.7
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/api/act.d.ts +5 -0
- package/dist/api/act.js +67 -2
- package/dist/api/server.js +10 -1
- package/package.json +1 -1
- package/src/api/act.ts +75 -2
- package/src/api/server.ts +11 -1
package/dist/api/act.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface ClickRequest {
|
|
|
23
23
|
tab: string;
|
|
24
24
|
selector?: string;
|
|
25
25
|
text?: string;
|
|
26
|
+
waitAfter?: number;
|
|
26
27
|
}
|
|
27
28
|
export declare function clickElement(request: ClickRequest, options: {
|
|
28
29
|
port?: number;
|
|
@@ -70,6 +71,10 @@ export declare function readPage(tabPattern: string, options: {
|
|
|
70
71
|
host?: string;
|
|
71
72
|
selector?: string;
|
|
72
73
|
}): Promise<any>;
|
|
74
|
+
export declare function dismissOverlays(tabPattern: string, options: {
|
|
75
|
+
port?: number;
|
|
76
|
+
host?: string;
|
|
77
|
+
}): Promise<any>;
|
|
73
78
|
export interface CaptchaRequest {
|
|
74
79
|
tab: string;
|
|
75
80
|
action: 'detect' | 'read' | 'next' | 'prev' | 'submit' | 'audio' | 'restart';
|
package/dist/api/act.js
CHANGED
|
@@ -135,9 +135,9 @@ export async function clickElement(request, options) {
|
|
|
135
135
|
}
|
|
136
136
|
if (!el && text) {
|
|
137
137
|
const lower = text.toLowerCase();
|
|
138
|
-
const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [onclick]');
|
|
138
|
+
const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [role="option"], [role="menuitem"], [role="listitem"], [role="tab"], [role="link"], li[aria-label], [onclick]');
|
|
139
139
|
for (const candidate of all) {
|
|
140
|
-
const t = (candidate.innerText || candidate.textContent || candidate.value || '').trim();
|
|
140
|
+
const t = (candidate.innerText || candidate.textContent || candidate.value || candidate.getAttribute('aria-label') || '').trim();
|
|
141
141
|
if (t.toLowerCase().includes(lower)) { el = candidate; break; }
|
|
142
142
|
}
|
|
143
143
|
}
|
|
@@ -159,6 +159,10 @@ export async function clickElement(request, options) {
|
|
|
159
159
|
`,
|
|
160
160
|
returnByValue: true
|
|
161
161
|
});
|
|
162
|
+
// Wait after click if requested (for page to settle after navigation/SPA route change)
|
|
163
|
+
if (request.waitAfter && request.waitAfter > 0) {
|
|
164
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(request.waitAfter, 10000)));
|
|
165
|
+
}
|
|
162
166
|
await client.close();
|
|
163
167
|
return result.result.value;
|
|
164
168
|
}
|
|
@@ -357,6 +361,67 @@ export async function readPage(tabPattern, options) {
|
|
|
357
361
|
throw error;
|
|
358
362
|
}
|
|
359
363
|
}
|
|
364
|
+
const DISMISS_OVERLAYS_SCRIPT = `
|
|
365
|
+
(function() {
|
|
366
|
+
const dismissed = [];
|
|
367
|
+
|
|
368
|
+
// Common cookie consent button patterns (multi-language)
|
|
369
|
+
const consentPatterns = [
|
|
370
|
+
'reject all', 'reject', 'decline', 'deny',
|
|
371
|
+
'accept all', 'accept', 'godta alle', 'godta',
|
|
372
|
+
'alle ablehnen', 'ablehnen', 'tout refuser', 'refuser',
|
|
373
|
+
'rechazar todo', 'rechazar', 'rifiuta tutto', 'rifiuta',
|
|
374
|
+
'bare nødvendige', 'only necessary', 'nur notwendige',
|
|
375
|
+
'manage preferences', 'cookie settings',
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
// Try cookie consent buttons
|
|
379
|
+
for (const btn of document.querySelectorAll('button, a[role="button"]')) {
|
|
380
|
+
const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
|
|
381
|
+
if (text.length > 50 || text.length < 2) continue;
|
|
382
|
+
for (const pattern of consentPatterns) {
|
|
383
|
+
if (text === pattern || text.startsWith(pattern)) {
|
|
384
|
+
btn.click();
|
|
385
|
+
dismissed.push({ type: 'cookie', text: text.substring(0, 40) });
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (dismissed.length) break;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Try closing modal dialogs (X button, close button, dismiss)
|
|
393
|
+
if (!dismissed.length) {
|
|
394
|
+
for (const btn of document.querySelectorAll('[aria-label*="Close" i], [aria-label*="Dismiss" i], [aria-label*="Lukk" i], [aria-label*="Schließen" i], [aria-label*="Fermer" i]')) {
|
|
395
|
+
const dialog = btn.closest('[role="dialog"], [role="alertdialog"], .modal, [data-overlay]');
|
|
396
|
+
if (dialog) {
|
|
397
|
+
btn.click();
|
|
398
|
+
dismissed.push({ type: 'dialog', text: btn.getAttribute('aria-label') || 'close' });
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return { dismissed, count: dismissed.length };
|
|
405
|
+
})()
|
|
406
|
+
`;
|
|
407
|
+
export async function dismissOverlays(tabPattern, options) {
|
|
408
|
+
const port = options.port || 9222;
|
|
409
|
+
const host = options.host || 'localhost';
|
|
410
|
+
const tab = await resolveTab(tabPattern, port, host);
|
|
411
|
+
const client = await connectToTab(tab.id, port, host);
|
|
412
|
+
try {
|
|
413
|
+
const r = await client.Runtime.evaluate({
|
|
414
|
+
expression: DISMISS_OVERLAYS_SCRIPT,
|
|
415
|
+
returnByValue: true
|
|
416
|
+
});
|
|
417
|
+
await client.close();
|
|
418
|
+
return r.result.value;
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
await client.close();
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
360
425
|
const CAPTCHA_DETECT_SCRIPT = `
|
|
361
426
|
(function() {
|
|
362
427
|
// Find captcha iframes on the page
|
package/dist/api/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import http from 'node:http';
|
|
3
3
|
import { reconUrl, reconTab } from './recon.js';
|
|
4
|
-
import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract } from './act.js';
|
|
4
|
+
import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract, dismissOverlays } from './act.js';
|
|
5
5
|
import { getAllTabs } from '../chrome/tabs.js';
|
|
6
6
|
const PORT = parseInt(process.env.API_PORT || '3456', 10);
|
|
7
7
|
const CDP_PORT = parseInt(process.env.CDP_PORT || '9222', 10);
|
|
@@ -90,6 +90,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
90
90
|
const result = await scrollPage(body, { port: CDP_PORT, host: CDP_HOST });
|
|
91
91
|
return json(res, 200, result);
|
|
92
92
|
}
|
|
93
|
+
// POST /dismiss — dismiss cookie banners, modals, overlays
|
|
94
|
+
if (path === '/dismiss' && req.method === 'POST') {
|
|
95
|
+
const body = parseBody(await readBody(req));
|
|
96
|
+
if (!body.tab) {
|
|
97
|
+
return json(res, 400, { error: 'Provide "tab"' });
|
|
98
|
+
}
|
|
99
|
+
const result = await dismissOverlays(body.tab, { port: CDP_PORT, host: CDP_HOST });
|
|
100
|
+
return json(res, 200, result);
|
|
101
|
+
}
|
|
93
102
|
// POST /captcha — detect and interact with captchas
|
|
94
103
|
if (path === '/captcha' && req.method === 'POST') {
|
|
95
104
|
const body = parseBody(await readBody(req));
|
package/package.json
CHANGED
package/src/api/act.ts
CHANGED
|
@@ -148,6 +148,7 @@ export interface ClickRequest {
|
|
|
148
148
|
tab: string;
|
|
149
149
|
selector?: string;
|
|
150
150
|
text?: string;
|
|
151
|
+
waitAfter?: number; // ms to wait after click for page to settle
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
export async function clickElement(
|
|
@@ -173,9 +174,9 @@ export async function clickElement(
|
|
|
173
174
|
}
|
|
174
175
|
if (!el && text) {
|
|
175
176
|
const lower = text.toLowerCase();
|
|
176
|
-
const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [onclick]');
|
|
177
|
+
const all = document.querySelectorAll('a, button, input[type="submit"], [role="button"], [role="option"], [role="menuitem"], [role="listitem"], [role="tab"], [role="link"], li[aria-label], [onclick]');
|
|
177
178
|
for (const candidate of all) {
|
|
178
|
-
const t = (candidate.innerText || candidate.textContent || candidate.value || '').trim();
|
|
179
|
+
const t = (candidate.innerText || candidate.textContent || candidate.value || candidate.getAttribute('aria-label') || '').trim();
|
|
179
180
|
if (t.toLowerCase().includes(lower)) { el = candidate; break; }
|
|
180
181
|
}
|
|
181
182
|
}
|
|
@@ -198,6 +199,11 @@ export async function clickElement(
|
|
|
198
199
|
returnByValue: true
|
|
199
200
|
});
|
|
200
201
|
|
|
202
|
+
// Wait after click if requested (for page to settle after navigation/SPA route change)
|
|
203
|
+
if (request.waitAfter && request.waitAfter > 0) {
|
|
204
|
+
await new Promise<void>(resolve => setTimeout(resolve, Math.min(request.waitAfter!, 10000)));
|
|
205
|
+
}
|
|
206
|
+
|
|
201
207
|
await client.close();
|
|
202
208
|
return result.result.value as any;
|
|
203
209
|
} catch (error) {
|
|
@@ -434,6 +440,73 @@ export async function readPage(
|
|
|
434
440
|
}
|
|
435
441
|
}
|
|
436
442
|
|
|
443
|
+
const DISMISS_OVERLAYS_SCRIPT = `
|
|
444
|
+
(function() {
|
|
445
|
+
const dismissed = [];
|
|
446
|
+
|
|
447
|
+
// Common cookie consent button patterns (multi-language)
|
|
448
|
+
const consentPatterns = [
|
|
449
|
+
'reject all', 'reject', 'decline', 'deny',
|
|
450
|
+
'accept all', 'accept', 'godta alle', 'godta',
|
|
451
|
+
'alle ablehnen', 'ablehnen', 'tout refuser', 'refuser',
|
|
452
|
+
'rechazar todo', 'rechazar', 'rifiuta tutto', 'rifiuta',
|
|
453
|
+
'bare nødvendige', 'only necessary', 'nur notwendige',
|
|
454
|
+
'manage preferences', 'cookie settings',
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
// Try cookie consent buttons
|
|
458
|
+
for (const btn of document.querySelectorAll('button, a[role="button"]')) {
|
|
459
|
+
const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
|
|
460
|
+
if (text.length > 50 || text.length < 2) continue;
|
|
461
|
+
for (const pattern of consentPatterns) {
|
|
462
|
+
if (text === pattern || text.startsWith(pattern)) {
|
|
463
|
+
btn.click();
|
|
464
|
+
dismissed.push({ type: 'cookie', text: text.substring(0, 40) });
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (dismissed.length) break;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Try closing modal dialogs (X button, close button, dismiss)
|
|
472
|
+
if (!dismissed.length) {
|
|
473
|
+
for (const btn of document.querySelectorAll('[aria-label*="Close" i], [aria-label*="Dismiss" i], [aria-label*="Lukk" i], [aria-label*="Schließen" i], [aria-label*="Fermer" i]')) {
|
|
474
|
+
const dialog = btn.closest('[role="dialog"], [role="alertdialog"], .modal, [data-overlay]');
|
|
475
|
+
if (dialog) {
|
|
476
|
+
btn.click();
|
|
477
|
+
dismissed.push({ type: 'dialog', text: btn.getAttribute('aria-label') || 'close' });
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return { dismissed, count: dismissed.length };
|
|
484
|
+
})()
|
|
485
|
+
`;
|
|
486
|
+
|
|
487
|
+
export async function dismissOverlays(
|
|
488
|
+
tabPattern: string,
|
|
489
|
+
options: { port?: number; host?: string }
|
|
490
|
+
): Promise<any> {
|
|
491
|
+
const port = options.port || 9222;
|
|
492
|
+
const host = options.host || 'localhost';
|
|
493
|
+
|
|
494
|
+
const tab = await resolveTab(tabPattern, port, host);
|
|
495
|
+
const client = await connectToTab(tab.id, port, host);
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
const r = await client.Runtime.evaluate({
|
|
499
|
+
expression: DISMISS_OVERLAYS_SCRIPT,
|
|
500
|
+
returnByValue: true
|
|
501
|
+
});
|
|
502
|
+
await client.close();
|
|
503
|
+
return r.result.value;
|
|
504
|
+
} catch (error) {
|
|
505
|
+
await client.close();
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
437
510
|
const CAPTCHA_DETECT_SCRIPT = `
|
|
438
511
|
(function() {
|
|
439
512
|
// Find captcha iframes on the page
|
package/src/api/server.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import { reconUrl, reconTab } from './recon.js';
|
|
5
|
-
import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract } from './act.js';
|
|
5
|
+
import { fillFields, clickElement, scrollPage, navigatePage, evalInTab, focusTab, readPage, captchaInteract, dismissOverlays } from './act.js';
|
|
6
6
|
import { getAllTabs } from '../chrome/tabs.js';
|
|
7
7
|
|
|
8
8
|
const PORT = parseInt(process.env.API_PORT || '3456', 10);
|
|
@@ -110,6 +110,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
110
110
|
return json(res, 200, result);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// POST /dismiss — dismiss cookie banners, modals, overlays
|
|
114
|
+
if (path === '/dismiss' && req.method === 'POST') {
|
|
115
|
+
const body = parseBody(await readBody(req));
|
|
116
|
+
if (!body.tab) {
|
|
117
|
+
return json(res, 400, { error: 'Provide "tab"' });
|
|
118
|
+
}
|
|
119
|
+
const result = await dismissOverlays(body.tab, { port: CDP_PORT, host: CDP_HOST });
|
|
120
|
+
return json(res, 200, result);
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
// POST /captcha — detect and interact with captchas
|
|
114
124
|
if (path === '/captcha' && req.method === 'POST') {
|
|
115
125
|
const body = parseBody(await readBody(req));
|