365 lines
13 KiB
JavaScript
365 lines
13 KiB
JavaScript
const TelegramBot = require('node-telegram-bot-api');
|
||
const { ChatOllama } = require('@langchain/ollama');
|
||
const axios = require('axios');
|
||
const dns = require('dns').promises;
|
||
require('dotenv').config();
|
||
const { sleep } = require('../utils/helpers');
|
||
const util = require('util');
|
||
const exec = util.promisify(require('child_process').exec);
|
||
|
||
class TelegramBotService {
|
||
constructor(token) {
|
||
if (!token) {
|
||
throw new Error('Token is required');
|
||
}
|
||
|
||
this.isRunning = false;
|
||
this.maxRetries = 3;
|
||
this.retryDelay = 5000; // 5 секунд между попытками
|
||
|
||
// Создаем бота без polling
|
||
this.bot = new TelegramBot(token, {
|
||
polling: false,
|
||
request: {
|
||
proxy: null,
|
||
agentOptions: {
|
||
rejectUnauthorized: true,
|
||
minVersion: 'TLSv1.2'
|
||
},
|
||
timeout: 30000
|
||
}
|
||
});
|
||
|
||
this.token = token;
|
||
this.chat = new ChatOllama({
|
||
model: 'mistral',
|
||
baseUrl: 'http://localhost:11434'
|
||
});
|
||
|
||
// Добавляем настройки прокси для axios
|
||
this.axiosConfig = {
|
||
timeout: 5000,
|
||
proxy: false,
|
||
httpsAgent: new (require('https').Agent)({
|
||
rejectUnauthorized: true,
|
||
minVersion: 'TLSv1.2'
|
||
})
|
||
};
|
||
}
|
||
|
||
setupHandlers() {
|
||
this.bot.onText(/.*/, async (msg) => {
|
||
try {
|
||
const chatId = msg.chat.id;
|
||
const userQuestion = msg.text;
|
||
|
||
// Пропускаем команды
|
||
if (userQuestion.startsWith('/')) {
|
||
return;
|
||
}
|
||
|
||
console.log('Получен вопрос:', userQuestion);
|
||
|
||
// Используем локальную модель
|
||
const result = await this.chat.invoke(userQuestion);
|
||
const assistantResponse = result.content;
|
||
|
||
await this.bot.sendMessage(chatId, assistantResponse);
|
||
} catch (error) {
|
||
console.error('Telegram bot error:', error);
|
||
await this.bot.sendMessage(msg.chat.id,
|
||
'Извините, произошла ошибка при обработке вашего запроса. ' +
|
||
'Попробуйте повторить позже или обратитесь к администратору.'
|
||
);
|
||
}
|
||
});
|
||
}
|
||
|
||
setupCommands() {
|
||
this.bot.onText(/\/start/, async (msg) => {
|
||
const welcomeMessage = `
|
||
👋 Здравствуйте! Я - ассистент DApp for Business.
|
||
|
||
Я готов помочь вам с вопросами о:
|
||
• Разработке dApps
|
||
• Блокчейн-технологиях
|
||
• Web3 и криптовалютах
|
||
|
||
Просто задавайте вопросы, а если нужна помощь -
|
||
используйте команду /help
|
||
`;
|
||
await this.bot.sendMessage(msg.chat.id, welcomeMessage);
|
||
});
|
||
|
||
this.bot.onText(/\/help/, async (msg) => {
|
||
const helpMessage = `
|
||
🤖 Я - ассистент DApp for Business
|
||
|
||
Я могу помочь вам с:
|
||
• Разработкой децентрализованных приложений
|
||
• Интеграцией блокчейн-технологий в бизнес
|
||
• Консультациями по Web3 и криптовалютам
|
||
|
||
Команды:
|
||
/start - начать работу с ботом
|
||
/help - показать это сообщение
|
||
/status - проверить состояние бота
|
||
|
||
Просто задавайте вопросы на русском или английском языке!
|
||
`;
|
||
await this.bot.sendMessage(msg.chat.id, helpMessage);
|
||
});
|
||
|
||
this.bot.onText(/\/status/, async (msg) => {
|
||
try {
|
||
const status = {
|
||
isRunning: this.isRunning,
|
||
uptime: process.uptime(),
|
||
memoryUsage: process.memoryUsage(),
|
||
connections: {
|
||
telegram: Boolean(this.bot),
|
||
ollama: Boolean(this.chat)
|
||
}
|
||
};
|
||
|
||
const statusMessage = `
|
||
📊 Статус бота:
|
||
|
||
🟢 Статус: ${status.isRunning ? 'Работает' : 'Остановлен'}
|
||
⏱ Время работы: ${Math.floor(status.uptime / 60)} мин
|
||
|
||
🔗 Подключения:
|
||
• Telegram API: ${status.connections.telegram ? '✅' : '❌'}
|
||
• Ollama: ${status.connections.ollama ? '✅' : '❌'}
|
||
|
||
💾 Использование памяти:
|
||
• Heap: ${Math.round(status.memoryUsage.heapUsed / 1024 / 1024)}MB
|
||
• RSS: ${Math.round(status.memoryUsage.rss / 1024 / 1024)}MB
|
||
`;
|
||
|
||
await this.bot.sendMessage(msg.chat.id, statusMessage);
|
||
} catch (error) {
|
||
console.error('Ошибка при получении статуса:', error);
|
||
await this.bot.sendMessage(msg.chat.id, 'Ошибка при получении статуса бота');
|
||
}
|
||
});
|
||
}
|
||
|
||
async initialize() {
|
||
let retries = 0;
|
||
|
||
while (retries < this.maxRetries) {
|
||
try {
|
||
console.log(`Попытка инициализации Telegram бота (${retries + 1}/${this.maxRetries})...`);
|
||
|
||
// Сначала проверяем DNS и доступность
|
||
try {
|
||
console.log('Проверка DNS для api.telegram.org...');
|
||
const addresses = await dns.resolve4('api.telegram.org');
|
||
console.log('IP адреса api.telegram.org:', addresses);
|
||
|
||
// Пинг для проверки доступности (теперь ждем результат)
|
||
try {
|
||
const { stdout } = await exec('ping -c 1 api.telegram.org');
|
||
console.log('Результат ping:', stdout);
|
||
} catch (pingError) {
|
||
console.error('Ошибка при выполнении ping:', pingError);
|
||
throw new Error('Сервер Telegram недоступен');
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка сетевой проверки:', error);
|
||
throw error;
|
||
}
|
||
|
||
// Затем проверяем API
|
||
try {
|
||
const response = await axios.get(
|
||
`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/getMe`,
|
||
this.axiosConfig
|
||
);
|
||
|
||
if (response.status !== 200) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
console.log('Успешное подключение к API Telegram:', {
|
||
botInfo: response.data.result
|
||
});
|
||
} catch (error) {
|
||
console.error('Ошибка при проверке API Telegram:', {
|
||
message: error.message,
|
||
code: error.code,
|
||
response: error.response?.data,
|
||
config: {
|
||
url: error.config?.url,
|
||
method: error.config?.method,
|
||
timeout: error.config?.timeout
|
||
}
|
||
});
|
||
throw error;
|
||
}
|
||
|
||
// Основная инициализация бота
|
||
await this.initBot();
|
||
console.log('Telegram bot service initialized');
|
||
return;
|
||
|
||
} catch (error) {
|
||
retries++;
|
||
console.error('Ошибка при инициализации Telegram бота:', {
|
||
name: error.name,
|
||
message: error.message,
|
||
code: error.code,
|
||
response: error.response?.data,
|
||
stack: error.stack
|
||
});
|
||
|
||
if (retries < this.maxRetries) {
|
||
console.log(`Повторная попытка через ${this.retryDelay/1000} секунд...`);
|
||
await sleep(this.retryDelay);
|
||
} else {
|
||
console.error('Превышено максимальное количество попыток подключения к Telegram');
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async initBot() {
|
||
try {
|
||
// Проверяем, не запущен ли уже бот
|
||
const webhookInfo = await this.bot.getWebHookInfo();
|
||
|
||
// Если есть webhook или активный polling, пробуем остановить
|
||
if (webhookInfo.url || webhookInfo.has_custom_certificate) {
|
||
console.log('Удаляем существующий webhook...');
|
||
await this.bot.deleteWebHook();
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
}
|
||
|
||
// Пробуем получить обновления с большим таймаутом
|
||
try {
|
||
console.log('Проверяем наличие других экземпляров бота...');
|
||
const updates = await this.bot.getUpdates({
|
||
offset: -1,
|
||
limit: 1,
|
||
timeout: 0
|
||
});
|
||
console.log('Проверка существующих подключений:', updates);
|
||
} catch (error) {
|
||
if (error.code === 409) {
|
||
console.log('Обнаружен активный бот, пробуем остановить...');
|
||
await this.stop();
|
||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||
// Повторная попытка получить обновления
|
||
await this.bot.getUpdates({ offset: -1, limit: 1, timeout: 0 });
|
||
}
|
||
}
|
||
|
||
// Небольшая пауза перед запуском поллинга
|
||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
|
||
// Запускаем polling
|
||
console.log('Запускаем polling...');
|
||
await this.bot.startPolling({
|
||
interval: 2000,
|
||
params: {
|
||
timeout: 10
|
||
}
|
||
});
|
||
|
||
this.isRunning = true;
|
||
this.setupHandlers();
|
||
this.setupErrorHandlers();
|
||
this.setupCommands();
|
||
} catch (error) {
|
||
if (error.code === 409) {
|
||
console.log('Бот уже запущен в другом процессе');
|
||
this.isRunning = false;
|
||
} else {
|
||
console.error('Ошибка при инициализации Telegram бота:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
setupErrorHandlers() {
|
||
this.bot.on('polling_error', (error) => {
|
||
console.error('Telegram polling error:', {
|
||
code: error.code,
|
||
message: error.message,
|
||
stack: error.stack
|
||
});
|
||
|
||
// Обработка различных ошибок
|
||
if (this.isRunning && (error.code === 'EFATAL' || error.code === 'ETELEGRAM')) {
|
||
console.log('Переподключение к Telegram через 5 секунд...');
|
||
setTimeout(async () => {
|
||
try {
|
||
await this.stop();
|
||
await this.initBot();
|
||
} catch (err) {
|
||
console.error('Ошибка при перезапуске бота:', err);
|
||
}
|
||
}, 5000);
|
||
} else if (error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED') {
|
||
// Для ошибок соединения пробуем сразу переподключиться
|
||
this.bot.startPolling();
|
||
}
|
||
});
|
||
|
||
// Обработка других ошибок
|
||
this.bot.on('error', (error) => {
|
||
console.error('Telegram bot error:', error);
|
||
// Пробуем переподключиться при любой ошибке
|
||
setTimeout(() => this.bot.startPolling(), 5000);
|
||
});
|
||
|
||
// Обработка webhook ошибок
|
||
this.bot.on('webhook_error', (error) => {
|
||
console.error('Telegram webhook error:', error);
|
||
});
|
||
}
|
||
|
||
async stop() {
|
||
if (this.isRunning) {
|
||
console.log('Останавливаем Telegram бота...');
|
||
try {
|
||
// Сначала отключаем обработчики
|
||
this.bot.removeAllListeners();
|
||
|
||
// Останавливаем поллинг
|
||
await this.bot.stopPolling();
|
||
|
||
// Очищаем очередь обновлений
|
||
await this.bot.getUpdates({
|
||
offset: -1,
|
||
limit: 1,
|
||
timeout: 1
|
||
});
|
||
|
||
this.isRunning = false;
|
||
console.log('Telegram бот остановлен');
|
||
} catch (error) {
|
||
console.error('Ошибка при остановке бота:', error);
|
||
// Принудительно отмечаем как остановленный
|
||
this.isRunning = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
async checkTelegramAvailability() {
|
||
const { stdout } = await exec('ping -c 1 api.telegram.org');
|
||
const match = stdout.match(/time=(\d+(\.\d+)?)/);
|
||
if (match) {
|
||
const pingTime = parseFloat(match[1]);
|
||
console.log(`Время отклика Telegram API: ${pingTime}ms`);
|
||
if (pingTime > 1000) { // Если пинг больше секунды
|
||
console.warn('Внимание: высокая задержка при подключении к Telegram API');
|
||
}
|
||
}
|
||
return stdout;
|
||
}
|
||
}
|
||
|
||
module.exports = TelegramBotService;
|