@detiq/crawler 1.1.8 → 1.1.9
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/dist/accessibility-auditor.js +74 -54
- package/dist/ai-login.js +249 -227
- package/dist/auth-token-rotator.js +67 -57
- package/dist/auth.js +336 -299
- package/dist/browser-extension.js +142 -104
- package/dist/captcha-solver.js +125 -98
- package/dist/color-contrast.js +89 -77
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +71 -54
- package/dist/cookie-consent.js +83 -68
- package/dist/cookie-jar.js +60 -53
- package/dist/crawler.js +5345 -4486
- package/dist/custom-js-injector.js +61 -45
- package/dist/dark-mode-capture.js +81 -67
- package/dist/dead-link-detector.js +55 -47
- package/dist/element-hash.js +12 -8
- package/dist/email-otp.js +195 -154
- package/dist/engine-router.js +98 -87
- package/dist/export/bulk-export.js +55 -70
- package/dist/export/vector-export.js +105 -77
- package/dist/extractors/advanced.js +266 -249
- package/dist/extractors/artifact-envelope.js +58 -59
- package/dist/failure-analysis-agent.js +33 -37
- package/dist/feature-flag-detector.js +66 -51
- package/dist/form-detector.js +75 -55
- package/dist/frontier.js +101 -76
- package/dist/gap-pass.js +89 -68
- package/dist/graphql-detector.js +72 -62
- package/dist/http-engine.js +330 -282
- package/dist/incremental-diff.js +87 -54
- package/dist/index.js +28 -67
- package/dist/input-fuzzer.js +97 -93
- package/dist/interactive-probe.js +827 -715
- package/dist/job-queue.js +2169 -2151
- package/dist/js-coverage.js +56 -52
- package/dist/js-router-detector.js +117 -86
- package/dist/language-detector.js +63 -51
- package/dist/link-classifier.js +50 -32
- package/dist/live-view.js +108 -91
- package/dist/locator-extractor.js +187 -151
- package/dist/logger.js +7 -9
- package/dist/markdown-extract.js +61 -48
- package/dist/native-crawler.js +946 -780
- package/dist/network-observer.js +156 -135
- package/dist/network-throttle.js +40 -29
- package/dist/openapi-generator.js +92 -77
- package/dist/pdf-extractor.js +90 -72
- package/dist/performance-metrics.js +102 -80
- package/dist/phash.js +144 -125
- package/dist/pii-scrubber.js +68 -65
- package/dist/playwright-recorder.js +58 -70
- package/dist/policies/crawl-safety.js +34 -31
- package/dist/profiles/domain-profile.js +125 -130
- package/dist/proxy.js +172 -146
- package/dist/rate-limiter.js +65 -58
- package/dist/redirect-tracker.js +64 -60
- package/dist/redis-frontier.js +136 -116
- package/dist/redis-queue.js +144 -157
- package/dist/response-cache.js +140 -127
- package/dist/responsive-detector.js +46 -42
- package/dist/retry-policy.js +50 -46
- package/dist/robots-txt.js +83 -69
- package/dist/screen-reader.js +70 -45
- package/dist/screenshot-masker.js +77 -59
- package/dist/seo-checker.js +107 -88
- package/dist/session-capture.js +212 -192
- package/dist/session-export.js +133 -107
- package/dist/sitemap-writer.js +41 -38
- package/dist/sitemap.js +202 -141
- package/dist/skyvern.js +148 -123
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +90 -74
- package/dist/stealth/human-behavior.js +173 -156
- package/dist/telemetry.js +82 -68
- package/dist/test-executor.js +335 -309
- package/dist/tls-fingerprint.js +88 -77
- package/dist/url-pattern-completer.js +90 -71
- package/dist/visual-diff-verdict.js +38 -23
- package/dist/webhook-notifier.js +69 -71
- package/dist/websocket-detector.js +68 -56
- package/dist/worker.js +38 -29
- package/package.json +1 -1
|
@@ -1,113 +1,151 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Browser extension injection support.
|
|
3
|
+
*
|
|
4
|
+
* Chrome extensions require a persistent profile directory — incompatible
|
|
5
|
+
* with headless Docker by default. This module handles:
|
|
6
|
+
* 1. Persistent profile directory per extension set
|
|
7
|
+
* 2. Extension download from .crx URL or load from local path
|
|
8
|
+
* 3. Launch args for Playwright / Stagehand
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const profile = await prepareExtensionProfile(['/path/to/ext.crx']);
|
|
12
|
+
* // pass profile.userDataDir and profile.args to browser launch
|
|
13
|
+
*/
|
|
14
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
15
|
+
import { createWriteStream, existsSync, mkdirSync } from 'node:fs';
|
|
16
|
+
import { writeFile, mkdir, rm } from 'node:fs/promises';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { pipeline } from 'node:stream/promises';
|
|
20
|
+
import { Readable } from 'node:stream';
|
|
21
|
+
const PROFILE_BASE = process.env.EXTENSION_PROFILE_DIR ?? join(tmpdir(), 'zeta-ext-profiles');
|
|
22
|
+
/**
|
|
23
|
+
* Download a .crx from a URL to a temp file. Returns path.
|
|
24
|
+
*/
|
|
9
25
|
async function downloadCrx(url) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
const dest = join(tmpdir(), `zeta-ext-${randomBytes(6).toString('hex')}.crx`);
|
|
27
|
+
const res = await fetch(url);
|
|
28
|
+
if (!res.ok)
|
|
29
|
+
throw new Error(`Failed to download extension from ${url}: ${res.status}`);
|
|
30
|
+
const writer = createWriteStream(dest);
|
|
31
|
+
await pipeline(Readable.fromWeb(res.body), writer);
|
|
32
|
+
return dest;
|
|
16
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Unpack a .crx file to a directory Chrome can load as an unpacked extension.
|
|
36
|
+
* CRX3 format: 4-byte magic + 4-byte version + 4-byte header_size + proto header + zip body
|
|
37
|
+
*/
|
|
17
38
|
async function unpackCrx(crxPath, destDir) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
const { readFile } = await import('node:fs/promises');
|
|
40
|
+
const buf = await readFile(crxPath);
|
|
41
|
+
const magic = buf.readUInt32LE(0);
|
|
42
|
+
if (magic !== 0x43723234) {
|
|
43
|
+
// Not CRX — assume it's already a zip (unpacked extension)
|
|
44
|
+
await mkdir(destDir, { recursive: true });
|
|
45
|
+
await writeFile(join(destDir, '__raw.crx'), buf);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const version = buf.readUInt32LE(4);
|
|
49
|
+
let zipStart;
|
|
50
|
+
if (version === 3) {
|
|
51
|
+
const headerSize = buf.readUInt32LE(8);
|
|
52
|
+
zipStart = 12 + headerSize;
|
|
53
|
+
}
|
|
54
|
+
else if (version === 2) {
|
|
55
|
+
const pubKeyLen = buf.readUInt32LE(8);
|
|
56
|
+
const sigLen = buf.readUInt32LE(12);
|
|
57
|
+
zipStart = 16 + pubKeyLen + sigLen;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
throw new Error(`Unknown CRX version ${version}`);
|
|
61
|
+
}
|
|
62
|
+
const zipBuf = buf.subarray(zipStart);
|
|
63
|
+
const tmpZip = join(tmpdir(), `zeta-ext-${randomBytes(4).toString('hex')}.zip`);
|
|
64
|
+
await writeFile(tmpZip, zipBuf);
|
|
22
65
|
await mkdir(destDir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else {
|
|
36
|
-
throw new Error(`Unknown CRX version ${version}`);
|
|
37
|
-
}
|
|
38
|
-
const zipBuf = buf.subarray(zipStart);
|
|
39
|
-
const tmpZip = join(tmpdir(), `zeta-ext-${randomBytes(4).toString("hex")}.zip`);
|
|
40
|
-
await writeFile(tmpZip, zipBuf);
|
|
41
|
-
await mkdir(destDir, { recursive: true });
|
|
42
|
-
const unzipper = await import("unzipper").catch(() => null);
|
|
43
|
-
if (unzipper) {
|
|
44
|
-
await unzipper.Open.file(tmpZip).then((d) => d.extract({ path: destDir }));
|
|
45
|
-
} else {
|
|
46
|
-
const { execFile } = await import("node:child_process");
|
|
47
|
-
await new Promise(
|
|
48
|
-
(res, rej) => execFile("unzip", ["-o", tmpZip, "-d", destDir], (err) => err ? rej(err) : res())
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
await rm(tmpZip, { force: true });
|
|
66
|
+
// Extract using unzipper (available in Node without native deps)
|
|
67
|
+
// @ts-ignore – no types for unzipper
|
|
68
|
+
const unzipper = await import('unzipper').catch(() => null);
|
|
69
|
+
if (unzipper) {
|
|
70
|
+
await unzipper.Open.file(tmpZip).then((d) => d.extract({ path: destDir }));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Fallback: shell unzip
|
|
74
|
+
const { execFile } = await import('node:child_process');
|
|
75
|
+
await new Promise((res, rej) => execFile('unzip', ['-o', tmpZip, '-d', destDir], (err) => err ? rej(err) : res()));
|
|
76
|
+
}
|
|
77
|
+
await rm(tmpZip, { force: true });
|
|
52
78
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Prepare a Chrome user-data-dir with the given extensions loaded.
|
|
81
|
+
* Returns profile info + cleanup function.
|
|
82
|
+
*/
|
|
83
|
+
export async function prepareExtensionProfile(extensions) {
|
|
84
|
+
// Stable profile key based on extension sources
|
|
85
|
+
const key = createHash('md5')
|
|
86
|
+
.update(extensions.map(e => e.source).sort().join('|'))
|
|
87
|
+
.digest('hex')
|
|
88
|
+
.slice(0, 12);
|
|
89
|
+
const profileDir = join(PROFILE_BASE, key);
|
|
90
|
+
const extensionDirs = [];
|
|
91
|
+
mkdirSync(profileDir, { recursive: true });
|
|
92
|
+
for (const ext of extensions) {
|
|
93
|
+
const extHash = createHash('md5').update(ext.source).digest('hex').slice(0, 8);
|
|
94
|
+
const extDir = join(profileDir, `ext-${extHash}`);
|
|
95
|
+
if (!existsSync(extDir)) {
|
|
96
|
+
let crxPath = ext.source;
|
|
97
|
+
if (ext.source.startsWith('http://') || ext.source.startsWith('https://')) {
|
|
98
|
+
crxPath = await downloadCrx(ext.source);
|
|
99
|
+
await unpackCrx(crxPath, extDir);
|
|
100
|
+
await rm(crxPath, { force: true });
|
|
101
|
+
}
|
|
102
|
+
else if (ext.source.endsWith('.crx')) {
|
|
103
|
+
await unpackCrx(ext.source, extDir);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Assume already-unpacked extension directory — symlink or copy
|
|
107
|
+
extensionDirs.push(ext.source);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
extensionDirs.push(extDir);
|
|
73
112
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}), "utf8");
|
|
86
|
-
}
|
|
87
|
-
const loadExtensionArg = extensionDirs.join(",");
|
|
88
|
-
const args = [
|
|
89
|
-
`--load-extension=${loadExtensionArg}`,
|
|
90
|
-
`--disable-extensions-except=${loadExtensionArg}`,
|
|
91
|
-
`--user-data-dir=${userDataDir}`,
|
|
92
|
-
"--no-first-run",
|
|
93
|
-
"--no-default-browser-check"
|
|
94
|
-
];
|
|
95
|
-
return {
|
|
96
|
-
userDataDir,
|
|
97
|
-
args,
|
|
98
|
-
cleanup: async () => {
|
|
99
|
-
await rm(profileDir, { recursive: true, force: true });
|
|
113
|
+
const userDataDir = join(profileDir, 'user-data');
|
|
114
|
+
mkdirSync(userDataDir, { recursive: true });
|
|
115
|
+
// Write a minimal Preferences file so Chrome doesn't show first-run UI
|
|
116
|
+
const defaultsDir = join(userDataDir, 'Default');
|
|
117
|
+
mkdirSync(defaultsDir, { recursive: true });
|
|
118
|
+
const prefsPath = join(defaultsDir, 'Preferences');
|
|
119
|
+
if (!existsSync(prefsPath)) {
|
|
120
|
+
await writeFile(prefsPath, JSON.stringify({
|
|
121
|
+
browser: { has_seen_welcome_page: true },
|
|
122
|
+
extensions: { alerts: { initialized: true } },
|
|
123
|
+
}), 'utf8');
|
|
100
124
|
}
|
|
101
|
-
|
|
125
|
+
const loadExtensionArg = extensionDirs.join(',');
|
|
126
|
+
const args = [
|
|
127
|
+
`--load-extension=${loadExtensionArg}`,
|
|
128
|
+
`--disable-extensions-except=${loadExtensionArg}`,
|
|
129
|
+
`--user-data-dir=${userDataDir}`,
|
|
130
|
+
'--no-first-run',
|
|
131
|
+
'--no-default-browser-check',
|
|
132
|
+
];
|
|
133
|
+
return {
|
|
134
|
+
userDataDir,
|
|
135
|
+
args,
|
|
136
|
+
cleanup: async () => {
|
|
137
|
+
await rm(profileDir, { recursive: true, force: true });
|
|
138
|
+
},
|
|
139
|
+
};
|
|
102
140
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Ad-blocker extension profile using the uBlock Origin CRX.
|
|
143
|
+
* Reduces noise from ad networks during crawls.
|
|
144
|
+
*/
|
|
145
|
+
export async function adBlockerProfile() {
|
|
146
|
+
// uBlock Origin 1.57.2 from GitHub release (MIT licensed)
|
|
147
|
+
return prepareExtensionProfile([{
|
|
148
|
+
name: 'uBlock Origin',
|
|
149
|
+
source: 'https://github.com/gorhill/uBlock/releases/download/1.57.2/uBlock0_1.57.2.chromium.zip',
|
|
150
|
+
}]);
|
|
108
151
|
}
|
|
109
|
-
export {
|
|
110
|
-
adBlockerProfile,
|
|
111
|
-
prepareExtensionProfile
|
|
112
|
-
};
|
|
113
|
-
//# sourceMappingURL=browser-extension.js.map
|
package/dist/captcha-solver.js
CHANGED
|
@@ -1,105 +1,132 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CAPTCHA auto-solving via 2captcha or CapSolver APIs.
|
|
3
|
+
* Supports reCAPTCHA v2, hCaptcha, and Cloudflare Turnstile.
|
|
4
|
+
*/
|
|
5
|
+
// Poll interval and max attempts
|
|
6
|
+
const POLL_INTERVAL_MS = 3_000;
|
|
7
|
+
const MAX_ATTEMPTS = 40; // 2 min max
|
|
8
|
+
/**
|
|
9
|
+
* Detect CAPTCHA type and sitekey on the page, then solve it.
|
|
10
|
+
* Returns null if no CAPTCHA found or solver not configured.
|
|
11
|
+
* Throws on solve failure.
|
|
12
|
+
*/
|
|
13
|
+
export async function solveCaptcha(page, pageUrl, apiKey, provider = '2captcha') {
|
|
14
|
+
// Detect CAPTCHA type
|
|
15
|
+
const captchaInfo = await page.evaluate(() => {
|
|
16
|
+
// reCAPTCHA v2
|
|
17
|
+
const recaptchaEl = document.querySelector('.g-recaptcha, [data-sitekey]');
|
|
18
|
+
if (recaptchaEl) {
|
|
19
|
+
const sitekey = recaptchaEl.getAttribute('data-sitekey') || '';
|
|
20
|
+
return { type: 'recaptcha', sitekey };
|
|
21
|
+
}
|
|
22
|
+
// hCaptcha
|
|
23
|
+
const hcaptchaEl = document.querySelector('.h-captcha, [data-hcaptcha-sitekey]');
|
|
24
|
+
if (hcaptchaEl) {
|
|
25
|
+
const sitekey = hcaptchaEl.getAttribute('data-sitekey') || hcaptchaEl.getAttribute('data-hcaptcha-sitekey') || '';
|
|
26
|
+
return { type: 'hcaptcha', sitekey };
|
|
27
|
+
}
|
|
28
|
+
// Cloudflare Turnstile
|
|
29
|
+
const turnstileEl = document.querySelector('.cf-turnstile');
|
|
30
|
+
if (turnstileEl) {
|
|
31
|
+
const sitekey = turnstileEl.getAttribute('data-sitekey') || '';
|
|
32
|
+
return { type: 'turnstile', sitekey };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
});
|
|
36
|
+
if (!captchaInfo?.sitekey)
|
|
37
|
+
return null;
|
|
38
|
+
const token = provider === 'capsolver'
|
|
39
|
+
? await solveWithCapSolver(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey)
|
|
40
|
+
: await solveWith2Captcha(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey);
|
|
41
|
+
// Inject the token into the page
|
|
42
|
+
await page.evaluate((t, type) => {
|
|
43
|
+
// reCAPTCHA
|
|
44
|
+
if (type === 'recaptcha') {
|
|
45
|
+
const el = document.querySelector('textarea.g-recaptcha-response');
|
|
46
|
+
if (el) {
|
|
47
|
+
el.value = t;
|
|
48
|
+
}
|
|
49
|
+
// Also trigger the callback if registered
|
|
50
|
+
if (window.grecaptcha?.getResponse && document.querySelector('[data-callback]')) {
|
|
51
|
+
const cb = document.querySelector('[data-callback]')?.getAttribute('data-callback');
|
|
52
|
+
if (cb && window[cb])
|
|
53
|
+
window[cb](t);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// hCaptcha
|
|
57
|
+
if (type === 'hcaptcha') {
|
|
58
|
+
const el = document.querySelector('textarea[name="h-captcha-response"]');
|
|
59
|
+
if (el) {
|
|
60
|
+
el.value = t;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Turnstile
|
|
64
|
+
if (type === 'turnstile') {
|
|
65
|
+
const el = document.querySelector('[name="cf-turnstile-response"]');
|
|
66
|
+
if (el) {
|
|
67
|
+
el.value = t;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, token, captchaInfo.type);
|
|
71
|
+
return { token, provider };
|
|
49
72
|
}
|
|
50
73
|
async function solveWith2Captcha(type, sitekey, pageUrl, apiKey) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
const method = type === 'hcaptcha' ? 'hcaptcha' : type === 'turnstile' ? 'turnstile' : 'userrecaptcha';
|
|
75
|
+
const submitRes = await fetch('http://2captcha.com/in.php', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: new URLSearchParams({
|
|
78
|
+
key: apiKey,
|
|
79
|
+
method,
|
|
80
|
+
googlekey: sitekey,
|
|
81
|
+
pageurl: pageUrl,
|
|
82
|
+
json: '1',
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
const submitData = await submitRes.json();
|
|
86
|
+
if (submitData.status !== 1)
|
|
87
|
+
throw new Error(`2captcha submit failed: ${submitData.request}`);
|
|
88
|
+
const taskId = submitData.request;
|
|
89
|
+
// Poll for result
|
|
90
|
+
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
91
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
92
|
+
const pollRes = await fetch(`http://2captcha.com/res.php?key=${apiKey}&action=get&id=${taskId}&json=1`);
|
|
93
|
+
const pollData = await pollRes.json();
|
|
94
|
+
if (pollData.status === 1)
|
|
95
|
+
return pollData.request;
|
|
96
|
+
if (pollData.request !== 'CAPCHA_NOT_READY')
|
|
97
|
+
throw new Error(`2captcha error: ${pollData.request}`);
|
|
98
|
+
}
|
|
99
|
+
throw new Error('2captcha timed out after 2 minutes');
|
|
73
100
|
}
|
|
74
101
|
async function solveWithCapSolver(type, sitekey, pageUrl, apiKey) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (createData.errorId !== 0) throw new Error(`CapSolver create failed: ${createData.errorDescription}`);
|
|
86
|
-
const taskId = createData.taskId;
|
|
87
|
-
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
88
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
89
|
-
const pollRes = await fetch("https://api.capsolver.com/getTaskResult", {
|
|
90
|
-
method: "POST",
|
|
91
|
-
headers: { "Content-Type": "application/json" },
|
|
92
|
-
body: JSON.stringify({ clientKey: apiKey, taskId })
|
|
102
|
+
const taskType = type === 'hcaptcha' ? 'HCaptchaTaskProxyless'
|
|
103
|
+
: type === 'turnstile' ? 'AntiTurnstileTaskProxyless'
|
|
104
|
+
: 'ReCaptchaV2TaskProxyless';
|
|
105
|
+
const createRes = await fetch('https://api.capsolver.com/createTask', {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
clientKey: apiKey,
|
|
110
|
+
task: { type: taskType, websiteURL: pageUrl, websiteKey: sitekey },
|
|
111
|
+
}),
|
|
93
112
|
});
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
const createData = await createRes.json();
|
|
114
|
+
if (createData.errorId !== 0)
|
|
115
|
+
throw new Error(`CapSolver create failed: ${createData.errorDescription}`);
|
|
116
|
+
const taskId = createData.taskId;
|
|
117
|
+
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
118
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
119
|
+
const pollRes = await fetch('https://api.capsolver.com/getTaskResult', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({ clientKey: apiKey, taskId }),
|
|
123
|
+
});
|
|
124
|
+
const pollData = await pollRes.json();
|
|
125
|
+
if (pollData.errorId !== 0)
|
|
126
|
+
throw new Error(`CapSolver error: ${pollData.errorDescription}`);
|
|
127
|
+
if (pollData.status === 'ready') {
|
|
128
|
+
return pollData.solution?.gRecaptchaResponse ?? pollData.solution?.token ?? '';
|
|
129
|
+
}
|
|
98
130
|
}
|
|
99
|
-
|
|
100
|
-
throw new Error("CapSolver timed out after 2 minutes");
|
|
131
|
+
throw new Error('CapSolver timed out after 2 minutes');
|
|
101
132
|
}
|
|
102
|
-
export {
|
|
103
|
-
solveCaptcha
|
|
104
|
-
};
|
|
105
|
-
//# sourceMappingURL=captcha-solver.js.map
|