@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 itd-api contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # @itd-api/captcha
2
+
3
+ Получает токены собственной капчи ИТД и Cloudflare Turnstile для входа через [`itd-api`](https://github.com/KiowDev/itd-api). Активного провайдера выбирает сервер.
4
+
5
+ [Руководство](https://kiowdev.github.io/itd-api/packages/captcha) ·
6
+ [API из TSDoc](https://kiowdev.github.io/itd-api/api/generated/captcha/)
7
+
8
+ Пакет запускает браузер, решает виджет выбранного типа, возвращает токен и закрывает браузер.
9
+
10
+ ## Когда пакет не нужен
11
+
12
+ Капча участвует только в самом входе по паролю — ни продление сессии, ни обычные запросы,
13
+ ни событийные соединения её не требуют. Пакет незачем ставить, если:
14
+
15
+ - сессия уже сохранена в `FileTokenStorage` — клиент продлевает её сам;
16
+ - токены можно [скопировать из браузера](https://kiowdev.github.io/itd-api/authentication/#токены-из-браузера), где вы уже вошли: DevTools отдают и access token, и cookie `refresh_token`;
17
+ - access token выдаёт серверное приложение или хранилище секретов — тогда подойдёт `auth: { getToken }`;
18
+ - токен капчи приходит из своего источника — `auth.captcha.getToken` принимает любую функцию.
19
+
20
+ ## Установка
21
+
22
+ ```sh
23
+ npm i @itd-api/captcha patchright
24
+ npx patchright install chromium
25
+ ```
26
+
27
+ Драйвер подключается динамически, в порядке `patchright` → `playwright` → `playwright-core`:
28
+ что из этого установлено, то и берётся. Все три — необязательные одноранговые зависимости,
29
+ достаточно любой одной. Любой другой совместимый по API драйвер передаётся через `launch`.
30
+ Какие связки сейчас выдают токен — в разделе [Совместимость](#совместимость).
31
+
32
+ ## Использование
33
+
34
+ ```ts
35
+ import { ItdClient } from 'itd-api';
36
+ import { FileTokenStorage } from 'itd-api/node';
37
+ import { createCaptchaSolver } from '@itd-api/captcha';
38
+
39
+ const itd = new ItdClient({
40
+ storage: new FileTokenStorage('./.itd-session.json'),
41
+ auth: {
42
+ email: process.env.ITD_EMAIL!,
43
+ password: process.env.ITD_PASSWORD!,
44
+ captcha: createCaptchaSolver(),
45
+ },
46
+ });
47
+ ```
48
+
49
+ `createCaptchaSolver()` отдаёт `getToken(type)`. Перед каждым входом клиент читает активного
50
+ провайдера и просит решить именно его виджет, поэтому переключение провайдера сервером
51
+ переживается без правок кода. Передаётся функция, а не готовый токен: токен одноразовый и
52
+ живёт несколько минут. Браузер поднимается на время одного вызова и сразу закрывается.
53
+
54
+ Решить капчу без клиента — тип называется явно:
55
+
56
+ ```ts
57
+ import { solveCaptcha, CaptchaType } from '@itd-api/captcha';
58
+
59
+ const token = await solveCaptcha(CaptchaType.Itd);
60
+ ```
61
+
62
+ Провайдера с другими настройками собирает его фабрика:
63
+
64
+ ```ts
65
+ import { solveCaptcha, itdCaptcha, turnstile } from '@itd-api/captcha';
66
+
67
+ const forRegistration = await solveCaptcha(itdCaptcha({ action: 'register' }));
68
+ const withOwnKey = await solveCaptcha(turnstile({ sitekey: '0x…' }), { headless: true });
69
+ ```
70
+
71
+ ## Свой провайдер
72
+
73
+ Новый виджет добавляется реализацией `CaptchaHandler` — менять пакет не нужно. Перехват
74
+ навигации, ожидание, клик по чекбоксу и повторы одинаковы для всех виджетов и уже сделаны;
75
+ описать нужно только то, чем виджет отличается:
76
+
77
+ ```ts
78
+ import { solveCaptcha, type CaptchaHandler } from '@itd-api/captcha';
79
+
80
+ const myCaptcha: CaptchaHandler = {
81
+ type: 'my-captcha',
82
+ label: 'моей капчи',
83
+ // Страница отдаётся вместо настоящей по адресу из `origin`, поэтому виджет видит верный домен.
84
+ buildPage: ({ theme }) => `<!doctype html>…<div id="widget" data-theme="${theme}"></div>…`,
85
+ // По этому селектору видно, что виджет отрисовался.
86
+ widgetReadySelector: '#widget',
87
+ // Насколько правее левого края контейнера чекбокс: клик идёт по координатам.
88
+ checkboxOffsetX: 30,
89
+ readState: (page) =>
90
+ page.evaluate(() => {
91
+ const field = document.querySelector('#my-token') as { value?: string } | null;
92
+ return { token: field?.value || null, error: null };
93
+ }),
94
+ // Повтор не поможет — например, ключ не разрешён для домена.
95
+ isPermanentWidgetError: (code) => code === 'domain-mismatch',
96
+ };
97
+
98
+ const token = await solveCaptcha(myCaptcha);
99
+ ```
100
+
101
+ Такой обработчик подставляется и в клиент: `captcha: { type: 'my-captcha', getToken: () =>
102
+ solveCaptcha(myCaptcha), field: 'myToken' }`.
103
+
104
+ ## Запуск на сервере
105
+
106
+ **Браузер по умолчанию запускается с окном.** В безоконном режиме виджет проходится заметно
107
+ хуже: признаки такого режима видны странице. На сервере без графической оболочки поднимите
108
+ виртуальный дисплей — это надёжнее, чем `headless: true`:
109
+
110
+ ```sh
111
+ apt install xvfb
112
+ xvfb-run -a node bot.js
113
+ ```
114
+
115
+ В Docker к образу нужны системные библиотеки браузера: `npx patchright install --with-deps chromium`.
116
+
117
+ ## Как это устроено
118
+
119
+ Пакет **не заходит на сайт**. Навигация на `https://xn--d1ah4a.com/` перехватывается и вместо
120
+ настоящей страницы отдаётся своя — с одним виджетом. Домен остаётся настоящим, поэтому
121
+ привязка ключа не нарушается, а сервер видит ожидаемое имя хоста.
122
+
123
+ Из этого следует остальное:
124
+
125
+ - **пароль в браузер не попадает** — форма входа не участвует, вход выполняет сам `itd-api`;
126
+ - ничего не ломается от изменений вёрстки сайта: важен только публичный ключ виджета;
127
+ - нет гонки с настоящим запросом входа, а значит и незачем его подвешивать.
128
+
129
+ Чекбокс живёт в iframe чужого происхождения, до его DOM не дотянуться — клик идёт по
130
+ координатам. Отсчёт ведётся от собственного контейнера известного размера, поэтому попадание
131
+ не зависит от чужой вёрстки. Координаты слегка разбрасываются, первому касанию предшествует
132
+ пауза, а `User-Agent` не подменяется: заявленная версия, разошедшаяся с реальным движком,
133
+ сама по себе служит признаком автоматизации.
134
+
135
+ Капча ИТД оценивает и поведение указателя, поэтому клик идёт настоящей мышью браузера (через
136
+ CDP), а не программной установкой значения, — то же движение курсора, что и у человека.
137
+
138
+ ## Настройки
139
+
140
+ Все необязательны.
141
+
142
+ | Параметр | По умолчанию | Что делает |
143
+ | --- | --- | --- |
144
+ | `headless` | `false` | Запуск без окна. См. раздел про сервер. |
145
+ | `disableSandbox` | `false` | Отключить sandbox Chromium; только для изолированного контейнера. |
146
+ | `timeout` | `60000` | Сколько ждать токен, мс. |
147
+ | `attempts` | `2` | Сколько попыток при таймауте. |
148
+ | `theme` | `'auto'` | Оформление виджета. |
149
+ | `origin` | `https://xn--d1ah4a.com` | Сайт, чей виджет решается. |
150
+ | `driver` | перебор | Какой драйвер брать, когда установлено несколько. |
151
+ | `executablePath` | — | Путь к браузеру, если он лежит не там, где его ищет драйвер. |
152
+ | `channel` | — | Канал браузера, например `chrome`, вместо сборки из комплекта драйвера. |
153
+ | `args` | — | Дополнительные аргументы командной строки. |
154
+ | `proxy` | — | Прокси для браузера. |
155
+ | `browser` | — | Готовый браузер. Тогда пакет его не запускает и не закрывает. |
156
+ | `launch` | — | Свой запуск браузера. Заменяет все параметры запуска. |
157
+ | `contextOptions` | `locale: 'ru-RU'`, окно 1280×800 | Настройки контекста. Заменяют стандартные целиком. |
158
+ | `logger` | — | Функция для вывода хода решения, например `console.debug`. |
159
+
160
+ Ключ, назначение токена и адрес виджета — настройки конкретного провайдера, поэтому задаются
161
+ в его фабрике:
162
+
163
+ | Фабрика | Параметр | По умолчанию | Что делает |
164
+ | --- | --- | --- | --- |
165
+ | `itdCaptcha` | `sitekey` | ключ итд.com | Публичный ключ виджета. |
166
+ | `itdCaptcha` | `action` | `'login'` | Назначение токена: `login`, `register`, `password_reset`. |
167
+ | `itdCaptcha` | `captchaOrigin` | `https://captcha.xn--d1ah4a.com` | Базовый адрес виджета. |
168
+ | `turnstile` | `sitekey` | ключ итд.com | Публичный ключ виджета. |
169
+
170
+ ```ts
171
+ await solveCaptcha(itdCaptcha({ action: 'password_reset' }), { timeout: 90_000 });
172
+ ```
173
+
174
+ Свой драйвер:
175
+
176
+ ```ts
177
+ createCaptchaSolver({
178
+ launch: async () => {
179
+ const { chromium } = await import('patchright');
180
+ return chromium.launch({ headless: false });
181
+ },
182
+ });
183
+ ```
184
+
185
+ Драйверу, который собирает отпечаток браузера сам, контекст лучше отдать целиком:
186
+
187
+ ```ts
188
+ createCaptchaSolver({
189
+ contextOptions: {},
190
+ launch: async () => {
191
+ const { Camoufox } = await import('camoufox-js');
192
+ return Camoufox({ headless: false, humanize: true });
193
+ },
194
+ });
195
+ ```
196
+
197
+ ## Ошибки
198
+
199
+ Всё, что пошло не так, приходит как `CaptchaError` с полем `reason` (и `type`, когда виджет
200
+ уже выбран):
201
+
202
+ | `reason` | Что делать |
203
+ | --- | --- |
204
+ | `driver-missing` | Установить `patchright` либо передать свой `launch`. |
205
+ | `launch-failed` | Браузер не запустился: нет исполняемого файла или дисплея. |
206
+ | `browser-closed` | Окно закрыли или процесс браузера завершился до получения токена. |
207
+ | `timeout` | Виджет не отдал токен. Обычно лечится повтором. |
208
+ | `widget-error` | Виджет отказал; для Cloudflare код лежит в `widgetCode`. |
209
+
210
+ Код `110200` в `widgetCode` означает, что ключ Turnstile не разрешён для указанного домена, —
211
+ повторять бессмысленно, и пакет этого не делает.
212
+
213
+ ## Совместимость
214
+
215
+ Виджет пропускает не всякий браузер. Таблица пересобирается скриптом `scripts/drivers.mjs`
216
+ из исходников пакета:
217
+
218
+ ```sh
219
+ npm i --no-save patchright playwright camoufox-js
220
+ node scripts/drivers.mjs # Turnstile
221
+ node scripts/drivers.mjs --itd # капча ИТД
222
+ ```
223
+
224
+ Всё, что удаётся получить с окном; `headless: true` токена почти нигде не даёт. Сборка
225
+ приезжает вместе с версией драйвера, ею и выбирается — `npm i playwright@1.61`. Поставленная
226
+ отдельно тоже годится: `npx @puppeteer/browsers install chrome@149.0.7827.55` и путь
227
+ в `executablePath`. Версию запущенного браузера пакет называет в сообщении о таймауте.
228
+
229
+ ## Лицензия
230
+
231
+ MIT