Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2026-07-26

### Added

- **GeeTest v3 (slide) support** via `geetest(gt, challenge, url, options)`.
Accepts the optional `api_server` domain override and the usual `proxy`
option. The result exposes the answer as the parsed `challenge`, `validate`,
and `seccode` fields, while `code` keeps the raw JSON string CapSkip returns.
- Parameter aliases `apiServer` and `api_subdomain` for `api_server`.
- TypeScript definitions for `geetest()`, `GeetestOptions`, and the new
`SolveResult` fields.
- `proxytype` is now validated against the values CapSkip accepts (`HTTP`,
`HTTPS`, `SOCKS5`, `SOCKS5H`, case-insensitive) for every proxy-capable captcha
type. `SOCKS4` and other values previously reached the server and came back as
`ERROR_BAD_PARAMETERS`; they now raise `ValidationException` locally.

## [1.0.2] - 2026-07-15

### Fixed
Expand Down
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Official Node.js client for the [CapSkip](https://capskip.com) **local** captcha solver.

CapSkip runs on your machine and exposes a standard captcha-solver HTTP API (the familiar `in.php` / `res.php` endpoints). This SDK wraps that API with clean, familiar method names, so you can solve captchas locally — no cloud service and no per-solve API fees beyond your CapSkip license.
CapSkip runs on your machine and exposes a standard captcha-solver HTTP API (the familiar `in.php` / `res.php` endpoints). This SDK wraps that API with clean, familiar method names, so you can solve captchas locally — no per-solve API fees beyond your CapSkip license.

---

Expand Down Expand Up @@ -70,6 +70,7 @@ Every solve method returns a Promise — use `await` or `.then()`.
| reCAPTCHA v3 Enterprise | `solver.recaptcha(sitekey, url, { version: 'v3', enterprise: 1 })` |
| Cloudflare Turnstile (widget) | `solver.turnstile(sitekey, url)` |
| Cloudflare Turnstile (challenge page) | `solver.turnstile(sitekey, url, { data, pagedata })` |
| GeeTest v3 (slide) | `solver.geetest(gt, challenge, url)` |

---

Expand Down Expand Up @@ -97,7 +98,7 @@ const solver = new CapSkip({
host: '127.0.0.1', // CapSkip host
port: 8080, // CapSkip port from app settings
defaultTimeout: 120, // seconds — image captcha polling timeout
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile polling timeout
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest polling timeout
pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
});
```
Expand Down Expand Up @@ -161,7 +162,23 @@ const v3 = await solver.recaptcha('...', 'https://example.com', {
const result = await solver.turnstile('0x4AAAAAAA...', 'https://example.com');
```

### With a proxy (reCAPTCHA & Turnstile only)
### GeeTest v3

`gt` is static per site, but `challenge` is single-use and expires in about a
minute — fetch a fresh pair right before solving.

```js
const result = await solver.geetest(
'81388ea1fc187e0c335c0a8907ff2625',
'7cf6a8b1a2c34d5e6f7089abcdef0123',
'https://example.com/login',
);

// Post these back exactly as the site's own front-end would
result.challenge, result.validate, result.seccode;
```

### With a proxy (reCAPTCHA, Turnstile & GeeTest only)

```js
// Proxy is not supported for image captcha
Expand Down Expand Up @@ -208,6 +225,9 @@ Every solve method resolves to:
}
```

GeeTest additionally expands its answer into `challenge`, `validate`, and
`seccode`, while `code` keeps the raw JSON string.

---

## Error handling
Expand Down
78 changes: 77 additions & 1 deletion docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ method returns a `Promise`.
| reCAPTCHA v2 | `recaptcha(sitekey, url)` | `userrecaptcha` |
| reCAPTCHA v3 | `recaptcha(sitekey, url, { version: 'v3' })` | `userrecaptcha` + `version=v3` |
| Cloudflare Turnstile | `turnstile()` | `turnstile` |
| GeeTest v3 (slide) | `geetest()` | `geetest` |

**Proxy** is supported for reCAPTCHA and Turnstile only — not for image captcha.
**Proxy** is supported for reCAPTCHA, Turnstile, and GeeTest — not for image captcha.

---

Expand Down Expand Up @@ -206,6 +207,66 @@ const challenge = await solver.turnstile('0x4AAAAAAA...', 'https://example.com',

---

## 5. GeeTest v3 — `geetest(gt, challenge, url, { ... })`

### POST `/in.php`

| Parameter | Type | Required | Description |
|---|---|---|---|
| `key` | string | Yes | CapSkip API key |
| `method` | string | Yes | `geetest` |
| `gt` | string | Yes | Static per-site GeeTest id |
| `challenge` | string | Yes | Single-use challenge token |
| `pageurl` | string | Yes | Full page URL |
| `api_server` | string | No | GeeTest API server domain, e.g. `api-na.geetest.com` |
| `json` | int | No | `0` plain text, `1` JSON |
| `proxy` | string | No | Proxy address |
| `proxytype` | string | No | Proxy type |

### Getting `gt` and `challenge`

Both come from the target site, which fetches them from an endpoint returning
`{"gt": "...", "challenge": "..."}` (often `.../register.php` or a `gettype`/`get.php`
request). Find it in DevTools → Network, or read them out of the
`initGeetest({ gt, challenge })` call in the page scripts.

> **`challenge` is single-use and expires in about a minute.** Fetch a fresh pair
> immediately before each solve. If a solve comes back with a bad-challenge error,
> request a new pair and retry — reusing one never succeeds.

### SDK usage

```js
const result = await solver.geetest(
'81388ea1fc187e0c335c0a8907ff2625',
'7cf6a8b1a2c34d5e6f7089abcdef0123',
'https://example.com/login',
);

result.challenge; // geetest_challenge
result.validate; // geetest_validate
result.seccode; // geetest_seccode
result.code; // the same answer as a raw JSON string
```

Post the three fields back exactly as the site's own front-end would:

```js
await fetch(LOGIN_URL, {
method: 'POST',
body: new URLSearchParams({
geetest_challenge: result.challenge,
geetest_validate: result.validate,
geetest_seccode: result.seccode,
}),
});
```

GeeTest is a real browser solve, so it uses the longer `recaptchaTimeout` budget
rather than `defaultTimeout`.

---

## Return value

Every solve method resolves to:
Expand All @@ -218,6 +279,19 @@ Every solve method resolves to:
}
```

GeeTest additionally expands its answer into `challenge`, `validate`, and
`seccode` (`code` keeps the raw JSON string):

```js
{
captchaId: '12345',
code: '{"geetest_challenge":"...","geetest_validate":"...","geetest_seccode":"..."}',
challenge: '...',
validate: '...',
seccode: '...',
}
```

---

## SDK parameter aliases
Expand All @@ -231,6 +305,8 @@ Convenience aliases mapped before sending to CapSkip:
| `minScore` | `min_score` |
| `datas` | `data-s` |
| `data_s` | `data-s` |
| `apiServer` | `api_server` |
| `api_subdomain` | `api_server` |
| `proxy` object | `proxy` + `proxytype` strings |

```js
Expand Down
1 change: 1 addition & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ node examples/recaptcha.js
| `image_captcha.js` | Image captcha from file, URL, or base64 |
| `recaptcha.js` | reCAPTCHA v2, v3, invisible, enterprise, proxy |
| `turnstile.js` | Cloudflare Turnstile widget and challenge page |
| `geetest.js` | GeeTest v3 slider, including fetching a fresh `gt`/`challenge` pair |
| `async_example.js` | Parallel solving |
| `verify_connection.js` | Check CapSkip is running |

Expand Down
42 changes: 42 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,48 @@ const result = await solver.recaptcha('...', '...', { proxy });

---

## GeeTest keeps failing with a bad-challenge error

**Symptom:** `geetest()` rejects with `ApiException` (or times out) even though
`gt` and `challenge` were copied correctly from the page.

**Cause:** The `challenge` value is **single-use and expires in about a minute**.
A pair copied out of DevTools minutes earlier, cached in a config file, or reused
across two solves is already dead.

**Fix:** Fetch a fresh pair programmatically immediately before each solve, and on
failure request a *new* pair rather than retrying the old one:

```js
const { gt, challenge } = await (await fetch(REGISTER_URL)).json();
const result = await solver.geetest(gt, challenge, PAGE_URL);
```

If the site loads GeeTest from a non-default API server domain, pass it through as well:

```js
await solver.geetest(gt, challenge, url, { api_server: 'api-na.geetest.com' });
```

---

## GeeTest result — where are challenge/validate/seccode?

`result.code` holds the raw JSON string CapSkip returns. The SDK also parses it
for you, so prefer the individual fields:

```js
result.challenge; // geetest_challenge
result.validate; // geetest_validate
result.seccode; // geetest_seccode
```

Submit all three under their `geetest_`-prefixed names, exactly as the site's own
front-end does. Sending only `validate` is the most common reason a correct solve
is rejected.

---

## NetworkException during manual polling

**Symptom:** `getResult()` keeps rejecting with `NetworkException`.
Expand Down
Loading
Loading