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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ 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(string $gt, string $challenge, string $url, array $options = [])`
on both `CapSkip` and `AsyncCapSkip`. Accepts the optional `api_server` domain
override and the usual `proxy` option. The result exposes the answer as the
parsed `challenge`, `validate`, and `seccode` keys, while `code` keeps the raw
JSON string CapSkip returns.
- Parameter aliases `apiServer` and `api_subdomain` for `api_server`.
- `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

### Changed
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 PHP 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 @@ echo $result['code']; // g-recaptcha-response token
| 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 @@ $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 @@ $v3 = $solver->recaptcha('...', 'https://example.com', [
$result = $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.

```php
$result = $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)

```php
// Proxy is not supported for image captcha
Expand Down Expand Up @@ -202,6 +219,9 @@ Every solve method returns an associative array:
]
```

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

---

## Error handling
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"recaptcha",
"cloudflare",
"turnstile",
"geetest",
"capskip",
"automation"
],
Expand Down
54 changes: 53 additions & 1 deletion docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ The SDK only supports the four captcha types documented by CapSkip.
| reCAPTCHA v2 | `recaptcha(..., ['version' => 'v2'])` | `userrecaptcha` |
| reCAPTCHA v3 | `recaptcha(..., ['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 @@ -203,6 +204,55 @@ $result = $solver->turnstile('0x4AAAAAAA...', 'https://example.com', [

---

## 5. GeeTest v3 — `geetest($gt, $challenge, $url, $options)`

### 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

```php
$result = $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.

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

---

## Return value

Every solve method returns:
Expand All @@ -228,6 +278,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` array | `proxy` + `proxytype` strings |

```php
Expand Down
1 change: 1 addition & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ php examples/recaptcha.php
| `image_captcha.php` | Image captcha from a file |
| `recaptcha.php` | reCAPTCHA v2 |
| `turnstile.php` | Cloudflare Turnstile widget |
| `geetest.php` | GeeTest v3 slider, including fetching a fresh `gt`/`challenge` pair |
| `async_example.php` | Solving several captcha types in a row |
| `verify_connection.php` | 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 @@ -153,6 +153,48 @@ $result = $solver->recaptcha('...', '...', ['proxy' => $proxy]);

---

## GeeTest keeps failing with a bad-challenge error

**Symptom:** `geetest()` throws `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:

```php
$pair = json_decode(file_get_contents($registerUrl), true);
$result = $solver->geetest($pair['gt'], $pair['challenge'], $pageUrl);
```

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

```php
$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:

```php
$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 throwing `NetworkException`.
Expand Down
Loading
Loading