Описание изменений
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"deploy": "hardhat run scripts/deploy.js --network sepolia",
|
"deploy": "hardhat run scripts/deploy.js --network sepolia",
|
||||||
"node": "hardhat node",
|
"node": "hardhat node",
|
||||||
"test": "hardhat test",
|
"test": "hardhat test",
|
||||||
"server": "nodemon server.js"
|
"server": "nodemon --signal SIGUSR2 server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@langchain/community": "^0.3.31",
|
"@langchain/community": "^0.3.31",
|
||||||
@@ -18,7 +18,11 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"imap": "^0.8.19",
|
||||||
"langchain": "^0.3.19",
|
"langchain": "^0.3.19",
|
||||||
|
"mailparser": "^3.7.2",
|
||||||
|
"node-telegram-bot-api": "^0.66.0",
|
||||||
|
"nodemailer": "^6.10.0",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"openai": "^4.85.2",
|
"openai": "^4.85.2",
|
||||||
"pg": "^8.13.3",
|
"pg": "^8.13.3",
|
||||||
@@ -40,5 +44,11 @@
|
|||||||
"elliptic": "^6.6.1",
|
"elliptic": "^6.6.1",
|
||||||
"secp256k1": "^5.0.0",
|
"secp256k1": "^5.0.0",
|
||||||
"cookie": "^0.7.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 { ethers } = require('ethers');
|
||||||
const contractABI = require('../artifacts/contracts/MyContract.sol/MyContract.json').abi;
|
const contractABI = require('../artifacts/contracts/MyContract.sol/MyContract.json').abi;
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const TelegramBotService = require('../services/telegramBot');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
@@ -806,8 +807,45 @@ async function initializeTables() {
|
|||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
console.log('Таблицы успешно инициализированы');
|
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) {
|
} catch (error) {
|
||||||
console.error('Ошибка инициализации таблиц:', error);
|
console.error('Ошибка при инициализации:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,24 @@ const { SiweMessage, generateNonce } = require('siwe');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const apiRoutes = require('./routes/api.js');
|
const apiRoutes = require('./routes/api.js');
|
||||||
const FileStore = require('session-file-store')(session);
|
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();
|
const app = express();
|
||||||
|
|
||||||
|
// Флаг для отслеживания состояния сервера
|
||||||
|
let isShuttingDown = false;
|
||||||
|
|
||||||
|
// Глобальные переменные для сервисов
|
||||||
|
let telegramBot;
|
||||||
|
let emailBot;
|
||||||
|
let vectorStore;
|
||||||
|
|
||||||
// 1. Парсинг JSON
|
// 1. Парсинг JSON
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
@@ -259,3 +274,126 @@ server.on('error', (error) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Функция корректного завершения работы
|
||||||
|
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