capskip 1.1.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/src/solver.js CHANGED
@@ -83,6 +83,13 @@ function applyPollResult(result, polled) {
83
83
  if (userAgent) {
84
84
  result.userAgent = userAgent;
85
85
  }
86
+ // ALTCHA's createTask-shaped `solution` object. Carried through so
87
+ // applyAltchaSolution can read the counter the server already worked out,
88
+ // which is the only reliable source for a proof-of-work v2 answer; that
89
+ // function deletes it, so it never reaches the caller.
90
+ if (polled.solution !== null && typeof polled.solution === 'object') {
91
+ result.solution = polled.solution;
92
+ }
86
93
  } else {
87
94
  result.code = polled;
88
95
  }
@@ -126,6 +133,159 @@ function applyGeetestSolution(result) {
126
133
  return result;
127
134
  }
128
135
 
136
+ // ALTCHA answers come back as a base64 payload: the challenge document with the
137
+ // winning counter added. That payload is what the site's own `altcha` form field
138
+ // carries, so it is posted back verbatim.
139
+
140
+ /**
141
+ * Expose the answer as `token`, and the winning counter as `number`.
142
+ *
143
+ * `code` keeps the raw answer so callers that forward it verbatim (or that were
144
+ * written against another solver's API) keep working; `token` is the same string,
145
+ * named for the form field it goes into. If the payload does not decode, the
146
+ * result is returned untouched rather than masking the server's reply.
147
+ */
148
+ /**
149
+ * Dig the winning counter out of a token, whichever scheme produced it.
150
+ *
151
+ * The two ALTCHA generations nest it differently: a legacy payload is the
152
+ * challenge document with a top-level `number` added, while a proof-of-work v2
153
+ * payload is `{ challenge: {...}, solution: { counter: N, ... } }` and has no
154
+ * `number` at all. Returns undefined if the payload does not decode.
155
+ */
156
+ function tokenCounter(code) {
157
+ let payload;
158
+ try {
159
+ const decoded = Buffer.from(code, 'base64');
160
+ // Buffer.from is lenient: it drops invalid characters instead of throwing,
161
+ // so round-trip to confirm the input really was base64 before trusting it.
162
+ if (decoded.toString('base64').replace(/=+$/, '') !== code.replace(/=+$/, '')) {
163
+ return undefined;
164
+ }
165
+ payload = JSON.parse(decoded.toString('utf8'));
166
+ } catch (err) {
167
+ return undefined;
168
+ }
169
+
170
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
171
+ return undefined;
172
+ }
173
+
174
+ if (payload.number !== undefined) {
175
+ return payload.number;
176
+ }
177
+
178
+ if (payload.solution !== null && typeof payload.solution === 'object') {
179
+ return payload.solution.counter;
180
+ }
181
+
182
+ return undefined;
183
+ }
184
+
185
+ /**
186
+ * Expose the answer as `token`, and the winning counter as `number`.
187
+ *
188
+ * `code` keeps the raw answer so callers that forward it verbatim (or that were
189
+ * written against another solver's API) keep working; `token` is the same
190
+ * string, named for the form field it goes into.
191
+ *
192
+ * The counter comes from the server's own `solution` object when the poll
193
+ * carried one, because that is the single field both ALTCHA generations report
194
+ * the same way. Only if it is absent — a plain-text poll — is it dug out of the
195
+ * token, which is shaped differently per scheme. If neither yields one, the
196
+ * result keeps its token and simply has no `number`, rather than masking the
197
+ * server's reply.
198
+ */
199
+ function applyAltchaSolution(result) {
200
+ const code = result.code || '';
201
+ result.token = code;
202
+
203
+ const { solution } = result;
204
+ delete result.solution;
205
+
206
+ let number = solution !== null && typeof solution === 'object'
207
+ ? solution.number
208
+ : undefined;
209
+ if (number === undefined) {
210
+ number = tokenCounter(code);
211
+ }
212
+
213
+ if (number !== undefined) {
214
+ result.number = number;
215
+ }
216
+
217
+ return result;
218
+ }
219
+
220
+ // A Capy solution is not a token. It is three values that together go into the
221
+ // target form, under the capy_ prefixed names the widget would have filled in.
222
+ const CAPY_FIELDS = ['captchakey', 'challengekey', 'answer', 'respKey'];
223
+
224
+ /**
225
+ * Expand the Capy answer into `captchakey` / `challengekey` / `answer`.
226
+ *
227
+ * `code` keeps the raw answer -- an object when polled with json=1, where the
228
+ * server puts it straight into `request`, or the JSON string it sends after
229
+ * `OK|` in plain-text mode -- so callers that forward it verbatim (or that were
230
+ * written against another solver's API) keep working.
231
+ *
232
+ * If the answer does not parse, the result is returned untouched rather than
233
+ * masking the server's reply.
234
+ */
235
+ function applyCapySolution(result) {
236
+ let payload = result.solution;
237
+ delete result.solution;
238
+
239
+ if (payload === null || typeof payload !== 'object') {
240
+ const { code } = result;
241
+ if (code !== null && typeof code === 'object') {
242
+ payload = code;
243
+ } else {
244
+ try {
245
+ payload = JSON.parse(code || '');
246
+ } catch (error) {
247
+ return result;
248
+ }
249
+ }
250
+ }
251
+
252
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
253
+ return result;
254
+ }
255
+
256
+ for (const field of CAPY_FIELDS) {
257
+ if (Object.prototype.hasOwnProperty.call(payload, field)) {
258
+ result[field] = payload[field];
259
+ }
260
+ }
261
+
262
+ return result;
263
+ }
264
+
265
+ /**
266
+ * Expose a single-token answer as `token`, named for the form field it fills.
267
+ *
268
+ * `code` keeps the raw answer so callers that forward it verbatim keep working;
269
+ * `token` is the same string. The server's createTask-shaped `solution` object
270
+ * carries that same string, so it is consumed here rather than handed back as a
271
+ * second copy -- but it is read first when `request` came through empty, so a
272
+ * client is never left without the token the poll actually carried.
273
+ */
274
+ function applyTokenSolution(result) {
275
+ const { solution } = result;
276
+ delete result.solution;
277
+
278
+ let code = result.code;
279
+
280
+ if (!code && solution !== null && typeof solution === 'object') {
281
+ code = solution.token || '';
282
+ result.code = code;
283
+ }
284
+
285
+ result.token = code || '';
286
+ return result;
287
+ }
288
+
129
289
  // CapSkip's in.php returns OK|<id> by default, or {"status":1,"request":"<id>"}
130
290
  // when the submit carried json=1. Accept both so submitting with json=1 works.
131
291
  function parseSubmitResponse(response) {
@@ -149,7 +309,7 @@ function parseSubmitResponse(response) {
149
309
  throw new ApiException(`cannot recognize response ${response}`);
150
310
  }
151
311
 
152
- /** Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile, GeeTest v3). */
312
+ /** Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile, GeeTest v3, ALTCHA). */
153
313
  class CapSkip {
154
314
  constructor({
155
315
  apiKey = 'capskip',
@@ -230,6 +390,169 @@ class CapSkip {
230
390
  return applyGeetestSolution(result);
231
391
  }
232
392
 
393
+ /**
394
+ * Solve an ALTCHA proof-of-work challenge.
395
+ *
396
+ * Pass `challengeUrl` for CapSkip to fetch the challenge itself, or
397
+ * `challengeJson` with the document you already have (a JSON string, or an
398
+ * object which is serialized for you). Sending both is allowed -- the inline
399
+ * document wins. A proxy applies only to the `challengeUrl` fetch.
400
+ *
401
+ * Challenges expire fast -- some sites inside two minutes -- and an expired one
402
+ * is refused with a bare "verification failed" that looks exactly like a wrong
403
+ * answer. Fetch the challenge immediately before calling, and post the token
404
+ * promptly.
405
+ *
406
+ * The result carries the raw answer as `code`, the same string as `token`
407
+ * (what the site's `altcha` form field expects, verbatim), and the counter
408
+ * that solved it as `number`.
409
+ */
410
+ async altcha(url, options = {}) {
411
+ // An unset challenge param is dropped rather than sent as undefined, so
412
+ // `altcha(url, { challengeUrl, challengeJson })` works with either left out.
413
+ const given = {};
414
+ for (const [key, value] of Object.entries(options)) {
415
+ if (value !== undefined && value !== null) {
416
+ given[key] = value;
417
+ }
418
+ }
419
+
420
+ // Unlike GeeTest and reCAPTCHA this is CPU proof-of-work measured in
421
+ // milliseconds, not a browser solve, so it keeps the default timeout.
422
+ const result = await this.solve({
423
+ url,
424
+ ...given,
425
+ method: 'altcha',
426
+ poll_json: 1,
427
+ });
428
+ return applyAltchaSolution(result);
429
+ }
430
+
431
+ /**
432
+ * Solve a Capy Puzzle captcha.
433
+ *
434
+ * `sitekey` is the site's public Capy key, conventionally prefixed `PUZZLE_`;
435
+ * it is sent as the `captchakey` the API documents. Pass `api_server` when the
436
+ * widget script points somewhere other than `https://jp.api.capy.me`.
437
+ *
438
+ * The result is not a token. It carries `captchakey`, `challengekey` and
439
+ * `answer`, which go into the target form's `capy_captchakey`,
440
+ * `capy_challengekey` and `capy_answer` fields, plus the raw answer as `code`.
441
+ * Submit `answer` verbatim -- it is the drag path the widget would have
442
+ * recorded, so trimming or re-encoding it invalidates the solve.
443
+ *
444
+ * The challenge key is single-use and short-lived, so submit promptly rather
445
+ * than caching the three values for a later request.
446
+ */
447
+ async capy(sitekey, url, options = {}) {
448
+ // An unset optional is dropped rather than sent as undefined, so
449
+ // `capy(key, url, { apiServer: undefined })` behaves as if it were omitted.
450
+ const given = {};
451
+ for (const [key, value] of Object.entries(options)) {
452
+ if (value !== undefined && value !== null) {
453
+ given[key] = value;
454
+ }
455
+ }
456
+
457
+ // A Capy solve is one HTTP fetch plus pixel math, not a browser session, so
458
+ // it keeps the default timeout. It is held back to roughly two seconds
459
+ // before the answer is released -- Capy refuses answers that arrive faster
460
+ // than a human could have produced them -- which the default absorbs.
461
+ const result = await this.solve({
462
+ captchakey: sitekey,
463
+ url,
464
+ ...given,
465
+ method: 'capy',
466
+ poll_json: 1,
467
+ });
468
+ return applyCapySolution(result);
469
+ }
470
+
471
+ /**
472
+ * Solve a CaptchaFox challenge.
473
+ *
474
+ * `sitekey` is the public key the widget renders with, conventionally prefixed
475
+ * `sk_`, and `url` has to be the page the widget actually runs on: CaptchaFox
476
+ * checks it against the domains the key is registered for and refuses a
477
+ * mismatch permanently rather than intermittently.
478
+ *
479
+ * Pass `api_server` only when the target page does not load the default
480
+ * widget. A page loading the MAM package expects a `MAM_` prefixed token, and
481
+ * sending the wrong source still succeeds -- it just returns a token in a
482
+ * format the site will not accept, which reads as a silent verification
483
+ * failure rather than an error.
484
+ *
485
+ * The result carries the token as both `code` and `token`, for the form's
486
+ * `cf-captcha-response` field, and `userAgent` when the solve reported one.
487
+ * That User-Agent is the browser's own, not any you sent, so submit the token
488
+ * under it.
489
+ */
490
+ async captchafox(sitekey, url, options = {}) {
491
+ const given = {};
492
+ for (const [key, value] of Object.entries(options)) {
493
+ if (value !== undefined && value !== null) {
494
+ given[key] = value;
495
+ }
496
+ }
497
+
498
+ // A real browser session, like reCAPTCHA and GeeTest, and longer again when
499
+ // an interactive challenge is drawn -- so it gets the longer of the two
500
+ // timeouts unless the caller asked for a specific one.
501
+ const result = await this.solve({
502
+ timeout: this.recaptchaTimeout,
503
+ sitekey,
504
+ url,
505
+ ...given,
506
+ method: 'captchafox',
507
+ poll_json: 1,
508
+ });
509
+ return applyTokenSolution(result);
510
+ }
511
+
512
+ /**
513
+ * Solve a Friendly Captcha proof-of-work challenge.
514
+ *
515
+ * Two different protocols ship under this name and a sitekey does not tell you
516
+ * which one a site uses, so say which: pass `version: 'v1'` or `version: 'v2'`,
517
+ * or pass `moduleScript` with the src of the widget's `type="module"` script
518
+ * tag and let CapSkip read the version off the build the site actually loads.
519
+ * With neither, v1 is assumed. Solving the wrong version returns a well-formed
520
+ * token the target site rejects, with nothing to indicate the version was the
521
+ * problem.
522
+ *
523
+ * Pass `api_server: 'eu'` for a sitekey on the EU data-residency tenant; both
524
+ * tenants mint a token for the same sitekey, so the wrong one is only caught
525
+ * by the site's own verification.
526
+ *
527
+ * The result carries the token as both `code` and `token`. It goes into
528
+ * `frc-captcha-solution` on v1 and `frc-captcha-response` on v2 -- the field
529
+ * names differ, which is what catches an integration moved from one to the
530
+ * other. A v2 token is roughly six kilobytes, so size whatever carries it
531
+ * accordingly.
532
+ */
533
+ async friendlyCaptcha(sitekey, url, options = {}) {
534
+ const given = {};
535
+ for (const [key, value] of Object.entries(options)) {
536
+ if (value !== undefined && value !== null) {
537
+ given[key] = value;
538
+ }
539
+ }
540
+
541
+ // Proof-of-work, but not the millisecond kind ALTCHA does: the service sets
542
+ // the difficulty per request and raises it for addresses it has seen a lot
543
+ // of, and v2 always solves in a browser. Both make solve time variable
544
+ // enough to want the longer timeout.
545
+ const result = await this.solve({
546
+ timeout: this.recaptchaTimeout,
547
+ sitekey,
548
+ url,
549
+ ...given,
550
+ method: 'friendly_captcha',
551
+ poll_json: 1,
552
+ });
553
+ return applyTokenSolution(result);
554
+ }
555
+
233
556
  async solve(options = {}) {
234
557
  const {
235
558
  timeout = 0,
@@ -318,6 +641,18 @@ class CapSkip {
318
641
  if (method === 'geetest') {
319
642
  return prepareSubmitParams(params, 'geetest');
320
643
  }
644
+ if (method === 'altcha') {
645
+ return prepareSubmitParams(params, 'altcha');
646
+ }
647
+ if (method === 'capy') {
648
+ return prepareSubmitParams(params, 'capy');
649
+ }
650
+ if (method === 'captchafox') {
651
+ return prepareSubmitParams(params, 'captchafox');
652
+ }
653
+ if (method === 'friendly_captcha') {
654
+ return prepareSubmitParams(params, 'friendly_captcha');
655
+ }
321
656
  return applyProxy(applyParamAliases(params));
322
657
  }
323
658
  }
@@ -330,4 +665,7 @@ module.exports = {
330
665
  parseSubmitResponse,
331
666
  applyPollResult,
332
667
  applyGeetestSolution,
668
+ applyAltchaSolution,
669
+ applyCapySolution,
670
+ applyTokenSolution,
333
671
  };
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Type definitions for the CapSkip Node.js SDK.
2
2
 
3
- /** Proxy passed to reCAPTCHA / Turnstile / GeeTest solves. */
3
+ /** Proxy passed to any solve except image captcha. */
4
4
  export interface Proxy {
5
5
  /** Proxy type: `HTTP`, `HTTPS`, `SOCKS5`, or `SOCKS5H`. */
6
6
  type: string;
@@ -18,7 +18,11 @@ export interface CapSkipOptions {
18
18
  port?: number;
19
19
  /** Seconds to poll an image captcha before timing out. Default `120`. */
20
20
  defaultTimeout?: number;
21
- /** Seconds to poll reCAPTCHA / Turnstile / GeeTest before timing out. Default `300`. */
21
+ /**
22
+ * Seconds to poll reCAPTCHA / Turnstile / GeeTest / CaptchaFox / Friendly
23
+ * Captcha before timing out. Default `300`. ALTCHA and Capy use
24
+ * `defaultTimeout` instead — neither is a browser solve.
25
+ */
22
26
  recaptchaTimeout?: number;
23
27
  /** Max seconds between polls; starts at `0.25` and backs off to this. Default `5`. */
24
28
  pollingInterval?: number;
@@ -31,10 +35,16 @@ export interface SolveResult {
31
35
  /**
32
36
  * The solution: recognized text for images, a token otherwise. For GeeTest
33
37
  * this is the raw JSON string CapSkip returns — prefer the parsed
34
- * `challenge` / `validate` / `seccode` fields below.
38
+ * `challenge` / `validate` / `seccode` fields below. For ALTCHA it is the
39
+ * base64 token, also exposed as `token`. For Capy it is the raw answer
40
+ * object, expanded into the three fields below.
41
+ */
42
+ code: string | Record<string, unknown>;
43
+ /**
44
+ * Turnstile and CaptchaFox — the User-Agent to use when submitting the token.
45
+ * For CaptchaFox this is the browser's own, not one you sent, and it is absent
46
+ * when the solve captured none rather than guessed.
35
47
  */
36
- code: string;
37
- /** Turnstile only — the User-Agent to use when submitting the token. */
38
48
  userAgent?: string;
39
49
  /** GeeTest only — the `geetest_challenge` value to post back. */
40
50
  challenge?: string;
@@ -42,6 +52,27 @@ export interface SolveResult {
42
52
  validate?: string;
43
53
  /** GeeTest only — the `geetest_seccode` value to post back. */
44
54
  seccode?: string;
55
+ /**
56
+ * ALTCHA only — the base64 payload to post back in the site's `altcha` form
57
+ * field. The same string as `code`, named for where it goes.
58
+ */
59
+ token?: string;
60
+ /** ALTCHA only — the counter that solved the challenge. */
61
+ number?: number;
62
+ /** Capy only — the value for the target form's `capy_captchakey` field. */
63
+ captchakey?: string;
64
+ /** Capy only — the value for the target form's `capy_challengekey` field. */
65
+ challengekey?: string;
66
+ /**
67
+ * Capy only — the value for the target form's `capy_answer` field. The drag
68
+ * path the widget would have recorded; submit it verbatim.
69
+ */
70
+ answer?: string;
71
+ /**
72
+ * Capy only — empty for a puzzle solve. Present for shape compatibility with
73
+ * 2Captcha's documented response; it carries nothing.
74
+ */
75
+ respKey?: string;
45
76
  }
46
77
 
47
78
  /** Extra options for {@link CapSkip.normal}. */
@@ -111,6 +142,103 @@ export interface GeetestOptions {
111
142
  [key: string]: unknown;
112
143
  }
113
144
 
145
+ /** Extra options for {@link CapSkip.altcha}. */
146
+ export interface AltchaOptions {
147
+ /** Endpoint CapSkip fetches the challenge from. */
148
+ challengeUrl?: string;
149
+ /** Endpoint CapSkip fetches the challenge from. */
150
+ challenge_url?: string;
151
+ /**
152
+ * The challenge document itself. An object is serialized for you; a string is
153
+ * sent as-is.
154
+ */
155
+ challengeJson?: string | Record<string, unknown>;
156
+ /** The challenge document itself. */
157
+ challenge_json?: string | Record<string, unknown>;
158
+ /** `1` to request the raw JSON response from CapSkip. */
159
+ json?: number;
160
+ /** Proxy — used only for the `challengeUrl` fetch, never for the solve. */
161
+ proxy?: Proxy | string;
162
+ /** Proxy type when `proxy` is a bare string. */
163
+ proxytype?: string;
164
+ [key: string]: unknown;
165
+ }
166
+
167
+ /** Extra options for {@link CapSkip.capy}. */
168
+ export interface CapyOptions {
169
+ /** Root of the Capy API the key lives behind (alias for `api_server`). */
170
+ apiServer?: string;
171
+ /** Root of the Capy API the key lives behind. Default `https://jp.api.capy.me`. */
172
+ api_server?: string;
173
+ /**
174
+ * The challenge family. Only `"puzzle"` (the default) is solved — `"avatar"`
175
+ * is refused before the request is made.
176
+ */
177
+ version?: string;
178
+ /** User-Agent to send with the puzzle request (alias for `useragent`). */
179
+ userAgent?: string;
180
+ /** User-Agent to send with the puzzle request. */
181
+ useragent?: string;
182
+ /** `1` to request the raw JSON response from CapSkip. */
183
+ json?: number;
184
+ /** Proxy — used for the puzzle-image fetch, the only request a Capy solve makes. */
185
+ proxy?: Proxy | string;
186
+ /** Proxy type when `proxy` is a bare string. */
187
+ proxytype?: string;
188
+ [key: string]: unknown;
189
+ }
190
+
191
+ /** Extra options for {@link CapSkip.captchafox}. */
192
+ export interface CaptchaFoxOptions {
193
+ /** Widget entry point (alias for `api_server`). */
194
+ apiServer?: string;
195
+ /**
196
+ * Widget entry point. Default `https://cdn.captchafox.com/`. The MAM package
197
+ * path returns a `MAM_` prefixed token instead.
198
+ */
199
+ api_server?: string;
200
+ /** Accepted for compatibility and not applied (alias for `useragent`). */
201
+ userAgent?: string;
202
+ /** Accepted for compatibility and not applied — CapSkip uses its own browser. */
203
+ useragent?: string;
204
+ /** `1` to request the raw JSON response from CapSkip. */
205
+ json?: number;
206
+ /** Proxy used for solving. */
207
+ proxy?: Proxy | string;
208
+ /** Proxy type when `proxy` is a bare string. */
209
+ proxytype?: string;
210
+ [key: string]: unknown;
211
+ }
212
+
213
+ /** Extra options for {@link CapSkip.friendlyCaptcha}. */
214
+ export interface FriendlyCaptchaOptions {
215
+ /** Protocol version: `"v1"` (default) or `"v2"`; a bare `1` or `2` works too. */
216
+ version?: string | number;
217
+ /** `src` of the widget script tag carrying `type="module"` (alias). */
218
+ moduleScript?: string;
219
+ /** `src` of the widget script tag carrying `type="module"`. */
220
+ module_script?: string;
221
+ /** `src` of the widget script tag carrying `nomodule` (alias). */
222
+ nomoduleScript?: string;
223
+ /** `src` of the widget script tag carrying `nomodule`. */
224
+ nomodule_script?: string;
225
+ /** Data residency tenant (alias for `api_server`). */
226
+ apiServer?: string;
227
+ /** Data residency tenant: `"global"` (default), `"eu"`, or a full URL. */
228
+ api_server?: string;
229
+ /** User-Agent to send with the request (alias for `useragent`). */
230
+ userAgent?: string;
231
+ /** User-Agent to send with the request. */
232
+ useragent?: string;
233
+ /** `1` to request the raw JSON response from CapSkip. */
234
+ json?: number;
235
+ /** Proxy used for solving. */
236
+ proxy?: Proxy | string;
237
+ /** Proxy type when `proxy` is a bare string. */
238
+ proxytype?: string;
239
+ [key: string]: unknown;
240
+ }
241
+
114
242
  /** Options for the {@link CapSkip.solve} manual workflow. */
115
243
  export interface SolveOptions {
116
244
  /** Poll timeout in seconds (falls back to the configured default). */
@@ -150,6 +278,48 @@ export class CapSkip {
150
278
  url: string,
151
279
  options?: GeetestOptions,
152
280
  ): Promise<SolveResult>;
281
+ /**
282
+ * Solve an ALTCHA proof-of-work challenge.
283
+ *
284
+ * Pass `challengeUrl` for CapSkip to fetch the challenge, or `challengeJson`
285
+ * with the document itself. Sending both is allowed — the inline document
286
+ * wins. Challenges expire fast, so fetch one immediately before calling.
287
+ */
288
+ altcha(url: string, options?: AltchaOptions): Promise<SolveResult>;
289
+ /**
290
+ * Solve a Capy Puzzle captcha.
291
+ *
292
+ * `sitekey` is the site's public Capy key, conventionally prefixed `PUZZLE_`,
293
+ * and is sent as the `captchakey` the API documents. The result is not a
294
+ * token: it carries `captchakey`, `challengekey` and `answer`, all three of
295
+ * which go into the target form.
296
+ */
297
+ capy(sitekey: string, url: string, options?: CapyOptions): Promise<SolveResult>;
298
+ /**
299
+ * Solve a CaptchaFox challenge.
300
+ *
301
+ * `url` has to be the page the widget actually runs on — CaptchaFox checks it
302
+ * against the domains the key is registered for. The result carries `token`
303
+ * for the form's `cf-captcha-response` field.
304
+ */
305
+ captchafox(
306
+ sitekey: string,
307
+ url: string,
308
+ options?: CaptchaFoxOptions,
309
+ ): Promise<SolveResult>;
310
+ /**
311
+ * Solve a Friendly Captcha proof-of-work challenge.
312
+ *
313
+ * Two different protocols ship under this name and a sitekey does not say
314
+ * which, so pass `version`, or pass `moduleScript` and let CapSkip read it off
315
+ * the build the site loads. The token goes into `frc-captcha-solution` on v1
316
+ * or `frc-captcha-response` on v2.
317
+ */
318
+ friendlyCaptcha(
319
+ sitekey: string,
320
+ url: string,
321
+ options?: FriendlyCaptchaOptions,
322
+ ): Promise<SolveResult>;
153
323
  /** Submit then poll to completion. Used by the higher-level solve methods. */
154
324
  solve(options?: SolveOptions): Promise<SolveResult>;
155
325
  /** Submit a captcha without polling; resolves to the captcha id. */