@itd-api/captcha 0.1.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.
@@ -0,0 +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/** Источник токена капчи для `auth.captcha` клиента `itd-api`. */\nexport interface CaptchaSolver {\n /** Решает капчу названного типа и отдаёт токен. */\n getToken(type: CaptchaType): Promise<string>;\n}\n\n/**\n * Собирает источник токена для клиента `itd-api`.\n *\n * Токен одноразовый и живёт несколько минут, поэтому клиент спрашивает его заново перед\n * каждым входом. Браузер поднимается на время одного вызова и сразу закрывается.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage } from 'itd-api/node';\n * import { createCaptchaSolver } from '@itd-api/captcha';\n *\n * const itd = new ItdClient({\n * storage: new FileTokenStorage('./.itd-session.json'),\n * auth: {\n * email: process.env.ITD_EMAIL!,\n * password: process.env.ITD_PASSWORD!,\n * captcha: createCaptchaSolver(),\n * },\n * });\n * ```\n */\nexport function createCaptchaSolver(options: SolveOptions = {}): CaptchaSolver {\n return {\n getToken: (type) => solveCaptcha(type, options),\n };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@itd-api/captcha",
3
+ "version": "0.1.0",
4
+ "description": "Токен капчи (собственная ИТД или Cloudflare Turnstile) для входа в itd-api через локальный браузер",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/KiowDev/itd-api.git",
8
+ "directory": "packages/captcha"
9
+ },
10
+ "homepage": "https://kiowdev.github.io/itd-api/packages/captcha",
11
+ "bugs": {
12
+ "url": "https://github.com/KiowDev/itd-api/issues"
13
+ },
14
+ "author": "Kiow",
15
+ "keywords": [
16
+ "itd",
17
+ "итд",
18
+ "itd-api",
19
+ "captcha",
20
+ "turnstile",
21
+ "cloudflare",
22
+ "playwright",
23
+ "patchright"
24
+ ],
25
+ "license": "MIT",
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "LICENSE",
34
+ "README.md"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "main": "./dist/index.cjs",
40
+ "module": "./dist/index.js",
41
+ "types": "./dist/index.d.ts",
42
+ "exports": {
43
+ ".": {
44
+ "import": {
45
+ "types": "./dist/index.d.ts",
46
+ "default": "./dist/index.js"
47
+ },
48
+ "require": {
49
+ "types": "./dist/index.d.cts",
50
+ "default": "./dist/index.cjs"
51
+ }
52
+ },
53
+ "./package.json": "./package.json"
54
+ },
55
+ "scripts": {
56
+ "build": "tsdown",
57
+ "dev": "tsdown --watch",
58
+ "test": "vitest run",
59
+ "smoke": "node scripts/smoke.mjs",
60
+ "drivers": "node scripts/drivers.mjs",
61
+ "typecheck": "tsc --noEmit",
62
+ "check:pack": "publint && attw --pack .",
63
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build && npm run check:pack"
64
+ },
65
+ "peerDependencies": {
66
+ "patchright": ">=1.40.0",
67
+ "playwright": ">=1.40.0"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "patchright": {
71
+ "optional": true
72
+ },
73
+ "playwright": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "devDependencies": {
78
+ "@arethetypeswrong/cli": "^0.18.5",
79
+ "@types/node": "^26.2.0",
80
+ "publint": "^0.3.24",
81
+ "tsdown": "^0.22.14",
82
+ "typescript": "^5.9.3",
83
+ "vitest": "^4.1.11"
84
+ }
85
+ }