nothing-browser 0.0.20 → 0.1.0
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/launch/detect.js +13 -0
- package/dist/launch/spawn.js +13 -0
- 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 +90 -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/launch/detect.d.ts +1 -1
- package/dist/piggy/launch/detect.d.ts.map +1 -1
- package/dist/piggy/launch/spawn.d.ts.map +1 -1
- 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 +81 -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 +914 -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.d.ts +79 -0
- package/piggy/find/index.ts +165 -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/launch/detect.ts +19 -2
- package/piggy/launch/spawn.ts +1 -8
- package/piggy/media/index.ts +46 -0
- package/piggy/navigation/index.ts +52 -0
- package/piggy/provide/index.ts +144 -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,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
|
+
}
|
package/piggy/launch/detect.ts
CHANGED
|
@@ -2,15 +2,32 @@ import { existsSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import logger from '../logger';
|
|
4
4
|
|
|
5
|
-
export type BinaryMode = 'headless' | 'headful';
|
|
5
|
+
export type BinaryMode = 'headless' | 'headful' | (string & {});
|
|
6
6
|
|
|
7
|
-
const BINARY_NAMES: Record<
|
|
7
|
+
const BINARY_NAMES: Record<string, string> = {
|
|
8
8
|
headless: 'nothing-browser-headless',
|
|
9
9
|
headful: 'nothing-browser-headful',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export function detectBinary(mode: BinaryMode = 'headless'): string | null {
|
|
13
13
|
const cwd = process.cwd();
|
|
14
|
+
|
|
15
|
+
// Custom path passed directly
|
|
16
|
+
if (mode !== 'headless' && mode !== 'headful') {
|
|
17
|
+
if (existsSync(mode)) {
|
|
18
|
+
logger.success(`Binary found (custom path): ${mode}`);
|
|
19
|
+
return mode;
|
|
20
|
+
}
|
|
21
|
+
// try relative to cwd
|
|
22
|
+
const abs = join(cwd, mode);
|
|
23
|
+
if (existsSync(abs)) {
|
|
24
|
+
logger.success(`Binary found (custom path): ${abs}`);
|
|
25
|
+
return abs;
|
|
26
|
+
}
|
|
27
|
+
logger.error(`Binary not found at custom path: ${mode}`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
const name = BINARY_NAMES[mode];
|
|
15
32
|
|
|
16
33
|
// Windows
|
package/piggy/launch/spawn.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// piggy/launch/spawn.ts
|
|
2
1
|
import { spawn as nodeSpawn } from "child_process";
|
|
3
2
|
import { execSync } from "child_process";
|
|
4
3
|
import { platform } from "os";
|
|
@@ -8,13 +7,11 @@ import logger from "../logger";
|
|
|
8
7
|
let activeProcess: any = null;
|
|
9
8
|
const extraProcesses: any[] = [];
|
|
10
9
|
|
|
11
|
-
// Runtime detection without importing "bun" at build time
|
|
12
10
|
const isBun = typeof (globalThis as any).Bun !== 'undefined';
|
|
13
11
|
|
|
14
12
|
export function killAllBrowsers(): void {
|
|
15
13
|
try {
|
|
16
14
|
logger.info('Cleaning up existing browser processes...');
|
|
17
|
-
|
|
18
15
|
if (platform() === 'win32') {
|
|
19
16
|
execSync('taskkill /F /IM nothing-browser-headless.exe 2>nul || true', { stdio: 'ignore' });
|
|
20
17
|
execSync('taskkill /F /IM nothing-browser-headful.exe 2>nul || true', { stdio: 'ignore' });
|
|
@@ -42,7 +39,6 @@ export async function spawnBrowser(mode: BinaryMode = 'headless'): Promise<strin
|
|
|
42
39
|
logger.info(`Spawning Nothing Browser (${mode}) from: ${binaryPath}`);
|
|
43
40
|
|
|
44
41
|
if (isBun) {
|
|
45
|
-
// Bun runtime - use Bun.spawn
|
|
46
42
|
const Bun = (globalThis as any).Bun;
|
|
47
43
|
activeProcess = Bun.spawn([binaryPath], {
|
|
48
44
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -66,7 +62,6 @@ export async function spawnBrowser(mode: BinaryMode = 'headless'): Promise<strin
|
|
|
66
62
|
activeProcess = null;
|
|
67
63
|
});
|
|
68
64
|
} else {
|
|
69
|
-
// Node.js runtime
|
|
70
65
|
activeProcess = nodeSpawn(binaryPath, [], {
|
|
71
66
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
72
67
|
env: process.env,
|
|
@@ -113,7 +108,6 @@ export async function spawnBrowserOnSocket(
|
|
|
113
108
|
logger.info(`Spawning browser (${mode}) on socket: ${socketName}`);
|
|
114
109
|
|
|
115
110
|
if (isBun) {
|
|
116
|
-
// Bun runtime
|
|
117
111
|
const Bun = (globalThis as any).Bun;
|
|
118
112
|
const proc = Bun.spawn([binaryPath], {
|
|
119
113
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -126,7 +120,6 @@ export async function spawnBrowserOnSocket(
|
|
|
126
120
|
logger.warn(`Browser on socket ${socketName} exited with code: ${code}`);
|
|
127
121
|
});
|
|
128
122
|
} else {
|
|
129
|
-
// Node.js runtime
|
|
130
123
|
const proc = nodeSpawn(binaryPath, [], {
|
|
131
124
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
132
125
|
env: { ...process.env, PIGGY_SOCKET: socketName },
|
|
@@ -153,7 +146,7 @@ export function killBrowser(): void {
|
|
|
153
146
|
}
|
|
154
147
|
activeProcess = null;
|
|
155
148
|
}
|
|
156
|
-
|
|
149
|
+
|
|
157
150
|
for (const proc of extraProcesses) {
|
|
158
151
|
if (isBun) {
|
|
159
152
|
proc.kill();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// piggy/media/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
|
|
6
|
+
export class MediaClient {
|
|
7
|
+
constructor(private client: PiggyClient) {}
|
|
8
|
+
|
|
9
|
+
// ── Screenshot ────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
async screenshot(filePath?: string, tabId = "default"): Promise<string> {
|
|
12
|
+
const b64 = await this.client.send<string>("screenshot", { tabId });
|
|
13
|
+
if (filePath) {
|
|
14
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
15
|
+
writeFileSync(filePath, Buffer.from(b64, "base64"));
|
|
16
|
+
return filePath;
|
|
17
|
+
}
|
|
18
|
+
return b64;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── PDF ───────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
async pdf(filePath?: string, tabId = "default"): Promise<string> {
|
|
24
|
+
const b64 = await this.client.send<string>("pdf", { tabId });
|
|
25
|
+
if (filePath) {
|
|
26
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
27
|
+
writeFileSync(filePath, Buffer.from(b64, "base64"));
|
|
28
|
+
return filePath;
|
|
29
|
+
}
|
|
30
|
+
return b64;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Image blocking ────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
blockImages(tabId = "default"): Promise<void> {
|
|
36
|
+
return this.client.send("intercept.block.images", { tabId });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
unblockImages(tabId = "default"): Promise<void> {
|
|
40
|
+
return this.client.send("intercept.unblock.images", { tabId });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createMediaAPI(client: PiggyClient): MediaClient {
|
|
45
|
+
return new MediaClient(client);
|
|
46
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// piggy/navigation/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
export class NavigationClient {
|
|
5
|
+
constructor(private client: PiggyClient) {}
|
|
6
|
+
|
|
7
|
+
// ── Navigation ────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
navigate(url: string, tabId = "default"): Promise<void> {
|
|
10
|
+
return this.client.send("navigate", { url, tabId });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
reload(tabId = "default"): Promise<void> {
|
|
14
|
+
return this.client.send("reload", { tabId });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
goBack(tabId = "default"): Promise<void> {
|
|
18
|
+
return this.client.send("go.back", { tabId });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
goForward(tabId = "default"): Promise<void> {
|
|
22
|
+
return this.client.send("go.forward", { tabId });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Page info ─────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
url(tabId = "default"): Promise<string> {
|
|
28
|
+
return this.client.send("page.url", { tabId });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
title(tabId = "default"): Promise<string> {
|
|
32
|
+
return this.client.send("page.title", { tabId });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
content(tabId = "default"): Promise<string> {
|
|
36
|
+
return this.client.send("page.content", { tabId });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Wait ──────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
waitForNavigation(tabId = "default"): Promise<void> {
|
|
42
|
+
return this.client.send("wait.navigation", { tabId });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
waitForSelector(selector: string, timeout = 10000, tabId = "default"): Promise<void> {
|
|
46
|
+
return this.client.send("wait.selector", { selector, timeout, tabId });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createNavigationAPI(client: PiggyClient): NavigationClient {
|
|
51
|
+
return new NavigationClient(client);
|
|
52
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// piggy/provide/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
// ─── Return types ─────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export interface ProvideTable {
|
|
7
|
+
headers: string[];
|
|
8
|
+
rows: string[][];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ProvideLink {
|
|
12
|
+
text: string;
|
|
13
|
+
href: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ProvideImage {
|
|
17
|
+
alt: string;
|
|
18
|
+
src: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ProvideForm {
|
|
22
|
+
[name: string]: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProvidePage {
|
|
26
|
+
title: string;
|
|
27
|
+
url: string;
|
|
28
|
+
html: string;
|
|
29
|
+
text: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProvideDiv {
|
|
33
|
+
tag: string;
|
|
34
|
+
id: string;
|
|
35
|
+
cls: string;
|
|
36
|
+
text: string;
|
|
37
|
+
html: string;
|
|
38
|
+
children: ProvideDiv[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ProvideMeta {
|
|
42
|
+
[name: string]: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ProvideSelectOption {
|
|
46
|
+
text: string;
|
|
47
|
+
value: string;
|
|
48
|
+
selected: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ProvideSelect {
|
|
52
|
+
value: string;
|
|
53
|
+
options: ProvideSelectOption[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── ProvideClient ────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export class ProvideClient {
|
|
59
|
+
constructor(private client: PiggyClient) {}
|
|
60
|
+
|
|
61
|
+
/** innerText of the first matched element. */
|
|
62
|
+
text(selector: string, tabId = "default"): Promise<string> {
|
|
63
|
+
return this.client.send("provide.text", { selector, tabId });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** innerText of all matched elements. */
|
|
67
|
+
textAll(selector: string, tabId = "default"): Promise<string[]> {
|
|
68
|
+
return this.client.send("provide.textAll", { selector, tabId });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Single attribute value from the first matched element. */
|
|
72
|
+
attr(selector: string, attr: string, tabId = "default"): Promise<string> {
|
|
73
|
+
return this.client.send("provide.attr", { selector, attr, tabId });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Attribute value from all matched elements. */
|
|
77
|
+
attrAll(selector: string, attr: string, tabId = "default"): Promise<string[]> {
|
|
78
|
+
return this.client.send("provide.attrAll", { selector, attr, tabId });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** innerHTML of the first matched element. */
|
|
82
|
+
html(selector: string, tabId = "default"): Promise<string> {
|
|
83
|
+
return this.client.send("provide.html", { selector, tabId });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Extract a table into headers + rows. */
|
|
87
|
+
table(selector: string, tabId = "default"): Promise<ProvideTable> {
|
|
88
|
+
return this.client.send("provide.table", { selector, tabId });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Extract a list of text items. Optionally scope items with itemSel. */
|
|
92
|
+
list(selector: string, itemSel?: string, tabId = "default"): Promise<string[]> {
|
|
93
|
+
return this.client.send("provide.list", { selector, itemSel, tabId });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** All links inside an optional selector. */
|
|
97
|
+
links(selector?: string, tabId = "default"): Promise<ProvideLink[]> {
|
|
98
|
+
return this.client.send("provide.links", { selector, tabId });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** All images inside an optional selector. */
|
|
102
|
+
images(selector?: string, tabId = "default"): Promise<ProvideImage[]> {
|
|
103
|
+
return this.client.send("provide.images", { selector, tabId });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Form field name→value map. */
|
|
107
|
+
form(selector: string, tabId = "default"): Promise<ProvideForm> {
|
|
108
|
+
return this.client.send("provide.form", { selector, tabId });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Full page info: title, url, html, text. */
|
|
112
|
+
page(tabId = "default"): Promise<ProvidePage> {
|
|
113
|
+
return this.client.send("provide.page", { tabId });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Structured div: tag, id, cls, text, html, children[]. */
|
|
117
|
+
div(selector: string, tabId = "default"): Promise<ProvideDiv> {
|
|
118
|
+
return this.client.send("provide.div", { selector, tabId });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** All <meta> name→content pairs. */
|
|
122
|
+
meta(tabId = "default"): Promise<ProvideMeta> {
|
|
123
|
+
return this.client.send("provide.meta", { tabId });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** <select> current value + all options. */
|
|
127
|
+
select(selector: string, tabId = "default"): Promise<ProvideSelect> {
|
|
128
|
+
return this.client.send("provide.select", { selector, tabId });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parse JSON from element innerText or script[type=application/json].
|
|
133
|
+
* selector is optional — defaults to the first matching JSON script tag.
|
|
134
|
+
*/
|
|
135
|
+
json(selector?: string, tabId = "default"): Promise<unknown> {
|
|
136
|
+
return this.client.send("provide.json", { selector, tabId });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ─── Factory helper ───────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
export function createProvideAPI(client: PiggyClient): ProvideClient {
|
|
143
|
+
return new ProvideClient(client);
|
|
144
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// piggy/proxy/index.ts
|
|
2
|
+
import { PiggyClient } from "../client";
|
|
3
|
+
|
|
4
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export type ProxyType = "http" | "https" | "socks5";
|
|
7
|
+
export type ProxyHealth = "alive" | "dead" | "checking" | "unchecked";
|
|
8
|
+
export type RotationMode = "none" | "timed" | "perrequest";
|
|
9
|
+
|
|
10
|
+
export interface ProxyEntry {
|
|
11
|
+
index: number;
|
|
12
|
+
host: string;
|
|
13
|
+
port: number;
|
|
14
|
+
type: ProxyType;
|
|
15
|
+
user: string;
|
|
16
|
+
proxy: string; // full string e.g. "socks5://user:pass@host:port"
|
|
17
|
+
latency: number;
|
|
18
|
+
health: ProxyHealth;
|
|
19
|
+
current: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ProxyStats {
|
|
23
|
+
total: number;
|
|
24
|
+
alive: number;
|
|
25
|
+
dead: number;
|
|
26
|
+
index: number;
|
|
27
|
+
active: boolean;
|
|
28
|
+
checking: boolean;
|
|
29
|
+
skipDead: boolean;
|
|
30
|
+
autoCheck: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ProxyListResult {
|
|
34
|
+
proxies: ProxyEntry[];
|
|
35
|
+
total: number;
|
|
36
|
+
shown: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ProxyCurrent {
|
|
40
|
+
active: boolean;
|
|
41
|
+
host?: string;
|
|
42
|
+
port?: number;
|
|
43
|
+
type?: ProxyType;
|
|
44
|
+
user?: string;
|
|
45
|
+
proxy?: string;
|
|
46
|
+
latency?: number;
|
|
47
|
+
health?: ProxyHealth;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProxyConfig {
|
|
51
|
+
skipDead: boolean;
|
|
52
|
+
autoCheck: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Inline set options ───────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
export type ProxySetOptions =
|
|
58
|
+
| { proxy: string }
|
|
59
|
+
| { host: string; port: number; type?: ProxyType; user?: string; pass?: string };
|
|
60
|
+
|
|
61
|
+
// ─── ProxyClient ──────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export class ProxyClient {
|
|
64
|
+
constructor(private client: PiggyClient) {}
|
|
65
|
+
|
|
66
|
+
/** Load proxies from a local file path. */
|
|
67
|
+
load(path: string): Promise<void> {
|
|
68
|
+
return this.client.send("proxy.load", { path });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Fetch proxies from a URL. Result comes via proxy:loaded / proxy:fetch:failed events. */
|
|
72
|
+
fetch(url: string): Promise<void> {
|
|
73
|
+
return this.client.send("proxy.fetch", { url });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Load an .ovpn config file. */
|
|
77
|
+
ovpn(path: string): Promise<void> {
|
|
78
|
+
return this.client.send("proxy.ovpn", { path });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Set a single proxy inline. */
|
|
82
|
+
set(opts: ProxySetOptions): Promise<void> {
|
|
83
|
+
return this.client.send("proxy.set", opts);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Health-check all loaded proxies. Results come via events. */
|
|
87
|
+
test(): Promise<void> {
|
|
88
|
+
return this.client.send("proxy.test", {});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Abort an in-progress health check. */
|
|
92
|
+
testStop(): Promise<void> {
|
|
93
|
+
return this.client.send("proxy.test.stop", {});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Rotate to the next proxy in the pool. */
|
|
97
|
+
next(): Promise<string> {
|
|
98
|
+
return this.client.send("proxy.next", {});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Alias for next(). */
|
|
102
|
+
rotate(): Promise<string> {
|
|
103
|
+
return this.client.send("proxy.rotate", {});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Disable the proxy — use the real IP. */
|
|
107
|
+
disable(): Promise<void> {
|
|
108
|
+
return this.client.send("proxy.disable", {});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Re-enable the current proxy. */
|
|
112
|
+
enable(): Promise<void> {
|
|
113
|
+
return this.client.send("proxy.enable", {});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Get the current active proxy details. */
|
|
117
|
+
current(): Promise<ProxyCurrent> {
|
|
118
|
+
return this.client.send("proxy.current", {});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Get pool stats: total, alive, dead, index, active, checking. */
|
|
122
|
+
stats(): Promise<ProxyStats> {
|
|
123
|
+
return this.client.send("proxy.stats", {});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** List all proxies with health info. limit defaults to 500. */
|
|
127
|
+
list(limit?: number): Promise<ProxyListResult> {
|
|
128
|
+
return this.client.send("proxy.list", { limit });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Set rotation mode and interval (seconds, only used for "timed"). */
|
|
132
|
+
rotation(mode: RotationMode, interval?: number): Promise<void> {
|
|
133
|
+
return this.client.send("proxy.rotation", { mode, interval });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Set skipDead / autoCheck flags. */
|
|
137
|
+
config(opts: Partial<ProxyConfig>): Promise<ProxyConfig> {
|
|
138
|
+
return this.client.send("proxy.config", opts);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Save the current proxy list to a file.
|
|
143
|
+
* filter: "all" | "alive" | "dead" — defaults to "all".
|
|
144
|
+
*/
|
|
145
|
+
save(path: string, filter?: "all" | "alive" | "dead"): Promise<void> {
|
|
146
|
+
return this.client.send("proxy.save", { path, filter });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─── Factory helper ───────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
export function createProxyAPI(client: PiggyClient): ProxyClient {
|
|
153
|
+
return new ProxyClient(client);
|
|
154
|
+
}
|