Описание изменений
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"deploy": "hardhat run scripts/deploy.js --network sepolia",
|
||||
"node": "hardhat node",
|
||||
"test": "hardhat test",
|
||||
"server": "nodemon server.js"
|
||||
"server": "nodemon --signal SIGUSR2 server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/community": "^0.3.31",
|
||||
@@ -18,7 +18,11 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.3",
|
||||
"express-session": "^1.18.0",
|
||||
"imap": "^0.8.19",
|
||||
"langchain": "^0.3.19",
|
||||
"mailparser": "^3.7.2",
|
||||
"node-telegram-bot-api": "^0.66.0",
|
||||
"nodemailer": "^6.10.0",
|
||||
"nodemon": "^3.1.0",
|
||||
"openai": "^4.85.2",
|
||||
"pg": "^8.13.3",
|
||||
@@ -40,5 +44,11 @@
|
||||
"elliptic": "^6.6.1",
|
||||
"secp256k1": "^5.0.0",
|
||||
"cookie": "^0.7.0"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"delay": "2000",
|
||||
"events": {
|
||||
"restart": "kill -SIGUSR2 $PPID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ const { Pool } = require('pg');
|
||||
const { ethers } = require('ethers');
|
||||
const contractABI = require('../artifacts/contracts/MyContract.sol/MyContract.json').abi;
|
||||
const crypto = require('crypto');
|
||||
const TelegramBotService = require('../services/telegramBot');
|
||||
require('dotenv').config();
|
||||
|
||||
const pool = new Pool({
|
||||
@@ -806,8 +807,45 @@ async function initializeTables() {
|
||||
);
|
||||
`);
|
||||
console.log('Таблицы успешно инициализированы');
|
||||
|
||||
// Инициализируем vectorStore
|
||||
vectorStore = await PGVectorStore.initialize(
|
||||
embeddings,
|
||||
{
|
||||
postgresConnectionOptions: {
|
||||
connectionString: process.env.DATABASE_URL
|
||||
},
|
||||
tableName: 'documents',
|
||||
columns: {
|
||||
idColumnName: 'id',
|
||||
vectorColumnName: 'embedding',
|
||||
contentColumnName: 'content',
|
||||
metadataColumnName: 'metadata'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('Векторное хранилище инициализировано:', {
|
||||
tableName: 'documents',
|
||||
columns: vectorStore.columns,
|
||||
config: {
|
||||
tableName: vectorStore.tableName,
|
||||
columns: vectorStore.columns,
|
||||
client: vectorStore.client ? 'Connected' : 'Not Connected',
|
||||
embeddings: vectorStore.embeddings ? 'Initialized' : 'Not Initialized'
|
||||
}
|
||||
});
|
||||
|
||||
// Создаем экземпляр TelegramBotService только после инициализации vectorStore
|
||||
if (vectorStore) {
|
||||
const telegramBot = new TelegramBotService(
|
||||
process.env.TELEGRAM_BOT_TOKEN,
|
||||
vectorStore
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации таблиц:', error);
|
||||
console.error('Ошибка при инициализации:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,24 @@ const { SiweMessage, generateNonce } = require('siwe');
|
||||
const path = require('path');
|
||||
const apiRoutes = require('./routes/api.js');
|
||||
const FileStore = require('session-file-store')(session);
|
||||
const TelegramBotService = require('./services/telegramBot');
|
||||
const EmailBotService = require('./services/emailBot');
|
||||
const { PGVectorStore } = require('@langchain/community/vectorstores/pgvector');
|
||||
const { OpenAIEmbeddings } = require('@langchain/openai');
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
const app = express();
|
||||
|
||||
// Флаг для отслеживания состояния сервера
|
||||
let isShuttingDown = false;
|
||||
|
||||
// Глобальные переменные для сервисов
|
||||
let telegramBot;
|
||||
let emailBot;
|
||||
let vectorStore;
|
||||
|
||||
// 1. Парсинг JSON
|
||||
app.use(express.json());
|
||||
|
||||
@@ -258,4 +273,127 @@ server.on('error', (error) => {
|
||||
server.listen(PORT);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Функция корректного завершения работы
|
||||
async function gracefulShutdown(signal) {
|
||||
if (isShuttingDown) return;
|
||||
isShuttingDown = true;
|
||||
|
||||
console.log(`Получен сигнал ${signal}, начинаем корректное завершение...`);
|
||||
|
||||
try {
|
||||
if (telegramBot) {
|
||||
console.log('Останавливаем Telegram бота...');
|
||||
await telegramBot.stop();
|
||||
}
|
||||
if (emailBot) {
|
||||
console.log('Останавливаем Email бота...');
|
||||
await emailBot.stop();
|
||||
}
|
||||
console.log('Все сервисы остановлены');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при остановке сервисов:', error);
|
||||
}
|
||||
|
||||
// Даем время на завершение всех процессов
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Обработчики сигналов
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2')); // Для nodemon
|
||||
|
||||
// Обработка необработанных исключений
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Необработанное исключение:', error);
|
||||
gracefulShutdown('uncaughtException');
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Необработанное отклонение промиса:', reason);
|
||||
gracefulShutdown('unhandledRejection');
|
||||
});
|
||||
|
||||
// Функция для проверки и остановки существующих процессов бота
|
||||
async function killExistingBotProcesses() {
|
||||
try {
|
||||
console.log('Проверяем существующие процессы бота...');
|
||||
const { stdout } = await execAsync('ps aux | grep "[n]ode.*telegram"');
|
||||
|
||||
if (stdout) {
|
||||
console.log('Найдены существующие процессы бота, останавливаем...');
|
||||
await execAsync('pkill -SIGTERM -f "[n]ode.*telegram"');
|
||||
// Ждем немного, чтобы процессы успели завершиться
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
} catch (error) {
|
||||
// Ошибка означает, что процессы не найдены - это нормально
|
||||
console.log('Активные процессы бота не найдены');
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация векторного хранилища
|
||||
async function initializeVectorStore() {
|
||||
try {
|
||||
// Сначала останавливаем существующие процессы
|
||||
await killExistingBotProcesses();
|
||||
|
||||
// Даем время на освобождение ресурсов
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Инициализируем embeddings
|
||||
const embeddings = new OpenAIEmbeddings({
|
||||
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||
configuration: {
|
||||
baseURL: process.env.OPENAI_API_BASE || 'https://api.openai.com/v1'
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем векторное хранилище
|
||||
vectorStore = await PGVectorStore.initialize(embeddings, {
|
||||
postgresConnectionOptions: {
|
||||
connectionString: process.env.DATABASE_URL
|
||||
},
|
||||
tableName: 'documents',
|
||||
columns: {
|
||||
idColumnName: 'id',
|
||||
vectorColumnName: 'embedding',
|
||||
contentColumnName: 'content',
|
||||
metadataColumnName: 'metadata',
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Векторное хранилище инициализировано');
|
||||
|
||||
// Инициализируем ботов только после успешной инициализации хранилища
|
||||
if (!telegramBot) {
|
||||
telegramBot = new TelegramBotService(process.env.TELEGRAM_BOT_TOKEN, vectorStore);
|
||||
await telegramBot.initialize().catch(error => {
|
||||
if (error.code === 409) {
|
||||
console.log('Telegram бот уже запущен в другом процессе');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!emailBot) {
|
||||
emailBot = new EmailBotService(vectorStore);
|
||||
}
|
||||
|
||||
return vectorStore;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при инициализации:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем инициализацию
|
||||
console.log('Начинаем инициализацию векторного хранилища...');
|
||||
initializeVectorStore().catch(error => {
|
||||
console.error('Критическая ошибка при инициализации:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
277
backend/services/emailBot.js
Normal file
277
backend/services/emailBot.js
Normal file
@@ -0,0 +1,277 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { PGVectorStore } = require('@langchain/community/vectorstores/pgvector');
|
||||
const { Pool } = require('pg');
|
||||
const Imap = require('imap');
|
||||
const { simpleParser } = require('mailparser');
|
||||
const { checkMailServer } = require('../utils/checkMail');
|
||||
require('dotenv').config();
|
||||
|
||||
class EmailBotService {
|
||||
constructor(vectorStore) {
|
||||
if (!vectorStore) {
|
||||
throw new Error('Vector store is required');
|
||||
}
|
||||
|
||||
console.log('Инициализация Email бота...');
|
||||
console.log('Проверяем настройки почты:', {
|
||||
smtp: {
|
||||
host: process.env.EMAIL_SMTP_HOST,
|
||||
port: process.env.EMAIL_SMTP_PORT
|
||||
},
|
||||
imap: {
|
||||
host: process.env.EMAIL_IMAP_HOST,
|
||||
port: process.env.EMAIL_IMAP_PORT
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализация базы данных
|
||||
this.pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
});
|
||||
|
||||
this.vectorStore = vectorStore;
|
||||
|
||||
// Инициализация LLM
|
||||
this.chat = new ChatOllama({
|
||||
model: 'mistral',
|
||||
baseUrl: 'http://localhost:11434'
|
||||
});
|
||||
|
||||
// Настройка почтового клиента для отправки
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_SMTP_HOST,
|
||||
port: process.env.EMAIL_SMTP_PORT,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
minVersion: 'TLSv1',
|
||||
ciphers: 'HIGH:MEDIUM:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:!LOW:!SSLv2:!MD5'
|
||||
},
|
||||
debug: true,
|
||||
logger: true
|
||||
});
|
||||
|
||||
// Проверяем подключение к SMTP
|
||||
this.transporter.verify((error, success) => {
|
||||
if (error) {
|
||||
console.error('Ошибка подключения к SMTP:', {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack
|
||||
});
|
||||
setTimeout(() => this.initSMTP(), 30000);
|
||||
} else {
|
||||
console.log('SMTP сервер готов к отправке сообщений');
|
||||
}
|
||||
});
|
||||
|
||||
// Настройка IMAP для получения писем
|
||||
const imapConfig = {
|
||||
user: process.env.EMAIL_USER,
|
||||
password: process.env.EMAIL_PASSWORD,
|
||||
host: process.env.EMAIL_IMAP_HOST,
|
||||
port: process.env.EMAIL_IMAP_PORT,
|
||||
tls: true,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
keepalive: true,
|
||||
authTimeout: 30000,
|
||||
connTimeout: 30000
|
||||
}
|
||||
this.imap = new Imap(imapConfig);
|
||||
|
||||
// Добавляем обработчик для всех событий IMAP
|
||||
this.imap.on('*', function(event, data) {
|
||||
console.log('IMAP Event:', event, data);
|
||||
});
|
||||
|
||||
// Проверяем MX записи
|
||||
const domain = process.env.EMAIL_USER ? process.env.EMAIL_USER.split('@')[1] : '';
|
||||
if (domain) {
|
||||
checkMailServer(domain).then(records => {
|
||||
if (!records) {
|
||||
console.error('Не удалось найти MX записи для домена');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('EMAIL_USER не настроен в .env файле');
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
this.initSMTP();
|
||||
this.initIMAP();
|
||||
console.log('Email bot service initialized');
|
||||
}
|
||||
|
||||
async initSMTP() {
|
||||
try {
|
||||
console.log('Попытка подключения к SMTP...');
|
||||
await this.transporter.verify();
|
||||
console.log('SMTP подключение установлено');
|
||||
} catch (error) {
|
||||
console.error('Ошибка подключения к SMTP:', {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
command: error.command,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async initIMAP() {
|
||||
try {
|
||||
await this.initEmailListener();
|
||||
console.log('IMAP подключение установлено');
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации IMAP:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async initEmailListener() {
|
||||
try {
|
||||
this.imap.on('ready', () => {
|
||||
this.imap.openBox('INBOX', false, (err, box) => {
|
||||
if (err) throw err;
|
||||
|
||||
// Слушаем новые письма
|
||||
this.imap.on('mail', () => {
|
||||
this.checkNewEmails();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.imap.on('error', (err) => {
|
||||
console.log('IMAP ошибка:', err);
|
||||
if (err.source === 'timeout-auth') {
|
||||
setTimeout(() => {
|
||||
console.log('Попытка переподключения к IMAP...');
|
||||
this.imap.connect();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
this.imap.connect();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при инициализации IMAP:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async processEmail(message) {
|
||||
try {
|
||||
// Проверяем, не является ли отправитель no-reply адресом
|
||||
if (message.from.toLowerCase().includes('no-reply') ||
|
||||
message.from.toLowerCase().includes('noreply')) {
|
||||
console.log('Пропускаем письмо от no-reply адреса:', message.from);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем валидность домена получателя
|
||||
const domain = message.from.split('@')[1];
|
||||
try {
|
||||
const records = await checkMailServer(domain);
|
||||
if (!records || records.length === 0) {
|
||||
console.log('Пропускаем письмо - домен не найден:', domain);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Ошибка проверки домена:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ответ от Ollama
|
||||
const result = await this.chat.invoke(message.text);
|
||||
|
||||
// Формируем и отправляем ответ
|
||||
await this.transporter.sendMail({
|
||||
from: process.env.EMAIL_USER,
|
||||
to: message.from,
|
||||
subject: `Re: ${message.subject}`,
|
||||
text: result.content
|
||||
});
|
||||
|
||||
console.log('Ответ отправлен:', {
|
||||
to: message.from,
|
||||
subject: message.subject
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обработке email:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async checkNewEmails() {
|
||||
try {
|
||||
const messages = await new Promise((resolve, reject) => {
|
||||
this.imap.search(['UNSEEN'], (err, results) => {
|
||||
if (err) reject(err);
|
||||
|
||||
if (!results || !results.length) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetch = this.imap.fetch(results, {
|
||||
bodies: '',
|
||||
markSeen: true
|
||||
});
|
||||
|
||||
const messages = [];
|
||||
|
||||
fetch.on('message', (msg) => {
|
||||
msg.on('body', (stream) => {
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.once('end', () => {
|
||||
messages.push({
|
||||
from: buffer.match(/From: (.*)/i)?.[1],
|
||||
subject: buffer.match(/Subject: (.*)/i)?.[1],
|
||||
text: buffer.split('\n\n').slice(1).join('\n\n')
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
fetch.once('error', reject);
|
||||
fetch.once('end', () => resolve(messages));
|
||||
});
|
||||
});
|
||||
|
||||
// Добавляем задержку между обработкой писем
|
||||
for (const message of messages) {
|
||||
await this.processEmail(message);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 секунда между письмами
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке новых писем:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.isRunning) {
|
||||
console.log('Останавливаем Email бота...');
|
||||
|
||||
// Закрываем SMTP соединение
|
||||
if (this.transporter) {
|
||||
await this.transporter.close();
|
||||
}
|
||||
|
||||
// Закрываем IMAP соединение
|
||||
if (this.imap) {
|
||||
this.imap.end();
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
console.log('Email бот остановлен');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmailBotService;
|
||||
347
backend/services/telegramBot.js
Normal file
347
backend/services/telegramBot.js
Normal file
@@ -0,0 +1,347 @@
|
||||
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');
|
||||
|
||||
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);
|
||||
|
||||
// Пинг для проверки доступности
|
||||
const { exec } = require('child_process');
|
||||
exec(`ping -c 1 api.telegram.org`, (error, stdout, stderr) => {
|
||||
console.log('Результат ping:', stdout);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка DNS резолвинга:', error);
|
||||
throw error; // Прерываем инициализацию если DNS недоступен
|
||||
}
|
||||
|
||||
// Затем проверяем 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramBotService;
|
||||
75
backend/services/telegramBot.js:
Normal file
75
backend/services/telegramBot.js:
Normal file
@@ -0,0 +1,75 @@
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { PGVectorStore } = require('@langchain/community/vectorstores/pgvector');
|
||||
|
||||
class TelegramBotService {
|
||||
constructor(token, vectorStore) {
|
||||
this.bot = new TelegramBot(token, { polling: true });
|
||||
this.vectorStore = vectorStore;
|
||||
this.chat = new ChatOllama({
|
||||
model: 'mistral',
|
||||
baseUrl: 'http://localhost:11434'
|
||||
});
|
||||
|
||||
this.userRequests = new Map(); // для отслеживания запросов
|
||||
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
isRateLimited(userId) {
|
||||
const now = Date.now();
|
||||
const userReqs = this.userRequests.get(userId) || [];
|
||||
|
||||
// Очищаем старые запросы
|
||||
const recentReqs = userReqs.filter(time => now - time < 60000);
|
||||
|
||||
// Максимум 10 запросов в минуту
|
||||
if (recentReqs.length >= 10) return true;
|
||||
|
||||
recentReqs.push(now);
|
||||
this.userRequests.set(userId, recentReqs);
|
||||
return false;
|
||||
}
|
||||
|
||||
setupHandlers() {
|
||||
this.bot.on('message', async (msg) => {
|
||||
const userId = msg.from.id;
|
||||
|
||||
if (this.isRateLimited(userId)) {
|
||||
await this.bot.sendMessage(msg.chat.id,
|
||||
'Пожалуйста, подождите минуту перед следующим запросом.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const chatId = msg.chat.id;
|
||||
const userQuestion = msg.text;
|
||||
|
||||
// Поиск релевантных документов
|
||||
const relevantDocs = await this.vectorStore.similaritySearch(userQuestion, 3);
|
||||
|
||||
// Формируем контекст из найденных документов
|
||||
const context = relevantDocs.map(doc => doc.pageContent).join('\n');
|
||||
|
||||
// Получаем ответ от LLM
|
||||
const response = await this.chat.invoke([
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are a helpful assistant. Use this context to answer: ${context}`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: userQuestion
|
||||
}
|
||||
]);
|
||||
|
||||
await this.bot.sendMessage(chatId, response);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Telegram bot error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramBotService;
|
||||
17
backend/utils/checkMail.js
Normal file
17
backend/utils/checkMail.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const dns = require('dns');
|
||||
const { promisify } = require('util');
|
||||
const resolveMx = promisify(dns.resolveMx);
|
||||
|
||||
async function checkMailServer(domain) {
|
||||
try {
|
||||
console.log(`Проверяем MX записи для домена ${domain}...`);
|
||||
const records = await resolveMx(domain);
|
||||
console.log('Найдены MX записи:', records);
|
||||
return records;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке MX записей:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkMailServer };
|
||||
8
backend/utils/helpers.js
Normal file
8
backend/utils/helpers.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Функция для создания задержки
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep
|
||||
};
|
||||
1296
backend/yarn.lock
1296
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user