no message

This commit is contained in:
2025-12-07 16:44:23 +03:00
parent 03a64d6263
commit 6bcac057d1
5 changed files with 76 additions and 43 deletions

View File

@@ -1,5 +1,8 @@
import threading
from typing import Any, Dict
import logging
logger = logging.getLogger()
class _SingletonMeta(type):
_instances = {}
@@ -23,10 +26,10 @@ class Context(metaclass=_SingletonMeta):
"""Очищает self.context, устанавливая его в None или пустой словарь"""
with self._lock: # потокобезопасная очистка
self.context = {}
print("Context очищен") # опциональный лог
logger.debug("Context очищен") # опциональный лог
def set_context(self, new_context: Dict[str, Any]):
"""Устанавливает новый контекст (бонусный метод)"""
with self._lock:
self.context = new_context
print("Новый контекст установлен")
logger.debug("Новый контекст установлен")

View File

@@ -1,3 +1,4 @@
from email.header import decode_header, make_header
import re
from datetime import datetime
from typing import List, Optional
@@ -11,33 +12,28 @@ from email.header import decode_header
import imaplib
import smtplib
from .objects import EmailMessage, EmailAttachment
class EmailMagic:
def __init__(self, email):
self.email = email
def _decode_header(self, header_value: str) -> str:
class EmailUtils:
@staticmethod
def extract_header(msg, header_name) -> str:
"""Декодировать заголовок письма."""
if header_value is None:
header = msg.get(header_name, "")
if header is None:
return ""
decoded = decode_header(header)
return str(make_header(decoded))
decoded_parts = []
for part, encoding in decode_header(header_value):
if isinstance(part, bytes):
if encoding:
try:
decoded_parts.append(part.decode(encoding))
except:
decoded_parts.append(part.decode('utf-8', errors='ignore'))
else:
decoded_parts.append(part.decode('utf-8', errors='ignore'))
else:
decoded_parts.append(str(part))
@staticmethod
def extract_email(text) -> str:
match = re.search(r'<([^<>]+)>', text)
if match:
return match.group(1)
return None
return ''.join(decoded_parts)
def _extract_body(self, msg: email.message.Message) -> str:
@staticmethod
def extract_body(msg: email.message.Message) -> str:
"""Извлечь текст письма из любого типа содержимого, кроме вложений"""
body = ""
if msg.is_multipart():
@@ -65,13 +61,8 @@ class EmailMagic:
return body
def __extract_email(self, text: str) -> str:
match = re.search(r'<([^<>]+)>', text)
if match:
return match.group(1)
return None
def _extract_first_sender(self, body: str):
@staticmethod
def extract_first_sender(body: str):
"""Извлекает адреса отправителей из пересылаемого сообщения. Нужно для отладки"""
# Ищем email внутри скобок после строки "Пересылаемое сообщение"
pattern = r"Пересылаемое сообщение.*?\((.*?)\)"
@@ -80,7 +71,8 @@ class EmailMagic:
return match.group(1)
return None
def _extract_attachments(self, msg: email.message.Message) -> List[EmailAttachment]:
@staticmethod
def _extract_attachments(msg: email.message.Message) -> List[EmailAttachment]:
"""Извлечь вложения из письма."""
attachments = []
@@ -91,9 +83,22 @@ class EmailMagic:
filename = part.get_filename()
if filename:
# Декодируем имя файла
filename = self._decode_header(filename)
filename = decode_header(filename)[0]
# Получаем содержимое
content = part.get_payload(decode=True)
if content:
attachments.append(EmailAttachment(filename=filename, content=content))
return attachments
@staticmethod
def extract_domain(email: str) -> str | None:
"""Вернуть домен из email либо None, если формат странный."""
if "@" not in email:
return None
# убираем пробелы по краям и берём часть после '@'
return email.strip().split("@", 1)[1]

View File

@@ -0,0 +1,11 @@
import logging
from mail_order_bot.email_processor.handlers.abstract_task import AbstractTask
logger = logging.getLogger(__name__)
class AttachmentHandler(AbstractTask):
def do(self) -> None:
email = self.context["email"]

View File

@@ -7,6 +7,7 @@ from pathlib import Path
logger = logging.getLogger(__name__)
from mail_order_bot.context import Context
from mail_order_bot.email_client.utils import EmailUtils
from enum import Enum
@@ -20,7 +21,7 @@ class RequestStatus(Enum):
class EmailProcessor(Context):
def __init__(self, configs_path: Path):
def __init__(self, configs_path: str):
super().__init__()
self.configs_path = configs_path
self.status = RequestStatus.NEW
@@ -33,10 +34,13 @@ class EmailProcessor(Context):
self.context["email"] = email
# Определить клиента
email_body = EmailUtils.extract_body(email)
email_from = EmailUtils.extract_first_sender(email_body)
client = EmailUtils.extract_domain(email_from)
try:
# Определить конфиг для пайплайна
config = {}
config = self._load_config(client)
# Запустить обработку пайплайна
for stage in config["pipeline"]:
@@ -45,8 +49,15 @@ class EmailProcessor(Context):
task = globals()[handler_name](stage.get("config", None), self.context)
task.do()
except FileNotFoundError:
logger.error(f"Конфиг для клиента {client} не найден")
#except Exception as e:
# logger.error(f"Произошла другая ошибка: {e}")
def _load_config(self, client) -> Dict[str, Any]:
"""Загружает конфигурацию из YAML или JSON"""
path = os.path.join(self.configs_path, client + '.yml')
with open(path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)

View File

@@ -34,10 +34,12 @@ class MailOrderBot(ConfigManager):
self.context.email_client = self.email_client
# Обработчик писем
self.email_processor = EmailProcessor()
self.email_processor = EmailProcessor("./configs")
logger.warning("MailOrderBot инициализирован")
def execute(self):
logger.debug(f"Check emails for new orders")
logger.info(f"Проверяем почту на наличие новых писем")
# Получить список айдишников письма
unread_email_ids = self.email_client.get_emails_id(folder="spareparts")
@@ -46,6 +48,7 @@ class MailOrderBot(ConfigManager):
# Обработать каждое письмо по идентификатору
for email_id in unread_email_ids:
logger.debug(f"==================================================")
logger.debug(f"Обработка письма с идентификатором {email_id}")
# Получить письмо по идентификатору и запустить его обработку
email = self.email_client.get_email(email_id)