@itd-api/captcha 0.1.1 → 0.2.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/README.md +42 -9
- package/dist/index.cjs +32 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.js +32 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
## Установка
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
npm i @itd-api/captcha patchright
|
|
24
|
+
npm i @itd-api/captcha patchright@1.61.1
|
|
25
25
|
npx patchright install chromium
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -130,7 +130,7 @@ CDP), а не программной установкой значения, —
|
|
|
130
130
|
| `browser` | — | Готовый браузер. Тогда пакет его не запускает и не закрывает. |
|
|
131
131
|
| `launch` | — | Свой запуск браузера. Заменяет все параметры запуска. |
|
|
132
132
|
| `contextOptions` | `locale: 'ru-RU'`, окно 1280×800 | Настройки контекста. Заменяют стандартные целиком. |
|
|
133
|
-
| `logger` |
|
|
133
|
+
| `logger` | логгер клиента | Куда писать ход решения: объект вида `console`. `createCaptchaSolver` без него пишет в логгер `itd-api`. |
|
|
134
134
|
| `type` | — | Какую капчу проходить всегда. Только для `createCaptchaSolver`. |
|
|
135
135
|
| `field` | — | Поле тела запроса для закреплённого типа. Только вместе с `type`. |
|
|
136
136
|
|
|
@@ -179,19 +179,52 @@ createCaptchaSolver({
|
|
|
179
179
|
|
|
180
180
|
## Совместимость
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
Ниже — связки, на которых Cloudflare Turnstile выдал токен. Проверено 19.09.2026
|
|
183
|
+
с окном, если не указано иное.
|
|
184
|
+
|
|
185
|
+
| Драйвер | Версия и конфигурация | Браузер |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `patchright` | 1.59.4 · 1.60.2 · 1.61.1, штатные сборки | Chromium 147 · 148 · 149 соответственно |
|
|
188
|
+
| | 1.62.3 · 1.63.0, `executablePath` | Chromium 149 |
|
|
189
|
+
| | 1.63.0, `executablePath` | Chromium 148 |
|
|
190
|
+
| `playwright` | 1.60.0 · 1.61.0, штатные сборки | Chromium 148 · 149 соответственно |
|
|
191
|
+
| | 1.62.1 · 1.63.0, `executablePath` | Chromium 149 |
|
|
192
|
+
| `playwright-core` | 1.62.1 · 1.63.0, `executablePath` | Chromium 149 |
|
|
193
|
+
| `camoufox-js` | 0.11.5 с окном · 0.12.0 с окном и headless, `contextOptions: {}` | Camoufox 152.0.4-beta.29 |
|
|
194
|
+
| `rebrowser-playwright` | 1.48.2 · 1.49.1, `executablePath` | Chromium 149 |
|
|
195
|
+
|
|
196
|
+
<details>
|
|
197
|
+
<summary>Комбинации, не прошедшие проверку</summary>
|
|
198
|
+
|
|
199
|
+
| Драйвер | Версия и конфигурация | Браузер | Результат |
|
|
200
|
+
|---|---|---|---|
|
|
201
|
+
| `patchright` | 1.62.3, штатная сборка | Chromium 151 | таймаут |
|
|
202
|
+
| | 1.63.0, штатная сборка | Chrome for Testing 153 | таймаут |
|
|
203
|
+
| | 1.63.0, `executablePath` | Google Chrome 152 | таймаут |
|
|
204
|
+
| `playwright` | 1.62.1, штатная сборка | Chromium 151 | `600010`, таймаут |
|
|
205
|
+
| | 1.63.0, штатная сборка | Chrome for Testing 153 | `600010`, таймаут |
|
|
206
|
+
| | 1.63.0, `executablePath` | Google Chrome 152 | `600010`, таймаут |
|
|
207
|
+
| `rebrowser-playwright` | 1.52.0, `executablePath` | Chromium 149 | `600010`, таймаут |
|
|
208
|
+
| `playwright-extra` + `puppeteer-extra-plugin-stealth` | 4.3.6 + 2.11.2, `playwright` 1.62.1 · 1.63.0, `executablePath` | Chromium 149 | `600010`, таймаут |
|
|
209
|
+
|
|
210
|
+
</details>
|
|
211
|
+
|
|
212
|
+
Собственная капча ИТД менее требовательна: ее можно получить любым подходящим драйвером и версией браузера.
|
|
213
|
+
|
|
214
|
+
Таблица проверяется скриптом `scripts/drivers.mjs` из исходников пакета:
|
|
184
215
|
|
|
185
216
|
```sh
|
|
186
|
-
npm i --no-save patchright playwright camoufox-js
|
|
217
|
+
npm i --no-save patchright playwright camoufox-js rebrowser-playwright \
|
|
218
|
+
playwright-extra puppeteer-extra-plugin-stealth
|
|
187
219
|
node scripts/drivers.mjs # Turnstile
|
|
188
220
|
node scripts/drivers.mjs --itd # капча ИТД
|
|
189
221
|
```
|
|
190
222
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
223
|
+
Штатная сборка приезжает вместе с версией драйвера. Другую можно поставить командой
|
|
224
|
+
`npx @puppeteer/browsers install chrome@149.0.7827.55` и передать путь в `executablePath`.
|
|
225
|
+
Версия драйвера сама по себе не гарантирует результат: например, `patchright` и
|
|
226
|
+
`playwright` 1.63.0 не проходят Turnstile на штатном Chrome for Testing 153, но проходят
|
|
227
|
+
на Chromium 149 через `executablePath`.
|
|
195
228
|
|
|
196
229
|
## Лицензия
|
|
197
230
|
|
package/dist/index.cjs
CHANGED
|
@@ -54,9 +54,8 @@ const DEFAULT_ARGS = ["--disable-dev-shm-usage", "--disable-blink-features=Autom
|
|
|
54
54
|
/**
|
|
55
55
|
* Драйверы в порядке предпочтения.
|
|
56
56
|
*
|
|
57
|
-
* `patchright` впереди намеренно:
|
|
58
|
-
*
|
|
59
|
-
* ничего не меняется — берётся он.
|
|
57
|
+
* `patchright` впереди намеренно: проверенная связка `patchright@1.61.1` со штатным
|
|
58
|
+
* Chromium 149 проходит виджет. Если стоит только `playwright`, берётся он.
|
|
60
59
|
*/
|
|
61
60
|
const DRIVERS = [
|
|
62
61
|
"patchright",
|
|
@@ -101,7 +100,7 @@ async function loadDriver(requested) {
|
|
|
101
100
|
} catch (error) {
|
|
102
101
|
if (!isModuleNotFound(error)) throw error;
|
|
103
102
|
}
|
|
104
|
-
throw new CaptchaError(CaptchaFailure.DriverMissing, requested ? `Драйвер ${requested} не установлен.` : "Не найден драйвер браузера. Установите
|
|
103
|
+
throw new CaptchaError(CaptchaFailure.DriverMissing, requested ? `Драйвер ${requested} не установлен.` : "Не найден драйвер браузера. Установите проверенную связку командой: npm i patchright@1.61.1 && npx patchright install chromium. Либо передайте свой запуск браузера через параметр launch.");
|
|
105
104
|
}
|
|
106
105
|
/** Собирает параметры запуска, не поднимая браузер. @internal */
|
|
107
106
|
function resolveLaunchOptions(options, driver = "") {
|
|
@@ -140,6 +139,12 @@ async function launchBrowser(options) {
|
|
|
140
139
|
//#region src/options.ts
|
|
141
140
|
/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */
|
|
142
141
|
const DEFAULT_ORIGIN = "https://xn--d1ah4a.com";
|
|
142
|
+
const LOGGER_METHODS = [
|
|
143
|
+
"debug",
|
|
144
|
+
"info",
|
|
145
|
+
"warn",
|
|
146
|
+
"error"
|
|
147
|
+
];
|
|
143
148
|
/**
|
|
144
149
|
* Приводит адрес к корню и проверяет его пригодность.
|
|
145
150
|
*
|
|
@@ -172,7 +177,10 @@ function resolveOptions(options) {
|
|
|
172
177
|
if (theme !== "auto" && theme !== "light" && theme !== "dark") throw new TypeError("theme должен быть 'auto', 'light' или 'dark'");
|
|
173
178
|
for (const [name, value] of [["headless", options.headless], ["disableSandbox", options.disableSandbox]]) if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`${name} должен быть boolean`);
|
|
174
179
|
for (const [name, value] of [["driver", options.driver], ["channel", options.channel]]) if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new TypeError(`${name} должен быть непустой строкой`);
|
|
175
|
-
if (options.logger !== void 0
|
|
180
|
+
if (options.logger !== void 0) {
|
|
181
|
+
if (typeof options.logger !== "object" || options.logger === null) throw new TypeError("logger должен быть объектом с методами debug, info, warn и error");
|
|
182
|
+
for (const method of LOGGER_METHODS) if (typeof options.logger[method] !== "function") throw new TypeError(`logger.${method} должен быть функцией`);
|
|
183
|
+
}
|
|
176
184
|
if (options.contextOptions !== void 0 && (typeof options.contextOptions !== "object" || options.contextOptions === null || Array.isArray(options.contextOptions))) throw new TypeError("contextOptions должен быть объектом");
|
|
177
185
|
if (options.launch !== void 0 && typeof options.launch !== "function") throw new TypeError("launch должен быть функцией");
|
|
178
186
|
if (options.args !== void 0 && (!Array.isArray(options.args) || options.args.some((argument) => typeof argument !== "string"))) throw new TypeError("args должен быть массивом строк");
|
|
@@ -521,7 +529,8 @@ async function clickCheckbox(page, offsetX) {
|
|
|
521
529
|
}
|
|
522
530
|
/** Ждёт токен, периодически подталкивая виджет кликом. */
|
|
523
531
|
async function waitForToken(page, handler, options, browser) {
|
|
524
|
-
const
|
|
532
|
+
const startedAt = Date.now();
|
|
533
|
+
const deadline = startedAt + options.timeout;
|
|
525
534
|
await page.waitForSelector(handler.widgetReadySelector, {
|
|
526
535
|
timeout: Math.min(WIDGET_APPEAR_TIMEOUT, options.timeout),
|
|
527
536
|
state: "attached"
|
|
@@ -531,7 +540,7 @@ async function waitForToken(page, handler, options, browser) {
|
|
|
531
540
|
while (Date.now() < deadline) {
|
|
532
541
|
const state = await handler.readState(page);
|
|
533
542
|
if (state.token) {
|
|
534
|
-
options.logger?.(
|
|
543
|
+
options.logger?.info(`токен ${handler.label} получен за ${Date.now() - startedAt} мс`);
|
|
535
544
|
return state.token;
|
|
536
545
|
}
|
|
537
546
|
if (state.error && state.error !== lastError) {
|
|
@@ -540,10 +549,10 @@ async function waitForToken(page, handler, options, browser) {
|
|
|
540
549
|
widgetCode: state.error
|
|
541
550
|
});
|
|
542
551
|
lastError = state.error;
|
|
543
|
-
options.logger?.(`виджет сообщил об ошибке ${state.error}, ждём повтора`);
|
|
552
|
+
options.logger?.debug(`виджет ${handler.label} сообщил об ошибке ${state.error}, ждём повтора`);
|
|
544
553
|
}
|
|
545
554
|
if (Date.now() >= nextClickAt && await clickCheckbox(page, handler.checkboxOffsetX)) {
|
|
546
|
-
options.logger?.(
|
|
555
|
+
options.logger?.debug(`клик по чекбоксу ${handler.label}`);
|
|
547
556
|
nextClickAt = Date.now() + CLICK_INTERVAL;
|
|
548
557
|
}
|
|
549
558
|
await sleep(POLL_INTERVAL);
|
|
@@ -609,16 +618,17 @@ function toCaptchaError(error, handler) {
|
|
|
609
618
|
* @throws {CaptchaError} если токен получить не удалось
|
|
610
619
|
*/
|
|
611
620
|
async function runSolver(handler, options) {
|
|
612
|
-
const browser = options.browser ?? await launchBrowser(options);
|
|
613
621
|
const owned = options.browser === void 0;
|
|
622
|
+
if (owned) options.logger?.info(`запуск браузера для ${handler.label}`);
|
|
623
|
+
const browser = options.browser ?? await launchBrowser(options);
|
|
614
624
|
try {
|
|
615
625
|
for (let attempt = 1; attempt <= options.attempts; attempt++) try {
|
|
616
|
-
options.logger?.(`попытка ${attempt} из ${options.attempts}`);
|
|
626
|
+
options.logger?.debug(`попытка ${attempt} из ${options.attempts} для ${handler.label}`);
|
|
617
627
|
return await solveOnce(browser, handler, options);
|
|
618
628
|
} catch (raw) {
|
|
619
629
|
const error = toCaptchaError(raw, handler);
|
|
620
630
|
if (isPermanent(error) || attempt === options.attempts) throw error;
|
|
621
|
-
options.logger?.(`попытка ${attempt} не
|
|
631
|
+
options.logger?.warn(`попытка ${attempt} из ${options.attempts} для ${handler.label} не удалась (${error instanceof Error ? error.message : String(error)}), повтор`);
|
|
622
632
|
}
|
|
623
633
|
throw new CaptchaError(CaptchaFailure.Timeout, `Токен ${handler.label} получить не удалось`, { type: handler.type });
|
|
624
634
|
} finally {
|
|
@@ -645,6 +655,14 @@ async function runSolver(handler, options) {
|
|
|
645
655
|
async function solveCaptcha(target, options = {}) {
|
|
646
656
|
return runSolver(resolveHandler(target), resolveOptions(options));
|
|
647
657
|
}
|
|
658
|
+
/** Без своего логгера источник пишет ход решения в логгер клиента. */
|
|
659
|
+
function withClientLogger(options, context) {
|
|
660
|
+
if (options.logger || !context?.logger) return options;
|
|
661
|
+
return {
|
|
662
|
+
...options,
|
|
663
|
+
logger: context.logger
|
|
664
|
+
};
|
|
665
|
+
}
|
|
648
666
|
/**
|
|
649
667
|
* Собирает источник токена капчи для клиента `itd-api`.
|
|
650
668
|
*
|
|
@@ -669,13 +687,13 @@ async function solveCaptcha(target, options = {}) {
|
|
|
669
687
|
*/
|
|
670
688
|
function createCaptchaSolver(options = {}) {
|
|
671
689
|
const { type, field, ...solveOptions } = options;
|
|
672
|
-
if (type === void 0) return { getToken: (requested) => solveCaptcha(requested, solveOptions) };
|
|
690
|
+
if (type === void 0) return { getToken: (requested, context) => solveCaptcha(requested, withClientLogger(solveOptions, context)) };
|
|
673
691
|
const handler = resolveHandler(type);
|
|
674
692
|
const resolved = resolveOptions(solveOptions);
|
|
675
693
|
return {
|
|
676
694
|
type: handler.type,
|
|
677
695
|
...field === void 0 ? {} : { field },
|
|
678
|
-
getToken: () => runSolver(handler, resolved)
|
|
696
|
+
getToken: (_requested, context) => runSolver(handler, withClientLogger(resolved, context))
|
|
679
697
|
};
|
|
680
698
|
}
|
|
681
699
|
//#endregion
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["WIDTH","HEIGHT","CHECKBOX_OFFSET_X","readState","CaptchaTypeValues"],"sources":["../src/errors.ts","../src/launch.ts","../src/options.ts","../src/html.ts","../src/types.ts","../src/providers/itd.ts","../src/providers/turnstile.ts","../src/providers/registry.ts","../src/runner.ts","../src/solve.ts"],"sourcesContent":["import type { CaptchaType } from './types.js';\n\n/** Почему не удалось получить токен. */\nexport const CaptchaFailure = Object.freeze({\n /** Драйвер браузера не установлен. */\n DriverMissing: 'driver-missing',\n /** Браузер не запустился: нет исполняемого файла, нет дисплея, отказ песочницы. */\n LaunchFailed: 'launch-failed',\n /** Браузер или страница закрылись, пока решался виджет. */\n BrowserClosed: 'browser-closed',\n /** Виджет не отдал токен за отведённое время. */\n Timeout: 'timeout',\n /** Сам виджет сообщил об ошибке — код лежит в `widgetCode`. */\n WidgetError: 'widget-error',\n} as const);\nexport type CaptchaFailure = (typeof CaptchaFailure)[keyof typeof CaptchaFailure];\n\n/**\n * Ошибка получения токена капчи.\n *\n * Причина в {@link CaptchaError.reason} определяет, что с ошибкой делать: отсутствие драйвера\n * чинится установкой, таймаут — повтором, а ошибка виджета повтором не лечится. Поле\n * {@link CaptchaError.type} говорит, чей именно виджет решался, — оно есть не у всех ошибок\n * (запуск браузера происходит до выбора виджета).\n */\nexport class CaptchaError extends Error {\n readonly reason: CaptchaFailure;\n /** Какой провайдер решался в момент ошибки. */\n readonly type: CaptchaType | undefined;\n /**\n * Код ошибки виджета, если он его сообщил.\n *\n * Для Cloudflare самые частые: `110200` — домен не разрешён для этого ключа, `300***`\n * и `600***` — внутренние сбои виджета, лечатся повтором.\n */\n readonly widgetCode: string | undefined;\n\n constructor(\n reason: CaptchaFailure,\n message: string,\n options: { type?: CaptchaType; widgetCode?: string } = {},\n ) {\n super(message);\n this.name = 'CaptchaError';\n this.reason = reason;\n this.type = options.type;\n this.widgetCode = options.widgetCode;\n }\n}\n","import type { Browser, DriverModule, LaunchOptions } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\n\n/**\n * Аргументы запуска Chromium.\n *\n * Подмены `User-Agent` здесь нет намеренно: заявленная версия браузера расходилась бы\n * с реальным движком, а такое расхождение само по себе служит признаком автоматизации.\n * Остаются безопасные общие флаги. Отключение sandbox вынесено в явную настройку:\n * удалённый код виджета исполняется в браузере, и ослаблять его изоляцию по умолчанию нельзя.\n */\nconst DEFAULT_ARGS = ['--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled'];\n\n/**\n * Драйверы в порядке предпочтения.\n *\n * `patchright` впереди намеренно: он ставит собственную сборку Chromium, и та проходит\n * виджет там, где сборка из свежего Playwright уже нет. Если стоит только `playwright`,\n * ничего не меняется — берётся он.\n */\nconst DRIVERS = ['patchright', 'playwright', 'playwright-core'];\n\n/**\n * Как выглядит имя пакета драйвера.\n *\n * Имя приходит извне и уезжает в динамический импорт, поэтому путями и относительными\n * адресами быть не должно — только имя пакета.\n */\nconst DRIVER_NAME = /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*$/i;\n\n/**\n * Драйверы, которые сами приводят аргументы запуска в порядок.\n *\n * Им ничего не подмешивается: набор флагов у них выверен, а лишний флаг — такой же след,\n * как и недостающий.\n */\nconst SELF_TUNED_DRIVERS = new Set(['patchright']);\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | null)?.code;\n return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';\n}\n\n/**\n * Подключает драйвер браузера.\n *\n * Импорт динамический: драйвер объявлен необязательной одноранговой зависимостью,\n * поэтому его может не быть вовсе. Названный явно берётся один, без перебора: молча\n * подставить вместо него другой значило бы проверить не то, что просили.\n */\nasync function loadDriver(requested?: string): Promise<{ driver: string; module: DriverModule }> {\n const wanted = requested ? [requested] : DRIVERS;\n\n for (const name of wanted) {\n try {\n return { driver: name, module: (await import(/* @vite-ignore */ name)) as DriverModule };\n } catch (error) {\n // Установленный драйвер, упавший при загрузке, — не то же самое, что отсутствующий.\n // Его ошибку нужно показать как есть, а не подменять советом установить пакет.\n if (!isModuleNotFound(error)) throw error;\n }\n }\n\n throw new CaptchaError(\n CaptchaFailure.DriverMissing,\n requested\n ? `Драйвер ${requested} не установлен.`\n : 'Не найден драйвер браузера. Установите его командой: npm i patchright && npx patchright install chromium. ' +\n 'Либо передайте свой запуск браузера через параметр launch.',\n );\n}\n\n/** Настройки запуска браузера. */\nexport interface BrowserOptions {\n /**\n * Запускать ли браузер без окна. По умолчанию `false`.\n *\n * В безоконном режиме виджет проходится заметно хуже: признаки такого режима видны\n * странице. На сервере поднимите виртуальный дисплей (`xvfb-run -a node bot.js`).\n */\n headless?: boolean | undefined;\n /**\n * Какой драйвер брать вместо перебора `patchright` → `playwright` → `playwright-core`.\n *\n * Нужен, когда установлено несколько и важно, какой именно поднимется. Названный\n * драйвер берётся один: не найден — ошибка, а не молчаливая подмена соседним.\n */\n driver?: string | undefined;\n /** Путь к исполняемому файлу браузера, если он лежит не там, где его ищет драйвер. */\n executablePath?: string | undefined;\n /** Канал браузера, например `chrome` или `msedge`, — вместо сборки из комплекта драйвера. */\n channel?: string | undefined;\n /** Дополнительные аргументы командной строки — добавляются к стандартным. */\n args?: readonly string[] | undefined;\n /**\n * Отключить sandbox Chromium. По умолчанию `false`.\n *\n * Используйте только внутри изолированного контейнера, где sandbox невозможно настроить:\n * страница исполняет удалённый код виджета, и без изоляции браузера его компрометация\n * получает больше доступа к процессу и системе.\n */\n disableSandbox?: boolean | undefined;\n /** Прокси для браузера. */\n proxy?: { server: string; username?: string; password?: string } | undefined;\n /**\n * Свой запуск браузера. Заменяет все остальные параметры запуска.\n *\n * Нужен, чтобы подставить другой драйвер или подключиться к уже работающему браузеру.\n *\n * @example\n * ```ts\n * launch: async () => {\n * const { chromium } = await import('patchright');\n * return chromium.launch({ headless: false });\n * };\n * ```\n */\n launch?: (() => Promise<Browser>) | undefined;\n}\n\n/** Собирает параметры запуска, не поднимая браузер. @internal */\nexport function resolveLaunchOptions(options: BrowserOptions, driver = ''): LaunchOptions {\n return {\n headless: options.headless ?? false,\n args: [\n ...(SELF_TUNED_DRIVERS.has(driver) ? [] : DEFAULT_ARGS),\n ...(options.disableSandbox ? ['--no-sandbox'] : []),\n ...(options.args ?? []),\n ],\n ...(options.executablePath ? { executablePath: options.executablePath } : {}),\n ...(options.channel ? { channel: options.channel } : {}),\n ...(options.proxy ? { proxy: options.proxy } : {}),\n };\n}\n\n/**\n * Поднимает браузер по настройкам.\n *\n * Тем же путём, что и солверы: те же флаги, тот же порядок драйверов. Пригодится, чтобы\n * поднять браузер один раз на несколько токенов и передать его в `browser`, — и чтобы\n * проверять связки драйвера и сборки ровно в том виде, в каком их поднимает пакет.\n */\nexport async function launchBrowser(options: BrowserOptions): Promise<Browser> {\n if (options.launch) return options.launch();\n\n if (options.driver !== undefined && !DRIVER_NAME.test(options.driver)) {\n throw new TypeError(`driver должен быть именем пакета, получено: ${options.driver}`);\n }\n\n const {\n driver,\n module: { chromium },\n } = await loadDriver(options.driver);\n const launchOptions = resolveLaunchOptions(options, driver);\n\n try {\n return await chromium.launch(launchOptions);\n } catch (error) {\n const hint =\n launchOptions.headless === false\n ? ' Если это сервер без графической оболочки, запустите процесс через xvfb-run -a.'\n : '';\n\n throw new CaptchaError(\n CaptchaFailure.LaunchFailed,\n `Не удалось запустить браузер: ${error instanceof Error ? error.message : String(error)}.${hint}`,\n );\n }\n}\n","import type { Browser, NewContextOptions } from './driver.js';\nimport type { BrowserOptions } from './launch.js';\nimport type { Theme } from './types.js';\n\n/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */\nexport const DEFAULT_ORIGIN = 'https://xn--d1ah4a.com';\n\n/**\n * Настройки получения токена, общие для любого виджета.\n *\n * Ключ, назначение токена и адрес виджета задаются при создании провайдера:\n * `solveCaptcha(itdCaptcha({ action: 'register' }))`.\n */\nexport interface SolveOptions extends BrowserOptions {\n /** Сайт, чей виджет решается. По умолчанию {@link DEFAULT_ORIGIN}. */\n origin?: string | undefined;\n /** Оформление виджета. По умолчанию `auto`. */\n theme?: Theme | undefined;\n /** Сколько ждать токен, мс. По умолчанию 60000. */\n timeout?: number | undefined;\n /** Сколько попыток делать при неудаче. По умолчанию 2. */\n attempts?: number | undefined;\n /**\n * Готовый браузер. Тогда пакет его не запускает и не закрывает.\n *\n * Пригодится, если браузер уже поднят для чего-то ещё.\n */\n browser?: Browser | undefined;\n /**\n * Настройки контекста. Заменяют стандартные целиком, а не дополняют их.\n *\n * По умолчанию `{ locale: 'ru-RU', viewport: { width: 1280, height: 800 } }`. Драйверам,\n * которые собирают отпечаток браузера сами, навязанный `locale` мешает — им передайте `{}`.\n */\n contextOptions?: NewContextOptions | undefined;\n /** Куда писать ход решения. Например `console.debug`. */\n logger?: ((message: string) => void) | undefined;\n}\n\n/** Настройки после подстановки умолчаний. @internal */\nexport interface ResolvedOptions extends SolveOptions {\n origin: string;\n theme: Theme;\n timeout: number;\n attempts: number;\n}\n\n/**\n * Приводит адрес к корню и проверяет его пригодность.\n *\n * Виджет привязан к домену, а не к пути: адрес приводится к `URL.origin`, чтобы перехват навигации\n * совпал с ним ровно один раз.\n */\nexport function resolveOrigin(origin: string, field: string): string {\n let parsed: URL;\n try {\n parsed = new URL(origin);\n } catch {\n throw new TypeError(`${field} должен быть абсолютным URL, получено: ${origin}`);\n }\n\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new TypeError(`${field} должен быть http или https, получено: ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new TypeError(\n `${field} не должен содержать логин, пароль, параметры запроса или фрагмент`,\n );\n }\n\n return parsed.origin;\n}\n\n/**\n * Проверяет общие настройки и подставляет умолчания.\n *\n * @throws {TypeError} при некорректных значениях\n */\nexport function resolveOptions(options: SolveOptions): ResolvedOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) {\n throw new TypeError('options должен быть объектом');\n }\n\n const timeout = options.timeout ?? 60_000;\n const attempts = options.attempts ?? 2;\n const theme = options.theme ?? 'auto';\n\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new TypeError(`timeout должен быть положительным числом, получено: ${timeout}`);\n }\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new TypeError(`attempts должен быть целым числом от 1, получено: ${attempts}`);\n }\n if (theme !== 'auto' && theme !== 'light' && theme !== 'dark') {\n throw new TypeError(\"theme должен быть 'auto', 'light' или 'dark'\");\n }\n for (const [name, value] of [\n ['headless', options.headless],\n ['disableSandbox', options.disableSandbox],\n ] as const) {\n if (value !== undefined && typeof value !== 'boolean') {\n throw new TypeError(`${name} должен быть boolean`);\n }\n }\n for (const [name, value] of [\n ['driver', options.driver],\n ['channel', options.channel],\n ] as const) {\n if (value !== undefined && (typeof value !== 'string' || value.trim() === '')) {\n throw new TypeError(`${name} должен быть непустой строкой`);\n }\n }\n if (options.logger !== undefined && typeof options.logger !== 'function') {\n throw new TypeError('logger должен быть функцией');\n }\n if (\n options.contextOptions !== undefined &&\n (typeof options.contextOptions !== 'object' ||\n options.contextOptions === null ||\n Array.isArray(options.contextOptions))\n ) {\n throw new TypeError('contextOptions должен быть объектом');\n }\n if (options.launch !== undefined && typeof options.launch !== 'function') {\n throw new TypeError('launch должен быть функцией');\n }\n if (\n options.args !== undefined &&\n (!Array.isArray(options.args) || options.args.some((argument) => typeof argument !== 'string'))\n ) {\n throw new TypeError('args должен быть массивом строк');\n }\n\n return {\n ...options,\n origin: resolveOrigin(options.origin ?? DEFAULT_ORIGIN, 'origin'),\n theme,\n timeout,\n attempts,\n };\n}\n\n/** Требует непустую строку: общая проверка для настроек провайдеров. @internal */\nexport function requireText(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new TypeError(`${field} должен быть непустой строкой`);\n }\n return value;\n}\n","/**\n * Сборка страницы, на которой живёт виджет.\n *\n * Страница отдаётся вместо настоящей — по адресу целевого сайта, через перехват навигации.\n * Благодаря этому `document.location.origin` для виджета настоящий, и привязка ключа\n * к домену не нарушается, а форма входа и пароль в браузере не участвуют.\n *\n * Каркас общий для всех провайдеров: контейнер `#widget` известного размера, от которого\n * общий цикл отсчитывает координаты клика. Различия провайдеров — в `head` и `body`.\n */\n\n/** Отступ контейнера от края окна, px. Виджету нужно место, чтобы раскрыть карточку. */\nconst WIDGET_MARGIN = 40;\n\n/**\n * Готовит значение к вставке в инлайновый скрипт или атрибут.\n *\n * Одного `JSON.stringify` мало: кавычки он экранирует, а `</script>` — нет, и такая\n * последовательность закрывает тег независимо от того, внутри строки она или нет.\n * Экранированный `<` разбирается движком как обычный символ, но парсер HTML его уже не видит.\n */\nexport function embed(value: string): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n/** Из чего собирается страница одного виджета. */\nexport interface PageParts {\n /** Заголовок вкладки. На проверку не влияет, но пустой заголовок выглядит подозрительно. */\n title: string;\n /** Размеры контейнера `#widget`, px. Столько же виджет занимает на самом сайте. */\n width: number;\n height: number;\n /** Дополнительные правила для `#widget` — например, если контейнер сам является iframe. */\n widgetStyle?: string | undefined;\n /** Содержимое `head`: скрипты и обработчики провайдера. */\n head?: string | undefined;\n /** Содержимое `body`: сам контейнер `#widget` и поля для результата. */\n body: string;\n}\n\n/** Собирает страницу с одним виджетом. */\nexport function buildWidgetPage(parts: PageParts): string {\n return `<!doctype html>\n<html lang=\"ru\">\n<head>\n<meta charset=\"utf-8\">\n<title>${parts.title}</title>\n<style>\n html, body { margin: 0; padding: 0; background: #fff; }\n #widget {\n width: ${parts.width}px;\n height: ${parts.height}px;\n margin: ${WIDGET_MARGIN}px;${parts.widgetStyle ?? ''}\n }\n</style>\n${parts.head ?? ''}\n</head>\n<body>\n${parts.body}\n</body>\n</html>`;\n}\n","/**\n * Открытый строковый enum.\n *\n * Даёт автодополнение известных значений, но не ломается, если появится новое.\n */\nexport type Loose<T extends string> = T | (string & {});\n\n/** Провайдер капчи — теми же именами, какими его называет сервер итд.com. */\nexport const CaptchaType = Object.freeze({\n /** Собственная капча ИТД: карточка «Я не робот» в iframe с `captcha.итд.com`. */\n Itd: 'itd',\n /** Cloudflare Turnstile. */\n Cloudflare: 'cloudflare',\n} as const);\nexport type CaptchaType = Loose<(typeof CaptchaType)[keyof typeof CaptchaType]>;\n\n/** Оформление виджета. */\nexport type Theme = 'auto' | 'light' | 'dark';\n\n/** Результат виджета, снятый со страницы через `page.evaluate`. */\nexport interface WidgetState {\n token: string | null;\n error: string | null;\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText, resolveOrigin } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/** Базовый адрес виджета собственной капчи ИТД. Домен в punycode: `captcha.итд.com`. */\nexport const DEFAULT_CAPTCHA_ORIGIN = 'https://captcha.xn--d1ah4a.com';\n\n/**\n * Публичный ключ виджета собственной капчи ИТД.\n *\n * Как и ключ Turnstile, он публичный и привязан к домену итд.com: виджет отдаёт его браузеру\n * каждому посетителю.\n */\nexport const ITD_CAPTCHA_SITE_KEY = 'sk_44d64cf7bf8bc8377f5b';\n\n/** Назначение токена по умолчанию. Сервер сверяет его с операцией. */\nconst DEFAULT_ACTION = 'login';\n\n/** Размеры карточки, px. Их же виджет сообщает сайту через `postMessage`. */\nconst WIDTH = 300;\nconst HEIGHT = 74;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 34;\n\n/**\n * Собирает страницу с одним виджетом собственной капчи ИТД.\n *\n * Виджет — iframe чужого происхождения (`captcha.итд.com`), и наружу он отдаёт токен\n * единственным способом: `postMessage` в родителя. Эта страница ровно так его и слушает —\n * как сам сайт.\n *\n * Слушатель кладёт результат в скрытые поля, а не в переменную `window`: стелс-драйверы вроде\n * `patchright` выполняют `page.evaluate` в изолированном мире, откуда переменные страницы\n * не видны, зато DOM общий.\n *\n * `action` задаёт назначение токена (`login`, `register`, `password_reset`) — сервер сверяет\n * его с операцией, поэтому он не выдумывается, а приходит из настроек обработчика.\n */\nexport function buildItdCaptchaPage(input: {\n sitekey: string;\n theme: Theme;\n action: string;\n captchaOrigin: string;\n}): string {\n const origin = embed(input.captchaOrigin);\n const src = embed(\n `${input.captchaOrigin}/widget.html` +\n `?sitekey=${encodeURIComponent(input.sitekey)}` +\n `&theme=${encodeURIComponent(input.theme)}` +\n `&action=${encodeURIComponent(input.action)}`,\n );\n\n return buildWidgetPage({\n title: 'Проверка',\n width: WIDTH,\n height: HEIGHT,\n widgetStyle: `\n border: 0;\n display: block;`,\n head: `<script>\n window.addEventListener('message', function (event) {\n if (event.origin !== ${origin}) return;\n var data;\n try {\n data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;\n } catch (e) {\n return;\n }\n if (!data) return;\n var token = document.getElementById('itd-token');\n var error = document.getElementById('itd-error');\n if (data.type === 'token' && data.token) token.value = data.token;\n else if (data.type === 'expired') token.value = '';\n else if (data.type === 'error') error.value = String(data.code || 'error');\n });\n</script>`,\n body: `<input type=\"hidden\" id=\"itd-token\">\n<input type=\"hidden\" id=\"itd-error\">\n<iframe id=\"widget\" src=${src} title=\"Проверка\"></iframe>`,\n });\n}\n\n/**\n * Читает результат виджета капчи ИТД.\n *\n * Токен приходит в родителя через `postMessage`, и слушатель страницы кладёт его в скрытое\n * поле. Читаем из DOM, а не из `window`: стелс-драйверы выполняют `evaluate` в изолированном\n * мире, где переменных страницы нет, а DOM общий.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const token = document.querySelector('#itd-token') as { value?: string } | null;\n const error = document.querySelector('#itd-error') as { value?: string } | null;\n return {\n token: token?.value || null,\n error: error?.value || null,\n };\n });\n}\n\n/** Настройки виджета собственной капчи ИТД. */\nexport interface ItdCaptchaOptions {\n /** Публичный ключ виджета. По умолчанию {@link ITD_CAPTCHA_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n /**\n * Назначение токена: `login`, `register`, `password_reset`. По умолчанию `login`.\n *\n * Сервер сверяет назначение с операцией, поэтому для регистрации или сброса пароля нужен\n * токен с соответствующим `action`.\n */\n action?: string | undefined;\n /** Базовый адрес виджета. По умолчанию {@link DEFAULT_CAPTCHA_ORIGIN}. */\n captchaOrigin?: string | undefined;\n}\n\n/**\n * Создаёт обработчик собственной капчи ИТД.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function itdCaptcha(options: ItdCaptchaOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? ITD_CAPTCHA_SITE_KEY, 'sitekey');\n const action = requireText(options.action ?? DEFAULT_ACTION, 'action');\n const captchaOrigin = resolveOrigin(\n options.captchaOrigin ?? DEFAULT_CAPTCHA_ORIGIN,\n 'captchaOrigin',\n );\n\n return {\n type: CaptchaType.Itd,\n label: 'капчи ИТД',\n buildPage: ({ theme }) => buildItdCaptchaPage({ sitekey, theme, action, captchaOrigin }),\n widgetReadySelector: '#widget',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n // Виджет ИТД шлёт лишь общий код ошибки и просит повторить: постоянных кодов у него нет.\n isPermanentWidgetError: () => false,\n };\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/**\n * Публичный ключ виджета Cloudflare Turnstile на итд.com.\n *\n * Ключ публичный — виджет отдаёт его браузеру каждому посетителю — и привязан к домену\n * итд.com, поэтому больше ни для чего не годится. Держим его прямо в пакете: это его данные,\n * а не пользовательская настройка. Тот же ключ экспортирует `itd-api`.\n */\nexport const TURNSTILE_SITE_KEY = '0x4AAAAAACHhxczw6fJGwPBg';\n\n/** Размеры контейнера виджета, px. Столько же он занимает на самом сайте. */\nconst WIDTH = 300;\nconst HEIGHT = 65;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 30;\n\n/** Коды Cloudflare вида `110***` означают, что ключ не разрешён для домена. */\nconst DOMAIN_ERROR_PREFIX = '110';\n\n/**\n * Собирает страницу с одним виджетом Cloudflare Turnstile.\n *\n * Виджет создаётся так же, как это делает сам сайт, — скриптом с `?onload=` и явным\n * `turnstile.render` с одним лишь `sitekey`. Ни `action`, ни `cdata` сайт не передаёт,\n * поэтому их не передаёт и эта страница: лишний параметр попал бы в ответ `siteverify`\n * и мог бы разойтись с тем, что ожидает сервер.\n */\nexport function buildTurnstilePage(sitekey: string, theme: Theme): string {\n const key = embed(sitekey);\n const widgetTheme = embed(theme);\n\n return buildWidgetPage({\n title: 'Turnstile',\n width: WIDTH,\n height: HEIGHT,\n head: `<script>\n window.__itdToken = null;\n window.__itdError = null;\n window.onTurnstileLoad = function () {\n var reset = function () {\n window.__itdToken = null;\n if (widgetId !== undefined) window.turnstile.reset(widgetId);\n };\n var widgetId = window.turnstile.render('#widget', {\n sitekey: ${key},\n theme: ${widgetTheme},\n callback: function (token) { window.__itdToken = token; },\n 'error-callback': function (code) { window.__itdError = String(code || 'unknown'); return true; },\n 'timeout-callback': reset,\n 'expired-callback': reset\n });\n };\n</script>\n<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad\" async defer></script>`,\n body: '<div id=\"widget\"></div>',\n });\n}\n\n/**\n * Читает результат виджета Turnstile.\n *\n * Источников два. Скрытое поле `cf-turnstile-response` виджет заполняет всегда, а вот\n * `callback` срабатывает не в каждом сценарии: пройдясь сам, виджет успевает убрать iframe,\n * и обработчика можно не дождаться. Поле при этом остаётся заполненным, и лежит оно\n * в нашей странице, а не внутри iframe чужого происхождения, поэтому доступно.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const field = document.querySelector('input[name=\"cf-turnstile-response\"]') as {\n value?: string;\n } | null;\n const scope = window as unknown as { __itdToken?: string | null; __itdError?: string | null };\n\n // Пустая строка в поле означает «ещё не решено», поэтому проверка на истинность.\n return {\n token: scope.__itdToken || field?.value || null,\n error: scope.__itdError ?? null,\n };\n });\n}\n\n/** Настройки виджета Cloudflare Turnstile. */\nexport interface TurnstileOptions {\n /** Публичный ключ виджета. По умолчанию {@link TURNSTILE_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n}\n\n/**\n * Создаёт обработчик Cloudflare Turnstile.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function turnstile(options: TurnstileOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? TURNSTILE_SITE_KEY, 'sitekey');\n\n return {\n type: CaptchaType.Cloudflare,\n label: 'Turnstile',\n buildPage: ({ theme }) => buildTurnstilePage(sitekey, theme),\n // Обёртка, а не iframe внутри неё: пройдясь без участия человека, виджет iframe убирает,\n // и ожидание iframe проспало бы уже готовый токен.\n widgetReadySelector: '#widget > div',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n isPermanentWidgetError: (code) => code.startsWith(DOMAIN_ERROR_PREFIX),\n };\n}\n","import type { CaptchaHandler } from '../handler.js';\nimport { type CaptchaType, CaptchaType as CaptchaTypeValues } from '../types.js';\nimport { itdCaptcha } from './itd.js';\nimport { turnstile } from './turnstile.js';\n\n/**\n * Встроенные обработчики по имени провайдера.\n *\n * Имена — те же, какими провайдера называет сервер итд.com, поэтому значение из\n * `itd.auth.captchaProvider()` подходит как есть. `turnstile` принимается синонимом\n * `cloudflare`.\n */\nconst BUILT_IN: Readonly<Record<string, () => CaptchaHandler>> = Object.freeze({\n [CaptchaTypeValues.Itd]: itdCaptcha,\n [CaptchaTypeValues.Cloudflare]: turnstile,\n turnstile,\n});\n\n/** Что решать: имя известного провайдера либо свой обработчик. */\nexport type CaptchaTarget = CaptchaType | CaptchaHandler;\n\nfunction isHandler(value: unknown): value is CaptchaHandler {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as CaptchaHandler).type === 'string' &&\n typeof (value as CaptchaHandler).buildPage === 'function' &&\n typeof (value as CaptchaHandler).readState === 'function'\n );\n}\n\n/**\n * Находит обработчик по имени провайдера либо принимает готовый.\n *\n * @throws {TypeError} если имя незнакомо или передано не то\n */\nexport function resolveHandler(target: CaptchaTarget): CaptchaHandler {\n if (isHandler(target)) return target;\n\n if (typeof target !== 'string' || target.trim() === '') {\n throw new TypeError(\n 'Тип капчи должен быть именем провайдера или объектом CaptchaHandler, ' +\n `получено: ${typeof target}`,\n );\n }\n\n const factory = BUILT_IN[target];\n if (!factory) {\n throw new TypeError(\n `Неизвестный тип капчи: ${target}. Известны ${Object.keys(BUILT_IN).join(', ')}. ` +\n 'Свой провайдер передайте объектом CaptchaHandler.',\n );\n }\n\n return factory();\n}\n","import type { Browser, NewContextOptions, Page } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\nimport type { CaptchaHandler } from './handler.js';\nimport { launchBrowser } from './launch.js';\nimport type { ResolvedOptions } from './options.js';\n\n/** Разброс координат клика, px в каждую сторону. */\nconst CLICK_JITTER = 4;\n/** Пауза перед первым касанием, мс. */\nconst HUMAN_DELAY = [1500, 2500] as const;\n/** Как часто опрашивается состояние виджета, мс. */\nconst POLL_INTERVAL = 250;\n/** Сколько ждать между повторными кликами, мс. */\nconst CLICK_INTERVAL = 4000;\n/** Сколько ждать появления виджета, мс. */\nconst WIDGET_APPEAR_TIMEOUT = 15_000;\n\n/** Настройки контекста по умолчанию. Заменяются целиком через `contextOptions`. */\nconst DEFAULT_CONTEXT_OPTIONS: NewContextOptions = {\n locale: 'ru-RU',\n viewport: { width: 1280, height: 800 },\n};\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst between = (min: number, max: number): number => min + Math.random() * (max - min);\n\n/**\n * Версия браузера для текста ошибки.\n *\n * Сборка Chromium — первое, что стоит проверить при отказе виджета: часть сборок его\n * не проходит. Метода может не быть, если браузер передали свой.\n */\nfunction browserVersion(browser: Browser): string | undefined {\n try {\n return browser.version?.();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Кликает по чекбоксу виджета.\n *\n * Чекбокс лежит в iframe чужого происхождения, до его DOM не дотянуться — клик идёт\n * по координатам. Отсчёт ведётся от собственного контейнера известного размера, поэтому\n * попадание не зависит от разметки виджета. Координаты слегка разбрасываются: один и тот же\n * пиксель раз за разом — заметная закономерность.\n */\nasync function clickCheckbox(page: Page, offsetX: number): Promise<boolean> {\n const widget = await page.$('#widget');\n const box = await widget?.boundingBox();\n if (!box) return false;\n\n const x = box.x + offsetX + between(-CLICK_JITTER, CLICK_JITTER);\n const y = box.y + box.height / 2 + between(-CLICK_JITTER, CLICK_JITTER);\n\n await page.mouse.move(x, y, { steps: 8 });\n await page.mouse.click(x, y, { delay: between(40, 110) });\n return true;\n}\n\n/** Ждёт токен, периодически подталкивая виджет кликом. */\nasync function waitForToken(\n page: Page,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n browser: Browser,\n): Promise<string> {\n const deadline = Date.now() + options.timeout;\n\n // Ожидание разметки необязательное: виджет её меняет, и привязываться жёстко нельзя.\n // Настоящий срок задаёт цикл ниже.\n await page\n .waitForSelector(handler.widgetReadySelector, {\n timeout: Math.min(WIDGET_APPEAR_TIMEOUT, options.timeout),\n state: 'attached',\n })\n .catch(() => {});\n\n // Первый клик не мгновенный: мгновенное касание только что отрисованного виджета само\n // по себе признак автоматизации, а капча ИТД к тому же измеряет время до клика.\n let nextClickAt = Date.now() + between(HUMAN_DELAY[0], HUMAN_DELAY[1]);\n let lastError: string | undefined;\n\n while (Date.now() < deadline) {\n const state = await handler.readState(page);\n\n if (state.token) {\n options.logger?.('токен получен');\n return state.token;\n }\n\n if (state.error && state.error !== lastError) {\n if (handler.isPermanentWidgetError(state.error)) {\n throw new CaptchaError(\n CaptchaFailure.WidgetError,\n `Виджет ${handler.label} отказал для домена ${options.origin} (код ${state.error}): ` +\n 'ключ не разрешён для этого домена.',\n { type: handler.type, widgetCode: state.error },\n );\n }\n\n // Прочие коды временные: обрывать попытку рано, виджет пробует снова сам.\n lastError = state.error;\n options.logger?.(`виджет сообщил об ошибке ${state.error}, ждём повтора`);\n }\n\n if (Date.now() >= nextClickAt && (await clickCheckbox(page, handler.checkboxOffsetX))) {\n options.logger?.('клик по чекбоксу');\n nextClickAt = Date.now() + CLICK_INTERVAL;\n }\n\n await sleep(POLL_INTERVAL);\n }\n\n const version = browserVersion(browser);\n\n throw new CaptchaError(\n CaptchaFailure.Timeout,\n `Виджет ${handler.label} не отдал токен за ${options.timeout} мс` +\n (lastError ? `; последняя ошибка виджета — ${lastError}` : '') +\n (version ? `; браузер — ${version}` : '') +\n '. Рабочие связки драйвера и браузера перечислены в README пакета.',\n lastError ? { type: handler.type, widgetCode: lastError } : { type: handler.type },\n );\n}\n\nasync function solveOnce(\n browser: Browser,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n // Каждая попытка идёт в чистом контексте: cookie и хранилище от неудачной попытки\n // достались бы следующей, а виджет их учитывает.\n const context = await browser.newContext(options.contextOptions ?? DEFAULT_CONTEXT_OPTIONS);\n\n try {\n const page = await context.newPage();\n const url = `${options.origin}/`;\n const body = handler.buildPage({ theme: options.theme });\n\n // На сайт запроса не уходит: навигация перехватывается и подменяется своей страницей.\n // Домен для браузера при этом настоящий, поэтому виджет проходит его проверку,\n // а сервер при проверке токена видит ожидаемый hostname.\n await page.route(url, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html; charset=utf-8', body }),\n );\n await page.goto(url, { waitUntil: 'domcontentloaded' });\n\n return await waitForToken(page, handler, options, browser);\n } finally {\n await context.close().catch(() => {});\n }\n}\n\n/** Ошибки настройки: повтор их не исправит. */\nfunction isPermanent(error: unknown): boolean {\n if (!(error instanceof CaptchaError)) return false;\n if (error.reason === CaptchaFailure.DriverMissing) return true;\n if (error.reason === CaptchaFailure.LaunchFailed) return true;\n if (error.reason === CaptchaFailure.BrowserClosed) return true;\n\n return error.reason === CaptchaFailure.WidgetError;\n}\n\n/**\n * Как драйверы сообщают, что страницы, контекста или браузера больше нет.\n *\n * Своего типа ошибки у них нет, поэтому остаётся текст сообщения.\n */\nconst CLOSED_MARKERS = [\n 'has been closed',\n 'target closed',\n 'browser has been closed',\n 'browser has disconnected',\n 'session closed',\n];\n\nfunction isClosedError(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return CLOSED_MARKERS.some((marker) => message.includes(marker));\n}\n\n/** Приводит закрытие браузера к {@link CaptchaError}; остальное отдаёт как есть. */\nfunction toCaptchaError(error: unknown, handler: CaptchaHandler): unknown {\n if (!isClosedError(error)) return error;\n\n return new CaptchaError(\n CaptchaFailure.BrowserClosed,\n `Браузер закрылся до того, как виджет ${handler.label} отдал токен.`,\n { type: handler.type },\n );\n}\n\n/**\n * Общий путь получения токена: поднимает браузер, решает виджет, закрывает браузер.\n *\n * Всё, что зависит от провайдера, приходит в {@link CaptchaHandler}; здесь остаётся только\n * то, что одинаково для любого виджета.\n *\n * @throws {CaptchaError} если токен получить не удалось\n */\nexport async function runSolver(\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n const browser = options.browser ?? (await launchBrowser(options));\n const owned = options.browser === undefined;\n\n try {\n for (let attempt = 1; attempt <= options.attempts; attempt++) {\n try {\n options.logger?.(`попытка ${attempt} из ${options.attempts}`);\n return await solveOnce(browser, handler, options);\n } catch (raw) {\n const error = toCaptchaError(raw, handler);\n if (isPermanent(error) || attempt === options.attempts) throw error;\n options.logger?.(\n `попытка ${attempt} не удалась: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // Недостижимо: последняя попытка либо возвращает токен, либо бросает ошибку.\n throw new CaptchaError(CaptchaFailure.Timeout, `Токен ${handler.label} получить не удалось`, {\n type: handler.type,\n });\n } finally {\n if (owned) await browser.close().catch(() => {});\n }\n}\n","import { resolveOptions, type SolveOptions } from './options.js';\nimport { type CaptchaTarget, resolveHandler } from './providers/registry.js';\nimport { runSolver } from './runner.js';\nimport type { CaptchaType } from './types.js';\n\n/**\n * Получает один токен капчи указанного типа.\n *\n * Поднимает браузер, решает виджет и закрывает браузер за собой.\n *\n * @example\n * ```ts\n * await solveCaptcha(CaptchaType.Itd);\n * await solveCaptcha(itdCaptcha({ action: 'register' }), { headless: true });\n * await solveCaptcha(myOwnHandler);\n * ```\n *\n * @throws {CaptchaError} если токен получить не удалось\n * @throws {TypeError} при некорректных настройках или незнакомом типе\n */\nexport async function solveCaptcha(\n target: CaptchaTarget,\n options: SolveOptions = {},\n): Promise<string> {\n const handler = resolveHandler(target);\n return runSolver(handler, resolveOptions(options));\n}\n\n/** Настройки источника токена для клиента `itd-api`. */\nexport interface CaptchaSolverOptions extends SolveOptions {\n /**\n * Какую капчу проходить всегда: `CaptchaType.Itd`, `CaptchaType.Cloudflare` либо готовый\n * провайдер из фабрики.\n *\n * Без этой настройки тип называет клиент, спросив активного провайдера у сервера.\n * Сервер принимает обе капчи, так что закреплённый тип избавляет от лишнего запроса.\n */\n type?: CaptchaTarget | undefined;\n /**\n * Поле тела запроса, в котором сервер ждёт токен.\n *\n * Нужно, только если сервер его переименовал: для закреплённого типа клиент берёт имя\n * из своей таблицы. Задаётся вместе с {@link CaptchaSolverOptions.type}.\n */\n field?: string | undefined;\n}\n\n/** Источник токена капчи для опции `captcha` клиента `itd-api`. */\nexport interface CaptchaSolver {\n /** Получает имя провайдера, чью капчу нужно пройти, и возвращает токен. */\n getToken(type: CaptchaType): Promise<string>;\n /** Тип, закреплённый за источником. Без него тип выбирает клиент. */\n readonly type?: CaptchaType | undefined;\n /** Поле тела запроса для закреплённого типа. */\n readonly field?: string | undefined;\n}\n\n/**\n * Собирает источник токена капчи для клиента `itd-api`.\n *\n * Токен одноразовый и живёт несколько минут, поэтому клиент спрашивает его заново перед\n * каждым запросом, которому нужна капча. Браузер поднимается на время одного вызова\n * и сразу закрывается.\n *\n * @throws {TypeError} если тип капчи незнаком или `field` задан без него\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n * import { createCaptchaSolver, CaptchaType } from '@itd-api/captcha';\n *\n * const itd = new ItdClient({\n * storage: new FileTokenStorage('./.itd-session.json'),\n * auth: { email: process.env.ITD_EMAIL!, password: process.env.ITD_PASSWORD! },\n * captcha: createCaptchaSolver({ type: CaptchaType.Cloudflare }),\n * });\n * ```\n */\nexport function createCaptchaSolver(options: CaptchaSolverOptions = {}): CaptchaSolver {\n const { type, field, ...solveOptions } = options;\n\n if (type === undefined) {\n return { getToken: (requested) => solveCaptcha(requested, solveOptions) };\n }\n\n // Обработчик собирается сразу: незнакомый тип должен разбираться при создании клиента,\n // а не в момент входа.\n const handler = resolveHandler(type);\n const resolved = resolveOptions(solveOptions);\n\n return {\n type: handler.type,\n ...(field === undefined ? {} : { field }),\n getToken: () => runSolver(handler, resolved),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["WIDTH","HEIGHT","CHECKBOX_OFFSET_X","readState","CaptchaTypeValues"],"sources":["../src/errors.ts","../src/launch.ts","../src/options.ts","../src/html.ts","../src/types.ts","../src/providers/itd.ts","../src/providers/turnstile.ts","../src/providers/registry.ts","../src/runner.ts","../src/solve.ts"],"sourcesContent":["import type { CaptchaType } from './types.js';\n\n/** Почему не удалось получить токен. */\nexport const CaptchaFailure = Object.freeze({\n /** Драйвер браузера не установлен. */\n DriverMissing: 'driver-missing',\n /** Браузер не запустился: нет исполняемого файла, нет дисплея, отказ песочницы. */\n LaunchFailed: 'launch-failed',\n /** Браузер или страница закрылись, пока решался виджет. */\n BrowserClosed: 'browser-closed',\n /** Виджет не отдал токен за отведённое время. */\n Timeout: 'timeout',\n /** Сам виджет сообщил об ошибке — код лежит в `widgetCode`. */\n WidgetError: 'widget-error',\n} as const);\nexport type CaptchaFailure = (typeof CaptchaFailure)[keyof typeof CaptchaFailure];\n\n/**\n * Ошибка получения токена капчи.\n *\n * Причина в {@link CaptchaError.reason} определяет, что с ошибкой делать: отсутствие драйвера\n * чинится установкой, таймаут — повтором, а ошибка виджета повтором не лечится. Поле\n * {@link CaptchaError.type} говорит, чей именно виджет решался, — оно есть не у всех ошибок\n * (запуск браузера происходит до выбора виджета).\n */\nexport class CaptchaError extends Error {\n readonly reason: CaptchaFailure;\n /** Какой провайдер решался в момент ошибки. */\n readonly type: CaptchaType | undefined;\n /**\n * Код ошибки виджета, если он его сообщил.\n *\n * Для Cloudflare самые частые: `110200` — домен не разрешён для этого ключа, `300***`\n * и `600***` — внутренние сбои виджета, лечатся повтором.\n */\n readonly widgetCode: string | undefined;\n\n constructor(\n reason: CaptchaFailure,\n message: string,\n options: { type?: CaptchaType; widgetCode?: string } = {},\n ) {\n super(message);\n this.name = 'CaptchaError';\n this.reason = reason;\n this.type = options.type;\n this.widgetCode = options.widgetCode;\n }\n}\n","import type { Browser, DriverModule, LaunchOptions } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\n\n/**\n * Аргументы запуска Chromium.\n *\n * Подмены `User-Agent` здесь нет намеренно: заявленная версия браузера расходилась бы\n * с реальным движком, а такое расхождение само по себе служит признаком автоматизации.\n * Остаются безопасные общие флаги. Отключение sandbox вынесено в явную настройку:\n * удалённый код виджета исполняется в браузере, и ослаблять его изоляцию по умолчанию нельзя.\n */\nconst DEFAULT_ARGS = ['--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled'];\n\n/**\n * Драйверы в порядке предпочтения.\n *\n * `patchright` впереди намеренно: проверенная связка `patchright@1.61.1` со штатным\n * Chromium 149 проходит виджет. Если стоит только `playwright`, берётся он.\n */\nconst DRIVERS = ['patchright', 'playwright', 'playwright-core'];\n\n/**\n * Как выглядит имя пакета драйвера.\n *\n * Имя приходит извне и уезжает в динамический импорт, поэтому путями и относительными\n * адресами быть не должно — только имя пакета.\n */\nconst DRIVER_NAME = /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*$/i;\n\n/**\n * Драйверы, которые сами приводят аргументы запуска в порядок.\n *\n * Им ничего не подмешивается: набор флагов у них выверен, а лишний флаг — такой же след,\n * как и недостающий.\n */\nconst SELF_TUNED_DRIVERS = new Set(['patchright']);\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | null)?.code;\n return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';\n}\n\n/**\n * Подключает драйвер браузера.\n *\n * Импорт динамический: драйвер объявлен необязательной одноранговой зависимостью,\n * поэтому его может не быть вовсе. Названный явно берётся один, без перебора: молча\n * подставить вместо него другой значило бы проверить не то, что просили.\n */\nasync function loadDriver(requested?: string): Promise<{ driver: string; module: DriverModule }> {\n const wanted = requested ? [requested] : DRIVERS;\n\n for (const name of wanted) {\n try {\n return { driver: name, module: (await import(/* @vite-ignore */ name)) as DriverModule };\n } catch (error) {\n // Установленный драйвер, упавший при загрузке, — не то же самое, что отсутствующий.\n // Его ошибку нужно показать как есть, а не подменять советом установить пакет.\n if (!isModuleNotFound(error)) throw error;\n }\n }\n\n throw new CaptchaError(\n CaptchaFailure.DriverMissing,\n requested\n ? `Драйвер ${requested} не установлен.`\n : 'Не найден драйвер браузера. Установите проверенную связку командой: npm i patchright@1.61.1 && npx patchright install chromium. ' +\n 'Либо передайте свой запуск браузера через параметр launch.',\n );\n}\n\n/** Настройки запуска браузера. */\nexport interface BrowserOptions {\n /**\n * Запускать ли браузер без окна. По умолчанию `false`.\n *\n * В безоконном режиме виджет проходится заметно хуже: признаки такого режима видны\n * странице. На сервере поднимите виртуальный дисплей (`xvfb-run -a node bot.js`).\n */\n headless?: boolean | undefined;\n /**\n * Какой драйвер брать вместо перебора `patchright` → `playwright` → `playwright-core`.\n *\n * Нужен, когда установлено несколько и важно, какой именно поднимется. Названный\n * драйвер берётся один: не найден — ошибка, а не молчаливая подмена соседним.\n */\n driver?: string | undefined;\n /** Путь к исполняемому файлу браузера, если он лежит не там, где его ищет драйвер. */\n executablePath?: string | undefined;\n /** Канал браузера, например `chrome` или `msedge`, — вместо сборки из комплекта драйвера. */\n channel?: string | undefined;\n /** Дополнительные аргументы командной строки — добавляются к стандартным. */\n args?: readonly string[] | undefined;\n /**\n * Отключить sandbox Chromium. По умолчанию `false`.\n *\n * Используйте только внутри изолированного контейнера, где sandbox невозможно настроить:\n * страница исполняет удалённый код виджета, и без изоляции браузера его компрометация\n * получает больше доступа к процессу и системе.\n */\n disableSandbox?: boolean | undefined;\n /** Прокси для браузера. */\n proxy?: { server: string; username?: string; password?: string } | undefined;\n /**\n * Свой запуск браузера. Заменяет все остальные параметры запуска.\n *\n * Нужен, чтобы подставить другой драйвер или подключиться к уже работающему браузеру.\n *\n * @example\n * ```ts\n * launch: async () => {\n * const { chromium } = await import('patchright');\n * return chromium.launch({ headless: false });\n * };\n * ```\n */\n launch?: (() => Promise<Browser>) | undefined;\n}\n\n/** Собирает параметры запуска, не поднимая браузер. @internal */\nexport function resolveLaunchOptions(options: BrowserOptions, driver = ''): LaunchOptions {\n return {\n headless: options.headless ?? false,\n args: [\n ...(SELF_TUNED_DRIVERS.has(driver) ? [] : DEFAULT_ARGS),\n ...(options.disableSandbox ? ['--no-sandbox'] : []),\n ...(options.args ?? []),\n ],\n ...(options.executablePath ? { executablePath: options.executablePath } : {}),\n ...(options.channel ? { channel: options.channel } : {}),\n ...(options.proxy ? { proxy: options.proxy } : {}),\n };\n}\n\n/**\n * Поднимает браузер по настройкам.\n *\n * Тем же путём, что и солверы: те же флаги, тот же порядок драйверов. Пригодится, чтобы\n * поднять браузер один раз на несколько токенов и передать его в `browser`, — и чтобы\n * проверять связки драйвера и сборки ровно в том виде, в каком их поднимает пакет.\n */\nexport async function launchBrowser(options: BrowserOptions): Promise<Browser> {\n if (options.launch) return options.launch();\n\n if (options.driver !== undefined && !DRIVER_NAME.test(options.driver)) {\n throw new TypeError(`driver должен быть именем пакета, получено: ${options.driver}`);\n }\n\n const {\n driver,\n module: { chromium },\n } = await loadDriver(options.driver);\n const launchOptions = resolveLaunchOptions(options, driver);\n\n try {\n return await chromium.launch(launchOptions);\n } catch (error) {\n const hint =\n launchOptions.headless === false\n ? ' Если это сервер без графической оболочки, запустите процесс через xvfb-run -a.'\n : '';\n\n throw new CaptchaError(\n CaptchaFailure.LaunchFailed,\n `Не удалось запустить браузер: ${error instanceof Error ? error.message : String(error)}.${hint}`,\n );\n }\n}\n","import type { Browser, NewContextOptions } from './driver.js';\nimport type { BrowserOptions } from './launch.js';\nimport type { Theme } from './types.js';\n\n/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */\nexport const DEFAULT_ORIGIN = 'https://xn--d1ah4a.com';\n\n/** Логгер хода решения. Совместим с `console` и с логгером клиента `itd-api`. */\nexport interface CaptchaLogger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\nconst LOGGER_METHODS = ['debug', 'info', 'warn', 'error'] as const;\n\n/**\n * Настройки получения токена, общие для любого виджета.\n *\n * Ключ, назначение токена и адрес виджета задаются при создании провайдера:\n * `solveCaptcha(itdCaptcha({ action: 'register' }))`.\n */\nexport interface SolveOptions extends BrowserOptions {\n /** Сайт, чей виджет решается. По умолчанию {@link DEFAULT_ORIGIN}. */\n origin?: string | undefined;\n /** Оформление виджета. По умолчанию `auto`. */\n theme?: Theme | undefined;\n /** Сколько ждать токен, мс. По умолчанию 60000. */\n timeout?: number | undefined;\n /** Сколько попыток делать при неудаче. По умолчанию 2. */\n attempts?: number | undefined;\n /**\n * Готовый браузер. Тогда пакет его не запускает и не закрывает.\n *\n * Пригодится, если браузер уже поднят для чего-то ещё.\n */\n browser?: Browser | undefined;\n /**\n * Настройки контекста. Заменяют стандартные целиком, а не дополняют их.\n *\n * По умолчанию `{ locale: 'ru-RU', viewport: { width: 1280, height: 800 } }`. Драйверам,\n * которые собирают отпечаток браузера сами, навязанный `locale` мешает — им передайте `{}`.\n */\n contextOptions?: NewContextOptions | undefined;\n /** Куда писать ход решения, например `console`. Без него — в логгер клиента `itd-api`. */\n logger?: CaptchaLogger | undefined;\n}\n\n/** Настройки после подстановки умолчаний. @internal */\nexport interface ResolvedOptions extends SolveOptions {\n origin: string;\n theme: Theme;\n timeout: number;\n attempts: number;\n}\n\n/**\n * Приводит адрес к корню и проверяет его пригодность.\n *\n * Виджет привязан к домену, а не к пути: адрес приводится к `URL.origin`, чтобы перехват навигации\n * совпал с ним ровно один раз.\n */\nexport function resolveOrigin(origin: string, field: string): string {\n let parsed: URL;\n try {\n parsed = new URL(origin);\n } catch {\n throw new TypeError(`${field} должен быть абсолютным URL, получено: ${origin}`);\n }\n\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new TypeError(`${field} должен быть http или https, получено: ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new TypeError(\n `${field} не должен содержать логин, пароль, параметры запроса или фрагмент`,\n );\n }\n\n return parsed.origin;\n}\n\n/**\n * Проверяет общие настройки и подставляет умолчания.\n *\n * @throws {TypeError} при некорректных значениях\n */\nexport function resolveOptions(options: SolveOptions): ResolvedOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) {\n throw new TypeError('options должен быть объектом');\n }\n\n const timeout = options.timeout ?? 60_000;\n const attempts = options.attempts ?? 2;\n const theme = options.theme ?? 'auto';\n\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new TypeError(`timeout должен быть положительным числом, получено: ${timeout}`);\n }\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new TypeError(`attempts должен быть целым числом от 1, получено: ${attempts}`);\n }\n if (theme !== 'auto' && theme !== 'light' && theme !== 'dark') {\n throw new TypeError(\"theme должен быть 'auto', 'light' или 'dark'\");\n }\n for (const [name, value] of [\n ['headless', options.headless],\n ['disableSandbox', options.disableSandbox],\n ] as const) {\n if (value !== undefined && typeof value !== 'boolean') {\n throw new TypeError(`${name} должен быть boolean`);\n }\n }\n for (const [name, value] of [\n ['driver', options.driver],\n ['channel', options.channel],\n ] as const) {\n if (value !== undefined && (typeof value !== 'string' || value.trim() === '')) {\n throw new TypeError(`${name} должен быть непустой строкой`);\n }\n }\n if (options.logger !== undefined) {\n if (typeof options.logger !== 'object' || options.logger === null) {\n throw new TypeError('logger должен быть объектом с методами debug, info, warn и error');\n }\n for (const method of LOGGER_METHODS) {\n if (typeof options.logger[method] !== 'function') {\n throw new TypeError(`logger.${method} должен быть функцией`);\n }\n }\n }\n if (\n options.contextOptions !== undefined &&\n (typeof options.contextOptions !== 'object' ||\n options.contextOptions === null ||\n Array.isArray(options.contextOptions))\n ) {\n throw new TypeError('contextOptions должен быть объектом');\n }\n if (options.launch !== undefined && typeof options.launch !== 'function') {\n throw new TypeError('launch должен быть функцией');\n }\n if (\n options.args !== undefined &&\n (!Array.isArray(options.args) || options.args.some((argument) => typeof argument !== 'string'))\n ) {\n throw new TypeError('args должен быть массивом строк');\n }\n\n return {\n ...options,\n origin: resolveOrigin(options.origin ?? DEFAULT_ORIGIN, 'origin'),\n theme,\n timeout,\n attempts,\n };\n}\n\n/** Требует непустую строку: общая проверка для настроек провайдеров. @internal */\nexport function requireText(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new TypeError(`${field} должен быть непустой строкой`);\n }\n return value;\n}\n","/**\n * Сборка страницы, на которой живёт виджет.\n *\n * Страница отдаётся вместо настоящей — по адресу целевого сайта, через перехват навигации.\n * Благодаря этому `document.location.origin` для виджета настоящий, и привязка ключа\n * к домену не нарушается, а форма входа и пароль в браузере не участвуют.\n *\n * Каркас общий для всех провайдеров: контейнер `#widget` известного размера, от которого\n * общий цикл отсчитывает координаты клика. Различия провайдеров — в `head` и `body`.\n */\n\n/** Отступ контейнера от края окна, px. Виджету нужно место, чтобы раскрыть карточку. */\nconst WIDGET_MARGIN = 40;\n\n/**\n * Готовит значение к вставке в инлайновый скрипт или атрибут.\n *\n * Одного `JSON.stringify` мало: кавычки он экранирует, а `</script>` — нет, и такая\n * последовательность закрывает тег независимо от того, внутри строки она или нет.\n * Экранированный `<` разбирается движком как обычный символ, но парсер HTML его уже не видит.\n */\nexport function embed(value: string): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n/** Из чего собирается страница одного виджета. */\nexport interface PageParts {\n /** Заголовок вкладки. На проверку не влияет, но пустой заголовок выглядит подозрительно. */\n title: string;\n /** Размеры контейнера `#widget`, px. Столько же виджет занимает на самом сайте. */\n width: number;\n height: number;\n /** Дополнительные правила для `#widget` — например, если контейнер сам является iframe. */\n widgetStyle?: string | undefined;\n /** Содержимое `head`: скрипты и обработчики провайдера. */\n head?: string | undefined;\n /** Содержимое `body`: сам контейнер `#widget` и поля для результата. */\n body: string;\n}\n\n/** Собирает страницу с одним виджетом. */\nexport function buildWidgetPage(parts: PageParts): string {\n return `<!doctype html>\n<html lang=\"ru\">\n<head>\n<meta charset=\"utf-8\">\n<title>${parts.title}</title>\n<style>\n html, body { margin: 0; padding: 0; background: #fff; }\n #widget {\n width: ${parts.width}px;\n height: ${parts.height}px;\n margin: ${WIDGET_MARGIN}px;${parts.widgetStyle ?? ''}\n }\n</style>\n${parts.head ?? ''}\n</head>\n<body>\n${parts.body}\n</body>\n</html>`;\n}\n","/**\n * Открытый строковый enum.\n *\n * Даёт автодополнение известных значений, но не ломается, если появится новое.\n */\nexport type Loose<T extends string> = T | (string & {});\n\n/** Провайдер капчи — теми же именами, какими его называет сервер итд.com. */\nexport const CaptchaType = Object.freeze({\n /** Собственная капча ИТД: карточка «Я не робот» в iframe с `captcha.итд.com`. */\n Itd: 'itd',\n /** Cloudflare Turnstile. */\n Cloudflare: 'cloudflare',\n} as const);\nexport type CaptchaType = Loose<(typeof CaptchaType)[keyof typeof CaptchaType]>;\n\n/** Оформление виджета. */\nexport type Theme = 'auto' | 'light' | 'dark';\n\n/** Результат виджета, снятый со страницы через `page.evaluate`. */\nexport interface WidgetState {\n token: string | null;\n error: string | null;\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText, resolveOrigin } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/** Базовый адрес виджета собственной капчи ИТД. Домен в punycode: `captcha.итд.com`. */\nexport const DEFAULT_CAPTCHA_ORIGIN = 'https://captcha.xn--d1ah4a.com';\n\n/**\n * Публичный ключ виджета собственной капчи ИТД.\n *\n * Как и ключ Turnstile, он публичный и привязан к домену итд.com: виджет отдаёт его браузеру\n * каждому посетителю.\n */\nexport const ITD_CAPTCHA_SITE_KEY = 'sk_44d64cf7bf8bc8377f5b';\n\n/** Назначение токена по умолчанию. Сервер сверяет его с операцией. */\nconst DEFAULT_ACTION = 'login';\n\n/** Размеры карточки, px. Их же виджет сообщает сайту через `postMessage`. */\nconst WIDTH = 300;\nconst HEIGHT = 74;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 34;\n\n/**\n * Собирает страницу с одним виджетом собственной капчи ИТД.\n *\n * Виджет — iframe чужого происхождения (`captcha.итд.com`), и наружу он отдаёт токен\n * единственным способом: `postMessage` в родителя. Эта страница ровно так его и слушает —\n * как сам сайт.\n *\n * Слушатель кладёт результат в скрытые поля, а не в переменную `window`: стелс-драйверы вроде\n * `patchright` выполняют `page.evaluate` в изолированном мире, откуда переменные страницы\n * не видны, зато DOM общий.\n *\n * `action` задаёт назначение токена (`login`, `register`, `password_reset`) — сервер сверяет\n * его с операцией, поэтому он не выдумывается, а приходит из настроек обработчика.\n */\nexport function buildItdCaptchaPage(input: {\n sitekey: string;\n theme: Theme;\n action: string;\n captchaOrigin: string;\n}): string {\n const origin = embed(input.captchaOrigin);\n const src = embed(\n `${input.captchaOrigin}/widget.html` +\n `?sitekey=${encodeURIComponent(input.sitekey)}` +\n `&theme=${encodeURIComponent(input.theme)}` +\n `&action=${encodeURIComponent(input.action)}`,\n );\n\n return buildWidgetPage({\n title: 'Проверка',\n width: WIDTH,\n height: HEIGHT,\n widgetStyle: `\n border: 0;\n display: block;`,\n head: `<script>\n window.addEventListener('message', function (event) {\n if (event.origin !== ${origin}) return;\n var data;\n try {\n data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;\n } catch (e) {\n return;\n }\n if (!data) return;\n var token = document.getElementById('itd-token');\n var error = document.getElementById('itd-error');\n if (data.type === 'token' && data.token) token.value = data.token;\n else if (data.type === 'expired') token.value = '';\n else if (data.type === 'error') error.value = String(data.code || 'error');\n });\n</script>`,\n body: `<input type=\"hidden\" id=\"itd-token\">\n<input type=\"hidden\" id=\"itd-error\">\n<iframe id=\"widget\" src=${src} title=\"Проверка\"></iframe>`,\n });\n}\n\n/**\n * Читает результат виджета капчи ИТД.\n *\n * Токен приходит в родителя через `postMessage`, и слушатель страницы кладёт его в скрытое\n * поле. Читаем из DOM, а не из `window`: стелс-драйверы выполняют `evaluate` в изолированном\n * мире, где переменных страницы нет, а DOM общий.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const token = document.querySelector('#itd-token') as { value?: string } | null;\n const error = document.querySelector('#itd-error') as { value?: string } | null;\n return {\n token: token?.value || null,\n error: error?.value || null,\n };\n });\n}\n\n/** Настройки виджета собственной капчи ИТД. */\nexport interface ItdCaptchaOptions {\n /** Публичный ключ виджета. По умолчанию {@link ITD_CAPTCHA_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n /**\n * Назначение токена: `login`, `register`, `password_reset`. По умолчанию `login`.\n *\n * Сервер сверяет назначение с операцией, поэтому для регистрации или сброса пароля нужен\n * токен с соответствующим `action`.\n */\n action?: string | undefined;\n /** Базовый адрес виджета. По умолчанию {@link DEFAULT_CAPTCHA_ORIGIN}. */\n captchaOrigin?: string | undefined;\n}\n\n/**\n * Создаёт обработчик собственной капчи ИТД.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function itdCaptcha(options: ItdCaptchaOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? ITD_CAPTCHA_SITE_KEY, 'sitekey');\n const action = requireText(options.action ?? DEFAULT_ACTION, 'action');\n const captchaOrigin = resolveOrigin(\n options.captchaOrigin ?? DEFAULT_CAPTCHA_ORIGIN,\n 'captchaOrigin',\n );\n\n return {\n type: CaptchaType.Itd,\n label: 'капчи ИТД',\n buildPage: ({ theme }) => buildItdCaptchaPage({ sitekey, theme, action, captchaOrigin }),\n widgetReadySelector: '#widget',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n // Виджет ИТД шлёт лишь общий код ошибки и просит повторить: постоянных кодов у него нет.\n isPermanentWidgetError: () => false,\n };\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/**\n * Публичный ключ виджета Cloudflare Turnstile на итд.com.\n *\n * Ключ публичный — виджет отдаёт его браузеру каждому посетителю — и привязан к домену\n * итд.com, поэтому больше ни для чего не годится. Держим его прямо в пакете: это его данные,\n * а не пользовательская настройка. Тот же ключ экспортирует `itd-api`.\n */\nexport const TURNSTILE_SITE_KEY = '0x4AAAAAACHhxczw6fJGwPBg';\n\n/** Размеры контейнера виджета, px. Столько же он занимает на самом сайте. */\nconst WIDTH = 300;\nconst HEIGHT = 65;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 30;\n\n/** Коды Cloudflare вида `110***` означают, что ключ не разрешён для домена. */\nconst DOMAIN_ERROR_PREFIX = '110';\n\n/**\n * Собирает страницу с одним виджетом Cloudflare Turnstile.\n *\n * Виджет создаётся так же, как это делает сам сайт, — скриптом с `?onload=` и явным\n * `turnstile.render` с одним лишь `sitekey`. Ни `action`, ни `cdata` сайт не передаёт,\n * поэтому их не передаёт и эта страница: лишний параметр попал бы в ответ `siteverify`\n * и мог бы разойтись с тем, что ожидает сервер.\n */\nexport function buildTurnstilePage(sitekey: string, theme: Theme): string {\n const key = embed(sitekey);\n const widgetTheme = embed(theme);\n\n return buildWidgetPage({\n title: 'Turnstile',\n width: WIDTH,\n height: HEIGHT,\n head: `<script>\n window.__itdToken = null;\n window.__itdError = null;\n window.onTurnstileLoad = function () {\n var reset = function () {\n window.__itdToken = null;\n if (widgetId !== undefined) window.turnstile.reset(widgetId);\n };\n var widgetId = window.turnstile.render('#widget', {\n sitekey: ${key},\n theme: ${widgetTheme},\n callback: function (token) { window.__itdToken = token; },\n 'error-callback': function (code) { window.__itdError = String(code || 'unknown'); return true; },\n 'timeout-callback': reset,\n 'expired-callback': reset\n });\n };\n</script>\n<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad\" async defer></script>`,\n body: '<div id=\"widget\"></div>',\n });\n}\n\n/**\n * Читает результат виджета Turnstile.\n *\n * Источников два. Скрытое поле `cf-turnstile-response` виджет заполняет всегда, а вот\n * `callback` срабатывает не в каждом сценарии: пройдясь сам, виджет успевает убрать iframe,\n * и обработчика можно не дождаться. Поле при этом остаётся заполненным, и лежит оно\n * в нашей странице, а не внутри iframe чужого происхождения, поэтому доступно.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const field = document.querySelector('input[name=\"cf-turnstile-response\"]') as {\n value?: string;\n } | null;\n const scope = window as unknown as { __itdToken?: string | null; __itdError?: string | null };\n\n // Пустая строка в поле означает «ещё не решено», поэтому проверка на истинность.\n return {\n token: scope.__itdToken || field?.value || null,\n error: scope.__itdError ?? null,\n };\n });\n}\n\n/** Настройки виджета Cloudflare Turnstile. */\nexport interface TurnstileOptions {\n /** Публичный ключ виджета. По умолчанию {@link TURNSTILE_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n}\n\n/**\n * Создаёт обработчик Cloudflare Turnstile.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function turnstile(options: TurnstileOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? TURNSTILE_SITE_KEY, 'sitekey');\n\n return {\n type: CaptchaType.Cloudflare,\n label: 'Turnstile',\n buildPage: ({ theme }) => buildTurnstilePage(sitekey, theme),\n // Обёртка, а не iframe внутри неё: пройдясь без участия человека, виджет iframe убирает,\n // и ожидание iframe проспало бы уже готовый токен.\n widgetReadySelector: '#widget > div',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n isPermanentWidgetError: (code) => code.startsWith(DOMAIN_ERROR_PREFIX),\n };\n}\n","import type { CaptchaHandler } from '../handler.js';\nimport { type CaptchaType, CaptchaType as CaptchaTypeValues } from '../types.js';\nimport { itdCaptcha } from './itd.js';\nimport { turnstile } from './turnstile.js';\n\n/**\n * Встроенные обработчики по имени провайдера.\n *\n * Имена — те же, какими провайдера называет сервер итд.com, поэтому значение из\n * `itd.auth.captchaProvider()` подходит как есть. `turnstile` принимается синонимом\n * `cloudflare`.\n */\nconst BUILT_IN: Readonly<Record<string, () => CaptchaHandler>> = Object.freeze({\n [CaptchaTypeValues.Itd]: itdCaptcha,\n [CaptchaTypeValues.Cloudflare]: turnstile,\n turnstile,\n});\n\n/** Что решать: имя известного провайдера либо свой обработчик. */\nexport type CaptchaTarget = CaptchaType | CaptchaHandler;\n\nfunction isHandler(value: unknown): value is CaptchaHandler {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as CaptchaHandler).type === 'string' &&\n typeof (value as CaptchaHandler).buildPage === 'function' &&\n typeof (value as CaptchaHandler).readState === 'function'\n );\n}\n\n/**\n * Находит обработчик по имени провайдера либо принимает готовый.\n *\n * @throws {TypeError} если имя незнакомо или передано не то\n */\nexport function resolveHandler(target: CaptchaTarget): CaptchaHandler {\n if (isHandler(target)) return target;\n\n if (typeof target !== 'string' || target.trim() === '') {\n throw new TypeError(\n 'Тип капчи должен быть именем провайдера или объектом CaptchaHandler, ' +\n `получено: ${typeof target}`,\n );\n }\n\n const factory = BUILT_IN[target];\n if (!factory) {\n throw new TypeError(\n `Неизвестный тип капчи: ${target}. Известны ${Object.keys(BUILT_IN).join(', ')}. ` +\n 'Свой провайдер передайте объектом CaptchaHandler.',\n );\n }\n\n return factory();\n}\n","import type { Browser, NewContextOptions, Page } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\nimport type { CaptchaHandler } from './handler.js';\nimport { launchBrowser } from './launch.js';\nimport type { ResolvedOptions } from './options.js';\n\n/** Разброс координат клика, px в каждую сторону. */\nconst CLICK_JITTER = 4;\n/** Пауза перед первым касанием, мс. */\nconst HUMAN_DELAY = [1500, 2500] as const;\n/** Как часто опрашивается состояние виджета, мс. */\nconst POLL_INTERVAL = 250;\n/** Сколько ждать между повторными кликами, мс. */\nconst CLICK_INTERVAL = 4000;\n/** Сколько ждать появления виджета, мс. */\nconst WIDGET_APPEAR_TIMEOUT = 15_000;\n\n/** Настройки контекста по умолчанию. Заменяются целиком через `contextOptions`. */\nconst DEFAULT_CONTEXT_OPTIONS: NewContextOptions = {\n locale: 'ru-RU',\n viewport: { width: 1280, height: 800 },\n};\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst between = (min: number, max: number): number => min + Math.random() * (max - min);\n\n/**\n * Версия браузера для текста ошибки.\n *\n * Сборка Chromium — первое, что стоит проверить при отказе виджета: часть сборок его\n * не проходит. Метода может не быть, если браузер передали свой.\n */\nfunction browserVersion(browser: Browser): string | undefined {\n try {\n return browser.version?.();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Кликает по чекбоксу виджета.\n *\n * Чекбокс лежит в iframe чужого происхождения, до его DOM не дотянуться — клик идёт\n * по координатам. Отсчёт ведётся от собственного контейнера известного размера, поэтому\n * попадание не зависит от разметки виджета. Координаты слегка разбрасываются: один и тот же\n * пиксель раз за разом — заметная закономерность.\n */\nasync function clickCheckbox(page: Page, offsetX: number): Promise<boolean> {\n const widget = await page.$('#widget');\n const box = await widget?.boundingBox();\n if (!box) return false;\n\n const x = box.x + offsetX + between(-CLICK_JITTER, CLICK_JITTER);\n const y = box.y + box.height / 2 + between(-CLICK_JITTER, CLICK_JITTER);\n\n await page.mouse.move(x, y, { steps: 8 });\n await page.mouse.click(x, y, { delay: between(40, 110) });\n return true;\n}\n\n/** Ждёт токен, периодически подталкивая виджет кликом. */\nasync function waitForToken(\n page: Page,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n browser: Browser,\n): Promise<string> {\n const startedAt = Date.now();\n const deadline = startedAt + options.timeout;\n\n // Ожидание разметки необязательное: виджет её меняет, и привязываться жёстко нельзя.\n // Настоящий срок задаёт цикл ниже.\n await page\n .waitForSelector(handler.widgetReadySelector, {\n timeout: Math.min(WIDGET_APPEAR_TIMEOUT, options.timeout),\n state: 'attached',\n })\n .catch(() => {});\n\n // Первый клик не мгновенный: мгновенное касание только что отрисованного виджета само\n // по себе признак автоматизации, а капча ИТД к тому же измеряет время до клика.\n let nextClickAt = Date.now() + between(HUMAN_DELAY[0], HUMAN_DELAY[1]);\n let lastError: string | undefined;\n\n while (Date.now() < deadline) {\n const state = await handler.readState(page);\n\n if (state.token) {\n options.logger?.info(`токен ${handler.label} получен за ${Date.now() - startedAt} мс`);\n return state.token;\n }\n\n if (state.error && state.error !== lastError) {\n if (handler.isPermanentWidgetError(state.error)) {\n throw new CaptchaError(\n CaptchaFailure.WidgetError,\n `Виджет ${handler.label} отказал для домена ${options.origin} (код ${state.error}): ` +\n 'ключ не разрешён для этого домена.',\n { type: handler.type, widgetCode: state.error },\n );\n }\n\n // Прочие коды временные: обрывать попытку рано, виджет пробует снова сам.\n lastError = state.error;\n options.logger?.debug(\n `виджет ${handler.label} сообщил об ошибке ${state.error}, ждём повтора`,\n );\n }\n\n if (Date.now() >= nextClickAt && (await clickCheckbox(page, handler.checkboxOffsetX))) {\n options.logger?.debug(`клик по чекбоксу ${handler.label}`);\n nextClickAt = Date.now() + CLICK_INTERVAL;\n }\n\n await sleep(POLL_INTERVAL);\n }\n\n const version = browserVersion(browser);\n\n throw new CaptchaError(\n CaptchaFailure.Timeout,\n `Виджет ${handler.label} не отдал токен за ${options.timeout} мс` +\n (lastError ? `; последняя ошибка виджета — ${lastError}` : '') +\n (version ? `; браузер — ${version}` : '') +\n '. Рабочие связки драйвера и браузера перечислены в README пакета.',\n lastError ? { type: handler.type, widgetCode: lastError } : { type: handler.type },\n );\n}\n\nasync function solveOnce(\n browser: Browser,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n // Каждая попытка идёт в чистом контексте: cookie и хранилище от неудачной попытки\n // достались бы следующей, а виджет их учитывает.\n const context = await browser.newContext(options.contextOptions ?? DEFAULT_CONTEXT_OPTIONS);\n\n try {\n const page = await context.newPage();\n const url = `${options.origin}/`;\n const body = handler.buildPage({ theme: options.theme });\n\n // На сайт запроса не уходит: навигация перехватывается и подменяется своей страницей.\n // Домен для браузера при этом настоящий, поэтому виджет проходит его проверку,\n // а сервер при проверке токена видит ожидаемый hostname.\n await page.route(url, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html; charset=utf-8', body }),\n );\n await page.goto(url, { waitUntil: 'domcontentloaded' });\n\n return await waitForToken(page, handler, options, browser);\n } finally {\n await context.close().catch(() => {});\n }\n}\n\n/** Ошибки настройки: повтор их не исправит. */\nfunction isPermanent(error: unknown): boolean {\n if (!(error instanceof CaptchaError)) return false;\n if (error.reason === CaptchaFailure.DriverMissing) return true;\n if (error.reason === CaptchaFailure.LaunchFailed) return true;\n if (error.reason === CaptchaFailure.BrowserClosed) return true;\n\n return error.reason === CaptchaFailure.WidgetError;\n}\n\n/**\n * Как драйверы сообщают, что страницы, контекста или браузера больше нет.\n *\n * Своего типа ошибки у них нет, поэтому остаётся текст сообщения.\n */\nconst CLOSED_MARKERS = [\n 'has been closed',\n 'target closed',\n 'browser has been closed',\n 'browser has disconnected',\n 'session closed',\n];\n\nfunction isClosedError(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return CLOSED_MARKERS.some((marker) => message.includes(marker));\n}\n\n/** Приводит закрытие браузера к {@link CaptchaError}; остальное отдаёт как есть. */\nfunction toCaptchaError(error: unknown, handler: CaptchaHandler): unknown {\n if (!isClosedError(error)) return error;\n\n return new CaptchaError(\n CaptchaFailure.BrowserClosed,\n `Браузер закрылся до того, как виджет ${handler.label} отдал токен.`,\n { type: handler.type },\n );\n}\n\n/**\n * Общий путь получения токена: поднимает браузер, решает виджет, закрывает браузер.\n *\n * Всё, что зависит от провайдера, приходит в {@link CaptchaHandler}; здесь остаётся только\n * то, что одинаково для любого виджета.\n *\n * @throws {CaptchaError} если токен получить не удалось\n */\nexport async function runSolver(\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n const owned = options.browser === undefined;\n if (owned) options.logger?.info(`запуск браузера для ${handler.label}`);\n const browser = options.browser ?? (await launchBrowser(options));\n\n try {\n for (let attempt = 1; attempt <= options.attempts; attempt++) {\n try {\n options.logger?.debug(`попытка ${attempt} из ${options.attempts} для ${handler.label}`);\n return await solveOnce(browser, handler, options);\n } catch (raw) {\n const error = toCaptchaError(raw, handler);\n if (isPermanent(error) || attempt === options.attempts) throw error;\n options.logger?.warn(\n `попытка ${attempt} из ${options.attempts} для ${handler.label} не удалась ` +\n `(${error instanceof Error ? error.message : String(error)}), повтор`,\n );\n }\n }\n\n // Недостижимо: последняя попытка либо возвращает токен, либо бросает ошибку.\n throw new CaptchaError(CaptchaFailure.Timeout, `Токен ${handler.label} получить не удалось`, {\n type: handler.type,\n });\n } finally {\n if (owned) await browser.close().catch(() => {});\n }\n}\n","import { type CaptchaLogger, resolveOptions, type SolveOptions } from './options.js';\nimport { type CaptchaTarget, resolveHandler } from './providers/registry.js';\nimport { runSolver } from './runner.js';\nimport type { CaptchaType } from './types.js';\n\n/**\n * Получает один токен капчи указанного типа.\n *\n * Поднимает браузер, решает виджет и закрывает браузер за собой.\n *\n * @example\n * ```ts\n * await solveCaptcha(CaptchaType.Itd);\n * await solveCaptcha(itdCaptcha({ action: 'register' }), { headless: true });\n * await solveCaptcha(myOwnHandler);\n * ```\n *\n * @throws {CaptchaError} если токен получить не удалось\n * @throws {TypeError} при некорректных настройках или незнакомом типе\n */\nexport async function solveCaptcha(\n target: CaptchaTarget,\n options: SolveOptions = {},\n): Promise<string> {\n const handler = resolveHandler(target);\n return runSolver(handler, resolveOptions(options));\n}\n\n/** Настройки источника токена для клиента `itd-api`. */\nexport interface CaptchaSolverOptions extends SolveOptions {\n /**\n * Какую капчу проходить всегда: `CaptchaType.Itd`, `CaptchaType.Cloudflare` либо готовый\n * провайдер из фабрики.\n *\n * Без этой настройки тип называет клиент, спросив активного провайдера у сервера.\n * Сервер принимает обе капчи, так что закреплённый тип избавляет от лишнего запроса.\n */\n type?: CaptchaTarget | undefined;\n /**\n * Поле тела запроса, в котором сервер ждёт токен.\n *\n * Нужно, только если сервер его переименовал: для закреплённого типа клиент берёт имя\n * из своей таблицы. Задаётся вместе с {@link CaptchaSolverOptions.type}.\n */\n field?: string | undefined;\n}\n\n/** Что клиент `itd-api` передаёт источнику при каждом обращении. */\nexport interface CaptchaSolveContext {\n /** Логгер клиента, если он включён. */\n logger?: CaptchaLogger | undefined;\n}\n\n/** Без своего логгера источник пишет ход решения в логгер клиента. */\nfunction withClientLogger<T extends SolveOptions>(\n options: T,\n context: CaptchaSolveContext | undefined,\n): T {\n if (options.logger || !context?.logger) return options;\n return { ...options, logger: context.logger };\n}\n\n/** Источник токена капчи для опции `captcha` клиента `itd-api`. */\nexport interface CaptchaSolver {\n /** Получает имя провайдера, чью капчу нужно пройти, и возвращает токен. */\n getToken(type: CaptchaType, context?: CaptchaSolveContext): Promise<string>;\n /** Тип, закреплённый за источником. Без него тип выбирает клиент. */\n readonly type?: CaptchaType | undefined;\n /** Поле тела запроса для закреплённого типа. */\n readonly field?: string | undefined;\n}\n\n/**\n * Собирает источник токена капчи для клиента `itd-api`.\n *\n * Токен одноразовый и живёт несколько минут, поэтому клиент спрашивает его заново перед\n * каждым запросом, которому нужна капча. Браузер поднимается на время одного вызова\n * и сразу закрывается.\n *\n * @throws {TypeError} если тип капчи незнаком или `field` задан без него\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n * import { createCaptchaSolver, CaptchaType } from '@itd-api/captcha';\n *\n * const itd = new ItdClient({\n * storage: new FileTokenStorage('./.itd-session.json'),\n * auth: { email: process.env.ITD_EMAIL!, password: process.env.ITD_PASSWORD! },\n * captcha: createCaptchaSolver({ type: CaptchaType.Cloudflare }),\n * });\n * ```\n */\nexport function createCaptchaSolver(options: CaptchaSolverOptions = {}): CaptchaSolver {\n const { type, field, ...solveOptions } = options;\n\n if (type === undefined) {\n return {\n getToken: (requested, context) =>\n solveCaptcha(requested, withClientLogger(solveOptions, context)),\n };\n }\n\n // Обработчик собирается сразу: незнакомый тип должен разбираться при создании клиента,\n // а не в момент входа.\n const handler = resolveHandler(type);\n const resolved = resolveOptions(solveOptions);\n\n return {\n type: handler.type,\n ...(field === undefined ? {} : { field }),\n getToken: (_requested, context) => runSolver(handler, withClientLogger(resolved, context)),\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -242,6 +242,13 @@ declare function launchBrowser(options: BrowserOptions): Promise<Browser>;
|
|
|
242
242
|
//#region src/options.d.ts
|
|
243
243
|
/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */
|
|
244
244
|
declare const DEFAULT_ORIGIN = "https://xn--d1ah4a.com";
|
|
245
|
+
/** Логгер хода решения. Совместим с `console` и с логгером клиента `itd-api`. */
|
|
246
|
+
interface CaptchaLogger {
|
|
247
|
+
debug(message: string, ...args: unknown[]): void;
|
|
248
|
+
info(message: string, ...args: unknown[]): void;
|
|
249
|
+
warn(message: string, ...args: unknown[]): void;
|
|
250
|
+
error(message: string, ...args: unknown[]): void;
|
|
251
|
+
}
|
|
245
252
|
/**
|
|
246
253
|
* Настройки получения токена, общие для любого виджета.
|
|
247
254
|
*
|
|
@@ -270,8 +277,8 @@ interface SolveOptions extends BrowserOptions {
|
|
|
270
277
|
* которые собирают отпечаток браузера сами, навязанный `locale` мешает — им передайте `{}`.
|
|
271
278
|
*/
|
|
272
279
|
contextOptions?: NewContextOptions | undefined;
|
|
273
|
-
/** Куда писать ход
|
|
274
|
-
logger?:
|
|
280
|
+
/** Куда писать ход решения, например `console`. Без него — в логгер клиента `itd-api`. */
|
|
281
|
+
logger?: CaptchaLogger | undefined;
|
|
275
282
|
}
|
|
276
283
|
//#endregion
|
|
277
284
|
//#region src/providers/itd.d.ts
|
|
@@ -365,10 +372,15 @@ interface CaptchaSolverOptions extends SolveOptions {
|
|
|
365
372
|
*/
|
|
366
373
|
field?: string | undefined;
|
|
367
374
|
}
|
|
375
|
+
/** Что клиент `itd-api` передаёт источнику при каждом обращении. */
|
|
376
|
+
interface CaptchaSolveContext {
|
|
377
|
+
/** Логгер клиента, если он включён. */
|
|
378
|
+
logger?: CaptchaLogger | undefined;
|
|
379
|
+
}
|
|
368
380
|
/** Источник токена капчи для опции `captcha` клиента `itd-api`. */
|
|
369
381
|
interface CaptchaSolver {
|
|
370
382
|
/** Получает имя провайдера, чью капчу нужно пройти, и возвращает токен. */
|
|
371
|
-
getToken(type: CaptchaType): Promise<string>;
|
|
383
|
+
getToken(type: CaptchaType, context?: CaptchaSolveContext): Promise<string>;
|
|
372
384
|
/** Тип, закреплённый за источником. Без него тип выбирает клиент. */
|
|
373
385
|
readonly type?: CaptchaType | undefined;
|
|
374
386
|
/** Поле тела запроса для закреплённого типа. */
|
|
@@ -398,5 +410,5 @@ interface CaptchaSolver {
|
|
|
398
410
|
*/
|
|
399
411
|
declare function createCaptchaSolver(options?: CaptchaSolverOptions): CaptchaSolver;
|
|
400
412
|
//#endregion
|
|
401
|
-
export { type Browser, type BrowserContext, type BrowserOptions, CaptchaError, CaptchaFailure, type CaptchaHandler, type CaptchaSolver, type CaptchaSolverOptions, type CaptchaTarget, CaptchaType, DEFAULT_CAPTCHA_ORIGIN, DEFAULT_ORIGIN, ITD_CAPTCHA_SITE_KEY, type ItdCaptchaOptions, type Loose, type NewContextOptions, type Page, type SolveOptions, TURNSTILE_SITE_KEY, type Theme, type TurnstileOptions, type WidgetState, createCaptchaSolver, itdCaptcha, launchBrowser, solveCaptcha, turnstile };
|
|
413
|
+
export { type Browser, type BrowserContext, type BrowserOptions, CaptchaError, CaptchaFailure, type CaptchaHandler, type CaptchaLogger, type CaptchaSolveContext, type CaptchaSolver, type CaptchaSolverOptions, type CaptchaTarget, CaptchaType, DEFAULT_CAPTCHA_ORIGIN, DEFAULT_ORIGIN, ITD_CAPTCHA_SITE_KEY, type ItdCaptchaOptions, type Loose, type NewContextOptions, type Page, type SolveOptions, TURNSTILE_SITE_KEY, type Theme, type TurnstileOptions, type WidgetState, createCaptchaSolver, itdCaptcha, launchBrowser, solveCaptcha, turnstile };
|
|
402
414
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -242,6 +242,13 @@ declare function launchBrowser(options: BrowserOptions): Promise<Browser>;
|
|
|
242
242
|
//#region src/options.d.ts
|
|
243
243
|
/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */
|
|
244
244
|
declare const DEFAULT_ORIGIN = "https://xn--d1ah4a.com";
|
|
245
|
+
/** Логгер хода решения. Совместим с `console` и с логгером клиента `itd-api`. */
|
|
246
|
+
interface CaptchaLogger {
|
|
247
|
+
debug(message: string, ...args: unknown[]): void;
|
|
248
|
+
info(message: string, ...args: unknown[]): void;
|
|
249
|
+
warn(message: string, ...args: unknown[]): void;
|
|
250
|
+
error(message: string, ...args: unknown[]): void;
|
|
251
|
+
}
|
|
245
252
|
/**
|
|
246
253
|
* Настройки получения токена, общие для любого виджета.
|
|
247
254
|
*
|
|
@@ -270,8 +277,8 @@ interface SolveOptions extends BrowserOptions {
|
|
|
270
277
|
* которые собирают отпечаток браузера сами, навязанный `locale` мешает — им передайте `{}`.
|
|
271
278
|
*/
|
|
272
279
|
contextOptions?: NewContextOptions | undefined;
|
|
273
|
-
/** Куда писать ход
|
|
274
|
-
logger?:
|
|
280
|
+
/** Куда писать ход решения, например `console`. Без него — в логгер клиента `itd-api`. */
|
|
281
|
+
logger?: CaptchaLogger | undefined;
|
|
275
282
|
}
|
|
276
283
|
//#endregion
|
|
277
284
|
//#region src/providers/itd.d.ts
|
|
@@ -365,10 +372,15 @@ interface CaptchaSolverOptions extends SolveOptions {
|
|
|
365
372
|
*/
|
|
366
373
|
field?: string | undefined;
|
|
367
374
|
}
|
|
375
|
+
/** Что клиент `itd-api` передаёт источнику при каждом обращении. */
|
|
376
|
+
interface CaptchaSolveContext {
|
|
377
|
+
/** Логгер клиента, если он включён. */
|
|
378
|
+
logger?: CaptchaLogger | undefined;
|
|
379
|
+
}
|
|
368
380
|
/** Источник токена капчи для опции `captcha` клиента `itd-api`. */
|
|
369
381
|
interface CaptchaSolver {
|
|
370
382
|
/** Получает имя провайдера, чью капчу нужно пройти, и возвращает токен. */
|
|
371
|
-
getToken(type: CaptchaType): Promise<string>;
|
|
383
|
+
getToken(type: CaptchaType, context?: CaptchaSolveContext): Promise<string>;
|
|
372
384
|
/** Тип, закреплённый за источником. Без него тип выбирает клиент. */
|
|
373
385
|
readonly type?: CaptchaType | undefined;
|
|
374
386
|
/** Поле тела запроса для закреплённого типа. */
|
|
@@ -398,5 +410,5 @@ interface CaptchaSolver {
|
|
|
398
410
|
*/
|
|
399
411
|
declare function createCaptchaSolver(options?: CaptchaSolverOptions): CaptchaSolver;
|
|
400
412
|
//#endregion
|
|
401
|
-
export { type Browser, type BrowserContext, type BrowserOptions, CaptchaError, CaptchaFailure, type CaptchaHandler, type CaptchaSolver, type CaptchaSolverOptions, type CaptchaTarget, CaptchaType, DEFAULT_CAPTCHA_ORIGIN, DEFAULT_ORIGIN, ITD_CAPTCHA_SITE_KEY, type ItdCaptchaOptions, type Loose, type NewContextOptions, type Page, type SolveOptions, TURNSTILE_SITE_KEY, type Theme, type TurnstileOptions, type WidgetState, createCaptchaSolver, itdCaptcha, launchBrowser, solveCaptcha, turnstile };
|
|
413
|
+
export { type Browser, type BrowserContext, type BrowserOptions, CaptchaError, CaptchaFailure, type CaptchaHandler, type CaptchaLogger, type CaptchaSolveContext, type CaptchaSolver, type CaptchaSolverOptions, type CaptchaTarget, CaptchaType, DEFAULT_CAPTCHA_ORIGIN, DEFAULT_ORIGIN, ITD_CAPTCHA_SITE_KEY, type ItdCaptchaOptions, type Loose, type NewContextOptions, type Page, type SolveOptions, TURNSTILE_SITE_KEY, type Theme, type TurnstileOptions, type WidgetState, createCaptchaSolver, itdCaptcha, launchBrowser, solveCaptcha, turnstile };
|
|
402
414
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -53,9 +53,8 @@ const DEFAULT_ARGS = ["--disable-dev-shm-usage", "--disable-blink-features=Autom
|
|
|
53
53
|
/**
|
|
54
54
|
* Драйверы в порядке предпочтения.
|
|
55
55
|
*
|
|
56
|
-
* `patchright` впереди намеренно:
|
|
57
|
-
*
|
|
58
|
-
* ничего не меняется — берётся он.
|
|
56
|
+
* `patchright` впереди намеренно: проверенная связка `patchright@1.61.1` со штатным
|
|
57
|
+
* Chromium 149 проходит виджет. Если стоит только `playwright`, берётся он.
|
|
59
58
|
*/
|
|
60
59
|
const DRIVERS = [
|
|
61
60
|
"patchright",
|
|
@@ -100,7 +99,7 @@ async function loadDriver(requested) {
|
|
|
100
99
|
} catch (error) {
|
|
101
100
|
if (!isModuleNotFound(error)) throw error;
|
|
102
101
|
}
|
|
103
|
-
throw new CaptchaError(CaptchaFailure.DriverMissing, requested ? `Драйвер ${requested} не установлен.` : "Не найден драйвер браузера. Установите
|
|
102
|
+
throw new CaptchaError(CaptchaFailure.DriverMissing, requested ? `Драйвер ${requested} не установлен.` : "Не найден драйвер браузера. Установите проверенную связку командой: npm i patchright@1.61.1 && npx patchright install chromium. Либо передайте свой запуск браузера через параметр launch.");
|
|
104
103
|
}
|
|
105
104
|
/** Собирает параметры запуска, не поднимая браузер. @internal */
|
|
106
105
|
function resolveLaunchOptions(options, driver = "") {
|
|
@@ -139,6 +138,12 @@ async function launchBrowser(options) {
|
|
|
139
138
|
//#region src/options.ts
|
|
140
139
|
/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */
|
|
141
140
|
const DEFAULT_ORIGIN = "https://xn--d1ah4a.com";
|
|
141
|
+
const LOGGER_METHODS = [
|
|
142
|
+
"debug",
|
|
143
|
+
"info",
|
|
144
|
+
"warn",
|
|
145
|
+
"error"
|
|
146
|
+
];
|
|
142
147
|
/**
|
|
143
148
|
* Приводит адрес к корню и проверяет его пригодность.
|
|
144
149
|
*
|
|
@@ -171,7 +176,10 @@ function resolveOptions(options) {
|
|
|
171
176
|
if (theme !== "auto" && theme !== "light" && theme !== "dark") throw new TypeError("theme должен быть 'auto', 'light' или 'dark'");
|
|
172
177
|
for (const [name, value] of [["headless", options.headless], ["disableSandbox", options.disableSandbox]]) if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`${name} должен быть boolean`);
|
|
173
178
|
for (const [name, value] of [["driver", options.driver], ["channel", options.channel]]) if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new TypeError(`${name} должен быть непустой строкой`);
|
|
174
|
-
if (options.logger !== void 0
|
|
179
|
+
if (options.logger !== void 0) {
|
|
180
|
+
if (typeof options.logger !== "object" || options.logger === null) throw new TypeError("logger должен быть объектом с методами debug, info, warn и error");
|
|
181
|
+
for (const method of LOGGER_METHODS) if (typeof options.logger[method] !== "function") throw new TypeError(`logger.${method} должен быть функцией`);
|
|
182
|
+
}
|
|
175
183
|
if (options.contextOptions !== void 0 && (typeof options.contextOptions !== "object" || options.contextOptions === null || Array.isArray(options.contextOptions))) throw new TypeError("contextOptions должен быть объектом");
|
|
176
184
|
if (options.launch !== void 0 && typeof options.launch !== "function") throw new TypeError("launch должен быть функцией");
|
|
177
185
|
if (options.args !== void 0 && (!Array.isArray(options.args) || options.args.some((argument) => typeof argument !== "string"))) throw new TypeError("args должен быть массивом строк");
|
|
@@ -520,7 +528,8 @@ async function clickCheckbox(page, offsetX) {
|
|
|
520
528
|
}
|
|
521
529
|
/** Ждёт токен, периодически подталкивая виджет кликом. */
|
|
522
530
|
async function waitForToken(page, handler, options, browser) {
|
|
523
|
-
const
|
|
531
|
+
const startedAt = Date.now();
|
|
532
|
+
const deadline = startedAt + options.timeout;
|
|
524
533
|
await page.waitForSelector(handler.widgetReadySelector, {
|
|
525
534
|
timeout: Math.min(WIDGET_APPEAR_TIMEOUT, options.timeout),
|
|
526
535
|
state: "attached"
|
|
@@ -530,7 +539,7 @@ async function waitForToken(page, handler, options, browser) {
|
|
|
530
539
|
while (Date.now() < deadline) {
|
|
531
540
|
const state = await handler.readState(page);
|
|
532
541
|
if (state.token) {
|
|
533
|
-
options.logger?.(
|
|
542
|
+
options.logger?.info(`токен ${handler.label} получен за ${Date.now() - startedAt} мс`);
|
|
534
543
|
return state.token;
|
|
535
544
|
}
|
|
536
545
|
if (state.error && state.error !== lastError) {
|
|
@@ -539,10 +548,10 @@ async function waitForToken(page, handler, options, browser) {
|
|
|
539
548
|
widgetCode: state.error
|
|
540
549
|
});
|
|
541
550
|
lastError = state.error;
|
|
542
|
-
options.logger?.(`виджет сообщил об ошибке ${state.error}, ждём повтора`);
|
|
551
|
+
options.logger?.debug(`виджет ${handler.label} сообщил об ошибке ${state.error}, ждём повтора`);
|
|
543
552
|
}
|
|
544
553
|
if (Date.now() >= nextClickAt && await clickCheckbox(page, handler.checkboxOffsetX)) {
|
|
545
|
-
options.logger?.(
|
|
554
|
+
options.logger?.debug(`клик по чекбоксу ${handler.label}`);
|
|
546
555
|
nextClickAt = Date.now() + CLICK_INTERVAL;
|
|
547
556
|
}
|
|
548
557
|
await sleep(POLL_INTERVAL);
|
|
@@ -608,16 +617,17 @@ function toCaptchaError(error, handler) {
|
|
|
608
617
|
* @throws {CaptchaError} если токен получить не удалось
|
|
609
618
|
*/
|
|
610
619
|
async function runSolver(handler, options) {
|
|
611
|
-
const browser = options.browser ?? await launchBrowser(options);
|
|
612
620
|
const owned = options.browser === void 0;
|
|
621
|
+
if (owned) options.logger?.info(`запуск браузера для ${handler.label}`);
|
|
622
|
+
const browser = options.browser ?? await launchBrowser(options);
|
|
613
623
|
try {
|
|
614
624
|
for (let attempt = 1; attempt <= options.attempts; attempt++) try {
|
|
615
|
-
options.logger?.(`попытка ${attempt} из ${options.attempts}`);
|
|
625
|
+
options.logger?.debug(`попытка ${attempt} из ${options.attempts} для ${handler.label}`);
|
|
616
626
|
return await solveOnce(browser, handler, options);
|
|
617
627
|
} catch (raw) {
|
|
618
628
|
const error = toCaptchaError(raw, handler);
|
|
619
629
|
if (isPermanent(error) || attempt === options.attempts) throw error;
|
|
620
|
-
options.logger?.(`попытка ${attempt} не
|
|
630
|
+
options.logger?.warn(`попытка ${attempt} из ${options.attempts} для ${handler.label} не удалась (${error instanceof Error ? error.message : String(error)}), повтор`);
|
|
621
631
|
}
|
|
622
632
|
throw new CaptchaError(CaptchaFailure.Timeout, `Токен ${handler.label} получить не удалось`, { type: handler.type });
|
|
623
633
|
} finally {
|
|
@@ -644,6 +654,14 @@ async function runSolver(handler, options) {
|
|
|
644
654
|
async function solveCaptcha(target, options = {}) {
|
|
645
655
|
return runSolver(resolveHandler(target), resolveOptions(options));
|
|
646
656
|
}
|
|
657
|
+
/** Без своего логгера источник пишет ход решения в логгер клиента. */
|
|
658
|
+
function withClientLogger(options, context) {
|
|
659
|
+
if (options.logger || !context?.logger) return options;
|
|
660
|
+
return {
|
|
661
|
+
...options,
|
|
662
|
+
logger: context.logger
|
|
663
|
+
};
|
|
664
|
+
}
|
|
647
665
|
/**
|
|
648
666
|
* Собирает источник токена капчи для клиента `itd-api`.
|
|
649
667
|
*
|
|
@@ -668,13 +686,13 @@ async function solveCaptcha(target, options = {}) {
|
|
|
668
686
|
*/
|
|
669
687
|
function createCaptchaSolver(options = {}) {
|
|
670
688
|
const { type, field, ...solveOptions } = options;
|
|
671
|
-
if (type === void 0) return { getToken: (requested) => solveCaptcha(requested, solveOptions) };
|
|
689
|
+
if (type === void 0) return { getToken: (requested, context) => solveCaptcha(requested, withClientLogger(solveOptions, context)) };
|
|
672
690
|
const handler = resolveHandler(type);
|
|
673
691
|
const resolved = resolveOptions(solveOptions);
|
|
674
692
|
return {
|
|
675
693
|
type: handler.type,
|
|
676
694
|
...field === void 0 ? {} : { field },
|
|
677
|
-
getToken: () => runSolver(handler, resolved)
|
|
695
|
+
getToken: (_requested, context) => runSolver(handler, withClientLogger(resolved, context))
|
|
678
696
|
};
|
|
679
697
|
}
|
|
680
698
|
//#endregion
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["WIDTH","HEIGHT","CHECKBOX_OFFSET_X","readState","CaptchaTypeValues"],"sources":["../src/errors.ts","../src/launch.ts","../src/options.ts","../src/html.ts","../src/types.ts","../src/providers/itd.ts","../src/providers/turnstile.ts","../src/providers/registry.ts","../src/runner.ts","../src/solve.ts"],"sourcesContent":["import type { CaptchaType } from './types.js';\n\n/** Почему не удалось получить токен. */\nexport const CaptchaFailure = Object.freeze({\n /** Драйвер браузера не установлен. */\n DriverMissing: 'driver-missing',\n /** Браузер не запустился: нет исполняемого файла, нет дисплея, отказ песочницы. */\n LaunchFailed: 'launch-failed',\n /** Браузер или страница закрылись, пока решался виджет. */\n BrowserClosed: 'browser-closed',\n /** Виджет не отдал токен за отведённое время. */\n Timeout: 'timeout',\n /** Сам виджет сообщил об ошибке — код лежит в `widgetCode`. */\n WidgetError: 'widget-error',\n} as const);\nexport type CaptchaFailure = (typeof CaptchaFailure)[keyof typeof CaptchaFailure];\n\n/**\n * Ошибка получения токена капчи.\n *\n * Причина в {@link CaptchaError.reason} определяет, что с ошибкой делать: отсутствие драйвера\n * чинится установкой, таймаут — повтором, а ошибка виджета повтором не лечится. Поле\n * {@link CaptchaError.type} говорит, чей именно виджет решался, — оно есть не у всех ошибок\n * (запуск браузера происходит до выбора виджета).\n */\nexport class CaptchaError extends Error {\n readonly reason: CaptchaFailure;\n /** Какой провайдер решался в момент ошибки. */\n readonly type: CaptchaType | undefined;\n /**\n * Код ошибки виджета, если он его сообщил.\n *\n * Для Cloudflare самые частые: `110200` — домен не разрешён для этого ключа, `300***`\n * и `600***` — внутренние сбои виджета, лечатся повтором.\n */\n readonly widgetCode: string | undefined;\n\n constructor(\n reason: CaptchaFailure,\n message: string,\n options: { type?: CaptchaType; widgetCode?: string } = {},\n ) {\n super(message);\n this.name = 'CaptchaError';\n this.reason = reason;\n this.type = options.type;\n this.widgetCode = options.widgetCode;\n }\n}\n","import type { Browser, DriverModule, LaunchOptions } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\n\n/**\n * Аргументы запуска Chromium.\n *\n * Подмены `User-Agent` здесь нет намеренно: заявленная версия браузера расходилась бы\n * с реальным движком, а такое расхождение само по себе служит признаком автоматизации.\n * Остаются безопасные общие флаги. Отключение sandbox вынесено в явную настройку:\n * удалённый код виджета исполняется в браузере, и ослаблять его изоляцию по умолчанию нельзя.\n */\nconst DEFAULT_ARGS = ['--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled'];\n\n/**\n * Драйверы в порядке предпочтения.\n *\n * `patchright` впереди намеренно: он ставит собственную сборку Chromium, и та проходит\n * виджет там, где сборка из свежего Playwright уже нет. Если стоит только `playwright`,\n * ничего не меняется — берётся он.\n */\nconst DRIVERS = ['patchright', 'playwright', 'playwright-core'];\n\n/**\n * Как выглядит имя пакета драйвера.\n *\n * Имя приходит извне и уезжает в динамический импорт, поэтому путями и относительными\n * адресами быть не должно — только имя пакета.\n */\nconst DRIVER_NAME = /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*$/i;\n\n/**\n * Драйверы, которые сами приводят аргументы запуска в порядок.\n *\n * Им ничего не подмешивается: набор флагов у них выверен, а лишний флаг — такой же след,\n * как и недостающий.\n */\nconst SELF_TUNED_DRIVERS = new Set(['patchright']);\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | null)?.code;\n return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';\n}\n\n/**\n * Подключает драйвер браузера.\n *\n * Импорт динамический: драйвер объявлен необязательной одноранговой зависимостью,\n * поэтому его может не быть вовсе. Названный явно берётся один, без перебора: молча\n * подставить вместо него другой значило бы проверить не то, что просили.\n */\nasync function loadDriver(requested?: string): Promise<{ driver: string; module: DriverModule }> {\n const wanted = requested ? [requested] : DRIVERS;\n\n for (const name of wanted) {\n try {\n return { driver: name, module: (await import(/* @vite-ignore */ name)) as DriverModule };\n } catch (error) {\n // Установленный драйвер, упавший при загрузке, — не то же самое, что отсутствующий.\n // Его ошибку нужно показать как есть, а не подменять советом установить пакет.\n if (!isModuleNotFound(error)) throw error;\n }\n }\n\n throw new CaptchaError(\n CaptchaFailure.DriverMissing,\n requested\n ? `Драйвер ${requested} не установлен.`\n : 'Не найден драйвер браузера. Установите его командой: npm i patchright && npx patchright install chromium. ' +\n 'Либо передайте свой запуск браузера через параметр launch.',\n );\n}\n\n/** Настройки запуска браузера. */\nexport interface BrowserOptions {\n /**\n * Запускать ли браузер без окна. По умолчанию `false`.\n *\n * В безоконном режиме виджет проходится заметно хуже: признаки такого режима видны\n * странице. На сервере поднимите виртуальный дисплей (`xvfb-run -a node bot.js`).\n */\n headless?: boolean | undefined;\n /**\n * Какой драйвер брать вместо перебора `patchright` → `playwright` → `playwright-core`.\n *\n * Нужен, когда установлено несколько и важно, какой именно поднимется. Названный\n * драйвер берётся один: не найден — ошибка, а не молчаливая подмена соседним.\n */\n driver?: string | undefined;\n /** Путь к исполняемому файлу браузера, если он лежит не там, где его ищет драйвер. */\n executablePath?: string | undefined;\n /** Канал браузера, например `chrome` или `msedge`, — вместо сборки из комплекта драйвера. */\n channel?: string | undefined;\n /** Дополнительные аргументы командной строки — добавляются к стандартным. */\n args?: readonly string[] | undefined;\n /**\n * Отключить sandbox Chromium. По умолчанию `false`.\n *\n * Используйте только внутри изолированного контейнера, где sandbox невозможно настроить:\n * страница исполняет удалённый код виджета, и без изоляции браузера его компрометация\n * получает больше доступа к процессу и системе.\n */\n disableSandbox?: boolean | undefined;\n /** Прокси для браузера. */\n proxy?: { server: string; username?: string; password?: string } | undefined;\n /**\n * Свой запуск браузера. Заменяет все остальные параметры запуска.\n *\n * Нужен, чтобы подставить другой драйвер или подключиться к уже работающему браузеру.\n *\n * @example\n * ```ts\n * launch: async () => {\n * const { chromium } = await import('patchright');\n * return chromium.launch({ headless: false });\n * };\n * ```\n */\n launch?: (() => Promise<Browser>) | undefined;\n}\n\n/** Собирает параметры запуска, не поднимая браузер. @internal */\nexport function resolveLaunchOptions(options: BrowserOptions, driver = ''): LaunchOptions {\n return {\n headless: options.headless ?? false,\n args: [\n ...(SELF_TUNED_DRIVERS.has(driver) ? [] : DEFAULT_ARGS),\n ...(options.disableSandbox ? ['--no-sandbox'] : []),\n ...(options.args ?? []),\n ],\n ...(options.executablePath ? { executablePath: options.executablePath } : {}),\n ...(options.channel ? { channel: options.channel } : {}),\n ...(options.proxy ? { proxy: options.proxy } : {}),\n };\n}\n\n/**\n * Поднимает браузер по настройкам.\n *\n * Тем же путём, что и солверы: те же флаги, тот же порядок драйверов. Пригодится, чтобы\n * поднять браузер один раз на несколько токенов и передать его в `browser`, — и чтобы\n * проверять связки драйвера и сборки ровно в том виде, в каком их поднимает пакет.\n */\nexport async function launchBrowser(options: BrowserOptions): Promise<Browser> {\n if (options.launch) return options.launch();\n\n if (options.driver !== undefined && !DRIVER_NAME.test(options.driver)) {\n throw new TypeError(`driver должен быть именем пакета, получено: ${options.driver}`);\n }\n\n const {\n driver,\n module: { chromium },\n } = await loadDriver(options.driver);\n const launchOptions = resolveLaunchOptions(options, driver);\n\n try {\n return await chromium.launch(launchOptions);\n } catch (error) {\n const hint =\n launchOptions.headless === false\n ? ' Если это сервер без графической оболочки, запустите процесс через xvfb-run -a.'\n : '';\n\n throw new CaptchaError(\n CaptchaFailure.LaunchFailed,\n `Не удалось запустить браузер: ${error instanceof Error ? error.message : String(error)}.${hint}`,\n );\n }\n}\n","import type { Browser, NewContextOptions } from './driver.js';\nimport type { BrowserOptions } from './launch.js';\nimport type { Theme } from './types.js';\n\n/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */\nexport const DEFAULT_ORIGIN = 'https://xn--d1ah4a.com';\n\n/**\n * Настройки получения токена, общие для любого виджета.\n *\n * Ключ, назначение токена и адрес виджета задаются при создании провайдера:\n * `solveCaptcha(itdCaptcha({ action: 'register' }))`.\n */\nexport interface SolveOptions extends BrowserOptions {\n /** Сайт, чей виджет решается. По умолчанию {@link DEFAULT_ORIGIN}. */\n origin?: string | undefined;\n /** Оформление виджета. По умолчанию `auto`. */\n theme?: Theme | undefined;\n /** Сколько ждать токен, мс. По умолчанию 60000. */\n timeout?: number | undefined;\n /** Сколько попыток делать при неудаче. По умолчанию 2. */\n attempts?: number | undefined;\n /**\n * Готовый браузер. Тогда пакет его не запускает и не закрывает.\n *\n * Пригодится, если браузер уже поднят для чего-то ещё.\n */\n browser?: Browser | undefined;\n /**\n * Настройки контекста. Заменяют стандартные целиком, а не дополняют их.\n *\n * По умолчанию `{ locale: 'ru-RU', viewport: { width: 1280, height: 800 } }`. Драйверам,\n * которые собирают отпечаток браузера сами, навязанный `locale` мешает — им передайте `{}`.\n */\n contextOptions?: NewContextOptions | undefined;\n /** Куда писать ход решения. Например `console.debug`. */\n logger?: ((message: string) => void) | undefined;\n}\n\n/** Настройки после подстановки умолчаний. @internal */\nexport interface ResolvedOptions extends SolveOptions {\n origin: string;\n theme: Theme;\n timeout: number;\n attempts: number;\n}\n\n/**\n * Приводит адрес к корню и проверяет его пригодность.\n *\n * Виджет привязан к домену, а не к пути: адрес приводится к `URL.origin`, чтобы перехват навигации\n * совпал с ним ровно один раз.\n */\nexport function resolveOrigin(origin: string, field: string): string {\n let parsed: URL;\n try {\n parsed = new URL(origin);\n } catch {\n throw new TypeError(`${field} должен быть абсолютным URL, получено: ${origin}`);\n }\n\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new TypeError(`${field} должен быть http или https, получено: ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new TypeError(\n `${field} не должен содержать логин, пароль, параметры запроса или фрагмент`,\n );\n }\n\n return parsed.origin;\n}\n\n/**\n * Проверяет общие настройки и подставляет умолчания.\n *\n * @throws {TypeError} при некорректных значениях\n */\nexport function resolveOptions(options: SolveOptions): ResolvedOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) {\n throw new TypeError('options должен быть объектом');\n }\n\n const timeout = options.timeout ?? 60_000;\n const attempts = options.attempts ?? 2;\n const theme = options.theme ?? 'auto';\n\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new TypeError(`timeout должен быть положительным числом, получено: ${timeout}`);\n }\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new TypeError(`attempts должен быть целым числом от 1, получено: ${attempts}`);\n }\n if (theme !== 'auto' && theme !== 'light' && theme !== 'dark') {\n throw new TypeError(\"theme должен быть 'auto', 'light' или 'dark'\");\n }\n for (const [name, value] of [\n ['headless', options.headless],\n ['disableSandbox', options.disableSandbox],\n ] as const) {\n if (value !== undefined && typeof value !== 'boolean') {\n throw new TypeError(`${name} должен быть boolean`);\n }\n }\n for (const [name, value] of [\n ['driver', options.driver],\n ['channel', options.channel],\n ] as const) {\n if (value !== undefined && (typeof value !== 'string' || value.trim() === '')) {\n throw new TypeError(`${name} должен быть непустой строкой`);\n }\n }\n if (options.logger !== undefined && typeof options.logger !== 'function') {\n throw new TypeError('logger должен быть функцией');\n }\n if (\n options.contextOptions !== undefined &&\n (typeof options.contextOptions !== 'object' ||\n options.contextOptions === null ||\n Array.isArray(options.contextOptions))\n ) {\n throw new TypeError('contextOptions должен быть объектом');\n }\n if (options.launch !== undefined && typeof options.launch !== 'function') {\n throw new TypeError('launch должен быть функцией');\n }\n if (\n options.args !== undefined &&\n (!Array.isArray(options.args) || options.args.some((argument) => typeof argument !== 'string'))\n ) {\n throw new TypeError('args должен быть массивом строк');\n }\n\n return {\n ...options,\n origin: resolveOrigin(options.origin ?? DEFAULT_ORIGIN, 'origin'),\n theme,\n timeout,\n attempts,\n };\n}\n\n/** Требует непустую строку: общая проверка для настроек провайдеров. @internal */\nexport function requireText(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new TypeError(`${field} должен быть непустой строкой`);\n }\n return value;\n}\n","/**\n * Сборка страницы, на которой живёт виджет.\n *\n * Страница отдаётся вместо настоящей — по адресу целевого сайта, через перехват навигации.\n * Благодаря этому `document.location.origin` для виджета настоящий, и привязка ключа\n * к домену не нарушается, а форма входа и пароль в браузере не участвуют.\n *\n * Каркас общий для всех провайдеров: контейнер `#widget` известного размера, от которого\n * общий цикл отсчитывает координаты клика. Различия провайдеров — в `head` и `body`.\n */\n\n/** Отступ контейнера от края окна, px. Виджету нужно место, чтобы раскрыть карточку. */\nconst WIDGET_MARGIN = 40;\n\n/**\n * Готовит значение к вставке в инлайновый скрипт или атрибут.\n *\n * Одного `JSON.stringify` мало: кавычки он экранирует, а `</script>` — нет, и такая\n * последовательность закрывает тег независимо от того, внутри строки она или нет.\n * Экранированный `<` разбирается движком как обычный символ, но парсер HTML его уже не видит.\n */\nexport function embed(value: string): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n/** Из чего собирается страница одного виджета. */\nexport interface PageParts {\n /** Заголовок вкладки. На проверку не влияет, но пустой заголовок выглядит подозрительно. */\n title: string;\n /** Размеры контейнера `#widget`, px. Столько же виджет занимает на самом сайте. */\n width: number;\n height: number;\n /** Дополнительные правила для `#widget` — например, если контейнер сам является iframe. */\n widgetStyle?: string | undefined;\n /** Содержимое `head`: скрипты и обработчики провайдера. */\n head?: string | undefined;\n /** Содержимое `body`: сам контейнер `#widget` и поля для результата. */\n body: string;\n}\n\n/** Собирает страницу с одним виджетом. */\nexport function buildWidgetPage(parts: PageParts): string {\n return `<!doctype html>\n<html lang=\"ru\">\n<head>\n<meta charset=\"utf-8\">\n<title>${parts.title}</title>\n<style>\n html, body { margin: 0; padding: 0; background: #fff; }\n #widget {\n width: ${parts.width}px;\n height: ${parts.height}px;\n margin: ${WIDGET_MARGIN}px;${parts.widgetStyle ?? ''}\n }\n</style>\n${parts.head ?? ''}\n</head>\n<body>\n${parts.body}\n</body>\n</html>`;\n}\n","/**\n * Открытый строковый enum.\n *\n * Даёт автодополнение известных значений, но не ломается, если появится новое.\n */\nexport type Loose<T extends string> = T | (string & {});\n\n/** Провайдер капчи — теми же именами, какими его называет сервер итд.com. */\nexport const CaptchaType = Object.freeze({\n /** Собственная капча ИТД: карточка «Я не робот» в iframe с `captcha.итд.com`. */\n Itd: 'itd',\n /** Cloudflare Turnstile. */\n Cloudflare: 'cloudflare',\n} as const);\nexport type CaptchaType = Loose<(typeof CaptchaType)[keyof typeof CaptchaType]>;\n\n/** Оформление виджета. */\nexport type Theme = 'auto' | 'light' | 'dark';\n\n/** Результат виджета, снятый со страницы через `page.evaluate`. */\nexport interface WidgetState {\n token: string | null;\n error: string | null;\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText, resolveOrigin } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/** Базовый адрес виджета собственной капчи ИТД. Домен в punycode: `captcha.итд.com`. */\nexport const DEFAULT_CAPTCHA_ORIGIN = 'https://captcha.xn--d1ah4a.com';\n\n/**\n * Публичный ключ виджета собственной капчи ИТД.\n *\n * Как и ключ Turnstile, он публичный и привязан к домену итд.com: виджет отдаёт его браузеру\n * каждому посетителю.\n */\nexport const ITD_CAPTCHA_SITE_KEY = 'sk_44d64cf7bf8bc8377f5b';\n\n/** Назначение токена по умолчанию. Сервер сверяет его с операцией. */\nconst DEFAULT_ACTION = 'login';\n\n/** Размеры карточки, px. Их же виджет сообщает сайту через `postMessage`. */\nconst WIDTH = 300;\nconst HEIGHT = 74;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 34;\n\n/**\n * Собирает страницу с одним виджетом собственной капчи ИТД.\n *\n * Виджет — iframe чужого происхождения (`captcha.итд.com`), и наружу он отдаёт токен\n * единственным способом: `postMessage` в родителя. Эта страница ровно так его и слушает —\n * как сам сайт.\n *\n * Слушатель кладёт результат в скрытые поля, а не в переменную `window`: стелс-драйверы вроде\n * `patchright` выполняют `page.evaluate` в изолированном мире, откуда переменные страницы\n * не видны, зато DOM общий.\n *\n * `action` задаёт назначение токена (`login`, `register`, `password_reset`) — сервер сверяет\n * его с операцией, поэтому он не выдумывается, а приходит из настроек обработчика.\n */\nexport function buildItdCaptchaPage(input: {\n sitekey: string;\n theme: Theme;\n action: string;\n captchaOrigin: string;\n}): string {\n const origin = embed(input.captchaOrigin);\n const src = embed(\n `${input.captchaOrigin}/widget.html` +\n `?sitekey=${encodeURIComponent(input.sitekey)}` +\n `&theme=${encodeURIComponent(input.theme)}` +\n `&action=${encodeURIComponent(input.action)}`,\n );\n\n return buildWidgetPage({\n title: 'Проверка',\n width: WIDTH,\n height: HEIGHT,\n widgetStyle: `\n border: 0;\n display: block;`,\n head: `<script>\n window.addEventListener('message', function (event) {\n if (event.origin !== ${origin}) return;\n var data;\n try {\n data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;\n } catch (e) {\n return;\n }\n if (!data) return;\n var token = document.getElementById('itd-token');\n var error = document.getElementById('itd-error');\n if (data.type === 'token' && data.token) token.value = data.token;\n else if (data.type === 'expired') token.value = '';\n else if (data.type === 'error') error.value = String(data.code || 'error');\n });\n</script>`,\n body: `<input type=\"hidden\" id=\"itd-token\">\n<input type=\"hidden\" id=\"itd-error\">\n<iframe id=\"widget\" src=${src} title=\"Проверка\"></iframe>`,\n });\n}\n\n/**\n * Читает результат виджета капчи ИТД.\n *\n * Токен приходит в родителя через `postMessage`, и слушатель страницы кладёт его в скрытое\n * поле. Читаем из DOM, а не из `window`: стелс-драйверы выполняют `evaluate` в изолированном\n * мире, где переменных страницы нет, а DOM общий.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const token = document.querySelector('#itd-token') as { value?: string } | null;\n const error = document.querySelector('#itd-error') as { value?: string } | null;\n return {\n token: token?.value || null,\n error: error?.value || null,\n };\n });\n}\n\n/** Настройки виджета собственной капчи ИТД. */\nexport interface ItdCaptchaOptions {\n /** Публичный ключ виджета. По умолчанию {@link ITD_CAPTCHA_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n /**\n * Назначение токена: `login`, `register`, `password_reset`. По умолчанию `login`.\n *\n * Сервер сверяет назначение с операцией, поэтому для регистрации или сброса пароля нужен\n * токен с соответствующим `action`.\n */\n action?: string | undefined;\n /** Базовый адрес виджета. По умолчанию {@link DEFAULT_CAPTCHA_ORIGIN}. */\n captchaOrigin?: string | undefined;\n}\n\n/**\n * Создаёт обработчик собственной капчи ИТД.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function itdCaptcha(options: ItdCaptchaOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? ITD_CAPTCHA_SITE_KEY, 'sitekey');\n const action = requireText(options.action ?? DEFAULT_ACTION, 'action');\n const captchaOrigin = resolveOrigin(\n options.captchaOrigin ?? DEFAULT_CAPTCHA_ORIGIN,\n 'captchaOrigin',\n );\n\n return {\n type: CaptchaType.Itd,\n label: 'капчи ИТД',\n buildPage: ({ theme }) => buildItdCaptchaPage({ sitekey, theme, action, captchaOrigin }),\n widgetReadySelector: '#widget',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n // Виджет ИТД шлёт лишь общий код ошибки и просит повторить: постоянных кодов у него нет.\n isPermanentWidgetError: () => false,\n };\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/**\n * Публичный ключ виджета Cloudflare Turnstile на итд.com.\n *\n * Ключ публичный — виджет отдаёт его браузеру каждому посетителю — и привязан к домену\n * итд.com, поэтому больше ни для чего не годится. Держим его прямо в пакете: это его данные,\n * а не пользовательская настройка. Тот же ключ экспортирует `itd-api`.\n */\nexport const TURNSTILE_SITE_KEY = '0x4AAAAAACHhxczw6fJGwPBg';\n\n/** Размеры контейнера виджета, px. Столько же он занимает на самом сайте. */\nconst WIDTH = 300;\nconst HEIGHT = 65;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 30;\n\n/** Коды Cloudflare вида `110***` означают, что ключ не разрешён для домена. */\nconst DOMAIN_ERROR_PREFIX = '110';\n\n/**\n * Собирает страницу с одним виджетом Cloudflare Turnstile.\n *\n * Виджет создаётся так же, как это делает сам сайт, — скриптом с `?onload=` и явным\n * `turnstile.render` с одним лишь `sitekey`. Ни `action`, ни `cdata` сайт не передаёт,\n * поэтому их не передаёт и эта страница: лишний параметр попал бы в ответ `siteverify`\n * и мог бы разойтись с тем, что ожидает сервер.\n */\nexport function buildTurnstilePage(sitekey: string, theme: Theme): string {\n const key = embed(sitekey);\n const widgetTheme = embed(theme);\n\n return buildWidgetPage({\n title: 'Turnstile',\n width: WIDTH,\n height: HEIGHT,\n head: `<script>\n window.__itdToken = null;\n window.__itdError = null;\n window.onTurnstileLoad = function () {\n var reset = function () {\n window.__itdToken = null;\n if (widgetId !== undefined) window.turnstile.reset(widgetId);\n };\n var widgetId = window.turnstile.render('#widget', {\n sitekey: ${key},\n theme: ${widgetTheme},\n callback: function (token) { window.__itdToken = token; },\n 'error-callback': function (code) { window.__itdError = String(code || 'unknown'); return true; },\n 'timeout-callback': reset,\n 'expired-callback': reset\n });\n };\n</script>\n<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad\" async defer></script>`,\n body: '<div id=\"widget\"></div>',\n });\n}\n\n/**\n * Читает результат виджета Turnstile.\n *\n * Источников два. Скрытое поле `cf-turnstile-response` виджет заполняет всегда, а вот\n * `callback` срабатывает не в каждом сценарии: пройдясь сам, виджет успевает убрать iframe,\n * и обработчика можно не дождаться. Поле при этом остаётся заполненным, и лежит оно\n * в нашей странице, а не внутри iframe чужого происхождения, поэтому доступно.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const field = document.querySelector('input[name=\"cf-turnstile-response\"]') as {\n value?: string;\n } | null;\n const scope = window as unknown as { __itdToken?: string | null; __itdError?: string | null };\n\n // Пустая строка в поле означает «ещё не решено», поэтому проверка на истинность.\n return {\n token: scope.__itdToken || field?.value || null,\n error: scope.__itdError ?? null,\n };\n });\n}\n\n/** Настройки виджета Cloudflare Turnstile. */\nexport interface TurnstileOptions {\n /** Публичный ключ виджета. По умолчанию {@link TURNSTILE_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n}\n\n/**\n * Создаёт обработчик Cloudflare Turnstile.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function turnstile(options: TurnstileOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? TURNSTILE_SITE_KEY, 'sitekey');\n\n return {\n type: CaptchaType.Cloudflare,\n label: 'Turnstile',\n buildPage: ({ theme }) => buildTurnstilePage(sitekey, theme),\n // Обёртка, а не iframe внутри неё: пройдясь без участия человека, виджет iframe убирает,\n // и ожидание iframe проспало бы уже готовый токен.\n widgetReadySelector: '#widget > div',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n isPermanentWidgetError: (code) => code.startsWith(DOMAIN_ERROR_PREFIX),\n };\n}\n","import type { CaptchaHandler } from '../handler.js';\nimport { type CaptchaType, CaptchaType as CaptchaTypeValues } from '../types.js';\nimport { itdCaptcha } from './itd.js';\nimport { turnstile } from './turnstile.js';\n\n/**\n * Встроенные обработчики по имени провайдера.\n *\n * Имена — те же, какими провайдера называет сервер итд.com, поэтому значение из\n * `itd.auth.captchaProvider()` подходит как есть. `turnstile` принимается синонимом\n * `cloudflare`.\n */\nconst BUILT_IN: Readonly<Record<string, () => CaptchaHandler>> = Object.freeze({\n [CaptchaTypeValues.Itd]: itdCaptcha,\n [CaptchaTypeValues.Cloudflare]: turnstile,\n turnstile,\n});\n\n/** Что решать: имя известного провайдера либо свой обработчик. */\nexport type CaptchaTarget = CaptchaType | CaptchaHandler;\n\nfunction isHandler(value: unknown): value is CaptchaHandler {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as CaptchaHandler).type === 'string' &&\n typeof (value as CaptchaHandler).buildPage === 'function' &&\n typeof (value as CaptchaHandler).readState === 'function'\n );\n}\n\n/**\n * Находит обработчик по имени провайдера либо принимает готовый.\n *\n * @throws {TypeError} если имя незнакомо или передано не то\n */\nexport function resolveHandler(target: CaptchaTarget): CaptchaHandler {\n if (isHandler(target)) return target;\n\n if (typeof target !== 'string' || target.trim() === '') {\n throw new TypeError(\n 'Тип капчи должен быть именем провайдера или объектом CaptchaHandler, ' +\n `получено: ${typeof target}`,\n );\n }\n\n const factory = BUILT_IN[target];\n if (!factory) {\n throw new TypeError(\n `Неизвестный тип капчи: ${target}. Известны ${Object.keys(BUILT_IN).join(', ')}. ` +\n 'Свой провайдер передайте объектом CaptchaHandler.',\n );\n }\n\n return factory();\n}\n","import type { Browser, NewContextOptions, Page } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\nimport type { CaptchaHandler } from './handler.js';\nimport { launchBrowser } from './launch.js';\nimport type { ResolvedOptions } from './options.js';\n\n/** Разброс координат клика, px в каждую сторону. */\nconst CLICK_JITTER = 4;\n/** Пауза перед первым касанием, мс. */\nconst HUMAN_DELAY = [1500, 2500] as const;\n/** Как часто опрашивается состояние виджета, мс. */\nconst POLL_INTERVAL = 250;\n/** Сколько ждать между повторными кликами, мс. */\nconst CLICK_INTERVAL = 4000;\n/** Сколько ждать появления виджета, мс. */\nconst WIDGET_APPEAR_TIMEOUT = 15_000;\n\n/** Настройки контекста по умолчанию. Заменяются целиком через `contextOptions`. */\nconst DEFAULT_CONTEXT_OPTIONS: NewContextOptions = {\n locale: 'ru-RU',\n viewport: { width: 1280, height: 800 },\n};\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst between = (min: number, max: number): number => min + Math.random() * (max - min);\n\n/**\n * Версия браузера для текста ошибки.\n *\n * Сборка Chromium — первое, что стоит проверить при отказе виджета: часть сборок его\n * не проходит. Метода может не быть, если браузер передали свой.\n */\nfunction browserVersion(browser: Browser): string | undefined {\n try {\n return browser.version?.();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Кликает по чекбоксу виджета.\n *\n * Чекбокс лежит в iframe чужого происхождения, до его DOM не дотянуться — клик идёт\n * по координатам. Отсчёт ведётся от собственного контейнера известного размера, поэтому\n * попадание не зависит от разметки виджета. Координаты слегка разбрасываются: один и тот же\n * пиксель раз за разом — заметная закономерность.\n */\nasync function clickCheckbox(page: Page, offsetX: number): Promise<boolean> {\n const widget = await page.$('#widget');\n const box = await widget?.boundingBox();\n if (!box) return false;\n\n const x = box.x + offsetX + between(-CLICK_JITTER, CLICK_JITTER);\n const y = box.y + box.height / 2 + between(-CLICK_JITTER, CLICK_JITTER);\n\n await page.mouse.move(x, y, { steps: 8 });\n await page.mouse.click(x, y, { delay: between(40, 110) });\n return true;\n}\n\n/** Ждёт токен, периодически подталкивая виджет кликом. */\nasync function waitForToken(\n page: Page,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n browser: Browser,\n): Promise<string> {\n const deadline = Date.now() + options.timeout;\n\n // Ожидание разметки необязательное: виджет её меняет, и привязываться жёстко нельзя.\n // Настоящий срок задаёт цикл ниже.\n await page\n .waitForSelector(handler.widgetReadySelector, {\n timeout: Math.min(WIDGET_APPEAR_TIMEOUT, options.timeout),\n state: 'attached',\n })\n .catch(() => {});\n\n // Первый клик не мгновенный: мгновенное касание только что отрисованного виджета само\n // по себе признак автоматизации, а капча ИТД к тому же измеряет время до клика.\n let nextClickAt = Date.now() + between(HUMAN_DELAY[0], HUMAN_DELAY[1]);\n let lastError: string | undefined;\n\n while (Date.now() < deadline) {\n const state = await handler.readState(page);\n\n if (state.token) {\n options.logger?.('токен получен');\n return state.token;\n }\n\n if (state.error && state.error !== lastError) {\n if (handler.isPermanentWidgetError(state.error)) {\n throw new CaptchaError(\n CaptchaFailure.WidgetError,\n `Виджет ${handler.label} отказал для домена ${options.origin} (код ${state.error}): ` +\n 'ключ не разрешён для этого домена.',\n { type: handler.type, widgetCode: state.error },\n );\n }\n\n // Прочие коды временные: обрывать попытку рано, виджет пробует снова сам.\n lastError = state.error;\n options.logger?.(`виджет сообщил об ошибке ${state.error}, ждём повтора`);\n }\n\n if (Date.now() >= nextClickAt && (await clickCheckbox(page, handler.checkboxOffsetX))) {\n options.logger?.('клик по чекбоксу');\n nextClickAt = Date.now() + CLICK_INTERVAL;\n }\n\n await sleep(POLL_INTERVAL);\n }\n\n const version = browserVersion(browser);\n\n throw new CaptchaError(\n CaptchaFailure.Timeout,\n `Виджет ${handler.label} не отдал токен за ${options.timeout} мс` +\n (lastError ? `; последняя ошибка виджета — ${lastError}` : '') +\n (version ? `; браузер — ${version}` : '') +\n '. Рабочие связки драйвера и браузера перечислены в README пакета.',\n lastError ? { type: handler.type, widgetCode: lastError } : { type: handler.type },\n );\n}\n\nasync function solveOnce(\n browser: Browser,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n // Каждая попытка идёт в чистом контексте: cookie и хранилище от неудачной попытки\n // достались бы следующей, а виджет их учитывает.\n const context = await browser.newContext(options.contextOptions ?? DEFAULT_CONTEXT_OPTIONS);\n\n try {\n const page = await context.newPage();\n const url = `${options.origin}/`;\n const body = handler.buildPage({ theme: options.theme });\n\n // На сайт запроса не уходит: навигация перехватывается и подменяется своей страницей.\n // Домен для браузера при этом настоящий, поэтому виджет проходит его проверку,\n // а сервер при проверке токена видит ожидаемый hostname.\n await page.route(url, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html; charset=utf-8', body }),\n );\n await page.goto(url, { waitUntil: 'domcontentloaded' });\n\n return await waitForToken(page, handler, options, browser);\n } finally {\n await context.close().catch(() => {});\n }\n}\n\n/** Ошибки настройки: повтор их не исправит. */\nfunction isPermanent(error: unknown): boolean {\n if (!(error instanceof CaptchaError)) return false;\n if (error.reason === CaptchaFailure.DriverMissing) return true;\n if (error.reason === CaptchaFailure.LaunchFailed) return true;\n if (error.reason === CaptchaFailure.BrowserClosed) return true;\n\n return error.reason === CaptchaFailure.WidgetError;\n}\n\n/**\n * Как драйверы сообщают, что страницы, контекста или браузера больше нет.\n *\n * Своего типа ошибки у них нет, поэтому остаётся текст сообщения.\n */\nconst CLOSED_MARKERS = [\n 'has been closed',\n 'target closed',\n 'browser has been closed',\n 'browser has disconnected',\n 'session closed',\n];\n\nfunction isClosedError(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return CLOSED_MARKERS.some((marker) => message.includes(marker));\n}\n\n/** Приводит закрытие браузера к {@link CaptchaError}; остальное отдаёт как есть. */\nfunction toCaptchaError(error: unknown, handler: CaptchaHandler): unknown {\n if (!isClosedError(error)) return error;\n\n return new CaptchaError(\n CaptchaFailure.BrowserClosed,\n `Браузер закрылся до того, как виджет ${handler.label} отдал токен.`,\n { type: handler.type },\n );\n}\n\n/**\n * Общий путь получения токена: поднимает браузер, решает виджет, закрывает браузер.\n *\n * Всё, что зависит от провайдера, приходит в {@link CaptchaHandler}; здесь остаётся только\n * то, что одинаково для любого виджета.\n *\n * @throws {CaptchaError} если токен получить не удалось\n */\nexport async function runSolver(\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n const browser = options.browser ?? (await launchBrowser(options));\n const owned = options.browser === undefined;\n\n try {\n for (let attempt = 1; attempt <= options.attempts; attempt++) {\n try {\n options.logger?.(`попытка ${attempt} из ${options.attempts}`);\n return await solveOnce(browser, handler, options);\n } catch (raw) {\n const error = toCaptchaError(raw, handler);\n if (isPermanent(error) || attempt === options.attempts) throw error;\n options.logger?.(\n `попытка ${attempt} не удалась: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // Недостижимо: последняя попытка либо возвращает токен, либо бросает ошибку.\n throw new CaptchaError(CaptchaFailure.Timeout, `Токен ${handler.label} получить не удалось`, {\n type: handler.type,\n });\n } finally {\n if (owned) await browser.close().catch(() => {});\n }\n}\n","import { resolveOptions, type SolveOptions } from './options.js';\nimport { type CaptchaTarget, resolveHandler } from './providers/registry.js';\nimport { runSolver } from './runner.js';\nimport type { CaptchaType } from './types.js';\n\n/**\n * Получает один токен капчи указанного типа.\n *\n * Поднимает браузер, решает виджет и закрывает браузер за собой.\n *\n * @example\n * ```ts\n * await solveCaptcha(CaptchaType.Itd);\n * await solveCaptcha(itdCaptcha({ action: 'register' }), { headless: true });\n * await solveCaptcha(myOwnHandler);\n * ```\n *\n * @throws {CaptchaError} если токен получить не удалось\n * @throws {TypeError} при некорректных настройках или незнакомом типе\n */\nexport async function solveCaptcha(\n target: CaptchaTarget,\n options: SolveOptions = {},\n): Promise<string> {\n const handler = resolveHandler(target);\n return runSolver(handler, resolveOptions(options));\n}\n\n/** Настройки источника токена для клиента `itd-api`. */\nexport interface CaptchaSolverOptions extends SolveOptions {\n /**\n * Какую капчу проходить всегда: `CaptchaType.Itd`, `CaptchaType.Cloudflare` либо готовый\n * провайдер из фабрики.\n *\n * Без этой настройки тип называет клиент, спросив активного провайдера у сервера.\n * Сервер принимает обе капчи, так что закреплённый тип избавляет от лишнего запроса.\n */\n type?: CaptchaTarget | undefined;\n /**\n * Поле тела запроса, в котором сервер ждёт токен.\n *\n * Нужно, только если сервер его переименовал: для закреплённого типа клиент берёт имя\n * из своей таблицы. Задаётся вместе с {@link CaptchaSolverOptions.type}.\n */\n field?: string | undefined;\n}\n\n/** Источник токена капчи для опции `captcha` клиента `itd-api`. */\nexport interface CaptchaSolver {\n /** Получает имя провайдера, чью капчу нужно пройти, и возвращает токен. */\n getToken(type: CaptchaType): Promise<string>;\n /** Тип, закреплённый за источником. Без него тип выбирает клиент. */\n readonly type?: CaptchaType | undefined;\n /** Поле тела запроса для закреплённого типа. */\n readonly field?: string | undefined;\n}\n\n/**\n * Собирает источник токена капчи для клиента `itd-api`.\n *\n * Токен одноразовый и живёт несколько минут, поэтому клиент спрашивает его заново перед\n * каждым запросом, которому нужна капча. Браузер поднимается на время одного вызова\n * и сразу закрывается.\n *\n * @throws {TypeError} если тип капчи незнаком или `field` задан без него\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n * import { createCaptchaSolver, CaptchaType } from '@itd-api/captcha';\n *\n * const itd = new ItdClient({\n * storage: new FileTokenStorage('./.itd-session.json'),\n * auth: { email: process.env.ITD_EMAIL!, password: process.env.ITD_PASSWORD! },\n * captcha: createCaptchaSolver({ type: CaptchaType.Cloudflare }),\n * });\n * ```\n */\nexport function createCaptchaSolver(options: CaptchaSolverOptions = {}): CaptchaSolver {\n const { type, field, ...solveOptions } = options;\n\n if (type === undefined) {\n return { getToken: (requested) => solveCaptcha(requested, solveOptions) };\n }\n\n // Обработчик собирается сразу: незнакомый тип должен разбираться при создании клиента,\n // а не в момент входа.\n const handler = resolveHandler(type);\n const resolved = resolveOptions(solveOptions);\n\n return {\n type: handler.type,\n ...(field === undefined ? {} : { field }),\n getToken: () => runSolver(handler, resolved),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["WIDTH","HEIGHT","CHECKBOX_OFFSET_X","readState","CaptchaTypeValues"],"sources":["../src/errors.ts","../src/launch.ts","../src/options.ts","../src/html.ts","../src/types.ts","../src/providers/itd.ts","../src/providers/turnstile.ts","../src/providers/registry.ts","../src/runner.ts","../src/solve.ts"],"sourcesContent":["import type { CaptchaType } from './types.js';\n\n/** Почему не удалось получить токен. */\nexport const CaptchaFailure = Object.freeze({\n /** Драйвер браузера не установлен. */\n DriverMissing: 'driver-missing',\n /** Браузер не запустился: нет исполняемого файла, нет дисплея, отказ песочницы. */\n LaunchFailed: 'launch-failed',\n /** Браузер или страница закрылись, пока решался виджет. */\n BrowserClosed: 'browser-closed',\n /** Виджет не отдал токен за отведённое время. */\n Timeout: 'timeout',\n /** Сам виджет сообщил об ошибке — код лежит в `widgetCode`. */\n WidgetError: 'widget-error',\n} as const);\nexport type CaptchaFailure = (typeof CaptchaFailure)[keyof typeof CaptchaFailure];\n\n/**\n * Ошибка получения токена капчи.\n *\n * Причина в {@link CaptchaError.reason} определяет, что с ошибкой делать: отсутствие драйвера\n * чинится установкой, таймаут — повтором, а ошибка виджета повтором не лечится. Поле\n * {@link CaptchaError.type} говорит, чей именно виджет решался, — оно есть не у всех ошибок\n * (запуск браузера происходит до выбора виджета).\n */\nexport class CaptchaError extends Error {\n readonly reason: CaptchaFailure;\n /** Какой провайдер решался в момент ошибки. */\n readonly type: CaptchaType | undefined;\n /**\n * Код ошибки виджета, если он его сообщил.\n *\n * Для Cloudflare самые частые: `110200` — домен не разрешён для этого ключа, `300***`\n * и `600***` — внутренние сбои виджета, лечатся повтором.\n */\n readonly widgetCode: string | undefined;\n\n constructor(\n reason: CaptchaFailure,\n message: string,\n options: { type?: CaptchaType; widgetCode?: string } = {},\n ) {\n super(message);\n this.name = 'CaptchaError';\n this.reason = reason;\n this.type = options.type;\n this.widgetCode = options.widgetCode;\n }\n}\n","import type { Browser, DriverModule, LaunchOptions } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\n\n/**\n * Аргументы запуска Chromium.\n *\n * Подмены `User-Agent` здесь нет намеренно: заявленная версия браузера расходилась бы\n * с реальным движком, а такое расхождение само по себе служит признаком автоматизации.\n * Остаются безопасные общие флаги. Отключение sandbox вынесено в явную настройку:\n * удалённый код виджета исполняется в браузере, и ослаблять его изоляцию по умолчанию нельзя.\n */\nconst DEFAULT_ARGS = ['--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled'];\n\n/**\n * Драйверы в порядке предпочтения.\n *\n * `patchright` впереди намеренно: проверенная связка `patchright@1.61.1` со штатным\n * Chromium 149 проходит виджет. Если стоит только `playwright`, берётся он.\n */\nconst DRIVERS = ['patchright', 'playwright', 'playwright-core'];\n\n/**\n * Как выглядит имя пакета драйвера.\n *\n * Имя приходит извне и уезжает в динамический импорт, поэтому путями и относительными\n * адресами быть не должно — только имя пакета.\n */\nconst DRIVER_NAME = /^(@[a-z0-9][\\w.-]*\\/)?[a-z0-9][\\w.-]*$/i;\n\n/**\n * Драйверы, которые сами приводят аргументы запуска в порядок.\n *\n * Им ничего не подмешивается: набор флагов у них выверен, а лишний флаг — такой же след,\n * как и недостающий.\n */\nconst SELF_TUNED_DRIVERS = new Set(['patchright']);\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | null)?.code;\n return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';\n}\n\n/**\n * Подключает драйвер браузера.\n *\n * Импорт динамический: драйвер объявлен необязательной одноранговой зависимостью,\n * поэтому его может не быть вовсе. Названный явно берётся один, без перебора: молча\n * подставить вместо него другой значило бы проверить не то, что просили.\n */\nasync function loadDriver(requested?: string): Promise<{ driver: string; module: DriverModule }> {\n const wanted = requested ? [requested] : DRIVERS;\n\n for (const name of wanted) {\n try {\n return { driver: name, module: (await import(/* @vite-ignore */ name)) as DriverModule };\n } catch (error) {\n // Установленный драйвер, упавший при загрузке, — не то же самое, что отсутствующий.\n // Его ошибку нужно показать как есть, а не подменять советом установить пакет.\n if (!isModuleNotFound(error)) throw error;\n }\n }\n\n throw new CaptchaError(\n CaptchaFailure.DriverMissing,\n requested\n ? `Драйвер ${requested} не установлен.`\n : 'Не найден драйвер браузера. Установите проверенную связку командой: npm i patchright@1.61.1 && npx patchright install chromium. ' +\n 'Либо передайте свой запуск браузера через параметр launch.',\n );\n}\n\n/** Настройки запуска браузера. */\nexport interface BrowserOptions {\n /**\n * Запускать ли браузер без окна. По умолчанию `false`.\n *\n * В безоконном режиме виджет проходится заметно хуже: признаки такого режима видны\n * странице. На сервере поднимите виртуальный дисплей (`xvfb-run -a node bot.js`).\n */\n headless?: boolean | undefined;\n /**\n * Какой драйвер брать вместо перебора `patchright` → `playwright` → `playwright-core`.\n *\n * Нужен, когда установлено несколько и важно, какой именно поднимется. Названный\n * драйвер берётся один: не найден — ошибка, а не молчаливая подмена соседним.\n */\n driver?: string | undefined;\n /** Путь к исполняемому файлу браузера, если он лежит не там, где его ищет драйвер. */\n executablePath?: string | undefined;\n /** Канал браузера, например `chrome` или `msedge`, — вместо сборки из комплекта драйвера. */\n channel?: string | undefined;\n /** Дополнительные аргументы командной строки — добавляются к стандартным. */\n args?: readonly string[] | undefined;\n /**\n * Отключить sandbox Chromium. По умолчанию `false`.\n *\n * Используйте только внутри изолированного контейнера, где sandbox невозможно настроить:\n * страница исполняет удалённый код виджета, и без изоляции браузера его компрометация\n * получает больше доступа к процессу и системе.\n */\n disableSandbox?: boolean | undefined;\n /** Прокси для браузера. */\n proxy?: { server: string; username?: string; password?: string } | undefined;\n /**\n * Свой запуск браузера. Заменяет все остальные параметры запуска.\n *\n * Нужен, чтобы подставить другой драйвер или подключиться к уже работающему браузеру.\n *\n * @example\n * ```ts\n * launch: async () => {\n * const { chromium } = await import('patchright');\n * return chromium.launch({ headless: false });\n * };\n * ```\n */\n launch?: (() => Promise<Browser>) | undefined;\n}\n\n/** Собирает параметры запуска, не поднимая браузер. @internal */\nexport function resolveLaunchOptions(options: BrowserOptions, driver = ''): LaunchOptions {\n return {\n headless: options.headless ?? false,\n args: [\n ...(SELF_TUNED_DRIVERS.has(driver) ? [] : DEFAULT_ARGS),\n ...(options.disableSandbox ? ['--no-sandbox'] : []),\n ...(options.args ?? []),\n ],\n ...(options.executablePath ? { executablePath: options.executablePath } : {}),\n ...(options.channel ? { channel: options.channel } : {}),\n ...(options.proxy ? { proxy: options.proxy } : {}),\n };\n}\n\n/**\n * Поднимает браузер по настройкам.\n *\n * Тем же путём, что и солверы: те же флаги, тот же порядок драйверов. Пригодится, чтобы\n * поднять браузер один раз на несколько токенов и передать его в `browser`, — и чтобы\n * проверять связки драйвера и сборки ровно в том виде, в каком их поднимает пакет.\n */\nexport async function launchBrowser(options: BrowserOptions): Promise<Browser> {\n if (options.launch) return options.launch();\n\n if (options.driver !== undefined && !DRIVER_NAME.test(options.driver)) {\n throw new TypeError(`driver должен быть именем пакета, получено: ${options.driver}`);\n }\n\n const {\n driver,\n module: { chromium },\n } = await loadDriver(options.driver);\n const launchOptions = resolveLaunchOptions(options, driver);\n\n try {\n return await chromium.launch(launchOptions);\n } catch (error) {\n const hint =\n launchOptions.headless === false\n ? ' Если это сервер без графической оболочки, запустите процесс через xvfb-run -a.'\n : '';\n\n throw new CaptchaError(\n CaptchaFailure.LaunchFailed,\n `Не удалось запустить браузер: ${error instanceof Error ? error.message : String(error)}.${hint}`,\n );\n }\n}\n","import type { Browser, NewContextOptions } from './driver.js';\nimport type { BrowserOptions } from './launch.js';\nimport type { Theme } from './types.js';\n\n/** Базовый URL сайта итд.com. Домен записан в punycode: `итд.com`. */\nexport const DEFAULT_ORIGIN = 'https://xn--d1ah4a.com';\n\n/** Логгер хода решения. Совместим с `console` и с логгером клиента `itd-api`. */\nexport interface CaptchaLogger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\nconst LOGGER_METHODS = ['debug', 'info', 'warn', 'error'] as const;\n\n/**\n * Настройки получения токена, общие для любого виджета.\n *\n * Ключ, назначение токена и адрес виджета задаются при создании провайдера:\n * `solveCaptcha(itdCaptcha({ action: 'register' }))`.\n */\nexport interface SolveOptions extends BrowserOptions {\n /** Сайт, чей виджет решается. По умолчанию {@link DEFAULT_ORIGIN}. */\n origin?: string | undefined;\n /** Оформление виджета. По умолчанию `auto`. */\n theme?: Theme | undefined;\n /** Сколько ждать токен, мс. По умолчанию 60000. */\n timeout?: number | undefined;\n /** Сколько попыток делать при неудаче. По умолчанию 2. */\n attempts?: number | undefined;\n /**\n * Готовый браузер. Тогда пакет его не запускает и не закрывает.\n *\n * Пригодится, если браузер уже поднят для чего-то ещё.\n */\n browser?: Browser | undefined;\n /**\n * Настройки контекста. Заменяют стандартные целиком, а не дополняют их.\n *\n * По умолчанию `{ locale: 'ru-RU', viewport: { width: 1280, height: 800 } }`. Драйверам,\n * которые собирают отпечаток браузера сами, навязанный `locale` мешает — им передайте `{}`.\n */\n contextOptions?: NewContextOptions | undefined;\n /** Куда писать ход решения, например `console`. Без него — в логгер клиента `itd-api`. */\n logger?: CaptchaLogger | undefined;\n}\n\n/** Настройки после подстановки умолчаний. @internal */\nexport interface ResolvedOptions extends SolveOptions {\n origin: string;\n theme: Theme;\n timeout: number;\n attempts: number;\n}\n\n/**\n * Приводит адрес к корню и проверяет его пригодность.\n *\n * Виджет привязан к домену, а не к пути: адрес приводится к `URL.origin`, чтобы перехват навигации\n * совпал с ним ровно один раз.\n */\nexport function resolveOrigin(origin: string, field: string): string {\n let parsed: URL;\n try {\n parsed = new URL(origin);\n } catch {\n throw new TypeError(`${field} должен быть абсолютным URL, получено: ${origin}`);\n }\n\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new TypeError(`${field} должен быть http или https, получено: ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new TypeError(\n `${field} не должен содержать логин, пароль, параметры запроса или фрагмент`,\n );\n }\n\n return parsed.origin;\n}\n\n/**\n * Проверяет общие настройки и подставляет умолчания.\n *\n * @throws {TypeError} при некорректных значениях\n */\nexport function resolveOptions(options: SolveOptions): ResolvedOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) {\n throw new TypeError('options должен быть объектом');\n }\n\n const timeout = options.timeout ?? 60_000;\n const attempts = options.attempts ?? 2;\n const theme = options.theme ?? 'auto';\n\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new TypeError(`timeout должен быть положительным числом, получено: ${timeout}`);\n }\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new TypeError(`attempts должен быть целым числом от 1, получено: ${attempts}`);\n }\n if (theme !== 'auto' && theme !== 'light' && theme !== 'dark') {\n throw new TypeError(\"theme должен быть 'auto', 'light' или 'dark'\");\n }\n for (const [name, value] of [\n ['headless', options.headless],\n ['disableSandbox', options.disableSandbox],\n ] as const) {\n if (value !== undefined && typeof value !== 'boolean') {\n throw new TypeError(`${name} должен быть boolean`);\n }\n }\n for (const [name, value] of [\n ['driver', options.driver],\n ['channel', options.channel],\n ] as const) {\n if (value !== undefined && (typeof value !== 'string' || value.trim() === '')) {\n throw new TypeError(`${name} должен быть непустой строкой`);\n }\n }\n if (options.logger !== undefined) {\n if (typeof options.logger !== 'object' || options.logger === null) {\n throw new TypeError('logger должен быть объектом с методами debug, info, warn и error');\n }\n for (const method of LOGGER_METHODS) {\n if (typeof options.logger[method] !== 'function') {\n throw new TypeError(`logger.${method} должен быть функцией`);\n }\n }\n }\n if (\n options.contextOptions !== undefined &&\n (typeof options.contextOptions !== 'object' ||\n options.contextOptions === null ||\n Array.isArray(options.contextOptions))\n ) {\n throw new TypeError('contextOptions должен быть объектом');\n }\n if (options.launch !== undefined && typeof options.launch !== 'function') {\n throw new TypeError('launch должен быть функцией');\n }\n if (\n options.args !== undefined &&\n (!Array.isArray(options.args) || options.args.some((argument) => typeof argument !== 'string'))\n ) {\n throw new TypeError('args должен быть массивом строк');\n }\n\n return {\n ...options,\n origin: resolveOrigin(options.origin ?? DEFAULT_ORIGIN, 'origin'),\n theme,\n timeout,\n attempts,\n };\n}\n\n/** Требует непустую строку: общая проверка для настроек провайдеров. @internal */\nexport function requireText(value: unknown, field: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new TypeError(`${field} должен быть непустой строкой`);\n }\n return value;\n}\n","/**\n * Сборка страницы, на которой живёт виджет.\n *\n * Страница отдаётся вместо настоящей — по адресу целевого сайта, через перехват навигации.\n * Благодаря этому `document.location.origin` для виджета настоящий, и привязка ключа\n * к домену не нарушается, а форма входа и пароль в браузере не участвуют.\n *\n * Каркас общий для всех провайдеров: контейнер `#widget` известного размера, от которого\n * общий цикл отсчитывает координаты клика. Различия провайдеров — в `head` и `body`.\n */\n\n/** Отступ контейнера от края окна, px. Виджету нужно место, чтобы раскрыть карточку. */\nconst WIDGET_MARGIN = 40;\n\n/**\n * Готовит значение к вставке в инлайновый скрипт или атрибут.\n *\n * Одного `JSON.stringify` мало: кавычки он экранирует, а `</script>` — нет, и такая\n * последовательность закрывает тег независимо от того, внутри строки она или нет.\n * Экранированный `<` разбирается движком как обычный символ, но парсер HTML его уже не видит.\n */\nexport function embed(value: string): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n/** Из чего собирается страница одного виджета. */\nexport interface PageParts {\n /** Заголовок вкладки. На проверку не влияет, но пустой заголовок выглядит подозрительно. */\n title: string;\n /** Размеры контейнера `#widget`, px. Столько же виджет занимает на самом сайте. */\n width: number;\n height: number;\n /** Дополнительные правила для `#widget` — например, если контейнер сам является iframe. */\n widgetStyle?: string | undefined;\n /** Содержимое `head`: скрипты и обработчики провайдера. */\n head?: string | undefined;\n /** Содержимое `body`: сам контейнер `#widget` и поля для результата. */\n body: string;\n}\n\n/** Собирает страницу с одним виджетом. */\nexport function buildWidgetPage(parts: PageParts): string {\n return `<!doctype html>\n<html lang=\"ru\">\n<head>\n<meta charset=\"utf-8\">\n<title>${parts.title}</title>\n<style>\n html, body { margin: 0; padding: 0; background: #fff; }\n #widget {\n width: ${parts.width}px;\n height: ${parts.height}px;\n margin: ${WIDGET_MARGIN}px;${parts.widgetStyle ?? ''}\n }\n</style>\n${parts.head ?? ''}\n</head>\n<body>\n${parts.body}\n</body>\n</html>`;\n}\n","/**\n * Открытый строковый enum.\n *\n * Даёт автодополнение известных значений, но не ломается, если появится новое.\n */\nexport type Loose<T extends string> = T | (string & {});\n\n/** Провайдер капчи — теми же именами, какими его называет сервер итд.com. */\nexport const CaptchaType = Object.freeze({\n /** Собственная капча ИТД: карточка «Я не робот» в iframe с `captcha.итд.com`. */\n Itd: 'itd',\n /** Cloudflare Turnstile. */\n Cloudflare: 'cloudflare',\n} as const);\nexport type CaptchaType = Loose<(typeof CaptchaType)[keyof typeof CaptchaType]>;\n\n/** Оформление виджета. */\nexport type Theme = 'auto' | 'light' | 'dark';\n\n/** Результат виджета, снятый со страницы через `page.evaluate`. */\nexport interface WidgetState {\n token: string | null;\n error: string | null;\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText, resolveOrigin } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/** Базовый адрес виджета собственной капчи ИТД. Домен в punycode: `captcha.итд.com`. */\nexport const DEFAULT_CAPTCHA_ORIGIN = 'https://captcha.xn--d1ah4a.com';\n\n/**\n * Публичный ключ виджета собственной капчи ИТД.\n *\n * Как и ключ Turnstile, он публичный и привязан к домену итд.com: виджет отдаёт его браузеру\n * каждому посетителю.\n */\nexport const ITD_CAPTCHA_SITE_KEY = 'sk_44d64cf7bf8bc8377f5b';\n\n/** Назначение токена по умолчанию. Сервер сверяет его с операцией. */\nconst DEFAULT_ACTION = 'login';\n\n/** Размеры карточки, px. Их же виджет сообщает сайту через `postMessage`. */\nconst WIDTH = 300;\nconst HEIGHT = 74;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 34;\n\n/**\n * Собирает страницу с одним виджетом собственной капчи ИТД.\n *\n * Виджет — iframe чужого происхождения (`captcha.итд.com`), и наружу он отдаёт токен\n * единственным способом: `postMessage` в родителя. Эта страница ровно так его и слушает —\n * как сам сайт.\n *\n * Слушатель кладёт результат в скрытые поля, а не в переменную `window`: стелс-драйверы вроде\n * `patchright` выполняют `page.evaluate` в изолированном мире, откуда переменные страницы\n * не видны, зато DOM общий.\n *\n * `action` задаёт назначение токена (`login`, `register`, `password_reset`) — сервер сверяет\n * его с операцией, поэтому он не выдумывается, а приходит из настроек обработчика.\n */\nexport function buildItdCaptchaPage(input: {\n sitekey: string;\n theme: Theme;\n action: string;\n captchaOrigin: string;\n}): string {\n const origin = embed(input.captchaOrigin);\n const src = embed(\n `${input.captchaOrigin}/widget.html` +\n `?sitekey=${encodeURIComponent(input.sitekey)}` +\n `&theme=${encodeURIComponent(input.theme)}` +\n `&action=${encodeURIComponent(input.action)}`,\n );\n\n return buildWidgetPage({\n title: 'Проверка',\n width: WIDTH,\n height: HEIGHT,\n widgetStyle: `\n border: 0;\n display: block;`,\n head: `<script>\n window.addEventListener('message', function (event) {\n if (event.origin !== ${origin}) return;\n var data;\n try {\n data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;\n } catch (e) {\n return;\n }\n if (!data) return;\n var token = document.getElementById('itd-token');\n var error = document.getElementById('itd-error');\n if (data.type === 'token' && data.token) token.value = data.token;\n else if (data.type === 'expired') token.value = '';\n else if (data.type === 'error') error.value = String(data.code || 'error');\n });\n</script>`,\n body: `<input type=\"hidden\" id=\"itd-token\">\n<input type=\"hidden\" id=\"itd-error\">\n<iframe id=\"widget\" src=${src} title=\"Проверка\"></iframe>`,\n });\n}\n\n/**\n * Читает результат виджета капчи ИТД.\n *\n * Токен приходит в родителя через `postMessage`, и слушатель страницы кладёт его в скрытое\n * поле. Читаем из DOM, а не из `window`: стелс-драйверы выполняют `evaluate` в изолированном\n * мире, где переменных страницы нет, а DOM общий.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const token = document.querySelector('#itd-token') as { value?: string } | null;\n const error = document.querySelector('#itd-error') as { value?: string } | null;\n return {\n token: token?.value || null,\n error: error?.value || null,\n };\n });\n}\n\n/** Настройки виджета собственной капчи ИТД. */\nexport interface ItdCaptchaOptions {\n /** Публичный ключ виджета. По умолчанию {@link ITD_CAPTCHA_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n /**\n * Назначение токена: `login`, `register`, `password_reset`. По умолчанию `login`.\n *\n * Сервер сверяет назначение с операцией, поэтому для регистрации или сброса пароля нужен\n * токен с соответствующим `action`.\n */\n action?: string | undefined;\n /** Базовый адрес виджета. По умолчанию {@link DEFAULT_CAPTCHA_ORIGIN}. */\n captchaOrigin?: string | undefined;\n}\n\n/**\n * Создаёт обработчик собственной капчи ИТД.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function itdCaptcha(options: ItdCaptchaOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? ITD_CAPTCHA_SITE_KEY, 'sitekey');\n const action = requireText(options.action ?? DEFAULT_ACTION, 'action');\n const captchaOrigin = resolveOrigin(\n options.captchaOrigin ?? DEFAULT_CAPTCHA_ORIGIN,\n 'captchaOrigin',\n );\n\n return {\n type: CaptchaType.Itd,\n label: 'капчи ИТД',\n buildPage: ({ theme }) => buildItdCaptchaPage({ sitekey, theme, action, captchaOrigin }),\n widgetReadySelector: '#widget',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n // Виджет ИТД шлёт лишь общий код ошибки и просит повторить: постоянных кодов у него нет.\n isPermanentWidgetError: () => false,\n };\n}\n","import type { Page } from '../driver.js';\nimport type { CaptchaHandler } from '../handler.js';\nimport { buildWidgetPage, embed } from '../html.js';\nimport { requireText } from '../options.js';\nimport { CaptchaType, type Theme, type WidgetState } from '../types.js';\n\n/**\n * Публичный ключ виджета Cloudflare Turnstile на итд.com.\n *\n * Ключ публичный — виджет отдаёт его браузеру каждому посетителю — и привязан к домену\n * итд.com, поэтому больше ни для чего не годится. Держим его прямо в пакете: это его данные,\n * а не пользовательская настройка. Тот же ключ экспортирует `itd-api`.\n */\nexport const TURNSTILE_SITE_KEY = '0x4AAAAAACHhxczw6fJGwPBg';\n\n/** Размеры контейнера виджета, px. Столько же он занимает на самом сайте. */\nconst WIDTH = 300;\nconst HEIGHT = 65;\n\n/** Насколько правее левого края контейнера находится чекбокс, px. */\nconst CHECKBOX_OFFSET_X = 30;\n\n/** Коды Cloudflare вида `110***` означают, что ключ не разрешён для домена. */\nconst DOMAIN_ERROR_PREFIX = '110';\n\n/**\n * Собирает страницу с одним виджетом Cloudflare Turnstile.\n *\n * Виджет создаётся так же, как это делает сам сайт, — скриптом с `?onload=` и явным\n * `turnstile.render` с одним лишь `sitekey`. Ни `action`, ни `cdata` сайт не передаёт,\n * поэтому их не передаёт и эта страница: лишний параметр попал бы в ответ `siteverify`\n * и мог бы разойтись с тем, что ожидает сервер.\n */\nexport function buildTurnstilePage(sitekey: string, theme: Theme): string {\n const key = embed(sitekey);\n const widgetTheme = embed(theme);\n\n return buildWidgetPage({\n title: 'Turnstile',\n width: WIDTH,\n height: HEIGHT,\n head: `<script>\n window.__itdToken = null;\n window.__itdError = null;\n window.onTurnstileLoad = function () {\n var reset = function () {\n window.__itdToken = null;\n if (widgetId !== undefined) window.turnstile.reset(widgetId);\n };\n var widgetId = window.turnstile.render('#widget', {\n sitekey: ${key},\n theme: ${widgetTheme},\n callback: function (token) { window.__itdToken = token; },\n 'error-callback': function (code) { window.__itdError = String(code || 'unknown'); return true; },\n 'timeout-callback': reset,\n 'expired-callback': reset\n });\n };\n</script>\n<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad\" async defer></script>`,\n body: '<div id=\"widget\"></div>',\n });\n}\n\n/**\n * Читает результат виджета Turnstile.\n *\n * Источников два. Скрытое поле `cf-turnstile-response` виджет заполняет всегда, а вот\n * `callback` срабатывает не в каждом сценарии: пройдясь сам, виджет успевает убрать iframe,\n * и обработчика можно не дождаться. Поле при этом остаётся заполненным, и лежит оно\n * в нашей странице, а не внутри iframe чужого происхождения, поэтому доступно.\n */\nfunction readState(page: Page): Promise<WidgetState> {\n return page.evaluate<WidgetState>(() => {\n const field = document.querySelector('input[name=\"cf-turnstile-response\"]') as {\n value?: string;\n } | null;\n const scope = window as unknown as { __itdToken?: string | null; __itdError?: string | null };\n\n // Пустая строка в поле означает «ещё не решено», поэтому проверка на истинность.\n return {\n token: scope.__itdToken || field?.value || null,\n error: scope.__itdError ?? null,\n };\n });\n}\n\n/** Настройки виджета Cloudflare Turnstile. */\nexport interface TurnstileOptions {\n /** Публичный ключ виджета. По умолчанию {@link TURNSTILE_SITE_KEY} — ключ итд.com. */\n sitekey?: string | undefined;\n}\n\n/**\n * Создаёт обработчик Cloudflare Turnstile.\n *\n * @throws {TypeError} при некорректных настройках\n */\nexport function turnstile(options: TurnstileOptions = {}): CaptchaHandler {\n const sitekey = requireText(options.sitekey ?? TURNSTILE_SITE_KEY, 'sitekey');\n\n return {\n type: CaptchaType.Cloudflare,\n label: 'Turnstile',\n buildPage: ({ theme }) => buildTurnstilePage(sitekey, theme),\n // Обёртка, а не iframe внутри неё: пройдясь без участия человека, виджет iframe убирает,\n // и ожидание iframe проспало бы уже готовый токен.\n widgetReadySelector: '#widget > div',\n checkboxOffsetX: CHECKBOX_OFFSET_X,\n readState,\n isPermanentWidgetError: (code) => code.startsWith(DOMAIN_ERROR_PREFIX),\n };\n}\n","import type { CaptchaHandler } from '../handler.js';\nimport { type CaptchaType, CaptchaType as CaptchaTypeValues } from '../types.js';\nimport { itdCaptcha } from './itd.js';\nimport { turnstile } from './turnstile.js';\n\n/**\n * Встроенные обработчики по имени провайдера.\n *\n * Имена — те же, какими провайдера называет сервер итд.com, поэтому значение из\n * `itd.auth.captchaProvider()` подходит как есть. `turnstile` принимается синонимом\n * `cloudflare`.\n */\nconst BUILT_IN: Readonly<Record<string, () => CaptchaHandler>> = Object.freeze({\n [CaptchaTypeValues.Itd]: itdCaptcha,\n [CaptchaTypeValues.Cloudflare]: turnstile,\n turnstile,\n});\n\n/** Что решать: имя известного провайдера либо свой обработчик. */\nexport type CaptchaTarget = CaptchaType | CaptchaHandler;\n\nfunction isHandler(value: unknown): value is CaptchaHandler {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as CaptchaHandler).type === 'string' &&\n typeof (value as CaptchaHandler).buildPage === 'function' &&\n typeof (value as CaptchaHandler).readState === 'function'\n );\n}\n\n/**\n * Находит обработчик по имени провайдера либо принимает готовый.\n *\n * @throws {TypeError} если имя незнакомо или передано не то\n */\nexport function resolveHandler(target: CaptchaTarget): CaptchaHandler {\n if (isHandler(target)) return target;\n\n if (typeof target !== 'string' || target.trim() === '') {\n throw new TypeError(\n 'Тип капчи должен быть именем провайдера или объектом CaptchaHandler, ' +\n `получено: ${typeof target}`,\n );\n }\n\n const factory = BUILT_IN[target];\n if (!factory) {\n throw new TypeError(\n `Неизвестный тип капчи: ${target}. Известны ${Object.keys(BUILT_IN).join(', ')}. ` +\n 'Свой провайдер передайте объектом CaptchaHandler.',\n );\n }\n\n return factory();\n}\n","import type { Browser, NewContextOptions, Page } from './driver.js';\nimport { CaptchaError, CaptchaFailure } from './errors.js';\nimport type { CaptchaHandler } from './handler.js';\nimport { launchBrowser } from './launch.js';\nimport type { ResolvedOptions } from './options.js';\n\n/** Разброс координат клика, px в каждую сторону. */\nconst CLICK_JITTER = 4;\n/** Пауза перед первым касанием, мс. */\nconst HUMAN_DELAY = [1500, 2500] as const;\n/** Как часто опрашивается состояние виджета, мс. */\nconst POLL_INTERVAL = 250;\n/** Сколько ждать между повторными кликами, мс. */\nconst CLICK_INTERVAL = 4000;\n/** Сколько ждать появления виджета, мс. */\nconst WIDGET_APPEAR_TIMEOUT = 15_000;\n\n/** Настройки контекста по умолчанию. Заменяются целиком через `contextOptions`. */\nconst DEFAULT_CONTEXT_OPTIONS: NewContextOptions = {\n locale: 'ru-RU',\n viewport: { width: 1280, height: 800 },\n};\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst between = (min: number, max: number): number => min + Math.random() * (max - min);\n\n/**\n * Версия браузера для текста ошибки.\n *\n * Сборка Chromium — первое, что стоит проверить при отказе виджета: часть сборок его\n * не проходит. Метода может не быть, если браузер передали свой.\n */\nfunction browserVersion(browser: Browser): string | undefined {\n try {\n return browser.version?.();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Кликает по чекбоксу виджета.\n *\n * Чекбокс лежит в iframe чужого происхождения, до его DOM не дотянуться — клик идёт\n * по координатам. Отсчёт ведётся от собственного контейнера известного размера, поэтому\n * попадание не зависит от разметки виджета. Координаты слегка разбрасываются: один и тот же\n * пиксель раз за разом — заметная закономерность.\n */\nasync function clickCheckbox(page: Page, offsetX: number): Promise<boolean> {\n const widget = await page.$('#widget');\n const box = await widget?.boundingBox();\n if (!box) return false;\n\n const x = box.x + offsetX + between(-CLICK_JITTER, CLICK_JITTER);\n const y = box.y + box.height / 2 + between(-CLICK_JITTER, CLICK_JITTER);\n\n await page.mouse.move(x, y, { steps: 8 });\n await page.mouse.click(x, y, { delay: between(40, 110) });\n return true;\n}\n\n/** Ждёт токен, периодически подталкивая виджет кликом. */\nasync function waitForToken(\n page: Page,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n browser: Browser,\n): Promise<string> {\n const startedAt = Date.now();\n const deadline = startedAt + options.timeout;\n\n // Ожидание разметки необязательное: виджет её меняет, и привязываться жёстко нельзя.\n // Настоящий срок задаёт цикл ниже.\n await page\n .waitForSelector(handler.widgetReadySelector, {\n timeout: Math.min(WIDGET_APPEAR_TIMEOUT, options.timeout),\n state: 'attached',\n })\n .catch(() => {});\n\n // Первый клик не мгновенный: мгновенное касание только что отрисованного виджета само\n // по себе признак автоматизации, а капча ИТД к тому же измеряет время до клика.\n let nextClickAt = Date.now() + between(HUMAN_DELAY[0], HUMAN_DELAY[1]);\n let lastError: string | undefined;\n\n while (Date.now() < deadline) {\n const state = await handler.readState(page);\n\n if (state.token) {\n options.logger?.info(`токен ${handler.label} получен за ${Date.now() - startedAt} мс`);\n return state.token;\n }\n\n if (state.error && state.error !== lastError) {\n if (handler.isPermanentWidgetError(state.error)) {\n throw new CaptchaError(\n CaptchaFailure.WidgetError,\n `Виджет ${handler.label} отказал для домена ${options.origin} (код ${state.error}): ` +\n 'ключ не разрешён для этого домена.',\n { type: handler.type, widgetCode: state.error },\n );\n }\n\n // Прочие коды временные: обрывать попытку рано, виджет пробует снова сам.\n lastError = state.error;\n options.logger?.debug(\n `виджет ${handler.label} сообщил об ошибке ${state.error}, ждём повтора`,\n );\n }\n\n if (Date.now() >= nextClickAt && (await clickCheckbox(page, handler.checkboxOffsetX))) {\n options.logger?.debug(`клик по чекбоксу ${handler.label}`);\n nextClickAt = Date.now() + CLICK_INTERVAL;\n }\n\n await sleep(POLL_INTERVAL);\n }\n\n const version = browserVersion(browser);\n\n throw new CaptchaError(\n CaptchaFailure.Timeout,\n `Виджет ${handler.label} не отдал токен за ${options.timeout} мс` +\n (lastError ? `; последняя ошибка виджета — ${lastError}` : '') +\n (version ? `; браузер — ${version}` : '') +\n '. Рабочие связки драйвера и браузера перечислены в README пакета.',\n lastError ? { type: handler.type, widgetCode: lastError } : { type: handler.type },\n );\n}\n\nasync function solveOnce(\n browser: Browser,\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n // Каждая попытка идёт в чистом контексте: cookie и хранилище от неудачной попытки\n // достались бы следующей, а виджет их учитывает.\n const context = await browser.newContext(options.contextOptions ?? DEFAULT_CONTEXT_OPTIONS);\n\n try {\n const page = await context.newPage();\n const url = `${options.origin}/`;\n const body = handler.buildPage({ theme: options.theme });\n\n // На сайт запроса не уходит: навигация перехватывается и подменяется своей страницей.\n // Домен для браузера при этом настоящий, поэтому виджет проходит его проверку,\n // а сервер при проверке токена видит ожидаемый hostname.\n await page.route(url, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html; charset=utf-8', body }),\n );\n await page.goto(url, { waitUntil: 'domcontentloaded' });\n\n return await waitForToken(page, handler, options, browser);\n } finally {\n await context.close().catch(() => {});\n }\n}\n\n/** Ошибки настройки: повтор их не исправит. */\nfunction isPermanent(error: unknown): boolean {\n if (!(error instanceof CaptchaError)) return false;\n if (error.reason === CaptchaFailure.DriverMissing) return true;\n if (error.reason === CaptchaFailure.LaunchFailed) return true;\n if (error.reason === CaptchaFailure.BrowserClosed) return true;\n\n return error.reason === CaptchaFailure.WidgetError;\n}\n\n/**\n * Как драйверы сообщают, что страницы, контекста или браузера больше нет.\n *\n * Своего типа ошибки у них нет, поэтому остаётся текст сообщения.\n */\nconst CLOSED_MARKERS = [\n 'has been closed',\n 'target closed',\n 'browser has been closed',\n 'browser has disconnected',\n 'session closed',\n];\n\nfunction isClosedError(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return CLOSED_MARKERS.some((marker) => message.includes(marker));\n}\n\n/** Приводит закрытие браузера к {@link CaptchaError}; остальное отдаёт как есть. */\nfunction toCaptchaError(error: unknown, handler: CaptchaHandler): unknown {\n if (!isClosedError(error)) return error;\n\n return new CaptchaError(\n CaptchaFailure.BrowserClosed,\n `Браузер закрылся до того, как виджет ${handler.label} отдал токен.`,\n { type: handler.type },\n );\n}\n\n/**\n * Общий путь получения токена: поднимает браузер, решает виджет, закрывает браузер.\n *\n * Всё, что зависит от провайдера, приходит в {@link CaptchaHandler}; здесь остаётся только\n * то, что одинаково для любого виджета.\n *\n * @throws {CaptchaError} если токен получить не удалось\n */\nexport async function runSolver(\n handler: CaptchaHandler,\n options: ResolvedOptions,\n): Promise<string> {\n const owned = options.browser === undefined;\n if (owned) options.logger?.info(`запуск браузера для ${handler.label}`);\n const browser = options.browser ?? (await launchBrowser(options));\n\n try {\n for (let attempt = 1; attempt <= options.attempts; attempt++) {\n try {\n options.logger?.debug(`попытка ${attempt} из ${options.attempts} для ${handler.label}`);\n return await solveOnce(browser, handler, options);\n } catch (raw) {\n const error = toCaptchaError(raw, handler);\n if (isPermanent(error) || attempt === options.attempts) throw error;\n options.logger?.warn(\n `попытка ${attempt} из ${options.attempts} для ${handler.label} не удалась ` +\n `(${error instanceof Error ? error.message : String(error)}), повтор`,\n );\n }\n }\n\n // Недостижимо: последняя попытка либо возвращает токен, либо бросает ошибку.\n throw new CaptchaError(CaptchaFailure.Timeout, `Токен ${handler.label} получить не удалось`, {\n type: handler.type,\n });\n } finally {\n if (owned) await browser.close().catch(() => {});\n }\n}\n","import { type CaptchaLogger, resolveOptions, type SolveOptions } from './options.js';\nimport { type CaptchaTarget, resolveHandler } from './providers/registry.js';\nimport { runSolver } from './runner.js';\nimport type { CaptchaType } from './types.js';\n\n/**\n * Получает один токен капчи указанного типа.\n *\n * Поднимает браузер, решает виджет и закрывает браузер за собой.\n *\n * @example\n * ```ts\n * await solveCaptcha(CaptchaType.Itd);\n * await solveCaptcha(itdCaptcha({ action: 'register' }), { headless: true });\n * await solveCaptcha(myOwnHandler);\n * ```\n *\n * @throws {CaptchaError} если токен получить не удалось\n * @throws {TypeError} при некорректных настройках или незнакомом типе\n */\nexport async function solveCaptcha(\n target: CaptchaTarget,\n options: SolveOptions = {},\n): Promise<string> {\n const handler = resolveHandler(target);\n return runSolver(handler, resolveOptions(options));\n}\n\n/** Настройки источника токена для клиента `itd-api`. */\nexport interface CaptchaSolverOptions extends SolveOptions {\n /**\n * Какую капчу проходить всегда: `CaptchaType.Itd`, `CaptchaType.Cloudflare` либо готовый\n * провайдер из фабрики.\n *\n * Без этой настройки тип называет клиент, спросив активного провайдера у сервера.\n * Сервер принимает обе капчи, так что закреплённый тип избавляет от лишнего запроса.\n */\n type?: CaptchaTarget | undefined;\n /**\n * Поле тела запроса, в котором сервер ждёт токен.\n *\n * Нужно, только если сервер его переименовал: для закреплённого типа клиент берёт имя\n * из своей таблицы. Задаётся вместе с {@link CaptchaSolverOptions.type}.\n */\n field?: string | undefined;\n}\n\n/** Что клиент `itd-api` передаёт источнику при каждом обращении. */\nexport interface CaptchaSolveContext {\n /** Логгер клиента, если он включён. */\n logger?: CaptchaLogger | undefined;\n}\n\n/** Без своего логгера источник пишет ход решения в логгер клиента. */\nfunction withClientLogger<T extends SolveOptions>(\n options: T,\n context: CaptchaSolveContext | undefined,\n): T {\n if (options.logger || !context?.logger) return options;\n return { ...options, logger: context.logger };\n}\n\n/** Источник токена капчи для опции `captcha` клиента `itd-api`. */\nexport interface CaptchaSolver {\n /** Получает имя провайдера, чью капчу нужно пройти, и возвращает токен. */\n getToken(type: CaptchaType, context?: CaptchaSolveContext): Promise<string>;\n /** Тип, закреплённый за источником. Без него тип выбирает клиент. */\n readonly type?: CaptchaType | undefined;\n /** Поле тела запроса для закреплённого типа. */\n readonly field?: string | undefined;\n}\n\n/**\n * Собирает источник токена капчи для клиента `itd-api`.\n *\n * Токен одноразовый и живёт несколько минут, поэтому клиент спрашивает его заново перед\n * каждым запросом, которому нужна капча. Браузер поднимается на время одного вызова\n * и сразу закрывается.\n *\n * @throws {TypeError} если тип капчи незнаком или `field` задан без него\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n * import { createCaptchaSolver, CaptchaType } from '@itd-api/captcha';\n *\n * const itd = new ItdClient({\n * storage: new FileTokenStorage('./.itd-session.json'),\n * auth: { email: process.env.ITD_EMAIL!, password: process.env.ITD_PASSWORD! },\n * captcha: createCaptchaSolver({ type: CaptchaType.Cloudflare }),\n * });\n * ```\n */\nexport function createCaptchaSolver(options: CaptchaSolverOptions = {}): CaptchaSolver {\n const { type, field, ...solveOptions } = options;\n\n if (type === undefined) {\n return {\n getToken: (requested, context) =>\n solveCaptcha(requested, withClientLogger(solveOptions, context)),\n };\n }\n\n // Обработчик собирается сразу: незнакомый тип должен разбираться при создании клиента,\n // а не в момент входа.\n const handler = resolveHandler(type);\n const resolved = resolveOptions(solveOptions);\n\n return {\n type: handler.type,\n ...(field === undefined ? {} : { field }),\n getToken: (_requested, context) => runSolver(handler, withClientLogger(resolved, context)),\n };\n}\n"],"mappings":"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"}
|