capskip 1.0.2
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/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/SECURITY.md +35 -0
- package/docs/API_REFERENCE.md +292 -0
- package/docs/GETTING_STARTED.md +167 -0
- package/docs/TROUBLESHOOTING.md +206 -0
- package/docs/TUTORIAL.md +504 -0
- package/examples/async_example.js +34 -0
- package/examples/captcha.png +0 -0
- package/examples/image_captcha.js +20 -0
- package/examples/recaptcha.js +19 -0
- package/examples/turnstile.js +20 -0
- package/examples/verify_connection.js +50 -0
- package/package.json +50 -0
- package/src/apiClient.js +96 -0
- package/src/apiParams.js +175 -0
- package/src/exceptions.js +37 -0
- package/src/http.js +158 -0
- package/src/index.js +31 -0
- package/src/solver.js +266 -0
- package/types/index.d.ts +167 -0
package/docs/TUTORIAL.md
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
# CapSkip Node.js SDK — Complete Tutorial
|
|
2
|
+
|
|
3
|
+
This tutorial takes you from zero to solving every captcha type CapSkip supports.
|
|
4
|
+
Work through it top to bottom, or jump to the section you need.
|
|
5
|
+
|
|
6
|
+
**Contents**
|
|
7
|
+
|
|
8
|
+
1. [How it works](#1-how-it-works)
|
|
9
|
+
2. [Install and configure](#2-install-and-configure)
|
|
10
|
+
3. [Your first solve](#3-your-first-solve)
|
|
11
|
+
4. [Image captcha](#4-image-captcha)
|
|
12
|
+
5. [reCAPTCHA v2](#5-recaptcha-v2)
|
|
13
|
+
6. [reCAPTCHA v3](#6-recaptcha-v3)
|
|
14
|
+
7. [Cloudflare Turnstile](#7-cloudflare-turnstile)
|
|
15
|
+
8. [Using a proxy](#8-using-a-proxy)
|
|
16
|
+
9. [Concurrency](#9-concurrency)
|
|
17
|
+
10. [The manual workflow](#10-the-manual-workflow)
|
|
18
|
+
11. [Return values](#11-return-values)
|
|
19
|
+
12. [Error handling](#12-error-handling)
|
|
20
|
+
13. [End-to-end: solve and submit](#13-end-to-end-solve-and-submit)
|
|
21
|
+
14. [Parameter reference](#14-parameter-reference)
|
|
22
|
+
15. [Best practices](#15-best-practices)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. How it works
|
|
27
|
+
|
|
28
|
+
CapSkip is a **local** application that solves captchas on your own machine and
|
|
29
|
+
exposes a standard captcha-solver HTTP API (documented in the [CapSkip API docs](https://capskip.com/api-docs/)):
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
POST http://<host>:<port>/in.php → submit a captcha, returns OK|<id>
|
|
33
|
+
GET http://<host>:<port>/res.php → poll for the answer, returns OK|<solution>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This SDK is a thin, friendly wrapper around that API. Every solve follows the
|
|
37
|
+
same three steps, which the SDK does for you:
|
|
38
|
+
|
|
39
|
+
1. **Submit** the captcha (`in.php`) and receive a captcha ID.
|
|
40
|
+
2. **Poll** the result endpoint (`res.php`) every few seconds while the answer is
|
|
41
|
+
not ready.
|
|
42
|
+
3. **Return** the solution once CapSkip finishes.
|
|
43
|
+
|
|
44
|
+
You never have to write the polling loop yourself — call one method and `await`
|
|
45
|
+
the answer.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Install and configure
|
|
50
|
+
|
|
51
|
+
### Install CapSkip
|
|
52
|
+
|
|
53
|
+
Download and launch the CapSkip desktop app from [capskip.com](https://capskip.com),
|
|
54
|
+
and leave it running. In **Settings**, note the **API port** (default `8080`) and,
|
|
55
|
+
if API-key validation is enabled, your **API key**.
|
|
56
|
+
|
|
57
|
+
### Install the SDK
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install capskip
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Verify:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
node -e "console.log(require('capskip').version)"
|
|
67
|
+
node examples/verify_connection.js # checks CapSkip is reachable
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Configure the client
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const { CapSkip } = require('capskip');
|
|
74
|
+
|
|
75
|
+
const solver = new CapSkip({
|
|
76
|
+
apiKey: 'capskip', // your CapSkip API key (any string if validation is off)
|
|
77
|
+
host: '127.0.0.1', // where CapSkip is listening
|
|
78
|
+
port: 8080, // API port from CapSkip settings
|
|
79
|
+
defaultTimeout: 120, // seconds to wait for an image captcha
|
|
80
|
+
recaptchaTimeout: 300, // seconds to wait for reCAPTCHA / Turnstile
|
|
81
|
+
pollingInterval: 5, // max seconds between result polls (starts at 0.25s, backs off to this)
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
In production, read configuration from the environment instead of hard-coding it:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const { CapSkip } = require('capskip');
|
|
89
|
+
|
|
90
|
+
const solver = new CapSkip({
|
|
91
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
92
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
93
|
+
port: parseInt(process.env.CAPSKIP_PORT || '8080', 10),
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 3. Your first solve
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const { CapSkip } = require('capskip');
|
|
103
|
+
|
|
104
|
+
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
|
|
105
|
+
|
|
106
|
+
(async () => {
|
|
107
|
+
const result = await solver.recaptcha(
|
|
108
|
+
'6Le-wvkS...your-sitekey',
|
|
109
|
+
'https://example.com/page-with-recaptcha',
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
console.log(result.code); // the g-recaptcha-response token
|
|
113
|
+
console.log(result.captchaId); // CapSkip's internal ID for this solve
|
|
114
|
+
})();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`result` is always an object. The solution is in `result.code`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 4. Image captcha
|
|
122
|
+
|
|
123
|
+
Use `solver.normal(...)` for classic distorted-text images. The SDK accepts four
|
|
124
|
+
input forms and auto-detects which one you passed:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
// 1. Local file path
|
|
128
|
+
await solver.normal('captcha.png');
|
|
129
|
+
|
|
130
|
+
// 2. Remote image URL (the SDK downloads and encodes it)
|
|
131
|
+
await solver.normal('https://example.com/captcha.jpg');
|
|
132
|
+
|
|
133
|
+
// 3. Base64 string (no file extension, longer than 50 characters)
|
|
134
|
+
const fs = require('fs');
|
|
135
|
+
const b64 = fs.readFileSync('captcha.png').toString('base64');
|
|
136
|
+
await solver.normal(b64);
|
|
137
|
+
|
|
138
|
+
// 4. Data-URI
|
|
139
|
+
const result = await solver.normal('data:image/png;base64,iVBORw0KGgo...');
|
|
140
|
+
|
|
141
|
+
console.log(result.code); // the recognized text
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Image captcha accepts only one extra option, `json`, which controls the raw
|
|
145
|
+
response format from CapSkip:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
await solver.normal('captcha.png', { json: 1 });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
> **Note:** Proxies are **not** supported for image captcha — passing one throws
|
|
152
|
+
> `ValidationException`. Proxies apply only to reCAPTCHA and Turnstile.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 5. reCAPTCHA v2
|
|
157
|
+
|
|
158
|
+
`solver.recaptcha(sitekey, url)` handles reCAPTCHA v2 by default. The `sitekey` is
|
|
159
|
+
the `data-sitekey` attribute of the widget; `url` is the full page URL where it
|
|
160
|
+
appears.
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
// Standard checkbox
|
|
164
|
+
await solver.recaptcha('6Le-wvkS...', 'https://example.com/login');
|
|
165
|
+
|
|
166
|
+
// Invisible reCAPTCHA v2
|
|
167
|
+
await solver.recaptcha('6Le-wvkS...', 'https://example.com', { invisible: 1 });
|
|
168
|
+
|
|
169
|
+
// Enterprise reCAPTCHA v2
|
|
170
|
+
await solver.recaptcha('6Le-wvkS...', 'https://example.com', { enterprise: 1 });
|
|
171
|
+
|
|
172
|
+
// Enterprise with a data-s value (SDK alias: datas)
|
|
173
|
+
const result = await solver.recaptcha('6Le-wvkS...', 'https://example.com', {
|
|
174
|
+
enterprise: 1,
|
|
175
|
+
datas: 'Crb7Vs...',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
console.log(result.code); // g-recaptcha-response token
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Do **not** pass `version`, `action`, or `min_score` to a v2 solve — those belong
|
|
182
|
+
to v3 and will throw `ValidationException`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 6. reCAPTCHA v3
|
|
187
|
+
|
|
188
|
+
reCAPTCHA v3 is score-based. Pass `version: 'v3'` plus the `action` your target
|
|
189
|
+
page uses and, optionally, a minimum score.
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
const result = await solver.recaptcha('6Le-wvkS...', 'https://example.com', {
|
|
193
|
+
version: 'v3',
|
|
194
|
+
action: 'submit', // must match the action in grecaptcha.execute()
|
|
195
|
+
score: 0.7, // SDK alias for min_score (0.1 – 0.9)
|
|
196
|
+
enterprise: 0, // set 1 for Enterprise v3
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
console.log(result.code);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`invisible` is a v2-only flag and is rejected for v3.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 7. Cloudflare Turnstile
|
|
207
|
+
|
|
208
|
+
`solver.turnstile(sitekey, url)` solves Cloudflare Turnstile. The SDK automatically
|
|
209
|
+
requests the JSON response so it can return the **User-Agent** Cloudflare expects.
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
// Standalone widget
|
|
213
|
+
const result = await solver.turnstile('0x4AAAAAAA...', 'https://example.com');
|
|
214
|
+
console.log(result.code); // cf-turnstile-response token
|
|
215
|
+
console.log(result.userAgent); // present when CapSkip returns it
|
|
216
|
+
|
|
217
|
+
// With an explicit action
|
|
218
|
+
await solver.turnstile('0x4AAAAAAA...', 'https://example.com', { action: 'login' });
|
|
219
|
+
|
|
220
|
+
// Cloudflare challenge page (needs cData and chlPageData from the page)
|
|
221
|
+
await solver.turnstile('0x4AAAAAAA...', 'https://example.com', {
|
|
222
|
+
action: 'managed',
|
|
223
|
+
data: 'your_cData_value',
|
|
224
|
+
pagedata: 'your_chlPageData_value',
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
> **Important:** For challenge pages you **must** send the returned token *and* use
|
|
229
|
+
> `result.userAgent` as the `User-Agent` header when you submit it. Mismatched
|
|
230
|
+
> User-Agents are the most common reason a valid token gets rejected.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 8. Using a proxy
|
|
235
|
+
|
|
236
|
+
Solving through the same IP you will submit from greatly improves acceptance rates
|
|
237
|
+
for reCAPTCHA and Turnstile. Pass the proxy as an object with `type` and `uri`:
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const proxy = { type: 'HTTPS', uri: 'user:pass@1.2.3.4:3128' };
|
|
241
|
+
|
|
242
|
+
await solver.recaptcha('...', 'https://example.com', { proxy });
|
|
243
|
+
await solver.turnstile('...', 'https://example.com', { proxy });
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Supported proxy types: `HTTP`, `HTTPS`, `SOCKS5`, `SOCKS5H`. The `uri` may include
|
|
247
|
+
credentials (`login:password@host:port`) or be a bare `host:port`.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 9. Concurrency
|
|
252
|
+
|
|
253
|
+
Every solve method returns a Promise, so you can solve many captchas concurrently
|
|
254
|
+
with `Promise.all`:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
const { CapSkip } = require('capskip');
|
|
258
|
+
|
|
259
|
+
async function main() {
|
|
260
|
+
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
|
|
261
|
+
|
|
262
|
+
const [r1, r2, r3] = await Promise.all([
|
|
263
|
+
solver.recaptcha('...', 'https://a.com'),
|
|
264
|
+
solver.recaptcha('...', 'https://b.com', { version: 'v3', action: 'submit' }),
|
|
265
|
+
solver.turnstile('0x4A...', 'https://c.com'),
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
console.log(r1.code, r2.code, r3.code);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
main();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Use `Promise.allSettled` if you want one failure not to reject the others:
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
const results = await Promise.allSettled([task1, task2]);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 10. The manual workflow
|
|
283
|
+
|
|
284
|
+
If you want to submit now and collect the answer later, use the two low-level
|
|
285
|
+
steps directly.
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
const { NetworkException } = require('capskip');
|
|
289
|
+
|
|
290
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
291
|
+
|
|
292
|
+
// 1. Submit — resolves to the captcha ID immediately, without waiting.
|
|
293
|
+
const captchaId = await solver.send({
|
|
294
|
+
method: 'userrecaptcha',
|
|
295
|
+
googlekey: '6Le-wvkS...',
|
|
296
|
+
pageurl: 'https://example.com',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// 2. Poll once. NetworkException means "not ready yet" — retry.
|
|
300
|
+
let code;
|
|
301
|
+
while (true) {
|
|
302
|
+
try {
|
|
303
|
+
code = await solver.getResult(captchaId);
|
|
304
|
+
break;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (!(err instanceof NetworkException)) throw err;
|
|
307
|
+
await sleep(5000);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log(code);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Pass `1` as the second argument to `getResult` to get the full object (including
|
|
315
|
+
`userAgent` for Turnstile) instead of a plain string.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## 11. Return values
|
|
320
|
+
|
|
321
|
+
Every high-level solve method (`normal`, `recaptcha`, `turnstile`, `solve`)
|
|
322
|
+
resolves to an object:
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
{
|
|
326
|
+
captchaId: '12345', // CapSkip's internal ID for this solve
|
|
327
|
+
code: 'TOKEN_OR_TEXT', // the solution: text for images, token otherwise
|
|
328
|
+
userAgent: 'Mozilla/...', // Turnstile only, when CapSkip provides it
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`solver.send()` resolves to just the ID string. `solver.getResult()` resolves to
|
|
333
|
+
the solution string (or an object when called with `json = 1`).
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 12. Error handling
|
|
338
|
+
|
|
339
|
+
The SDK throws four error types, all subclasses of `CapSkipError`:
|
|
340
|
+
|
|
341
|
+
| Exception | When it is thrown |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `ValidationException` | Invalid or unsupported parameters (e.g. proxy on image captcha) |
|
|
344
|
+
| `NetworkException` | CapSkip is unreachable, or the captcha is not ready yet |
|
|
345
|
+
| `ApiException` | CapSkip returned an error code (e.g. `ERROR_WRONG_USER_KEY`) |
|
|
346
|
+
| `TimeoutException` | Polling exceeded the configured timeout |
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
const {
|
|
350
|
+
CapSkip, ValidationException, NetworkException, ApiException, TimeoutException,
|
|
351
|
+
} = require('capskip');
|
|
352
|
+
|
|
353
|
+
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
const result = await solver.recaptcha('...', 'https://example.com');
|
|
357
|
+
console.log(result.code);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
if (err instanceof ValidationException) {
|
|
360
|
+
console.log('Bad parameters:', err.message);
|
|
361
|
+
} else if (err instanceof NetworkException) {
|
|
362
|
+
console.log('Is CapSkip running?', err.message);
|
|
363
|
+
} else if (err instanceof ApiException) {
|
|
364
|
+
console.log('CapSkip returned an error:', err.message);
|
|
365
|
+
} else if (err instanceof TimeoutException) {
|
|
366
|
+
console.log('Gave up waiting:', err.message);
|
|
367
|
+
} else {
|
|
368
|
+
throw err;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
You can also catch them all at once with the base class:
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
const { CapSkipError } = require('capskip');
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const result = await solver.turnstile('...', '...');
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (err instanceof CapSkipError) {
|
|
382
|
+
console.log('Solve failed:', err.message);
|
|
383
|
+
} else {
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 13. End-to-end: solve and submit
|
|
392
|
+
|
|
393
|
+
A realistic flow — solve a reCAPTCHA, then submit the token to the target site
|
|
394
|
+
through the **same** proxy:
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
const { CapSkip, CapSkipError } = require('capskip');
|
|
398
|
+
|
|
399
|
+
const solver = new CapSkip({
|
|
400
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
401
|
+
host: '127.0.0.1',
|
|
402
|
+
port: 8080,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const SITEKEY = '6Le-wvkS...your-sitekey';
|
|
406
|
+
const LOGIN_URL = 'https://example.com/login';
|
|
407
|
+
const PROXY = { type: 'HTTP', uri: '1.2.3.4:3128' };
|
|
408
|
+
|
|
409
|
+
(async () => {
|
|
410
|
+
let solved;
|
|
411
|
+
try {
|
|
412
|
+
solved = await solver.recaptcha(SITEKEY, LOGIN_URL, { proxy: PROXY });
|
|
413
|
+
} catch (err) {
|
|
414
|
+
if (err instanceof CapSkipError) {
|
|
415
|
+
console.error('Could not solve captcha:', err.message);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const token = solved.code;
|
|
422
|
+
|
|
423
|
+
// Submit the form using the same proxy so the IP matches.
|
|
424
|
+
// (Use your HTTP client of choice, e.g. undici's fetch with a ProxyAgent.)
|
|
425
|
+
const response = await fetch(LOGIN_URL, {
|
|
426
|
+
method: 'POST',
|
|
427
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
428
|
+
body: new URLSearchParams({
|
|
429
|
+
username: 'myuser',
|
|
430
|
+
password: 'mypass',
|
|
431
|
+
'g-recaptcha-response': token,
|
|
432
|
+
}),
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
console.log(response.status);
|
|
436
|
+
})();
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
For Turnstile challenge pages, also set the User-Agent header:
|
|
440
|
+
|
|
441
|
+
```js
|
|
442
|
+
const solved = await solver.turnstile('0x4A...', CHALLENGE_URL, {
|
|
443
|
+
data: 'cData',
|
|
444
|
+
pagedata: 'chlPageData',
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
await fetch(CHALLENGE_URL, {
|
|
448
|
+
method: 'POST',
|
|
449
|
+
headers: { 'User-Agent': solved.userAgent },
|
|
450
|
+
body: new URLSearchParams({ 'cf-turnstile-response': solved.code }),
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 14. Parameter reference
|
|
457
|
+
|
|
458
|
+
### Solve methods
|
|
459
|
+
|
|
460
|
+
| Method | Signature |
|
|
461
|
+
|---|---|
|
|
462
|
+
| Image | `normal(file, { json })` |
|
|
463
|
+
| reCAPTCHA | `recaptcha(sitekey, url, { version, enterprise, ... })` |
|
|
464
|
+
| Turnstile | `turnstile(sitekey, url, { ... })` |
|
|
465
|
+
| Manual submit | `send(params) -> Promise<id>` |
|
|
466
|
+
| Manual poll | `getResult(id, json)` |
|
|
467
|
+
|
|
468
|
+
### Convenience aliases
|
|
469
|
+
|
|
470
|
+
The SDK accepts friendly names and converts them to the raw API parameters:
|
|
471
|
+
|
|
472
|
+
| SDK name | CapSkip API parameter |
|
|
473
|
+
|---|---|
|
|
474
|
+
| `url` | `pageurl` |
|
|
475
|
+
| `score`, `minScore` | `min_score` |
|
|
476
|
+
| `datas`, `data_s` | `data-s` |
|
|
477
|
+
| `proxy` (object) | `proxy` + `proxytype` strings |
|
|
478
|
+
|
|
479
|
+
Anything CapSkip does not document for a given captcha type is rejected with
|
|
480
|
+
`ValidationException`, so typos fail fast instead of silently doing nothing.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## 15. Best practices
|
|
485
|
+
|
|
486
|
+
- **Keep CapSkip running.** The SDK talks to a local app; if it is not running you
|
|
487
|
+
get `NetworkException`.
|
|
488
|
+
- **Use the token immediately.** reCAPTCHA and Turnstile tokens expire within a
|
|
489
|
+
couple of minutes.
|
|
490
|
+
- **Match sitekey and pageurl exactly** to the page the widget loads on.
|
|
491
|
+
- **Solve and submit from the same IP** (same proxy) for reCAPTCHA and Turnstile.
|
|
492
|
+
- **Never commit secrets.** Read `CAPSKIP_API_KEY` and proxy credentials from the
|
|
493
|
+
environment, not source code.
|
|
494
|
+
- **Tune timeouts** for slow captcha types with `recaptchaTimeout` and
|
|
495
|
+
`defaultTimeout`.
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
### Where to go next
|
|
500
|
+
|
|
501
|
+
- [API Reference](API_REFERENCE.md) — every method, parameter, and endpoint
|
|
502
|
+
- [Getting Started](GETTING_STARTED.md) — installation walkthrough
|
|
503
|
+
- [Troubleshooting](TROUBLESHOOTING.md) — fixes for common errors
|
|
504
|
+
- [CapSkip API docs](https://capskip.com/api-docs/) — the raw HTTP API
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { AsyncCapSkip } = require('capskip');
|
|
4
|
+
|
|
5
|
+
// Official public test keys and demo pages — safe to run as-is.
|
|
6
|
+
const RECAPTCHA_SITEKEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'; // Google reCAPTCHA v2 test key
|
|
7
|
+
const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api2/demo';
|
|
8
|
+
const TURNSTILE_SITEKEY = '1x00000000000000000000AA'; // Cloudflare Turnstile test key (always passes)
|
|
9
|
+
const TURNSTILE_URL = 'https://demo.turnstile.workers.dev/';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const solver = new AsyncCapSkip({
|
|
13
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
14
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
15
|
+
port: parseInt(process.env.CAPSKIP_PORT || '8080', 10),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Promise.allSettled lets one failure not cancel the others.
|
|
19
|
+
const results = await Promise.allSettled([
|
|
20
|
+
solver.recaptcha(RECAPTCHA_SITEKEY, RECAPTCHA_URL),
|
|
21
|
+
solver.recaptcha(RECAPTCHA_SITEKEY, RECAPTCHA_URL, { version: 'v3', action: 'submit', score: 0.7 }),
|
|
22
|
+
solver.turnstile(TURNSTILE_SITEKEY, TURNSTILE_URL),
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const names = ['recaptcha v2', 'recaptcha v3', 'turnstile'];
|
|
26
|
+
results.forEach((result, index) => {
|
|
27
|
+
const value = result.status === 'fulfilled'
|
|
28
|
+
? JSON.stringify(result.value)
|
|
29
|
+
: String(result.reason);
|
|
30
|
+
console.log(`${names[index]}: ${value}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main();
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { CapSkip } = require('capskip');
|
|
5
|
+
|
|
6
|
+
const solver = new CapSkip({
|
|
7
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
8
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
9
|
+
port: parseInt(process.env.CAPSKIP_PORT || '8080', 10),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Sample captcha image shipped alongside this script — resolved relative to the
|
|
13
|
+
// file so it works no matter which directory you run from.
|
|
14
|
+
const IMAGE = path.join(__dirname, 'captcha.png');
|
|
15
|
+
|
|
16
|
+
(async () => {
|
|
17
|
+
const result = await solver.normal(IMAGE);
|
|
18
|
+
console.log('Captcha ID:', result.captchaId);
|
|
19
|
+
console.log('Solution: ', result.code);
|
|
20
|
+
})();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CapSkip } = require('capskip');
|
|
4
|
+
|
|
5
|
+
const solver = new CapSkip({
|
|
6
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
7
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
8
|
+
port: parseInt(process.env.CAPSKIP_PORT || '8080', 10),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Google's official reCAPTCHA v2 test key and demo page — safe to run as-is.
|
|
12
|
+
const SITEKEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI';
|
|
13
|
+
const PAGE_URL = 'https://www.google.com/recaptcha/api2/demo';
|
|
14
|
+
|
|
15
|
+
(async () => {
|
|
16
|
+
const result = await solver.recaptcha(SITEKEY, PAGE_URL);
|
|
17
|
+
console.log('Captcha ID:', result.captchaId);
|
|
18
|
+
console.log('Token: ', result.code);
|
|
19
|
+
})();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CapSkip } = require('capskip');
|
|
4
|
+
|
|
5
|
+
const solver = new CapSkip({
|
|
6
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
7
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
8
|
+
port: parseInt(process.env.CAPSKIP_PORT || '8080', 10),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Cloudflare's official Turnstile test key (always passes) and demo page — safe to run as-is.
|
|
12
|
+
const SITEKEY = '1x00000000000000000000AA';
|
|
13
|
+
const PAGE_URL = 'https://demo.turnstile.workers.dev/';
|
|
14
|
+
|
|
15
|
+
(async () => {
|
|
16
|
+
const result = await solver.turnstile(SITEKEY, PAGE_URL);
|
|
17
|
+
console.log('Captcha ID:', result.captchaId);
|
|
18
|
+
console.log('Token: ', result.code);
|
|
19
|
+
console.log('User-Agent:', result.userAgent);
|
|
20
|
+
})();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Verify CapSkip is running and reachable.
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
ApiClient, ApiException, NetworkException, version,
|
|
8
|
+
} = require('capskip');
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = { host: '127.0.0.1', port: 8080, apiKey: 'capskip' };
|
|
12
|
+
for (let i = 2; i < argv.length; i += 1) {
|
|
13
|
+
const arg = argv[i];
|
|
14
|
+
if (arg === '--host') {
|
|
15
|
+
args.host = argv[++i];
|
|
16
|
+
} else if (arg === '--port') {
|
|
17
|
+
args.port = parseInt(argv[++i], 10);
|
|
18
|
+
} else if (arg === '--api-key') {
|
|
19
|
+
args.apiKey = argv[++i];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const args = parseArgs(process.argv);
|
|
27
|
+
const client = new ApiClient({ host: args.host, port: args.port });
|
|
28
|
+
|
|
29
|
+
console.log(`CapSkip SDK : ${version}`);
|
|
30
|
+
console.log(`Target : ${client.baseUrl}`);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await client.res({ key: args.apiKey, action: 'get', id: '0' });
|
|
34
|
+
console.log('Status : OK — CapSkip is reachable');
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof ApiException) {
|
|
37
|
+
console.log('Status : OK — CapSkip is reachable');
|
|
38
|
+
console.log(`Response : ${err.message}`);
|
|
39
|
+
} else if (err instanceof NetworkException) {
|
|
40
|
+
console.log(`Status : FAILED — ${err.message}`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
} else {
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('Try: node examples/recaptcha.js');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capskip",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Node.js SDK for the CapSkip local captcha solver",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./types/index.d.ts",
|
|
10
|
+
"require": "./src/index.js",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test",
|
|
21
|
+
"test:watch": "node --test --watch"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"captcha",
|
|
25
|
+
"captcha-solver",
|
|
26
|
+
"recaptcha",
|
|
27
|
+
"cloudflare",
|
|
28
|
+
"turnstile",
|
|
29
|
+
"capskip",
|
|
30
|
+
"automation"
|
|
31
|
+
],
|
|
32
|
+
"author": "CapSkip <support@capskip.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"homepage": "https://capskip.com",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/capskip/capskip-node.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/capskip/capskip-node/issues"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"src",
|
|
44
|
+
"types",
|
|
45
|
+
"docs",
|
|
46
|
+
"examples",
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"SECURITY.md"
|
|
49
|
+
]
|
|
50
|
+
}
|