From 4cb8c9c88e528cabee9a77e795da0e2ac992089d Mon Sep 17 00:00:00 2001 From: zosimovaa Date: Mon, 29 Dec 2025 06:55:15 +0300 Subject: [PATCH] Add Telegram Bot settings to example.env --- example.env | 4 + .../abcp_api/telegram/__init__.py | 0 .../handlers/abcp/api_save_order.py | 0 .../handlers/destination_time/from_config.py | 0 .../handlers/email/email_parcer.py | 41 ++++ .../excel_parcers/update_excel_file.py | 51 +++++ .../stock_selectors/stock_selector.py | 0 src/mail_order_bot/telegram/__init__.py | 4 + src/mail_order_bot/telegram/client.py | 176 ++++++++++++++++++ 9 files changed, 276 insertions(+) create mode 100644 src/mail_order_bot/abcp_api/telegram/__init__.py create mode 100644 src/mail_order_bot/task_processor/handlers/abcp/api_save_order.py create mode 100644 src/mail_order_bot/task_processor/handlers/destination_time/from_config.py create mode 100644 src/mail_order_bot/task_processor/handlers/email/email_parcer.py create mode 100644 src/mail_order_bot/task_processor/handlers/excel_parcers/update_excel_file.py create mode 100644 src/mail_order_bot/task_processor/handlers/stock_selectors/stock_selector.py create mode 100644 src/mail_order_bot/telegram/__init__.py create mode 100644 src/mail_order_bot/telegram/client.py diff --git a/example.env b/example.env index b04e0fe..66502d6 100644 --- a/example.env +++ b/example.env @@ -10,3 +10,7 @@ IMAP_PORT=993 SMTP_HOST=smtp.gmail.com SMTP_PORT=587 +# Telegram Bot settings +TELEGRAM_BOT_TOKEN=your_bot_token_here +TELEGRAM_CHAT_ID=your_chat_id_here + diff --git a/src/mail_order_bot/abcp_api/telegram/__init__.py b/src/mail_order_bot/abcp_api/telegram/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mail_order_bot/task_processor/handlers/abcp/api_save_order.py b/src/mail_order_bot/task_processor/handlers/abcp/api_save_order.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mail_order_bot/task_processor/handlers/destination_time/from_config.py b/src/mail_order_bot/task_processor/handlers/destination_time/from_config.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mail_order_bot/task_processor/handlers/email/email_parcer.py b/src/mail_order_bot/task_processor/handlers/email/email_parcer.py new file mode 100644 index 0000000..6aa758f --- /dev/null +++ b/src/mail_order_bot/task_processor/handlers/email/email_parcer.py @@ -0,0 +1,41 @@ +""" +Обрабатывает письмо + +""" +import logging + +from mail_order_bot.task_processor.abstract_task import AbstractTask +from mail_order_bot.email_client.utils import EmailUtils + +logger = logging.getLogger(__name__) + +class EmailParcer(AbstractTask): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def do(self) -> None: + # Определить клиента + email = self.context.data.get("email", None) + if email is not None: + email_body = EmailUtils.extract_body(email) + self.context.data["email_body"] = email_body + + # todo при переводе на основной ящик переделать на другую функцию + email_from = EmailUtils.extract_first_sender(email_body) + self.context.data["email_from"] = email_from + + email_subj = EmailUtils.extract_header(email, "subj") + self.context.data["email_subj"] = email_subj + + client = EmailUtils.extract_domain(email_from) + self.context.data["client"] = client + + attachments = EmailUtils.extract_attachments(email) + self.context.data["attachments"] = attachments + logger.info(f"Извлечено вложений: {len(attachments)} ") + + + + + + diff --git a/src/mail_order_bot/task_processor/handlers/excel_parcers/update_excel_file.py b/src/mail_order_bot/task_processor/handlers/excel_parcers/update_excel_file.py new file mode 100644 index 0000000..1c44851 --- /dev/null +++ b/src/mail_order_bot/task_processor/handlers/excel_parcers/update_excel_file.py @@ -0,0 +1,51 @@ +import logging +import pandas as pd +from io import BytesIO +# from mail_order_bot.task_processor.handlers.order_position import OrderPosition +from mail_order_bot.task_processor.abstract_task import AbstractTask + + +from mail_order_bot.order.auto_part_position import PositionStatus +from mail_order_bot.parsers.excel_parcer import ExcelFileParcer + +logger = logging.getLogger(__name__) + + +class UpdateExcelFile(AbstractTask): + """ + Хендлер для каждого вложения считывает эксель файл и сохраняет его контекст + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.excel_config = self.config.get("excel", {}) + + def do(self) -> None: + # todo сделать проверку на наличие файла и его тип + + attachments = self.context.data.get("attachments", []) + for attachment in attachments: + excel_file = attachment.get("excel") + order = attachment.get("order") + config = self.context.data.get("excel", {}) + updatable_fields = config.get("updatable_fields", {}) + + for position in order.positions: + if position.status == PositionStatus.READY: + sku = position.sku + manufacturer = position.manufacturer + + for key, value in updatable_fields.items(): + + if key == "ordered_quantity": + column = value + value = position.ordered_quantity + excel_file.set_value(sku, manufacturer, column, value) + + if key == "ordered_price": + column = value + value = position.ordered_price + excel_file.set_value(sku, manufacturer, column, value) + pass + + diff --git a/src/mail_order_bot/task_processor/handlers/stock_selectors/stock_selector.py b/src/mail_order_bot/task_processor/handlers/stock_selectors/stock_selector.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mail_order_bot/telegram/__init__.py b/src/mail_order_bot/telegram/__init__.py new file mode 100644 index 0000000..5614dfd --- /dev/null +++ b/src/mail_order_bot/telegram/__init__.py @@ -0,0 +1,4 @@ +from mail_order_bot.telegram.client import TelegramClient + +__all__ = ['TelegramClient'] + diff --git a/src/mail_order_bot/telegram/client.py b/src/mail_order_bot/telegram/client.py new file mode 100644 index 0000000..3156d2a --- /dev/null +++ b/src/mail_order_bot/telegram/client.py @@ -0,0 +1,176 @@ +import os +import logging +import requests +from typing import Optional +from io import BytesIO + +logger = logging.getLogger(__name__) + + +class TelegramClient: + """ + Класс для отправки сообщений через Telegram Bot API. + + Поддерживает отправку: + - Текстовых сообщений + - Сообщений с вложением (Excel файл в формате BytesIO) + """ + + BASE_URL = "https://api.telegram.org/bot" + + def __init__(self, bot_token: Optional[str] = None, chat_id: Optional[str] = None): + """ + Инициализация TelegramClient. + + Args: + bot_token: Токен бота Telegram. Если не указан, берется из TELEGRAM_BOT_TOKEN + chat_id: ID чата для отправки сообщений. Если не указан, берется из TELEGRAM_CHAT_ID + """ + self.bot_token = bot_token or os.getenv('TELEGRAM_BOT_TOKEN') + self.chat_id = chat_id or os.getenv('TELEGRAM_CHAT_ID') + + if not self.bot_token: + raise ValueError("Telegram bot token is required. Set TELEGRAM_BOT_TOKEN environment variable or pass bot_token parameter.") + + if not self.chat_id: + raise ValueError("Telegram chat ID is required. Set TELEGRAM_CHAT_ID environment variable or pass chat_id parameter.") + + self.api_url = f"{self.BASE_URL}{self.bot_token}" + + def send_message(self, text: str, parse_mode: Optional[str] = None) -> dict: + """ + Отправляет текстовое сообщение в Telegram. + + Args: + text: Текст сообщения для отправки + parse_mode: Режим парсинга (HTML, Markdown, MarkdownV2). По умолчанию None + + Returns: + dict: Результат запроса с полями success (bool) и data/error + """ + url = f"{self.api_url}/sendMessage" + + payload = { + "chat_id": self.chat_id, + "text": text + } + + if parse_mode: + payload["parse_mode"] = parse_mode + + try: + response = requests.post(url, json=payload) + response.raise_for_status() + + result = response.json() + if result.get("ok"): + logger.debug(f"Сообщение успешно отправлено в Telegram") + return { + "success": True, + "data": result.get("result") + } + else: + error_description = result.get("description", "Unknown error") + logger.warning(f"Ошибка отправки сообщения в Telegram: {error_description}") + return { + "success": False, + "error": error_description + } + + except requests.exceptions.RequestException as e: + logger.error(f"Ошибка при отправке сообщения в Telegram: {e}") + return { + "success": False, + "error": str(e) + } + + def send_document( + self, + document: BytesIO, + filename: str = "document.xlsx", + caption: Optional[str] = None, + parse_mode: Optional[str] = None + ) -> dict: + """ + Отправляет документ (Excel файл) в Telegram. + + Args: + document: BytesIO объект с содержимым файла + filename: Имя файла для отправки (по умолчанию "document.xlsx") + caption: Подпись к документу (опционально) + parse_mode: Режим парсинга для подписи (HTML, Markdown, MarkdownV2). По умолчанию None + + Returns: + dict: Результат запроса с полями success (bool) и data/error + """ + url = f"{self.api_url}/sendDocument" + + # Убедимся, что указатель файла находится в начале + document.seek(0) + + files = { + "document": (filename, document, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + } + + data = { + "chat_id": self.chat_id + } + + if caption: + data["caption"] = caption + + if parse_mode: + data["parse_mode"] = parse_mode + + try: + response = requests.post(url, files=files, data=data) + response.raise_for_status() + + result = response.json() + if result.get("ok"): + logger.debug(f"Документ успешно отправлен в Telegram") + return { + "success": True, + "data": result.get("result") + } + else: + error_description = result.get("description", "Unknown error") + logger.warning(f"Ошибка отправки документа в Telegram: {error_description}") + return { + "success": False, + "error": error_description + } + + except requests.exceptions.RequestException as e: + logger.error(f"Ошибка при отправке документа в Telegram: {e}") + return { + "success": False, + "error": str(e) + } + + def send_message_with_document( + self, + text: str, + document: BytesIO, + filename: str = "document.xlsx", + parse_mode: Optional[str] = None + ) -> dict: + """ + Отправляет сообщение с документом. Текст используется как подпись к документу. + + Args: + text: Текст сообщения (будет использован как подпись к документу) + document: BytesIO объект с содержимым файла + filename: Имя файла для отправки (по умолчанию "document.xlsx") + parse_mode: Режим парсинга для подписи (HTML, Markdown, MarkdownV2). По умолчанию None + + Returns: + dict: Результат запроса с полями success (bool) и data/error + """ + return self.send_document( + document=document, + filename=filename, + caption=text, + parse_mode=parse_mode + ) +