nothing-browser 0.0.21 → 0.1.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/LICENSE +1 -1
- package/dist/human/index.js +25 -1
- package/dist/piggy/captcha/index.d.ts +39 -0
- package/dist/piggy/captcha/index.d.ts.map +1 -0
- package/dist/piggy/capture/index.d.ts +48 -0
- package/dist/piggy/capture/index.d.ts.map +1 -0
- package/dist/piggy/dialog/index.d.ts +28 -0
- package/dist/piggy/dialog/index.d.ts.map +1 -0
- package/dist/piggy/export/index.d.ts +62 -0
- package/dist/piggy/export/index.d.ts.map +1 -0
- package/dist/piggy/find/index.d.ts +52 -0
- package/dist/piggy/find/index.d.ts.map +1 -0
- package/dist/piggy/http/index.d.ts +14 -0
- package/dist/piggy/http/index.d.ts.map +1 -0
- package/dist/piggy/human/index.d.ts +36 -4
- package/dist/piggy/human/index.d.ts.map +1 -1
- package/dist/piggy/iframe/index.d.ts +53 -0
- package/dist/piggy/iframe/index.d.ts.map +1 -0
- package/dist/piggy/interactions/index.d.ts +24 -0
- package/dist/piggy/interactions/index.d.ts.map +1 -0
- package/dist/piggy/media/index.d.ts +11 -0
- package/dist/piggy/media/index.d.ts.map +1 -0
- package/dist/piggy/navigation/index.d.ts +16 -0
- package/dist/piggy/navigation/index.d.ts.map +1 -0
- package/dist/piggy/provide/index.d.ts +98 -0
- package/dist/piggy/provide/index.d.ts.map +1 -0
- package/dist/piggy/proxy/index.d.ts +94 -0
- package/dist/piggy/proxy/index.d.ts.map +1 -0
- package/dist/piggy/register/index.d.ts.map +1 -1
- package/dist/piggy/router/index.d.ts +38 -0
- package/dist/piggy/router/index.d.ts.map +1 -0
- package/dist/piggy/session/index.d.ts +27 -0
- package/dist/piggy/session/index.d.ts.map +1 -0
- package/dist/piggy/tabs/index.d.ts +10 -0
- package/dist/piggy/tabs/index.d.ts.map +1 -0
- package/dist/piggy/wait/index.d.ts +28 -0
- package/dist/piggy/wait/index.d.ts.map +1 -0
- package/dist/piggy.d.ts.map +1 -1
- package/dist/piggy.js +898 -181
- package/dist/register/index.js +39 -86
- package/package.json +1 -1
- package/piggy/captcha/index.d.ts +35 -0
- package/piggy/captcha/index.ts +93 -0
- package/piggy/capture/index.ts +76 -0
- package/piggy/dialog/index.d.ts +29 -0
- package/piggy/dialog/index.ts +85 -0
- package/piggy/export/index.d.ts +117 -0
- package/piggy/export/index.ts +147 -0
- package/piggy/find/index.ts +118 -0
- package/piggy/http/index.ts +65 -0
- package/piggy/human/index.ts +115 -53
- package/piggy/iframe/index.ts +79 -0
- package/piggy/interactions/index.ts +79 -0
- package/piggy/media/index.ts +46 -0
- package/piggy/navigation/index.ts +52 -0
- package/piggy/provide/index.ts +170 -0
- package/piggy/proxy/index.ts +154 -0
- package/piggy/register/index.ts +41 -59
- package/piggy/router/index.ts +69 -0
- package/piggy/session/index.ts +76 -0
- package/piggy/tabs/index.ts +22 -0
- package/piggy/wait/index.ts +90 -0
- package/piggy.ts +94 -39
- package/dist/piggy/open/index.d.ts +0 -4
- package/dist/piggy/open/index.d.ts.map +0 -1
- package/piggy/open/index.d.ts +0 -4
- package/piggy/open/index.d.ts.map +0 -1
- package/piggy/open/index.ts +0 -5
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// piggy/export/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
import type { CapturedRequest, WebSocketFrame, CapturedCookie } from "../capture";
|
|
4
|
+
import type { SessionPaths } from "../session";
|
|
5
|
+
|
|
6
|
+
// ─── Intercept ────────────────────────────────────────────────────────────────
|
|
7
|
+
export interface InterceptRule {
|
|
8
|
+
pattern: string;
|
|
9
|
+
block?: boolean;
|
|
10
|
+
redirect?: string;
|
|
11
|
+
setHeaders?: Record<string, string>;
|
|
12
|
+
removeHeaders?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ─── Cookie types (defined here, imported by session) ────────────────────────
|
|
16
|
+
export interface CookieSetOptions {
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
domain: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
httpOnly?: boolean;
|
|
22
|
+
secure?: boolean;
|
|
23
|
+
expiry?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CookieDeleteOptions {
|
|
27
|
+
name: string;
|
|
28
|
+
domain: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Session export types (re-exported from capture) ────────────────────────
|
|
32
|
+
export type { CapturedRequest, WebSocketFrame, CapturedCookie, SessionPaths };
|
|
33
|
+
|
|
34
|
+
export interface SessionExport {
|
|
35
|
+
url: string;
|
|
36
|
+
requests: CapturedRequest[];
|
|
37
|
+
ws: WebSocketFrame[];
|
|
38
|
+
cookies: CapturedCookie[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ExposedFunctionCall {
|
|
42
|
+
name: string;
|
|
43
|
+
callId: string;
|
|
44
|
+
data: string;
|
|
45
|
+
tabId: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── ExportClient ─────────────────────────────────────────────────────────────
|
|
49
|
+
export class ExportClient {
|
|
50
|
+
constructor(private client: PiggyClient) {}
|
|
51
|
+
|
|
52
|
+
// DOM fetch
|
|
53
|
+
searchCss(query: string, tabId = "default"): Promise<any> {
|
|
54
|
+
return this.client.send("search.css", { query, tabId });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
searchId(query: string, tabId = "default"): Promise<any> {
|
|
58
|
+
return this.client.send("search.id", { query, tabId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cookies
|
|
62
|
+
setCookie(opts: CookieSetOptions, tabId = "default"): Promise<void> {
|
|
63
|
+
return this.client.send("cookie.set", { ...opts, tabId });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
deleteCookie(opts: CookieDeleteOptions, tabId = "default"): Promise<void> {
|
|
67
|
+
return this.client.send("cookie.delete", { ...opts, tabId });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Session
|
|
71
|
+
sessionReload(tabId = "default"): Promise<void> {
|
|
72
|
+
return this.client.send("session.reload", { tabId });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cookiesPath(): Promise<string> {
|
|
76
|
+
return this.client.send("session.cookies.path", {});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
profilePath(): Promise<string> {
|
|
80
|
+
return this.client.send("session.profile.path", {});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
wsPath(): Promise<string> {
|
|
84
|
+
return this.client.send("session.ws.path", {});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pingsPath(): Promise<string> {
|
|
88
|
+
return this.client.send("session.pings.path", {});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
sessionPaths(): Promise<SessionPaths> {
|
|
92
|
+
return this.client.send("session.paths", {});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setWsSave(enabled: boolean): Promise<void> {
|
|
96
|
+
return this.client.send("session.ws.save", { enabled });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setPingsSave(enabled: boolean): Promise<void> {
|
|
100
|
+
return this.client.send("session.pings.save", { enabled });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Intercept rules
|
|
104
|
+
addInterceptRule(rule: InterceptRule, tabId = "default"): Promise<void> {
|
|
105
|
+
return this.client.send("intercept.rule.add", { ...rule, tabId });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
clearInterceptRules(tabId = "default"): Promise<void> {
|
|
109
|
+
return this.client.send("intercept.rule.clear", { tabId });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Session export / import
|
|
113
|
+
async exportSession(tabId = "default"): Promise<SessionExport> {
|
|
114
|
+
const raw = await this.client.send<string>("session.export", { tabId });
|
|
115
|
+
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
importSession(data: SessionExport, tabId = "default"): Promise<void> {
|
|
119
|
+
return this.client.send("session.import", {
|
|
120
|
+
data: JSON.stringify(data),
|
|
121
|
+
tabId,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Exposed functions
|
|
126
|
+
exposeFunction(name: string, tabId = "default"): Promise<void> {
|
|
127
|
+
return this.client.send("expose.function", { name, tabId });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
resolveExposed(callId: string, result: string, isError = false, tabId = "default"): Promise<void> {
|
|
131
|
+
return this.client.send("exposed.result", { callId, result, isError, tabId });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Init scripts
|
|
135
|
+
addInitScript(js: string, tabId = "default"): Promise<void> {
|
|
136
|
+
return this.client.send("addInitScript", { js, tabId });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Events
|
|
140
|
+
onExposedFunctionCalled(tabId: string, handler: (call: ExposedFunctionCall) => void): () => void {
|
|
141
|
+
return this.client.onEvent("exposed_call", tabId, handler);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function createExportAPI(client: PiggyClient): ExportClient {
|
|
146
|
+
return new ExportClient(client);
|
|
147
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// piggy/find/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
// ─── Element descriptor ───────────────────────────────────────────────────────
|
|
5
|
+
// Mirrors __nb_serialize() in PiggyFind.cpp
|
|
6
|
+
|
|
7
|
+
export interface ElementDescriptor {
|
|
8
|
+
tag: string;
|
|
9
|
+
id: string;
|
|
10
|
+
cls: string;
|
|
11
|
+
/** First 400 chars of innerText */
|
|
12
|
+
text: string;
|
|
13
|
+
/** First 800 chars of innerHTML */
|
|
14
|
+
html: string;
|
|
15
|
+
href: string;
|
|
16
|
+
src: string;
|
|
17
|
+
value: string;
|
|
18
|
+
attrs: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─── FindClient ───────────────────────────────────────────────────────────────
|
|
22
|
+
// find answers ONE question: "is this thing here, and where?"
|
|
23
|
+
// All methods take plain selector strings — no option objects, no parent scoping.
|
|
24
|
+
// If you need a value out of an element, use provide instead.
|
|
25
|
+
|
|
26
|
+
export class FindClient {
|
|
27
|
+
constructor(private client: PiggyClient) {}
|
|
28
|
+
|
|
29
|
+
// ── Multi-result ─────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/** querySelectorAll — returns all matching elements. */
|
|
32
|
+
css(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
33
|
+
return this.client.send("find.css", { selector, tabId });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Alias for css(). */
|
|
37
|
+
all(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
38
|
+
return this.client.send("find.all", { selector, tabId });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** querySelector — returns a single-element array or []. */
|
|
42
|
+
first(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
43
|
+
return this.client.send("find.first", { selector, tabId });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Find elements whose innerText contains the given text. */
|
|
47
|
+
byText(text: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
48
|
+
return this.client.send("find.byText", { text, tabId });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Find elements by attribute name (and optional value). */
|
|
52
|
+
byAttr(attr: string, value?: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
53
|
+
return this.client.send("find.byAttr", { attr, value, tabId });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** getElementsByTagName. */
|
|
57
|
+
byTag(tag: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
58
|
+
return this.client.send("find.byTag", { tag, tabId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Find inputs/textareas whose placeholder contains the given text. */
|
|
62
|
+
byPlaceholder(text: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
63
|
+
return this.client.send("find.byPlaceholder", { text, tabId });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Find elements by ARIA role, optionally filtered by accessible name. */
|
|
67
|
+
byRole(role: string, name?: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
68
|
+
return this.client.send("find.byRole", { role, name, tabId });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Direct children of the matched element. */
|
|
72
|
+
children(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
73
|
+
return this.client.send("find.children", { selector, tabId });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** parentElement of the matched element. */
|
|
77
|
+
parent(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
78
|
+
return this.client.send("find.parent", { selector, tabId });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Walk up the DOM from selector until ancestor matches. */
|
|
82
|
+
closest(selector: string, ancestor: string, tabId = "default"): Promise<ElementDescriptor[]> {
|
|
83
|
+
return this.client.send("find.closest", { selector, ancestor, tabId });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Boolean / numeric ────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/** Number of elements matching the selector. */
|
|
89
|
+
count(selector: string, tabId = "default"): Promise<number> {
|
|
90
|
+
return this.client.send("find.count", { selector, tabId });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** True if at least one element matches. */
|
|
94
|
+
exists(selector: string, tabId = "default"): Promise<boolean> {
|
|
95
|
+
return this.client.send("find.exists", { selector, tabId });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** True if the first matched element is visible. */
|
|
99
|
+
visible(selector: string, tabId = "default"): Promise<boolean> {
|
|
100
|
+
return this.client.send("find.visible", { selector, tabId });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** True if the first matched element is not disabled. */
|
|
104
|
+
enabled(selector: string, tabId = "default"): Promise<boolean> {
|
|
105
|
+
return this.client.send("find.enabled", { selector, tabId });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** True if the first matched checkbox/radio is checked. */
|
|
109
|
+
checked(selector: string, tabId = "default"): Promise<boolean> {
|
|
110
|
+
return this.client.send("find.checked", { selector, tabId });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Factory helper ───────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
export function createFindAPI(client: PiggyClient): FindClient {
|
|
117
|
+
return new FindClient(client);
|
|
118
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// piggy/http/index.ts
|
|
2
|
+
// Mirrors PiggyHttp.cpp — connects to the HTTP server on port 2005.
|
|
3
|
+
// Use this when you want to talk to the browser over HTTP instead of the unix socket.
|
|
4
|
+
|
|
5
|
+
export interface HttpClientOptions {
|
|
6
|
+
host?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
key: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class PiggyHttpClient {
|
|
12
|
+
private baseUrl: string;
|
|
13
|
+
private key: string;
|
|
14
|
+
|
|
15
|
+
constructor(opts: HttpClientOptions) {
|
|
16
|
+
const host = opts.host ?? "localhost";
|
|
17
|
+
const port = opts.port ?? 2005;
|
|
18
|
+
this.baseUrl = `http://${host}:${port}`;
|
|
19
|
+
this.key = opts.key;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── Health check ──────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
async ping(): Promise<boolean> {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(this.baseUrl, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"X-Piggy-Key": this.key,
|
|
31
|
+
},
|
|
32
|
+
body: "hello",
|
|
33
|
+
});
|
|
34
|
+
const text = await res.text();
|
|
35
|
+
return text.includes("active");
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Send command ──────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
async send<T = any>(cmd: string, payload: Record<string, any> = {}): Promise<T> {
|
|
44
|
+
const res = await fetch(this.baseUrl, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
"X-Piggy-Key": this.key,
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({ id: String(Date.now()), cmd, payload }),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (res.status === 401) throw new Error("Unauthorized — invalid X-Piggy-Key");
|
|
54
|
+
if (res.status === 400) throw new Error("Bad request — invalid JSON");
|
|
55
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
56
|
+
|
|
57
|
+
const data = await res.json() as { ok: boolean; data: T };
|
|
58
|
+
if (!data.ok) throw new Error(String(data.data) ?? "command failed");
|
|
59
|
+
return data.data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createHttpClient(opts: HttpClientOptions): PiggyHttpClient {
|
|
64
|
+
return new PiggyHttpClient(opts);
|
|
65
|
+
}
|
package/piggy/human/index.ts
CHANGED
|
@@ -1,53 +1,115 @@
|
|
|
1
|
-
// piggy/human/index.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
// piggy/human/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
// ─── Local utilities ──────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function randomDelay(min: number, max: number): Promise<void> {
|
|
7
|
+
return new Promise(r => setTimeout(r, Math.floor(Math.random() * (max - min + 1)) + min));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function humanTypeSequence(text: string): string[] {
|
|
11
|
+
const adjacent: Record<string, string[]> = {
|
|
12
|
+
a: ["q","w","s","z"], b: ["v","g","h","n"], c: ["x","d","f","v"],
|
|
13
|
+
d: ["s","e","r","f","c","x"], e: ["w","r","d","s"],
|
|
14
|
+
f: ["d","r","t","g","v","c"], g: ["f","t","y","h","b","v"],
|
|
15
|
+
h: ["g","y","u","j","n","b"], i: ["u","o","k","j"],
|
|
16
|
+
j: ["h","u","i","k","m","n"], k: ["j","i","o","l","m"],
|
|
17
|
+
l: ["k","o","p"], m: ["n","j","k"], n: ["b","h","j","m"],
|
|
18
|
+
o: ["i","p","l","k"], p: ["o","l"], q: ["w","a"],
|
|
19
|
+
r: ["e","t","f","d"], s: ["a","w","e","d","x","z"],
|
|
20
|
+
t: ["r","y","g","f"], u: ["y","i","h","j"],
|
|
21
|
+
v: ["c","f","g","b"], w: ["q","e","a","s"],
|
|
22
|
+
x: ["z","s","d","c"], y: ["t","u","g","h"],
|
|
23
|
+
z: ["a","s","x"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const actions: string[] = [];
|
|
27
|
+
const typoIndices = new Set<number>();
|
|
28
|
+
|
|
29
|
+
if (text.length > 4) {
|
|
30
|
+
let tries = 0;
|
|
31
|
+
while (typoIndices.size < 2 && tries < 20) {
|
|
32
|
+
typoIndices.add(Math.floor(Math.random() * text.length));
|
|
33
|
+
tries++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < text.length; i++) {
|
|
38
|
+
if (typoIndices.has(i)) {
|
|
39
|
+
const ch = text[i]!.toLowerCase();
|
|
40
|
+
const neighbors = adjacent[ch];
|
|
41
|
+
const typo = neighbors
|
|
42
|
+
? neighbors[Math.floor(Math.random() * neighbors.length)] ?? ch
|
|
43
|
+
: ch;
|
|
44
|
+
actions.push(typo);
|
|
45
|
+
actions.push("BACKSPACE");
|
|
46
|
+
}
|
|
47
|
+
actions.push(text[i]!);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return actions;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Profile types ────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export type TypingSpeed = "slow" | "normal" | "fast";
|
|
56
|
+
export type ClickDelay = "cautious" | "normal" | "fast";
|
|
57
|
+
export type ScrollSpeed = "slow" | "normal" | "fast";
|
|
58
|
+
|
|
59
|
+
export interface HumanProfile {
|
|
60
|
+
typingSpeed: TypingSpeed;
|
|
61
|
+
clickDelay: ClickDelay;
|
|
62
|
+
scrollSpeed: ScrollSpeed;
|
|
63
|
+
mouseWiggle: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface HumanSetOptions {
|
|
67
|
+
typingSpeed?: TypingSpeed;
|
|
68
|
+
clickDelay?: ClickDelay;
|
|
69
|
+
scrollSpeed?: ScrollSpeed;
|
|
70
|
+
mouseWiggle?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface HumanTypeOptions {
|
|
74
|
+
selector: string;
|
|
75
|
+
text: string;
|
|
76
|
+
clear?: boolean;
|
|
77
|
+
speed?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface HumanClickOptions {
|
|
81
|
+
selector: string;
|
|
82
|
+
force?: boolean;
|
|
83
|
+
delay?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── HumanClient ─────────────────────────────────────────────────────────────
|
|
87
|
+
// Maps 1:1 to PiggyHuman.cpp commands
|
|
88
|
+
|
|
89
|
+
export class HumanClient {
|
|
90
|
+
constructor(private client: PiggyClient) {}
|
|
91
|
+
|
|
92
|
+
// human.set — update global profile
|
|
93
|
+
set(opts: HumanSetOptions, tabId = "default"): Promise<HumanProfile> {
|
|
94
|
+
return this.client.send("human.set", { ...opts, tabId });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// human.get — read current profile
|
|
98
|
+
get(tabId = "default"): Promise<HumanProfile> {
|
|
99
|
+
return this.client.send("human.get", { tabId });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// human.type — char-by-char typing with realistic delays
|
|
103
|
+
type(opts: HumanTypeOptions, tabId = "default"): Promise<void> {
|
|
104
|
+
return this.client.send("human.type", { ...opts, tabId });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// human.click — delayed click with optional force dispatch
|
|
108
|
+
click(opts: HumanClickOptions, tabId = "default"): Promise<void> {
|
|
109
|
+
return this.client.send("human.click", { ...opts, tabId });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function createHumanAPI(client: PiggyClient): HumanClient {
|
|
114
|
+
return new HumanClient(client);
|
|
115
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// piggy/iframe/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
// ─── Descriptor types ─────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export interface IframeDescriptor {
|
|
7
|
+
index: number;
|
|
8
|
+
src: string;
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ─── Option types ─────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/** Target an iframe by its index or src. One must be provided. */
|
|
16
|
+
export type IframeTarget =
|
|
17
|
+
| { index: number; src?: never }
|
|
18
|
+
| { src: string; index?: never };
|
|
19
|
+
|
|
20
|
+
export type IframeEvaluateOptions = IframeTarget & { js: string };
|
|
21
|
+
export type IframeClickOptions = IframeTarget & { sel: string };
|
|
22
|
+
export type IframeTypeOptions = IframeTarget & { sel: string; text: string };
|
|
23
|
+
export type IframeTextOptions = IframeTarget & { sel: string };
|
|
24
|
+
export type IframeHtmlOptions = IframeTarget;
|
|
25
|
+
export type IframeWaitSelOptions = IframeTarget & { sel: string; timeout?: number };
|
|
26
|
+
|
|
27
|
+
// ─── IframeClient ─────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export class IframeClient {
|
|
30
|
+
constructor(private client: PiggyClient) {}
|
|
31
|
+
|
|
32
|
+
/** List all iframes on the page: index, src, id, name. */
|
|
33
|
+
list(tabId = "default"): Promise<IframeDescriptor[]> {
|
|
34
|
+
return this.client.send("iframe.list", { tabId });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Run arbitrary JS inside the targeted iframe. Returns whatever the script returns. */
|
|
38
|
+
evaluate(opts: IframeEvaluateOptions, tabId = "default"): Promise<unknown> {
|
|
39
|
+
return this.client.send("iframe.evaluate", { ...opts, tabId });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Click a selector inside the targeted iframe. */
|
|
43
|
+
click(opts: IframeClickOptions, tabId = "default"): Promise<boolean> {
|
|
44
|
+
return this.client.send("iframe.click", { ...opts, tabId });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Type text into a selector inside the targeted iframe. */
|
|
48
|
+
type(opts: IframeTypeOptions, tabId = "default"): Promise<boolean> {
|
|
49
|
+
return this.client.send("iframe.type", { ...opts, tabId });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Get innerText of a selector inside the targeted iframe. */
|
|
53
|
+
text(opts: IframeTextOptions, tabId = "default"): Promise<string> {
|
|
54
|
+
return this.client.send("iframe.text", { ...opts, tabId });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Get the full HTML of the targeted iframe. */
|
|
58
|
+
html(opts: IframeHtmlOptions, tabId = "default"): Promise<string> {
|
|
59
|
+
return this.client.send("iframe.html", { ...opts, tabId });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Wait until a selector appears inside the targeted iframe. */
|
|
63
|
+
waitSel(opts: IframeWaitSelOptions, tabId = "default"): Promise<boolean> {
|
|
64
|
+
return this.client.send("iframe.waitSel", { ...opts, tabId });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Factory helper ───────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export function createIframeAPI(client: PiggyClient): IframeClient {
|
|
71
|
+
return new IframeClient(client);
|
|
72
|
+
}
|
|
73
|
+
// Done. One thing worth noting — `IframeTarget` is a discriminated union so TypeScript enforces you pass either `index` or `src`, never both. Usage looks like:
|
|
74
|
+
|
|
75
|
+
// ```js
|
|
76
|
+
// piggy.google.iframe.list()
|
|
77
|
+
// piggy.google.iframe.click({ index: 0, sel: "#btn" })
|
|
78
|
+
// piggy.google.iframe.evaluate({ src: "https://...", js: "document.title" })
|
|
79
|
+
// ```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// piggy/interactions/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
export interface MousePosition {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class InteractionsClient {
|
|
10
|
+
constructor(private client: PiggyClient) {}
|
|
11
|
+
|
|
12
|
+
// ── Click ─────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
click(selector: string, tabId = "default"): Promise<boolean> {
|
|
15
|
+
return this.client.send("click", { selector, tabId });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
dblclick(selector: string, tabId = "default"): Promise<boolean> {
|
|
19
|
+
return this.client.send("dblclick", { selector, tabId });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hover(selector: string, tabId = "default"): Promise<boolean> {
|
|
23
|
+
return this.client.send("hover", { selector, tabId });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Input ─────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
type(selector: string, text: string, tabId = "default"): Promise<boolean> {
|
|
29
|
+
return this.client.send("type", { selector, text, tabId });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
typeClear(selector: string, text: string, tabId = "default"): Promise<boolean> {
|
|
33
|
+
return this.client.send("type", { selector, text, clear: true, tabId });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
select(selector: string, value: string, tabId = "default"): Promise<boolean> {
|
|
37
|
+
return this.client.send("select", { selector, value, tabId });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Scroll ────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
scrollTo(selector: string, tabId = "default"): Promise<boolean> {
|
|
43
|
+
return this.client.send("scroll.to", { selector, tabId });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
scrollBy(px: number, tabId = "default"): Promise<boolean> {
|
|
47
|
+
return this.client.send("scroll.by", { px, tabId });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Keyboard ──────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
keyPress(key: string, tabId = "default"): Promise<boolean> {
|
|
53
|
+
return this.client.send("keyboard.press", { key, tabId });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
keyCombo(combo: string, tabId = "default"): Promise<boolean> {
|
|
57
|
+
return this.client.send("keyboard.combo", { combo, tabId });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Mouse ─────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
mouseMove(x: number, y: number, tabId = "default"): Promise<boolean> {
|
|
63
|
+
return this.client.send("mouse.move", { x, y, tabId });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
mouseDrag(from: MousePosition, to: MousePosition, tabId = "default"): Promise<boolean> {
|
|
67
|
+
return this.client.send("mouse.drag", { from, to, tabId });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Evaluate ──────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
evaluate(js: string, tabId = "default"): Promise<unknown> {
|
|
73
|
+
return this.client.send("evaluate", { js, tabId });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createInteractionsAPI(client: PiggyClient): InteractionsClient {
|
|
78
|
+
return new InteractionsClient(client);
|
|
79
|
+
}
|