capskip 1.2.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -5,6 +5,49 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0] - 2026-09-22
9
+
10
+ ### Added
11
+
12
+ - **Capy Puzzle support** via `capy(sitekey, url, options)`. The site key
13
+ (conventionally prefixed `PUZZLE_`) is sent as the `captchakey` the API
14
+ documents. Optional `api_server` selects the Capy API the key lives behind,
15
+ defaulting to `https://jp.api.capy.me`.
16
+ A Capy answer is **not a token**: the result expands into `captchakey`,
17
+ `challengekey` and `answer`, which go into the target form's `capy_captchakey`,
18
+ `capy_challengekey` and `capy_answer` fields. `code` keeps the raw answer.
19
+ - **CaptchaFox support** via `captchafox(sitekey, url, options)`. The result
20
+ exposes `token` (for the form's `cf-captcha-response` field) and, when the
21
+ solve reported one, `userAgent` — the UA the browser actually minted the token
22
+ under, which is not the one you sent. Optional `api_server` selects the widget
23
+ source; the MAM package returns a `MAM_` prefixed token.
24
+ - **Friendly Captcha support** via `friendlyCaptcha(sitekey, url, options)`.
25
+ Pass `version` (`v1`/`v2`, or a bare `1`/`2`), or `moduleScript` /
26
+ `nomoduleScript` with the widget script URLs and let CapSkip read the version
27
+ off the build the site actually loads. Optional `api_server` accepts `global`
28
+ (the default), `eu`, or a full URL for the data-residency tenant. The result
29
+ exposes `token`, for `frc-captcha-solution` on v1 or `frc-captcha-response` on
30
+ v2.
31
+ - Parameter aliases `userAgent`/`user_agent` for `useragent`, `captchaKey` for
32
+ `captchakey`, and `moduleScript`/`nomoduleScript` for `module_script` /
33
+ `nomodule_script`.
34
+ - TypeScript definitions for `CapyOptions`, `CaptchaFoxOptions` and
35
+ `FriendlyCaptchaOptions`, plus the new `SolveResult` fields.
36
+
37
+ ### Notes
38
+
39
+ - `version: 'avatar'` is refused locally for Capy. CapSkip solves the puzzle
40
+ family only, and answering an avatar request with a puzzle answer would bill
41
+ for a solve the target site rejects, indistinguishably from a broken solver.
42
+ - An unknown Friendly Captcha `version` is refused locally too. The two versions
43
+ are different protocols sharing one sitekey namespace, so solving the wrong one
44
+ returns a well-formed token the site rejects with nothing to indicate why.
45
+ - CaptchaFox and Friendly Captcha use `recaptchaTimeout`: the first is a real
46
+ browser solve, and the second has its difficulty set per request by the service
47
+ (and always solves in a browser on v2). Capy uses `defaultTimeout` — it is one
48
+ HTTP fetch plus pixel math, held back to roughly two seconds because Capy
49
+ refuses answers that arrive faster than a human could have produced them.
50
+
8
51
  ## [1.2.0] - 2026-09-12
9
52
 
10
53
  ### Added
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
6
  [![Tests](https://github.com/capskip/capskip-node/actions/workflows/ci.yml/badge.svg)](https://github.com/capskip/capskip-node/actions/workflows/ci.yml)
7
7
 
8
- **Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA and image captchas from Node.js.**
8
+ **Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha and image captchas from Node.js.**
9
9
 
10
10
  Official Node.js client for [CapSkip](https://capskip.com), a **local captcha solver** that runs on your own machine. Licensed once, not billed per solve. TypeScript definitions included.
11
11
 
@@ -19,7 +19,7 @@ npm install capskip
19
19
 
20
20
  CapSkip is a desktop app. It does the solving on your machine and exposes the standard captcha-solver HTTP API — the same `in.php` / `res.php` endpoints every 2captcha-compatible client already speaks — on `127.0.0.1:8080`.
21
21
 
22
- This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
22
+ This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
23
23
 
24
24
  ## Supported captcha types
25
25
 
@@ -35,6 +35,9 @@ This SDK is a thin wrapper over that API, with the method names you would expect
35
35
  | Cloudflare Turnstile (challenge page) | `solver.turnstile(sitekey, url, { data, pagedata })` |
36
36
  | **GeeTest v3 solver** (slide puzzle) | `solver.geetest(gt, challenge, url)` |
37
37
  | **ALTCHA solver** (proof-of-work) | `solver.altcha(url, { challengeUrl })` |
38
+ | **Capy Puzzle solver** (slide puzzle) | `solver.capy(sitekey, url)` |
39
+ | **CaptchaFox solver** (widget) | `solver.captchafox(sitekey, url)` |
40
+ | **Friendly Captcha solver** (proof-of-work) | `solver.friendlyCaptcha(sitekey, url, { version })` |
38
41
 
39
42
  **hCaptcha and FunCaptcha/Arkose are not supported.** hCaptcha is the one people misidentify most often, since it also puts a `data-sitekey` on the widget — check for `class="h-captcha"` or a `js.hcaptcha.com` script before reaching for `recaptcha()`.
40
43
 
@@ -131,8 +134,9 @@ const solver = new CapSkip({
131
134
  host: '127.0.0.1', // CapSkip host
132
135
  port: 8080, // CapSkip port from app settings
133
136
  defaultTimeout: 120, // seconds — image captcha polling timeout
134
- recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest polling timeout
135
- // (ALTCHA uses defaultTimeout — CPU work, not a browser solve)
137
+ recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
138
+ // Friendly Captcha polling timeout
139
+ // (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
136
140
  pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
137
141
  });
138
142
  ```
@@ -230,7 +234,86 @@ result.token;
230
234
  Challenges expire fast — some sites inside two minutes — so fetch one
231
235
  immediately before solving and submit the token promptly.
232
236
 
233
- ### With a proxy (reCAPTCHA, Turnstile, GeeTest & ALTCHA only)
237
+ ### Capy Puzzle
238
+
239
+ A Capy solution is not a token. It is three values that together go into the
240
+ target form, so post all three back exactly as returned.
241
+
242
+ ```js
243
+ const result = await solver.capy(
244
+ 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
245
+ 'https://example.com/login',
246
+ );
247
+
248
+ // capy_captchakey, capy_challengekey and capy_answer respectively
249
+ result.captchakey, result.challengekey, result.answer;
250
+ ```
251
+
252
+ `answer` is the drag path the widget would have recorded — submit it verbatim,
253
+ without trimming or re-encoding it. The challenge key is single-use and
254
+ short-lived, so submit promptly rather than caching the three values.
255
+
256
+ Pass `api_server` when the widget script points somewhere other than
257
+ `https://jp.api.capy.me`. Only the `puzzle` family is solved; `version: 'avatar'`
258
+ is refused rather than answered with a puzzle answer the site would reject.
259
+
260
+ ### CaptchaFox
261
+
262
+ ```js
263
+ const result = await solver.captchafox(
264
+ 'sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G',
265
+ 'https://example.com/signup',
266
+ );
267
+
268
+ // Post this back in the form field the widget uses, named `cf-captcha-response`
269
+ result.token;
270
+
271
+ // The UA the token was minted under — submit under this one, not your own
272
+ result.userAgent;
273
+ ```
274
+
275
+ `url` has to be the page the widget actually runs on: CaptchaFox checks it
276
+ against the domains the key is registered for and refuses a mismatch
277
+ permanently, not intermittently.
278
+
279
+ Pass `api_server` only when the page does not load the default widget. A page
280
+ loading the MAM package expects a `MAM_` prefixed token, and sending the wrong
281
+ source still succeeds — it just returns a token the site will not accept, which
282
+ reads as a silent verification failure rather than an error.
283
+
284
+ ### Friendly Captcha
285
+
286
+ Two different protocols ship under this name and a sitekey does not tell you
287
+ which one a site uses, so say which — or send the widget script and let CapSkip
288
+ read it off the build the site actually loads.
289
+
290
+ ```js
291
+ const result = await solver.friendlyCaptcha(
292
+ 'FCMGEMUD2M567T8G',
293
+ 'https://example.com/signup',
294
+ { version: 'v2' },
295
+ );
296
+
297
+ // v1 → frc-captcha-solution, v2 → frc-captcha-response
298
+ result.token;
299
+ ```
300
+
301
+ ```js
302
+ // Or let the script URL decide the version
303
+ const result = await solver.friendlyCaptcha(
304
+ 'FCMGEMUD2M567T8G',
305
+ 'https://example.com/signup',
306
+ { moduleScript: 'https://cdn.example.com/@friendlycaptcha/sdk@0.1.6/site.min.js' },
307
+ );
308
+ ```
309
+
310
+ Solving the wrong version returns a well-formed token the target site rejects,
311
+ with nothing to indicate the version was the problem. With neither `version` nor
312
+ a script URL, v1 is assumed. Pass `api_server: 'eu'` for a sitekey on the EU
313
+ data-residency tenant. A v2 token is roughly six kilobytes, so size whatever
314
+ carries it accordingly.
315
+
316
+ ### With a proxy (every method except image captcha)
234
317
 
235
318
  ```js
236
319
  // Proxy is not supported for image captcha
@@ -325,7 +408,10 @@ Every solve method resolves to:
325
408
  ```
326
409
 
327
410
  GeeTest additionally expands its answer into `challenge`, `validate`, and
328
- `seccode`, while `code` keeps the raw JSON string. ALTCHA adds `token` (the same
411
+ `seccode`, while `code` keeps the raw JSON string. CaptchaFox and Friendly
412
+ Captcha add `token`, the same string as `code`. Capy expands its answer into
413
+ `captchakey`, `challengekey` and `answer` — it returns an object rather than a
414
+ token, and `code` keeps the raw answer. ALTCHA adds `token` (the same
329
415
  string as `code`) and `number`, the counter that solved it.
330
416
 
331
417
  ---
@@ -371,7 +457,7 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
371
457
 
372
458
  ### How do I solve a captcha in Node.js?
373
459
 
374
- Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token, or the recognized text in the case of an image captcha.
460
+ Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token, or the recognized text in the case of an image captcha.
375
461
 
376
462
  ### Is this a free captcha solver?
377
463
 
@@ -379,7 +465,7 @@ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is
379
465
 
380
466
  ### Which captchas can it solve?
381
467
 
382
- reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
468
+ reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
383
469
 
384
470
  ### Does it work with Puppeteer and Playwright?
385
471
 
@@ -24,9 +24,13 @@ method returns a `Promise`.
24
24
  | Cloudflare Turnstile | `turnstile()` | `turnstile` |
25
25
  | GeeTest v3 (slide) | `geetest()` | `geetest` |
26
26
  | ALTCHA (proof-of-work) | `altcha()` | `altcha` |
27
+ | Capy Puzzle (slide) | `capy()` | `capy` |
28
+ | CaptchaFox (widget) | `captchafox()` | `captchafox` |
29
+ | Friendly Captcha (proof-of-work) | `friendlyCaptcha()` | `friendly_captcha` |
27
30
 
28
- **Proxy** is supported for reCAPTCHA, Turnstile, GeeTest, and ALTCHA — not for image
29
- captcha. For ALTCHA the proxy is used only for the `challengeUrl` fetch.
31
+ **Proxy** is supported for every method except image captcha. For ALTCHA the
32
+ proxy is used only for the `challengeUrl` fetch; for Capy, only for the
33
+ puzzle-image fetch, which is the only request a Capy solve makes.
30
34
 
31
35
  ---
32
36
 
@@ -377,6 +381,237 @@ purely visual and never reaches CapSkip.
377
381
 
378
382
  ---
379
383
 
384
+ ## 7. Capy Puzzle — `capy(sitekey, url, { ... })`
385
+
386
+ Capy is a slide puzzle: a piece has to be dragged into the hole cut out of a
387
+ photograph. CapSkip locates the hole and produces the drag path — one HTTP fetch
388
+ plus pixel math, no browser.
389
+
390
+ ### POST `/in.php`
391
+
392
+ | Parameter | Type | Required | Description |
393
+ |---|---|---|---|
394
+ | `key` | string | Yes | CapSkip API key |
395
+ | `method` | string | Yes | `capy` |
396
+ | `captchakey` | string | Yes | The site's public Capy key, conventionally prefixed `PUZZLE_`. The SDK sends its `sitekey` argument here. |
397
+ | `pageurl` | string | Yes | Full URL of the page the captcha is on |
398
+ | `api_server` | string | No | Root of the Capy API the key lives behind. Default `https://jp.api.capy.me` |
399
+ | `version` | string | No | `puzzle` (the default). Only the puzzle family is solved |
400
+ | `useragent` | string | No | User-Agent to send with the puzzle request |
401
+ | `json` | int | No | `0` plain text, `1` JSON |
402
+ | `proxy` | string | No | Proxy address |
403
+ | `proxytype` | string | No | Proxy type |
404
+
405
+ `sitekey` and `websiteKey` are accepted by the server as aliases for
406
+ `captchakey`; the SDK sends the documented name.
407
+
408
+ `version: 'avatar'` is refused by the SDK before the request is made. Avatar is a
409
+ different challenge behind a different endpoint, and answering it with a puzzle
410
+ answer would bill for a solve the target site rejects, indistinguishably from a
411
+ broken solver.
412
+
413
+ > `api.capy.me` no longer resolves, although several solver services still
414
+ > document it. The live host is `jp.api.capy.me`; if a site's widget points
415
+ > somewhere else, pass that as `api_server`.
416
+
417
+ ### Getting the captcha key
418
+
419
+ It is in the page source as `capy_captchakey`, or in the widget script URL:
420
+ `<script src="https://jp.api.capy.me/puzzle/get_js/?k=PUZZLE_XXXX">`.
421
+
422
+ ### GET `/res.php`
423
+
424
+ The answer is **not a token**. It is an object, which the SDK expands into three
425
+ result fields. See [Return value](#return-value).
426
+
427
+ ### SDK usage
428
+
429
+ ```js
430
+ const result = await solver.capy(
431
+ 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
432
+ 'https://example.com/login',
433
+ );
434
+
435
+ // With a non-default API server
436
+ const result = await solver.capy(
437
+ 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
438
+ 'https://example.com/login',
439
+ { api_server: 'https://jp.api.capy.me/' },
440
+ );
441
+
442
+ // capy_captchakey, capy_challengekey, capy_answer
443
+ result.captchakey, result.challengekey, result.answer;
444
+ ```
445
+
446
+ ### Timing
447
+
448
+ Capy grades the wall-clock gap between issuing the puzzle and verifying the
449
+ answer, and refuses anything superhuman with `CAPTCHA verification failed` — the
450
+ same message a wrong answer gets. CapSkip therefore holds every result until two
451
+ seconds have elapsed since it drew the puzzle. This is invisible if you poll: the
452
+ task simply takes about two seconds instead of a fifth of one. Nothing to
453
+ configure.
454
+
455
+ The challenge key is single-use and short-lived, so submit the three values
456
+ promptly rather than caching them.
457
+
458
+ ---
459
+
460
+ ## 8. CaptchaFox — `captchafox(sitekey, url, { ... })`
461
+
462
+ CaptchaFox scores the browser itself rather than asking the visitor to read
463
+ anything; most visitors never see a puzzle. CapSkip drives the real widget in a
464
+ real browser and returns the verification token.
465
+
466
+ ### POST `/in.php`
467
+
468
+ | Parameter | Type | Required | Description |
469
+ |---|---|---|---|
470
+ | `key` | string | Yes | CapSkip API key |
471
+ | `method` | string | Yes | `captchafox` |
472
+ | `sitekey` | string | Yes | The public key the widget renders with, conventionally prefixed `sk_` |
473
+ | `pageurl` | string | Yes | Full URL of the page the widget appears on |
474
+ | `api_server` | string | No | Widget entry point. Default `https://cdn.captchafox.com/` |
475
+ | `useragent` | string | No | Accepted for compatibility and not applied — CapSkip uses its own browser's identity |
476
+ | `json` | int | No | `0` plain text, `1` JSON |
477
+ | `proxy` | string | No | Proxy address |
478
+ | `proxytype` | string | No | Proxy type |
479
+
480
+ ### Choosing the widget source
481
+
482
+ | `api_server` | Token | Default |
483
+ |---|---|---|
484
+ | `https://cdn.captchafox.com/` | Plain | Yes |
485
+ | `https://s.uicdn.com/mampkg/…` | `MAM_` prefixed | No |
486
+
487
+ Read the value from the `<script>` tag that loads the widget. If you send the
488
+ wrong one the solve still succeeds, but the token comes back in a format the
489
+ target site will not accept — a silent verification failure rather than an error.
490
+
491
+ ### The page URL has to match the key
492
+
493
+ CaptchaFox keys are registered against a list of allowed domains and the service
494
+ checks the host before issuing anything. A correct key used on a page outside
495
+ that list is refused permanently, not intermittently. CapSkip reports that case
496
+ rather than retrying it, because retrying cannot help.
497
+
498
+ ### Challenge types
499
+
500
+ | Challenge | Frequency | Supported |
501
+ |---|---|---|
502
+ | Invisible | Usually | Yes |
503
+ | Slide | Sometimes | Yes |
504
+ | Image select | Rarely | No |
505
+ | Audio | Rarely | No |
506
+
507
+ The two unsupported ones are uncommon and a retry usually draws a different
508
+ challenge, so treat an unsolvable result as a signal to resubmit rather than a
509
+ permanent failure of the key.
510
+
511
+ ### SDK usage
512
+
513
+ ```js
514
+ const result = await solver.captchafox(
515
+ 'sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G',
516
+ 'https://example.com/signup',
517
+ );
518
+
519
+ // Post in the form field named cf-captcha-response
520
+ result.token;
521
+
522
+ // Submit under the UA that minted the token, not your own
523
+ result.userAgent;
524
+ ```
525
+
526
+ Uses `recaptchaTimeout` — it is a real browser session, and longer again when an
527
+ interactive challenge is drawn.
528
+
529
+ ---
530
+
531
+ ## 9. Friendly Captcha — `friendlyCaptcha(sitekey, url, { ... })`
532
+
533
+ Proof-of-work, with nothing shown on screen. The widget searches for values that
534
+ hash below a difficulty the service sets per request.
535
+
536
+ ### POST `/in.php`
537
+
538
+ | Parameter | Type | Required | Description |
539
+ |---|---|---|---|
540
+ | `key` | string | Yes | CapSkip API key |
541
+ | `method` | string | Yes | `friendly_captcha` |
542
+ | `sitekey` | string | Yes | The `data-sitekey` of the element carrying `class="frc-captcha"` |
543
+ | `pageurl` | string | Yes | Full URL of the page the widget appears on |
544
+ | `version` | string | No | `v1` (default) or `v2`; a bare `1` or `2` is accepted |
545
+ | `module_script` | string | No | `src` of the widget script tag carrying `type="module"` |
546
+ | `nomodule_script` | string | No | `src` of the widget script tag carrying `nomodule` |
547
+ | `api_server` | string | No | Data residency endpoint: `global` (default), `eu`, or a full URL |
548
+ | `useragent` | string | No | User-Agent to send with the request |
549
+ | `json` | int | No | `0` plain text, `1` JSON |
550
+ | `proxy` | string | No | Proxy address |
551
+ | `proxytype` | string | No | Proxy type |
552
+
553
+ ### Version 1 and version 2
554
+
555
+ Two entirely different protocols ship under this one name, and a sitekey does not
556
+ tell you which one a site uses — they share a brand and a sitekey namespace and
557
+ nothing else. Both are live. Solve the wrong one and you get a well-formed token
558
+ the target site rejects, with no indication that the version was the problem.
559
+
560
+ | `version` | Widget package | Script |
561
+ |---|---|---|
562
+ | `v1` | `friendly-challenge` | `widget.module.min.js` / `widget.min.js` |
563
+ | `v2` | `@friendlycaptcha/sdk` | `site.min.js` |
564
+
565
+ CapSkip decides in this order, stopping at the first answer: the `version`
566
+ parameter; then the script URL from `moduleScript` or `nomoduleScript`, which is
567
+ the most reliable signal because it is the build the site actually loads; then
568
+ v1. The SDK refuses any other `version` value locally.
569
+
570
+ ### Submitting the token
571
+
572
+ | `version` | Form field |
573
+ |---|---|
574
+ | `v1` | `frc-captcha-solution` |
575
+ | `v2` | `frc-captcha-response` |
576
+
577
+ The field names differ, which is what catches an integration moved from one to
578
+ the other. A v1 token is four dot-separated parts and runs to a few hundred
579
+ characters; a v2 token is a single opaque string beginning `AQQA.` and is roughly
580
+ six kilobytes, so size whatever carries it accordingly.
581
+
582
+ ### SDK usage
583
+
584
+ ```js
585
+ // Say which version
586
+ const result = await solver.friendlyCaptcha(
587
+ 'FCMGEMUD2M567T8G',
588
+ 'https://example.com/signup',
589
+ { version: 'v2' },
590
+ );
591
+
592
+ // …or let the script URL decide it
593
+ const result = await solver.friendlyCaptcha(
594
+ 'FCMGEMUD2M567T8G',
595
+ 'https://example.com/signup',
596
+ { moduleScript: 'https://cdn.example.com/@friendlycaptcha/sdk@0.1.6/site.min.js' },
597
+ );
598
+
599
+ // EU data-residency tenant
600
+ const result = await solver.friendlyCaptcha(
601
+ 'FCMGEMUD2M567T8G',
602
+ 'https://example.com/signup',
603
+ { version: 'v2', api_server: 'eu' },
604
+ );
605
+
606
+ result.token;
607
+ ```
608
+
609
+ Uses `recaptchaTimeout`: the service decides how much work a request is worth at
610
+ the moment it is made, so solve time is not a constant, and v2 always solves in a
611
+ browser.
612
+
613
+ ---
614
+
380
615
  ## Return value
381
616
 
382
617
  Every solve method resolves to:
@@ -401,6 +636,33 @@ form field it goes in) and `number`, the counter that solved it:
401
636
  }
402
637
  ```
403
638
 
639
+ CaptchaFox and Friendly Captcha expose `token`, the same string as `code`, named
640
+ for the form field it goes in. CaptchaFox adds `userAgent` when the solve
641
+ reported one — the UA the browser minted the token under, not one you sent:
642
+
643
+ ```js
644
+ {
645
+ captchaId: '12345',
646
+ code: '177f50c25b845601e5c779cdb51b040d…',
647
+ token: '177f50c25b845601e5c779cdb51b040d…',
648
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) …',
649
+ }
650
+ ```
651
+
652
+ Capy returns an object rather than a token, expanded into the three fields the
653
+ target form takes (`code` keeps the raw answer):
654
+
655
+ ```js
656
+ {
657
+ captchaId: '12345',
658
+ code: { captchakey: 'PUZZLE_…', challengekey: '…', answer: '…', respKey: '' },
659
+ captchakey: 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
660
+ challengekey: 'BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP',
661
+ answer: '0xax8ex0xax84x0xkx7qx0x18x76x…',
662
+ respKey: '',
663
+ }
664
+ ```
665
+
404
666
  GeeTest additionally expands its answer into `challenge`, `validate`, and
405
667
  `seccode` (`code` keeps the raw JSON string):
406
668
 
@@ -431,6 +693,10 @@ Convenience aliases mapped before sending to CapSkip:
431
693
  | `api_subdomain` | `api_server` |
432
694
  | `challengeUrl` / `challengeURL` | `challenge_url` |
433
695
  | `challengeJson` / `challengeJSON` | `challenge_json` |
696
+ | `userAgent` / `user_agent` | `useragent` |
697
+ | `captchaKey` | `captchakey` |
698
+ | `moduleScript` | `module_script` |
699
+ | `nomoduleScript` / `noModuleScript` | `nomodule_script` |
434
700
  | `proxy` object | `proxy` + `proxytype` strings |
435
701
 
436
702
  ```js
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Solve a CaptchaFox challenge.
5
+ *
6
+ * CaptchaFox scores the browser itself rather than asking the visitor to read
7
+ * anything, so most solves draw no puzzle at all. CapSkip drives the real widget
8
+ * in a real browser and returns the verification token it would have produced.
9
+ *
10
+ * You need two values, both read off the target page:
11
+ *
12
+ * * sitekey - the public key the widget renders with, conventionally prefixed
13
+ * `sk_`. It is on the widget container as `data-sitekey`, or in the
14
+ * `captchafox.render(...)` call. If the page builds the widget at runtime,
15
+ * open DevTools -> Network and find the request to api.captchafox.com: the
16
+ * key is the path segment after /captcha/.
17
+ * * pageurl - the full URL of the page the widget appears on
18
+ *
19
+ * The page URL has to match: CaptchaFox keys are registered against a list of
20
+ * allowed domains and the service checks the host before it issues anything. A
21
+ * key that is correct but used on a page outside that list is refused
22
+ * permanently, not intermittently -- so if a sitekey fails immediately and
23
+ * consistently, check that you are sending the page the widget actually runs on
24
+ * rather than a search page, a redirect or a shortened link.
25
+ */
26
+
27
+ const { CapSkip } = require('capskip');
28
+
29
+ const solver = new CapSkip({
30
+ apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
31
+ host: process.env.CAPSKIP_HOST || '127.0.0.1',
32
+ port: Number(process.env.CAPSKIP_PORT || 8080),
33
+ });
34
+
35
+ const SITEKEY = 'sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G';
36
+ const PAGE_URL = 'https://example.com/signup';
37
+
38
+ async function main() {
39
+ const result = await solver.captchafox(SITEKEY, PAGE_URL);
40
+
41
+ console.log('Captcha ID:', result.captchaId);
42
+ console.log('Token: ', `${result.token.slice(0, 60)}...`);
43
+ console.log('User-Agent:', result.userAgent || '(none reported)');
44
+
45
+ // --- Choosing the widget source ------------------------------------------
46
+ // CaptchaFox publishes its widget from two places, and which one a site loads
47
+ // decides the shape of the token it expects back. Send api_server only when
48
+ // the target page does not use the default (https://cdn.captchafox.com/).
49
+ //
50
+ // await solver.captchafox(SITEKEY, PAGE_URL, {
51
+ // api_server: 'https://s.uicdn.com/mampkg/@mamdev/core.frontend.libs.captchafox/',
52
+ // });
53
+ //
54
+ // That one returns a MAM_ prefixed token. Read the value from the <script>
55
+ // tag on the target page: if you send the wrong one the solve still succeeds,
56
+ // but the token comes back in a format the site will not accept, which looks
57
+ // like a silent verification failure rather than an error.
58
+ //
59
+ // --- With a per-request proxy --------------------------------------------
60
+ // CapSkip does not force a proxy, but CaptchaFox scores the network a widget
61
+ // runs on as well as the browser, so repeated solves from one address push
62
+ // that address toward the interactive challenges and then toward refusals.
63
+ //
64
+ // await solver.captchafox(SITEKEY, PAGE_URL, {
65
+ // proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
66
+ // });
67
+
68
+ // Post the token back in the form field the widget uses, named
69
+ // `cf-captcha-response`. Treat it as opaque and pass it through unchanged --
70
+ // it is verified server side against the session that produced it, so any
71
+ // edit invalidates it.
72
+ //
73
+ // await fetch(SIGNUP_URL, {
74
+ // method: 'POST',
75
+ // headers: { 'User-Agent': result.userAgent },
76
+ // body: new URLSearchParams({
77
+ // email: 'someone@example.com',
78
+ // 'cf-captcha-response': result.token,
79
+ // }),
80
+ // });
81
+ //
82
+ // Some integrations read the token from a JSON body field instead, so check
83
+ // what the page's own submit sends and mirror it.
84
+ console.log('Form field:', { 'cf-captcha-response': `${result.token.slice(0, 40)}...` });
85
+
86
+ // Submit under the User-Agent the token was minted with, not your own.
87
+ // CapSkip solves in a real browser and uses that browser's identity, so the
88
+ // UA it reports is the one the token is bound to.
89
+ //
90
+ // Tokens are short lived: submit promptly rather than holding one while a
91
+ // user fills in a form, and solve again if the form is abandoned and resumed.
92
+
93
+ // Two of CaptchaFox's challenge types are not solvable -- image-select and
94
+ // the audio fallback, both rare. They are reported as unsolvable rather than
95
+ // waited out, so treat a failure as a signal to resubmit into a fresh
96
+ // challenge rather than as a permanent failure of the key.
97
+ }
98
+
99
+ main().catch((error) => {
100
+ console.error(`${error.name}: ${error.message}`);
101
+ process.exitCode = 1;
102
+ });