no message
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
import threading
|
import threading
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
class _SingletonMeta(type):
|
class _SingletonMeta(type):
|
||||||
_instances = {}
|
_instances = {}
|
||||||
@@ -23,10 +26,10 @@ class Context(metaclass=_SingletonMeta):
|
|||||||
"""Очищает self.context, устанавливая его в None или пустой словарь"""
|
"""Очищает self.context, устанавливая его в None или пустой словарь"""
|
||||||
with self._lock: # потокобезопасная очистка
|
with self._lock: # потокобезопасная очистка
|
||||||
self.context = {}
|
self.context = {}
|
||||||
print("Context очищен") # опциональный лог
|
logger.debug("Context очищен") # опциональный лог
|
||||||
|
|
||||||
def set_context(self, new_context: Dict[str, Any]):
|
def set_context(self, new_context: Dict[str, Any]):
|
||||||
"""Устанавливает новый контекст (бонусный метод)"""
|
"""Устанавливает новый контекст (бонусный метод)"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.context = new_context
|
self.context = new_context
|
||||||
print("Новый контекст установлен")
|
logger.debug("Новый контекст установлен")
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from email.header import decode_header, make_header
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
@@ -11,33 +12,28 @@ from email.header import decode_header
|
|||||||
import imaplib
|
import imaplib
|
||||||
import smtplib
|
import smtplib
|
||||||
|
|
||||||
|
from .objects import EmailMessage, EmailAttachment
|
||||||
|
|
||||||
|
|
||||||
class EmailMagic:
|
class EmailUtils:
|
||||||
def __init__(self, email):
|
@staticmethod
|
||||||
self.email = email
|
def extract_header(msg, header_name) -> str:
|
||||||
|
|
||||||
def _decode_header(self, header_value: str) -> str:
|
|
||||||
"""Декодировать заголовок письма."""
|
"""Декодировать заголовок письма."""
|
||||||
if header_value is None:
|
header = msg.get(header_name, "")
|
||||||
|
if header is None:
|
||||||
return ""
|
return ""
|
||||||
|
decoded = decode_header(header)
|
||||||
|
return str(make_header(decoded))
|
||||||
|
|
||||||
decoded_parts = []
|
@staticmethod
|
||||||
for part, encoding in decode_header(header_value):
|
def extract_email(text) -> str:
|
||||||
if isinstance(part, bytes):
|
match = re.search(r'<([^<>]+)>', text)
|
||||||
if encoding:
|
if match:
|
||||||
try:
|
return match.group(1)
|
||||||
decoded_parts.append(part.decode(encoding))
|
return None
|
||||||
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))
|
|
||||||
|
|
||||||
return ''.join(decoded_parts)
|
@staticmethod
|
||||||
|
def extract_body(msg: email.message.Message) -> str:
|
||||||
def _extract_body(self, msg: email.message.Message) -> str:
|
|
||||||
"""Извлечь текст письма из любого типа содержимого, кроме вложений"""
|
"""Извлечь текст письма из любого типа содержимого, кроме вложений"""
|
||||||
body = ""
|
body = ""
|
||||||
if msg.is_multipart():
|
if msg.is_multipart():
|
||||||
@@ -65,13 +61,8 @@ class EmailMagic:
|
|||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def __extract_email(self, text: str) -> str:
|
@staticmethod
|
||||||
match = re.search(r'<([^<>]+)>', text)
|
def extract_first_sender(body: str):
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _extract_first_sender(self, body: str):
|
|
||||||
"""Извлекает адреса отправителей из пересылаемого сообщения. Нужно для отладки"""
|
"""Извлекает адреса отправителей из пересылаемого сообщения. Нужно для отладки"""
|
||||||
# Ищем email внутри скобок после строки "Пересылаемое сообщение"
|
# Ищем email внутри скобок после строки "Пересылаемое сообщение"
|
||||||
pattern = r"Пересылаемое сообщение.*?\((.*?)\)"
|
pattern = r"Пересылаемое сообщение.*?\((.*?)\)"
|
||||||
@@ -80,7 +71,8 @@ class EmailMagic:
|
|||||||
return match.group(1)
|
return match.group(1)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _extract_attachments(self, msg: email.message.Message) -> List[EmailAttachment]:
|
@staticmethod
|
||||||
|
def _extract_attachments(msg: email.message.Message) -> List[EmailAttachment]:
|
||||||
"""Извлечь вложения из письма."""
|
"""Извлечь вложения из письма."""
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
@@ -91,9 +83,22 @@ class EmailMagic:
|
|||||||
filename = part.get_filename()
|
filename = part.get_filename()
|
||||||
if filename:
|
if filename:
|
||||||
# Декодируем имя файла
|
# Декодируем имя файла
|
||||||
filename = self._decode_header(filename)
|
filename = decode_header(filename)[0]
|
||||||
# Получаем содержимое
|
# Получаем содержимое
|
||||||
content = part.get_payload(decode=True)
|
content = part.get_payload(decode=True)
|
||||||
if content:
|
if content:
|
||||||
attachments.append(EmailAttachment(filename=filename, content=content))
|
attachments.append(EmailAttachment(filename=filename, content=content))
|
||||||
return attachments
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from mail_order_bot.context import Context
|
from mail_order_bot.context import Context
|
||||||
|
from mail_order_bot.email_client.utils import EmailUtils
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ class RequestStatus(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class EmailProcessor(Context):
|
class EmailProcessor(Context):
|
||||||
def __init__(self, configs_path: Path):
|
def __init__(self, configs_path: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.configs_path = configs_path
|
self.configs_path = configs_path
|
||||||
self.status = RequestStatus.NEW
|
self.status = RequestStatus.NEW
|
||||||
@@ -33,10 +34,13 @@ class EmailProcessor(Context):
|
|||||||
self.context["email"] = email
|
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"]:
|
for stage in config["pipeline"]:
|
||||||
@@ -45,8 +49,15 @@ class EmailProcessor(Context):
|
|||||||
task = globals()[handler_name](stage.get("config", None), self.context)
|
task = globals()[handler_name](stage.get("config", None), self.context)
|
||||||
task.do()
|
task.do()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"Конфиг для клиента {client} не найден")
|
||||||
|
#except Exception as e:
|
||||||
|
# logger.error(f"Произошла другая ошибка: {e}")
|
||||||
|
|
||||||
|
|
||||||
def _load_config(self, client) -> Dict[str, Any]:
|
def _load_config(self, client) -> Dict[str, Any]:
|
||||||
"""Загружает конфигурацию из YAML или JSON"""
|
"""Загружает конфигурацию из YAML или JSON"""
|
||||||
|
|
||||||
path = os.path.join(self.configs_path, client + '.yml')
|
path = os.path.join(self.configs_path, client + '.yml')
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
return yaml.safe_load(f)
|
return yaml.safe_load(f)
|
||||||
@@ -34,10 +34,12 @@ class MailOrderBot(ConfigManager):
|
|||||||
self.context.email_client = self.email_client
|
self.context.email_client = self.email_client
|
||||||
|
|
||||||
# Обработчик писем
|
# Обработчик писем
|
||||||
self.email_processor = EmailProcessor()
|
self.email_processor = EmailProcessor("./configs")
|
||||||
|
logger.warning("MailOrderBot инициализирован")
|
||||||
|
|
||||||
|
|
||||||
def execute(self):
|
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")
|
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:
|
for email_id in unread_email_ids:
|
||||||
|
logger.debug(f"==================================================")
|
||||||
logger.debug(f"Обработка письма с идентификатором {email_id}")
|
logger.debug(f"Обработка письма с идентификатором {email_id}")
|
||||||
# Получить письмо по идентификатору и запустить его обработку
|
# Получить письмо по идентификатору и запустить его обработку
|
||||||
email = self.email_client.get_email(email_id)
|
email = self.email_client.get_email(email_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user