@gatesolve/playwright-plugin 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/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @gatesolve/playwright-plugin
2
+
3
+ Automatic CAPTCHA solving for Playwright. Detects Cloudflare Turnstile, reCAPTCHA, and hCaptcha challenges and solves them via [GateSolve](https://gatesolve.dev).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @gatesolve/playwright-plugin
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### One-shot: Solve CAPTCHA on current page
14
+
15
+ ```typescript
16
+ import { chromium } from 'playwright';
17
+ import { solveOnPage } from '@gatesolve/playwright-plugin';
18
+
19
+ const browser = await chromium.launch();
20
+ const page = await browser.newPage();
21
+ await page.goto('https://example.com/login');
22
+
23
+ // Detect and solve any CAPTCHA on the page
24
+ const result = await solveOnPage(page, {
25
+ apiKey: 'gs_your_key_here', // Free at gatesolve.dev
26
+ });
27
+
28
+ if (result?.success) {
29
+ console.log(`Solved ${result.type} in ${result.solveTimeMs}ms`);
30
+ // Token is already injected into the page — submit the form
31
+ await page.click('button[type="submit"]');
32
+ }
33
+ ```
34
+
35
+ ### Auto-solve: Wrap browser context
36
+
37
+ ```typescript
38
+ import { chromium } from 'playwright';
39
+ import { withGateSolve } from '@gatesolve/playwright-plugin';
40
+
41
+ const browser = await chromium.launch();
42
+ const context = await browser.newContext();
43
+
44
+ // Every page in this context will auto-solve CAPTCHAs
45
+ withGateSolve(context, {
46
+ apiKey: 'gs_your_key_here',
47
+ debug: true,
48
+ });
49
+
50
+ const page = await context.newPage();
51
+ await page.goto('https://example.com/protected');
52
+ // CAPTCHA is solved automatically on page load
53
+ ```
54
+
55
+ ### Detect only (no solve)
56
+
57
+ ```typescript
58
+ import { detectCaptcha } from '@gatesolve/playwright-plugin';
59
+
60
+ const detection = await detectCaptcha(page);
61
+ if (detection) {
62
+ console.log(`Found ${detection.type} with siteKey ${detection.siteKey}`);
63
+ }
64
+ ```
65
+
66
+ ## API
67
+
68
+ ### `solveOnPage(page, options)`
69
+
70
+ Detect and solve a CAPTCHA on the current page. Returns `SolveResult | null`.
71
+
72
+ ### `withGateSolve(context, options)`
73
+
74
+ Wrap a `BrowserContext` to auto-solve CAPTCHAs on every page load. Returns the same context.
75
+
76
+ ### `detectCaptcha(page)`
77
+
78
+ Detect CAPTCHA type and siteKey without solving. Returns `CaptchaDetection | null`.
79
+
80
+ ## Options
81
+
82
+ | Option | Type | Default | Description |
83
+ |--------|------|---------|-------------|
84
+ | `apiKey` | `string` | required | GateSolve API key |
85
+ | `baseUrl` | `string` | `https://gatesolve.dev` | API base URL |
86
+ | `timeout` | `number` | `30000` | Max solve wait (ms) |
87
+ | `pollInterval` | `number` | `2000` | Poll frequency (ms) |
88
+ | `debug` | `boolean` | `false` | Log debug info |
89
+
90
+ ## Supported CAPTCHAs
91
+
92
+ - **Cloudflare Turnstile** — $0.02/solve
93
+ - **reCAPTCHA v2/v3** — $0.03/solve
94
+ - **hCaptcha** — $0.03/solve
95
+
96
+ ## Free Tier
97
+
98
+ 100 free solves per API key. No credit card required. Get your key at [gatesolve.dev](https://gatesolve.dev).
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @gatesolve/playwright-plugin
3
+ *
4
+ * Automatic CAPTCHA solving for Playwright.
5
+ * Detects Cloudflare Turnstile, reCAPTCHA, and hCaptcha on page navigation
6
+ * and solves them via GateSolve API.
7
+ *
8
+ * Usage:
9
+ * import { solveOnPage, withGateSolve } from '@gatesolve/playwright-plugin';
10
+ *
11
+ * // Option 1: One-shot — solve any CAPTCHA on current page
12
+ * const token = await solveOnPage(page, { apiKey: 'gs_...' });
13
+ *
14
+ * // Option 2: Auto-detect — wrap browser context, solves on every navigation
15
+ * const context = await withGateSolve(browserContext, { apiKey: 'gs_...' });
16
+ */
17
+ export interface GateSolveOptions {
18
+ /** GateSolve API key. Get one free at gatesolve.dev */
19
+ apiKey: string;
20
+ /** API base URL (default: https://gatesolve.dev) */
21
+ baseUrl?: string;
22
+ /** Max time to wait for solve in ms (default: 30000) */
23
+ timeout?: number;
24
+ /** Poll interval in ms (default: 2000) */
25
+ pollInterval?: number;
26
+ /** Log debug info (default: false) */
27
+ debug?: boolean;
28
+ }
29
+ export interface CaptchaDetection {
30
+ type: "turnstile" | "recaptcha" | "hcaptcha";
31
+ siteKey: string;
32
+ pageUrl: string;
33
+ }
34
+ export interface SolveResult {
35
+ success: boolean;
36
+ token?: string;
37
+ type?: string;
38
+ error?: string;
39
+ solveTimeMs?: number;
40
+ }
41
+ interface MinimalPage {
42
+ url(): string;
43
+ evaluate<T>(fn: string | ((...args: any[]) => T), ...args: any[]): Promise<T>;
44
+ waitForTimeout(ms: number): Promise<void>;
45
+ }
46
+ interface MinimalContext {
47
+ on(event: "page", handler: (page: MinimalPage) => void): void;
48
+ pages(): MinimalPage[];
49
+ }
50
+ /**
51
+ * Detect CAPTCHA challenges on a page.
52
+ */
53
+ export declare function detectCaptcha(page: MinimalPage): Promise<CaptchaDetection | null>;
54
+ /**
55
+ * Detect and solve any CAPTCHA on the current page.
56
+ * Returns the solve result, or null if no CAPTCHA was found.
57
+ */
58
+ export declare function solveOnPage(page: MinimalPage, opts: GateSolveOptions): Promise<SolveResult | null>;
59
+ /**
60
+ * Wrap a Playwright BrowserContext to automatically solve CAPTCHAs on every page load.
61
+ * Returns the same context (mutated with event listener).
62
+ */
63
+ export declare function withGateSolve<T extends MinimalContext>(context: T, opts: GateSolveOptions): T;
64
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * @gatesolve/playwright-plugin
4
+ *
5
+ * Automatic CAPTCHA solving for Playwright.
6
+ * Detects Cloudflare Turnstile, reCAPTCHA, and hCaptcha on page navigation
7
+ * and solves them via GateSolve API.
8
+ *
9
+ * Usage:
10
+ * import { solveOnPage, withGateSolve } from '@gatesolve/playwright-plugin';
11
+ *
12
+ * // Option 1: One-shot — solve any CAPTCHA on current page
13
+ * const token = await solveOnPage(page, { apiKey: 'gs_...' });
14
+ *
15
+ * // Option 2: Auto-detect — wrap browser context, solves on every navigation
16
+ * const context = await withGateSolve(browserContext, { apiKey: 'gs_...' });
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.detectCaptcha = detectCaptcha;
20
+ exports.solveOnPage = solveOnPage;
21
+ exports.withGateSolve = withGateSolve;
22
+ const DEFAULT_BASE_URL = "https://gatesolve.dev";
23
+ const DEFAULT_TIMEOUT = 30000;
24
+ const DEFAULT_POLL_INTERVAL = 2000;
25
+ function log(opts, ...args) {
26
+ if (opts.debug)
27
+ console.log("[gatesolve]", ...args);
28
+ }
29
+ /**
30
+ * Detect CAPTCHA challenges on a page.
31
+ */
32
+ async function detectCaptcha(page) {
33
+ return page.evaluate(() => {
34
+ // Cloudflare Turnstile
35
+ const turnstileEl = document.querySelector("[data-sitekey].cf-turnstile") ||
36
+ document.querySelector('iframe[src*="challenges.cloudflare.com"]');
37
+ if (turnstileEl) {
38
+ const siteKey = turnstileEl.getAttribute("data-sitekey") ||
39
+ // Extract from iframe src
40
+ new URL(turnstileEl.src || "").searchParams.get("k") ||
41
+ "";
42
+ return {
43
+ type: "turnstile",
44
+ siteKey,
45
+ pageUrl: window.location.href,
46
+ };
47
+ }
48
+ // reCAPTCHA v2/v3
49
+ const recaptchaEl = document.querySelector(".g-recaptcha[data-sitekey]") ||
50
+ document.querySelector('iframe[src*="google.com/recaptcha"]');
51
+ if (recaptchaEl) {
52
+ const siteKey = recaptchaEl.getAttribute("data-sitekey") ||
53
+ new URL(recaptchaEl.src || "").searchParams.get("k") ||
54
+ "";
55
+ return {
56
+ type: "recaptcha",
57
+ siteKey,
58
+ pageUrl: window.location.href,
59
+ };
60
+ }
61
+ // hCaptcha
62
+ const hcaptchaEl = document.querySelector(".h-captcha[data-sitekey]") ||
63
+ document.querySelector('iframe[src*="hcaptcha.com"]');
64
+ if (hcaptchaEl) {
65
+ const siteKey = hcaptchaEl.getAttribute("data-sitekey") ||
66
+ new URL(hcaptchaEl.src || "").searchParams.get("sitekey") ||
67
+ "";
68
+ return {
69
+ type: "hcaptcha",
70
+ siteKey,
71
+ pageUrl: window.location.href,
72
+ };
73
+ }
74
+ return null;
75
+ });
76
+ }
77
+ /**
78
+ * Submit a solve request to GateSolve and poll for result.
79
+ */
80
+ async function submitAndPoll(detection, opts) {
81
+ const baseUrl = opts.baseUrl || DEFAULT_BASE_URL;
82
+ const timeout = opts.timeout || DEFAULT_TIMEOUT;
83
+ const pollInterval = opts.pollInterval || DEFAULT_POLL_INTERVAL;
84
+ const startTime = Date.now();
85
+ log(opts, `Solving ${detection.type} for ${detection.pageUrl}`);
86
+ // Submit
87
+ const submitRes = await fetch(`${baseUrl}/api/solve`, {
88
+ method: "POST",
89
+ headers: {
90
+ "Content-Type": "application/json",
91
+ "x-api-key": opts.apiKey,
92
+ },
93
+ body: JSON.stringify({
94
+ type: detection.type,
95
+ siteKey: detection.siteKey,
96
+ pageUrl: detection.pageUrl,
97
+ }),
98
+ });
99
+ if (!submitRes.ok) {
100
+ const err = await submitRes.text();
101
+ return { success: false, error: `Submit failed (${submitRes.status}): ${err}` };
102
+ }
103
+ const submitData = (await submitRes.json());
104
+ // If immediate result (unlikely but handle it)
105
+ if (submitData.token) {
106
+ return {
107
+ success: true,
108
+ token: submitData.token,
109
+ type: detection.type,
110
+ solveTimeMs: Date.now() - startTime,
111
+ };
112
+ }
113
+ if (!submitData.id) {
114
+ return { success: false, error: submitData.error || "No job ID returned" };
115
+ }
116
+ // Poll
117
+ const jobId = submitData.id;
118
+ log(opts, `Job ${jobId} submitted, polling...`);
119
+ while (Date.now() - startTime < timeout) {
120
+ await new Promise((r) => setTimeout(r, pollInterval));
121
+ const pollRes = await fetch(`${baseUrl}/api/solve?id=${jobId}`, {
122
+ headers: { "x-api-key": opts.apiKey },
123
+ });
124
+ if (!pollRes.ok)
125
+ continue;
126
+ const pollData = (await pollRes.json());
127
+ if (pollData.status === "completed" && pollData.token) {
128
+ log(opts, `Solved in ${Date.now() - startTime}ms`);
129
+ return {
130
+ success: true,
131
+ token: pollData.token,
132
+ type: detection.type,
133
+ solveTimeMs: Date.now() - startTime,
134
+ };
135
+ }
136
+ if (pollData.status === "failed") {
137
+ return { success: false, error: pollData.error || "Solve failed" };
138
+ }
139
+ log(opts, `Status: ${pollData.status}, waiting...`);
140
+ }
141
+ return { success: false, error: `Timeout after ${timeout}ms` };
142
+ }
143
+ /**
144
+ * Inject a solved CAPTCHA token back into the page.
145
+ */
146
+ async function injectToken(page, detection, token) {
147
+ await page.evaluate((args) => {
148
+ const { type, token } = args;
149
+ if (type === "turnstile") {
150
+ // Set the hidden input
151
+ const input = document.querySelector('input[name="cf-turnstile-response"]') ||
152
+ document.querySelector(".cf-turnstile input[type=hidden]");
153
+ if (input)
154
+ input.value = token;
155
+ // Call the callback if it exists
156
+ const widget = document.querySelector(".cf-turnstile");
157
+ const cbName = widget?.getAttribute("data-callback");
158
+ if (cbName && typeof window[cbName] === "function") {
159
+ window[cbName](token);
160
+ }
161
+ }
162
+ else if (type === "recaptcha") {
163
+ const textarea = document.querySelector("#g-recaptcha-response");
164
+ if (textarea) {
165
+ textarea.style.display = "block";
166
+ textarea.value = token;
167
+ }
168
+ // Trigger callback
169
+ if (typeof window.___grecaptcha_cfg !== "undefined") {
170
+ const clients = window.___grecaptcha_cfg?.clients;
171
+ if (clients) {
172
+ for (const client of Object.values(clients)) {
173
+ for (const val of Object.values(client)) {
174
+ if (val?.callback && typeof val.callback === "function") {
175
+ val.callback(token);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ else if (type === "hcaptcha") {
183
+ const textarea = document.querySelector('[name="h-captcha-response"]');
184
+ if (textarea)
185
+ textarea.value = token;
186
+ const iframe = document.querySelector('iframe[src*="hcaptcha.com"]');
187
+ const cbName = iframe
188
+ ?.closest(".h-captcha")
189
+ ?.getAttribute("data-callback");
190
+ if (cbName && typeof window[cbName] === "function") {
191
+ window[cbName](token);
192
+ }
193
+ }
194
+ }, { type: detection.type, token });
195
+ }
196
+ /**
197
+ * Detect and solve any CAPTCHA on the current page.
198
+ * Returns the solve result, or null if no CAPTCHA was found.
199
+ */
200
+ async function solveOnPage(page, opts) {
201
+ const detection = await detectCaptcha(page);
202
+ if (!detection) {
203
+ log(opts, "No CAPTCHA detected");
204
+ return null;
205
+ }
206
+ log(opts, `Detected ${detection.type} (siteKey: ${detection.siteKey})`);
207
+ const result = await submitAndPoll(detection, opts);
208
+ if (result.success && result.token) {
209
+ await injectToken(page, detection, result.token);
210
+ log(opts, "Token injected into page");
211
+ }
212
+ return result;
213
+ }
214
+ /**
215
+ * Wrap a Playwright BrowserContext to automatically solve CAPTCHAs on every page load.
216
+ * Returns the same context (mutated with event listener).
217
+ */
218
+ function withGateSolve(context, opts) {
219
+ const handlePage = (page) => {
220
+ // We need to listen for navigation completion. Since we use minimal types,
221
+ // check if the page has a 'on' method for load events
222
+ const pageAny = page;
223
+ if (typeof pageAny.on === "function") {
224
+ pageAny.on("load", async () => {
225
+ try {
226
+ // Small delay to let JS render CAPTCHA widgets
227
+ await page.waitForTimeout(1000);
228
+ await solveOnPage(page, opts);
229
+ }
230
+ catch (err) {
231
+ log(opts, "Auto-solve error:", err);
232
+ }
233
+ });
234
+ }
235
+ };
236
+ // Attach to existing pages
237
+ for (const page of context.pages()) {
238
+ handlePage(page);
239
+ }
240
+ // Attach to future pages
241
+ context.on("page", handlePage);
242
+ return context;
243
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@gatesolve/playwright-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Automatic CAPTCHA solving for Playwright. Detects Cloudflare Turnstile, reCAPTCHA, and hCaptcha challenges and solves them via GateSolve.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "playwright",
13
+ "captcha",
14
+ "turnstile",
15
+ "cloudflare",
16
+ "recaptcha",
17
+ "hcaptcha",
18
+ "gatesolve",
19
+ "automation",
20
+ "ai-agent"
21
+ ],
22
+ "author": "Arson <arson@agentmail.to>",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/arsonx-dev/gatesolve-playwright"
27
+ },
28
+ "peerDependencies": {
29
+ "playwright": ">=1.40.0",
30
+ "playwright-core": ">=1.40.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "playwright": {
34
+ "optional": true
35
+ },
36
+ "playwright-core": {
37
+ "optional": true
38
+ }
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md"
46
+ ]
47
+ }