no message
This commit is contained in:
@@ -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("Новый контекст установлен")
|
||||
@@ -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]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user