@detiq/crawler 1.1.9 → 1.1.10
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 +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
|
@@ -1,151 +1,113 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
*/
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { createWriteStream, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { writeFile, mkdir, rm } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import { Readable } from "node:stream";
|
|
8
|
+
const PROFILE_BASE = process.env.EXTENSION_PROFILE_DIR ?? join(tmpdir(), "zeta-ext-profiles");
|
|
25
9
|
async function downloadCrx(url) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return dest;
|
|
10
|
+
const dest = join(tmpdir(), `zeta-ext-${randomBytes(6).toString("hex")}.crx`);
|
|
11
|
+
const res = await fetch(url);
|
|
12
|
+
if (!res.ok) throw new Error(`Failed to download extension from ${url}: ${res.status}`);
|
|
13
|
+
const writer = createWriteStream(dest);
|
|
14
|
+
await pipeline(Readable.fromWeb(res.body), writer);
|
|
15
|
+
return dest;
|
|
33
16
|
}
|
|
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
|
-
*/
|
|
38
17
|
async function unpackCrx(crxPath, destDir) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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);
|
|
18
|
+
const { readFile } = await import("node:fs/promises");
|
|
19
|
+
const buf = await readFile(crxPath);
|
|
20
|
+
const magic = buf.readUInt32LE(0);
|
|
21
|
+
if (magic !== 1131557428) {
|
|
65
22
|
await mkdir(destDir, { recursive: true });
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
23
|
+
await writeFile(join(destDir, "__raw.crx"), buf);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const version = buf.readUInt32LE(4);
|
|
27
|
+
let zipStart;
|
|
28
|
+
if (version === 3) {
|
|
29
|
+
const headerSize = buf.readUInt32LE(8);
|
|
30
|
+
zipStart = 12 + headerSize;
|
|
31
|
+
} else if (version === 2) {
|
|
32
|
+
const pubKeyLen = buf.readUInt32LE(8);
|
|
33
|
+
const sigLen = buf.readUInt32LE(12);
|
|
34
|
+
zipStart = 16 + pubKeyLen + sigLen;
|
|
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 });
|
|
78
52
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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);
|
|
53
|
+
async function prepareExtensionProfile(extensions) {
|
|
54
|
+
const key = createHash("md5").update(extensions.map((e) => e.source).sort().join("|")).digest("hex").slice(0, 12);
|
|
55
|
+
const profileDir = join(PROFILE_BASE, key);
|
|
56
|
+
const extensionDirs = [];
|
|
57
|
+
mkdirSync(profileDir, { recursive: true });
|
|
58
|
+
for (const ext of extensions) {
|
|
59
|
+
const extHash = createHash("md5").update(ext.source).digest("hex").slice(0, 8);
|
|
60
|
+
const extDir = join(profileDir, `ext-${extHash}`);
|
|
61
|
+
if (!existsSync(extDir)) {
|
|
62
|
+
let crxPath = ext.source;
|
|
63
|
+
if (ext.source.startsWith("http://") || ext.source.startsWith("https://")) {
|
|
64
|
+
crxPath = await downloadCrx(ext.source);
|
|
65
|
+
await unpackCrx(crxPath, extDir);
|
|
66
|
+
await rm(crxPath, { force: true });
|
|
67
|
+
} else if (ext.source.endsWith(".crx")) {
|
|
68
|
+
await unpackCrx(ext.source, extDir);
|
|
69
|
+
} else {
|
|
70
|
+
extensionDirs.push(ext.source);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
112
73
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
74
|
+
extensionDirs.push(extDir);
|
|
75
|
+
}
|
|
76
|
+
const userDataDir = join(profileDir, "user-data");
|
|
77
|
+
mkdirSync(userDataDir, { recursive: true });
|
|
78
|
+
const defaultsDir = join(userDataDir, "Default");
|
|
79
|
+
mkdirSync(defaultsDir, { recursive: true });
|
|
80
|
+
const prefsPath = join(defaultsDir, "Preferences");
|
|
81
|
+
if (!existsSync(prefsPath)) {
|
|
82
|
+
await writeFile(prefsPath, JSON.stringify({
|
|
83
|
+
browser: { has_seen_welcome_page: true },
|
|
84
|
+
extensions: { alerts: { initialized: true } }
|
|
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 });
|
|
124
100
|
}
|
|
125
|
-
|
|
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
|
-
};
|
|
101
|
+
};
|
|
140
102
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
}]);
|
|
103
|
+
async function adBlockerProfile() {
|
|
104
|
+
return prepareExtensionProfile([{
|
|
105
|
+
name: "uBlock Origin",
|
|
106
|
+
source: "https://github.com/gorhill/uBlock/releases/download/1.57.2/uBlock0_1.57.2.chromium.zip"
|
|
107
|
+
}]);
|
|
151
108
|
}
|
|
109
|
+
export {
|
|
110
|
+
adBlockerProfile,
|
|
111
|
+
prepareExtensionProfile
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=browser-extension.js.map
|
package/dist/captcha-solver.js
CHANGED
|
@@ -1,132 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
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
|
-
|
|
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 };
|
|
1
|
+
const POLL_INTERVAL_MS = 3e3;
|
|
2
|
+
const MAX_ATTEMPTS = 40;
|
|
3
|
+
async function solveCaptcha(page, pageUrl, apiKey, provider = "2captcha") {
|
|
4
|
+
const captchaInfo = await page.evaluate(() => {
|
|
5
|
+
const recaptchaEl = document.querySelector(".g-recaptcha, [data-sitekey]");
|
|
6
|
+
if (recaptchaEl) {
|
|
7
|
+
const sitekey = recaptchaEl.getAttribute("data-sitekey") || "";
|
|
8
|
+
return { type: "recaptcha", sitekey };
|
|
9
|
+
}
|
|
10
|
+
const hcaptchaEl = document.querySelector(".h-captcha, [data-hcaptcha-sitekey]");
|
|
11
|
+
if (hcaptchaEl) {
|
|
12
|
+
const sitekey = hcaptchaEl.getAttribute("data-sitekey") || hcaptchaEl.getAttribute("data-hcaptcha-sitekey") || "";
|
|
13
|
+
return { type: "hcaptcha", sitekey };
|
|
14
|
+
}
|
|
15
|
+
const turnstileEl = document.querySelector(".cf-turnstile");
|
|
16
|
+
if (turnstileEl) {
|
|
17
|
+
const sitekey = turnstileEl.getAttribute("data-sitekey") || "";
|
|
18
|
+
return { type: "turnstile", sitekey };
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
});
|
|
22
|
+
if (!captchaInfo?.sitekey) return null;
|
|
23
|
+
const token = provider === "capsolver" ? await solveWithCapSolver(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey) : await solveWith2Captcha(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey);
|
|
24
|
+
await page.evaluate((t, type) => {
|
|
25
|
+
if (type === "recaptcha") {
|
|
26
|
+
const el = document.querySelector("textarea.g-recaptcha-response");
|
|
27
|
+
if (el) {
|
|
28
|
+
el.value = t;
|
|
29
|
+
}
|
|
30
|
+
if (window.grecaptcha?.getResponse && document.querySelector("[data-callback]")) {
|
|
31
|
+
const cb = document.querySelector("[data-callback]")?.getAttribute("data-callback");
|
|
32
|
+
if (cb && window[cb]) window[cb](t);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (type === "hcaptcha") {
|
|
36
|
+
const el = document.querySelector('textarea[name="h-captcha-response"]');
|
|
37
|
+
if (el) {
|
|
38
|
+
el.value = t;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (type === "turnstile") {
|
|
42
|
+
const el = document.querySelector('[name="cf-turnstile-response"]');
|
|
43
|
+
if (el) {
|
|
44
|
+
el.value = t;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, token, captchaInfo.type);
|
|
48
|
+
return { token, provider };
|
|
72
49
|
}
|
|
73
50
|
async function solveWith2Captcha(type, sitekey, pageUrl, apiKey) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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');
|
|
51
|
+
const method = type === "hcaptcha" ? "hcaptcha" : type === "turnstile" ? "turnstile" : "userrecaptcha";
|
|
52
|
+
const submitRes = await fetch("http://2captcha.com/in.php", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
body: new URLSearchParams({
|
|
55
|
+
key: apiKey,
|
|
56
|
+
method,
|
|
57
|
+
googlekey: sitekey,
|
|
58
|
+
pageurl: pageUrl,
|
|
59
|
+
json: "1"
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
const submitData = await submitRes.json();
|
|
63
|
+
if (submitData.status !== 1) throw new Error(`2captcha submit failed: ${submitData.request}`);
|
|
64
|
+
const taskId = submitData.request;
|
|
65
|
+
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
66
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
67
|
+
const pollRes = await fetch(`http://2captcha.com/res.php?key=${apiKey}&action=get&id=${taskId}&json=1`);
|
|
68
|
+
const pollData = await pollRes.json();
|
|
69
|
+
if (pollData.status === 1) return pollData.request;
|
|
70
|
+
if (pollData.request !== "CAPCHA_NOT_READY") throw new Error(`2captcha error: ${pollData.request}`);
|
|
71
|
+
}
|
|
72
|
+
throw new Error("2captcha timed out after 2 minutes");
|
|
100
73
|
}
|
|
101
74
|
async function solveWithCapSolver(type, sitekey, pageUrl, apiKey) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
75
|
+
const taskType = type === "hcaptcha" ? "HCaptchaTaskProxyless" : type === "turnstile" ? "AntiTurnstileTaskProxyless" : "ReCaptchaV2TaskProxyless";
|
|
76
|
+
const createRes = await fetch("https://api.capsolver.com/createTask", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
clientKey: apiKey,
|
|
81
|
+
task: { type: taskType, websiteURL: pageUrl, websiteKey: sitekey }
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
const createData = await createRes.json();
|
|
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 })
|
|
112
93
|
});
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
94
|
+
const pollData = await pollRes.json();
|
|
95
|
+
if (pollData.errorId !== 0) throw new Error(`CapSolver error: ${pollData.errorDescription}`);
|
|
96
|
+
if (pollData.status === "ready") {
|
|
97
|
+
return pollData.solution?.gRecaptchaResponse ?? pollData.solution?.token ?? "";
|
|
130
98
|
}
|
|
131
|
-
|
|
99
|
+
}
|
|
100
|
+
throw new Error("CapSolver timed out after 2 minutes");
|
|
132
101
|
}
|
|
102
|
+
export {
|
|
103
|
+
solveCaptcha
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=captcha-solver.js.map
|