Compare commits

...

2 Commits

Author SHA1 Message Date
e6a9743de3 fix: использовать переменную окружения для выбора модели OpenRouter 2025-06-27 18:58:47 +03:00
31b7e14f75 feat(logging): внедрено логирование через winston
- Добавлен модуль logger.js с настройками winston для логирования в файлы и консоль
- Заменены все console.log/warn/error в bot.js на вызовы logger.info/warn/error
- Добавлена зависимость winston в package.json
- Логи теперь структурированы, с таймстампами и цветовой подсветкой в консоли
- Улучшена читаемость и удобство отладки приложения
2025-06-27 18:58:17 +03:00
3 changed files with 77 additions and 32 deletions

64
bot.js
View File

@ -3,6 +3,7 @@ const fs = require('fs').promises;
const path = require('path'); const path = require('path');
const { OpenRouterClient } = require('openrouter-kit'); const { OpenRouterClient } = require('openrouter-kit');
const { getPrompt } = require('./prompts.js'); const { getPrompt } = require('./prompts.js');
const logger = require('./logger.js');
require('dotenv').config(); require('dotenv').config();
const char = {name:'marina'} const char = {name:'marina'}
class TelegramHistoryBot { class TelegramHistoryBot {
@ -13,7 +14,7 @@ class TelegramHistoryBot {
// Инициализация OpenRouter с проверкой ключа // Инициализация OpenRouter с проверкой ключа
if (!process.env.OPENROUTER_API_KEY) { if (!process.env.OPENROUTER_API_KEY) {
console.warn('⚠️ OPENROUTER_API_KEY не найден, ИИ-суммаризация будет недоступна'); logger.warn('⚠️ OPENROUTER_API_KEY не найден, ИИ-суммаризация будет недоступна');
this.openRouter = null; this.openRouter = null;
} else { } else {
this.openRouter = new OpenRouterClient({ this.openRouter = new OpenRouterClient({
@ -37,9 +38,9 @@ class TelegramHistoryBot {
try { try {
await this.loadHistory(); await this.loadHistory();
this.setupHandlers(); this.setupHandlers();
console.log('✅ Bot запущен и готов к работе'); logger.info('✅ Bot запущен и готов к работе');
} catch (error) { } catch (error) {
console.error('❌ Ошибка инициализации бота:', error); logger.error('❌ Ошибка инициализации бота:', error);
process.exit(1); process.exit(1);
} }
} }
@ -48,9 +49,9 @@ class TelegramHistoryBot {
try { try {
const data = await fs.readFile(this.historyFile, 'utf8'); const data = await fs.readFile(this.historyFile, 'utf8');
this.history = JSON.parse(data); this.history = JSON.parse(data);
console.log(`📚 Загружено ${this.history.length} сообщений из истории`); logger.info(`📚 Загружено ${this.history.length} сообщений из истории`);
} catch (error) { } catch (error) {
console.log('📝 История не найдена, создаем новую'); logger.warn('📝 История не найдена, создаем новую');
this.history = []; this.history = [];
await this.saveHistory(); await this.saveHistory();
} }
@ -60,7 +61,7 @@ class TelegramHistoryBot {
try { try {
await fs.writeFile(this.historyFile, JSON.stringify(this.history, null, 2)); await fs.writeFile(this.historyFile, JSON.stringify(this.history, null, 2));
} catch (error) { } catch (error) {
console.error('❌ Ошибка сохранения истории:', error); logger.error('❌ Ошибка сохранения истории:', error);
} }
} }
@ -72,7 +73,7 @@ class TelegramHistoryBot {
// await ctx.deleteMessage // await ctx.deleteMessage
// return // return
// } // }
console.log('📊 Получена команда summary_day'); logger.info('📊 Получена команда summary_day');
const args = ctx.message.text.split(' '); const args = ctx.message.text.split(' ');
if (args.length > 1) { if (args.length > 1) {
char.name=args[1] char.name=args[1]
@ -82,7 +83,7 @@ class TelegramHistoryBot {
}); });
this.bot.command('summary_hours', async (ctx) => { this.bot.command('summary_hours', async (ctx) => {
console.log('📊 Получена команда summary_hours'); logger.info('📊 Получена команда summary_hours');
const args = ctx.message.text.split(' '); const args = ctx.message.text.split(' ');
if (args.length < 2 || isNaN(parseInt(args[1]))) { if (args.length < 2 || isNaN(parseInt(args[1]))) {
await ctx.reply('❗ Укажите количество часов: /summary_hours 6'); await ctx.reply('❗ Укажите количество часов: /summary_hours 6');
@ -94,7 +95,7 @@ class TelegramHistoryBot {
}); });
this.bot.command('summary_last', async (ctx) => { this.bot.command('summary_last', async (ctx) => {
console.log('📊 Получена команда summary_last'); logger.info('📊 Получена команда summary_last');
const args = ctx.message.text.split(' '); const args = ctx.message.text.split(' ');
if (args.length < 2 || isNaN(parseInt(args[1]))) { if (args.length < 2 || isNaN(parseInt(args[1]))) {
await ctx.reply('❗ Укажите количество сообщений: /summary_last 50'); await ctx.reply('❗ Укажите количество сообщений: /summary_last 50');
@ -120,25 +121,25 @@ class TelegramHistoryBot {
// Обработка ошибок // Обработка ошибок
this.bot.catch(async (err, ctx) => { this.bot.catch(async (err, ctx) => {
console.error('❌ Ошибка бота:', err); logger.error('❌ Ошибка бота:', err);
try { try {
await ctx.reply('❌ Произошла ошибка при обработке команды'); await ctx.reply('❌ Произошла ошибка при обработке команды');
} catch (replyError) { } catch (replyError) {
console.error('❌ Не удалось отправить сообщение об ошибке:', replyError); logger.error('❌ Не удалось отправить сообщение об ошибке:', replyError);
} }
}); });
// Запуск бота // Запуск бота
this.bot.launch(); this.bot.launch();
console.log('🚀 Бот запущен'); logger.info('🚀 Бот запущен');
// Graceful stop // Graceful stop
process.once('SIGINT', () => { process.once('SIGINT', () => {
console.log('🛑 Получен сигнал SIGINT, останавливаем бота...'); logger.warn('🛑 Получен сигнал SIGINT, останавливаем бота...');
this.bot.stop('SIGINT'); this.bot.stop('SIGINT');
}); });
process.once('SIGTERM', () => { process.once('SIGTERM', () => {
console.log('🛑 Получен сигнал SIGTERM, останавливаем бота...'); logger.warn('🛑 Получен сигнал SIGTERM, останавливаем бота...');
this.bot.stop('SIGTERM'); this.bot.stop('SIGTERM');
}); });
} }
@ -169,9 +170,9 @@ class TelegramHistoryBot {
const userName = messageData.first_name || messageData.username || 'Unknown'; const userName = messageData.first_name || messageData.username || 'Unknown';
const content = messageData.text || `[${messageData.message_type}]`; const content = messageData.text || `[${messageData.message_type}]`;
console.log(`💾 Сохранено сообщение от ${userName}: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`); logger.info(`💾 Сохранено сообщение от ${userName} в ${msg.chat.id} : ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`);
} catch (error) { } catch (error) {
console.error('❌ Ошибка при обработке сообщения:', error); logger.error('❌ Ошибка при обработке сообщения:', error);
} }
} }
@ -220,7 +221,7 @@ class TelegramHistoryBot {
} }
async handleSummaryCommand(ctx, type, value) { async handleSummaryCommand(ctx, type, value) {
console.log(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`); logger.info(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`);
const chatId = ctx.chat.id; const chatId = ctx.chat.id;
let messages = []; let messages = [];
@ -250,7 +251,7 @@ class TelegramHistoryBot {
break; break;
} }
console.log(`📈 Найдено ${messages.length} сообщений для суммаризации`); logger.info(`📈 Найдено ${messages.length} сообщений для суммаризации`);
if (messages.length === 0) { if (messages.length === 0) {
await ctx.reply('❗ Сообщения для суммаризации не найдены'); await ctx.reply('❗ Сообщения для суммаризации не найдены');
@ -267,10 +268,10 @@ class TelegramHistoryBot {
// Отправляем результат // Отправляем результат
await ctx.reply(summary, { parse_mode: 'HTML' }); await ctx.reply(summary, { parse_mode: 'HTML' });
console.log('✅ Суммаризация отправлена'); logger.info('✅ Суммаризация отправлена');
} catch (error) { } catch (error) {
console.error('❌ Ошибка при создании суммаризации:', error); logger.error('❌ Ошибка при создании суммаризации:', error);
await ctx.reply('❌ Произошла ошибка при создании суммаризации. Попробуйте позже.'); await ctx.reply('❌ Произошла ошибка при создании суммаризации. Попробуйте позже.');
} }
} }
@ -388,7 +389,7 @@ class TelegramHistoryBot {
createSummaryPrompt(type, value) { createSummaryPrompt(type, value) {
const timeDescription = this.getTimeDescription(type, value); const timeDescription = this.getTimeDescription(type, value);
console.log('Выбранный персонаж: ', char) logger.info('Выбранный персонаж: ', char)
return `Ты получишь данные чата Telegram ${timeDescription}. ` + return `Ты получишь данные чата Telegram ${timeDescription}. ` +
getPrompt(char.name) getPrompt(char.name)
@ -443,13 +444,13 @@ ${conversationFlow}`;
async callAISummarization(prompt, data) { async callAISummarization(prompt, data) {
// Если API ключ недоступен, используем fallback // Если API ключ недоступен, используем fallback
if (!process.env.OPENROUTER_API_KEY) { if (!process.env.OPENROUTER_API_KEY) {
console.log('⚠️ OpenRouter API ключ недоступен, используем базовую суммаризацию'); logger.warn('⚠️ OpenRouter API ключ недоступен, используем базовую суммаризацию');
return this.generateFallbackSummary(data); return this.generateFallbackSummary(data);
} }
try { try {
const formattedMessages = this.formatMessagesForAI(data); const formattedMessages = this.formatMessagesForAI(data);
console.log('🤖 Отправляем запрос к OpenRouter...'); logger.info('🤖 Отправляем запрос к OpenRouter...');
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST', method: 'POST',
@ -460,7 +461,8 @@ ${conversationFlow}`;
'X-Title': 'Telegram History Bot' 'X-Title': 'Telegram History Bot'
}, },
body: JSON.stringify({ body: JSON.stringify({
model: 'google/gemini-2.5-flash-preview-05-20', model: process.env.OPENROUTER_MODEL,
// model: 'google/gemini-2.5-flash-preview-05-20',
// model: 'google/gemini-2.0-flash-exp:free', // model: 'google/gemini-2.0-flash-exp:free',
// model: 'deepseek/deepseek-chat-v3-0324:free', // model: 'deepseek/deepseek-chat-v3-0324:free',
messages: [ messages: [
@ -491,21 +493,21 @@ ${conversationFlow}`;
throw new Error('Пустой ответ от ИИ'); throw new Error('Пустой ответ от ИИ');
} }
console.log('✅ Получен ответ от OpenRouter'); logger.info('✅ Получен ответ от OpenRouter');
return `🎬 <b>История чата</b>\n\n${aiSummary}`; return `🎬 <b>История чата</b>\n\n${aiSummary}`;
} catch (error) { } catch (error) {
console.error('❌ Ошибка при вызове OpenRouter API:', error); logger.error('❌ Ошибка при вызове OpenRouter API:', error);
// Детальная обработка различных типов ошибок // Детальная обработка различных типов ошибок
if (error.message.includes('401')) { if (error.message.includes('401')) {
console.error('❌ Неверный API ключ OpenRouter'); logger.error('❌ Неверный API ключ OpenRouter');
} else if (error.message.includes('429')) { } else if (error.message.includes('429')) {
console.error('❌ Превышен лимит запросов OpenRouter'); logger.error('❌ Превышен лимит запросов OpenRouter');
} else if (error.message.includes('402')) { } else if (error.message.includes('402')) {
console.error('❌ Недостаточно средств на счету OpenRouter'); logger.error('❌ Недостаточно средств на счету OpenRouter');
} else if (error.code === 'ENOTFOUND') { } else if (error.code === 'ENOTFOUND') {
console.error('❌ Проблемы с сетевым подключением'); logger.error('❌ Проблемы с сетевым подключением');
} }
return this.generateFallbackSummary(data); return this.generateFallbackSummary(data);
@ -558,7 +560,7 @@ ${conversationFlow}`;
const BOT_TOKEN = process.env.BOT_TOKEN; const BOT_TOKEN = process.env.BOT_TOKEN;
if (!BOT_TOKEN) { if (!BOT_TOKEN) {
console.error('❌ Укажите токен бота в переменной окружения BOT_TOKEN'); logger.error('❌ Укажите токен бота в переменной окружения BOT_TOKEN');
process.exit(1); process.exit(1);
} }

42
logger.js Normal file
View File

@ -0,0 +1,42 @@
const winston = require('winston');
const path = require('path');
// __dirname уже доступен в CommonJS, нет нужды использовать fileURLToPath
// Формат для файла (подробный JSON)
const fileFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.json()
);
// Формат для консоли (краткий, с цветами)
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'HH:mm:ss' }),
winston.format.printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
})
);
const logger = winston.createLogger({
level: 'info',
transports: [
// Логирование ошибок в отдельный файл
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error',
format: fileFormat
}),
// Общий лог файл
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log'),
format: fileFormat
}),
// Консольный вывод
new winston.transports.Console({
format: consoleFormat
})
]
});
module.exports = logger;

View File

@ -11,7 +11,8 @@
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"openrouter-kit": "^0.1.65", "openrouter-kit": "^0.1.65",
"process": "^0.11.10", "process": "^0.11.10",
"telegraf": "^4.12.2" "telegraf": "^4.12.2",
"winston": "^3.17.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.0.1" "nodemon": "^3.0.1"