diff --git a/maxapi/bot.py b/maxapi/bot.py index c188715..70d10ff 100644 --- a/maxapi/bot.py +++ b/maxapi/bot.py @@ -1,68 +1,69 @@ +from __future__ import annotations + from datetime import datetime -from typing import Any, Dict, List, TYPE_CHECKING, Optional - -from .methods.download_media import DownloadMedia -from .methods.get_upload_url import GetUploadURL -from .methods.get_updates import GetUpdates -from .methods.remove_member_chat import RemoveMemberChat -from .methods.add_admin_chat import AddAdminChat -from .methods.add_members_chat import AddMembersChat -from .methods.get_members_chat import GetMembersChat -from .methods.remove_admin import RemoveAdmin -from .methods.get_list_admin_chat import GetListAdminChat -from .methods.delete_bot_from_chat import DeleteMeFromMessage -from .methods.get_me_from_chat import GetMeFromChat -from .methods.delete_pin_message import DeletePinMessage -from .methods.get_pinned_message import GetPinnedMessage -from .methods.pin_message import PinMessage -from .methods.delete_chat import DeleteChat -from .methods.send_action import SendAction -from .methods.edit_chat import EditChat -from .methods.get_chat_by_id import GetChatById -from .methods.get_chat_by_link import GetChatByLink -from .methods.send_callback import SendCallback -from .methods.get_video import GetVideo -from .methods.delete_message import DeleteMessage -from .methods.edit_message import EditMessage -from .methods.change_info import ChangeInfo -from .methods.get_me import GetMe -from .methods.get_messages import GetMessages -from .methods.get_chats import GetChats -from .methods.send_message import SendMessage +from typing import Any, Dict, List, Optional, TYPE_CHECKING +from .connection.base import BaseConnection from .enums.parse_mode import ParseMode from .enums.sender_action import SenderAction from .enums.upload_type import UploadType -from .types.message import Message -from .types.attachments.attachment import Attachment -from .types.attachments.image import PhotoAttachmentRequestPayload -from .types.message import Messages, NewMessageLink -from .types.users import ChatAdmin, User -from .types.command import BotCommand +from .methods.add_admin_chat import AddAdminChat +from .methods.add_members_chat import AddMembersChat +from .methods.change_info import ChangeInfo +from .methods.delete_bot_from_chat import DeleteMeFromMessage +from .methods.delete_chat import DeleteChat +from .methods.delete_message import DeleteMessage +from .methods.delete_pin_message import DeletePinMessage +from .methods.download_media import DownloadMedia +from .methods.edit_chat import EditChat +from .methods.edit_message import EditMessage +from .methods.get_chat_by_id import GetChatById +from .methods.get_chat_by_link import GetChatByLink +from .methods.get_chats import GetChats +from .methods.get_list_admin_chat import GetListAdminChat +from .methods.get_me import GetMe +from .methods.get_me_from_chat import GetMeFromChat +from .methods.get_members_chat import GetMembersChat +from .methods.get_messages import GetMessages +from .methods.get_pinned_message import GetPinnedMessage +from .methods.get_updates import GetUpdates +from .methods.get_upload_url import GetUploadURL +from .methods.get_video import GetVideo +from .methods.pin_message import PinMessage +from .methods.remove_admin import RemoveAdmin +from .methods.remove_member_chat import RemoveMemberChat +from .methods.send_action import SendAction +from .methods.send_callback import SendCallback +from .methods.send_message import SendMessage -from .connection.base import BaseConnection +if TYPE_CHECKING: + from .types.attachments.attachment import Attachment + from .types.attachments.image import PhotoAttachmentRequestPayload + from .types.attachments.video import Video + from .types.chats import Chat, ChatMember, Chats + from .types.command import BotCommand + from .types.message import Message, Messages, NewMessageLink + from .types.updates import UpdateUnion + from .types.users import ChatAdmin, User -from .methods.types.added_admin_chat import AddedListAdminChat -from .methods.types.added_members_chat import AddedMembersChat -from .methods.types.deleted_bot_from_chat import DeletedBotFromChat -from .methods.types.deleted_chat import DeletedChat -from .methods.types.deleted_message import DeletedMessage -from .methods.types.deleted_pin_message import DeletedPinMessage -from .methods.types.edited_message import EditedMessage -from .methods.types.getted_list_admin_chat import GettedListAdminChat -from .methods.types.getted_members_chat import GettedMembersChat -from .methods.types.getted_pineed_message import GettedPin -from .methods.types.getted_upload_url import GettedUploadUrl -from .methods.types.pinned_message import PinnedMessage -from .methods.types.removed_admin import RemovedAdmin -from .methods.types.removed_member_chat import RemovedMemberChat -from .methods.types.sended_action import SendedAction -from .methods.types.sended_callback import SendedCallback -from .methods.types.sended_message import SendedMessage -from .types.attachments.video import Video -from .types.chats import Chat, ChatMember, Chats -from .types.updates import UpdateUnion + from .methods.types.added_admin_chat import AddedListAdminChat + from .methods.types.added_members_chat import AddedMembersChat + from .methods.types.deleted_bot_from_chat import DeletedBotFromChat + from .methods.types.deleted_chat import DeletedChat + from .methods.types.deleted_message import DeletedMessage + from .methods.types.deleted_pin_message import DeletedPinMessage + from .methods.types.edited_message import EditedMessage + from .methods.types.getted_list_admin_chat import GettedListAdminChat + from .methods.types.getted_members_chat import GettedMembersChat + from .methods.types.getted_pineed_message import GettedPin + from .methods.types.getted_upload_url import GettedUploadUrl + from .methods.types.pinned_message import PinnedMessage + from .methods.types.removed_admin import RemovedAdmin + from .methods.types.removed_member_chat import RemovedMemberChat + from .methods.types.sended_action import SendedAction + from .methods.types.sended_callback import SendedCallback + from .methods.types.sended_message import SendedMessage class Bot(BaseConnection): @@ -102,6 +103,12 @@ class Bot(BaseConnection): self.notify = notify self.auto_requests = auto_requests + def _resolve_notify(self, notify: Optional[bool]) -> Optional[bool]: + return notify if notify is not None else self.notify + + def _resolve_parse_mode(self, mode: Optional[ParseMode]) -> Optional[ParseMode]: + return mode if mode is not None else self.parse_mode + async def send_message( self, chat_id: int = None, @@ -133,10 +140,8 @@ class Bot(BaseConnection): text=text, attachments=attachments, link=link, - notify=notify if notify \ - else self.notify, - parse_mode=parse_mode if parse_mode \ - else self.parse_mode + notify=self._resolve_notify(notify), + parse_mode=self._resolve_parse_mode(notify) ).request() async def send_action( @@ -187,10 +192,8 @@ class Bot(BaseConnection): text=text, attachments=attachments, link=link, - notify=notify if notify \ - else self.notify, - parse_mode=parse_mode if parse_mode \ - else self.parse_mode + notify=self._resolve_notify(notify), + parse_mode=self._resolve_parse_mode(notify) ).request() async def delete_message( @@ -398,8 +401,7 @@ class Bot(BaseConnection): icon=icon, title=title, pin=pin, - notify=notify if notify \ - else self.notify, + notify=self._resolve_notify(notify), ).request() async def get_video( @@ -422,7 +424,7 @@ class Bot(BaseConnection): async def send_callback( self, callback_id: str, - message: 'Message' = None, + message: Message = None, notification: str = None ) -> SendedCallback: @@ -462,8 +464,7 @@ class Bot(BaseConnection): bot=self, chat_id=chat_id, message_id=message_id, - notify=notify if notify \ - else self.notify, + notify=self._resolve_notify(notify), ).request() async def delete_pin_message( diff --git a/maxapi/connection/base.py b/maxapi/connection/base.py index 2179570..7951521 100644 --- a/maxapi/connection/base.py +++ b/maxapi/connection/base.py @@ -113,8 +113,8 @@ class BaseConnection: :return: Сырой .text() ответ от сервера после загрузки файла """ - with open(path, 'rb') as f: - file_data = f.read() + async with aiofiles.open(path, 'rb') as f: + file_data = await f.read() basename = os.path.basename(path) _, ext = os.path.splitext(basename) diff --git a/maxapi/dispatcher.py b/maxapi/dispatcher.py index 43d1a30..0a93d65 100644 --- a/maxapi/dispatcher.py +++ b/maxapi/dispatcher.py @@ -1,3 +1,5 @@ +import asyncio + from typing import Any, Callable, Dict, List from fastapi import FastAPI, Request @@ -22,7 +24,8 @@ from .enums.update import UpdateType from .loggers import logger_dp -app = FastAPI() +webhook_app = FastAPI() +CONNECTION_RETRY_DELAY = 30 class Dispatcher: @@ -39,8 +42,8 @@ class Dispatcher: self.filters: List[MagicFilter] = [] self.middlewares: List[BaseMiddleware] = [] - self.bot = None - self.on_started_func = None + self.bot: Bot = None + self.on_started_func: Callable = None self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self) self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self) @@ -230,7 +233,8 @@ class Dispatcher: for event in processed_events: await self.handle(event) except ClientConnectorError: - logger_dp.error(f'Ошибка подключения: {e}') + logger_dp.error(f'Ошибка подключения, жду {CONNECTION_RETRY_DELAY} секунд') + await asyncio.sleep(CONNECTION_RETRY_DELAY) except Exception as e: logger_dp.error(f'Общая ошибка при обработке событий: {e}') @@ -246,7 +250,7 @@ class Dispatcher: await self.__ready(bot) - @app.post('/') + @webhook_app.post('/') async def _(request: Request): try: event_json = await request.json() @@ -262,7 +266,7 @@ class Dispatcher: except Exception as e: logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {e}") - config = Config(app=app, host=host, port=port, log_level="critical") + config = Config(app=webhook_app, host=host, port=port, log_level="critical") server = Server(config) await server.serve() diff --git a/maxapi/methods/send_callback.py b/maxapi/methods/send_callback.py index 5ecb025..4355247 100644 --- a/maxapi/methods/send_callback.py +++ b/maxapi/methods/send_callback.py @@ -1,5 +1,4 @@ - - +from __future__ import annotations from typing import TYPE_CHECKING from ..methods.types.sended_callback import SendedCallback @@ -37,7 +36,7 @@ class SendCallback(BaseConnection): self, bot: 'Bot', callback_id: str, - message: 'Message' = None, + message: Message = None, notification: str = None ): self.bot = bot diff --git a/maxapi/methods/send_message.py b/maxapi/methods/send_message.py index 0f14e36..d9ecfb6 100644 --- a/maxapi/methods/send_message.py +++ b/maxapi/methods/send_message.py @@ -23,10 +23,10 @@ from ..loggers import logger_bot if TYPE_CHECKING: from ..bot import Bot + - -class UploadResponse: - token: str = None +RETRY_DELAY = 2 +ATTEMPTS_COUNT = 5 class SendMessage(BaseConnection): @@ -70,6 +70,9 @@ class SendMessage(BaseConnection): att: InputMedia ): + # очень нестабильный метод независящий от модуля + # ждем обновлений MAX API + """ Загружает файл вложения и формирует объект AttachmentUpload. @@ -140,11 +143,11 @@ class SendMessage(BaseConnection): json['attachments'].append(att.model_dump()) if not self.link is None: json['link'] = self.link.model_dump() - if not self.notify is None: json['notify'] = self.notify + json['notify'] = self.notify if not self.parse_mode is None: json['format'] = self.parse_mode.value response = None - for attempt in range(5): + for attempt in range(ATTEMPTS_COUNT): response = await super().request( method=HTTPMethod.POST, path=ApiPath.MESSAGES, @@ -155,8 +158,8 @@ class SendMessage(BaseConnection): if isinstance(response, Error): if response.raw.get('code') == 'attachment.not.ready': - logger_bot.info(f'Ошибка при отправке загруженного медиа, попытка {attempt+1}, жду 2 секунды') - await asyncio.sleep(2) + logger_bot.info(f'Ошибка при отправке загруженного медиа, попытка {attempt+1}, жду {RETRY_DELAY} секунды') + await asyncio.sleep(RETRY_DELAY) continue return response diff --git a/pyproject.toml b/pyproject.toml index f6c5ff7..8ea311a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "magic_filter>=1.0.0", "pydantic>=1.8.0", "uvicorn>=0.15.0", + "aiofiles==24.1.0", ] [project.urls]