imperium-crawl 2.3.1 → 2.5.1
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 +146 -11
- package/dist/cli-explore.d.ts +30 -0
- package/dist/cli-explore.d.ts.map +1 -0
- package/dist/cli-explore.js +427 -0
- package/dist/cli-explore.js.map +1 -0
- package/dist/cli-recorder.d.ts +44 -0
- package/dist/cli-recorder.d.ts.map +1 -0
- package/dist/cli-recorder.js +67 -0
- package/dist/cli-recorder.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +51 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +31 -1
- package/dist/constants.js.map +1 -1
- package/dist/flows/engine.d.ts +7 -0
- package/dist/flows/engine.d.ts.map +1 -0
- package/dist/flows/engine.js +183 -0
- package/dist/flows/engine.js.map +1 -0
- package/dist/flows/index.d.ts +6 -0
- package/dist/flows/index.d.ts.map +1 -0
- package/dist/flows/index.js +6 -0
- package/dist/flows/index.js.map +1 -0
- package/dist/flows/server.d.ts +11 -0
- package/dist/flows/server.d.ts.map +1 -0
- package/dist/flows/server.js +81 -0
- package/dist/flows/server.js.map +1 -0
- package/dist/flows/smart-target.d.ts +9 -0
- package/dist/flows/smart-target.d.ts.map +1 -0
- package/dist/flows/smart-target.js +84 -0
- package/dist/flows/smart-target.js.map +1 -0
- package/dist/flows/storage.d.ts +26 -0
- package/dist/flows/storage.d.ts.map +1 -0
- package/dist/flows/storage.js +118 -0
- package/dist/flows/storage.js.map +1 -0
- package/dist/flows/templates.d.ts +4 -0
- package/dist/flows/templates.d.ts.map +1 -0
- package/dist/flows/templates.js +35 -0
- package/dist/flows/templates.js.map +1 -0
- package/dist/flows/types.d.ts +3356 -0
- package/dist/flows/types.d.ts.map +1 -0
- package/dist/flows/types.js +133 -0
- package/dist/flows/types.js.map +1 -0
- package/dist/knowledge/index.d.ts +1 -0
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +1 -0
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/record-browser.d.ts +17 -0
- package/dist/knowledge/record-browser.d.ts.map +1 -0
- package/dist/knowledge/record-browser.js +29 -0
- package/dist/knowledge/record-browser.js.map +1 -0
- package/dist/knowledge/store.d.ts +19 -0
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +63 -4
- package/dist/knowledge/store.js.map +1 -1
- package/dist/llm/retry.d.ts +4 -2
- package/dist/llm/retry.d.ts.map +1 -1
- package/dist/llm/retry.js +15 -4
- package/dist/llm/retry.js.map +1 -1
- package/dist/sessions/browser-connect.d.ts +30 -0
- package/dist/sessions/browser-connect.d.ts.map +1 -0
- package/dist/sessions/browser-connect.js +68 -0
- package/dist/sessions/browser-connect.js.map +1 -0
- package/dist/sessions/browser-state.d.ts +35 -0
- package/dist/sessions/browser-state.d.ts.map +1 -0
- package/dist/sessions/browser-state.js +74 -0
- package/dist/sessions/browser-state.js.map +1 -0
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/inject-cookies.d.ts +20 -0
- package/dist/sessions/inject-cookies.d.ts.map +1 -0
- package/dist/sessions/inject-cookies.js +57 -0
- package/dist/sessions/inject-cookies.js.map +1 -0
- package/dist/sessions/manager.d.ts +31 -1
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +97 -6
- package/dist/sessions/manager.js.map +1 -1
- package/dist/sessions/types.d.ts +2 -0
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/skills/chain.d.ts +61 -0
- package/dist/skills/chain.d.ts.map +1 -0
- package/dist/skills/chain.js +182 -0
- package/dist/skills/chain.js.map +1 -0
- package/dist/skills/conditions.d.ts +14 -0
- package/dist/skills/conditions.d.ts.map +1 -0
- package/dist/skills/conditions.js +208 -0
- package/dist/skills/conditions.js.map +1 -0
- package/dist/skills/manager.d.ts +47 -2
- package/dist/skills/manager.d.ts.map +1 -1
- package/dist/skills/manager.js.map +1 -1
- package/dist/skills/parameters.d.ts +49 -0
- package/dist/skills/parameters.d.ts.map +1 -0
- package/dist/skills/parameters.js +157 -0
- package/dist/skills/parameters.js.map +1 -0
- package/dist/snapshot/store.d.ts +8 -0
- package/dist/snapshot/store.d.ts.map +1 -1
- package/dist/snapshot/store.js +48 -0
- package/dist/snapshot/store.js.map +1 -1
- package/dist/stealth/antibot-detector.d.ts +1 -1
- package/dist/stealth/antibot-detector.d.ts.map +1 -1
- package/dist/stealth/antibot-detector.js +56 -0
- package/dist/stealth/antibot-detector.js.map +1 -1
- package/dist/stealth/browser-image-extract.d.ts +43 -0
- package/dist/stealth/browser-image-extract.d.ts.map +1 -0
- package/dist/stealth/browser-image-extract.js +268 -0
- package/dist/stealth/browser-image-extract.js.map +1 -0
- package/dist/stealth/browser.d.ts +5 -0
- package/dist/stealth/browser.d.ts.map +1 -1
- package/dist/stealth/browser.js +82 -1
- package/dist/stealth/browser.js.map +1 -1
- package/dist/stealth/chrome-profile.d.ts +1 -0
- package/dist/stealth/chrome-profile.d.ts.map +1 -1
- package/dist/stealth/chrome-profile.js +28 -5
- package/dist/stealth/chrome-profile.js.map +1 -1
- package/dist/stealth/detector.d.ts +10 -1
- package/dist/stealth/detector.d.ts.map +1 -1
- package/dist/stealth/detector.js +117 -25
- package/dist/stealth/detector.js.map +1 -1
- package/dist/stealth/headers.d.ts +1 -1
- package/dist/stealth/headers.d.ts.map +1 -1
- package/dist/stealth/headers.js +94 -2
- package/dist/stealth/headers.js.map +1 -1
- package/dist/stealth/index.d.ts +5 -0
- package/dist/stealth/index.d.ts.map +1 -1
- package/dist/stealth/index.js +257 -27
- package/dist/stealth/index.js.map +1 -1
- package/dist/stealth/proxy.d.ts +40 -1
- package/dist/stealth/proxy.d.ts.map +1 -1
- package/dist/stealth/proxy.js +90 -6
- package/dist/stealth/proxy.js.map +1 -1
- package/dist/tools/action-executor.d.ts +66 -0
- package/dist/tools/action-executor.d.ts.map +1 -0
- package/dist/tools/action-executor.js +403 -0
- package/dist/tools/action-executor.js.map +1 -0
- package/dist/tools/batch-download.d.ts +33 -0
- package/dist/tools/batch-download.d.ts.map +1 -0
- package/dist/tools/batch-download.js +208 -0
- package/dist/tools/batch-download.js.map +1 -0
- package/dist/tools/batch-scrape.d.ts +2 -2
- package/dist/tools/browser.d.ts +100 -0
- package/dist/tools/browser.d.ts.map +1 -0
- package/dist/tools/browser.js +448 -0
- package/dist/tools/browser.js.map +1 -0
- package/dist/tools/crawl.d.ts +2 -2
- package/dist/tools/create-skill.d.ts +2 -2
- package/dist/tools/discover-apis.d.ts +1 -1
- package/dist/tools/discover-apis.d.ts.map +1 -1
- package/dist/tools/discover-apis.js +3 -0
- package/dist/tools/discover-apis.js.map +1 -1
- package/dist/tools/download.d.ts +39 -6
- package/dist/tools/download.d.ts.map +1 -1
- package/dist/tools/download.js +248 -44
- package/dist/tools/download.js.map +1 -1
- package/dist/tools/extract.d.ts +1 -1
- package/dist/tools/image-search.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +26 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspect-flow.d.ts +24 -0
- package/dist/tools/inspect-flow.d.ts.map +1 -0
- package/dist/tools/inspect-flow.js +23 -0
- package/dist/tools/inspect-flow.js.map +1 -0
- package/dist/tools/instagram.d.ts +2 -2
- package/dist/tools/interact.d.ts +91 -50
- package/dist/tools/interact.d.ts.map +1 -1
- package/dist/tools/interact.js +80 -299
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/knowledge.d.ts +24 -0
- package/dist/tools/knowledge.d.ts.map +1 -0
- package/dist/tools/knowledge.js +99 -0
- package/dist/tools/knowledge.js.map +1 -0
- package/dist/tools/list-flows.d.ts +21 -0
- package/dist/tools/list-flows.d.ts.map +1 -0
- package/dist/tools/list-flows.js +18 -0
- package/dist/tools/list-flows.js.map +1 -0
- package/dist/tools/list-skills.js +1 -1
- package/dist/tools/list-skills.js.map +1 -1
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/manifest.js +48 -0
- package/dist/tools/manifest.js.map +1 -1
- package/dist/tools/monitor-websocket.d.ts +1 -1
- package/dist/tools/monitor.d.ts +46 -0
- package/dist/tools/monitor.d.ts.map +1 -0
- package/dist/tools/monitor.js +213 -0
- package/dist/tools/monitor.js.map +1 -0
- package/dist/tools/news-search.d.ts +1 -1
- package/dist/tools/pdf-extract.d.ts +38 -0
- package/dist/tools/pdf-extract.d.ts.map +1 -0
- package/dist/tools/pdf-extract.js +244 -0
- package/dist/tools/pdf-extract.js.map +1 -0
- package/dist/tools/query-api.d.ts +6 -6
- package/dist/tools/readability.d.ts +2 -2
- package/dist/tools/record-flow.d.ts +39 -0
- package/dist/tools/record-flow.d.ts.map +1 -0
- package/dist/tools/record-flow.js +406 -0
- package/dist/tools/record-flow.js.map +1 -0
- package/dist/tools/reddit.d.ts +4 -4
- package/dist/tools/run-flow.d.ts +54 -0
- package/dist/tools/run-flow.d.ts.map +1 -0
- package/dist/tools/run-flow.js +47 -0
- package/dist/tools/run-flow.js.map +1 -0
- package/dist/tools/run-skill.d.ts +14 -4
- package/dist/tools/run-skill.d.ts.map +1 -1
- package/dist/tools/run-skill.js +74 -0
- package/dist/tools/run-skill.js.map +1 -1
- package/dist/tools/scrape.d.ts +9 -6
- package/dist/tools/scrape.d.ts.map +1 -1
- package/dist/tools/scrape.js +19 -1
- package/dist/tools/scrape.js.map +1 -1
- package/dist/tools/screenshot.d.ts.map +1 -1
- package/dist/tools/screenshot.js +6 -0
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/serve-flow.d.ts +36 -0
- package/dist/tools/serve-flow.d.ts.map +1 -0
- package/dist/tools/serve-flow.js +42 -0
- package/dist/tools/serve-flow.js.map +1 -0
- package/dist/tools/snapshot.d.ts +5 -5
- package/dist/tools/snapshot.d.ts.map +1 -1
- package/dist/tools/snapshot.js +3 -0
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/validate-flow.d.ts +24 -0
- package/dist/tools/validate-flow.d.ts.map +1 -0
- package/dist/tools/validate-flow.js +23 -0
- package/dist/tools/validate-flow.js.map +1 -0
- package/dist/tools/video-search.d.ts +1 -1
- package/dist/tools/watch.d.ts +68 -0
- package/dist/tools/watch.d.ts.map +1 -0
- package/dist/tools/watch.js +224 -0
- package/dist/tools/watch.js.map +1 -0
- package/dist/tools/youtube.d.ts +2 -2
- package/dist/utils/fetcher.d.ts +13 -4
- package/dist/utils/fetcher.d.ts.map +1 -1
- package/dist/utils/fetcher.js +153 -23
- package/dist/utils/fetcher.js.map +1 -1
- package/package.json +19 -5
package/dist/stealth/proxy.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Proxy parsing, validation,
|
|
2
|
+
* Proxy parsing, validation, round-robin rotation with health tracking.
|
|
3
3
|
*
|
|
4
4
|
* Supports: http, https, socks4, socks5 proxy URLs.
|
|
5
5
|
* Priority: per-request override > rotator.next() > undefined (no proxy).
|
|
6
|
+
*
|
|
7
|
+
* Health tracking: each proxy tracks success/failure counts and cooldown.
|
|
8
|
+
* Failed proxies are skipped for COOLDOWN_MS before being retried.
|
|
6
9
|
*/
|
|
7
10
|
function defaultPort(protocol) {
|
|
8
11
|
if (protocol === "socks4:" || protocol === "socks5:")
|
|
@@ -27,15 +30,25 @@ export function parseProxyUrl(raw) {
|
|
|
27
30
|
password: parsed.password || undefined,
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
|
-
// ── Round-Robin Rotator ──
|
|
33
|
+
// ── Round-Robin Rotator with Health Tracking ──
|
|
34
|
+
const COOLDOWN_MS = 60_000; // 60s cooldown after failure
|
|
31
35
|
export class ProxyRotator {
|
|
32
36
|
proxies;
|
|
33
37
|
index = 0;
|
|
38
|
+
health = new Map();
|
|
34
39
|
constructor(urls) {
|
|
35
40
|
this.proxies = [];
|
|
36
41
|
for (const url of urls) {
|
|
37
42
|
try {
|
|
38
|
-
|
|
43
|
+
const proxy = parseProxyUrl(url);
|
|
44
|
+
this.proxies.push(proxy);
|
|
45
|
+
this.health.set(proxy.url, {
|
|
46
|
+
successCount: 0,
|
|
47
|
+
failureCount: 0,
|
|
48
|
+
lastSuccessTime: 0,
|
|
49
|
+
lastFailureTime: 0,
|
|
50
|
+
cooldownUntil: 0,
|
|
51
|
+
});
|
|
39
52
|
}
|
|
40
53
|
catch {
|
|
41
54
|
console.warn(`[imperium-crawl] Skipping invalid proxy URL: ${url}`);
|
|
@@ -45,12 +58,67 @@ export class ProxyRotator {
|
|
|
45
58
|
get size() {
|
|
46
59
|
return this.proxies.length;
|
|
47
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Get next healthy proxy, skipping those in cooldown.
|
|
63
|
+
* If ALL proxies are in cooldown, returns the one whose cooldown expires soonest.
|
|
64
|
+
*/
|
|
48
65
|
next() {
|
|
49
66
|
if (this.proxies.length === 0)
|
|
50
67
|
return undefined;
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
// Try to find a healthy proxy (not in cooldown)
|
|
70
|
+
for (let i = 0; i < this.proxies.length; i++) {
|
|
71
|
+
const idx = (this.index + i) % this.proxies.length;
|
|
72
|
+
const proxy = this.proxies[idx];
|
|
73
|
+
const h = this.health.get(proxy.url);
|
|
74
|
+
if (!h || now >= h.cooldownUntil) {
|
|
75
|
+
this.index = (idx + 1) % this.proxies.length;
|
|
76
|
+
return proxy;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// All in cooldown — return the one with soonest expiry
|
|
80
|
+
let bestProxy = this.proxies[this.index];
|
|
81
|
+
let soonestCooldown = Infinity;
|
|
82
|
+
for (const proxy of this.proxies) {
|
|
83
|
+
const h = this.health.get(proxy.url);
|
|
84
|
+
if (h && h.cooldownUntil < soonestCooldown) {
|
|
85
|
+
soonestCooldown = h.cooldownUntil;
|
|
86
|
+
bestProxy = proxy;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.index = (this.proxies.indexOf(bestProxy) + 1) % this.proxies.length;
|
|
90
|
+
return bestProxy;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Mark a proxy as having succeeded. Resets its failure state.
|
|
94
|
+
*/
|
|
95
|
+
markSuccess(proxyUrl) {
|
|
96
|
+
const h = this.health.get(proxyUrl);
|
|
97
|
+
if (h) {
|
|
98
|
+
h.successCount++;
|
|
99
|
+
h.lastSuccessTime = Date.now();
|
|
100
|
+
h.cooldownUntil = 0; // Clear cooldown on success
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Mark a proxy as having failed. Puts it in cooldown.
|
|
105
|
+
*/
|
|
106
|
+
markFailed(proxyUrl) {
|
|
107
|
+
const h = this.health.get(proxyUrl);
|
|
108
|
+
if (h) {
|
|
109
|
+
h.failureCount++;
|
|
110
|
+
h.lastFailureTime = Date.now();
|
|
111
|
+
h.cooldownUntil = Date.now() + COOLDOWN_MS;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get health stats for all proxies (for debugging/monitoring).
|
|
116
|
+
*/
|
|
117
|
+
getHealthStats() {
|
|
118
|
+
return this.proxies.map((p) => ({
|
|
119
|
+
url: p.url,
|
|
120
|
+
health: this.health.get(p.url),
|
|
121
|
+
}));
|
|
54
122
|
}
|
|
55
123
|
}
|
|
56
124
|
// ── Singleton ──
|
|
@@ -82,6 +150,22 @@ export function resolveProxy(override) {
|
|
|
82
150
|
return override;
|
|
83
151
|
return rotator?.next()?.url;
|
|
84
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Mark a proxy as having succeeded (for health tracking).
|
|
155
|
+
*/
|
|
156
|
+
export function markProxySuccess(proxyUrl) {
|
|
157
|
+
if (proxyUrl && rotator) {
|
|
158
|
+
rotator.markSuccess(proxyUrl);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Mark a proxy as having failed (puts it in cooldown).
|
|
163
|
+
*/
|
|
164
|
+
export function markProxyFailed(proxyUrl) {
|
|
165
|
+
if (proxyUrl && rotator) {
|
|
166
|
+
rotator.markFailed(proxyUrl);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
85
169
|
export function hasProxyConfigured() {
|
|
86
170
|
return !!(process.env.PROXY_URL?.trim() || process.env.PROXY_URLS?.trim());
|
|
87
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/stealth/proxy.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/stealth/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Executor — extracted from interact.ts.
|
|
3
|
+
*
|
|
4
|
+
* Shared between:
|
|
5
|
+
* - interact tool (automated action sequences)
|
|
6
|
+
* - explore REPL (interactive CLI browser session)
|
|
7
|
+
*
|
|
8
|
+
* All browser action handlers live here. interact.ts and cli-explore.ts
|
|
9
|
+
* import executeAction from this module.
|
|
10
|
+
*
|
|
11
|
+
* Note: evaluate and paginate actions use new Function() intentionally —
|
|
12
|
+
* the user explicitly provides JS scripts for browser-side execution.
|
|
13
|
+
* This is the same pattern used in the original interact.ts.
|
|
14
|
+
*/
|
|
15
|
+
import type { RefEntry } from "../snapshot/index.js";
|
|
16
|
+
export interface ActionResult {
|
|
17
|
+
type: string;
|
|
18
|
+
success: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
result?: unknown;
|
|
21
|
+
}
|
|
22
|
+
export type ActionInput = {
|
|
23
|
+
type: string;
|
|
24
|
+
selector?: string;
|
|
25
|
+
ref?: string;
|
|
26
|
+
text?: string;
|
|
27
|
+
value?: string;
|
|
28
|
+
script?: string;
|
|
29
|
+
key?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
duration?: number;
|
|
32
|
+
x?: number;
|
|
33
|
+
y?: number;
|
|
34
|
+
target_selector?: string;
|
|
35
|
+
target_ref?: string;
|
|
36
|
+
file_paths?: string[];
|
|
37
|
+
storage?: "local" | "session";
|
|
38
|
+
auth_profile?: string;
|
|
39
|
+
next_selector?: string;
|
|
40
|
+
next_ref?: string;
|
|
41
|
+
extract_script?: string;
|
|
42
|
+
max_pages?: number;
|
|
43
|
+
wait_after_click?: number;
|
|
44
|
+
keywords?: string[];
|
|
45
|
+
max_clicks?: number;
|
|
46
|
+
cookies?: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
value: string;
|
|
49
|
+
domain?: string;
|
|
50
|
+
path?: string;
|
|
51
|
+
expires?: number;
|
|
52
|
+
httpOnly?: boolean;
|
|
53
|
+
secure?: boolean;
|
|
54
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
55
|
+
}>;
|
|
56
|
+
};
|
|
57
|
+
export declare function humanDelay(): number;
|
|
58
|
+
export declare function resolveRefToLocator(page: import("rebrowser-playwright").Page, refEntry: RefEntry): import("rebrowser-playwright").Locator;
|
|
59
|
+
export declare function getTargetSelector(action: ActionInput, sessionId: string | undefined): {
|
|
60
|
+
selector?: string;
|
|
61
|
+
refEntry?: RefEntry;
|
|
62
|
+
error?: string;
|
|
63
|
+
};
|
|
64
|
+
export declare function buildCssSelectorFromRef(refEntry: RefEntry): string;
|
|
65
|
+
export declare function executeAction(page: import("rebrowser-playwright").Page, action: ActionInput, screenshots: string[], timeout: number, sessionId?: string): Promise<ActionResult>;
|
|
66
|
+
//# sourceMappingURL=action-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-executor.d.ts","sourceRoot":"","sources":["../../src/tools/action-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;KACtC,CAAC,CAAC;CACJ,CAAC;AAIF,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,sBAAsB,EAAE,IAAI,EACzC,QAAQ,EAAE,QAAQ,GACjB,OAAO,sBAAsB,EAAE,OAAO,CASxC;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAe5D;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAWlE;AAID,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,sBAAsB,EAAE,IAAI,EACzC,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAiUvB"}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Executor — extracted from interact.ts.
|
|
3
|
+
*
|
|
4
|
+
* Shared between:
|
|
5
|
+
* - interact tool (automated action sequences)
|
|
6
|
+
* - explore REPL (interactive CLI browser session)
|
|
7
|
+
*
|
|
8
|
+
* All browser action handlers live here. interact.ts and cli-explore.ts
|
|
9
|
+
* import executeAction from this module.
|
|
10
|
+
*
|
|
11
|
+
* Note: evaluate and paginate actions use new Function() intentionally —
|
|
12
|
+
* the user explicitly provides JS scripts for browser-side execution.
|
|
13
|
+
* This is the same pattern used in the original interact.ts.
|
|
14
|
+
*/
|
|
15
|
+
import { HUMAN_DELAY_MIN_MS, HUMAN_DELAY_MAX_MS, } from "../constants.js";
|
|
16
|
+
import { getSnapshotStore } from "../snapshot/index.js";
|
|
17
|
+
import { getAuthProfile, updateLastLogin } from "../security/auth-vault.js";
|
|
18
|
+
// ── Helpers ──
|
|
19
|
+
export function humanDelay() {
|
|
20
|
+
return (HUMAN_DELAY_MIN_MS +
|
|
21
|
+
Math.floor(Math.random() * (HUMAN_DELAY_MAX_MS - HUMAN_DELAY_MIN_MS)));
|
|
22
|
+
}
|
|
23
|
+
export function resolveRefToLocator(page, refEntry) {
|
|
24
|
+
const locator = refEntry.name
|
|
25
|
+
? page.getByRole(refEntry.role, {
|
|
26
|
+
name: refEntry.name,
|
|
27
|
+
exact: true,
|
|
28
|
+
})
|
|
29
|
+
: page.getByRole(refEntry.role);
|
|
30
|
+
return refEntry.nth !== undefined ? locator.nth(refEntry.nth) : locator;
|
|
31
|
+
}
|
|
32
|
+
export function getTargetSelector(action, sessionId) {
|
|
33
|
+
if (action.ref && action.selector) {
|
|
34
|
+
return { error: "ref and selector are mutually exclusive — provide one, not both" };
|
|
35
|
+
}
|
|
36
|
+
if (action.ref && sessionId) {
|
|
37
|
+
const entry = getSnapshotStore().resolveRef(sessionId, action.ref);
|
|
38
|
+
if (!entry) {
|
|
39
|
+
return { error: `ref '${action.ref}' not found. Take a snapshot first to get valid refs.` };
|
|
40
|
+
}
|
|
41
|
+
return { refEntry: entry };
|
|
42
|
+
}
|
|
43
|
+
if (action.ref && !sessionId) {
|
|
44
|
+
return { error: "ref requires session_id to resolve. Provide session_id or use selector instead." };
|
|
45
|
+
}
|
|
46
|
+
return { selector: action.selector };
|
|
47
|
+
}
|
|
48
|
+
export function buildCssSelectorFromRef(refEntry) {
|
|
49
|
+
const roleMap = {
|
|
50
|
+
button: "button",
|
|
51
|
+
link: "a",
|
|
52
|
+
textbox: "input",
|
|
53
|
+
searchbox: "input[type=search]",
|
|
54
|
+
checkbox: "input[type=checkbox]",
|
|
55
|
+
radio: "input[type=radio]",
|
|
56
|
+
};
|
|
57
|
+
const tag = roleMap[refEntry.role] ?? `[role="${refEntry.role}"]`;
|
|
58
|
+
return refEntry.name ? `${tag}:has-text("${refEntry.name.replace(/"/g, '\\"')}")` : tag;
|
|
59
|
+
}
|
|
60
|
+
// ── Core Action Executor ──
|
|
61
|
+
export async function executeAction(page, action, screenshots, timeout, sessionId) {
|
|
62
|
+
try {
|
|
63
|
+
const needsTarget = ["click", "type", "hover", "select", "press", "wait", "drag", "upload"].includes(action.type);
|
|
64
|
+
let refEntry;
|
|
65
|
+
let cssSelector;
|
|
66
|
+
if (needsTarget && (action.ref || action.selector)) {
|
|
67
|
+
const target = getTargetSelector(action, sessionId);
|
|
68
|
+
if (target.error)
|
|
69
|
+
return { type: action.type, success: false, error: target.error };
|
|
70
|
+
refEntry = target.refEntry;
|
|
71
|
+
cssSelector = target.selector;
|
|
72
|
+
}
|
|
73
|
+
switch (action.type) {
|
|
74
|
+
case "click": {
|
|
75
|
+
if (!refEntry && !cssSelector)
|
|
76
|
+
return { type: "click", success: false, error: "selector or ref required" };
|
|
77
|
+
if (refEntry) {
|
|
78
|
+
await resolveRefToLocator(page, refEntry).click({ timeout });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await page.click(cssSelector, { timeout });
|
|
82
|
+
}
|
|
83
|
+
await page.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
84
|
+
return { type: "click", success: true };
|
|
85
|
+
}
|
|
86
|
+
case "type": {
|
|
87
|
+
if (!refEntry && !cssSelector)
|
|
88
|
+
return { type: "type", success: false, error: "selector or ref required" };
|
|
89
|
+
if (action.text === undefined)
|
|
90
|
+
return { type: "type", success: false, error: "text required" };
|
|
91
|
+
if (refEntry) {
|
|
92
|
+
await resolveRefToLocator(page, refEntry).fill(action.text, { timeout });
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
await page.fill(cssSelector, action.text, { timeout });
|
|
96
|
+
}
|
|
97
|
+
return { type: "type", success: true };
|
|
98
|
+
}
|
|
99
|
+
case "scroll": {
|
|
100
|
+
const x = action.x ?? 0;
|
|
101
|
+
const y = action.y ?? 500;
|
|
102
|
+
await page.evaluate(({ dx, dy }) => window.scrollBy(dx, dy), { dx: x, dy: y });
|
|
103
|
+
return { type: "scroll", success: true };
|
|
104
|
+
}
|
|
105
|
+
case "wait": {
|
|
106
|
+
if (refEntry) {
|
|
107
|
+
await resolveRefToLocator(page, refEntry).waitFor({ timeout });
|
|
108
|
+
}
|
|
109
|
+
else if (cssSelector) {
|
|
110
|
+
await page.waitForSelector(cssSelector, { timeout });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
await page.waitForTimeout(action.duration ?? 1000);
|
|
114
|
+
}
|
|
115
|
+
return { type: "wait", success: true };
|
|
116
|
+
}
|
|
117
|
+
case "screenshot": {
|
|
118
|
+
const buf = await page.screenshot({ fullPage: false });
|
|
119
|
+
screenshots.push(buf.toString("base64"));
|
|
120
|
+
return { type: "screenshot", success: true };
|
|
121
|
+
}
|
|
122
|
+
case "evaluate": {
|
|
123
|
+
if (!action.script)
|
|
124
|
+
return { type: "evaluate", success: false, error: "script required" };
|
|
125
|
+
const evalFn = new Function(action.script);
|
|
126
|
+
const result = await page.evaluate(evalFn);
|
|
127
|
+
return { type: "evaluate", success: true, result };
|
|
128
|
+
}
|
|
129
|
+
case "select": {
|
|
130
|
+
if (!refEntry && !cssSelector)
|
|
131
|
+
return { type: "select", success: false, error: "selector or ref required" };
|
|
132
|
+
if (action.value === undefined)
|
|
133
|
+
return { type: "select", success: false, error: "value required" };
|
|
134
|
+
if (refEntry) {
|
|
135
|
+
await resolveRefToLocator(page, refEntry).selectOption(action.value, { timeout });
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await page.selectOption(cssSelector, action.value, { timeout });
|
|
139
|
+
}
|
|
140
|
+
return { type: "select", success: true };
|
|
141
|
+
}
|
|
142
|
+
case "hover": {
|
|
143
|
+
if (!refEntry && !cssSelector)
|
|
144
|
+
return { type: "hover", success: false, error: "selector or ref required" };
|
|
145
|
+
if (refEntry) {
|
|
146
|
+
await resolveRefToLocator(page, refEntry).hover({ timeout });
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
await page.hover(cssSelector, { timeout });
|
|
150
|
+
}
|
|
151
|
+
return { type: "hover", success: true };
|
|
152
|
+
}
|
|
153
|
+
case "press": {
|
|
154
|
+
if (!action.key)
|
|
155
|
+
return { type: "press", success: false, error: "key required" };
|
|
156
|
+
if (refEntry) {
|
|
157
|
+
await resolveRefToLocator(page, refEntry).press(action.key, { timeout });
|
|
158
|
+
}
|
|
159
|
+
else if (cssSelector) {
|
|
160
|
+
await page.press(cssSelector, action.key, { timeout });
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
await page.keyboard.press(action.key);
|
|
164
|
+
}
|
|
165
|
+
return { type: "press", success: true };
|
|
166
|
+
}
|
|
167
|
+
case "navigate": {
|
|
168
|
+
if (!action.url)
|
|
169
|
+
return { type: "navigate", success: false, error: "url required" };
|
|
170
|
+
await page.goto(action.url, { waitUntil: "load", timeout });
|
|
171
|
+
return { type: "navigate", success: true };
|
|
172
|
+
}
|
|
173
|
+
case "drag": {
|
|
174
|
+
if (!refEntry && !cssSelector)
|
|
175
|
+
return { type: "drag", success: false, error: "selector or ref required for source" };
|
|
176
|
+
if (!action.target_selector && !action.target_ref)
|
|
177
|
+
return { type: "drag", success: false, error: "target_selector or target_ref required" };
|
|
178
|
+
const sourceSelector = refEntry ? buildCssSelectorFromRef(refEntry) : cssSelector;
|
|
179
|
+
let targetSelector;
|
|
180
|
+
if (action.target_ref && sessionId) {
|
|
181
|
+
const targetEntry = getSnapshotStore().resolveRef(sessionId, action.target_ref);
|
|
182
|
+
if (!targetEntry)
|
|
183
|
+
return { type: "drag", success: false, error: `target ref '${action.target_ref}' not found` };
|
|
184
|
+
targetSelector = buildCssSelectorFromRef(targetEntry);
|
|
185
|
+
}
|
|
186
|
+
else if (action.target_selector) {
|
|
187
|
+
targetSelector = action.target_selector;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
return { type: "drag", success: false, error: "target_selector or target_ref required" };
|
|
191
|
+
}
|
|
192
|
+
await page.dragAndDrop(sourceSelector, targetSelector, { timeout });
|
|
193
|
+
return { type: "drag", success: true };
|
|
194
|
+
}
|
|
195
|
+
case "upload": {
|
|
196
|
+
if (!refEntry && !cssSelector)
|
|
197
|
+
return { type: "upload", success: false, error: "selector or ref required" };
|
|
198
|
+
if (!action.file_paths?.length)
|
|
199
|
+
return { type: "upload", success: false, error: "file_paths required" };
|
|
200
|
+
if (refEntry) {
|
|
201
|
+
await resolveRefToLocator(page, refEntry).setInputFiles(action.file_paths, { timeout });
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
await page.setInputFiles(cssSelector, action.file_paths, { timeout });
|
|
205
|
+
}
|
|
206
|
+
return { type: "upload", success: true };
|
|
207
|
+
}
|
|
208
|
+
case "storage_get": {
|
|
209
|
+
if (!action.storage)
|
|
210
|
+
return { type: "storage_get", success: false, error: "storage type required (local or session)" };
|
|
211
|
+
const storageObj = action.storage === "local" ? "localStorage" : "sessionStorage";
|
|
212
|
+
const storageKey = action.key;
|
|
213
|
+
const storageResult = await page.evaluate(({ obj, k }) => {
|
|
214
|
+
const storage = obj === "localStorage" ? localStorage : sessionStorage;
|
|
215
|
+
if (k)
|
|
216
|
+
return storage.getItem(k);
|
|
217
|
+
const all = {};
|
|
218
|
+
for (let i = 0; i < storage.length; i++) {
|
|
219
|
+
const key = storage.key(i);
|
|
220
|
+
if (key)
|
|
221
|
+
all[key] = storage.getItem(key);
|
|
222
|
+
}
|
|
223
|
+
return all;
|
|
224
|
+
}, { obj: storageObj, k: storageKey });
|
|
225
|
+
return { type: "storage_get", success: true, result: storageResult };
|
|
226
|
+
}
|
|
227
|
+
case "storage_set": {
|
|
228
|
+
if (!action.storage)
|
|
229
|
+
return { type: "storage_set", success: false, error: "storage type required" };
|
|
230
|
+
if (!action.key)
|
|
231
|
+
return { type: "storage_set", success: false, error: "key required" };
|
|
232
|
+
if (action.value === undefined)
|
|
233
|
+
return { type: "storage_set", success: false, error: "value required" };
|
|
234
|
+
const storageTarget = action.storage === "local" ? "localStorage" : "sessionStorage";
|
|
235
|
+
await page.evaluate(({ obj, k, v }) => {
|
|
236
|
+
const storage = obj === "localStorage" ? localStorage : sessionStorage;
|
|
237
|
+
storage.setItem(k, v);
|
|
238
|
+
}, { obj: storageTarget, k: action.key, v: action.value });
|
|
239
|
+
return { type: "storage_set", success: true };
|
|
240
|
+
}
|
|
241
|
+
case "cookie_get": {
|
|
242
|
+
const allCookies = await page.context().cookies();
|
|
243
|
+
const filtered = action.key ? allCookies.filter((c) => c.name === action.key) : allCookies;
|
|
244
|
+
return { type: "cookie_get", success: true, result: filtered };
|
|
245
|
+
}
|
|
246
|
+
case "cookie_set": {
|
|
247
|
+
if (!action.cookies?.length)
|
|
248
|
+
return { type: "cookie_set", success: false, error: "cookies array required" };
|
|
249
|
+
const pageUrl = page.url();
|
|
250
|
+
const cookiesToSet = action.cookies.map((c) => ({ ...c, url: c.domain ? undefined : pageUrl }));
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
+
await page.context().addCookies(cookiesToSet);
|
|
253
|
+
return { type: "cookie_set", success: true };
|
|
254
|
+
}
|
|
255
|
+
case "pdf": {
|
|
256
|
+
const pdfBuf = await page.pdf();
|
|
257
|
+
screenshots.push(pdfBuf.toString("base64"));
|
|
258
|
+
return { type: "pdf", success: true, result: "PDF saved to screenshots array as base64" };
|
|
259
|
+
}
|
|
260
|
+
case "refresh": {
|
|
261
|
+
await page.reload({ waitUntil: "load", timeout });
|
|
262
|
+
return { type: "refresh", success: true };
|
|
263
|
+
}
|
|
264
|
+
case "paginate": {
|
|
265
|
+
if (!action.extract_script)
|
|
266
|
+
return { type: "paginate", success: false, error: "extract_script required" };
|
|
267
|
+
if (!action.next_selector && !action.next_ref)
|
|
268
|
+
return { type: "paginate", success: false, error: "next_selector or next_ref required" };
|
|
269
|
+
const maxPages = action.max_pages ?? 10;
|
|
270
|
+
const waitMs = action.wait_after_click ?? 2000;
|
|
271
|
+
const allData = [];
|
|
272
|
+
let nextRefEntry;
|
|
273
|
+
let nextCss;
|
|
274
|
+
if (action.next_ref && sessionId) {
|
|
275
|
+
const entry = getSnapshotStore().resolveRef(sessionId, action.next_ref);
|
|
276
|
+
if (!entry)
|
|
277
|
+
return { type: "paginate", success: false, error: `next_ref '${action.next_ref}' not found` };
|
|
278
|
+
nextRefEntry = entry;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
nextCss = action.next_selector;
|
|
282
|
+
}
|
|
283
|
+
const extractFn = action.extract_script;
|
|
284
|
+
for (let pg = 0; pg < maxPages; pg++) {
|
|
285
|
+
const extracted = await page.evaluate((script) => (new Function(script))(), extractFn);
|
|
286
|
+
if (extracted !== null && extracted !== undefined) {
|
|
287
|
+
if (Array.isArray(extracted)) {
|
|
288
|
+
allData.push(...extracted);
|
|
289
|
+
}
|
|
290
|
+
else if (typeof extracted === "string") {
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(extracted);
|
|
293
|
+
Array.isArray(parsed) ? allData.push(...parsed) : allData.push(parsed);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
allData.push(extracted);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
allData.push(extracted);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (pg >= maxPages - 1)
|
|
304
|
+
break;
|
|
305
|
+
try {
|
|
306
|
+
if (nextRefEntry) {
|
|
307
|
+
const loc = resolveRefToLocator(page, nextRefEntry);
|
|
308
|
+
if (!await loc.isVisible().catch(() => false))
|
|
309
|
+
break;
|
|
310
|
+
await loc.click({ timeout });
|
|
311
|
+
}
|
|
312
|
+
else if (nextCss) {
|
|
313
|
+
const el = page.locator(nextCss);
|
|
314
|
+
if (!await el.isVisible().catch(() => false))
|
|
315
|
+
break;
|
|
316
|
+
const isDisabled = await el.evaluate((node) => {
|
|
317
|
+
const htmlEl = node;
|
|
318
|
+
return htmlEl.classList.contains("disabled") ||
|
|
319
|
+
htmlEl.getAttribute("aria-disabled") === "true" ||
|
|
320
|
+
htmlEl.disabled === true;
|
|
321
|
+
}).catch(() => false);
|
|
322
|
+
if (isDisabled)
|
|
323
|
+
break;
|
|
324
|
+
await el.click({ timeout });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
await page.waitForTimeout(waitMs);
|
|
331
|
+
await page.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
332
|
+
}
|
|
333
|
+
return { type: "paginate", success: true, result: allData };
|
|
334
|
+
}
|
|
335
|
+
case "auto_click": {
|
|
336
|
+
const defaultKeywords = [
|
|
337
|
+
"show more", "load more", "gallery", "view images", "view photos",
|
|
338
|
+
"prikaži više", "učitaj još", "galerija", "slike", "fotografije",
|
|
339
|
+
"photos", "images", "see more", "more images", "more photos",
|
|
340
|
+
"expand", "prikazi jos", "jos slika", "vise slika",
|
|
341
|
+
"ucitaj vise", "prikazi vise", "prikaži još", "učitaj više",
|
|
342
|
+
];
|
|
343
|
+
const keywords = action.keywords ?? defaultKeywords;
|
|
344
|
+
const maxClicks = action.max_clicks ?? 5;
|
|
345
|
+
const clicked = [];
|
|
346
|
+
for (let round = 0; round < maxClicks; round++) {
|
|
347
|
+
const found = await page.evaluate((kw) => {
|
|
348
|
+
const clickedTexts = [];
|
|
349
|
+
const buttons = Array.from(document.querySelectorAll("button, a, [role=button], .btn, .button, [class*=gallery], [class*=image], [class*=photo], [class*=more], [class*=load], [class*=expand], [id*=gallery], [id*=image], [id*=photo], [id*=more]"));
|
|
350
|
+
for (const btn of buttons) {
|
|
351
|
+
const el = btn;
|
|
352
|
+
const text = (el.textContent || el.title || el.getAttribute("aria-label") || "").toLowerCase();
|
|
353
|
+
const matched = kw.some((k) => text.includes(k.toLowerCase()));
|
|
354
|
+
if (matched && el.offsetParent !== null) {
|
|
355
|
+
try {
|
|
356
|
+
el.click();
|
|
357
|
+
el.scrollIntoView({ behavior: "instant", block: "center" });
|
|
358
|
+
clickedTexts.push(text.slice(0, 100));
|
|
359
|
+
}
|
|
360
|
+
catch { }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return clickedTexts;
|
|
364
|
+
}, keywords);
|
|
365
|
+
if (found.length === 0)
|
|
366
|
+
break;
|
|
367
|
+
clicked.push(...found);
|
|
368
|
+
await page.waitForTimeout(action.wait_after_click ?? 2500);
|
|
369
|
+
await page.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
370
|
+
}
|
|
371
|
+
return { type: "auto_click", success: true, result: { clicked: clicked.length, buttons: clicked } };
|
|
372
|
+
}
|
|
373
|
+
case "auth_login": {
|
|
374
|
+
if (!action.auth_profile)
|
|
375
|
+
return { type: "auth_login", success: false, error: "auth_profile name required" };
|
|
376
|
+
const profile = await getAuthProfile(action.auth_profile);
|
|
377
|
+
if (!profile)
|
|
378
|
+
return { type: "auth_login", success: false, error: `Auth profile '${action.auth_profile}' not found` };
|
|
379
|
+
if (profile.url) {
|
|
380
|
+
await page.goto(profile.url, { waitUntil: "load", timeout });
|
|
381
|
+
}
|
|
382
|
+
await page.fill(profile.selectors.username, profile.username, { timeout });
|
|
383
|
+
await page.waitForTimeout(humanDelay());
|
|
384
|
+
await page.fill(profile.selectors.password, profile.password, { timeout });
|
|
385
|
+
await page.waitForTimeout(humanDelay());
|
|
386
|
+
await page.click(profile.selectors.submit, { timeout });
|
|
387
|
+
await page.waitForLoadState("networkidle", { timeout: 10000 }).catch(() => { });
|
|
388
|
+
await updateLastLogin(action.auth_profile);
|
|
389
|
+
return { type: "auth_login", success: true, result: `Logged in as ${profile.username}` };
|
|
390
|
+
}
|
|
391
|
+
default:
|
|
392
|
+
return { type: action.type, success: false, error: "unknown action type" };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
return {
|
|
397
|
+
type: action.type,
|
|
398
|
+
success: false,
|
|
399
|
+
error: err instanceof Error ? err.message : String(err),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=action-executor.js.map
|