Files
DLE/backend/services/emailBot.js

277 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;