qwen-api-proxy 1.0.12 → 1.0.13
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 +656 -654
- 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 успешен, запускаем перезапуск сервера');
|
|
@@ -1568,27 +1568,27 @@ async function extractZip(zipPath, sessionPath, chatId) {
|
|
|
1568
1568
|
const zipEntries = zip.getEntries();
|
|
1569
1569
|
|
|
1570
1570
|
// Для отладки: показываем первые несколько записей в архиве
|
|
1571
|
-
const firstEntries = zipEntries.slice(0, 20).map(e => e.entryName);
|
|
1572
|
-
logInfo(
|
|
1571
|
+
const firstEntries = zipEntries.slice(0, 20).map((e) => e.entryName);
|
|
1572
|
+
logInfo('📂 Первые 20 записей в ZIP архиве:');
|
|
1573
1573
|
logInfo(firstEntries.join('\n'));
|
|
1574
1574
|
|
|
1575
1575
|
// Проверяем что есть папка session
|
|
1576
|
-
const hasSessionFolder = zipEntries.some(entry =>
|
|
1576
|
+
const hasSessionFolder = zipEntries.some((entry) =>
|
|
1577
1577
|
entry.entryName.startsWith('session/') || entry.entryName === 'session'
|
|
1578
1578
|
);
|
|
1579
1579
|
|
|
1580
1580
|
if (!hasSessionFolder) {
|
|
1581
1581
|
// Пытаемся найти session в любом месте архива
|
|
1582
|
-
const sessionEntries = zipEntries.filter(e =>
|
|
1582
|
+
const sessionEntries = zipEntries.filter((e) =>
|
|
1583
1583
|
e.entryName.includes('session') || e.entryName.includes('Session')
|
|
1584
1584
|
);
|
|
1585
1585
|
|
|
1586
1586
|
if (sessionEntries.length > 0) {
|
|
1587
|
-
logInfo(
|
|
1588
|
-
logInfo(sessionEntries.slice(0, 10).map(e => e.entryName).join('\n'));
|
|
1587
|
+
logInfo('🔍 Найдены записи содержащие \'session\':');
|
|
1588
|
+
logInfo(sessionEntries.slice(0, 10).map((e) => e.entryName).join('\n'));
|
|
1589
1589
|
reject(new Error(
|
|
1590
1590
|
`Архив содержит '${sessionEntries[0].entryName}', но не содержит 'session/' в корне. ` +
|
|
1591
|
-
|
|
1591
|
+
'Переместите папку session/ в корень архива'
|
|
1592
1592
|
));
|
|
1593
1593
|
} else {
|
|
1594
1594
|
reject(new Error('Архив не содержит папку "session". Проверите что архив содержит папку session/ в корне'));
|
|
@@ -1607,7 +1607,7 @@ async function extractZip(zipPath, sessionPath, chatId) {
|
|
|
1607
1607
|
let successCount = 0;
|
|
1608
1608
|
let errorCount = 0;
|
|
1609
1609
|
|
|
1610
|
-
zipEntries.forEach(entry => {
|
|
1610
|
+
zipEntries.forEach((entry) => {
|
|
1611
1611
|
if (entry.entryName.startsWith('session/')) {
|
|
1612
1612
|
try {
|
|
1613
1613
|
const relativePath = entry.entryName.substring('session/'.length);
|
|
@@ -1753,7 +1753,7 @@ async function extract7z(sevenZPath, sessionPath, chatId) {
|
|
|
1753
1753
|
const items = fs.readdirSync(sourceDir);
|
|
1754
1754
|
logInfo(`📂 Найдено элементов для копирования: ${items.length}`);
|
|
1755
1755
|
|
|
1756
|
-
items.forEach(item => {
|
|
1756
|
+
items.forEach((item) => {
|
|
1757
1757
|
const sourcePath = path.join(sourceDir, item);
|
|
1758
1758
|
const targetPath = path.join(targetDir, item);
|
|
1759
1759
|
|
|
@@ -1819,8 +1819,8 @@ async function gracefulRestart(chatId) {
|
|
|
1819
1819
|
logInfo('🔄 Запуск корректного перезапуска сервиса...');
|
|
1820
1820
|
|
|
1821
1821
|
await sendMessage(chatId,
|
|
1822
|
-
|
|
1823
|
-
|
|
1822
|
+
'🔄 <b>Перезапуск сервиса...</b>\n\n' +
|
|
1823
|
+
'⏱️ Сервис будет перезапущен в течение 5 секунд'
|
|
1824
1824
|
);
|
|
1825
1825
|
|
|
1826
1826
|
// Даем Docker Compose время на перезапуск
|
|
@@ -1854,51 +1854,51 @@ async function sendHelpMessage(chatId) {
|
|
|
1854
1854
|
const accountsExist = hasAccounts();
|
|
1855
1855
|
|
|
1856
1856
|
let helpText =
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1857
|
+
'🤖 <b>FreeQwenApi Bot - Управление</b>\n\n' +
|
|
1858
|
+
'📋 <b>Команды управления:</b>\n\n' +
|
|
1859
|
+
'/help - Показать это сообщение\n' +
|
|
1860
|
+
'/status - Показать статус сервиса\n' +
|
|
1861
|
+
'/restart - Перезапустить сервис\n' +
|
|
1862
|
+
'<s>/extend</s> - 🔧 Временно отключено\n\n';
|
|
1863
1863
|
|
|
1864
1864
|
// Команды генерации изображений
|
|
1865
1865
|
helpText +=
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1866
|
+
'🎨 <b>Генерация изображений:</b>\n\n' +
|
|
1867
|
+
'/image <описание> - Сгенерировать изображение\n' +
|
|
1868
|
+
'/imagine <описание> - Альтернативная команда\n\n' +
|
|
1869
|
+
'💡 Пример: /image A beautiful sunset over the ocean\n\n';
|
|
1870
1870
|
|
|
1871
1871
|
// Показываем LLM команды только если есть аккаунты
|
|
1872
1872
|
if (accountsExist) {
|
|
1873
1873
|
helpText +=
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1874
|
+
'🤖 <b>LLM Чат (AI ассистент):</b>\n\n' +
|
|
1875
|
+
'/chat - Показать состояние LLM чата\n' +
|
|
1876
|
+
'/togglechat - Включить/выключить LLM чат\n' +
|
|
1877
|
+
'/clear - Очистить контекст чата\n' +
|
|
1878
|
+
'/model - Информация о модели\n' +
|
|
1879
|
+
'/setmodel <название> - Сменить модель\n\n' +
|
|
1880
|
+
'💡 Когда LLM чат включен, просто отправляйте сообщения!\n\n';
|
|
1881
1881
|
} else {
|
|
1882
1882
|
helpText +=
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1883
|
+
'⚠️ <b>LLM Чат недоступен</b>\n\n' +
|
|
1884
|
+
'🔒 Функции AI ассистента временно недоступны\n' +
|
|
1885
|
+
'📦 Отправьте архив с сессиями для активации\n\n';
|
|
1886
1886
|
}
|
|
1887
1887
|
|
|
1888
1888
|
helpText +=
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1889
|
+
'📦 <b>Загрузка сессий:</b>\n\n' +
|
|
1890
|
+
'Отправьте ZIP или 7z архив с папкой "session" внутри.\n' +
|
|
1891
|
+
'Бот распакует его и перезапустит сервис.\n\n' +
|
|
1892
|
+
'/archive - Инструкция по созданию архива\n\n' +
|
|
1893
|
+
'📏 <b>Лимиты:</b>\n' +
|
|
1894
|
+
'• Максимальный размер файла: 50MB\n' +
|
|
1895
|
+
'• Поддерживаемые форматы: .zip, .7z\n\n' +
|
|
1896
|
+
'📚 <b>Дополнительные команды:</b>\n\n' +
|
|
1897
|
+
'/setup - Инструкция по созданию сессии\n' +
|
|
1898
|
+
'/connect - Как подключить к проекту\n' +
|
|
1899
|
+
'/about - Информация о проекте\n\n' +
|
|
1900
|
+
'🐳 <b>Docker:</b>\n' +
|
|
1901
|
+
'https://hub.docker.com/r/endykaufman/qwen-api-proxy';
|
|
1902
1902
|
|
|
1903
1903
|
await sendMessage(chatId, helpText);
|
|
1904
1904
|
}
|
|
@@ -1908,179 +1908,179 @@ async function sendHelpMessage(chatId) {
|
|
|
1908
1908
|
*/
|
|
1909
1909
|
async function sendSetupMessage(chatId) {
|
|
1910
1910
|
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
|
-
|
|
1911
|
+
'🛠️ <b>Создание сессии авторизации</b>\n\n' +
|
|
1912
|
+
'<b>📖 Что нужно знать:</b>\n' +
|
|
1913
|
+
'• <b>Git</b> - система управления версиями (опционально)\n' +
|
|
1914
|
+
'• <b>Docker Compose</b> - инструмент для управления контейнерами\n' +
|
|
1915
|
+
'• Если используете Docker Desktop - Compose уже встроен!\n\n' +
|
|
1916
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
|
|
1917
|
+
'<b>Способ 1: Локальная установка (Node.js)</b>\n\n' +
|
|
1918
|
+
'<b>Вариант A: С Git:</b>\n' +
|
|
1919
|
+
'1. <code>git clone https://github.com/EndyKaufman/FreeQwenApi</code>\n' +
|
|
1920
|
+
'2. <code>cd FreeQwenApi</code>\n' +
|
|
1921
|
+
'3. <code>npm install</code>\n' +
|
|
1922
|
+
'4. <code>npm start</code>\n' +
|
|
1923
|
+
'5. Выберите <code>1</code> - добавить аккаунт\n' +
|
|
1924
|
+
'6. Войдите в аккаунт Qwen в браузере\n' +
|
|
1925
|
+
'7. Токен сохранится автоматически\n\n' +
|
|
1926
|
+
'<b>Вариант B: Без Git (ZIP):</b>\n' +
|
|
1927
|
+
'1. Скачайте ZIP: https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
1928
|
+
'2. Нажмите <b>"<> Code"</b> → <b>"Download ZIP"</b>\n' +
|
|
1929
|
+
'3. Распакуйте и перейдите в папку\n' +
|
|
1930
|
+
'4. <code>npm install</code>\n' +
|
|
1931
|
+
'5. <code>npm start</code> → выберите <code>1</code>\n\n' +
|
|
1932
|
+
'<b>Способ 2: Docker</b>\n\n' +
|
|
1933
|
+
'<b>Что такое Docker Compose?</b>\n' +
|
|
1934
|
+
'• Входит в Docker Desktop (Windows/macOS)\n' +
|
|
1935
|
+
'• Linux: <code>sudo apt install docker-compose-plugin</code>\n' +
|
|
1936
|
+
'• Проверка: <code>docker compose version</code>\n\n' +
|
|
1937
|
+
'<b>С Compose:</b>\n' +
|
|
1938
|
+
'1. Сначала создайте сессию локально:\n' +
|
|
1939
|
+
' <code>npm run auth</code> (или <code>npm start</code> → <code>1</code>)\n' +
|
|
1940
|
+
'2. Соберите Docker:\n' +
|
|
1941
|
+
' <code>docker compose build --no-cache</code>\n' +
|
|
1942
|
+
'3. Запустите:\n' +
|
|
1943
|
+
' <code>docker compose up -d</code>\n\n' +
|
|
1944
|
+
'<b>Без Compose (обычный Docker):</b>\n' +
|
|
1945
|
+
'1. Создайте сессию локально (см. Способ 1)\n' +
|
|
1946
|
+
'2. Соберите образ:\n' +
|
|
1947
|
+
' <code>docker build -t qwen-proxy .</code>\n' +
|
|
1948
|
+
'3. Запустите:\n' +
|
|
1949
|
+
' <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' +
|
|
1950
|
+
'<b>Структура папки session:</b>\n' +
|
|
1951
|
+
'<code>session/</code>\n' +
|
|
1952
|
+
'├── <code>accounts/</code>\n' +
|
|
1953
|
+
'│ ├── <code>acc_123456/</code>\n' +
|
|
1954
|
+
'│ │ └── <code>token.txt</code>\n' +
|
|
1955
|
+
'│ └── <code>acc_789012/</code>\n' +
|
|
1956
|
+
'│ └── <code>token.txt</code>\n' +
|
|
1957
|
+
'└── <code>tokens.json</code>\n\n' +
|
|
1958
|
+
'💡 <b>Совет:</b> Используйте /archive для подробной инструкции\n\n' +
|
|
1959
|
+
'📖 Подробнее: https://github.com/EndyKaufman/FreeQwenApi';
|
|
1960
|
+
|
|
1961
1961
|
await sendMessage(chatId, setupText);
|
|
1962
1962
|
}
|
|
1963
1963
|
|
|
1964
1964
|
async function sendConnectMessage(chatId) {
|
|
1965
1965
|
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
|
-
|
|
1966
|
+
'🔌 <b>Подключение FreeQwenApi к проекту</b>\n\n' +
|
|
1967
|
+
'<b>📖 Что такое Docker Compose?</b>\n' +
|
|
1968
|
+
'Это инструмент для запуска многоконтейнерных приложений.\n' +
|
|
1969
|
+
'Он управляет контейнерами через файл <code>docker-compose.yml</code>.\n\n' +
|
|
1970
|
+
'<b>Установка Docker Compose:</b>\n' +
|
|
1971
|
+
'• Входит в <b>Docker Desktop</b> (Windows/macOS)\n' +
|
|
1972
|
+
'• Linux: <code>sudo apt install docker-compose-plugin</code>\n' +
|
|
1973
|
+
'• Проверка: <code>docker compose version</code>\n\n' +
|
|
1974
|
+
'💡 <i>Если Docker Desktop установлен - Compose уже есть!</i>\n\n' +
|
|
1975
|
+
'<b>━━━━━━━━━━━━━━━━━━━━━━━━━</b>\n\n' +
|
|
1976
|
+
'<b>Шаг 1: Запуск через Docker Compose</b>\n\n' +
|
|
1977
|
+
'Добавьте в ваш <code>docker-compose.yml</code>:\n\n' +
|
|
1978
|
+
'<pre>\n' +
|
|
1979
|
+
'services:\n' +
|
|
1980
|
+
' qwen-proxy:\n' +
|
|
1981
|
+
' build: .\n' +
|
|
1982
|
+
' container_name: qwen-proxy\n' +
|
|
1983
|
+
' environment:\n' +
|
|
1984
|
+
' - SKIP_ACCOUNT_MENU=true\n' +
|
|
1985
|
+
' - PORT=3264\n' +
|
|
1986
|
+
' ports:\n' +
|
|
1987
|
+
' - "3264:3264"\n' +
|
|
1988
|
+
' volumes:\n' +
|
|
1989
|
+
' - ./session_backup:/app/session_backup\n' +
|
|
1990
|
+
' - ./session:/app/session\n' +
|
|
1991
|
+
' - ./logs:/app/logs\n' +
|
|
1992
|
+
' - ./uploads:/app/uploads\n' +
|
|
1993
|
+
' - ./temp:/app/temp\n' +
|
|
1994
|
+
' restart: unless-stopped\n' +
|
|
1995
|
+
'</pre>\n\n' +
|
|
1996
|
+
'Или используйте наш <code>docker-compose.yml</code>:\n' +
|
|
1997
|
+
'<code>docker compose up -d</code>\n\n' +
|
|
1998
|
+
'<b>Альтернатива: Без Docker Compose</b>\n\n' +
|
|
1999
|
+
'Если Compose не установлен, используйте обычный Docker:\n\n' +
|
|
2000
|
+
'<pre>\n' +
|
|
2001
|
+
'docker build -t qwen-proxy .\n' +
|
|
2002
|
+
'docker run -d \\\n' +
|
|
2003
|
+
' --name qwen-proxy \\\n' +
|
|
2004
|
+
' -p 3264:3264 \\\n' +
|
|
2005
|
+
' -e SKIP_ACCOUNT_MENU=true \\\n' +
|
|
2006
|
+
' -v $(pwd)/session:/app/session \\\n' +
|
|
2007
|
+
' -v $(pwd)/logs:/app/logs \\\n' +
|
|
2008
|
+
' -v $(pwd)/uploads:/app/uploads \\\n' +
|
|
2009
|
+
' -v $(pwd)/temp:/app/temp \\\n' +
|
|
2010
|
+
' qwen-proxy\n' +
|
|
2011
|
+
'</pre>\n\n' +
|
|
2012
|
+
'<b>━━━━━━━━━━━━━━━━━━━━━━━━━</b>\n\n' +
|
|
2013
|
+
'<b>Шаг 2: Первый запрос через curl</b>\n\n' +
|
|
2014
|
+
'<b>Простой запрос:</b>\n' +
|
|
2015
|
+
'<pre>\n' +
|
|
2016
|
+
'curl http://localhost:3264/api/chat/completions \\\n' +
|
|
2017
|
+
' -H "Content-Type: application/json" \\\n' +
|
|
2018
|
+
' -d \'{"model":"qwen3.5-plus","messages":[{"role":"user","content":"Привет!"}]}\'\n' +
|
|
2019
|
+
'</pre>\n\n' +
|
|
2020
|
+
'<b>С продолжением диалога:</b>\n' +
|
|
2021
|
+
'<pre>\n' +
|
|
2022
|
+
'curl -X POST http://localhost:3264/api/chat/completions \\\n' +
|
|
2023
|
+
' -H "Content-Type: application/json" \\\n' +
|
|
2024
|
+
' -d \'{\n' +
|
|
2025
|
+
' "model": "qwen3.5-plus",\n' +
|
|
2026
|
+
' "messages": [{"role": "user", "content": "Сколько будет 2+2?"}]\n' +
|
|
2027
|
+
' }\'\n' +
|
|
2028
|
+
'</pre>\n\n' +
|
|
2029
|
+
'<b>Шаг 3: Использование с OpenAI SDK</b>\n\n' +
|
|
2030
|
+
'<pre>\n' +
|
|
2031
|
+
'import OpenAI from \'openai\';\n\n' +
|
|
2032
|
+
'const client = new OpenAI({\n' +
|
|
2033
|
+
' baseURL: \'http://localhost:3264/api\',\n' +
|
|
2034
|
+
' apiKey: \'any-string\'\n' +
|
|
2035
|
+
'});\n\n' +
|
|
2036
|
+
'const response = await client.chat.completions.create({\n' +
|
|
2037
|
+
' model: \'qwen3.5-plus\',\n' +
|
|
2038
|
+
' messages: [{ role: \'user\', content: \'Привет!\' }]\n' +
|
|
2039
|
+
'});\n' +
|
|
2040
|
+
'</pre>\n\n' +
|
|
2041
|
+
'📖 API Docs: http://localhost:3264/api\n' +
|
|
2042
|
+
'📚 GitHub: https://github.com/EndyKaufman/FreeQwenApi';
|
|
2043
|
+
|
|
2044
2044
|
await sendMessage(chatId, connectText);
|
|
2045
2045
|
}
|
|
2046
2046
|
|
|
2047
2047
|
async function sendAboutMessage(chatId) {
|
|
2048
2048
|
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
|
-
|
|
2049
|
+
'📚 <b>О проекте FreeQwenApi</b>\n\n' +
|
|
2050
|
+
'<b>🌐 Оригинальный проект:</b>\n' +
|
|
2051
|
+
'https://github.com/y13sint/FreeQwenApi\n\n' +
|
|
2052
|
+
'<b>🔧 Мой форк:</b>\n' +
|
|
2053
|
+
'https://github.com/EndyKaufman/FreeQwenApi\n\n' +
|
|
2054
|
+
'<b>⭐ Ключевые отличия форка:</b>\n\n' +
|
|
2055
|
+
'✅ <b>Telegram Bot интеграция</b>\n' +
|
|
2056
|
+
' - Управление сервисом через Telegram\n' +
|
|
2057
|
+
' - Загрузка сессий архивами (.zip/.7z)\n' +
|
|
2058
|
+
' - LLM чат с AI ассистентом\n' +
|
|
2059
|
+
' - Мониторинг статуса в реальном времени\n\n' +
|
|
2060
|
+
'✅ <b>Прокси поддержка для Telegram</b>\n' +
|
|
2061
|
+
' - HTTP/HTTPS/SOCKS прокси\n' +
|
|
2062
|
+
' - Безопасное логирование (без credentials)\n\n' +
|
|
2063
|
+
'✅ <b>Автоматическая распаковка архивов</b>\n' +
|
|
2064
|
+
' - Backup перед обновлением\n' +
|
|
2065
|
+
' - Error-tolerant extraction\n' +
|
|
2066
|
+
' - Health check при запуске\n\n' +
|
|
2067
|
+
'✅ <b>Улучшенная документация</b>\n' +
|
|
2068
|
+
' - Подробные README на русском\n' +
|
|
2069
|
+
' - Telegram bot guides\n' +
|
|
2070
|
+
'✅ <b>Production-ready features</b>\n' +
|
|
2071
|
+
' - Docker оптимизация\n' +
|
|
2072
|
+
' - Graceful restarts\n' +
|
|
2073
|
+
' - System health monitoring\n\n' +
|
|
2074
|
+
'<b>📊 Общие возможности:</b>\n' +
|
|
2075
|
+
'• 25+ моделей Qwen (включая Qwen 3.5)\n' +
|
|
2076
|
+
'• OpenAI-совместимый API\n' +
|
|
2077
|
+
'• Генерация изображений\n' +
|
|
2078
|
+
'• Загрузка файлов\n' +
|
|
2079
|
+
'• Streaming ответов (SSE)\n' +
|
|
2080
|
+
'• Мультиаккаунт ротация\n' +
|
|
2081
|
+
'• Бесплатный доступ к Qwen AI\n\n' +
|
|
2082
|
+
'💡 <i>Оба проекта используют MIT лицензию</i>';
|
|
2083
|
+
|
|
2084
2084
|
await sendMessage(chatId, aboutText);
|
|
2085
2085
|
}
|
|
2086
2086
|
|
|
@@ -2089,81 +2089,81 @@ async function sendAboutMessage(chatId) {
|
|
|
2089
2089
|
*/
|
|
2090
2090
|
async function sendArchiveInstructions(chatId) {
|
|
2091
2091
|
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
|
-
|
|
2092
|
+
'📦 <b>Создание архива сессии для Docker</b>\n\n' +
|
|
2093
|
+
'Эта инструкция поможет создать архив с авторизацией\n' +
|
|
2094
|
+
'для последующей загрузки в Telegram бота.\n\n' +
|
|
2095
|
+
'<b>🔹 Шаг 1: Установка Node.js</b>\n\n' +
|
|
2096
|
+
'<b>Windows:</b>\n' +
|
|
2097
|
+
'• Скачайте с <code>nodejs.org</code>\n' +
|
|
2098
|
+
'• Установите (галочка "Add to PATH")\n\n' +
|
|
2099
|
+
'<b>macOS:</b>\n' +
|
|
2100
|
+
'• <code>brew install node</code>\n' +
|
|
2101
|
+
'• Или скачайте с <code>nodejs.org</code>\n\n' +
|
|
2102
|
+
'<b>Linux (Ubuntu/Debian):</b>\n' +
|
|
2103
|
+
'• <code>curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -</code>\n' +
|
|
2104
|
+
'• <code>sudo apt install -y nodejs</code>\n\n' +
|
|
2105
|
+
'<b>🔹 Шаг 2: Скачивание проекта</b>\n\n' +
|
|
2106
|
+
'<b>Способ A: Git (если установлен):</b>\n' +
|
|
2107
|
+
'<pre>\n' +
|
|
2108
|
+
'git clone https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
2109
|
+
'cd FreeQwenApi\n' +
|
|
2110
|
+
'npm install\n' +
|
|
2111
|
+
'</pre>\n\n' +
|
|
2112
|
+
'<b>Способ B: Без Git (ZIP архив):</b>\n' +
|
|
2113
|
+
'1. Откройте: https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
2114
|
+
'2. Нажмите зелёную кнопку <b>"<> Code"</b>\n' +
|
|
2115
|
+
'3. Выберите <b>"Download ZIP"</b>\n' +
|
|
2116
|
+
'4. Распакуйте архив\n' +
|
|
2117
|
+
'5. Откройте терминал в папке проекта\n' +
|
|
2118
|
+
'6. <code>npm install</code>\n\n' +
|
|
2119
|
+
'💡 <i>Этот способ не требует установки Git!</i>\n\n' +
|
|
2120
|
+
'<b>🔹 Шаг 3: Создание архива сессии</b>\n\n' +
|
|
2121
|
+
'<pre>\n' +
|
|
2122
|
+
'npm run create-session-archive\n' +
|
|
2123
|
+
'</pre>\n\n' +
|
|
2124
|
+
'<b>Что произойдет:</b>\n' +
|
|
2125
|
+
'1. Откроется браузер\n' +
|
|
2126
|
+
'2. Войдите в Qwen (GitHub/Google/email)\n' +
|
|
2127
|
+
'3. Нажмите ENTER в консоли\n' +
|
|
2128
|
+
'4. Сессия сохранится\n' +
|
|
2129
|
+
'5. Создется ZIP архив\n\n' +
|
|
2130
|
+
'<b>🔹 Шаг 4: Отправка в Telegram бота</b>\n\n' +
|
|
2131
|
+
'1. Откройте нашего бота\n' +
|
|
2132
|
+
'2. Нажмите 📎 (скрепка)\n' +
|
|
2133
|
+
'3. Выберите <b>"Файл"</b> (НЕ "Фото"!)\n' +
|
|
2134
|
+
'4. Выберите <code>session_backup_*.zip</code>\n' +
|
|
2135
|
+
'5. Отправьте\n' +
|
|
2136
|
+
'6. Дождитесь: <code>✅ Архив распакован</code>\n\n' +
|
|
2137
|
+
'<b>🔹 Альтернатива: Ручной способ</b>\n\n' +
|
|
2138
|
+
'Если <code>npm run</code> не работает:\n\n' +
|
|
2139
|
+
'<b>Windows PowerShell:</b>\n' +
|
|
2140
|
+
'<pre>\n' +
|
|
2141
|
+
'node scripts/createSessionArchive.js\n' +
|
|
2142
|
+
'</pre>\n\n' +
|
|
2143
|
+
'<b>Linux/macOS:</b>\n' +
|
|
2144
|
+
'<pre>\n' +
|
|
2145
|
+
'node scripts/createSessionArchive.js\n' +
|
|
2146
|
+
'</pre>\n\n' +
|
|
2147
|
+
'<b>🔹 Структура архива:</b>\n\n' +
|
|
2148
|
+
'<pre>\n' +
|
|
2149
|
+
'session/\n' +
|
|
2150
|
+
'├── accounts/\n' +
|
|
2151
|
+
'│ ├── acc_123456/\n' +
|
|
2152
|
+
'│ │ ├── token.txt\n' +
|
|
2153
|
+
'│ │ └── cookies.json\n' +
|
|
2154
|
+
'│ └── acc_789012/\n' +
|
|
2155
|
+
'│ ├── token.txt\n' +
|
|
2156
|
+
'│ └── cookies.json\n' +
|
|
2157
|
+
'└── tokens.json\n' +
|
|
2158
|
+
'</pre>\n\n' +
|
|
2159
|
+
'<b>⚠️ Важно:</b>\n' +
|
|
2160
|
+
'• <code>cookies.json</code> обязателен!\n' +
|
|
2161
|
+
'• Без cookies сессия не продлится\n' +
|
|
2162
|
+
'• Архив должен содержать папку <code>session/</code>\n\n' +
|
|
2163
|
+
'<b>🆘 Проблемы?</b>\n' +
|
|
2164
|
+
'• GitHub: https://github.com/EndyKaufman/FreeQwenApi\n' +
|
|
2165
|
+
'• Используйте /help для списка команд';
|
|
2166
|
+
|
|
2167
2167
|
await sendMessage(chatId, archiveText);
|
|
2168
2168
|
}
|
|
2169
2169
|
|
|
@@ -2172,92 +2172,92 @@ async function sendStatusMessage(chatId, isScheduled = false) {
|
|
|
2172
2172
|
// Получаем статус бота
|
|
2173
2173
|
const telegramToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
2174
2174
|
const botStarted = !!telegramToken;
|
|
2175
|
-
|
|
2175
|
+
|
|
2176
2176
|
// Проверяем все подсистемы (не отправляем автоматически, так как sendStatusMessage отправит сам)
|
|
2177
2177
|
const checks = await checkAllSubsystems(botStarted, false);
|
|
2178
|
-
|
|
2178
|
+
|
|
2179
2179
|
// Формируем сообщение с统一的格式
|
|
2180
2180
|
const reportLines = [];
|
|
2181
2181
|
|
|
2182
2182
|
// Заголовок
|
|
2183
2183
|
if (isScheduled) {
|
|
2184
2184
|
const now = new Date();
|
|
2185
|
-
const timeStr = now.toLocaleString('ru-RU', {
|
|
2186
|
-
day: '2-digit',
|
|
2187
|
-
month: '2-digit',
|
|
2185
|
+
const timeStr = now.toLocaleString('ru-RU', {
|
|
2186
|
+
day: '2-digit',
|
|
2187
|
+
month: '2-digit',
|
|
2188
2188
|
year: 'numeric',
|
|
2189
|
-
hour: '2-digit',
|
|
2190
|
-
minute: '2-digit'
|
|
2189
|
+
hour: '2-digit',
|
|
2190
|
+
minute: '2-digit'
|
|
2191
2191
|
});
|
|
2192
2192
|
reportLines.push(`⏰ <b>Плановая проверка</b> (${timeStr})\n`);
|
|
2193
2193
|
} else {
|
|
2194
|
-
reportLines.push(
|
|
2194
|
+
reportLines.push('🚀 <b>Сервис запущен!</b>\n');
|
|
2195
2195
|
}
|
|
2196
2196
|
|
|
2197
2197
|
// Группа 1: Основные компоненты
|
|
2198
|
-
reportLines.push(
|
|
2199
|
-
const mainComponents = checks.filter(c =>
|
|
2200
|
-
c.name.includes('Session') || c.name.includes('Токены') || c.name.includes('Telegram') ||
|
|
2198
|
+
reportLines.push('<b>🔑 Основные компоненты:</b>');
|
|
2199
|
+
const mainComponents = checks.filter((c) =>
|
|
2200
|
+
c.name.includes('Session') || c.name.includes('Токены') || c.name.includes('Telegram') ||
|
|
2201
2201
|
c.name.includes('Токен ') || c.name.includes('AI') || c.name.includes('Ответ')
|
|
2202
2202
|
);
|
|
2203
|
-
mainComponents.forEach(check => {
|
|
2203
|
+
mainComponents.forEach((check) => {
|
|
2204
2204
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
2205
2205
|
});
|
|
2206
2206
|
|
|
2207
2207
|
reportLines.push('');
|
|
2208
2208
|
|
|
2209
2209
|
// Группа 2: Инфраструктура
|
|
2210
|
-
reportLines.push(
|
|
2211
|
-
const infrastructure = checks.filter(c =>
|
|
2210
|
+
reportLines.push('<b>🏗️ Инфраструктура:</b>');
|
|
2211
|
+
const infrastructure = checks.filter((c) =>
|
|
2212
2212
|
c.name.includes('Прокси') || c.name.includes('Uploads') || c.name.includes('Логирование')
|
|
2213
2213
|
);
|
|
2214
|
-
infrastructure.forEach(check => {
|
|
2214
|
+
infrastructure.forEach((check) => {
|
|
2215
2215
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
2216
2216
|
});
|
|
2217
2217
|
|
|
2218
2218
|
reportLines.push('');
|
|
2219
2219
|
|
|
2220
2220
|
// Группа 3: Инструменты
|
|
2221
|
-
reportLines.push(
|
|
2222
|
-
const tools = checks.filter(c =>
|
|
2221
|
+
reportLines.push('<b>🔧 Инструменты:</b>');
|
|
2222
|
+
const tools = checks.filter((c) =>
|
|
2223
2223
|
c.name.includes('p7zip')
|
|
2224
2224
|
);
|
|
2225
|
-
tools.forEach(check => {
|
|
2225
|
+
tools.forEach((check) => {
|
|
2226
2226
|
reportLines.push(`${check.name}: ${check.details}`);
|
|
2227
2227
|
});
|
|
2228
2228
|
|
|
2229
2229
|
reportLines.push('');
|
|
2230
|
-
reportLines.push(
|
|
2230
|
+
reportLines.push('━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2231
2231
|
|
|
2232
2232
|
// Итоговый статус
|
|
2233
2233
|
const tokens = loadTokens();
|
|
2234
2234
|
const hasTokens = tokens.length > 0;
|
|
2235
|
-
const allOk = checks.every(c => c.status);
|
|
2236
|
-
|
|
2235
|
+
const allOk = checks.every((c) => c.status);
|
|
2236
|
+
|
|
2237
2237
|
if (hasTokens && allOk) {
|
|
2238
|
-
reportLines.push(
|
|
2238
|
+
reportLines.push('✅ <b>Все системы работают</b>');
|
|
2239
2239
|
} else if (!hasTokens) {
|
|
2240
|
-
reportLines.push(
|
|
2241
|
-
reportLines.push(
|
|
2240
|
+
reportLines.push('⚠️ <b>Режим ожидания архива</b>');
|
|
2241
|
+
reportLines.push('📦 Отправьте архив с сессиями');
|
|
2242
2242
|
} else {
|
|
2243
|
-
reportLines.push(
|
|
2243
|
+
reportLines.push('⚠️ <b>Есть проблемы</b>');
|
|
2244
2244
|
}
|
|
2245
2245
|
|
|
2246
2246
|
// Ссылки
|
|
2247
2247
|
reportLines.push(`\n🌐 API: http://localhost:${process.env.PORT || 3264}`);
|
|
2248
2248
|
reportLines.push(`📖 Docs: http://localhost:${process.env.PORT || 3264}/api`);
|
|
2249
|
-
|
|
2249
|
+
|
|
2250
2250
|
// Репозиторий
|
|
2251
|
-
reportLines.push(
|
|
2252
|
-
reportLines.push(
|
|
2253
|
-
reportLines.push(
|
|
2254
|
-
reportLines.push(
|
|
2255
|
-
|
|
2251
|
+
reportLines.push('\n📚 <b>Репозиторий:</b>');
|
|
2252
|
+
reportLines.push('🔗 GitHub: https://github.com/EndyKaufman/FreeQwenApi');
|
|
2253
|
+
reportLines.push('⭐ Оригинал: https://github.com/y1n7sint/FreeQwenApi');
|
|
2254
|
+
reportLines.push('🐳 Docker: https://hub.docker.com/r/endykaufman/qwen-api-proxy');
|
|
2255
|
+
|
|
2256
2256
|
// Справка
|
|
2257
|
-
reportLines.push(
|
|
2258
|
-
reportLines.push(
|
|
2259
|
-
reportLines.push(
|
|
2260
|
-
reportLines.push(
|
|
2257
|
+
reportLines.push('\n💡 <b>Справка:</b>');
|
|
2258
|
+
reportLines.push('📝 Используйте /help для списка команд');
|
|
2259
|
+
reportLines.push('🔍 Используйте /status для проверки состояния');
|
|
2260
|
+
reportLines.push('🤖 Используйте /chat для включения LLM режима');
|
|
2261
2261
|
|
|
2262
2262
|
const report = reportLines.join('\n');
|
|
2263
2263
|
await sendMessage(chatId, report);
|
|
@@ -2272,7 +2272,7 @@ async function sendStatusMessage(chatId, isScheduled = false) {
|
|
|
2272
2272
|
*/
|
|
2273
2273
|
async function handleRestart(chatId) {
|
|
2274
2274
|
await sendMessage(chatId, '🔄 Перезапуск сервиса...');
|
|
2275
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2275
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2276
2276
|
await gracefulRestart(chatId);
|
|
2277
2277
|
}
|
|
2278
2278
|
|
|
@@ -2287,11 +2287,11 @@ async function handleExtendSession(chatId) {
|
|
|
2287
2287
|
const { loadSession, saveAuthToken } = await import('../browser/session.js');
|
|
2288
2288
|
const { loadTokens, saveTokens } = await import('../api/tokenManager.js');
|
|
2289
2289
|
const { CHAT_PAGE_URL } = await import('../config.js');
|
|
2290
|
-
|
|
2290
|
+
|
|
2291
2291
|
const tokens = loadTokens();
|
|
2292
|
-
|
|
2292
|
+
|
|
2293
2293
|
if (tokens.length === 0) {
|
|
2294
|
-
await sendMessage(chatId,
|
|
2294
|
+
await sendMessage(chatId,
|
|
2295
2295
|
'⚠️ <b>Нет аккаунтов</b>\n\n' +
|
|
2296
2296
|
'Сначала создайте сессию:\n' +
|
|
2297
2297
|
'1. Запустите <code>npm run create-session-archive</code>\n' +
|
|
@@ -2302,21 +2302,21 @@ async function handleExtendSession(chatId) {
|
|
|
2302
2302
|
|
|
2303
2303
|
// Фильтруем только действительные токены для продления
|
|
2304
2304
|
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;
|
|
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; }
|
|
2309
2309
|
// Проверяем наличие cookies.json
|
|
2310
2310
|
const cookiesPath = path.join(process.cwd(), SESSION_DIR, 'accounts', t.id, 'cookies.json');
|
|
2311
|
-
if (!fs.existsSync(cookiesPath)) return false;
|
|
2311
|
+
if (!fs.existsSync(cookiesPath)) { return false; }
|
|
2312
2312
|
return true;
|
|
2313
2313
|
});
|
|
2314
2314
|
|
|
2315
2315
|
const expiredCount = tokens.length - validTokens.length;
|
|
2316
2316
|
|
|
2317
2317
|
if (validTokens.length === 0) {
|
|
2318
|
-
await sendMessage(chatId,
|
|
2319
|
-
|
|
2318
|
+
await sendMessage(chatId,
|
|
2319
|
+
'⚠️ <b>Нет действительных токенов</b>\n\n' +
|
|
2320
2320
|
`Все ${tokens.length} токенов истекли.\n\n` +
|
|
2321
2321
|
'Создайте новые сессии:\n' +
|
|
2322
2322
|
'1. Запустите <code>npm run create-session-archive</code>\n' +
|
|
@@ -2325,15 +2325,15 @@ async function handleExtendSession(chatId) {
|
|
|
2325
2325
|
return;
|
|
2326
2326
|
}
|
|
2327
2327
|
|
|
2328
|
-
let startMessage =
|
|
2328
|
+
let startMessage = '🔄 <b>Продление сессий...</b>\n\n' +
|
|
2329
2329
|
`📊 Найдено аккаунтов: ${validTokens.length}`;
|
|
2330
|
-
|
|
2330
|
+
|
|
2331
2331
|
if (expiredCount > 0) {
|
|
2332
2332
|
startMessage += ` (пропущено ${expiredCount} истекших)`;
|
|
2333
2333
|
}
|
|
2334
|
-
|
|
2335
|
-
startMessage +=
|
|
2336
|
-
|
|
2334
|
+
|
|
2335
|
+
startMessage += '\n⏳ Это может занять несколько минут...\n' +
|
|
2336
|
+
'🕐 Примерное время: ~2-4 минуты на аккаунт';
|
|
2337
2337
|
|
|
2338
2338
|
await sendMessage(chatId, startMessage);
|
|
2339
2339
|
|
|
@@ -2345,7 +2345,7 @@ async function handleExtendSession(chatId) {
|
|
|
2345
2345
|
try {
|
|
2346
2346
|
// Показываем прогресс
|
|
2347
2347
|
const currentNum = results.length + 1;
|
|
2348
|
-
await sendMessage(chatId,
|
|
2348
|
+
await sendMessage(chatId,
|
|
2349
2349
|
`🔄 Обрабатываю аккаунт ${currentNum}/${tokens.length}...\n` +
|
|
2350
2350
|
`👤 ${token.id}`
|
|
2351
2351
|
);
|
|
@@ -2359,7 +2359,7 @@ async function handleExtendSession(chatId) {
|
|
|
2359
2359
|
|
|
2360
2360
|
// Загружаем cookies для аккаунта
|
|
2361
2361
|
const cookiesPath = path.join(process.cwd(), SESSION_DIR, 'accounts', token.id, 'cookies.json');
|
|
2362
|
-
|
|
2362
|
+
|
|
2363
2363
|
if (!fs.existsSync(cookiesPath)) {
|
|
2364
2364
|
results.push(`❌ ${token.id} - нет cookies`);
|
|
2365
2365
|
failCount++;
|
|
@@ -2372,26 +2372,26 @@ async function handleExtendSession(chatId) {
|
|
|
2372
2372
|
|
|
2373
2373
|
// Открываем браузер в headless режиме
|
|
2374
2374
|
const browserOk = await initBrowser(false, true);
|
|
2375
|
-
|
|
2375
|
+
|
|
2376
2376
|
if (!browserOk) {
|
|
2377
2377
|
throw new Error('Не удалось открыть браузер');
|
|
2378
2378
|
}
|
|
2379
2379
|
|
|
2380
2380
|
const ctx = getBrowserContext();
|
|
2381
|
-
|
|
2381
|
+
|
|
2382
2382
|
// Загружаем cookies
|
|
2383
2383
|
if (ctx && typeof ctx.setCookie === 'function') {
|
|
2384
2384
|
await ctx.setCookie(...cookies);
|
|
2385
2385
|
}
|
|
2386
2386
|
|
|
2387
2387
|
// Переходим на Qwen для обновления сессии (3 минуты таймаут)
|
|
2388
|
-
await ctx.goto(CHAT_PAGE_URL, {
|
|
2389
|
-
waitUntil: 'domcontentloaded',
|
|
2388
|
+
await ctx.goto(CHAT_PAGE_URL, {
|
|
2389
|
+
waitUntil: 'domcontentloaded',
|
|
2390
2390
|
timeout: 180000 // 3 минуты
|
|
2391
2391
|
});
|
|
2392
2392
|
|
|
2393
2393
|
// Ждем загрузки страницы (1 минута для полной загрузки)
|
|
2394
|
-
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
2394
|
+
await new Promise((resolve) => setTimeout(resolve, 60000));
|
|
2395
2395
|
|
|
2396
2396
|
// Извлекаем новый токен
|
|
2397
2397
|
const newToken = await extractAuthToken(ctx, true);
|
|
@@ -2409,7 +2409,7 @@ async function handleExtendSession(chatId) {
|
|
|
2409
2409
|
saveAuthToken(newToken);
|
|
2410
2410
|
|
|
2411
2411
|
// Обновляем tokens.json
|
|
2412
|
-
const tokenIndex = tokens.findIndex(t => t.id === token.id);
|
|
2412
|
+
const tokenIndex = tokens.findIndex((t) => t.id === token.id);
|
|
2413
2413
|
if (tokenIndex !== -1) {
|
|
2414
2414
|
tokens[tokenIndex].token = newToken;
|
|
2415
2415
|
tokens[tokenIndex].resetAt = null;
|
|
@@ -2430,14 +2430,14 @@ async function handleExtendSession(chatId) {
|
|
|
2430
2430
|
|
|
2431
2431
|
// Небольшая задержка между аккаунтами
|
|
2432
2432
|
if (successCount < tokens.length) {
|
|
2433
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
2433
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2434
2434
|
}
|
|
2435
2435
|
|
|
2436
2436
|
} catch (error) {
|
|
2437
2437
|
logError(`Ошибка продления ${token.id}`, error);
|
|
2438
2438
|
results.push(`❌ ${token.id} - ${error.message}`);
|
|
2439
2439
|
failCount++;
|
|
2440
|
-
|
|
2440
|
+
|
|
2441
2441
|
// Убеждаемся что браузер закрыт
|
|
2442
2442
|
try {
|
|
2443
2443
|
await shutdownBrowser();
|
|
@@ -2448,31 +2448,31 @@ async function handleExtendSession(chatId) {
|
|
|
2448
2448
|
}
|
|
2449
2449
|
|
|
2450
2450
|
// Формируем отчет
|
|
2451
|
-
let report =
|
|
2451
|
+
let report = '📋 <b>Результат продления сессий</b>\n\n';
|
|
2452
2452
|
report += `✅ Успешно: ${successCount}\n`;
|
|
2453
2453
|
report += `❌ Ошибки: ${failCount}\n\n`;
|
|
2454
|
-
report +=
|
|
2454
|
+
report += '<b>Детали:</b>\n';
|
|
2455
2455
|
report += results.join('\n');
|
|
2456
2456
|
|
|
2457
2457
|
if (successCount > 0) {
|
|
2458
|
-
report +=
|
|
2458
|
+
report += '\n\n🎉 Сессии продлены!';
|
|
2459
2459
|
}
|
|
2460
2460
|
|
|
2461
2461
|
if (failCount > 0 && successCount === 0) {
|
|
2462
|
-
report +=
|
|
2463
|
-
|
|
2462
|
+
report += '\n\n⚠️ Все сессии не удалось продлить.\n';
|
|
2463
|
+
|
|
2464
2464
|
// Check if the issue is missing cookies
|
|
2465
|
-
const missingCookiesCount = results.filter(r => r.includes('нет cookies')).length;
|
|
2466
|
-
|
|
2465
|
+
const missingCookiesCount = results.filter((r) => r.includes('нет cookies')).length;
|
|
2466
|
+
|
|
2467
2467
|
if (missingCookiesCount > 0) {
|
|
2468
|
-
report +=
|
|
2469
|
-
report +=
|
|
2470
|
-
report +=
|
|
2471
|
-
report +=
|
|
2472
|
-
report +=
|
|
2473
|
-
report +=
|
|
2468
|
+
report += '\n📦 <b>Причина: отсутствуют cookies.json</b>\n';
|
|
2469
|
+
report += '\nДля создания новой сессии с cookies:\n';
|
|
2470
|
+
report += '1. Запустите: <code>npm run create-session-archive</code>\n';
|
|
2471
|
+
report += '2. Войдите в систему в браузере\n';
|
|
2472
|
+
report += '3. Бот автоматически сохранит cookies и токен\n';
|
|
2473
|
+
report += '\n💡 Или отправьте архив с сессиями через бота';
|
|
2474
2474
|
} else {
|
|
2475
|
-
report +=
|
|
2475
|
+
report += '\nВыполните: <code>npm run create-session-archive</code>';
|
|
2476
2476
|
}
|
|
2477
2477
|
}
|
|
2478
2478
|
|
|
@@ -2480,12 +2480,12 @@ async function handleExtendSession(chatId) {
|
|
|
2480
2480
|
|
|
2481
2481
|
} catch (error) {
|
|
2482
2482
|
logError('Ошибка при продлении сессий', error);
|
|
2483
|
-
await sendMessage(chatId,
|
|
2484
|
-
|
|
2483
|
+
await sendMessage(chatId,
|
|
2484
|
+
'❌ <b>Ошибка продления сессий</b>\n\n' +
|
|
2485
2485
|
`Ошибка: ${error.message}\n\n` +
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2486
|
+
'Попробуйте:\n' +
|
|
2487
|
+
'1. <code>npm run create-session-archive</code>\n' +
|
|
2488
|
+
'2. Или отправьте архив с сессиями'
|
|
2489
2489
|
);
|
|
2490
2490
|
}
|
|
2491
2491
|
}
|
|
@@ -2501,7 +2501,7 @@ async function sendScheduledStatusToAdmins() {
|
|
|
2501
2501
|
}
|
|
2502
2502
|
|
|
2503
2503
|
const adminUserIds = TELEGRAM_USER_IDS;
|
|
2504
|
-
|
|
2504
|
+
|
|
2505
2505
|
if (adminUserIds.length === 0) {
|
|
2506
2506
|
logInfo('Нет админов для отправки плановой проверки');
|
|
2507
2507
|
return;
|
|
@@ -2527,9 +2527,9 @@ async function sendScheduledStatusToAdmins() {
|
|
|
2527
2527
|
*/
|
|
2528
2528
|
export function startPeriodicHealthCheck() {
|
|
2529
2529
|
const FOUR_HOURS = 4 * 60 * 60 * 1000; // 4 часа в миллисекундах
|
|
2530
|
-
|
|
2531
|
-
logInfo(
|
|
2532
|
-
|
|
2530
|
+
|
|
2531
|
+
logInfo('Запуск периодической проверки здоровья каждые 4 часа');
|
|
2532
|
+
|
|
2533
2533
|
setInterval(async () => {
|
|
2534
2534
|
logInfo('Выполняется плановая проверка здоровья...');
|
|
2535
2535
|
await sendScheduledStatusToAdmins();
|
|
@@ -2580,6 +2580,7 @@ export async function notifyAllUsers(message) {
|
|
|
2580
2580
|
* Обработчик LLM чата - отправляет сообщение в Qwen API
|
|
2581
2581
|
*/
|
|
2582
2582
|
async function handleLLMChat(chatId, userMessage) {
|
|
2583
|
+
let context;
|
|
2583
2584
|
try {
|
|
2584
2585
|
// Показываем индикатор набора текста
|
|
2585
2586
|
await sendChatAction(chatId, 'typing');
|
|
@@ -2588,7 +2589,8 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2588
2589
|
if (!chatContexts.has(chatId)) {
|
|
2589
2590
|
chatContexts.set(chatId, []);
|
|
2590
2591
|
}
|
|
2591
|
-
|
|
2592
|
+
|
|
2593
|
+
context = chatContexts.get(chatId);
|
|
2592
2594
|
|
|
2593
2595
|
// Добавляем сообщение пользователя в контекст
|
|
2594
2596
|
context.push({ role: 'user', content: userMessage });
|
|
@@ -2624,17 +2626,17 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2624
2626
|
method: 'POST',
|
|
2625
2627
|
headers: {
|
|
2626
2628
|
'Content-Type': 'application/json',
|
|
2627
|
-
'x-chat-id': `telegram-${chatId}`
|
|
2629
|
+
'x-chat-id': `telegram-${chatId}` // Уникальный ID для каждого Telegram чата
|
|
2628
2630
|
},
|
|
2629
2631
|
body: JSON.stringify(requestBody)
|
|
2630
2632
|
});
|
|
2631
2633
|
|
|
2632
2634
|
if (!response.ok) {
|
|
2633
2635
|
const errorText = await response.text();
|
|
2634
|
-
|
|
2636
|
+
|
|
2635
2637
|
// Логируем полную ошибку
|
|
2636
2638
|
logError(`❌ LLM Chat: Qwen API error ${response.status}`, errorText);
|
|
2637
|
-
|
|
2639
|
+
|
|
2638
2640
|
// Пытаемся распарсить JSON для лучшего форматирования
|
|
2639
2641
|
let errorJson;
|
|
2640
2642
|
try {
|
|
@@ -2642,16 +2644,16 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2642
2644
|
} catch {
|
|
2643
2645
|
errorJson = { error: errorText };
|
|
2644
2646
|
}
|
|
2645
|
-
|
|
2647
|
+
|
|
2646
2648
|
// Формируем сообщение об ошибке с полным JSON
|
|
2647
2649
|
const escapedJson = escapeHtmlForCode(JSON.stringify(errorJson, null, 2));
|
|
2648
|
-
const errorMessage =
|
|
2649
|
-
|
|
2650
|
+
const errorMessage =
|
|
2651
|
+
'❌ <b>Ошибка Qwen API</b>\n\n' +
|
|
2650
2652
|
`Статус: <code>${response.status}</code>\n\n` +
|
|
2651
|
-
|
|
2653
|
+
'<b>Полный ответ:</b>\n' +
|
|
2652
2654
|
`<pre>${escapedJson}</pre>\n\n` +
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
+
'💡 Попробуйте еще раз или используйте /clear';
|
|
2656
|
+
|
|
2655
2657
|
// Отправляем ошибку (разбиваем если длинная)
|
|
2656
2658
|
if (errorMessage.length > 4000) {
|
|
2657
2659
|
const chunks = splitMessage(errorMessage, 4000);
|
|
@@ -2661,12 +2663,12 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2661
2663
|
} else {
|
|
2662
2664
|
await sendMessage(chatId, errorMessage);
|
|
2663
2665
|
}
|
|
2664
|
-
|
|
2666
|
+
|
|
2665
2667
|
// Удаляем последнее сообщение пользователя из контекста (оно не было обработано)
|
|
2666
2668
|
if (context.length > 0) {
|
|
2667
2669
|
context.pop();
|
|
2668
2670
|
}
|
|
2669
|
-
|
|
2671
|
+
|
|
2670
2672
|
return; // Выходим, не выбрасывая ошибку
|
|
2671
2673
|
}
|
|
2672
2674
|
|
|
@@ -2693,17 +2695,17 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2693
2695
|
|
|
2694
2696
|
} catch (error) {
|
|
2695
2697
|
logError('❌ LLM Chat: Ошибка', error);
|
|
2696
|
-
|
|
2698
|
+
|
|
2697
2699
|
// Формируем сообщение об ошибке с деталями
|
|
2698
2700
|
const escapedStack = escapeHtmlForCode(error.stack || 'Stack trace unavailable');
|
|
2699
|
-
const errorMessage =
|
|
2700
|
-
|
|
2701
|
+
const errorMessage =
|
|
2702
|
+
'❌ <b>Ошибка при обработке запроса</b>\n\n' +
|
|
2701
2703
|
`<b>Тип:</b> ${error.name || 'Unknown'}\n` +
|
|
2702
2704
|
`<b>Сообщение:</b> ${escapeHtml(error.message)}\n\n` +
|
|
2703
|
-
|
|
2705
|
+
'<b>Стек:</b>\n' +
|
|
2704
2706
|
`<pre>${escapedStack}</pre>\n\n` +
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
+
'💡 Попробуйте еще раз или используйте /clear для очистки контекста.';
|
|
2708
|
+
|
|
2707
2709
|
// Отправляем ошибку (разбиваем если длинная)
|
|
2708
2710
|
if (errorMessage.length > 4000) {
|
|
2709
2711
|
const chunks = splitMessage(errorMessage, 4000);
|
|
@@ -2713,7 +2715,7 @@ async function handleLLMChat(chatId, userMessage) {
|
|
|
2713
2715
|
} else {
|
|
2714
2716
|
await sendMessage(chatId, errorMessage);
|
|
2715
2717
|
}
|
|
2716
|
-
|
|
2718
|
+
|
|
2717
2719
|
// Удаляем последнее сообщение пользователя из контекста
|
|
2718
2720
|
if (context && context.length > 0) {
|
|
2719
2721
|
context.pop();
|
|
@@ -2728,18 +2730,18 @@ async function toggleLLMChat(chatId) {
|
|
|
2728
2730
|
// Проверяем есть ли аккаунты
|
|
2729
2731
|
if (!hasAccounts()) {
|
|
2730
2732
|
await sendMessage(chatId,
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2733
|
+
'❌ <b>LLM чат недоступен</b>\n\n' +
|
|
2734
|
+
'🔒 Для работы AI ассистента нужны аккаунты\n' +
|
|
2735
|
+
'📦 Отправьте архив с сессиями через бота\n' +
|
|
2736
|
+
'💡 После загрузки аккаунтов функции будут доступны'
|
|
2735
2737
|
);
|
|
2736
|
-
logInfo(
|
|
2738
|
+
logInfo('❌ LLM Chat запрошен, но аккаунты отсутствуют');
|
|
2737
2739
|
return;
|
|
2738
2740
|
}
|
|
2739
2741
|
|
|
2740
2742
|
// Если LLM уже включен - выключаем, и наоборот
|
|
2741
2743
|
llmChatEnabled = !llmChatEnabled;
|
|
2742
|
-
|
|
2744
|
+
|
|
2743
2745
|
// Сохраняем состояние LLM чата
|
|
2744
2746
|
saveBotSettings({
|
|
2745
2747
|
activeModel: activeModel,
|
|
@@ -2753,26 +2755,26 @@ async function toggleLLMChat(chatId) {
|
|
|
2753
2755
|
}
|
|
2754
2756
|
|
|
2755
2757
|
await sendMessage(chatId,
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
+
'✅ <b>LLM чат включен!</b>\n\n' +
|
|
2759
|
+
'🤖 Теперь я отвечаю как AI ассистент.\n' +
|
|
2758
2760
|
`📝 Модель: ${getModelForChat(chatId)}\n` +
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2761
|
+
'💬 Просто отправляйте сообщения.\n' +
|
|
2762
|
+
'💾 Настройка сохранена\n\n' +
|
|
2763
|
+
'<b>Команды:</b>\n' +
|
|
2764
|
+
'/togglechat - Выключить LLM чат\n' +
|
|
2765
|
+
'/clear - Очистить контекст\n' +
|
|
2766
|
+
'/model - Информация о модели\n' +
|
|
2767
|
+
'/setmodel <название> - Сменить модель\n' +
|
|
2768
|
+
'/help - Все команды бота'
|
|
2767
2769
|
);
|
|
2768
2770
|
|
|
2769
2771
|
logInfo(`✅ LLM Chat включен для пользователя ${chatId} (сохранено)`);
|
|
2770
2772
|
} else {
|
|
2771
2773
|
await sendMessage(chatId,
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2774
|
+
'❌ <b>LLM чат выключен</b>\n\n' +
|
|
2775
|
+
'🔧 Возвращен в режим управления ботом.\n' +
|
|
2776
|
+
'💾 Настройка сохранена\n' +
|
|
2777
|
+
'Используйте /togglechat чтобы включить снова.'
|
|
2776
2778
|
);
|
|
2777
2779
|
|
|
2778
2780
|
logInfo(`❌ LLM Chat выключен для пользователя ${chatId} (сохранено)`);
|
|
@@ -2786,15 +2788,15 @@ async function showLLMChatStatus(chatId) {
|
|
|
2786
2788
|
const status = llmChatEnabled ? '✅ Включен' : '❌ Выключен';
|
|
2787
2789
|
const model = getModelForChat(chatId);
|
|
2788
2790
|
const context = chatContexts.get(chatId) || [];
|
|
2789
|
-
|
|
2791
|
+
|
|
2790
2792
|
await sendMessage(chatId,
|
|
2791
|
-
|
|
2793
|
+
'📊 <b>Состояние LLM чата</b>\n\n' +
|
|
2792
2794
|
`🔧 Статус: ${status}\n` +
|
|
2793
2795
|
`🤖 Модель: <code>${model}</code>\n` +
|
|
2794
2796
|
`💬 Сообщений в контексте: ${context.length}\n\n` +
|
|
2795
2797
|
`💡 Используйте /togglechat чтобы ${llmChatEnabled ? 'выключить' : 'включить'} LLM чат`
|
|
2796
2798
|
);
|
|
2797
|
-
|
|
2799
|
+
|
|
2798
2800
|
logInfo(`📊 Проверка статуса LLM чата: ${llmChatEnabled ? 'включен' : 'выключен'}`);
|
|
2799
2801
|
}
|
|
2800
2802
|
|
|
@@ -2803,44 +2805,44 @@ async function showLLMChatStatus(chatId) {
|
|
|
2803
2805
|
*/
|
|
2804
2806
|
async function handleSetModel(chatId, text) {
|
|
2805
2807
|
const parts = text.trim().split(/\s+/);
|
|
2806
|
-
|
|
2808
|
+
|
|
2807
2809
|
// Если нет аргумента - показываем текущую модель
|
|
2808
2810
|
if (parts.length < 2) {
|
|
2809
2811
|
await showModelInfo(chatId);
|
|
2810
2812
|
return;
|
|
2811
2813
|
}
|
|
2812
|
-
|
|
2814
|
+
|
|
2813
2815
|
const requestedModel = parts[1].trim();
|
|
2814
2816
|
const { getAvailableModelsFromFile } = await import('../api/chat.js');
|
|
2815
2817
|
const availableModels = getAvailableModelsFromFile();
|
|
2816
|
-
|
|
2818
|
+
|
|
2817
2819
|
// Проверяем что модель существует
|
|
2818
2820
|
if (!availableModels.includes(requestedModel)) {
|
|
2819
2821
|
await sendMessage(chatId,
|
|
2820
|
-
|
|
2822
|
+
'❌ <b>Модель не найдена</b>\n\n' +
|
|
2821
2823
|
`Модель <code>${requestedModel}</code> не найдена в списке доступных.\n\n` +
|
|
2822
|
-
|
|
2824
|
+
'<b>Используйте /model для списка доступных моделей</b>'
|
|
2823
2825
|
);
|
|
2824
2826
|
logWarn(`❌ Пользователь ${chatId} попытался установить несуществующую модель: ${requestedModel}`);
|
|
2825
2827
|
return;
|
|
2826
2828
|
}
|
|
2827
|
-
|
|
2829
|
+
|
|
2828
2830
|
// Устанавливаем глобальную активную модель
|
|
2829
2831
|
activeModel = requestedModel;
|
|
2830
|
-
|
|
2832
|
+
|
|
2831
2833
|
// Сохраняем в файл
|
|
2832
2834
|
const settings = loadBotSettings();
|
|
2833
2835
|
settings.activeModel = requestedModel;
|
|
2834
2836
|
saveBotSettings(settings);
|
|
2835
|
-
|
|
2837
|
+
|
|
2836
2838
|
await sendMessage(chatId,
|
|
2837
|
-
|
|
2839
|
+
'✅ <b>Модель изменена!</b>\n\n' +
|
|
2838
2840
|
`🤖 Новая модель: <code>${requestedModel}</code>\n` +
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2841
|
+
'💬 Будет использоваться во всех чатах\n' +
|
|
2842
|
+
'💾 Настройка сохранена\n\n' +
|
|
2843
|
+
'💡 Для сброса используйте /clear'
|
|
2842
2844
|
);
|
|
2843
|
-
|
|
2845
|
+
|
|
2844
2846
|
logInfo(`✅ Установлена глобальная модель: ${requestedModel} (сохранено)`);
|
|
2845
2847
|
}
|
|
2846
2848
|
|
|
@@ -2853,7 +2855,7 @@ function getModelForChat(chatId) {
|
|
|
2853
2855
|
if (activeModel) {
|
|
2854
2856
|
return activeModel;
|
|
2855
2857
|
}
|
|
2856
|
-
|
|
2858
|
+
|
|
2857
2859
|
// Возвращаем модель из настроек бота
|
|
2858
2860
|
return getBotSettingsModel();
|
|
2859
2861
|
}
|
|
@@ -2877,15 +2879,15 @@ async function showModelInfo(chatId) {
|
|
|
2877
2879
|
const availableModels = getAvailableModelsFromFile();
|
|
2878
2880
|
|
|
2879
2881
|
const message =
|
|
2880
|
-
|
|
2882
|
+
'📊 <b>Информация о модели</b>\n\n' +
|
|
2881
2883
|
`🤖 Активная модель: <code>${currentModel}</code>\n` +
|
|
2882
2884
|
`💬 Сообщений в контексте: ${context.length}\n` +
|
|
2883
2885
|
`🔧 LLM чат: ${llmChatEnabled ? '✅ Включен' : '❌ Выключен'}\n\n` +
|
|
2884
|
-
|
|
2885
|
-
availableModels.map(m => `<code>${m}</code>`).join(', ') +
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2886
|
+
'<b>Доступные модели:</b>\n' +
|
|
2887
|
+
availableModels.map((m) => `<code>${m}</code>`).join(', ') +
|
|
2888
|
+
'\n\n💡 Для смены модели используйте:\n' +
|
|
2889
|
+
'/setmodel <название_модели>\n' +
|
|
2890
|
+
'Например: /setmodel qwen3-max';
|
|
2889
2891
|
|
|
2890
2892
|
await sendMessage(chatId, message);
|
|
2891
2893
|
}
|
|
@@ -2897,8 +2899,8 @@ async function clearChatContext(chatId) {
|
|
|
2897
2899
|
chatContexts.set(chatId, []);
|
|
2898
2900
|
|
|
2899
2901
|
await sendMessage(chatId,
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
+
'🗑️ <b>Контекст чата очищен</b>\n\n' +
|
|
2903
|
+
'💬 Новая история чата начата.\n' +
|
|
2902
2904
|
`🤖 LLM чат: ${llmChatEnabled ? '✅ Включен' : '❌ Выключен'}`
|
|
2903
2905
|
);
|
|
2904
2906
|
|
|
@@ -2958,7 +2960,7 @@ function splitMessage(text, maxLength) {
|
|
|
2958
2960
|
* Экранирует HTML специальные символы
|
|
2959
2961
|
*/
|
|
2960
2962
|
function escapeHtml(text) {
|
|
2961
|
-
if (!text) return '';
|
|
2963
|
+
if (!text) { return ''; }
|
|
2962
2964
|
return String(text)
|
|
2963
2965
|
.replace(/&/g, '&')
|
|
2964
2966
|
.replace(/</g, '<')
|
|
@@ -2969,7 +2971,7 @@ function escapeHtml(text) {
|
|
|
2969
2971
|
* Экранирует текст для вставки в <pre> тег (только < и &)
|
|
2970
2972
|
*/
|
|
2971
2973
|
function escapeHtmlForCode(text) {
|
|
2972
|
-
if (!text) return '';
|
|
2974
|
+
if (!text) { return ''; }
|
|
2973
2975
|
return String(text)
|
|
2974
2976
|
.replace(/&/g, '&')
|
|
2975
2977
|
.replace(/</g, '<')
|