qwen-api-proxy 1.0.12 → 1.0.14
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 +5 -5
- package/bin/qwen-api-proxy.js +53 -53
- package/index.js +33 -33
- package/package.json +8 -2
- package/src/api/chat.js +71 -71
- package/src/api/chatHistory.js +19 -19
- package/src/api/fileUpload.js +23 -23
- package/src/api/imageGeneration.js +23 -23
- package/src/api/modelMapping.js +145 -153
- package/src/api/routes.js +148 -148
- package/src/api/tokenManager.js +93 -93
- package/src/browser/auth.js +13 -13
- package/src/browser/browser.js +9 -9
- package/src/browser/session.js +3 -3
- package/src/config.js +4 -4
- package/src/utils/accountSetup.js +14 -14
- package/src/utils/botSettings.js +14 -14
- package/src/utils/permissionChecker.js +157 -157
- package/src/utils/prompt.js +1 -1
- package/src/utils/proxy.js +11 -11
- package/src/utils/telegramBot.js +692 -664
- package/src/utils/telegramNotifier.js +7 -7
package/src/utils/telegramBot.js
CHANGED
|
@@ -40,7 +40,7 @@ function loadPersistedSettings() {
|
|
|
40
40
|
activeModel = settings.activeModel;
|
|
41
41
|
logInfo(`📝 Загружена активная модель: ${activeModel}`);
|
|
42
42
|
} else {
|
|
43
|
-
logInfo(
|
|
43
|
+
logInfo('📝 Активная модель не установлена, используется модель по умолчанию');
|
|
44
44
|
}
|
|
45
45
|
llmChatEnabled = settings.llmChatEnabled || false;
|
|
46
46
|
logInfo(`📝 LLM чат: ${llmChatEnabled ? 'включен' : 'выключен'}`);
|
|
@@ -74,27 +74,27 @@ async function checkAIHealth(tokens) {
|
|
|
74
74
|
|
|
75
75
|
try {
|
|
76
76
|
logInfo('🧪 Тестирование AI нейросети (ping pong)...');
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// Импортируем функцию sendMessage для прямого запроса к Qwen
|
|
79
79
|
const { sendMessage } = await import('../api/chat.js');
|
|
80
80
|
const testModel = getBotSettingsModel();
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
// Делаем запрос напрямую к Qwen API через наш модуль
|
|
83
83
|
const startTime = Date.now();
|
|
84
84
|
const result = await sendMessage('ping', testModel, null, null, null, null, null, null, 't2t', null, true, 0);
|
|
85
85
|
const responseTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
if (result && !result.error) {
|
|
88
88
|
const responseContent = result.choices?.[0]?.message?.content || '';
|
|
89
89
|
const usedModel = result.model || testModel;
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
// Проверяем что ответ содержит "pong" (в любом регистре)
|
|
92
92
|
const hasPong = responseContent.toLowerCase().includes('pong');
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
if (hasPong) {
|
|
95
95
|
// Тест пройден - ответ содержит pong
|
|
96
96
|
logInfo(`✅ AI тест прошел успешно: модель=${usedModel}, время=${responseTime}с, ответ="${responseContent.substring(0, 50)}"`);
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
return [
|
|
99
99
|
{
|
|
100
100
|
name: '🧠 AI Нейросеть',
|
|
@@ -110,16 +110,16 @@ async function checkAIHealth(tokens) {
|
|
|
110
110
|
} else {
|
|
111
111
|
// Тест не пройден - ответ не содержит pong
|
|
112
112
|
const fullResponse = JSON.stringify(result, null, 2);
|
|
113
|
-
|
|
114
|
-
logError(
|
|
113
|
+
|
|
114
|
+
logError('❌ AI тест не пройден: ответ не содержит "pong"');
|
|
115
115
|
logError(`Получен ответ: ${responseContent.substring(0, 200)}`);
|
|
116
116
|
logDebug(`Полный JSON ответа: ${fullResponse.substring(0, 1000)}`);
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
return [
|
|
119
119
|
{
|
|
120
120
|
name: '🧠 AI Нейросеть',
|
|
121
121
|
status: false,
|
|
122
|
-
details:
|
|
122
|
+
details: '❌ Тест не пройден: ответ не содержит "pong"'
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
name: ' 📝 Ответ',
|
|
@@ -132,10 +132,10 @@ async function checkAIHealth(tokens) {
|
|
|
132
132
|
// Ошибка от API
|
|
133
133
|
const errorMsg = result.error || 'Unknown error';
|
|
134
134
|
const fullResponse = JSON.stringify(result, null, 2);
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
logError(`❌ AI тест не пройден: ${errorMsg}`);
|
|
137
137
|
logDebug(`Полный JSON ошибки: ${fullResponse.substring(0, 1000)}`);
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
return [{
|
|
140
140
|
name: '🧠 AI Нейросеть',
|
|
141
141
|
status: false,
|
|
@@ -145,7 +145,7 @@ async function checkAIHealth(tokens) {
|
|
|
145
145
|
} catch (error) {
|
|
146
146
|
// Ошибка подключения
|
|
147
147
|
logError('❌ AI тест не пройден (ошибка подключения)', error);
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
return [{
|
|
150
150
|
name: '🧠 AI Нейросеть',
|
|
151
151
|
status: false,
|
|
@@ -301,33 +301,33 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
301
301
|
// 2. Проверяем токены
|
|
302
302
|
const tokens = loadTokens();
|
|
303
303
|
const now = Date.now();
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
// Фильтруем только действительные токены (не invalid, не rate-limited, не истекшие, с cookies)
|
|
306
|
-
const validTokens = tokens.filter(t => {
|
|
307
|
-
if (t.invalid) return false;
|
|
308
|
-
if (t.resetAt && new Date(t.resetAt).getTime() > now) return false;
|
|
309
|
-
if (t.expiryTime && t.expiryTime <= now) return false;
|
|
306
|
+
const validTokens = tokens.filter((t) => {
|
|
307
|
+
if (t.invalid) { return false; }
|
|
308
|
+
if (t.resetAt && new Date(t.resetAt).getTime() > now) { return false; }
|
|
309
|
+
if (t.expiryTime && t.expiryTime <= now) { return false; }
|
|
310
310
|
// Проверяем наличие cookies.json
|
|
311
311
|
const cookiesPath = path.join(process.cwd(), SESSION_DIR, 'accounts', t.id, 'cookies.json');
|
|
312
|
-
if (!fs.existsSync(cookiesPath)) return false;
|
|
312
|
+
if (!fs.existsSync(cookiesPath)) { return false; }
|
|
313
313
|
return true;
|
|
314
314
|
});
|
|
315
315
|
|
|
316
316
|
// Проверка оставшегося времени для токенов
|
|
317
317
|
if (tokens.length > 0) {
|
|
318
318
|
const filteredCount = tokens.length - validTokens.length;
|
|
319
|
-
|
|
319
|
+
|
|
320
320
|
const expirySummary = validTokens.reduce((acc, token) => {
|
|
321
321
|
const now = Date.now();
|
|
322
|
-
|
|
322
|
+
|
|
323
323
|
// Если expiryTime не установлен
|
|
324
324
|
if (!token.expiryTime) {
|
|
325
325
|
acc.tokens.push({ timeStr: 'Неизвестно', id: token.id, hasExpiry: false });
|
|
326
326
|
return acc;
|
|
327
327
|
}
|
|
328
|
-
|
|
328
|
+
|
|
329
329
|
const timeLeft = token.expiryTime - now;
|
|
330
|
-
|
|
330
|
+
|
|
331
331
|
// Форматируем время в удобочитаемый вид
|
|
332
332
|
let timeStr;
|
|
333
333
|
if (timeLeft <= 0) {
|
|
@@ -338,31 +338,31 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
338
338
|
const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
339
339
|
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
|
|
340
340
|
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
const parts = [];
|
|
343
|
-
if (days > 0) parts.push(`${days}д`);
|
|
344
|
-
if (hours > 0) parts.push(`${hours}ч`);
|
|
345
|
-
if (minutes > 0) parts.push(`${minutes}м`);
|
|
343
|
+
if (days > 0) { parts.push(`${days}д`); }
|
|
344
|
+
if (hours > 0) { parts.push(`${hours}ч`); }
|
|
345
|
+
if (minutes > 0) { parts.push(`${minutes}м`); }
|
|
346
346
|
parts.push(`${seconds}с`);
|
|
347
|
-
|
|
347
|
+
|
|
348
348
|
timeStr = parts.join(' ');
|
|
349
349
|
}
|
|
350
|
-
|
|
350
|
+
|
|
351
351
|
acc.tokens.push({ timeStr, id: token.id, hasExpiry: true });
|
|
352
352
|
return acc;
|
|
353
353
|
}, { expired: 0, tokens: [] });
|
|
354
|
-
|
|
354
|
+
|
|
355
355
|
let tokenDetails = `✅ Доступно: ${validTokens.length}`;
|
|
356
356
|
if (filteredCount > 0) {
|
|
357
357
|
tokenDetails += ` (пропущено ${filteredCount} истекших)`;
|
|
358
358
|
}
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
checks.push({
|
|
361
361
|
name: '🎫 Токены',
|
|
362
362
|
status: validTokens.length > 0,
|
|
363
363
|
details: tokenDetails
|
|
364
364
|
});
|
|
365
|
-
|
|
365
|
+
|
|
366
366
|
// Показываем только действительные токены
|
|
367
367
|
if (expirySummary.tokens.length > 0) {
|
|
368
368
|
expirySummary.tokens.forEach((token, index) => {
|
|
@@ -370,17 +370,17 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
370
370
|
const cookiesPath = path.join(process.cwd(), SESSION_DIR, 'accounts', token.id, 'cookies.json');
|
|
371
371
|
const hasCookies = fs.existsSync(cookiesPath);
|
|
372
372
|
const cookieStatus = hasCookies ? '✅' : '❌';
|
|
373
|
-
|
|
373
|
+
|
|
374
374
|
checks.push({
|
|
375
375
|
name: ` Токен ${index + 1}`,
|
|
376
376
|
status: token.hasExpiry && hasCookies,
|
|
377
|
-
details: token.hasExpiry
|
|
377
|
+
details: token.hasExpiry
|
|
378
378
|
? `${cookieStatus} ${token.id}\n ⏱️ Осталось: ${token.timeStr}`
|
|
379
379
|
: `${cookieStatus} ${token.id}\n ⚠️ Время истечения: ${token.timeStr}`
|
|
380
380
|
});
|
|
381
381
|
});
|
|
382
382
|
}
|
|
383
|
-
|
|
383
|
+
|
|
384
384
|
// Если все токены истекли, показываем предупреждение
|
|
385
385
|
if (validTokens.length === 0) {
|
|
386
386
|
checks.push({
|
|
@@ -403,7 +403,7 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
403
403
|
status: botStarted,
|
|
404
404
|
details: botStarted ? '✅ Работает' : '❌ Не запущен'
|
|
405
405
|
});
|
|
406
|
-
|
|
406
|
+
|
|
407
407
|
// 3.1. Показываем настройки бота (если загружены)
|
|
408
408
|
if (botStarted) {
|
|
409
409
|
const llmStatus = llmChatEnabled ? '✅ Включен' : '❌ Выключен';
|
|
@@ -468,7 +468,7 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
468
468
|
logInfo('🔍 ПРОВЕРКА ПОД СИСТЕМ ПРИ ЗАПУСКЕ');
|
|
469
469
|
logInfo('='.repeat(60));
|
|
470
470
|
|
|
471
|
-
checks.forEach(check => {
|
|
471
|
+
checks.forEach((check) => {
|
|
472
472
|
const status = check.status ? '✅' : '❌';
|
|
473
473
|
logInfo(`${status} ${check.name}: ${check.details}`);
|
|
474
474
|
});
|
|
@@ -489,36 +489,36 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
489
489
|
const reportLines = [];
|
|
490
490
|
|
|
491
491
|
// Заголовок
|
|
492
|
-
reportLines.push(
|
|
492
|
+
reportLines.push('🚀 <b>Сервис запущен!</b>\n');
|
|
493
493
|
|
|
494
494
|
// Группа 1: Основные компоненты
|
|
495
|
-
reportLines.push(
|
|
496
|
-
const mainComponents = checks.filter(c =>
|
|
497
|
-
c.name.includes('Session') || c.name.includes('Токены') || c.name.includes('Telegram') ||
|
|
495
|
+
reportLines.push('<b>🔑 Основные компоненты:</b>');
|
|
496
|
+
const mainComponents = checks.filter((c) =>
|
|
497
|
+
c.name.includes('Session') || c.name.includes('Токены') || c.name.includes('Telegram') ||
|
|
498
498
|
c.name.includes('Токен ') || c.name.includes('AI') || c.name.includes('Ответ')
|
|
499
499
|
);
|
|
500
|
-
mainComponents.forEach(check => {
|
|
500
|
+
mainComponents.forEach((check) => {
|
|
501
501
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
502
502
|
});
|
|
503
503
|
|
|
504
504
|
reportLines.push('');
|
|
505
505
|
|
|
506
506
|
// Группа 2: Инфраструктура
|
|
507
|
-
reportLines.push(
|
|
508
|
-
const infrastructure = checks.filter(c =>
|
|
507
|
+
reportLines.push('<b>🏗️ Инфраструктура:</b>');
|
|
508
|
+
const infrastructure = checks.filter((c) =>
|
|
509
509
|
c.name.includes('Прокси') || c.name.includes('Uploads') || c.name.includes('Логирование')
|
|
510
510
|
);
|
|
511
|
-
infrastructure.forEach(check => {
|
|
511
|
+
infrastructure.forEach((check) => {
|
|
512
512
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
513
513
|
});
|
|
514
514
|
|
|
515
515
|
reportLines.push('');
|
|
516
516
|
|
|
517
517
|
// Группа 3: Инструменты
|
|
518
|
-
const tools = checks.filter(c => c.name.includes('p7zip'));
|
|
518
|
+
const tools = checks.filter((c) => c.name.includes('p7zip'));
|
|
519
519
|
if (tools.length > 0) {
|
|
520
|
-
reportLines.push(
|
|
521
|
-
tools.forEach(check => {
|
|
520
|
+
reportLines.push('<b>🔧 Инструменты:</b>');
|
|
521
|
+
tools.forEach((check) => {
|
|
522
522
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
523
523
|
});
|
|
524
524
|
reportLines.push('');
|
|
@@ -529,29 +529,29 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
529
529
|
|
|
530
530
|
// Итоговый статус
|
|
531
531
|
if (hasTokens && allOk) {
|
|
532
|
-
reportLines.push(
|
|
532
|
+
reportLines.push('✅ <b>Все системы работают</b>');
|
|
533
533
|
} else if (!hasTokens) {
|
|
534
|
-
reportLines.push(
|
|
535
|
-
reportLines.push(
|
|
534
|
+
reportLines.push('⚠️ <b>Режим ожидания архива</b>');
|
|
535
|
+
reportLines.push('📦 Отправьте архив с сессиями');
|
|
536
536
|
} else {
|
|
537
|
-
reportLines.push(
|
|
537
|
+
reportLines.push('⚠️ <b>Есть проблемы</b>');
|
|
538
538
|
}
|
|
539
539
|
|
|
540
540
|
// Ссылки
|
|
541
541
|
reportLines.push(`\n🌐 API: http://localhost:${process.env.PORT || 3264}`);
|
|
542
542
|
reportLines.push(`📖 Docs: http://localhost:${process.env.PORT || 3264}/api`);
|
|
543
|
-
|
|
543
|
+
|
|
544
544
|
// Репозиторий
|
|
545
|
-
reportLines.push(
|
|
546
|
-
reportLines.push(
|
|
547
|
-
reportLines.push(
|
|
548
|
-
reportLines.push(
|
|
549
|
-
|
|
545
|
+
reportLines.push('\n📚 <b>Репозиторий:</b>');
|
|
546
|
+
reportLines.push('🔗 GitHub: https://github.com/EndyKaufman/FreeQwenApi');
|
|
547
|
+
reportLines.push('⭐ Оригинал: https://github.com/y1n7sint/FreeQwenApi');
|
|
548
|
+
reportLines.push('🐳 Docker: https://hub.docker.com/r/endykaufman/qwen-api-proxy');
|
|
549
|
+
|
|
550
550
|
// Справка
|
|
551
|
-
reportLines.push(
|
|
552
|
-
reportLines.push(
|
|
553
|
-
reportLines.push(
|
|
554
|
-
reportLines.push(
|
|
551
|
+
reportLines.push('\n💡 <b>Справка:</b>');
|
|
552
|
+
reportLines.push('📝 Используйте /help для списка команд');
|
|
553
|
+
reportLines.push('🔍 Используйте /status для проверки состояния');
|
|
554
|
+
reportLines.push('🤖 Используйте /chat для включения LLM режима');
|
|
555
555
|
|
|
556
556
|
const report = reportLines.join('\n');
|
|
557
557
|
|
|
@@ -564,7 +564,7 @@ export async function checkAllSubsystems(botStarted, autoSend = true) {
|
|
|
564
564
|
logError('❌ Не удалось отправить отчет в Telegram', e);
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
|
-
|
|
567
|
+
|
|
568
568
|
// Возвращаем массив проверок для повторного использования
|
|
569
569
|
return checks;
|
|
570
570
|
}
|
|
@@ -581,7 +581,7 @@ async function fetchWithProxy(url, options = {}, skipLog = false) {
|
|
|
581
581
|
if (proxyAgent) {
|
|
582
582
|
fetchOptions.dispatcher = proxyAgent;
|
|
583
583
|
if (proxyConfigured && !skipLog) {
|
|
584
|
-
logInfo(
|
|
584
|
+
logInfo('🌐 Запрос через прокси...');
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
587
|
|
|
@@ -606,7 +606,7 @@ export async function startTelegramBot() {
|
|
|
606
606
|
|
|
607
607
|
try {
|
|
608
608
|
logInfo('🤖 Запуск Telegram бота...');
|
|
609
|
-
|
|
609
|
+
|
|
610
610
|
// Загружаем сохраненные настройки
|
|
611
611
|
loadPersistedSettings();
|
|
612
612
|
|
|
@@ -685,7 +685,7 @@ async function startPolling() {
|
|
|
685
685
|
if (!response.ok) {
|
|
686
686
|
const errorText = await response.text().catch(() => '');
|
|
687
687
|
logError(`Ошибка получения обновлений Telegram: HTTP ${response.status}${errorText ? ` | Ответ: ${errorText.substring(0, 500)}` : ''}`);
|
|
688
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
688
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
689
689
|
continue;
|
|
690
690
|
}
|
|
691
691
|
|
|
@@ -707,7 +707,7 @@ async function startPolling() {
|
|
|
707
707
|
}
|
|
708
708
|
} catch (error) {
|
|
709
709
|
logError('Ошибка в polling Telegram', error);
|
|
710
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
710
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
713
|
}
|
|
@@ -736,10 +736,10 @@ async function processUpdate(update) {
|
|
|
736
736
|
await handleLLMChat(chatId, message.text);
|
|
737
737
|
} else {
|
|
738
738
|
await sendMessage(chatId,
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
739
|
+
'❌ <b>LLM чат временно недоступен</b>\n\n' +
|
|
740
|
+
'🔒 Нет аккаунтов для обработки запросов\n' +
|
|
741
|
+
'📦 Отправьте архив с сессиями\n' +
|
|
742
|
+
'💡 Или используйте /chat чтобы выключить LLM режим'
|
|
743
743
|
);
|
|
744
744
|
}
|
|
745
745
|
return;
|
|
@@ -792,45 +792,45 @@ async function handleImageGeneration(chatId, prompt, imagePath = null) {
|
|
|
792
792
|
if (imagePath) {
|
|
793
793
|
logInfo(`📸 Режим image-to-image с файлом: ${imagePath}`);
|
|
794
794
|
}
|
|
795
|
-
|
|
795
|
+
|
|
796
796
|
// Отправляем сообщение о начале генерации
|
|
797
|
-
await sendMessage(chatId,
|
|
798
|
-
|
|
797
|
+
await sendMessage(chatId,
|
|
798
|
+
'🎨 <b>Генерация изображения...</b>\n\n' +
|
|
799
799
|
`📝 Запрос: ${prompt}\n` +
|
|
800
|
-
(imagePath ?
|
|
801
|
-
|
|
800
|
+
(imagePath ? '📸 Режим: Image-to-Image\n' : '') +
|
|
801
|
+
'⏳ Пожалуйста, подождите...'
|
|
802
802
|
);
|
|
803
803
|
|
|
804
804
|
// Импортируем функцию генерации
|
|
805
805
|
const { generateImage } = await import('../api/imageGeneration.js');
|
|
806
806
|
const { getActiveModel } = await import('./botSettings.js');
|
|
807
|
-
|
|
807
|
+
|
|
808
808
|
// Используем модель для генерации изображений
|
|
809
809
|
const model = 'qwen-image-plus';
|
|
810
|
-
|
|
810
|
+
|
|
811
811
|
const startTime = Date.now();
|
|
812
812
|
const options = {
|
|
813
813
|
size: '1024*1024'
|
|
814
814
|
};
|
|
815
|
-
|
|
815
|
+
|
|
816
816
|
// Если есть изображение, передаем путь к файлу
|
|
817
817
|
if (imagePath) {
|
|
818
818
|
options.imagePath = imagePath;
|
|
819
819
|
}
|
|
820
|
-
|
|
820
|
+
|
|
821
821
|
const result = await generateImage(prompt, model, options);
|
|
822
822
|
const generationTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
823
823
|
|
|
824
824
|
if (result.success && result.imageUrl) {
|
|
825
825
|
logInfo(`✅ Изображение сгенерировано за ${generationTime}с: ${result.imageUrl}`);
|
|
826
|
-
|
|
826
|
+
|
|
827
827
|
// Отправляем изображение как фото
|
|
828
828
|
try {
|
|
829
829
|
await sendPhoto(chatId, result.imageUrl, prompt);
|
|
830
|
-
|
|
830
|
+
|
|
831
831
|
// Отправляем дополнительную информацию
|
|
832
832
|
await sendMessage(chatId,
|
|
833
|
-
|
|
833
|
+
'✅ <b>Изображение сгенерировано!</b>\n\n' +
|
|
834
834
|
`🎨 Модель: ${result.model || model}\n` +
|
|
835
835
|
`⏱️ Время: ${generationTime}с\n` +
|
|
836
836
|
`📝 Prompt: ${prompt}`
|
|
@@ -839,7 +839,7 @@ async function handleImageGeneration(chatId, prompt, imagePath = null) {
|
|
|
839
839
|
// Если не удалось отправить как фото, отправляем как ссылку
|
|
840
840
|
logWarn('Не удалось отправить изображение как фото, отправляю ссылку');
|
|
841
841
|
await sendMessage(chatId,
|
|
842
|
-
|
|
842
|
+
'✅ <b>Изображение сгенерировано!</b>\n\n' +
|
|
843
843
|
`🖼️ <a href="${result.imageUrl}">Скачать изображение</a>\n\n` +
|
|
844
844
|
`🎨 Модель: ${result.model || model}\n` +
|
|
845
845
|
`⏱️ Время: ${generationTime}с\n` +
|
|
@@ -848,23 +848,23 @@ async function handleImageGeneration(chatId, prompt, imagePath = null) {
|
|
|
848
848
|
}
|
|
849
849
|
} else {
|
|
850
850
|
logError(`❌ Ошибка генерации изображения: ${result.error}`);
|
|
851
|
-
|
|
851
|
+
|
|
852
852
|
// Проверяем, это rate limit?
|
|
853
853
|
if (result.rateLimit) {
|
|
854
854
|
const hours = result.rateLimitHours || 24;
|
|
855
855
|
await sendMessage(chatId,
|
|
856
|
-
|
|
857
|
-
|
|
856
|
+
'⏳ <b>Лимит генерации изображений достигнут</b>\n\n' +
|
|
857
|
+
'⚠️ API Qwen ограничивает количество генераций в день\n' +
|
|
858
858
|
`⏰ Попробуйте через ${hours}ч\n\n` +
|
|
859
|
-
|
|
860
|
-
|
|
859
|
+
'💡 Совет: используйте другой аккаунт с токеном\n' +
|
|
860
|
+
'📝 Или подождите сброса лимита'
|
|
861
861
|
);
|
|
862
862
|
return;
|
|
863
863
|
}
|
|
864
|
-
|
|
864
|
+
|
|
865
865
|
// Формируем детальное сообщение об ошибке
|
|
866
866
|
let errorMessage = result.error || 'Неизвестная ошибка';
|
|
867
|
-
|
|
867
|
+
|
|
868
868
|
// Если есть дополнительные детали в rawResponse
|
|
869
869
|
if (result.rawResponse) {
|
|
870
870
|
// Проверяем errorBody (JSON строка)
|
|
@@ -919,19 +919,19 @@ async function handleImageGeneration(chatId, prompt, imagePath = null) {
|
|
|
919
919
|
}
|
|
920
920
|
}
|
|
921
921
|
}
|
|
922
|
-
|
|
922
|
+
|
|
923
923
|
await sendMessage(chatId,
|
|
924
|
-
|
|
924
|
+
'❌ <b>Ошибка генерации изображения</b>\n\n' +
|
|
925
925
|
`⚠️ ${escapeHtml(errorMessage)}\n\n` +
|
|
926
|
-
|
|
926
|
+
'💡 Попробуйте изменить запрос или повторите позже'
|
|
927
927
|
);
|
|
928
928
|
}
|
|
929
929
|
} catch (error) {
|
|
930
930
|
logError('❌ Ошибка в handleImageGeneration', error);
|
|
931
931
|
await sendMessage(chatId,
|
|
932
|
-
|
|
932
|
+
'❌ <b>Произошла ошибка</b>\n\n' +
|
|
933
933
|
`⚠️ ${error.message}\n\n` +
|
|
934
|
-
|
|
934
|
+
'💡 Попробуйте позже'
|
|
935
935
|
);
|
|
936
936
|
}
|
|
937
937
|
}
|
|
@@ -942,7 +942,7 @@ async function handleImageGeneration(chatId, prompt, imagePath = null) {
|
|
|
942
942
|
async function sendPhoto(chatId, photoUrl, caption = '') {
|
|
943
943
|
try {
|
|
944
944
|
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendPhoto`;
|
|
945
|
-
|
|
945
|
+
|
|
946
946
|
const body = {
|
|
947
947
|
chat_id: chatId,
|
|
948
948
|
photo: photoUrl,
|
|
@@ -993,71 +993,71 @@ async function handleCommand(chatId, text) {
|
|
|
993
993
|
}
|
|
994
994
|
|
|
995
995
|
switch (command) {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
996
|
+
case '/start':
|
|
997
|
+
case '/help':
|
|
998
|
+
await sendHelpMessage(chatId);
|
|
999
|
+
break;
|
|
1000
1000
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1001
|
+
case '/status':
|
|
1002
|
+
await sendStatusMessage(chatId);
|
|
1003
|
+
break;
|
|
1004
1004
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1005
|
+
case '/restart':
|
|
1006
|
+
await handleRestart(chatId);
|
|
1007
|
+
break;
|
|
1008
1008
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1009
|
+
case '/chat':
|
|
1010
|
+
await showLLMChatStatus(chatId);
|
|
1011
|
+
break;
|
|
1012
1012
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1013
|
+
case '/togglechat':
|
|
1014
|
+
await toggleLLMChat(chatId);
|
|
1015
|
+
break;
|
|
1016
1016
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1017
|
+
case '/model':
|
|
1018
|
+
await showModelInfo(chatId);
|
|
1019
|
+
break;
|
|
1020
1020
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1021
|
+
case '/setmodel':
|
|
1022
|
+
await showModelInfo(chatId);
|
|
1023
|
+
break;
|
|
1024
1024
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1025
|
+
case '/clear':
|
|
1026
|
+
await clearChatContext(chatId);
|
|
1027
|
+
break;
|
|
1028
1028
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1029
|
+
case '/setup':
|
|
1030
|
+
await sendSetupMessage(chatId);
|
|
1031
|
+
break;
|
|
1032
1032
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1033
|
+
case '/connect':
|
|
1034
|
+
await sendConnectMessage(chatId);
|
|
1035
|
+
break;
|
|
1036
1036
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1037
|
+
case '/about':
|
|
1038
|
+
await sendAboutMessage(chatId);
|
|
1039
|
+
break;
|
|
1040
1040
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1041
|
+
case '/archive':
|
|
1042
|
+
await sendArchiveInstructions(chatId);
|
|
1043
|
+
break;
|
|
1044
1044
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1045
|
+
case '/extend':
|
|
1046
|
+
// 🔧 ВРЕМЕННО ОТКЛЮЧЕНО
|
|
1047
|
+
await sendMessage(chatId,
|
|
1048
|
+
'🔧 <b>Команда /extend временно отключена</b>\n\n' +
|
|
1049
|
+
'Функция продления сессий находится на техническом обслуживании.\n\n' +
|
|
1050
|
+
'📦 <b>Что делать:</b>\n' +
|
|
1051
|
+
'1. Создайте новую сессию: <code>npm run create-session-archive</code>\n' +
|
|
1052
|
+
'2. Отправьте архив через бота\n\n' +
|
|
1053
|
+
'⏳ Функция будет доступна в ближайшее время.'
|
|
1054
|
+
);
|
|
1055
|
+
break;
|
|
1056
1056
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1057
|
+
case '/image':
|
|
1058
|
+
case '/imagine':
|
|
1059
|
+
await sendMessage(chatId,
|
|
1060
|
+
'🎨 <b>Генерация изображений</b>\n\n' +
|
|
1061
1061
|
'💬 <b>Текстовый режим:</b>\n' +
|
|
1062
1062
|
'/image <описание> - генерация по описанию\n\n' +
|
|
1063
1063
|
'📸 <b>Режим Image-to-Image:</b>\n' +
|
|
@@ -1066,21 +1066,21 @@ async function handleCommand(chatId, text) {
|
|
|
1066
1066
|
'📝 Примеры:\n' +
|
|
1067
1067
|
'• /image A beautiful sunset\n' +
|
|
1068
1068
|
'• Отправьте фото с текстом "Улучши это"'
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
} else {
|
|
1079
|
-
await sendMessage(chatId, '🎨 Пожалуйста, укажите описание изображения\n\nПример: /image A beautiful sunset over the ocean');
|
|
1080
|
-
}
|
|
1069
|
+
);
|
|
1070
|
+
break;
|
|
1071
|
+
|
|
1072
|
+
default:
|
|
1073
|
+
// Проверяем, начинается ли сообщение с /image или /imagine с аргументами
|
|
1074
|
+
if (text.startsWith('/image ') || text.startsWith('/imagine ')) {
|
|
1075
|
+
const prompt = text.substring(text.indexOf(' ') + 1).trim();
|
|
1076
|
+
if (prompt) {
|
|
1077
|
+
await handleImageGeneration(chatId, prompt);
|
|
1081
1078
|
} else {
|
|
1082
|
-
await sendMessage(chatId, '
|
|
1079
|
+
await sendMessage(chatId, '🎨 Пожалуйста, укажите описание изображения\n\nПример: /image A beautiful sunset over the ocean');
|
|
1083
1080
|
}
|
|
1081
|
+
} else {
|
|
1082
|
+
await sendMessage(chatId, '❓ Неизвестная команда. Используйте /help для списка команд');
|
|
1083
|
+
}
|
|
1084
1084
|
}
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
@@ -1090,48 +1090,48 @@ async function handleCommand(chatId, text) {
|
|
|
1090
1090
|
async function handlePhoto(chatId, photos, caption = '') {
|
|
1091
1091
|
try {
|
|
1092
1092
|
logInfo(`📸 Получено фото с caption: "${caption.substring(0, 50)}${caption.length > 50 ? '...' : ''}"`);
|
|
1093
|
-
|
|
1093
|
+
|
|
1094
1094
|
// Проверяем, есть ли команда в caption
|
|
1095
1095
|
let prompt = caption || 'Улучши это изображение';
|
|
1096
1096
|
let hasCommand = false;
|
|
1097
|
-
|
|
1097
|
+
|
|
1098
1098
|
// Если caption начинается с /image или /imagine
|
|
1099
1099
|
if (caption.startsWith('/image ') || caption.startsWith('/imagine ')) {
|
|
1100
1100
|
hasCommand = true;
|
|
1101
1101
|
prompt = caption.substring(caption.indexOf(' ') + 1).trim();
|
|
1102
1102
|
}
|
|
1103
|
-
|
|
1103
|
+
|
|
1104
1104
|
if (!hasCommand && !caption) {
|
|
1105
1105
|
// Если просто фото без caption - не обрабатываем как image-to-image
|
|
1106
1106
|
logInfo('📸 Фото без caption - пропускаем обработку');
|
|
1107
1107
|
return;
|
|
1108
1108
|
}
|
|
1109
|
-
|
|
1109
|
+
|
|
1110
1110
|
// Telegram отправляет несколько размеров фото, берем самый большой (последний в массиве)
|
|
1111
1111
|
const photo = photos[photos.length - 1];
|
|
1112
1112
|
const fileId = photo.file_id;
|
|
1113
1113
|
const fileSize = photo.file_size;
|
|
1114
|
-
|
|
1114
|
+
|
|
1115
1115
|
logInfo(`📸 Загрузка фото из Telegram (file_id: ${fileId}, size: ${fileSize} bytes)`);
|
|
1116
|
-
|
|
1117
|
-
await sendMessage(chatId,
|
|
1118
|
-
|
|
1116
|
+
|
|
1117
|
+
await sendMessage(chatId,
|
|
1118
|
+
'🎨 <b>Обработка изображения...</b>\n\n' +
|
|
1119
1119
|
`📝 Запрос: ${prompt}\n` +
|
|
1120
|
-
|
|
1120
|
+
'⏳ Пожалуйста, подождите...'
|
|
1121
1121
|
);
|
|
1122
|
-
|
|
1122
|
+
|
|
1123
1123
|
// Скачиваем фото из Telegram и сохраняем во временный файл
|
|
1124
1124
|
const tempFilePath = await downloadTelegramFileToTemp(fileId);
|
|
1125
|
-
|
|
1125
|
+
|
|
1126
1126
|
if (!tempFilePath) {
|
|
1127
1127
|
throw new Error('Не удалось скачать фото из Telegram');
|
|
1128
1128
|
}
|
|
1129
|
-
|
|
1129
|
+
|
|
1130
1130
|
logInfo(`✅ Фото скачано: ${tempFilePath}`);
|
|
1131
|
-
|
|
1131
|
+
|
|
1132
1132
|
// Генерируем изображение с использованием фото
|
|
1133
1133
|
await handleImageGeneration(chatId, prompt, tempFilePath);
|
|
1134
|
-
|
|
1134
|
+
|
|
1135
1135
|
// Удаляем временный файл
|
|
1136
1136
|
try {
|
|
1137
1137
|
fs.unlinkSync(tempFilePath);
|
|
@@ -1139,13 +1139,13 @@ async function handlePhoto(chatId, photos, caption = '') {
|
|
|
1139
1139
|
} catch (e) {
|
|
1140
1140
|
logWarn('Не удалось удалить временный файл', e);
|
|
1141
1141
|
}
|
|
1142
|
-
|
|
1142
|
+
|
|
1143
1143
|
} catch (error) {
|
|
1144
1144
|
logError('❌ Ошибка в handlePhoto', error);
|
|
1145
1145
|
await sendMessage(chatId,
|
|
1146
|
-
|
|
1146
|
+
'❌ <b>Ошибка обработки фото</b>\n\n' +
|
|
1147
1147
|
`⚠️ ${error.message}\n\n` +
|
|
1148
|
-
|
|
1148
|
+
'💡 Попробуйте позже'
|
|
1149
1149
|
);
|
|
1150
1150
|
}
|
|
1151
1151
|
}
|
|
@@ -1160,45 +1160,45 @@ async function downloadTelegramFileToTemp(fileId) {
|
|
|
1160
1160
|
// Получаем информацию о файле
|
|
1161
1161
|
const fileUrl = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getFile?file_id=${fileId}`;
|
|
1162
1162
|
const fileResponse = await fetchWithProxy(fileUrl, undefined, true);
|
|
1163
|
-
|
|
1163
|
+
|
|
1164
1164
|
if (!fileResponse.ok) {
|
|
1165
1165
|
throw new Error(`Не удалось получить информацию о файле: HTTP ${fileResponse.status}`);
|
|
1166
1166
|
}
|
|
1167
|
-
|
|
1167
|
+
|
|
1168
1168
|
const fileData = await fileResponse.json();
|
|
1169
|
-
|
|
1169
|
+
|
|
1170
1170
|
if (!fileData.ok) {
|
|
1171
1171
|
throw new Error(`Telegram API error: ${fileData.description || 'Unknown error'}`);
|
|
1172
1172
|
}
|
|
1173
|
-
|
|
1173
|
+
|
|
1174
1174
|
const filePath = fileData.result.file_path;
|
|
1175
1175
|
const downloadUrl = `https://api.telegram.org/file/bot${TELEGRAM_BOT_TOKEN}/${filePath}`;
|
|
1176
|
-
|
|
1176
|
+
|
|
1177
1177
|
logInfo(`📥 URL для скачивания файла: ${downloadUrl}`);
|
|
1178
|
-
|
|
1178
|
+
|
|
1179
1179
|
// Скачиваем файл
|
|
1180
1180
|
const downloadResponse = await fetchWithProxy(downloadUrl, undefined, true);
|
|
1181
|
-
|
|
1181
|
+
|
|
1182
1182
|
if (!downloadResponse.ok) {
|
|
1183
1183
|
throw new Error(`Не удалось скачать файл: HTTP ${downloadResponse.status}`);
|
|
1184
1184
|
}
|
|
1185
|
-
|
|
1185
|
+
|
|
1186
1186
|
// Сохраняем во временный файл
|
|
1187
1187
|
const tempDir = path.join(process.cwd(), 'temp');
|
|
1188
1188
|
if (!fs.existsSync(tempDir)) {
|
|
1189
1189
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
1190
1190
|
}
|
|
1191
|
-
|
|
1191
|
+
|
|
1192
1192
|
const tempFileName = `telegram_${Date.now()}_${fileId}.jpg`;
|
|
1193
1193
|
const tempFilePath = path.join(tempDir, tempFileName);
|
|
1194
|
-
|
|
1194
|
+
|
|
1195
1195
|
const buffer = Buffer.from(await downloadResponse.arrayBuffer());
|
|
1196
1196
|
fs.writeFileSync(tempFilePath, buffer);
|
|
1197
|
-
|
|
1197
|
+
|
|
1198
1198
|
logInfo(`✅ Файл сохранен: ${tempFilePath} (${buffer.length} bytes)`);
|
|
1199
|
-
|
|
1199
|
+
|
|
1200
1200
|
return tempFilePath;
|
|
1201
|
-
|
|
1201
|
+
|
|
1202
1202
|
} catch (error) {
|
|
1203
1203
|
logError('❌ Ошибка скачивания файла из Telegram', error);
|
|
1204
1204
|
throw error;
|
|
@@ -1222,7 +1222,7 @@ async function handleDocument(chatId, document) {
|
|
|
1222
1222
|
if (!allowedExtensions.includes(ext)) {
|
|
1223
1223
|
await sendMessage(chatId,
|
|
1224
1224
|
`❌ Неподдерживаемый формат файла: ${ext}\n` +
|
|
1225
|
-
|
|
1225
|
+
'📎 Поддерживаются только: .zip и .7z'
|
|
1226
1226
|
);
|
|
1227
1227
|
return;
|
|
1228
1228
|
}
|
|
@@ -1232,7 +1232,7 @@ async function handleDocument(chatId, document) {
|
|
|
1232
1232
|
if (fileSize > maxSize) {
|
|
1233
1233
|
await sendMessage(chatId,
|
|
1234
1234
|
`❌ Файл слишком большой: ${(fileSize / 1024 / 1024).toFixed(2)}MB\n` +
|
|
1235
|
-
|
|
1235
|
+
'📏 Максимальный размер: 50MB'
|
|
1236
1236
|
);
|
|
1237
1237
|
return;
|
|
1238
1238
|
}
|
|
@@ -1246,7 +1246,7 @@ async function handleDocument(chatId, document) {
|
|
|
1246
1246
|
logInfo(`⚠️ Архив ${fileName} уже ожидает распаковки, перезапускаем`);
|
|
1247
1247
|
await sendMessage(chatId,
|
|
1248
1248
|
`✅ Архив ${fileName} уже загружен\n` +
|
|
1249
|
-
|
|
1249
|
+
'🔄 Перезапуск для распаковки...'
|
|
1250
1250
|
);
|
|
1251
1251
|
// Запускаем перезапуск
|
|
1252
1252
|
await gracefulRestart(chatId);
|
|
@@ -1260,7 +1260,7 @@ async function handleDocument(chatId, document) {
|
|
|
1260
1260
|
// Проверяем существует ли уже файл в temp (по оригинальному имени)
|
|
1261
1261
|
const tempDir = path.join(process.cwd(), 'temp');
|
|
1262
1262
|
const existingFiles = fs.existsSync(tempDir) ? fs.readdirSync(tempDir) : [];
|
|
1263
|
-
const existingFile = existingFiles.find(f => f.endsWith(`_${fileName}`) || f === fileName);
|
|
1263
|
+
const existingFile = existingFiles.find((f) => f.endsWith(`_${fileName}`) || f === fileName);
|
|
1264
1264
|
|
|
1265
1265
|
if (existingFile) {
|
|
1266
1266
|
const tempFilePath = path.join(tempDir, existingFile);
|
|
@@ -1274,14 +1274,14 @@ async function handleDocument(chatId, document) {
|
|
|
1274
1274
|
ext: ext,
|
|
1275
1275
|
uploadedAt: new Date().toISOString()
|
|
1276
1276
|
}));
|
|
1277
|
-
logInfo(
|
|
1277
|
+
logInfo('📝 Создан флаг для существующего архива');
|
|
1278
1278
|
|
|
1279
1279
|
// Запускаем перезапуск
|
|
1280
1280
|
await gracefulRestart(chatId);
|
|
1281
1281
|
} else {
|
|
1282
1282
|
await sendMessage(chatId,
|
|
1283
|
-
|
|
1284
|
-
|
|
1283
|
+
'✅ Архив уже загружен и ожидает распаковки\n' +
|
|
1284
|
+
'🔄 Сервис будет перезапущен...'
|
|
1285
1285
|
);
|
|
1286
1286
|
// Запускаем перезапуск
|
|
1287
1287
|
await gracefulRestart(chatId);
|
|
@@ -1313,7 +1313,7 @@ async function handleDocument(chatId, document) {
|
|
|
1313
1313
|
logInfo(`📊 Ожидаемый размер (из Telegram): ${fileData.result.file_size || 'unknown'} bytes`);
|
|
1314
1314
|
|
|
1315
1315
|
// Скачиваем файл
|
|
1316
|
-
logInfo(
|
|
1316
|
+
logInfo('📥 Начинаем загрузку файла...');
|
|
1317
1317
|
const downloadResponse = await fetchWithProxy(downloadUrl);
|
|
1318
1318
|
logInfo(`📥 Статус загрузки: ${downloadResponse.status}`);
|
|
1319
1319
|
logInfo(`📥 Content-Type: ${downloadResponse.headers.get('content-type')}`);
|
|
@@ -1326,7 +1326,7 @@ async function handleDocument(chatId, document) {
|
|
|
1326
1326
|
logInfo(`📥 Загружено ${fileSize} bytes из Telegram`);
|
|
1327
1327
|
|
|
1328
1328
|
if (fileSize === 0) {
|
|
1329
|
-
logError(
|
|
1329
|
+
logError('❌ Загружен пустой файл!');
|
|
1330
1330
|
logError(`📡 Статус ответа: ${downloadResponse.status}`);
|
|
1331
1331
|
logError(`📊 Content-Length header: ${downloadResponse.headers.get('content-length')}`);
|
|
1332
1332
|
logError(`🔗 URL: ${downloadUrl}`);
|
|
@@ -1348,10 +1348,10 @@ async function handleDocument(chatId, document) {
|
|
|
1348
1348
|
const tempFilePath = path.join(tempDir, uniqueFileName);
|
|
1349
1349
|
|
|
1350
1350
|
// Удаляем старые файлы с таким же именем (если есть)
|
|
1351
|
-
const oldFiles = existingFiles.filter(f => f.endsWith(`_${fileName}`) || f === fileName);
|
|
1351
|
+
const oldFiles = existingFiles.filter((f) => f.endsWith(`_${fileName}`) || f === fileName);
|
|
1352
1352
|
if (oldFiles.length > 0) {
|
|
1353
1353
|
logInfo(`🗑️ Найдено ${oldFiles.length} старых файлов с именем ${fileName}, удаляем`);
|
|
1354
|
-
oldFiles.forEach(oldFile => {
|
|
1354
|
+
oldFiles.forEach((oldFile) => {
|
|
1355
1355
|
try {
|
|
1356
1356
|
fs.unlinkSync(path.join(tempDir, oldFile));
|
|
1357
1357
|
logInfo(`🗑️ Удален старый файл: ${oldFile}`);
|
|
@@ -1374,7 +1374,7 @@ async function handleDocument(chatId, document) {
|
|
|
1374
1374
|
}
|
|
1375
1375
|
|
|
1376
1376
|
logInfo(`✅ Файл сохранен: ${tempFilePath}`);
|
|
1377
|
-
await sendMessage(chatId,
|
|
1377
|
+
await sendMessage(chatId, '✅ Файл загружен. Распаковка...');
|
|
1378
1378
|
|
|
1379
1379
|
// Сохраняем информацию об архиве для обработки при перезапуске
|
|
1380
1380
|
fs.writeFileSync(archiveInfoPath, JSON.stringify({
|
|
@@ -1386,8 +1386,8 @@ async function handleDocument(chatId, document) {
|
|
|
1386
1386
|
|
|
1387
1387
|
logInfo(`📝 Записан флаг ожидающего архива: ${archiveInfoPath}`);
|
|
1388
1388
|
await sendMessage(chatId,
|
|
1389
|
-
|
|
1390
|
-
|
|
1389
|
+
'✅ Архив сохранен\n' +
|
|
1390
|
+
'🔄 При перезапуске будет автоматически распакован\n' +
|
|
1391
1391
|
`📂 Файл: ${tempFilePath}`
|
|
1392
1392
|
);
|
|
1393
1393
|
|
|
@@ -1428,9 +1428,9 @@ async function createSessionBackup(sessionPath, chatId) {
|
|
|
1428
1428
|
logWarn('⛔ session_backup отменен - СЕРВЕР НЕ БУДЕТ ПЕРЕЗАПУЩЕН');
|
|
1429
1429
|
try {
|
|
1430
1430
|
await sendMessage(chatId,
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1431
|
+
'❌ Ошибка session_backup: нет прав для создания папки\n' +
|
|
1432
|
+
'⛔ Распаковка продолжена, но сервер НЕ будет перезапущен\n' +
|
|
1433
|
+
'💡 Перезапустите сервер вручную после проверки файлов'
|
|
1434
1434
|
);
|
|
1435
1435
|
} catch (sendError) {
|
|
1436
1436
|
// Игнорируем
|
|
@@ -1448,7 +1448,7 @@ async function createSessionBackup(sessionPath, chatId) {
|
|
|
1448
1448
|
|
|
1449
1449
|
try {
|
|
1450
1450
|
await sendMessage(chatId,
|
|
1451
|
-
|
|
1451
|
+
'💾 Создание session_backup текущей session...\n' +
|
|
1452
1452
|
`📁 Найдено элементов: ${items.length}`
|
|
1453
1453
|
);
|
|
1454
1454
|
} catch (sendError) {
|
|
@@ -1489,8 +1489,8 @@ async function createSessionBackup(sessionPath, chatId) {
|
|
|
1489
1489
|
try {
|
|
1490
1490
|
await sendMessage(chatId,
|
|
1491
1491
|
`❌ Ошибка session_backup: ${error.message}\n` +
|
|
1492
|
-
|
|
1493
|
-
|
|
1492
|
+
'⛔ Распаковка продолжена, но сервер НЕ будет перезапущен\n' +
|
|
1493
|
+
'💡 Перезапустите сервер вручную после проверки файлов'
|
|
1494
1494
|
);
|
|
1495
1495
|
} catch (sendError) {
|
|
1496
1496
|
// Игнорируем
|
|
@@ -1513,10 +1513,10 @@ async function extractArchive(filePath, chatId, ext) {
|
|
|
1513
1513
|
if (!backupSuccess) {
|
|
1514
1514
|
logWarn('⛔ Распаковка отменена из-за ошибки backup');
|
|
1515
1515
|
await sendMessage(chatId,
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1516
|
+
'⚠️ <b>Распаковка отменена</b>\n\n' +
|
|
1517
|
+
'❌ Не удалось создать backup текущей session\n' +
|
|
1518
|
+
'🔒 Файлы не были изменены для безопасности\n' +
|
|
1519
|
+
'💡 Проверьте права доступа и попробуйте снова'
|
|
1520
1520
|
);
|
|
1521
1521
|
return; // Выходим без перезапуска
|
|
1522
1522
|
}
|
|
@@ -1530,9 +1530,9 @@ async function extractArchive(filePath, chatId, ext) {
|
|
|
1530
1530
|
}
|
|
1531
1531
|
|
|
1532
1532
|
let statusMessage =
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1533
|
+
'✅ <b>Архив успешно распакован!</b>\n\n' +
|
|
1534
|
+
'📂 Папка session обновлена\n' +
|
|
1535
|
+
'💾 Старая версия сохранена в session_backup\n';
|
|
1536
1536
|
|
|
1537
1537
|
if (result && result.successCount !== 'все') {
|
|
1538
1538
|
statusMessage += `📊 Распаковано: ${result.successCount} файлов\n`;
|
|
@@ -1541,12 +1541,12 @@ async function extractArchive(filePath, chatId, ext) {
|
|
|
1541
1541
|
}
|
|
1542
1542
|
}
|
|
1543
1543
|
|
|
1544
|
-
statusMessage +=
|
|
1544
|
+
statusMessage += '🔄 Сервис будет перезапущен...';
|
|
1545
1545
|
|
|
1546
1546
|
await sendMessage(chatId, statusMessage);
|
|
1547
1547
|
|
|
1548
1548
|
// Ждем 2 секунды чтобы сообщение дошло
|
|
1549
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1549
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1550
1550
|
|
|
1551
1551
|
// Запускаем перезапуск только если backup был успешен
|
|
1552
1552
|
logInfo('✅ Backup успешен, запускаем перезапуск сервера');
|
|
@@ -1567,28 +1567,56 @@ async function extractZip(zipPath, sessionPath, chatId) {
|
|
|
1567
1567
|
const zip = new AdmZip(zipPath);
|
|
1568
1568
|
const zipEntries = zip.getEntries();
|
|
1569
1569
|
|
|
1570
|
+
// Нормализуем пути: заменяем обратные слеши на прямые (Windows -> Unix)
|
|
1571
|
+
const normalizedEntries = zipEntries.map((entry) => ({
|
|
1572
|
+
...entry,
|
|
1573
|
+
normalizedPath: entry.entryName.replace(/\\/g, '/')
|
|
1574
|
+
}));
|
|
1575
|
+
|
|
1570
1576
|
// Для отладки: показываем первые несколько записей в архиве
|
|
1571
|
-
const firstEntries =
|
|
1572
|
-
logInfo(
|
|
1577
|
+
const firstEntries = normalizedEntries.slice(0, 20).map((e) => e.normalizedPath);
|
|
1578
|
+
logInfo('📂 Первые 20 записей в ZIP архиве:');
|
|
1573
1579
|
logInfo(firstEntries.join('\n'));
|
|
1574
1580
|
|
|
1575
|
-
// Проверяем что есть папка session
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
)
|
|
1581
|
+
// Проверяем что есть папка session (с нормализованными путями)
|
|
1582
|
+
// Поддерживаем разные варианты:
|
|
1583
|
+
// 1. "session/" - папка session в корне
|
|
1584
|
+
// 2. "session" - ровно папка session (без слеша)
|
|
1585
|
+
// 3. "session/accounts/..." - содержимое session
|
|
1586
|
+
const hasSessionFolder = normalizedEntries.some((entry) => {
|
|
1587
|
+
const p = entry.normalizedPath;
|
|
1588
|
+
// Точное совпадение "session" или "session/"
|
|
1589
|
+
if (p === 'session' || p === 'session/') {return true;}
|
|
1590
|
+
// Начинается с "session/"
|
|
1591
|
+
if (p.startsWith('session/')) {return true;}
|
|
1592
|
+
// Для ZIP созданных на Windows может быть "session\" который уже нормализован
|
|
1593
|
+
return false;
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
if (hasSessionFolder) {
|
|
1597
|
+
logInfo('✅ Найдена папка session в корне архива');
|
|
1598
|
+
// Логируем пример найденного пути для отладки
|
|
1599
|
+
const sampleEntry = normalizedEntries.find((e) => {
|
|
1600
|
+
const p = e.normalizedPath;
|
|
1601
|
+
return p === 'session' || p === 'session/' || p.startsWith('session/');
|
|
1602
|
+
});
|
|
1603
|
+
if (sampleEntry) {
|
|
1604
|
+
logInfo(`📂 Пример: ${sampleEntry.normalizedPath}`);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1579
1607
|
|
|
1580
1608
|
if (!hasSessionFolder) {
|
|
1581
1609
|
// Пытаемся найти session в любом месте архива
|
|
1582
|
-
const sessionEntries =
|
|
1583
|
-
e.
|
|
1610
|
+
const sessionEntries = normalizedEntries.filter((e) =>
|
|
1611
|
+
e.normalizedPath.includes('session') || e.normalizedPath.includes('Session')
|
|
1584
1612
|
);
|
|
1585
1613
|
|
|
1586
1614
|
if (sessionEntries.length > 0) {
|
|
1587
|
-
logInfo(
|
|
1588
|
-
logInfo(sessionEntries.slice(0, 10).map(e => e.
|
|
1615
|
+
logInfo('🔍 Найдены записи содержащие \'session\':');
|
|
1616
|
+
logInfo(sessionEntries.slice(0, 10).map((e) => e.normalizedPath).join('\n'));
|
|
1589
1617
|
reject(new Error(
|
|
1590
|
-
`Архив содержит '${sessionEntries[0].
|
|
1591
|
-
|
|
1618
|
+
`Архив содержит '${sessionEntries[0].normalizedPath}', но не содержит 'session/' в корне. ` +
|
|
1619
|
+
'Переместите папку session/ в корень архива'
|
|
1592
1620
|
));
|
|
1593
1621
|
} else {
|
|
1594
1622
|
reject(new Error('Архив не содержит папку "session". Проверите что архив содержит папку session/ в корне'));
|
|
@@ -1596,8 +1624,6 @@ async function extractZip(zipPath, sessionPath, chatId) {
|
|
|
1596
1624
|
return;
|
|
1597
1625
|
}
|
|
1598
1626
|
|
|
1599
|
-
logInfo('✅ Найдена папка session в корне архива');
|
|
1600
|
-
|
|
1601
1627
|
// Создаем папку session если не существует
|
|
1602
1628
|
if (!fs.existsSync(sessionPath)) {
|
|
1603
1629
|
fs.mkdirSync(sessionPath, { recursive: true });
|
|
@@ -1607,10 +1633,10 @@ async function extractZip(zipPath, sessionPath, chatId) {
|
|
|
1607
1633
|
let successCount = 0;
|
|
1608
1634
|
let errorCount = 0;
|
|
1609
1635
|
|
|
1610
|
-
|
|
1611
|
-
if (entry.
|
|
1636
|
+
normalizedEntries.forEach((entry) => {
|
|
1637
|
+
if (entry.normalizedPath.startsWith('session/')) {
|
|
1612
1638
|
try {
|
|
1613
|
-
const relativePath = entry.
|
|
1639
|
+
const relativePath = entry.normalizedPath.substring('session/'.length);
|
|
1614
1640
|
if (relativePath) {
|
|
1615
1641
|
const targetPath = path.join(sessionPath, relativePath);
|
|
1616
1642
|
|
|
@@ -1631,7 +1657,7 @@ async function extractZip(zipPath, sessionPath, chatId) {
|
|
|
1631
1657
|
}
|
|
1632
1658
|
} catch (err) {
|
|
1633
1659
|
errorCount++;
|
|
1634
|
-
logWarn(`⚠️ Ошибка распаковки ${entry.
|
|
1660
|
+
logWarn(`⚠️ Ошибка распаковки ${entry.normalizedPath}: ${err.message}`);
|
|
1635
1661
|
}
|
|
1636
1662
|
}
|
|
1637
1663
|
});
|
|
@@ -1753,7 +1779,7 @@ async function extract7z(sevenZPath, sessionPath, chatId) {
|
|
|
1753
1779
|
const items = fs.readdirSync(sourceDir);
|
|
1754
1780
|
logInfo(`📂 Найдено элементов для копирования: ${items.length}`);
|
|
1755
1781
|
|
|
1756
|
-
items.forEach(item => {
|
|
1782
|
+
items.forEach((item) => {
|
|
1757
1783
|
const sourcePath = path.join(sourceDir, item);
|
|
1758
1784
|
const targetPath = path.join(targetDir, item);
|
|
1759
1785
|
|
|
@@ -1819,8 +1845,8 @@ async function gracefulRestart(chatId) {
|
|
|
1819
1845
|
logInfo('🔄 Запуск корректного перезапуска сервиса...');
|
|
1820
1846
|
|
|
1821
1847
|
await sendMessage(chatId,
|
|
1822
|
-
|
|
1823
|
-
|
|
1848
|
+
'🔄 <b>Перезапуск сервиса...</b>\n\n' +
|
|
1849
|
+
'⏱️ Сервис будет перезапущен в течение 5 секунд'
|
|
1824
1850
|
);
|
|
1825
1851
|
|
|
1826
1852
|
// Даем Docker Compose время на перезапуск
|
|
@@ -1854,51 +1880,51 @@ async function sendHelpMessage(chatId) {
|
|
|
1854
1880
|
const accountsExist = hasAccounts();
|
|
1855
1881
|
|
|
1856
1882
|
let helpText =
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1883
|
+
'🤖 <b>FreeQwenApi Bot - Управление</b>\n\n' +
|
|
1884
|
+
'📋 <b>Команды управления:</b>\n\n' +
|
|
1885
|
+
'/help - Показать это сообщение\n' +
|
|
1886
|
+
'/status - Показать статус сервиса\n' +
|
|
1887
|
+
'/restart - Перезапустить сервис\n' +
|
|
1888
|
+
'<s>/extend</s> - 🔧 Временно отключено\n\n';
|
|
1863
1889
|
|
|
1864
1890
|
// Команды генерации изображений
|
|
1865
1891
|
helpText +=
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1892
|
+
'🎨 <b>Генерация изображений:</b>\n\n' +
|
|
1893
|
+
'/image <описание> - Сгенерировать изображение\n' +
|
|
1894
|
+
'/imagine <описание> - Альтернативная команда\n\n' +
|
|
1895
|
+
'💡 Пример: /image A beautiful sunset over the ocean\n\n';
|
|
1870
1896
|
|
|
1871
1897
|
// Показываем LLM команды только если есть аккаунты
|
|
1872
1898
|
if (accountsExist) {
|
|
1873
1899
|
helpText +=
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1900
|
+
'🤖 <b>LLM Чат (AI ассистент):</b>\n\n' +
|
|
1901
|
+
'/chat - Показать состояние LLM чата\n' +
|
|
1902
|
+
'/togglechat - Включить/выключить LLM чат\n' +
|
|
1903
|
+
'/clear - Очистить контекст чата\n' +
|
|
1904
|
+
'/model - Информация о модели\n' +
|
|
1905
|
+
'/setmodel <название> - Сменить модель\n\n' +
|
|
1906
|
+
'💡 Когда LLM чат включен, просто отправляйте сообщения!\n\n';
|
|
1881
1907
|
} else {
|
|
1882
1908
|
helpText +=
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1909
|
+
'⚠️ <b>LLM Чат недоступен</b>\n\n' +
|
|
1910
|
+
'🔒 Функции AI ассистента временно недоступны\n' +
|
|
1911
|
+
'📦 Отправьте архив с сессиями для активации\n\n';
|
|
1886
1912
|
}
|
|
1887
1913
|
|
|
1888
1914
|
helpText +=
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1915
|
+
'📦 <b>Загрузка сессий:</b>\n\n' +
|
|
1916
|
+
'Отправьте ZIP или 7z архив с папкой "session" внутри.\n' +
|
|
1917
|
+
'Бот распакует его и перезапустит сервис.\n\n' +
|
|
1918
|
+
'/archive - Инструкция по созданию архива\n\n' +
|
|
1919
|
+
'📏 <b>Лимиты:</b>\n' +
|
|
1920
|
+
'• Максимальный размер файла: 50MB\n' +
|
|
1921
|
+
'• Поддерживаемые форматы: .zip, .7z\n\n' +
|
|
1922
|
+
'📚 <b>Дополнительные команды:</b>\n\n' +
|
|
1923
|
+
'/setup - Инструкция по созданию сессии\n' +
|
|
1924
|
+
'/connect - Как подключить к проекту\n' +
|
|
1925
|
+
'/about - Информация о проекте\n\n' +
|
|
1926
|
+
'🐳 <b>Docker:</b>\n' +
|
|
1927
|
+
'https://hub.docker.com/r/endykaufman/qwen-api-proxy';
|
|
1902
1928
|
|
|
1903
1929
|
await sendMessage(chatId, helpText);
|
|
1904
1930
|
}
|
|
@@ -1908,179 +1934,179 @@ async function sendHelpMessage(chatId) {
|
|
|
1908
1934
|
*/
|
|
1909
1935
|
async function sendSetupMessage(chatId) {
|
|
1910
1936
|
const setupText =
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1937
|
+
'🛠️ <b>Создание сессии авторизации</b>\n\n' +
|
|
1938
|
+
'<b>📖 Что нужно знать:</b>\n' +
|
|
1939
|
+
'• <b>Git</b> - система управления версиями (опционально)\n' +
|
|
1940
|
+
'• <b>Docker Compose</b> - инструмент для управления контейнерами\n' +
|
|
1941
|
+
'• Если используете Docker Desktop - Compose уже встроен!\n\n' +
|
|
1942
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
|
|
1943
|
+
'<b>Способ 1: Локальная установка (Node.js)</b>\n\n' +
|
|
1944
|
+
'<b>Вариант A: С Git:</b>\n' +
|
|
1945
|
+
'1. <code>git clone https://github.com/EndyKaufman/FreeQwenApi</code>\n' +
|
|
1946
|
+
'2. <code>cd FreeQwenApi</code>\n' +
|
|
1947
|
+
'3. <code>npm install</code>\n' +
|
|
1948
|
+
'4. <code>npm start</code>\n' +
|
|
1949
|
+
'5. Выберите <code>1</code> - добавить аккаунт\n' +
|
|
1950
|
+
'6. Войдите в аккаунт Qwen в браузере\n' +
|
|
1951
|
+
'7. Токен сохранится автоматически\n\n' +
|
|
1952
|
+
'<b>Вариант B: Без Git (ZIP):</b>\n' +
|
|
1953
|
+
'1. Скачайте ZIP: https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
1954
|
+
'2. Нажмите <b>"<> Code"</b> → <b>"Download ZIP"</b>\n' +
|
|
1955
|
+
'3. Распакуйте и перейдите в папку\n' +
|
|
1956
|
+
'4. <code>npm install</code>\n' +
|
|
1957
|
+
'5. <code>npm start</code> → выберите <code>1</code>\n\n' +
|
|
1958
|
+
'<b>Способ 2: Docker</b>\n\n' +
|
|
1959
|
+
'<b>Что такое Docker Compose?</b>\n' +
|
|
1960
|
+
'• Входит в Docker Desktop (Windows/macOS)\n' +
|
|
1961
|
+
'• Linux: <code>sudo apt install docker-compose-plugin</code>\n' +
|
|
1962
|
+
'• Проверка: <code>docker compose version</code>\n\n' +
|
|
1963
|
+
'<b>С Compose:</b>\n' +
|
|
1964
|
+
'1. Сначала создайте сессию локально:\n' +
|
|
1965
|
+
' <code>npm run auth</code> (или <code>npm start</code> → <code>1</code>)\n' +
|
|
1966
|
+
'2. Соберите Docker:\n' +
|
|
1967
|
+
' <code>docker compose build --no-cache</code>\n' +
|
|
1968
|
+
'3. Запустите:\n' +
|
|
1969
|
+
' <code>docker compose up -d</code>\n\n' +
|
|
1970
|
+
'<b>Без Compose (обычный Docker):</b>\n' +
|
|
1971
|
+
'1. Создайте сессию локально (см. Способ 1)\n' +
|
|
1972
|
+
'2. Соберите образ:\n' +
|
|
1973
|
+
' <code>docker build -t qwen-proxy .</code>\n' +
|
|
1974
|
+
'3. Запустите:\n' +
|
|
1975
|
+
' <code>docker run -d --name qwen-proxy -p 3264:3264 -e SKIP_ACCOUNT_MENU=true -v $(pwd)/session:/app/session qwen-proxy</code>\n\n' +
|
|
1976
|
+
'<b>Структура папки session:</b>\n' +
|
|
1977
|
+
'<code>session/</code>\n' +
|
|
1978
|
+
'├── <code>accounts/</code>\n' +
|
|
1979
|
+
'│ ├── <code>acc_123456/</code>\n' +
|
|
1980
|
+
'│ │ └── <code>token.txt</code>\n' +
|
|
1981
|
+
'│ └── <code>acc_789012/</code>\n' +
|
|
1982
|
+
'│ └── <code>token.txt</code>\n' +
|
|
1983
|
+
'└── <code>tokens.json</code>\n\n' +
|
|
1984
|
+
'💡 <b>Совет:</b> Используйте /archive для подробной инструкции\n\n' +
|
|
1985
|
+
'📖 Подробнее: https://github.com/EndyKaufman/FreeQwenApi';
|
|
1986
|
+
|
|
1961
1987
|
await sendMessage(chatId, setupText);
|
|
1962
1988
|
}
|
|
1963
1989
|
|
|
1964
1990
|
async function sendConnectMessage(chatId) {
|
|
1965
1991
|
const connectText =
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
1992
|
+
'🔌 <b>Подключение FreeQwenApi к проекту</b>\n\n' +
|
|
1993
|
+
'<b>📖 Что такое Docker Compose?</b>\n' +
|
|
1994
|
+
'Это инструмент для запуска многоконтейнерных приложений.\n' +
|
|
1995
|
+
'Он управляет контейнерами через файл <code>docker-compose.yml</code>.\n\n' +
|
|
1996
|
+
'<b>Установка Docker Compose:</b>\n' +
|
|
1997
|
+
'• Входит в <b>Docker Desktop</b> (Windows/macOS)\n' +
|
|
1998
|
+
'• Linux: <code>sudo apt install docker-compose-plugin</code>\n' +
|
|
1999
|
+
'• Проверка: <code>docker compose version</code>\n\n' +
|
|
2000
|
+
'💡 <i>Если Docker Desktop установлен - Compose уже есть!</i>\n\n' +
|
|
2001
|
+
'<b>━━━━━━━━━━━━━━━━━━━━━━━━━</b>\n\n' +
|
|
2002
|
+
'<b>Шаг 1: Запуск через Docker Compose</b>\n\n' +
|
|
2003
|
+
'Добавьте в ваш <code>docker-compose.yml</code>:\n\n' +
|
|
2004
|
+
'<pre>\n' +
|
|
2005
|
+
'services:\n' +
|
|
2006
|
+
' qwen-proxy:\n' +
|
|
2007
|
+
' build: .\n' +
|
|
2008
|
+
' container_name: qwen-proxy\n' +
|
|
2009
|
+
' environment:\n' +
|
|
2010
|
+
' - SKIP_ACCOUNT_MENU=true\n' +
|
|
2011
|
+
' - PORT=3264\n' +
|
|
2012
|
+
' ports:\n' +
|
|
2013
|
+
' - "3264:3264"\n' +
|
|
2014
|
+
' volumes:\n' +
|
|
2015
|
+
' - ./session_backup:/app/session_backup\n' +
|
|
2016
|
+
' - ./session:/app/session\n' +
|
|
2017
|
+
' - ./logs:/app/logs\n' +
|
|
2018
|
+
' - ./uploads:/app/uploads\n' +
|
|
2019
|
+
' - ./temp:/app/temp\n' +
|
|
2020
|
+
' restart: unless-stopped\n' +
|
|
2021
|
+
'</pre>\n\n' +
|
|
2022
|
+
'Или используйте наш <code>docker-compose.yml</code>:\n' +
|
|
2023
|
+
'<code>docker compose up -d</code>\n\n' +
|
|
2024
|
+
'<b>Альтернатива: Без Docker Compose</b>\n\n' +
|
|
2025
|
+
'Если Compose не установлен, используйте обычный Docker:\n\n' +
|
|
2026
|
+
'<pre>\n' +
|
|
2027
|
+
'docker build -t qwen-proxy .\n' +
|
|
2028
|
+
'docker run -d \\\n' +
|
|
2029
|
+
' --name qwen-proxy \\\n' +
|
|
2030
|
+
' -p 3264:3264 \\\n' +
|
|
2031
|
+
' -e SKIP_ACCOUNT_MENU=true \\\n' +
|
|
2032
|
+
' -v $(pwd)/session:/app/session \\\n' +
|
|
2033
|
+
' -v $(pwd)/logs:/app/logs \\\n' +
|
|
2034
|
+
' -v $(pwd)/uploads:/app/uploads \\\n' +
|
|
2035
|
+
' -v $(pwd)/temp:/app/temp \\\n' +
|
|
2036
|
+
' qwen-proxy\n' +
|
|
2037
|
+
'</pre>\n\n' +
|
|
2038
|
+
'<b>━━━━━━━━━━━━━━━━━━━━━━━━━</b>\n\n' +
|
|
2039
|
+
'<b>Шаг 2: Первый запрос через curl</b>\n\n' +
|
|
2040
|
+
'<b>Простой запрос:</b>\n' +
|
|
2041
|
+
'<pre>\n' +
|
|
2042
|
+
'curl http://localhost:3264/api/chat/completions \\\n' +
|
|
2043
|
+
' -H "Content-Type: application/json" \\\n' +
|
|
2044
|
+
' -d \'{"model":"qwen3.5-plus","messages":[{"role":"user","content":"Привет!"}]}\'\n' +
|
|
2045
|
+
'</pre>\n\n' +
|
|
2046
|
+
'<b>С продолжением диалога:</b>\n' +
|
|
2047
|
+
'<pre>\n' +
|
|
2048
|
+
'curl -X POST http://localhost:3264/api/chat/completions \\\n' +
|
|
2049
|
+
' -H "Content-Type: application/json" \\\n' +
|
|
2050
|
+
' -d \'{\n' +
|
|
2051
|
+
' "model": "qwen3.5-plus",\n' +
|
|
2052
|
+
' "messages": [{"role": "user", "content": "Сколько будет 2+2?"}]\n' +
|
|
2053
|
+
' }\'\n' +
|
|
2054
|
+
'</pre>\n\n' +
|
|
2055
|
+
'<b>Шаг 3: Использование с OpenAI SDK</b>\n\n' +
|
|
2056
|
+
'<pre>\n' +
|
|
2057
|
+
'import OpenAI from \'openai\';\n\n' +
|
|
2058
|
+
'const client = new OpenAI({\n' +
|
|
2059
|
+
' baseURL: \'http://localhost:3264/api\',\n' +
|
|
2060
|
+
' apiKey: \'any-string\'\n' +
|
|
2061
|
+
'});\n\n' +
|
|
2062
|
+
'const response = await client.chat.completions.create({\n' +
|
|
2063
|
+
' model: \'qwen3.5-plus\',\n' +
|
|
2064
|
+
' messages: [{ role: \'user\', content: \'Привет!\' }]\n' +
|
|
2065
|
+
'});\n' +
|
|
2066
|
+
'</pre>\n\n' +
|
|
2067
|
+
'📖 API Docs: http://localhost:3264/api\n' +
|
|
2068
|
+
'📚 GitHub: https://github.com/EndyKaufman/FreeQwenApi';
|
|
2069
|
+
|
|
2044
2070
|
await sendMessage(chatId, connectText);
|
|
2045
2071
|
}
|
|
2046
2072
|
|
|
2047
2073
|
async function sendAboutMessage(chatId) {
|
|
2048
2074
|
const aboutText =
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2075
|
+
'📚 <b>О проекте FreeQwenApi</b>\n\n' +
|
|
2076
|
+
'<b>🌐 Оригинальный проект:</b>\n' +
|
|
2077
|
+
'https://github.com/y13sint/FreeQwenApi\n\n' +
|
|
2078
|
+
'<b>🔧 Мой форк:</b>\n' +
|
|
2079
|
+
'https://github.com/EndyKaufman/FreeQwenApi\n\n' +
|
|
2080
|
+
'<b>⭐ Ключевые отличия форка:</b>\n\n' +
|
|
2081
|
+
'✅ <b>Telegram Bot интеграция</b>\n' +
|
|
2082
|
+
' - Управление сервисом через Telegram\n' +
|
|
2083
|
+
' - Загрузка сессий архивами (.zip/.7z)\n' +
|
|
2084
|
+
' - LLM чат с AI ассистентом\n' +
|
|
2085
|
+
' - Мониторинг статуса в реальном времени\n\n' +
|
|
2086
|
+
'✅ <b>Прокси поддержка для Telegram</b>\n' +
|
|
2087
|
+
' - HTTP/HTTPS/SOCKS прокси\n' +
|
|
2088
|
+
' - Безопасное логирование (без credentials)\n\n' +
|
|
2089
|
+
'✅ <b>Автоматическая распаковка архивов</b>\n' +
|
|
2090
|
+
' - Backup перед обновлением\n' +
|
|
2091
|
+
' - Error-tolerant extraction\n' +
|
|
2092
|
+
' - Health check при запуске\n\n' +
|
|
2093
|
+
'✅ <b>Улучшенная документация</b>\n' +
|
|
2094
|
+
' - Подробные README на русском\n' +
|
|
2095
|
+
' - Telegram bot guides\n' +
|
|
2096
|
+
'✅ <b>Production-ready features</b>\n' +
|
|
2097
|
+
' - Docker оптимизация\n' +
|
|
2098
|
+
' - Graceful restarts\n' +
|
|
2099
|
+
' - System health monitoring\n\n' +
|
|
2100
|
+
'<b>📊 Общие возможности:</b>\n' +
|
|
2101
|
+
'• 25+ моделей Qwen (включая Qwen 3.5)\n' +
|
|
2102
|
+
'• OpenAI-совместимый API\n' +
|
|
2103
|
+
'• Генерация изображений\n' +
|
|
2104
|
+
'• Загрузка файлов\n' +
|
|
2105
|
+
'• Streaming ответов (SSE)\n' +
|
|
2106
|
+
'• Мультиаккаунт ротация\n' +
|
|
2107
|
+
'• Бесплатный доступ к Qwen AI\n\n' +
|
|
2108
|
+
'💡 <i>Оба проекта используют MIT лицензию</i>';
|
|
2109
|
+
|
|
2084
2110
|
await sendMessage(chatId, aboutText);
|
|
2085
2111
|
}
|
|
2086
2112
|
|
|
@@ -2089,81 +2115,81 @@ async function sendAboutMessage(chatId) {
|
|
|
2089
2115
|
*/
|
|
2090
2116
|
async function sendArchiveInstructions(chatId) {
|
|
2091
2117
|
const archiveText =
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2118
|
+
'📦 <b>Создание архива сессии для Docker</b>\n\n' +
|
|
2119
|
+
'Эта инструкция поможет создать архив с авторизацией\n' +
|
|
2120
|
+
'для последующей загрузки в Telegram бота.\n\n' +
|
|
2121
|
+
'<b>🔹 Шаг 1: Установка Node.js</b>\n\n' +
|
|
2122
|
+
'<b>Windows:</b>\n' +
|
|
2123
|
+
'• Скачайте с <code>nodejs.org</code>\n' +
|
|
2124
|
+
'• Установите (галочка "Add to PATH")\n\n' +
|
|
2125
|
+
'<b>macOS:</b>\n' +
|
|
2126
|
+
'• <code>brew install node</code>\n' +
|
|
2127
|
+
'• Или скачайте с <code>nodejs.org</code>\n\n' +
|
|
2128
|
+
'<b>Linux (Ubuntu/Debian):</b>\n' +
|
|
2129
|
+
'• <code>curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -</code>\n' +
|
|
2130
|
+
'• <code>sudo apt install -y nodejs</code>\n\n' +
|
|
2131
|
+
'<b>🔹 Шаг 2: Скачивание проекта</b>\n\n' +
|
|
2132
|
+
'<b>Способ A: Git (если установлен):</b>\n' +
|
|
2133
|
+
'<pre>\n' +
|
|
2134
|
+
'git clone https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
2135
|
+
'cd FreeQwenApi\n' +
|
|
2136
|
+
'npm install\n' +
|
|
2137
|
+
'</pre>\n\n' +
|
|
2138
|
+
'<b>Способ B: Без Git (ZIP архив):</b>\n' +
|
|
2139
|
+
'1. Откройте: https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
2140
|
+
'2. Нажмите зелёную кнопку <b>"<> Code"</b>\n' +
|
|
2141
|
+
'3. Выберите <b>"Download ZIP"</b>\n' +
|
|
2142
|
+
'4. Распакуйте архив\n' +
|
|
2143
|
+
'5. Откройте терминал в папке проекта\n' +
|
|
2144
|
+
'6. <code>npm install</code>\n\n' +
|
|
2145
|
+
'💡 <i>Этот способ не требует установки Git!</i>\n\n' +
|
|
2146
|
+
'<b>🔹 Шаг 3: Создание архива сессии</b>\n\n' +
|
|
2147
|
+
'<pre>\n' +
|
|
2148
|
+
'npm run create-session-archive\n' +
|
|
2149
|
+
'</pre>\n\n' +
|
|
2150
|
+
'<b>Что произойдет:</b>\n' +
|
|
2151
|
+
'1. Откроется браузер\n' +
|
|
2152
|
+
'2. Войдите в Qwen (GitHub/Google/email)\n' +
|
|
2153
|
+
'3. Нажмите ENTER в консоли\n' +
|
|
2154
|
+
'4. Сессия сохранится\n' +
|
|
2155
|
+
'5. Создется ZIP архив\n\n' +
|
|
2156
|
+
'<b>🔹 Шаг 4: Отправка в Telegram бота</b>\n\n' +
|
|
2157
|
+
'1. Откройте нашего бота\n' +
|
|
2158
|
+
'2. Нажмите 📎 (скрепка)\n' +
|
|
2159
|
+
'3. Выберите <b>"Файл"</b> (НЕ "Фото"!)\n' +
|
|
2160
|
+
'4. Выберите <code>session_backup_*.zip</code>\n' +
|
|
2161
|
+
'5. Отправьте\n' +
|
|
2162
|
+
'6. Дождитесь: <code>✅ Архив распакован</code>\n\n' +
|
|
2163
|
+
'<b>🔹 Альтернатива: Ручной способ</b>\n\n' +
|
|
2164
|
+
'Если <code>npm run</code> не работает:\n\n' +
|
|
2165
|
+
'<b>Windows PowerShell:</b>\n' +
|
|
2166
|
+
'<pre>\n' +
|
|
2167
|
+
'node scripts/createSessionArchive.js\n' +
|
|
2168
|
+
'</pre>\n\n' +
|
|
2169
|
+
'<b>Linux/macOS:</b>\n' +
|
|
2170
|
+
'<pre>\n' +
|
|
2171
|
+
'node scripts/createSessionArchive.js\n' +
|
|
2172
|
+
'</pre>\n\n' +
|
|
2173
|
+
'<b>🔹 Структура архива:</b>\n\n' +
|
|
2174
|
+
'<pre>\n' +
|
|
2175
|
+
'session/\n' +
|
|
2176
|
+
'├── accounts/\n' +
|
|
2177
|
+
'│ ├── acc_123456/\n' +
|
|
2178
|
+
'│ │ ├── token.txt\n' +
|
|
2179
|
+
'│ │ └── cookies.json\n' +
|
|
2180
|
+
'│ └── acc_789012/\n' +
|
|
2181
|
+
'│ ├── token.txt\n' +
|
|
2182
|
+
'│ └── cookies.json\n' +
|
|
2183
|
+
'└── tokens.json\n' +
|
|
2184
|
+
'</pre>\n\n' +
|
|
2185
|
+
'<b>⚠️ Важно:</b>\n' +
|
|
2186
|
+
'• <code>cookies.json</code> обязателен!\n' +
|
|
2187
|
+
'• Без cookies сессия не продлится\n' +
|
|
2188
|
+
'• Архив должен содержать папку <code>session/</code>\n\n' +
|
|
2189
|
+
'<b>🆘 Проблемы?</b>\n' +
|
|
2190
|
+
'• GitHub: https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
2191
|
+
'• Используйте /help для списка команд';
|
|
2192
|
+
|
|
2167
2193
|
await sendMessage(chatId, archiveText);
|
|
2168
2194
|
}
|
|
2169
2195
|
|
|
@@ -2172,92 +2198,92 @@ async function sendStatusMessage(chatId, isScheduled = false) {
|
|
|
2172
2198
|
// Получаем статус бота
|
|
2173
2199
|
const telegramToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
2174
2200
|
const botStarted = !!telegramToken;
|
|
2175
|
-
|
|
2201
|
+
|
|
2176
2202
|
// Проверяем все подсистемы (не отправляем автоматически, так как sendStatusMessage отправит сам)
|
|
2177
2203
|
const checks = await checkAllSubsystems(botStarted, false);
|
|
2178
|
-
|
|
2204
|
+
|
|
2179
2205
|
// Формируем сообщение с统一的格式
|
|
2180
2206
|
const reportLines = [];
|
|
2181
2207
|
|
|
2182
2208
|
// Заголовок
|
|
2183
2209
|
if (isScheduled) {
|
|
2184
2210
|
const now = new Date();
|
|
2185
|
-
const timeStr = now.toLocaleString('ru-RU', {
|
|
2186
|
-
day: '2-digit',
|
|
2187
|
-
month: '2-digit',
|
|
2211
|
+
const timeStr = now.toLocaleString('ru-RU', {
|
|
2212
|
+
day: '2-digit',
|
|
2213
|
+
month: '2-digit',
|
|
2188
2214
|
year: 'numeric',
|
|
2189
|
-
hour: '2-digit',
|
|
2190
|
-
minute: '2-digit'
|
|
2215
|
+
hour: '2-digit',
|
|
2216
|
+
minute: '2-digit'
|
|
2191
2217
|
});
|
|
2192
2218
|
reportLines.push(`⏰ <b>Плановая проверка</b> (${timeStr})\n`);
|
|
2193
2219
|
} else {
|
|
2194
|
-
reportLines.push(
|
|
2220
|
+
reportLines.push('🚀 <b>Сервис запущен!</b>\n');
|
|
2195
2221
|
}
|
|
2196
2222
|
|
|
2197
2223
|
// Группа 1: Основные компоненты
|
|
2198
|
-
reportLines.push(
|
|
2199
|
-
const mainComponents = checks.filter(c =>
|
|
2200
|
-
c.name.includes('Session') || c.name.includes('Токены') || c.name.includes('Telegram') ||
|
|
2224
|
+
reportLines.push('<b>🔑 Основные компоненты:</b>');
|
|
2225
|
+
const mainComponents = checks.filter((c) =>
|
|
2226
|
+
c.name.includes('Session') || c.name.includes('Токены') || c.name.includes('Telegram') ||
|
|
2201
2227
|
c.name.includes('Токен ') || c.name.includes('AI') || c.name.includes('Ответ')
|
|
2202
2228
|
);
|
|
2203
|
-
mainComponents.forEach(check => {
|
|
2229
|
+
mainComponents.forEach((check) => {
|
|
2204
2230
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
2205
2231
|
});
|
|
2206
2232
|
|
|
2207
2233
|
reportLines.push('');
|
|
2208
2234
|
|
|
2209
2235
|
// Группа 2: Инфраструктура
|
|
2210
|
-
reportLines.push(
|
|
2211
|
-
const infrastructure = checks.filter(c =>
|
|
2236
|
+
reportLines.push('<b>🏗️ Инфраструктура:</b>');
|
|
2237
|
+
const infrastructure = checks.filter((c) =>
|
|
2212
2238
|
c.name.includes('Прокси') || c.name.includes('Uploads') || c.name.includes('Логирование')
|
|
2213
2239
|
);
|
|
2214
|
-
infrastructure.forEach(check => {
|
|
2240
|
+
infrastructure.forEach((check) => {
|
|
2215
2241
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
2216
2242
|
});
|
|
2217
2243
|
|
|
2218
2244
|
reportLines.push('');
|
|
2219
2245
|
|
|
2220
2246
|
// Группа 3: Инструменты
|
|
2221
|
-
reportLines.push(
|
|
2222
|
-
const tools = checks.filter(c =>
|
|
2247
|
+
reportLines.push('<b>🔧 Инструменты:</b>');
|
|
2248
|
+
const tools = checks.filter((c) =>
|
|
2223
2249
|
c.name.includes('p7zip')
|
|
2224
2250
|
);
|
|
2225
|
-
tools.forEach(check => {
|
|
2251
|
+
tools.forEach((check) => {
|
|
2226
2252
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
2227
2253
|
});
|
|
2228
2254
|
|
|
2229
2255
|
reportLines.push('');
|
|
2230
|
-
reportLines.push(
|
|
2256
|
+
reportLines.push('━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2231
2257
|
|
|
2232
2258
|
// Итоговый статус
|
|
2233
2259
|
const tokens = loadTokens();
|
|
2234
2260
|
const hasTokens = tokens.length > 0;
|
|
2235
|
-
const allOk = checks.every(c => c.status);
|
|
2236
|
-
|
|
2261
|
+
const allOk = checks.every((c) => c.status);
|
|
2262
|
+
|
|
2237
2263
|
if (hasTokens && allOk) {
|
|
2238
|
-
reportLines.push(
|
|
2264
|
+
reportLines.push('✅ <b>Все системы работают</b>');
|
|
2239
2265
|
} else if (!hasTokens) {
|
|
2240
|
-
reportLines.push(
|
|
2241
|
-
reportLines.push(
|
|
2266
|
+
reportLines.push('⚠️ <b>Режим ожидания архива</b>');
|
|
2267
|
+
reportLines.push('📦 Отправьте архив с сессиями');
|
|
2242
2268
|
} else {
|
|
2243
|
-
reportLines.push(
|
|
2269
|
+
reportLines.push('⚠️ <b>Есть проблемы</b>');
|
|
2244
2270
|
}
|
|
2245
2271
|
|
|
2246
2272
|
// Ссылки
|
|
2247
2273
|
reportLines.push(`\n🌐 API: http://localhost:${process.env.PORT || 3264}`);
|
|
2248
2274
|
reportLines.push(`📖 Docs: http://localhost:${process.env.PORT || 3264}/api`);
|
|
2249
|
-
|
|
2275
|
+
|
|
2250
2276
|
// Репозиторий
|
|
2251
|
-
reportLines.push(
|
|
2252
|
-
reportLines.push(
|
|
2253
|
-
reportLines.push(
|
|
2254
|
-
reportLines.push(
|
|
2255
|
-
|
|
2277
|
+
reportLines.push('\n📚 <b>Репозиторий:</b>');
|
|
2278
|
+
reportLines.push('🔗 GitHub: https://github.com/EndyKaufman/FreeQwenApi');
|
|
2279
|
+
reportLines.push('⭐ Оригинал: https://github.com/y1n7sint/FreeQwenApi');
|
|
2280
|
+
reportLines.push('🐳 Docker: https://hub.docker.com/r/endykaufman/qwen-api-proxy');
|
|
2281
|
+
|
|
2256
2282
|
// Справка
|
|
2257
|
-
reportLines.push(
|
|
2258
|
-
reportLines.push(
|
|
2259
|
-
reportLines.push(
|
|
2260
|
-
reportLines.push(
|
|
2283
|
+
reportLines.push('\n💡 <b>Справка:</b>');
|
|
2284
|
+
reportLines.push('📝 Используйте /help для списка команд');
|
|
2285
|
+
reportLines.push('🔍 Используйте /status для проверки состояния');
|
|
2286
|
+
reportLines.push('🤖 Используйте /chat для включения LLM режима');
|
|
2261
2287
|
|
|
2262
2288
|
const report = reportLines.join('\n');
|
|
2263
2289
|
await sendMessage(chatId, report);
|
|
@@ -2272,7 +2298,7 @@ async function sendStatusMessage(chatId, isScheduled = false) {
|
|
|
2272
2298
|
*/
|
|
2273
2299
|
async function handleRestart(chatId) {
|
|
2274
2300
|
await sendMessage(chatId, '🔄 Перезапуск сервиса...');
|
|
2275
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2301
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2276
2302
|
await gracefulRestart(chatId);
|
|
2277
2303
|
}
|
|
2278
2304
|
|
|
@@ -2287,11 +2313,11 @@ async function handleExtendSession(chatId) {
|
|
|
2287
2313
|
const { loadSession, saveAuthToken } = await import('../browser/session.js');
|
|
2288
2314
|
const { loadTokens, saveTokens } = await import('../api/tokenManager.js');
|
|
2289
2315
|
const { CHAT_PAGE_URL } = await import('../config.js');
|
|
2290
|
-
|
|
2316
|
+
|
|
2291
2317
|
const tokens = loadTokens();
|
|
2292
|
-
|
|
2318
|
+
|
|
2293
2319
|
if (tokens.length === 0) {
|
|
2294
|
-
await sendMessage(chatId,
|
|
2320
|
+
await sendMessage(chatId,
|
|
2295
2321
|
'⚠️ <b>Нет аккаунтов</b>\n\n' +
|
|
2296
2322
|
'Сначала создайте сессию:\n' +
|
|
2297
2323
|
'1. Запустите <code>npm run create-session-archive</code>\n' +
|
|
@@ -2302,21 +2328,21 @@ async function handleExtendSession(chatId) {
|
|
|
2302
2328
|
|
|
2303
2329
|
// Фильтруем только действительные токены для продления
|
|
2304
2330
|
const now = Date.now();
|
|
2305
|
-
const validTokens = tokens.filter(t => {
|
|
2306
|
-
if (t.invalid) return false;
|
|
2307
|
-
if (t.resetAt && new Date(t.resetAt).getTime() > now) return false;
|
|
2308
|
-
if (t.expiryTime && t.expiryTime <= now) return false;
|
|
2331
|
+
const validTokens = tokens.filter((t) => {
|
|
2332
|
+
if (t.invalid) { return false; }
|
|
2333
|
+
if (t.resetAt && new Date(t.resetAt).getTime() > now) { return false; }
|
|
2334
|
+
if (t.expiryTime && t.expiryTime <= now) { return false; }
|
|
2309
2335
|
// Проверяем наличие cookies.json
|
|
2310
2336
|
const cookiesPath = path.join(process.cwd(), SESSION_DIR, 'accounts', t.id, 'cookies.json');
|
|
2311
|
-
if (!fs.existsSync(cookiesPath)) return false;
|
|
2337
|
+
if (!fs.existsSync(cookiesPath)) { return false; }
|
|
2312
2338
|
return true;
|
|
2313
2339
|
});
|
|
2314
2340
|
|
|
2315
2341
|
const expiredCount = tokens.length - validTokens.length;
|
|
2316
2342
|
|
|
2317
2343
|
if (validTokens.length === 0) {
|
|
2318
|
-
await sendMessage(chatId,
|
|
2319
|
-
|
|
2344
|
+
await sendMessage(chatId,
|
|
2345
|
+
'⚠️ <b>Нет действительных токенов</b>\n\n' +
|
|
2320
2346
|
`Все ${tokens.length} токенов истекли.\n\n` +
|
|
2321
2347
|
'Создайте новые сессии:\n' +
|
|
2322
2348
|
'1. Запустите <code>npm run create-session-archive</code>\n' +
|
|
@@ -2325,15 +2351,15 @@ async function handleExtendSession(chatId) {
|
|
|
2325
2351
|
return;
|
|
2326
2352
|
}
|
|
2327
2353
|
|
|
2328
|
-
let startMessage =
|
|
2354
|
+
let startMessage = '🔄 <b>Продление сессий...</b>\n\n' +
|
|
2329
2355
|
`📊 Найдено аккаунтов: ${validTokens.length}`;
|
|
2330
|
-
|
|
2356
|
+
|
|
2331
2357
|
if (expiredCount > 0) {
|
|
2332
2358
|
startMessage += ` (пропущено ${expiredCount} истекших)`;
|
|
2333
2359
|
}
|
|
2334
|
-
|
|
2335
|
-
startMessage +=
|
|
2336
|
-
|
|
2360
|
+
|
|
2361
|
+
startMessage += '\n⏳ Это может занять несколько минут...\n' +
|
|
2362
|
+
'🕐 Примерное время: ~2-4 минуты на аккаунт';
|
|
2337
2363
|
|
|
2338
2364
|
await sendMessage(chatId, startMessage);
|
|
2339
2365
|
|
|
@@ -2345,7 +2371,7 @@ async function handleExtendSession(chatId) {
|
|
|
2345
2371
|
try {
|
|
2346
2372
|
// Показываем прогресс
|
|
2347
2373
|
const currentNum = results.length + 1;
|
|
2348
|
-
await sendMessage(chatId,
|
|
2374
|
+
await sendMessage(chatId,
|
|
2349
2375
|
`🔄 Обрабатываю аккаунт ${currentNum}/${tokens.length}...\n` +
|
|
2350
2376
|
`👤 ${token.id}`
|
|
2351
2377
|
);
|
|
@@ -2359,7 +2385,7 @@ async function handleExtendSession(chatId) {
|
|
|
2359
2385
|
|
|
2360
2386
|
// Загружаем cookies для аккаунта
|
|
2361
2387
|
const cookiesPath = path.join(process.cwd(), SESSION_DIR, 'accounts', token.id, 'cookies.json');
|
|
2362
|
-
|
|
2388
|
+
|
|
2363
2389
|
if (!fs.existsSync(cookiesPath)) {
|
|
2364
2390
|
results.push(`❌ ${token.id} - нет cookies`);
|
|
2365
2391
|
failCount++;
|
|
@@ -2372,26 +2398,26 @@ async function handleExtendSession(chatId) {
|
|
|
2372
2398
|
|
|
2373
2399
|
// Открываем браузер в headless режиме
|
|
2374
2400
|
const browserOk = await initBrowser(false, true);
|
|
2375
|
-
|
|
2401
|
+
|
|
2376
2402
|
if (!browserOk) {
|
|
2377
2403
|
throw new Error('Не удалось открыть браузер');
|
|
2378
2404
|
}
|
|
2379
2405
|
|
|
2380
2406
|
const ctx = getBrowserContext();
|
|
2381
|
-
|
|
2407
|
+
|
|
2382
2408
|
// Загружаем cookies
|
|
2383
2409
|
if (ctx && typeof ctx.setCookie === 'function') {
|
|
2384
2410
|
await ctx.setCookie(...cookies);
|
|
2385
2411
|
}
|
|
2386
2412
|
|
|
2387
2413
|
// Переходим на Qwen для обновления сессии (3 минуты таймаут)
|
|
2388
|
-
await ctx.goto(CHAT_PAGE_URL, {
|
|
2389
|
-
waitUntil: 'domcontentloaded',
|
|
2414
|
+
await ctx.goto(CHAT_PAGE_URL, {
|
|
2415
|
+
waitUntil: 'domcontentloaded',
|
|
2390
2416
|
timeout: 180000 // 3 минуты
|
|
2391
2417
|
});
|
|
2392
2418
|
|
|
2393
2419
|
// Ждем загрузки страницы (1 минута для полной загрузки)
|
|
2394
|
-
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
2420
|
+
await new Promise((resolve) => setTimeout(resolve, 60000));
|
|
2395
2421
|
|
|
2396
2422
|
// Извлекаем новый токен
|
|
2397
2423
|
const newToken = await extractAuthToken(ctx, true);
|
|
@@ -2409,7 +2435,7 @@ async function handleExtendSession(chatId) {
|
|
|
2409
2435
|
saveAuthToken(newToken);
|
|
2410
2436
|
|
|
2411
2437
|
// Обновляем tokens.json
|
|
2412
|
-
const tokenIndex = tokens.findIndex(t => t.id === token.id);
|
|
2438
|
+
const tokenIndex = tokens.findIndex((t) => t.id === token.id);
|
|
2413
2439
|
if (tokenIndex !== -1) {
|
|
2414
2440
|
tokens[tokenIndex].token = newToken;
|
|
2415
2441
|
tokens[tokenIndex].resetAt = null;
|
|
@@ -2430,14 +2456,14 @@ async function handleExtendSession(chatId) {
|
|
|
2430
2456
|
|
|
2431
2457
|
// Небольшая задержка между аккаунтами
|
|
2432
2458
|
if (successCount < tokens.length) {
|
|
2433
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2459
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2434
2460
|
}
|
|
2435
2461
|
|
|
2436
2462
|
} catch (error) {
|
|
2437
2463
|
logError(`Ошибка продления ${token.id}`, error);
|
|
2438
2464
|
results.push(`❌ ${token.id} - ${error.message}`);
|
|
2439
2465
|
failCount++;
|
|
2440
|
-
|
|
2466
|
+
|
|
2441
2467
|
// Убеждаемся что браузер закрыт
|
|
2442
2468
|
try {
|
|
2443
2469
|
await shutdownBrowser();
|
|
@@ -2448,31 +2474,31 @@ async function handleExtendSession(chatId) {
|
|
|
2448
2474
|
}
|
|
2449
2475
|
|
|
2450
2476
|
// Формируем отчет
|
|
2451
|
-
let report =
|
|
2477
|
+
let report = '📋 <b>Результат продления сессий</b>\n\n';
|
|
2452
2478
|
report += `✅ Успешно: ${successCount}\n`;
|
|
2453
2479
|
report += `❌ Ошибки: ${failCount}\n\n`;
|
|
2454
|
-
report +=
|
|
2480
|
+
report += '<b>Детали:</b>\n';
|
|
2455
2481
|
report += results.join('\n');
|
|
2456
2482
|
|
|
2457
2483
|
if (successCount > 0) {
|
|
2458
|
-
report +=
|
|
2484
|
+
report += '\n\n🎉 Сессии продлены!';
|
|
2459
2485
|
}
|
|
2460
2486
|
|
|
2461
2487
|
if (failCount > 0 && successCount === 0) {
|
|
2462
|
-
report +=
|
|
2463
|
-
|
|
2488
|
+
report += '\n\n⚠️ Все сессии не удалось продлить.\n';
|
|
2489
|
+
|
|
2464
2490
|
// Check if the issue is missing cookies
|
|
2465
|
-
const missingCookiesCount = results.filter(r => r.includes('нет cookies')).length;
|
|
2466
|
-
|
|
2491
|
+
const missingCookiesCount = results.filter((r) => r.includes('нет cookies')).length;
|
|
2492
|
+
|
|
2467
2493
|
if (missingCookiesCount > 0) {
|
|
2468
|
-
report +=
|
|
2469
|
-
report +=
|
|
2470
|
-
report +=
|
|
2471
|
-
report +=
|
|
2472
|
-
report +=
|
|
2473
|
-
report +=
|
|
2494
|
+
report += '\n📦 <b>Причина: отсутствуют cookies.json</b>\n';
|
|
2495
|
+
report += '\nДля создания новой сессии с cookies:\n';
|
|
2496
|
+
report += '1. Запустите: <code>npm run create-session-archive</code>\n';
|
|
2497
|
+
report += '2. Войдите в систему в браузере\n';
|
|
2498
|
+
report += '3. Бот автоматически сохранит cookies и токен\n';
|
|
2499
|
+
report += '\n💡 Или отправьте архив с сессиями через бота';
|
|
2474
2500
|
} else {
|
|
2475
|
-
report +=
|
|
2501
|
+
report += '\nВыполните: <code>npm run create-session-archive</code>';
|
|
2476
2502
|
}
|
|
2477
2503
|
}
|
|
2478
2504
|
|
|
@@ -2480,12 +2506,12 @@ async function handleExtendSession(chatId) {
|
|
|
2480
2506
|
|
|
2481
2507
|
} catch (error) {
|
|
2482
2508
|
logError('Ошибка при продлении сессий', error);
|
|
2483
|
-
await sendMessage(chatId,
|
|
2484
|
-
|
|
2509
|
+
await sendMessage(chatId,
|
|
2510
|
+
'❌ <b>Ошибка продления сессий</b>\n\n' +
|
|
2485
2511
|
`Ошибка: ${error.message}\n\n` +
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2512
|
+
'Попробуйте:\n' +
|
|
2513
|
+
'1. <code>npm run create-session-archive</code>\n' +
|
|
2514
|
+
'2. Или отправьте архив с сессиями'
|
|
2489
2515
|
);
|
|
2490
2516
|
}
|
|
2491
2517
|
}
|
|
@@ -2501,7 +2527,7 @@ async function sendScheduledStatusToAdmins() {
|
|
|
2501
2527
|
}
|
|
2502
2528
|
|
|
2503
2529
|
const adminUserIds = TELEGRAM_USER_IDS;
|
|
2504
|
-
|
|
2530
|
+
|
|
2505
2531
|
if (adminUserIds.length === 0) {
|
|
2506
2532
|
logInfo('Нет админов для отправки плановой проверки');
|
|
2507
2533
|
return;
|
|
@@ -2527,9 +2553,9 @@ async function sendScheduledStatusToAdmins() {
|
|
|
2527
2553
|
*/
|
|
2528
2554
|
export function startPeriodicHealthCheck() {
|
|
2529
2555
|
const FOUR_HOURS = 4 * 60 * 60 * 1000; // 4 часа в миллисекундах
|
|
2530
|
-
|
|
2531
|
-
logInfo(
|
|
2532
|
-
|
|
2556
|
+
|
|
2557
|
+
logInfo('Запуск периодической проверки здоровья каждые 4 часа');
|
|
2558
|
+
|
|
2533
2559
|
setInterval(async () => {
|
|
2534
2560
|
logInfo('Выполняется плановая проверка здоровья...');
|
|
2535
2561
|
await sendScheduledStatusToAdmins();
|
|
@@ -2580,6 +2606,7 @@ export async function notifyAllUsers(message) {
|
|
|
2580
2606
|
* Обработчик LLM чата - отправляет сообщение в Qwen API
|
|
2581
2607
|
*/
|
|
2582
2608
|
async function handleLLMChat(chatId, userMessage) {
|
|
2609
|
+
let context;
|
|
2583
2610
|
try {
|
|
2584
2611
|
// Показываем индикатор набора текста
|
|
2585
2612
|
await sendChatAction(chatId, 'typing');
|
|
@@ -2588,7 +2615,8 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2588
2615
|
if (!chatContexts.has(chatId)) {
|
|
2589
2616
|
chatContexts.set(chatId, []);
|
|
2590
2617
|
}
|
|
2591
|
-
|
|
2618
|
+
|
|
2619
|
+
context = chatContexts.get(chatId);
|
|
2592
2620
|
|
|
2593
2621
|
// Добавляем сообщение пользователя в контекст
|
|
2594
2622
|
context.push({ role: 'user', content: userMessage });
|
|
@@ -2624,17 +2652,17 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2624
2652
|
method: 'POST',
|
|
2625
2653
|
headers: {
|
|
2626
2654
|
'Content-Type': 'application/json',
|
|
2627
|
-
'x-chat-id': `telegram-${chatId}`
|
|
2655
|
+
'x-chat-id': `telegram-${chatId}` // Уникальный ID для каждого Telegram чата
|
|
2628
2656
|
},
|
|
2629
2657
|
body: JSON.stringify(requestBody)
|
|
2630
2658
|
});
|
|
2631
2659
|
|
|
2632
2660
|
if (!response.ok) {
|
|
2633
2661
|
const errorText = await response.text();
|
|
2634
|
-
|
|
2662
|
+
|
|
2635
2663
|
// Логируем полную ошибку
|
|
2636
2664
|
logError(`❌ LLM Chat: Qwen API error ${response.status}`, errorText);
|
|
2637
|
-
|
|
2665
|
+
|
|
2638
2666
|
// Пытаемся распарсить JSON для лучшего форматирования
|
|
2639
2667
|
let errorJson;
|
|
2640
2668
|
try {
|
|
@@ -2642,16 +2670,16 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2642
2670
|
} catch {
|
|
2643
2671
|
errorJson = { error: errorText };
|
|
2644
2672
|
}
|
|
2645
|
-
|
|
2673
|
+
|
|
2646
2674
|
// Формируем сообщение об ошибке с полным JSON
|
|
2647
2675
|
const escapedJson = escapeHtmlForCode(JSON.stringify(errorJson, null, 2));
|
|
2648
|
-
const errorMessage =
|
|
2649
|
-
|
|
2676
|
+
const errorMessage =
|
|
2677
|
+
'❌ <b>Ошибка Qwen API</b>\n\n' +
|
|
2650
2678
|
`Статус: <code>${response.status}</code>\n\n` +
|
|
2651
|
-
|
|
2679
|
+
'<b>Полный ответ:</b>\n' +
|
|
2652
2680
|
`<pre>${escapedJson}</pre>\n\n` +
|
|
2653
|
-
|
|
2654
|
-
|
|
2681
|
+
'💡 Попробуйте еще раз или используйте /clear';
|
|
2682
|
+
|
|
2655
2683
|
// Отправляем ошибку (разбиваем если длинная)
|
|
2656
2684
|
if (errorMessage.length > 4000) {
|
|
2657
2685
|
const chunks = splitMessage(errorMessage, 4000);
|
|
@@ -2661,12 +2689,12 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2661
2689
|
} else {
|
|
2662
2690
|
await sendMessage(chatId, errorMessage);
|
|
2663
2691
|
}
|
|
2664
|
-
|
|
2692
|
+
|
|
2665
2693
|
// Удаляем последнее сообщение пользователя из контекста (оно не было обработано)
|
|
2666
2694
|
if (context.length > 0) {
|
|
2667
2695
|
context.pop();
|
|
2668
2696
|
}
|
|
2669
|
-
|
|
2697
|
+
|
|
2670
2698
|
return; // Выходим, не выбрасывая ошибку
|
|
2671
2699
|
}
|
|
2672
2700
|
|
|
@@ -2693,17 +2721,17 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2693
2721
|
|
|
2694
2722
|
} catch (error) {
|
|
2695
2723
|
logError('❌ LLM Chat: Ошибка', error);
|
|
2696
|
-
|
|
2724
|
+
|
|
2697
2725
|
// Формируем сообщение об ошибке с деталями
|
|
2698
2726
|
const escapedStack = escapeHtmlForCode(error.stack || 'Stack trace unavailable');
|
|
2699
|
-
const errorMessage =
|
|
2700
|
-
|
|
2727
|
+
const errorMessage =
|
|
2728
|
+
'❌ <b>Ошибка при обработке запроса</b>\n\n' +
|
|
2701
2729
|
`<b>Тип:</b> ${error.name || 'Unknown'}\n` +
|
|
2702
2730
|
`<b>Сообщение:</b> ${escapeHtml(error.message)}\n\n` +
|
|
2703
|
-
|
|
2731
|
+
'<b>Стек:</b>\n' +
|
|
2704
2732
|
`<pre>${escapedStack}</pre>\n\n` +
|
|
2705
|
-
|
|
2706
|
-
|
|
2733
|
+
'💡 Попробуйте еще раз или используйте /clear для очистки контекста.';
|
|
2734
|
+
|
|
2707
2735
|
// Отправляем ошибку (разбиваем если длинная)
|
|
2708
2736
|
if (errorMessage.length > 4000) {
|
|
2709
2737
|
const chunks = splitMessage(errorMessage, 4000);
|
|
@@ -2713,7 +2741,7 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2713
2741
|
} else {
|
|
2714
2742
|
await sendMessage(chatId, errorMessage);
|
|
2715
2743
|
}
|
|
2716
|
-
|
|
2744
|
+
|
|
2717
2745
|
// Удаляем последнее сообщение пользователя из контекста
|
|
2718
2746
|
if (context && context.length > 0) {
|
|
2719
2747
|
context.pop();
|
|
@@ -2728,18 +2756,18 @@ async function toggleLLMChat(chatId) {
|
|
|
2728
2756
|
// Проверяем есть ли аккаунты
|
|
2729
2757
|
if (!hasAccounts()) {
|
|
2730
2758
|
await sendMessage(chatId,
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2759
|
+
'❌ <b>LLM чат недоступен</b>\n\n' +
|
|
2760
|
+
'🔒 Для работы AI ассистента нужны аккаунты\n' +
|
|
2761
|
+
'📦 Отправьте архив с сессиями через бота\n' +
|
|
2762
|
+
'💡 После загрузки аккаунтов функции будут доступны'
|
|
2735
2763
|
);
|
|
2736
|
-
logInfo(
|
|
2764
|
+
logInfo('❌ LLM Chat запрошен, но аккаунты отсутствуют');
|
|
2737
2765
|
return;
|
|
2738
2766
|
}
|
|
2739
2767
|
|
|
2740
2768
|
// Если LLM уже включен - выключаем, и наоборот
|
|
2741
2769
|
llmChatEnabled = !llmChatEnabled;
|
|
2742
|
-
|
|
2770
|
+
|
|
2743
2771
|
// Сохраняем состояние LLM чата
|
|
2744
2772
|
saveBotSettings({
|
|
2745
2773
|
activeModel: activeModel,
|
|
@@ -2753,26 +2781,26 @@ async function toggleLLMChat(chatId) {
|
|
|
2753
2781
|
}
|
|
2754
2782
|
|
|
2755
2783
|
await sendMessage(chatId,
|
|
2756
|
-
|
|
2757
|
-
|
|
2784
|
+
'✅ <b>LLM чат включен!</b>\n\n' +
|
|
2785
|
+
'🤖 Теперь я отвечаю как AI ассистент.\n' +
|
|
2758
2786
|
`📝 Модель: ${getModelForChat(chatId)}\n` +
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2787
|
+
'💬 Просто отправляйте сообщения.\n' +
|
|
2788
|
+
'💾 Настройка сохранена\n\n' +
|
|
2789
|
+
'<b>Команды:</b>\n' +
|
|
2790
|
+
'/togglechat - Выключить LLM чат\n' +
|
|
2791
|
+
'/clear - Очистить контекст\n' +
|
|
2792
|
+
'/model - Информация о модели\n' +
|
|
2793
|
+
'/setmodel <название> - Сменить модель\n' +
|
|
2794
|
+
'/help - Все команды бота'
|
|
2767
2795
|
);
|
|
2768
2796
|
|
|
2769
2797
|
logInfo(`✅ LLM Chat включен для пользователя ${chatId} (сохранено)`);
|
|
2770
2798
|
} else {
|
|
2771
2799
|
await sendMessage(chatId,
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2800
|
+
'❌ <b>LLM чат выключен</b>\n\n' +
|
|
2801
|
+
'🔧 Возвращен в режим управления ботом.\n' +
|
|
2802
|
+
'💾 Настройка сохранена\n' +
|
|
2803
|
+
'Используйте /togglechat чтобы включить снова.'
|
|
2776
2804
|
);
|
|
2777
2805
|
|
|
2778
2806
|
logInfo(`❌ LLM Chat выключен для пользователя ${chatId} (сохранено)`);
|
|
@@ -2786,15 +2814,15 @@ async function showLLMChatStatus(chatId) {
|
|
|
2786
2814
|
const status = llmChatEnabled ? '✅ Включен' : '❌ Выключен';
|
|
2787
2815
|
const model = getModelForChat(chatId);
|
|
2788
2816
|
const context = chatContexts.get(chatId) || [];
|
|
2789
|
-
|
|
2817
|
+
|
|
2790
2818
|
await sendMessage(chatId,
|
|
2791
|
-
|
|
2819
|
+
'📊 <b>Состояние LLM чата</b>\n\n' +
|
|
2792
2820
|
`🔧 Статус: ${status}\n` +
|
|
2793
2821
|
`🤖 Модель: <code>${model}</code>\n` +
|
|
2794
2822
|
`💬 Сообщений в контексте: ${context.length}\n\n` +
|
|
2795
2823
|
`💡 Используйте /togglechat чтобы ${llmChatEnabled ? 'выключить' : 'включить'} LLM чат`
|
|
2796
2824
|
);
|
|
2797
|
-
|
|
2825
|
+
|
|
2798
2826
|
logInfo(`📊 Проверка статуса LLM чата: ${llmChatEnabled ? 'включен' : 'выключен'}`);
|
|
2799
2827
|
}
|
|
2800
2828
|
|
|
@@ -2803,44 +2831,44 @@ async function showLLMChatStatus(chatId) {
|
|
|
2803
2831
|
*/
|
|
2804
2832
|
async function handleSetModel(chatId, text) {
|
|
2805
2833
|
const parts = text.trim().split(/\s+/);
|
|
2806
|
-
|
|
2834
|
+
|
|
2807
2835
|
// Если нет аргумента - показываем текущую модель
|
|
2808
2836
|
if (parts.length < 2) {
|
|
2809
2837
|
await showModelInfo(chatId);
|
|
2810
2838
|
return;
|
|
2811
2839
|
}
|
|
2812
|
-
|
|
2840
|
+
|
|
2813
2841
|
const requestedModel = parts[1].trim();
|
|
2814
2842
|
const { getAvailableModelsFromFile } = await import('../api/chat.js');
|
|
2815
2843
|
const availableModels = getAvailableModelsFromFile();
|
|
2816
|
-
|
|
2844
|
+
|
|
2817
2845
|
// Проверяем что модель существует
|
|
2818
2846
|
if (!availableModels.includes(requestedModel)) {
|
|
2819
2847
|
await sendMessage(chatId,
|
|
2820
|
-
|
|
2848
|
+
'❌ <b>Модель не найдена</b>\n\n' +
|
|
2821
2849
|
`Модель <code>${requestedModel}</code> не найдена в списке доступных.\n\n` +
|
|
2822
|
-
|
|
2850
|
+
'<b>Используйте /model для списка доступных моделей</b>'
|
|
2823
2851
|
);
|
|
2824
2852
|
logWarn(`❌ Пользователь ${chatId} попытался установить несуществующую модель: ${requestedModel}`);
|
|
2825
2853
|
return;
|
|
2826
2854
|
}
|
|
2827
|
-
|
|
2855
|
+
|
|
2828
2856
|
// Устанавливаем глобальную активную модель
|
|
2829
2857
|
activeModel = requestedModel;
|
|
2830
|
-
|
|
2858
|
+
|
|
2831
2859
|
// Сохраняем в файл
|
|
2832
2860
|
const settings = loadBotSettings();
|
|
2833
2861
|
settings.activeModel = requestedModel;
|
|
2834
2862
|
saveBotSettings(settings);
|
|
2835
|
-
|
|
2863
|
+
|
|
2836
2864
|
await sendMessage(chatId,
|
|
2837
|
-
|
|
2865
|
+
'✅ <b>Модель изменена!</b>\n\n' +
|
|
2838
2866
|
`🤖 Новая модель: <code>${requestedModel}</code>\n` +
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2867
|
+
'💬 Будет использоваться во всех чатах\n' +
|
|
2868
|
+
'💾 Настройка сохранена\n\n' +
|
|
2869
|
+
'💡 Для сброса используйте /clear'
|
|
2842
2870
|
);
|
|
2843
|
-
|
|
2871
|
+
|
|
2844
2872
|
logInfo(`✅ Установлена глобальная модель: ${requestedModel} (сохранено)`);
|
|
2845
2873
|
}
|
|
2846
2874
|
|
|
@@ -2853,7 +2881,7 @@ function getModelForChat(chatId) {
|
|
|
2853
2881
|
if (activeModel) {
|
|
2854
2882
|
return activeModel;
|
|
2855
2883
|
}
|
|
2856
|
-
|
|
2884
|
+
|
|
2857
2885
|
// Возвращаем модель из настроек бота
|
|
2858
2886
|
return getBotSettingsModel();
|
|
2859
2887
|
}
|
|
@@ -2877,15 +2905,15 @@ async function showModelInfo(chatId) {
|
|
|
2877
2905
|
const availableModels = getAvailableModelsFromFile();
|
|
2878
2906
|
|
|
2879
2907
|
const message =
|
|
2880
|
-
|
|
2908
|
+
'📊 <b>Информация о модели</b>\n\n' +
|
|
2881
2909
|
`🤖 Активная модель: <code>${currentModel}</code>\n` +
|
|
2882
2910
|
`💬 Сообщений в контексте: ${context.length}\n` +
|
|
2883
2911
|
`🔧 LLM чат: ${llmChatEnabled ? '✅ Включен' : '❌ Выключен'}\n\n` +
|
|
2884
|
-
|
|
2885
|
-
availableModels.map(m => `<code>${m}</code>`).join(', ') +
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2912
|
+
'<b>Доступные модели:</b>\n' +
|
|
2913
|
+
availableModels.map((m) => `<code>${m}</code>`).join(', ') +
|
|
2914
|
+
'\n\n💡 Для смены модели используйте:\n' +
|
|
2915
|
+
'/setmodel <название_модели>\n' +
|
|
2916
|
+
'Например: /setmodel qwen3-max';
|
|
2889
2917
|
|
|
2890
2918
|
await sendMessage(chatId, message);
|
|
2891
2919
|
}
|
|
@@ -2897,8 +2925,8 @@ async function clearChatContext(chatId) {
|
|
|
2897
2925
|
chatContexts.set(chatId, []);
|
|
2898
2926
|
|
|
2899
2927
|
await sendMessage(chatId,
|
|
2900
|
-
|
|
2901
|
-
|
|
2928
|
+
'🗑️ <b>Контекст чата очищен</b>\n\n' +
|
|
2929
|
+
'💬 Новая история чата начата.\n' +
|
|
2902
2930
|
`🤖 LLM чат: ${llmChatEnabled ? '✅ Включен' : '❌ Выключен'}`
|
|
2903
2931
|
);
|
|
2904
2932
|
|
|
@@ -2958,7 +2986,7 @@ function splitMessage(text, maxLength) {
|
|
|
2958
2986
|
* Экранирует HTML специальные символы
|
|
2959
2987
|
*/
|
|
2960
2988
|
function escapeHtml(text) {
|
|
2961
|
-
if (!text) return '';
|
|
2989
|
+
if (!text) { return ''; }
|
|
2962
2990
|
return String(text)
|
|
2963
2991
|
.replace(/&/g, '&')
|
|
2964
2992
|
.replace(/</g, '<')
|
|
@@ -2969,7 +2997,7 @@ function escapeHtml(text) {
|
|
|
2969
2997
|
* Экранирует текст для вставки в <pre> тег (только < и &)
|
|
2970
2998
|
*/
|
|
2971
2999
|
function escapeHtmlForCode(text) {
|
|
2972
|
-
if (!text) return '';
|
|
3000
|
+
if (!text) { return ''; }
|
|
2973
3001
|
return String(text)
|
|
2974
3002
|
.replace(/&/g, '&')
|
|
2975
3003
|
.replace(/</g, '<')
|