yandex-smartcaptcha-solver 1.0.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 +434 -0
- package/index.js +429 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# yandex-smartcaptcha-solver
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/yandex-smartcaptcha-solver)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Автоматическое решение Yandex SmartCaptcha с использованием сервиса распознавания XEvil.
|
|
7
|
+
|
|
8
|
+
## 🚀 Установка
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install yandex-smartcaptcha-solver
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 📖 Быстрый старт
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
const YandexSmartCaptchaSolver = require('yandex-smartcaptcha-solver');
|
|
18
|
+
|
|
19
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
20
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
21
|
+
xevilApiKey: 'your_xevil_api_key',
|
|
22
|
+
siteUrl: 'https://example.com/page-with-captcha',
|
|
23
|
+
siteKey: 'ysc1_YourSiteKeyHere'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Решить капчу
|
|
27
|
+
const result = await solver.solve();
|
|
28
|
+
console.log(result);
|
|
29
|
+
// { status: 'ok', token: 'dD0xNzcx...' }
|
|
30
|
+
|
|
31
|
+
// Использовать токен в вашем запросе
|
|
32
|
+
const response = await fetch('https://example.com/api/submit', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
captcha_token: result.token,
|
|
36
|
+
// другие данные...
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 🔧 API
|
|
42
|
+
|
|
43
|
+
### Constructor
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
new YandexSmartCaptchaSolver(config, options)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Обязательные параметры (config):
|
|
50
|
+
|
|
51
|
+
- **xevilUrl** (string) - URL вашего XEvil сервера (например: `'http://127.0.0.1:80'`)
|
|
52
|
+
- **xevilApiKey** (string) - API ключ для XEvil
|
|
53
|
+
- **siteUrl** (string) - Полный URL страницы с капчей (например: `'https://example.com/page'`)
|
|
54
|
+
- **siteKey** (string) - Site key Yandex SmartCaptcha (находится в коде страницы)
|
|
55
|
+
- **coreName** (string, optional) - Имя конкретного ядра XEvil для использования (например: `'XEvil1'`, `'XEvil2'`)
|
|
56
|
+
|
|
57
|
+
#### Опциональные параметры (options):
|
|
58
|
+
|
|
59
|
+
- **userAgent** (string) - User-Agent браузера (по умолчанию: Chrome 120)
|
|
60
|
+
- **debug** (boolean) - Режим отладки с детальными логами (по умолчанию: `false`)
|
|
61
|
+
- **saveImages** (boolean) - Сохранять изображения капчи для анализа (по умолчанию: `false`)
|
|
62
|
+
- **imagesDir** (string) - Папка для сохранения изображений (по умолчанию: `'./captcha_images'`)
|
|
63
|
+
- **humanDelayMin** (number) - Минимальная задержка перед отправкой ответа в мс (по умолчанию: `2000`)
|
|
64
|
+
- **humanDelayMax** (number) - Максимальная задержка перед отправкой ответа в мс (по умолчанию: `4000`)
|
|
65
|
+
- **timeout** (number) - Timeout для HTTP запросов в мс (по умолчанию: `60000`)
|
|
66
|
+
|
|
67
|
+
### Методы
|
|
68
|
+
|
|
69
|
+
#### solve()
|
|
70
|
+
|
|
71
|
+
Решает капчу один раз.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const result = await solver.solve();
|
|
75
|
+
// { status: 'ok', token: 'dD0x...' }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Возвращает:** `Promise<{status: string, token: string}>`
|
|
79
|
+
|
|
80
|
+
**Выбрасывает:** `Error` при неудаче
|
|
81
|
+
|
|
82
|
+
#### solveWithRetry(maxAttempts, retryDelay)
|
|
83
|
+
|
|
84
|
+
Решает капчу с автоматическими повторными попытками.
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
const result = await solver.solveWithRetry(5, 4000);
|
|
88
|
+
// До 5 попыток с задержкой 4 секунды между ними
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Параметры:**
|
|
92
|
+
- `maxAttempts` (number, по умолчанию: 3) - Максимальное количество попыток
|
|
93
|
+
- `retryDelay` (number, по умолчанию: 3000) - Задержка между попытками в мс
|
|
94
|
+
|
|
95
|
+
**Возвращает:** `Promise<{status: string, token: string}>`
|
|
96
|
+
|
|
97
|
+
## 📝 Примеры
|
|
98
|
+
|
|
99
|
+
### Базовое использование
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const YandexSmartCaptchaSolver = require('yandex-smartcaptcha-solver');
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
106
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
107
|
+
xevilApiKey: 'your_api_key',
|
|
108
|
+
siteUrl: 'https://example.com/page',
|
|
109
|
+
siteKey: 'ysc1_SiteKey123'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await solver.solve();
|
|
114
|
+
console.log('Token:', result.token);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Error:', error.message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### С retry и debug режимом
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
127
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
128
|
+
xevilApiKey: 'your_api_key',
|
|
129
|
+
siteUrl: 'https://example.com/page',
|
|
130
|
+
siteKey: 'ysc1_SiteKey123',
|
|
131
|
+
coreName: 'XEvil2' // Использовать конкретное ядро XEvil
|
|
132
|
+
}, {
|
|
133
|
+
debug: true, // Включить детальные логи
|
|
134
|
+
saveImages: true, // Сохранять изображения
|
|
135
|
+
humanDelayMin: 3000, // Увеличить задержку до 3-6 секунд
|
|
136
|
+
humanDelayMax: 6000
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// До 5 попыток с задержкой 4 секунды
|
|
140
|
+
const result = await solver.solveWithRetry(5, 4000);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### С использованием конкретного ядра XEvil
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Если у вас несколько ядер XEvil, можно указать конкретное
|
|
147
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
148
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
149
|
+
xevilApiKey: 'your_api_key',
|
|
150
|
+
siteUrl: 'https://example.com/page',
|
|
151
|
+
siteKey: 'ysc1_SiteKey123',
|
|
152
|
+
coreName: 'XEvil1' // Название ядра из XEvil
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = await solver.solve();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Использование с переменными окружения
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
require('dotenv').config();
|
|
162
|
+
|
|
163
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
164
|
+
xevilUrl: process.env.XEVIL_URL,
|
|
165
|
+
xevilApiKey: process.env.XEVIL_API_KEY,
|
|
166
|
+
siteUrl: process.env.SITE_URL,
|
|
167
|
+
siteKey: process.env.SITE_KEY
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = await solver.solveWithRetry();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Express API endpoint
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const express = require('express');
|
|
177
|
+
const YandexSmartCaptchaSolver = require('yandex-smartcaptcha-solver');
|
|
178
|
+
|
|
179
|
+
const app = express();
|
|
180
|
+
|
|
181
|
+
app.post('/api/solve-captcha', async (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
184
|
+
xevilUrl: process.env.XEVIL_URL,
|
|
185
|
+
xevilApiKey: process.env.XEVIL_API_KEY,
|
|
186
|
+
siteUrl: req.body.siteUrl,
|
|
187
|
+
siteKey: req.body.siteKey
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const result = await solver.solveWithRetry(3);
|
|
191
|
+
res.json(result);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
res.status(500).json({ error: error.message });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
app.listen(3000, () => {
|
|
198
|
+
console.log('Server running on port 3000');
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Пример для HurmaCredit
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
const YandexSmartCaptchaSolver = require('yandex-smartcaptcha-solver');
|
|
206
|
+
|
|
207
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
208
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
209
|
+
xevilApiKey: 'your_xevil_key',
|
|
210
|
+
siteUrl: 'https://borrow.hurmacredit.ru/?term=12&amount=10000',
|
|
211
|
+
siteKey: 'ysc1_LuYGybUg4evR0gMXB5JMtarE9ExcyxmHrELxL1oWb310434a'
|
|
212
|
+
}, {
|
|
213
|
+
debug: true,
|
|
214
|
+
saveImages: true
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = await solver.solveWithRetry(5);
|
|
218
|
+
console.log('Spravka token:', result.token);
|
|
219
|
+
|
|
220
|
+
// Использовать result.token для отправки заявки
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## 🔍 Отладка
|
|
224
|
+
|
|
225
|
+
### Включение debug режима
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
229
|
+
// ... config
|
|
230
|
+
}, {
|
|
231
|
+
debug: true
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Вывод в консоль:
|
|
236
|
+
```
|
|
237
|
+
[2024-01-15T12:34:56.789Z] [INFO] Starting captcha solving process...
|
|
238
|
+
[2024-01-15T12:34:57.123Z] [DEBUG] Generated GUID: abc-123-def
|
|
239
|
+
[2024-01-15T12:34:58.456Z] [INFO] Sending captcha to XEvil...
|
|
240
|
+
[2024-01-15T12:35:03.789Z] [INFO] Captcha recognized: "текст"
|
|
241
|
+
[2024-01-15T12:35:07.012Z] [INFO] Submitting answer to Yandex...
|
|
242
|
+
[2024-01-15T12:35:08.345Z] [INFO] Captcha solved successfully!
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Сохранение изображений
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
249
|
+
// ... config
|
|
250
|
+
}, {
|
|
251
|
+
saveImages: true,
|
|
252
|
+
imagesDir: './my-captcha-images'
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Изображения сохраняются с префиксами:
|
|
257
|
+
- `captcha_timestamp_SUCCESS_text.png` - успешно решено
|
|
258
|
+
- `captcha_timestamp_FAILED_text.png` - Яндекс отклонил
|
|
259
|
+
- `captcha_timestamp_UNKNOWN_text.png` - ошибка при решении
|
|
260
|
+
|
|
261
|
+
## ⚙️ Как найти siteKey
|
|
262
|
+
|
|
263
|
+
1. Откройте страницу с капчей в браузере
|
|
264
|
+
2. Откройте DevTools (F12)
|
|
265
|
+
3. Перейдите во вкладку Elements/Инспектор
|
|
266
|
+
4. Найдите элемент с `smartcaptcha.yandexcloud.net`
|
|
267
|
+
5. Найдите параметр `data-sitekey` или `sitekey` в коде
|
|
268
|
+
6. Скопируйте значение (обычно начинается с `ysc1_`)
|
|
269
|
+
|
|
270
|
+
Или через консоль браузера:
|
|
271
|
+
```javascript
|
|
272
|
+
// В консоли DevTools
|
|
273
|
+
document.querySelector('[data-sitekey]').getAttribute('data-sitekey')
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## 🎯 Использование конкретного ядра XEvil (coreName)
|
|
277
|
+
|
|
278
|
+
Если у вас установлено несколько ядер XEvil, вы можете указать конкретное ядро для решения капчи:
|
|
279
|
+
|
|
280
|
+
### Как узнать имя ядра:
|
|
281
|
+
1. Откройте интерфейс XEvil (обычно http://127.0.0.1:80)
|
|
282
|
+
2. В разделе "Cores" / "Ядра" вы увидите список доступных ядер
|
|
283
|
+
3. Имена обычно выглядят как: `XEvil1`, `XEvil2`, `XEvil3`, и т.д.
|
|
284
|
+
|
|
285
|
+
### Примеры использования:
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
// Использовать конкретное ядро
|
|
289
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
290
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
291
|
+
xevilApiKey: 'your_key',
|
|
292
|
+
siteUrl: 'https://example.com',
|
|
293
|
+
siteKey: 'ysc1_SiteKey',
|
|
294
|
+
coreName: 'XEvil2' // Указываем имя ядра
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
// Через переменную окружения
|
|
300
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
301
|
+
xevilUrl: process.env.XEVIL_URL,
|
|
302
|
+
xevilApiKey: process.env.XEVIL_API_KEY,
|
|
303
|
+
siteUrl: process.env.SITE_URL,
|
|
304
|
+
siteKey: process.env.SITE_KEY,
|
|
305
|
+
coreName: process.env.XEVIL_CORE // XEvil1, XEvil2, etc.
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
// Если не указывать coreName, будет использовано ядро по умолчанию
|
|
311
|
+
const solver = new YandexSmartCaptchaSolver({
|
|
312
|
+
xevilUrl: 'http://127.0.0.1:80',
|
|
313
|
+
xevilApiKey: 'your_key',
|
|
314
|
+
siteUrl: 'https://example.com',
|
|
315
|
+
siteKey: 'ysc1_SiteKey'
|
|
316
|
+
// coreName не указан - используется default core
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Зачем использовать конкретное ядро?
|
|
321
|
+
|
|
322
|
+
- **Балансировка нагрузки** - распределить задачи между ядрами
|
|
323
|
+
- **Приоритизация** - выделить отдельное ядро для важных задач
|
|
324
|
+
- **Производительность** - разные ядра могут иметь разную скорость/точность
|
|
325
|
+
- **Изоляция** - разделить проекты по ядрам
|
|
326
|
+
|
|
327
|
+
## 🛠️ Требования
|
|
328
|
+
|
|
329
|
+
- Node.js >= 14.0.0
|
|
330
|
+
- Работающий XEvil сервер (купить на xevil.net)
|
|
331
|
+
- npm пакеты: axios, uuid, form-data
|
|
332
|
+
|
|
333
|
+
## 📊 Производительность
|
|
334
|
+
|
|
335
|
+
- **Среднее время решения:** 8-15 секунд
|
|
336
|
+
- XEvil распознавание: 5-10 секунд
|
|
337
|
+
- Задержка перед submit: 2-4 секунды
|
|
338
|
+
- **Успешность:** ~70-90% (с использованием retry)
|
|
339
|
+
|
|
340
|
+
## ⚠️ Обработка ошибок
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
try {
|
|
344
|
+
const result = await solver.solve();
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (error.message.includes('XEvil')) {
|
|
347
|
+
console.error('Проблема с XEvil:', error.message);
|
|
348
|
+
// Проверьте что XEvil запущен и доступен
|
|
349
|
+
} else if (error.message.includes('Yandex rejected')) {
|
|
350
|
+
console.error('Яндекс отклонил ответ:', error.message);
|
|
351
|
+
// Попробуйте увеличить humanDelay или используйте retry
|
|
352
|
+
} else if (error.message.includes('timeout')) {
|
|
353
|
+
console.error('Timeout:', error.message);
|
|
354
|
+
// Увеличьте timeout в настройках
|
|
355
|
+
} else {
|
|
356
|
+
console.error('Неизвестная ошибка:', error.message);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## 💡 Советы
|
|
362
|
+
|
|
363
|
+
1. **Всегда используйте retry** для повышения успешности:
|
|
364
|
+
```javascript
|
|
365
|
+
await solver.solveWithRetry(5, 4000);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
2. **Увеличьте задержку** если Яндекс часто отклоняет правильные ответы:
|
|
369
|
+
```javascript
|
|
370
|
+
{ humanDelayMin: 5000, humanDelayMax: 8000 }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
3. **Используйте переменные окружения** для конфиденциальных данных:
|
|
374
|
+
```javascript
|
|
375
|
+
xevilApiKey: process.env.XEVIL_API_KEY
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
4. **Включите debug и saveImages** при проблемах с решением
|
|
379
|
+
|
|
380
|
+
5. **Мониторьте баланс XEvil:**
|
|
381
|
+
```bash
|
|
382
|
+
curl "http://127.0.0.1:80/res.php?key=YOUR_KEY&action=getbalance"
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## 🐛 Известные проблемы
|
|
386
|
+
|
|
387
|
+
### "Yandex rejected the answer"
|
|
388
|
+
|
|
389
|
+
**Причина:** Яндекс отклоняет правильно распознанный текст
|
|
390
|
+
|
|
391
|
+
**Решение:**
|
|
392
|
+
- Увеличьте `humanDelayMin` и `humanDelayMax` до 5-8 секунд
|
|
393
|
+
- Используйте `solveWithRetry()` для автоматических повторов
|
|
394
|
+
- Проверьте что siteKey актуален
|
|
395
|
+
|
|
396
|
+
### "XEvil timeout"
|
|
397
|
+
|
|
398
|
+
**Причина:** XEvil слишком долго решает капчу
|
|
399
|
+
|
|
400
|
+
**Решение:**
|
|
401
|
+
- Проверьте что XEvil запущен: `curl http://127.0.0.1:80`
|
|
402
|
+
- Проверьте баланс XEvil
|
|
403
|
+
- Убедитесь что сервер не перегружен
|
|
404
|
+
|
|
405
|
+
## 📄 Лицензия
|
|
406
|
+
|
|
407
|
+
MIT
|
|
408
|
+
|
|
409
|
+
## ⚖️ Disclaimer
|
|
410
|
+
|
|
411
|
+
Этот инструмент предназначен только для законного использования, тестирования и образовательных целей. Убедитесь, что вы не нарушаете Terms of Service целевого сайта. Авторы не несут ответственности за неправомерное использование.
|
|
412
|
+
|
|
413
|
+
## 🤝 Вклад
|
|
414
|
+
|
|
415
|
+
Contributions приветствуются! Пожалуйста, создавайте pull requests или issues на GitHub.
|
|
416
|
+
|
|
417
|
+
## 📞 Поддержка
|
|
418
|
+
|
|
419
|
+
Если у вас возникли проблемы:
|
|
420
|
+
|
|
421
|
+
1. Проверьте что все требования выполнены
|
|
422
|
+
2. Включите `debug: true` и проверьте логи
|
|
423
|
+
3. Проверьте сохраненные изображения (если `saveImages: true`)
|
|
424
|
+
4. Создайте issue на GitHub с логами и описанием проблемы
|
|
425
|
+
|
|
426
|
+
## 🔗 Полезные ссылки
|
|
427
|
+
|
|
428
|
+
- [XEvil официальный сайт](https://xevil.net)
|
|
429
|
+
- [Yandex SmartCaptcha документация](https://cloud.yandex.ru/docs/smartcaptcha/)
|
|
430
|
+
- [GitHub репозиторий](https://github.com/yourusername/yandex-smartcaptcha-solver)
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
Made with ❤️ for automation
|
package/index.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
const FormData = require('form-data');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Yandex SmartCaptcha Solver
|
|
9
|
+
* Автоматическое решение Yandex SmartCaptcha через XEvil
|
|
10
|
+
* @module yandex-smartcaptcha-solver
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class YandexSmartCaptchaSolver {
|
|
14
|
+
/**
|
|
15
|
+
* Создать новый экземпляр солвера
|
|
16
|
+
* @param {Object} config - Конфигурация
|
|
17
|
+
* @param {string} config.xevilUrl - URL XEvil сервера (например: 'http://127.0.0.1:80')
|
|
18
|
+
* @param {string} config.xevilApiKey - API ключ для XEvil
|
|
19
|
+
* @param {string} config.siteUrl - Полный URL страницы с капчей
|
|
20
|
+
* @param {string} config.siteKey - Site key Yandex SmartCaptcha
|
|
21
|
+
* @param {string} [config.coreName] - Имя ядра XEvil для использования (опционально)
|
|
22
|
+
* @param {Object} [options] - Дополнительные опции
|
|
23
|
+
* @param {string} [options.userAgent] - User-Agent браузера
|
|
24
|
+
* @param {boolean} [options.debug=false] - Режим отладки
|
|
25
|
+
* @param {boolean} [options.saveImages=false] - Сохранять изображения капчи
|
|
26
|
+
* @param {string} [options.imagesDir='./captcha_images'] - Папка для изображений
|
|
27
|
+
* @param {number} [options.humanDelayMin=2000] - Минимальная задержка перед submit (мс)
|
|
28
|
+
* @param {number} [options.humanDelayMax=4000] - Максимальная задержка перед submit (мс)
|
|
29
|
+
* @param {number} [options.timeout=60000] - Timeout для HTTP запросов (мс)
|
|
30
|
+
*/
|
|
31
|
+
constructor(config, options = {}) {
|
|
32
|
+
if (!config.xevilUrl) {
|
|
33
|
+
throw new Error('xevilUrl is required');
|
|
34
|
+
}
|
|
35
|
+
if (!config.xevilApiKey) {
|
|
36
|
+
throw new Error('xevilApiKey is required');
|
|
37
|
+
}
|
|
38
|
+
if (!config.siteUrl) {
|
|
39
|
+
throw new Error('siteUrl is required');
|
|
40
|
+
}
|
|
41
|
+
if (!config.siteKey) {
|
|
42
|
+
throw new Error('siteKey is required');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Парсим URL
|
|
46
|
+
const url = new URL(config.siteUrl);
|
|
47
|
+
|
|
48
|
+
this.config = {
|
|
49
|
+
xevilUrl: config.xevilUrl,
|
|
50
|
+
xevilApiKey: config.xevilApiKey,
|
|
51
|
+
siteUrl: config.siteUrl,
|
|
52
|
+
siteKey: config.siteKey,
|
|
53
|
+
host: url.hostname,
|
|
54
|
+
coreName: config.coreName || null, // Опциональное имя ядра
|
|
55
|
+
|
|
56
|
+
// Опции
|
|
57
|
+
userAgent: options.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
58
|
+
debug: options.debug || false,
|
|
59
|
+
saveImages: options.saveImages || false,
|
|
60
|
+
imagesDir: options.imagesDir || './captcha_images',
|
|
61
|
+
humanDelayMin: options.humanDelayMin || 2000,
|
|
62
|
+
humanDelayMax: options.humanDelayMax || 4000,
|
|
63
|
+
timeout: options.timeout || 60000
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
this.state = {
|
|
67
|
+
captchaId: null,
|
|
68
|
+
recognizedText: null
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.axios = axios.create({
|
|
72
|
+
headers: {
|
|
73
|
+
'User-Agent': this.config.userAgent,
|
|
74
|
+
'Accept': '*/*',
|
|
75
|
+
'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8',
|
|
76
|
+
'Accept-Encoding': 'gzip, deflate, br'
|
|
77
|
+
},
|
|
78
|
+
timeout: this.config.timeout
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (this.config.saveImages && !fs.existsSync(this.config.imagesDir)) {
|
|
82
|
+
fs.mkdirSync(this.config.imagesDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Логирование
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
_log(message, level = 'info') {
|
|
91
|
+
if (!this.config.debug && level === 'debug') return;
|
|
92
|
+
|
|
93
|
+
const timestamp = new Date().toISOString();
|
|
94
|
+
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
|
|
95
|
+
|
|
96
|
+
console.log(`${prefix} ${message}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Сохранить изображение капчи
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
_saveImage(imageBuffer, text, success) {
|
|
104
|
+
if (!this.config.saveImages) return;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const timestamp = Date.now();
|
|
108
|
+
const status = success === true ? 'SUCCESS' : success === false ? 'FAILED' : 'UNKNOWN';
|
|
109
|
+
const cleanText = text.replace(/[^a-zA-Zа-яА-Я0-9]/g, '_');
|
|
110
|
+
const filename = `captcha_${timestamp}_${status}_${cleanText}.png`;
|
|
111
|
+
const filepath = path.join(this.config.imagesDir, filename);
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(filepath, imageBuffer);
|
|
114
|
+
this._log(`Image saved: ${filename}`, 'debug');
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this._log(`Failed to save image: ${error.message}`, 'warn');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Сообщить XEvil о неправильном ответе
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
async _reportBadCaptcha() {
|
|
125
|
+
if (!this.state.captchaId) return;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
this._log(`Reporting incorrect answer to XEvil (ID: ${this.state.captchaId})`, 'debug');
|
|
129
|
+
|
|
130
|
+
await axios.get(`${this.config.xevilUrl}/res.php`, {
|
|
131
|
+
params: {
|
|
132
|
+
key: this.config.xevilApiKey,
|
|
133
|
+
action: 'reportbad',
|
|
134
|
+
id: this.state.captchaId
|
|
135
|
+
},
|
|
136
|
+
timeout: 10000
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this._log('Bad answer reported successfully', 'debug');
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this._log(`Failed to report bad answer: ${error.message}`, 'warn');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Первый запрос к Yandex SmartCaptcha
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
async _firstCheck() {
|
|
150
|
+
this._log('Performing first check request...', 'debug');
|
|
151
|
+
|
|
152
|
+
const url = `https://smartcaptcha.yandexcloud.net/check?host=${this.config.host}&sitekey=${this.config.siteKey}&href=${encodeURIComponent(this.config.siteUrl)}`;
|
|
153
|
+
const data = `sitekey=${this.config.siteKey}`;
|
|
154
|
+
|
|
155
|
+
const headers = {
|
|
156
|
+
'Referer': `https://smartcaptcha.yandexcloud.net/backend.636bb879d1085041bc19.html?sitekey=${this.config.siteKey}&theme=light&hl=ru&host=${this.config.host}&href=${encodeURIComponent(this.config.siteUrl)}&test=false&webview=false&hideChallengeContainer=false`,
|
|
157
|
+
'Content-Type': 'text/plain;charset=UTF-8',
|
|
158
|
+
'Origin': 'https://smartcaptcha.yandexcloud.net',
|
|
159
|
+
'Sec-Fetch-Dest': 'empty',
|
|
160
|
+
'Sec-Fetch-Mode': 'cors',
|
|
161
|
+
'Sec-Fetch-Site': 'same-origin'
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const response = await this.axios.post(url, data, { headers });
|
|
165
|
+
return response.data;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Второй запрос к Yandex SmartCaptcha
|
|
170
|
+
* @private
|
|
171
|
+
*/
|
|
172
|
+
async _secondCheck(captchaKey) {
|
|
173
|
+
this._log('Performing second check request...', 'debug');
|
|
174
|
+
|
|
175
|
+
const url = `https://smartcaptcha.yandexcloud.net/check?host=${this.config.host}&sitekey=${this.config.siteKey}&href=${encodeURIComponent(this.config.siteUrl)}`;
|
|
176
|
+
const data = `key=${captchaKey}&sitekey=${this.config.siteKey}&lang=ru&test=false&webview=false`;
|
|
177
|
+
|
|
178
|
+
const headers = {
|
|
179
|
+
'Content-Type': 'text/plain;charset=UTF-8',
|
|
180
|
+
'Origin': 'https://smartcaptcha.yandexcloud.net',
|
|
181
|
+
'Sec-Fetch-Dest': 'empty',
|
|
182
|
+
'Sec-Fetch-Mode': 'cors',
|
|
183
|
+
'Sec-Fetch-Site': 'same-origin'
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const response = await this.axios.post(url, data, { headers });
|
|
187
|
+
return response.data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Получить изображение капчи
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
async _getImage(imageUrl) {
|
|
195
|
+
this._log('Downloading captcha image...', 'debug');
|
|
196
|
+
|
|
197
|
+
const response = await this.axios.get(imageUrl, {
|
|
198
|
+
responseType: 'arraybuffer',
|
|
199
|
+
headers: {
|
|
200
|
+
'Referer': 'https://smartcaptcha.yandexcloud.net/'
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this._log(`Image downloaded: ${response.data.length} bytes`, 'debug');
|
|
205
|
+
return Buffer.from(response.data);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Распознать капчу через XEvil
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
async _recognize(imageBuffer) {
|
|
213
|
+
this._log('Sending captcha to XEvil...', 'info');
|
|
214
|
+
|
|
215
|
+
const base64Image = imageBuffer.toString('base64');
|
|
216
|
+
|
|
217
|
+
const formData = new FormData();
|
|
218
|
+
formData.append('key', this.config.xevilApiKey);
|
|
219
|
+
formData.append('method', 'base64');
|
|
220
|
+
formData.append('body', base64Image);
|
|
221
|
+
formData.append('json', '1');
|
|
222
|
+
|
|
223
|
+
// Добавляем coreName если указан
|
|
224
|
+
if (this.config.coreName) {
|
|
225
|
+
formData.append('core', this.config.coreName);
|
|
226
|
+
this._log(`Using XEvil core: ${this.config.coreName}`, 'debug');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const inResponse = await axios.post(
|
|
230
|
+
`${this.config.xevilUrl}/in.php`,
|
|
231
|
+
formData,
|
|
232
|
+
{
|
|
233
|
+
headers: formData.getHeaders(),
|
|
234
|
+
timeout: 30000
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (inResponse.data.status !== 1) {
|
|
239
|
+
throw new Error(`XEvil error: ${inResponse.data.request || JSON.stringify(inResponse.data)}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.state.captchaId = inResponse.data.request;
|
|
243
|
+
this._log(`Captcha sent to XEvil (ID: ${this.state.captchaId})`, 'debug');
|
|
244
|
+
|
|
245
|
+
// Ожидание результата
|
|
246
|
+
this._log('Waiting for XEvil to solve captcha...', 'info');
|
|
247
|
+
|
|
248
|
+
const maxAttempts = 30;
|
|
249
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
250
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
251
|
+
|
|
252
|
+
const resResponse = await axios.get(
|
|
253
|
+
`${this.config.xevilUrl}/res.php`,
|
|
254
|
+
{
|
|
255
|
+
params: {
|
|
256
|
+
key: this.config.xevilApiKey,
|
|
257
|
+
action: 'get',
|
|
258
|
+
id: this.state.captchaId,
|
|
259
|
+
json: 1
|
|
260
|
+
},
|
|
261
|
+
timeout: 30000
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
if (resResponse.data.status === 1) {
|
|
266
|
+
const result = resResponse.data.request.trim();
|
|
267
|
+
this._log(`Captcha recognized: "${result}"`, 'info');
|
|
268
|
+
this.state.recognizedText = result;
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (resResponse.data.request !== 'CAPCHA_NOT_READY') {
|
|
273
|
+
throw new Error(`XEvil error: ${resResponse.data.request}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this._log(`Attempt ${attempt}/${maxAttempts}: still processing...`, 'debug');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
throw new Error('XEvil timeout: captcha not solved in time');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Закодировать результат
|
|
284
|
+
* @private
|
|
285
|
+
*/
|
|
286
|
+
_encode(text) {
|
|
287
|
+
const bytes = Buffer.from(text, 'utf8');
|
|
288
|
+
let encoded = '';
|
|
289
|
+
|
|
290
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
291
|
+
encoded += '%' + bytes[i].toString(16).toUpperCase().padStart(2, '0');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return encoded;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Отправить результат в Yandex
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
async _submit(captchaKey, encodedResult) {
|
|
302
|
+
this._log('Submitting answer to Yandex...', 'info');
|
|
303
|
+
|
|
304
|
+
// Задержка для имитации человека
|
|
305
|
+
const delay = this.config.humanDelayMin +
|
|
306
|
+
Math.random() * (this.config.humanDelayMax - this.config.humanDelayMin);
|
|
307
|
+
this._log(`Waiting ${Math.round(delay/1000)}s before submit (human simulation)...`, 'debug');
|
|
308
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
309
|
+
|
|
310
|
+
const url = `https://smartcaptcha.yandexcloud.net/check?host=${this.config.host}&sitekey=${this.config.siteKey}&href=${encodeURIComponent(this.config.siteUrl)}`;
|
|
311
|
+
const data = `key=${captchaKey}&sitekey=${this.config.siteKey}&lang=ru&test=false&webview=false&rep=${encodedResult}`;
|
|
312
|
+
|
|
313
|
+
const headers = {
|
|
314
|
+
'Content-Type': 'text/plain;charset=UTF-8',
|
|
315
|
+
'Origin': 'https://smartcaptcha.yandexcloud.net',
|
|
316
|
+
'Sec-Fetch-Dest': 'empty',
|
|
317
|
+
'Sec-Fetch-Mode': 'cors',
|
|
318
|
+
'Sec-Fetch-Site': 'same-origin'
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const response = await this.axios.post(url, data, { headers });
|
|
322
|
+
return response.data;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Решить капчу
|
|
327
|
+
* @returns {Promise<Object>} Результат с полями status и token (spravka)
|
|
328
|
+
* @throws {Error} При неудаче
|
|
329
|
+
* @example
|
|
330
|
+
* const result = await solver.solve();
|
|
331
|
+
* console.log(result); // { status: 'ok', token: 'dD0x...' }
|
|
332
|
+
*/
|
|
333
|
+
async solve() {
|
|
334
|
+
let imageBuffer = null;
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
this._log('Starting captcha solving process...', 'info');
|
|
338
|
+
|
|
339
|
+
// Генерируем GUID
|
|
340
|
+
const guid = uuidv4();
|
|
341
|
+
this._log(`Generated GUID: ${guid}`, 'debug');
|
|
342
|
+
|
|
343
|
+
// Первый запрос
|
|
344
|
+
const firstResponse = await this._firstCheck();
|
|
345
|
+
if (!firstResponse.captcha?.key) {
|
|
346
|
+
throw new Error('No captcha key received from first request');
|
|
347
|
+
}
|
|
348
|
+
this._log('First captcha key received', 'debug');
|
|
349
|
+
|
|
350
|
+
// Второй запрос
|
|
351
|
+
const secondResponse = await this._secondCheck(firstResponse.captcha.key);
|
|
352
|
+
if (!secondResponse.captcha?.image) {
|
|
353
|
+
throw new Error('No captcha image URL received');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const captchaKey = secondResponse.captcha.key;
|
|
357
|
+
this._log('Second captcha key received', 'debug');
|
|
358
|
+
|
|
359
|
+
// Получаем и распознаем изображение
|
|
360
|
+
imageBuffer = await this._getImage(secondResponse.captcha.image);
|
|
361
|
+
const recognizedText = await this._recognize(imageBuffer);
|
|
362
|
+
|
|
363
|
+
// Кодируем и отправляем
|
|
364
|
+
const encodedResult = this._encode(recognizedText);
|
|
365
|
+
this._log(`Encoded result: ${encodedResult}`, 'debug');
|
|
366
|
+
|
|
367
|
+
const finalResponse = await this._submit(captchaKey, encodedResult);
|
|
368
|
+
|
|
369
|
+
// Проверяем результат
|
|
370
|
+
if (finalResponse.status === 'ok') {
|
|
371
|
+
this._log('Captcha solved successfully!', 'info');
|
|
372
|
+
this._saveImage(imageBuffer, recognizedText, true);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
status: 'ok',
|
|
376
|
+
token: finalResponse.token || finalResponse.key
|
|
377
|
+
};
|
|
378
|
+
} else {
|
|
379
|
+
// Неудача - сообщаем XEvil
|
|
380
|
+
await this._reportBadCaptcha();
|
|
381
|
+
this._saveImage(imageBuffer, recognizedText, false);
|
|
382
|
+
|
|
383
|
+
throw new Error(`Yandex rejected the answer. Recognized text: "${recognizedText}"`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
} catch (error) {
|
|
387
|
+
this._log(`Error: ${error.message}`, 'error');
|
|
388
|
+
|
|
389
|
+
if (imageBuffer && this.state.recognizedText) {
|
|
390
|
+
this._saveImage(imageBuffer, this.state.recognizedText, null);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Решить капчу с автоматическими повторными попытками
|
|
399
|
+
* @param {number} [maxAttempts=3] - Максимальное количество попыток
|
|
400
|
+
* @param {number} [retryDelay=3000] - Задержка между попытками (мс)
|
|
401
|
+
* @returns {Promise<Object>} Результат с полями status и token
|
|
402
|
+
* @throws {Error} Если все попытки неудачны
|
|
403
|
+
* @example
|
|
404
|
+
* const result = await solver.solveWithRetry(5, 4000);
|
|
405
|
+
*/
|
|
406
|
+
async solveWithRetry(maxAttempts = 3, retryDelay = 3000) {
|
|
407
|
+
this._log(`Will attempt to solve up to ${maxAttempts} times`, 'info');
|
|
408
|
+
|
|
409
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
410
|
+
try {
|
|
411
|
+
this._log(`Attempt ${attempt}/${maxAttempts}`, 'info');
|
|
412
|
+
const result = await this.solve();
|
|
413
|
+
this._log(`Successfully solved on attempt ${attempt}!`, 'info');
|
|
414
|
+
return result;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
this._log(`Attempt ${attempt} failed: ${error.message}`, 'warn');
|
|
417
|
+
|
|
418
|
+
if (attempt < maxAttempts) {
|
|
419
|
+
this._log(`Waiting ${retryDelay/1000}s before retry...`, 'info');
|
|
420
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
throw new Error(`Failed to solve captcha after ${maxAttempts} attempts`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
module.exports = YandexSmartCaptchaSolver;
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yandex-smartcaptcha-solver",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatic Yandex SmartCaptcha solver using XEvil OCR service",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node test/test.js",
|
|
8
|
+
"example": "node examples/basic.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"captcha",
|
|
12
|
+
"yandex",
|
|
13
|
+
"smartcaptcha",
|
|
14
|
+
"captcha-solver",
|
|
15
|
+
"xevil",
|
|
16
|
+
"ocr",
|
|
17
|
+
"automation",
|
|
18
|
+
"bot"
|
|
19
|
+
],
|
|
20
|
+
"author": "SH3EL1T",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/sh3el1t/yandex-smartcaptcha-solver.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/sh3el1t/yandex-smartcaptcha-solver/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/sh3el1t/yandex-smartcaptcha-solver#readme",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"axios": "^1.6.0",
|
|
32
|
+
"uuid": "^9.0.0",
|
|
33
|
+
"form-data": "^4.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"dotenv": "^16.0.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=14.0.0"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"index.js",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
]
|
|
46
|
+
}
|