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;