From b2283ab53860ff6cdc44a4df7950938a1bdbbbcd Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 17 Jun 2025 23:14:25 +0300 Subject: [PATCH] upd --- example.py | 18 +++++-- maxapi/bot.py | 62 ++++++++++++++++++++-- maxapi/connection/base.py | 17 +++++- maxapi/dispatcher.py | 4 +- maxapi/enums/api_path.py | 4 +- maxapi/enums/http_method.py | 1 + maxapi/methods/delete_message.py | 33 ++++++++++++ maxapi/methods/edit_message.py | 16 +++--- maxapi/methods/get_messages.py | 34 +++++++++++- maxapi/methods/get_video.py | 34 ++++++++++++ maxapi/methods/send_callback.py | 53 +++++++++++++++++++ maxapi/methods/send_message.py | 53 +++++++++---------- maxapi/methods/types/deleted_message.py | 7 +++ maxapi/methods/types/edited_message.py | 6 +-- maxapi/methods/types/sended_callback.py | 14 +++++ maxapi/methods/types/sended_message.py | 1 + maxapi/types/attachments/video.py | 25 +++++++-- maxapi/types/callback.py | 4 +- maxapi/types/message.py | 34 ++++++++++-- maxapi/types/updates/message_callback.py | 66 +++++++++++++++++++++++- maxapi/types/updates/message_created.py | 14 ++++- 21 files changed, 437 insertions(+), 63 deletions(-) create mode 100644 maxapi/methods/delete_message.py create mode 100644 maxapi/methods/get_video.py create mode 100644 maxapi/methods/send_callback.py create mode 100644 maxapi/methods/types/deleted_message.py create mode 100644 maxapi/methods/types/sended_callback.py diff --git a/example.py b/example.py index b34f575..dfc8d6f 100644 --- a/example.py +++ b/example.py @@ -15,9 +15,14 @@ bot = Bot('токен') dp = Dispatcher() # Отвечает только на текст "Привет" -@dp.message_created(F.message.body.text == 'Привет') +@dp.message_created(F.message.body.text == 'q') async def hello(obj: MessageCreated): - await obj.message.answer('Привет 👋') + msg = await obj.message.answer('Привет 👋') + + a = await obj.bot.get_video('f9LHodD0cOJ5BfLGZ81uXgypU1z7PNhJMkmIe_dtEcxfC3V8vxWk65mRJX8MFQ5F9OAs3yDgbUv6DS6X1p7P') + + ... + # Отвечает только на текст "Клавиатура" @dp.message_created(F.message.body.text == 'Клавиатура') @@ -34,7 +39,8 @@ async def hello(obj: MessageCreated): # Ответчает на коллбек с начинкой "1" @dp.message_callback(F.callback.payload == '1') async def _(obj: MessageCallback): - await obj.message.answer('Вы нажали на кнопку 1 🤩') + a = await obj.answer('test') + ... # Ответчает на коллбек с начинкой "2" @dp.message_callback(F.callback.payload == '2') @@ -47,4 +53,10 @@ async def hello(obj: MessageCreated): await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}') +@dp.message_created() +async def hello(obj: MessageCreated): + # await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}') + pass + + dp.handle_webhook(bot) \ No newline at end of file diff --git a/maxapi/bot.py b/maxapi/bot.py index b44c409..a1a272b 100644 --- a/maxapi/bot.py +++ b/maxapi/bot.py @@ -1,5 +1,11 @@ -from typing import Any, Dict, List +from datetime import datetime +from typing import Any, Dict, List, TYPE_CHECKING +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 .enums.parse_mode import ParseMode from .types.attachments.attachment import Attachment @@ -11,11 +17,17 @@ from .methods.get_messages import GetMessages from .methods.get_chats import GetChats from .methods.send_message import SendMessage from .connection.base import BaseConnection + +if TYPE_CHECKING: + from .types.message import Message class Bot(BaseConnection): def __init__(self, token: str): + super().__init__() + self.bot = self + self.__token = token self.params = { 'access_token': self.__token @@ -62,9 +74,35 @@ class Bot(BaseConnection): notify=notify, parse_mode=parse_mode ).request() + + async def delete_message( + self, + message_id: str + ): + return await DeleteMessage( + bot=self, + message_id=message_id, + ).request() - async def get_messages(self, chat_id: int = None): - return await GetMessages(self, chat_id).request() + async def get_messages( + self, + chat_id: int = None, + message_ids: List[str] = None, + from_time: datetime | int = None, + to_time: datetime | int = None, + count: int = 50, + ): + return await GetMessages( + bot=self, + chat_id=chat_id, + message_ids=message_ids, + from_time=from_time, + to_time=to_time, + count=count + ).request() + + async def get_message(self, message_id: str): + return await self.get_messages(message_ids=[message_id]) async def get_me(self): return await GetMe(self).request() @@ -86,4 +124,20 @@ class Bot(BaseConnection): ).request() async def get_chats(self): - return await GetChats(self).request() \ No newline at end of file + return await GetChats(self).request() + + async def get_video(self, video_token: str): + return await GetVideo(self, video_token).request() + + async def send_callback( + self, + callback_id: str, + message: 'Message' = None, + notification: str = None + ): + return await SendCallback( + bot=self, + callback_id=callback_id, + message=message, + notification=notification + ).request() \ No newline at end of file diff --git a/maxapi/connection/base.py b/maxapi/connection/base.py index e9091d9..d42d5c6 100644 --- a/maxapi/connection/base.py +++ b/maxapi/connection/base.py @@ -10,6 +10,9 @@ class BaseConnection: API_URL = 'https://botapi.max.ru' + def __init__(self): + self.bot = None + async def request( self, method: HTTPMethod, @@ -21,7 +24,7 @@ class BaseConnection: async with aiohttp.ClientSession(self.API_URL) as s: r = await s.request( method=method.value, - url=path.value, + url=path.value if isinstance(path, ApiPath) else path, **kwargs ) @@ -33,4 +36,14 @@ class BaseConnection: if is_return_raw: return raw - return model(**raw) \ No newline at end of file + model = model(**raw) + + if hasattr(model, 'message'): + attr = getattr(model, 'message') + if hasattr(attr, 'bot'): + attr.bot = self.bot + + if hasattr(model, 'bot'): + model.bot = self.bot + + return model \ No newline at end of file diff --git a/maxapi/dispatcher.py b/maxapi/dispatcher.py index c2a0abe..d2f6941 100644 --- a/maxapi/dispatcher.py +++ b/maxapi/dispatcher.py @@ -67,7 +67,7 @@ class Dispatcher: for event in router.event_handlers: self.event_handlers.append(event) - def handle_webhook(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080): + def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080): self.bot = bot app = FastAPI() @@ -91,11 +91,13 @@ class Dispatcher: case UpdateType.MESSAGE_CALLBACK: event_object = MessageCallback(**event_json) event_object.message.bot = self.bot + event_object.bot = self.bot case UpdateType.MESSAGE_CHAT_CREATED: event_object = MessageChatCreated(**event_json) case UpdateType.MESSAGE_CREATED: event_object = MessageCreated(**event_json) event_object.message.bot = self.bot + event_object.bot = self.bot case UpdateType.MESSAGE_EDITED: event_object = MessageEdited(**event_json) case UpdateType.MESSAGE_REMOVED: diff --git a/maxapi/enums/api_path.py b/maxapi/enums/api_path.py index 0b23947..45eae36 100644 --- a/maxapi/enums/api_path.py +++ b/maxapi/enums/api_path.py @@ -4,4 +4,6 @@ class ApiPath(str, Enum): ME = '/me' CHATS = '/chats' MESSAGES = '/messages' - UPDATES = '/updates' \ No newline at end of file + UPDATES = '/updates' + VIDEOS = '/videos' + ANSWERS = '/answers' \ No newline at end of file diff --git a/maxapi/enums/http_method.py b/maxapi/enums/http_method.py index 0f288f5..068aa5f 100644 --- a/maxapi/enums/http_method.py +++ b/maxapi/enums/http_method.py @@ -6,3 +6,4 @@ class HTTPMethod(str, Enum): GET = 'GET' PATCH = 'PATCH' PUT = 'PUT' + DELETE = 'DELETE' diff --git a/maxapi/methods/delete_message.py b/maxapi/methods/delete_message.py new file mode 100644 index 0000000..e7f0be4 --- /dev/null +++ b/maxapi/methods/delete_message.py @@ -0,0 +1,33 @@ +from typing import TYPE_CHECKING + +from ..methods.types.deleted_message import DeletedMessage + +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class DeleteMessage(BaseConnection): + def __init__( + self, + bot: 'Bot', + message_id: str, + ): + self.bot = bot + self.message_id = message_id + + async def request(self) -> DeletedMessage: + params = self.bot.params.copy() + + params['message_id'] = self.message_id + + return await super().request( + method=HTTPMethod.DELETE, + path=ApiPath.MESSAGES, + model=DeletedMessage, + params=params, + ) \ No newline at end of file diff --git a/maxapi/methods/edit_message.py b/maxapi/methods/edit_message.py index f0387ae..e7674f4 100644 --- a/maxapi/methods/edit_message.py +++ b/maxapi/methods/edit_message.py @@ -1,15 +1,11 @@ - - from typing import List, TYPE_CHECKING -from aiomax.enums.parse_mode import ParseMode - +from .types.edited_message import EditedMessage +from ..types.message import NewMessageLink from ..types.attachments.attachment import Attachment - +from ..enums.parse_mode import ParseMode from ..enums.http_method import HTTPMethod from ..enums.api_path import ApiPath -from ..types.message import NewMessageLink -from .types.edited_message import EditedMessage from ..connection.base import BaseConnection @@ -23,8 +19,8 @@ class EditMessage(BaseConnection): bot: 'Bot', message_id: str, text: str = None, - attachments: List[Attachment] = None, - link: NewMessageLink = None, + attachments: List['Attachment'] = None, + link: 'NewMessageLink' = None, notify: bool = True, parse_mode: ParseMode = None ): @@ -36,7 +32,7 @@ class EditMessage(BaseConnection): self.notify = notify self.parse_mode = parse_mode - async def request(self) -> 'EditedMessage': + async def request(self) -> EditedMessage: params = self.bot.params.copy() json = {} diff --git a/maxapi/methods/get_messages.py b/maxapi/methods/get_messages.py index b667f4f..801075c 100644 --- a/maxapi/methods/get_messages.py +++ b/maxapi/methods/get_messages.py @@ -1,6 +1,7 @@ -from typing import TYPE_CHECKING +from datetime import datetime +from typing import TYPE_CHECKING, List from ..types.message import Messages from ..enums.http_method import HTTPMethod @@ -13,15 +14,44 @@ if TYPE_CHECKING: class GetMessages(BaseConnection): - def __init__(self, bot: 'Bot', chat_id: int = None): + def __init__( + self, + bot: 'Bot', + chat_id: int, + message_ids: List[str] = None, + from_time: datetime | int = None, + to_time: datetime | int = None, + count: int = 50, + ): self.bot = bot self.chat_id = chat_id + self.message_ids = message_ids + self.from_time = from_time + self.to_time = to_time + self.count = count async def request(self) -> Messages: params = self.bot.params.copy() if self.chat_id: params['chat_id'] = self.chat_id + if self.message_ids: + params['message_ids'] = ','.join(self.message_ids) + + if self.from_time: + if isinstance(self.from_time, datetime): + params['from_time'] = int(self.from_time.timestamp()) + else: + params['from_time'] = self.from_time + + if self.to_time: + if isinstance(self.to_time, datetime): + params['to_time'] = int(self.to_time.timestamp()) + else: + params['to_time'] = self.to_time + + params['count'] = self.count + return await super().request( method=HTTPMethod.GET, path=ApiPath.MESSAGES, diff --git a/maxapi/methods/get_video.py b/maxapi/methods/get_video.py new file mode 100644 index 0000000..4548eea --- /dev/null +++ b/maxapi/methods/get_video.py @@ -0,0 +1,34 @@ +from typing import List, TYPE_CHECKING + +from ..types.attachments.video import Video + +from .types.edited_message import EditedMessage +from ..types.message import NewMessageLink +from ..types.attachments.attachment import Attachment +from ..enums.parse_mode import ParseMode +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class GetVideo(BaseConnection): + def __init__( + self, + bot: 'Bot', + video_token: str + ): + self.bot = bot + self.video_token = video_token + + async def request(self) -> Video: + + return await super().request( + method=HTTPMethod.GET, + path=ApiPath.VIDEOS.value + '/' + self.video_token, + model=Video, + params=self.bot.params, + ) \ No newline at end of file diff --git a/maxapi/methods/send_callback.py b/maxapi/methods/send_callback.py new file mode 100644 index 0000000..9e0b065 --- /dev/null +++ b/maxapi/methods/send_callback.py @@ -0,0 +1,53 @@ + + +from typing import List, TYPE_CHECKING + +from ..methods.types.sended_callback import SendedCallback + +from .types.sended_message import SendedMessage +from ..types.attachments.attachment import Attachment +from ..enums.parse_mode import ParseMode +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + from ..types.message import Message + + +class SendCallback(BaseConnection): + def __init__( + self, + bot: 'Bot', + callback_id: str, + message: 'Message' = None, + notification: str = None + ): + self.bot = bot + self.callback_id = callback_id + self.message = message + self.notification = notification + + async def request(self) -> SendedCallback: + try: + params = self.bot.params.copy() + + params['callback_id'] = self.callback_id + + json = {} + + if self.message: json['message'] = self.message.model_dump() + if self.notification: json['notification'] = self.notification + + return await super().request( + method=HTTPMethod.POST, + path=ApiPath.ANSWERS, + model=SendedCallback, + params=params, + json=json + ) + except Exception as e: + print(e) + ... \ No newline at end of file diff --git a/maxapi/methods/send_message.py b/maxapi/methods/send_message.py index 04179f3..de67e68 100644 --- a/maxapi/methods/send_message.py +++ b/maxapi/methods/send_message.py @@ -2,15 +2,12 @@ from typing import List, TYPE_CHECKING -from ..enums.parse_mode import ParseMode - +from .types.sended_message import SendedMessage from ..types.message import NewMessageLink - from ..types.attachments.attachment import Attachment - +from ..enums.parse_mode import ParseMode from ..enums.http_method import HTTPMethod from ..enums.api_path import ApiPath -from .types.sended_message import SendedMessage from ..connection.base import BaseConnection @@ -41,28 +38,32 @@ class SendMessage(BaseConnection): self.notify = notify self.parse_mode = parse_mode - async def request(self) -> 'SendedMessage': - params = self.bot.params.copy() + async def request(self) -> SendedMessage: + try: + params = self.bot.params.copy() - json = {} + json = {} - if self.chat_id: params['chat_id'] = self.chat_id - elif self.user_id: params['user_id'] = self.user_id + if self.chat_id: params['chat_id'] = self.chat_id + elif self.user_id: params['user_id'] = self.user_id - json['text'] = self.text - json['disable_link_preview'] = str(self.disable_link_preview).lower() - - if self.attachments: json['attachments'] = \ - [att.model_dump() for att in self.attachments] - - if not self.link is None: json['link'] = self.link.model_dump() - if not self.notify is None: json['notify'] = self.notify - if not self.parse_mode is None: json['format'] = self.parse_mode.value + json['text'] = self.text + json['disable_link_preview'] = str(self.disable_link_preview).lower() + + if self.attachments: json['attachments'] = \ + [att.model_dump() for att in self.attachments] + + if not self.link is None: json['link'] = self.link.model_dump() + if not self.notify is None: json['notify'] = self.notify + if not self.parse_mode is None: json['format'] = self.parse_mode.value - return await super().request( - method=HTTPMethod.POST, - path=ApiPath.MESSAGES, - model=SendedMessage, - params=params, - json=json - ) \ No newline at end of file + return await super().request( + method=HTTPMethod.POST, + path=ApiPath.MESSAGES, + model=SendedMessage, + params=params, + json=json + ) + except Exception as e: + print(e) + ... \ No newline at end of file diff --git a/maxapi/methods/types/deleted_message.py b/maxapi/methods/types/deleted_message.py new file mode 100644 index 0000000..fa53861 --- /dev/null +++ b/maxapi/methods/types/deleted_message.py @@ -0,0 +1,7 @@ +from typing import Optional +from pydantic import BaseModel + + +class DeletedMessage(BaseModel): + success: bool + message: Optional[str] = None \ No newline at end of file diff --git a/maxapi/methods/types/edited_message.py b/maxapi/methods/types/edited_message.py index 1c4729d..8aa82a5 100644 --- a/maxapi/methods/types/edited_message.py +++ b/maxapi/methods/types/edited_message.py @@ -1,7 +1,7 @@ +from typing import Optional from pydantic import BaseModel -from ...types.message import Message - class EditedMessage(BaseModel): - message: Message \ No newline at end of file + success: bool + message: Optional[str] = None \ No newline at end of file diff --git a/maxapi/methods/types/sended_callback.py b/maxapi/methods/types/sended_callback.py new file mode 100644 index 0000000..674c08b --- /dev/null +++ b/maxapi/methods/types/sended_callback.py @@ -0,0 +1,14 @@ +from typing import TYPE_CHECKING, Any, Optional +from pydantic import BaseModel, Field + +if TYPE_CHECKING: + from ...bot import Bot + + +class SendedCallback(BaseModel): + success: bool + message: Optional[str] = None + bot: Optional[Any] = Field(default=None, exclude=True) + + if TYPE_CHECKING: + bot: Optional[Bot] diff --git a/maxapi/methods/types/sended_message.py b/maxapi/methods/types/sended_message.py index d218079..f013211 100644 --- a/maxapi/methods/types/sended_message.py +++ b/maxapi/methods/types/sended_message.py @@ -1,3 +1,4 @@ +from typing import Any from pydantic import BaseModel from ...types.message import Message diff --git a/maxapi/types/attachments/video.py b/maxapi/types/attachments/video.py index 58dba39..d9d9fde 100644 --- a/maxapi/types/attachments/video.py +++ b/maxapi/types/attachments/video.py @@ -1,16 +1,35 @@ -from typing import Literal, Optional -from pydantic import BaseModel +from typing import TYPE_CHECKING, Any, Literal, Optional +from pydantic import BaseModel, Field from .attachment import Attachment +if TYPE_CHECKING: + from ...bot import Bot + + +class VideoUrl(BaseModel): + mp4_1080: Optional[str] = None + mp4_720: Optional[str] = None + mp4_480: Optional[str] = None + mp4_360: Optional[str] = None + mp4_240: Optional[str] = None + mp4_144: Optional[str] = None + hls: Optional[str] = None + class VideoThumbnail(BaseModel): url: str class Video(Attachment): - type: Literal['video'] = 'video' + type: Optional[Literal['video']] = 'video' + token: Optional[str] = None + urls: Optional[VideoUrl] = None thumbnail: VideoThumbnail width: Optional[int] = None height: Optional[int] = None duration: Optional[int] = None + bot: Optional[Any] = Field(default=None, exclude=True) + + if TYPE_CHECKING: + bot: Optional['Bot'] diff --git a/maxapi/types/callback.py b/maxapi/types/callback.py index bfb74fc..1df7eaf 100644 --- a/maxapi/types/callback.py +++ b/maxapi/types/callback.py @@ -1,8 +1,10 @@ -from typing import Optional +from typing import List, Optional, Union from pydantic import BaseModel from ..types.users import User +from ..types.users import User + class Callback(BaseModel): timestamp: int diff --git a/maxapi/types/message.py b/maxapi/types/message.py index de97c38..a0a72fc 100644 --- a/maxapi/types/message.py +++ b/maxapi/types/message.py @@ -87,7 +87,10 @@ class Message(BaseModel): body: Optional[MessageBody] = None stat: Optional[MessageStat] = None url: Optional[str] = None - bot: Optional[Any] = None + bot: Optional[Any] = Field(default=None, exclude=True) + + if TYPE_CHECKING: + bot: Optional[Bot] async def answer(self, text: str = None, @@ -97,8 +100,7 @@ class Message(BaseModel): notify: bool = True, parse_mode: ParseMode = None ): - bot: Bot = self.bot - return await bot.send_message( + return await self.bot.send_message( chat_id=self.recipient.chat_id, user_id=self.recipient.user_id, text=text, @@ -108,10 +110,36 @@ class Message(BaseModel): notify=notify, parse_mode=parse_mode ) + + async def edit( + self, + text: str = None, + attachments: List[Attachment] = None, + link: NewMessageLink = None, + notify: bool = True, + parse_mode: ParseMode = None + ): + return await self.bot.edit_message( + message_id=self.body.mid, + text=text, + attachments=attachments, + link=link, + notify=notify, + parse_mode=parse_mode + ) + + async def delete(self): + return await self.bot.delete_message( + message_id=self.body.mid, + ) class Messages(BaseModel): messages: List[Message] + bot: Optional[Any] = Field(default=None, exclude=True) + + if TYPE_CHECKING: + bot: Optional[Bot] class NewMessageLink(BaseModel): diff --git a/maxapi/types/updates/message_callback.py b/maxapi/types/updates/message_callback.py index 3cc2da9..e1da1b1 100644 --- a/maxapi/types/updates/message_callback.py +++ b/maxapi/types/updates/message_callback.py @@ -1,11 +1,73 @@ -from typing import Optional +from typing import Any, List, Optional, TYPE_CHECKING, Union + +from pydantic import BaseModel, Field from . import Update from ...types.callback import Callback from ...types.message import Message +from ...enums.parse_mode import ParseMode +from ...types.message import NewMessageLink +from ...types.attachments.share import Share +from ..attachments.buttons.attachment_button import AttachmentButton +from ..attachments.sticker import Sticker +from ..attachments.file import File +from ..attachments.image import Image +from ..attachments.video import Video +from ..attachments.audio import Audio + + +if TYPE_CHECKING: + from ...bot import Bot + + +class MessageForCallback(BaseModel): + text: Optional[str] = None + attachments: Optional[ + List[ + Union[ + AttachmentButton, + Audio, + Video, + File, + Image, + Sticker, + Share + ] + ] + ] = [] + link: Optional[NewMessageLink] = None + notify: Optional[bool] = True + format: Optional[ParseMode] = None + class MessageCallback(Update): message: Message user_locale: Optional[str] = None - callback: Callback \ No newline at end of file + callback: Callback + bot: Optional[Any] = Field(default=None, exclude=True) + + if TYPE_CHECKING: + bot: Optional[Bot] + + async def answer( + self, + text: str, + link: NewMessageLink = None, + notify: bool = True, + format: ParseMode = None, + notification: str = None + ): + message = MessageForCallback() + + message.text = text + message.attachments = self.message.body.attachments + message.link = link + message.notify = notify + message.format = format + + return await self.bot.send_callback( + callback_id=self.callback.callback_id, + message=message, + notification=notification + ) \ No newline at end of file diff --git a/maxapi/types/updates/message_created.py b/maxapi/types/updates/message_created.py index 4e8e7d4..d0e1952 100644 --- a/maxapi/types/updates/message_created.py +++ b/maxapi/types/updates/message_created.py @@ -1,9 +1,19 @@ -from typing import Optional +from __future__ import annotations +from typing import Any, Optional, TYPE_CHECKING, ForwardRef + +from pydantic import Field from . import Update from ...types.message import Message +if TYPE_CHECKING: + from ...bot import Bot + class MessageCreated(Update): message: Message - user_locale: Optional[str] = None \ No newline at end of file + user_locale: Optional[str] = None + bot: Optional[Any] = Field(default=None, exclude=True) + + if TYPE_CHECKING: + bot: Optional[Bot] \ No newline at end of file