Правки по mypy

This commit is contained in:
Денис Семёнов 2025-07-24 18:54:41 +03:00
parent 2cd3d64bb8
commit 7d2826c4b5
51 changed files with 461 additions and 338 deletions

View File

@ -1,7 +1,9 @@
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
from .client.default import DefaultConnectionProperties
from .types.input_media import InputMedia, InputMediaBuffer
@ -82,6 +84,7 @@ class Bot(BaseConnection):
parse_mode: Optional[ParseMode] = None,
notify: Optional[bool] = None,
auto_requests: bool = True,
default_connection: Optional[DefaultConnectionProperties] = None
):
"""
@ -91,22 +94,24 @@ class Bot(BaseConnection):
:param parse_mode: Форматирование по умолчанию
:param notify: Отключение уведомлений при отправке сообщений (по умолчанию игнорируется) (не работает на стороне MAX)
:param auto_requests: Автоматическое заполнение полей chat и from_user в Update
:param default_connection: Настройки aiohttp
с помощью API запросов если они не заложены как полноценные объекты в Update (по умолчанию True, при False chat и from_user в некоторых событиях будут выдавать None)
"""
super().__init__()
self.bot = self
self.default_connection = default_connection or DefaultConnectionProperties()
self.__token = token
self.params = {'access_token': self.__token}
self.params: Dict[str, Any] = {'access_token': self.__token}
self.marker_updates = None
self.parse_mode = parse_mode
self.notify = notify
self.auto_requests = auto_requests
self._me: User = None
self._me: User | None = None
@property
def me(self):
@ -120,11 +125,11 @@ class Bot(BaseConnection):
async def send_message(
self,
chat_id: int = None,
user_id: int = None,
text: str = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
link: NewMessageLink = None,
chat_id: Optional[int] = None,
user_id: Optional[int] = None,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
link: Optional[NewMessageLink] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
) -> SendedMessage:
@ -152,11 +157,11 @@ class Bot(BaseConnection):
link=link,
notify=self._resolve_notify(notify),
parse_mode=self._resolve_parse_mode(parse_mode)
).request()
).fetch()
async def send_action(
self,
chat_id: int = None,
chat_id: Optional[int] = None,
action: SenderAction = SenderAction.TYPING_ON
) -> SendedAction:
@ -173,14 +178,14 @@ class Bot(BaseConnection):
bot=self,
chat_id=chat_id,
action=action
).request()
).fetch()
async def edit_message(
self,
message_id: str,
text: str = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
link: NewMessageLink = None,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
link: Optional[NewMessageLink] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
) -> EditedMessage:
@ -206,7 +211,7 @@ class Bot(BaseConnection):
link=link,
notify=self._resolve_notify(notify),
parse_mode=self._resolve_parse_mode(parse_mode)
).request()
).fetch()
async def delete_message(
self,
@ -224,7 +229,7 @@ class Bot(BaseConnection):
return await DeleteMessage(
bot=self,
message_id=message_id,
).request()
).fetch()
async def delete_chat(
self,
@ -242,14 +247,14 @@ class Bot(BaseConnection):
return await DeleteChat(
bot=self,
chat_id=chat_id,
).request()
).fetch()
async def get_messages(
self,
chat_id: int = None,
message_ids: List[str] = None,
from_time: datetime | int = None,
to_time: datetime | int = None,
chat_id: Optional[int] = None,
message_ids: Optional[List[str]] = None,
from_time: Optional[Union[datetime, int]] = None,
to_time: Optional[Union[datetime, int]] = None,
count: int = 50,
) -> Messages:
@ -272,7 +277,7 @@ class Bot(BaseConnection):
from_time=from_time,
to_time=to_time,
count=count
).request()
).fetch()
async def get_message(
self,
@ -299,7 +304,7 @@ class Bot(BaseConnection):
:return: Объект пользователя бота
"""
return await GetMe(self).request()
return await GetMe(self).fetch()
async def get_pin_message(
self,
@ -317,14 +322,14 @@ class Bot(BaseConnection):
return await GetPinnedMessage(
bot=self,
chat_id=chat_id
).request()
).fetch()
async def change_info(
self,
name: str = None,
description: str = None,
commands: List[BotCommand] = None,
photo: Dict[str, Any] = None
name: Optional[str] = None,
description: Optional[str] = None,
commands: Optional[List[BotCommand]] = None,
photo: Optional[Dict[str, Any]] = None
) -> User:
"""
@ -344,12 +349,12 @@ class Bot(BaseConnection):
description=description,
commands=commands,
photo=photo
).request()
).fetch()
async def get_chats(
self,
count: int = 50,
marker: int = None
marker: Optional[int] = None
) -> Chats:
"""
@ -365,7 +370,7 @@ class Bot(BaseConnection):
bot=self,
count=count,
marker=marker
).request()
).fetch()
async def get_chat_by_link(
self,
@ -380,7 +385,7 @@ class Bot(BaseConnection):
:return: Объект чата
"""
return await GetChatByLink(bot=self, link=link).request()
return await GetChatByLink(bot=self, link=link).fetch()
async def get_chat_by_id(
self,
@ -395,14 +400,14 @@ class Bot(BaseConnection):
:return: Объект чата
"""
return await GetChatById(bot=self, id=id).request()
return await GetChatById(bot=self, id=id).fetch()
async def edit_chat(
self,
chat_id: int,
icon: PhotoAttachmentRequestPayload = None,
title: str = None,
pin: str = None,
icon: Optional[PhotoAttachmentRequestPayload] = None,
title: Optional[str] = None,
pin: Optional[str] = None,
notify: Optional[bool] = None,
) -> Chat:
@ -425,7 +430,7 @@ class Bot(BaseConnection):
title=title,
pin=pin,
notify=self._resolve_notify(notify),
).request()
).fetch()
async def get_video(
self,
@ -443,13 +448,13 @@ class Bot(BaseConnection):
return await GetVideo(
bot=self,
video_token=video_token
).request()
).fetch()
async def send_callback(
self,
callback_id: str,
message: Message = None,
notification: str = None
message: Optional[Message] = None,
notification: Optional[str] = None
) -> SendedCallback:
"""
@ -467,7 +472,7 @@ class Bot(BaseConnection):
callback_id=callback_id,
message=message,
notification=notification
).request()
).fetch()
async def pin_message(
self,
@ -491,7 +496,7 @@ class Bot(BaseConnection):
chat_id=chat_id,
message_id=message_id,
notify=self._resolve_notify(notify),
).request()
).fetch()
async def delete_pin_message(
self,
@ -509,7 +514,7 @@ class Bot(BaseConnection):
return await DeletePinMessage(
bot=self,
chat_id=chat_id,
).request()
).fetch()
async def get_me_from_chat(
self,
@ -527,7 +532,7 @@ class Bot(BaseConnection):
return await GetMeFromChat(
bot=self,
chat_id=chat_id,
).request()
).fetch()
async def delete_me_from_chat(
self,
@ -545,7 +550,7 @@ class Bot(BaseConnection):
return await DeleteMeFromMessage(
bot=self,
chat_id=chat_id,
).request()
).fetch()
async def get_list_admin_chat(
self,
@ -563,13 +568,13 @@ class Bot(BaseConnection):
return await GetListAdminChat(
bot=self,
chat_id=chat_id,
).request()
).fetch()
async def add_list_admin_chat(
self,
chat_id: int,
admins: List[ChatAdmin],
marker: int = None
marker: Optional[int] = None
) -> AddedListAdminChat:
"""
@ -587,7 +592,7 @@ class Bot(BaseConnection):
chat_id=chat_id,
admins=admins,
marker=marker,
).request()
).fetch()
async def remove_admin(
self,
@ -608,14 +613,14 @@ class Bot(BaseConnection):
bot=self,
chat_id=chat_id,
user_id=user_id,
).request()
).fetch()
async def get_chat_members(
self,
chat_id: int,
user_ids: List[int] = None,
marker: int = None,
count: int = None,
user_ids: Optional[List[int]] = None,
marker: Optional[int] = None,
count: Optional[int] = None,
) -> GettedMembersChat:
"""
@ -635,13 +640,13 @@ class Bot(BaseConnection):
user_ids=user_ids,
marker=marker,
count=count,
).request()
).fetch()
async def get_chat_member(
self,
chat_id: int,
user_id: int,
) -> GettedMembersChat:
) -> Optional[ChatMember]:
"""
Получает участника чата.
@ -660,10 +665,12 @@ class Bot(BaseConnection):
if members.members:
return members.members[0]
return None
async def add_chat_members(
self,
chat_id: int,
user_ids: List[str],
user_ids: List[int],
) -> AddedMembersChat:
"""
@ -679,7 +686,7 @@ class Bot(BaseConnection):
bot=self,
chat_id=chat_id,
user_ids=user_ids,
).request()
).fetch()
async def kick_chat_member(
self,
@ -703,11 +710,11 @@ class Bot(BaseConnection):
chat_id=chat_id,
user_id=user_id,
block=block,
).request()
).fetch()
async def get_updates(
self,
) -> UpdateUnion:
) -> Dict:
"""
Получает обновления для бота.
@ -717,7 +724,7 @@ class Bot(BaseConnection):
return await GetUpdates(
bot=self,
).request()
).fetch()
async def get_upload_url(
self,
@ -735,7 +742,7 @@ class Bot(BaseConnection):
return await GetUploadURL(
bot=self,
type=type
).request()
).fetch()
async def set_my_commands(
self,
@ -753,7 +760,7 @@ class Bot(BaseConnection):
return await ChangeInfo(
bot=self,
commands=list(commands)
).request()
).fetch()
async def download_file(
self,
@ -777,4 +784,4 @@ class Bot(BaseConnection):
path=path,
media_url=url,
media_token=token
).request()
).fetch()

10
maxapi/client/default.py Normal file
View File

@ -0,0 +1,10 @@
from aiohttp import ClientTimeout
class DefaultConnectionProperties:
def __init__(self, timeout: int = 5 * 30, sock_connect: int = 30, **kwargs):
self.timeout = ClientTimeout(total=timeout, sock_connect=sock_connect)
self.kwargs = kwargs

View File

@ -1,7 +1,9 @@
from __future__ import annotations
import os
import mimetypes
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Optional
from uuid import uuid4
import aiofiles
@ -18,7 +20,7 @@ from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..enums.upload_type import UploadType
from ..loggers import logger_bot, logger_connection
from ..loggers import logger_bot
if TYPE_CHECKING:
from ..bot import Bot
@ -36,15 +38,15 @@ class BaseConnection:
API_URL = 'https://botapi.max.ru'
def __init__(self):
self.bot: 'Bot' = None
self.session: ClientSession = None
def __init__(self) -> None:
self.bot: Optional[Bot] = None
self.session: Optional[ClientSession] = None
async def request(
self,
method: HTTPMethod,
path: ApiPath,
model: BaseModel = None,
path: ApiPath | str,
model: BaseModel | Any = None,
is_return_raw: bool = False,
**kwargs
):
@ -64,8 +66,14 @@ class BaseConnection:
- dict (если is_return_raw=True)
"""
assert self.bot is not None
if not self.bot.session:
self.bot.session = ClientSession(self.bot.API_URL)
self.bot.session = ClientSession(
base_url=self.bot.API_URL,
timeout=self.bot.default_connection.timeout,
**self.bot.default_connection.kwargs
)
try:
r = await self.bot.session.request(
@ -90,7 +98,7 @@ class BaseConnection:
if is_return_raw: return raw
model = model(**raw)
model = model(**raw) # type: ignore
if hasattr(model, 'message'):
attr = getattr(model, 'message')

View File

@ -1,6 +1,6 @@
import asyncio
from typing import Any, Dict
from typing import Any, Dict, Optional, Union
from ..context.state_machine import State, StatesGroup
@ -19,7 +19,7 @@ class MemoryContext:
self.chat_id = chat_id
self.user_id = user_id
self._context: Dict[str, Any] = {}
self._state: State | None = None
self._state: State | str | None = None
self._lock = asyncio.Lock()
async def get_data(self) -> dict[str, Any]:
@ -58,7 +58,7 @@ class MemoryContext:
async with self._lock:
self._context.update(kwargs)
async def set_state(self, state: State | str = None):
async def set_state(self, state: Optional[Union[State, str]] = None):
"""
Устанавливает новое состояние.

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import asyncio
from typing import Any, Callable, Dict, List, TYPE_CHECKING
from typing import Any, Callable, Dict, List, TYPE_CHECKING, Optional, cast
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
@ -45,7 +45,7 @@ class Dispatcher:
применение middleware, фильтров и вызов соответствующих обработчиков.
"""
def __init__(self):
def __init__(self) -> None:
"""
Инициализация диспетчера.
@ -53,12 +53,12 @@ class Dispatcher:
self.event_handlers: List[Handler] = []
self.contexts: List[MemoryContext] = []
self.routers: List[Router] = []
self.routers: List[Router | Dispatcher] = []
self.filters: List[MagicFilter] = []
self.middlewares: List[BaseMiddleware] = []
self.bot: Bot = None
self.on_started_func: Callable = None
self.bot: Optional[Bot] = None
self.on_started_func: Optional[Callable] = None
self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self)
self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self)
@ -249,18 +249,18 @@ class Dispatcher:
while True:
try:
events = await self.bot.get_updates()
events: Dict = await self.bot.get_updates() # type: ignore
if isinstance(events, Error):
logger_dp.info(f'Ошибка при получении обновлений: {events}, жду {GET_UPDATES_RETRY_DELAY} секунд')
await asyncio.sleep(GET_UPDATES_RETRY_DELAY)
continue
self.bot.marker_updates = events.get('marker')
self.bot.marker_updates = events.get('marker') # type: ignore
processed_events = await process_update_request(
events=events,
bot=self.bot
bot=self.bot # type: ignore
)
for event in processed_events:
@ -270,7 +270,7 @@ class Dispatcher:
logger_dp.error(f'Ошибка подключения, жду {CONNECTION_RETRY_DELAY} секунд')
await asyncio.sleep(CONNECTION_RETRY_DELAY)
except Exception as e:
logger_dp.error(f'Общая ошибка при обработке событий: {e}')
logger_dp.error(f'Общая ошибка при обработке событий: {e.__class__} - {e}')
async def handle_webhook(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080):
@ -289,7 +289,7 @@ class Dispatcher:
event_object = await process_update_webhook(
event_json=event_json,
bot=self.bot
bot=self.bot # type: ignore
)
await self.handle(event_object)

View File

@ -17,3 +17,4 @@ class AttachmentType(str, Enum):
CONTACT = 'contact'
INLINE_KEYBOARD = 'inline_keyboard'
LOCATION = 'location'
SHARE = 'share'

View File

@ -1,3 +1,11 @@
class MaxConnection(BaseException):
...
class MaxUploadFileFailed(BaseException):
...
class MaxIconParamsException(BaseException):
...

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from .types.added_admin_chat import AddedListAdminChat
from ..types.users import ChatAdmin
@ -30,14 +30,14 @@ class AddAdminChat(BaseConnection):
bot: 'Bot',
chat_id: int,
admins: List[ChatAdmin],
marker: int = None
marker: Optional[int] = None
):
self.bot = bot
self.chat_id = chat_id
self.admins = admins
self.marker = marker
async def request(self) -> AddedListAdminChat:
async def fetch(self) -> AddedListAdminChat:
"""
Выполняет HTTP POST запрос для добавления администраторов в чат.
@ -48,7 +48,9 @@ class AddAdminChat(BaseConnection):
AddedListAdminChat: Результат операции с информацией об успешности.
"""
json = {}
assert self.bot is not None
json: Dict[str, Any] = {}
json['admins'] = [admin.model_dump() for admin in self.admins]
json['marker'] = self.marker

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, Any, Dict, List
from ..methods.types.added_members_chat import AddedMembersChat
@ -34,7 +34,7 @@ class AddMembersChat(BaseConnection):
self.chat_id = chat_id
self.user_ids = user_ids
async def request(self) -> AddedMembersChat:
async def fetch(self) -> AddedMembersChat:
"""
Отправляет POST-запрос на добавление пользователей в чат.
@ -45,7 +45,9 @@ class AddMembersChat(BaseConnection):
AddedMembersChat: Результат операции с информацией об успешности добавления.
"""
json = {}
assert self.bot is not None
json: Dict[str, Any] = {}
json['user_ids'] = self.user_ids

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, List, TYPE_CHECKING
from typing import Any, Dict, List, TYPE_CHECKING, Optional
from ..types.users import User
from ..types.command import BotCommand
@ -29,10 +29,10 @@ class ChangeInfo(BaseConnection):
def __init__(
self,
bot: 'Bot',
name: str = None,
description: str = None,
commands: List[BotCommand] = None,
photo: Dict[str, Any] = None
name: Optional[str] = None,
description: Optional[str] = None,
commands: Optional[List[BotCommand]] = None,
photo: Optional[Dict[str, Any]] = None
):
self.bot = bot
self.name = name
@ -40,7 +40,7 @@ class ChangeInfo(BaseConnection):
self.commands = commands
self.photo = photo
async def request(self) -> User:
async def fetch(self) -> User:
"""Отправляет запрос на изменение информации о боте.
@ -48,7 +48,9 @@ class ChangeInfo(BaseConnection):
User: Объект с обновленными данными бота
"""
json = {}
assert self.bot is not None
json: Dict[str, Any] = {}
if self.name: json['name'] = self.name
if self.description: json['description'] = self.description

View File

@ -30,7 +30,7 @@ class DeleteMeFromMessage(BaseConnection):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> DeletedBotFromChat:
async def fetch(self) -> DeletedBotFromChat:
"""
Отправляет DELETE-запрос для удаления бота из чата.
@ -39,6 +39,7 @@ class DeleteMeFromMessage(BaseConnection):
DeletedBotFromChat: Результат операции удаления.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,

View File

@ -29,7 +29,7 @@ class DeleteChat(BaseConnection):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> DeletedChat:
async def fetch(self) -> DeletedChat:
"""
Отправляет DELETE-запрос для удаления указанного чата.
@ -38,6 +38,7 @@ class DeleteChat(BaseConnection):
DeletedChat: Результат операции удаления чата.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS.value + '/' + str(self.chat_id),

View File

@ -29,7 +29,7 @@ class DeleteMessage(BaseConnection):
self.bot = bot
self.message_id = message_id
async def request(self) -> DeletedMessage:
async def fetch(self) -> DeletedMessage:
"""
Выполняет DELETE-запрос для удаления сообщения.
@ -40,6 +40,7 @@ class DeleteMessage(BaseConnection):
DeletedMessage: Результат операции удаления сообщения.
"""
assert self.bot is not None
params = self.bot.params.copy()
params['message_id'] = self.message_id

View File

@ -19,18 +19,18 @@ class DeletePinMessage(BaseConnection):
Args:
bot (Bot): Экземпляр бота для выполнения запроса.
chat_id (str): Идентификатор чата, из которого нужно удалить закреплённое сообщение.
chat_id (int): Идентификатор чата, из которого нужно удалить закреплённое сообщение.
"""
def __init__(
self,
bot: 'Bot',
chat_id: str,
chat_id: int,
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> DeletedPinMessage:
async def fetch(self) -> DeletedPinMessage:
"""
Выполняет DELETE-запрос для удаления закреплённого сообщения.
@ -38,7 +38,7 @@ class DeletePinMessage(BaseConnection):
Returns:
DeletedPinMessage: Результат операции удаления закреплённого сообщения.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,

View File

@ -36,7 +36,7 @@ class DownloadMedia(BaseConnection):
self.media_url = media_url
self.media_token = media_token
async def request(self) -> int:
async def fetch(self) -> int:
"""
Выполняет GET-запрос для скачивания медиафайла

View File

@ -1,9 +1,11 @@
from logging import getLogger
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, Optional
from collections import Counter
from ..exceptions.max import MaxIconParamsException
from ..types.attachments.image import PhotoAttachmentRequestPayload
from ..types.chats import Chat
@ -37,10 +39,10 @@ class EditChat(BaseConnection):
self,
bot: 'Bot',
chat_id: int,
icon: PhotoAttachmentRequestPayload = None,
title: str = None,
pin: str = None,
notify: bool = True,
icon: Optional[PhotoAttachmentRequestPayload] = None,
title: Optional[str] = None,
pin: Optional[str] = None,
notify: Optional[bool] = None,
):
self.bot = bot
self.chat_id = chat_id
@ -49,7 +51,7 @@ class EditChat(BaseConnection):
self.pin = pin
self.notify = notify
async def request(self) -> Chat:
async def fetch(self) -> Chat:
"""
Выполняет PATCH-запрос для обновления параметров чата.
@ -62,7 +64,8 @@ class EditChat(BaseConnection):
Chat: Обновлённый объект чата.
"""
json = {}
assert self.bot is not None
json: Dict[str, Any] = {}
if self.icon:
dump = self.icon.model_dump()
@ -70,7 +73,8 @@ class EditChat(BaseConnection):
if not None in counter or \
not counter[None] == 2:
return logger.error(
raise MaxIconParamsException(
'Все атрибуты модели Icon являются взаимоисключающими | '
'https://dev.max.ru/docs-api/methods/PATCH/chats/-chatId-'
)

View File

@ -1,6 +1,8 @@
from __future__ import annotations
from typing import List, TYPE_CHECKING, Optional
from typing import Any, Dict, List, TYPE_CHECKING, Optional
from ..utils.message import process_input_media
from .types.edited_message import EditedMessage
from ..types.message import NewMessageLink
@ -37,10 +39,10 @@ class EditMessage(BaseConnection):
self,
bot: Bot,
message_id: str,
text: str = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
link: NewMessageLink = None,
notify: bool = True,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
link: Optional[NewMessageLink] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
):
self.bot = bot
@ -51,7 +53,7 @@ class EditMessage(BaseConnection):
self.notify = notify
self.parse_mode = parse_mode
async def request(self) -> EditedMessage:
async def fetch(self) -> EditedMessage:
"""
Выполняет PUT-запрос для обновления сообщения.
@ -62,15 +64,31 @@ class EditMessage(BaseConnection):
EditedMessage: Обновлённое сообщение.
"""
assert self.bot is not None
params = self.bot.params.copy()
json = {}
json: Dict[str, Any] = {}
params['message_id'] = self.message_id
if not self.text is None: json['text'] = self.text
if self.attachments: json['attachments'] = \
[att.model_dump() for att in self.attachments]
if self.attachments:
for att in self.attachments:
if isinstance(att, InputMedia) or isinstance(att, InputMediaBuffer):
input_media = await process_input_media(
base_connection=self,
bot=self.bot,
att=att
)
json['attachments'].append(
input_media.model_dump()
)
else:
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
if not self.parse_mode is None: json['format'] = self.parse_mode.value

View File

@ -30,7 +30,7 @@ class GetChatById(BaseConnection):
self.bot = bot
self.id = id
async def request(self) -> Chat:
async def fetch(self) -> Chat:
"""
Выполняет GET-запрос для получения данных чата.
@ -39,6 +39,7 @@ class GetChatById(BaseConnection):
Chat: Объект чата с полной информацией.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.id),

View File

@ -40,7 +40,7 @@ class GetChatByLink(BaseConnection):
if not self.link:
return
async def request(self) -> Chat:
async def fetch(self) -> Chat:
"""
Выполняет GET-запрос для получения данных чата по ссылке.
@ -49,6 +49,7 @@ class GetChatByLink(BaseConnection):
Chat: Объект с информацией о чате.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + self.link[-1],

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
from ..types.chats import Chats
@ -32,13 +32,13 @@ class GetChats(BaseConnection):
self,
bot: 'Bot',
count: int = 50,
marker: int = None
marker: Optional[int] = None
):
self.bot = bot
self.count = count
self.marker = marker
async def request(self) -> Chats:
async def fetch(self) -> Chats:
"""
Выполняет GET-запрос для получения списка чатов.
@ -46,7 +46,7 @@ class GetChats(BaseConnection):
Returns:
Chats: Объект с данными по списку чатов.
"""
assert self.bot is not None
params = self.bot.params.copy()
params['count'] = self.count

View File

@ -34,7 +34,7 @@ class GetListAdminChat(BaseConnection):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> GettedListAdminChat:
async def fetch(self) -> GettedListAdminChat:
"""
Выполняет GET-запрос для получения списка администраторов указанного чата.
@ -42,7 +42,7 @@ class GetListAdminChat(BaseConnection):
Returns:
GettedListAdminChat: Объект с информацией о администраторах чата.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,

View File

@ -24,7 +24,7 @@ class GetMe(BaseConnection):
def __init__(self, bot: 'Bot'):
self.bot = bot
async def request(self) -> User:
async def fetch(self) -> User:
"""
Выполняет GET-запрос для получения данных о боте.
@ -32,7 +32,7 @@ class GetMe(BaseConnection):
Returns:
User: Объект пользователя с полной информацией.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.ME,

View File

@ -34,7 +34,7 @@ class GetMeFromChat(BaseConnection):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> ChatMember:
async def fetch(self) -> ChatMember:
"""
Выполняет GET-запрос для получения информации о боте в указанном чате.
@ -42,7 +42,7 @@ class GetMeFromChat(BaseConnection):
Returns:
ChatMember: Информация о боте как участнике чата.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, List, Optional
from ..methods.types.getted_members_chat import GettedMembersChat
@ -27,7 +27,7 @@ class GetMembersChat(BaseConnection):
Attributes:
bot (Bot): Экземпляр бота.
chat_id (int): Идентификатор чата.
user_ids (List[str] | None): Список ID пользователей для фильтра.
user_ids (List[int] | None): Список ID пользователей для фильтра.
marker (int | None): Позиция для пагинации.
count (int | None): Максимальное количество участников.
"""
@ -36,9 +36,9 @@ class GetMembersChat(BaseConnection):
self,
bot: 'Bot',
chat_id: int,
user_ids: List[str] = None,
marker: int = None,
count: int = None,
user_ids: Optional[List[int]] = None,
marker: Optional[int] = None,
count: Optional[int] = None,
):
self.bot = bot
@ -47,7 +47,7 @@ class GetMembersChat(BaseConnection):
self.marker = marker
self.count = count
async def request(self) -> GettedMembersChat:
async def fetch(self) -> GettedMembersChat:
"""
Выполняет GET-запрос для получения участников чата с опциональной фильтрацией.
@ -57,12 +57,12 @@ class GetMembersChat(BaseConnection):
Returns:
GettedMembersChat: Объект с данными по участникам чата.
"""
assert self.bot is not None
params = self.bot.params.copy()
if self.user_ids:
self.user_ids = [str(user_id) for user_id in self.user_ids]
params['user_ids'] = ','.join(self.user_ids)
params['user_ids'] = ','.join([str(user_id) for user_id in self.user_ids])
if self.marker: params['marker'] = self.marker
if self.count: params['marker'] = self.count

View File

@ -1,5 +1,5 @@
from datetime import datetime
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, List, Optional, Union
from ..types.message import Messages
from ..enums.http_method import HTTPMethod
@ -36,10 +36,10 @@ class GetMessages(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
message_ids: List[str] = None,
from_time: datetime | int = None,
to_time: datetime | int = None,
chat_id: Optional[int] = None,
message_ids: Optional[List[str]] = None,
from_time: Optional[Union[datetime, int]] = None,
to_time: Optional[Union[datetime, int]] = None,
count: int = 50,
):
self.bot = bot
@ -49,7 +49,7 @@ class GetMessages(BaseConnection):
self.to_time = to_time
self.count = count
async def request(self) -> Messages:
async def fetch(self) -> Messages:
"""
Выполняет GET-запрос для получения сообщений с учётом параметров фильтрации.
@ -59,7 +59,7 @@ class GetMessages(BaseConnection):
Returns:
Messages: Объект с полученными сообщениями.
"""
assert self.bot is not None
params = self.bot.params.copy()
if self.chat_id: params['chat_id'] = self.chat_id

View File

@ -29,7 +29,7 @@ class GetPinnedMessage(BaseConnection):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> GettedPin:
async def fetch(self) -> GettedPin:
"""
Выполняет GET-запрос для получения закреплённого сообщения.
@ -37,7 +37,7 @@ class GetPinnedMessage(BaseConnection):
Returns:
GettedPin: Объект с информацией о закреплённом сообщении.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,

View File

@ -1,4 +1,5 @@
from typing import TYPE_CHECKING
from __future__ import annotations
from typing import TYPE_CHECKING, Dict
from ..types.updates import UpdateUnion
@ -28,13 +29,13 @@ class GetUpdates(BaseConnection):
def __init__(
self,
bot: 'Bot',
bot: Bot,
limit: int = 100,
):
self.bot = bot
self.limit = limit
async def request(self) -> UpdateUnion:
async def fetch(self) -> Dict:
"""
Выполняет GET-запрос для получения обновлений с указанным лимитом.
@ -44,7 +45,7 @@ class GetUpdates(BaseConnection):
Returns:
UpdateUnion: Объединённый тип данных обновлений.
"""
assert self.bot is not None
params = self.bot.params.copy()
params['limit'] = self.limit

View File

@ -33,7 +33,7 @@ class GetUploadURL(BaseConnection):
self.bot = bot
self.type = type
async def request(self) -> GettedUploadUrl:
async def fetch(self) -> GettedUploadUrl:
"""
Выполняет POST-запрос для получения URL загрузки файла.
@ -43,7 +43,7 @@ class GetUploadURL(BaseConnection):
Returns:
GettedUploadUrl: Результат с URL для загрузки.
"""
assert self.bot is not None
params = self.bot.params.copy()
params['type'] = self.type.value

View File

@ -30,7 +30,7 @@ class GetVideo(BaseConnection):
self.bot = bot
self.video_token = video_token
async def request(self) -> Video:
async def fetch(self) -> Video:
"""
Выполняет GET-запрос для получения данных видео по токену.
@ -38,7 +38,7 @@ class GetVideo(BaseConnection):
Returns:
Video: Объект с информацией о видео.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.VIDEOS.value + '/' + self.video_token,

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, Optional
from .types.pinned_message import PinnedMessage
@ -35,14 +35,14 @@ class PinMessage(BaseConnection):
bot: 'Bot',
chat_id: int,
message_id: str,
notify: bool = True
notify: Optional[bool] = None
):
self.bot = bot
self.chat_id = chat_id
self.message_id = message_id
self.notify = notify
async def request(self) -> PinnedMessage:
async def fetch(self) -> PinnedMessage:
"""
Выполняет PUT-запрос для закрепления сообщения в чате.
@ -52,8 +52,8 @@ class PinMessage(BaseConnection):
Returns:
PinnedMessage: Объект с информацией о закреплённом сообщении.
"""
json = {}
assert self.bot is not None
json: Dict[str, Any] = {}
json['message_id'] = self.message_id
json['notify'] = self.notify

View File

@ -38,7 +38,7 @@ class RemoveAdmin(BaseConnection):
self.chat_id = chat_id
self.user_id = user_id
async def request(self) -> RemovedAdmin:
async def fetch(self) -> RemovedAdmin:
"""
Выполняет DELETE-запрос для отмены прав администратора в чате.
@ -46,7 +46,7 @@ class RemoveAdmin(BaseConnection):
Returns:
RemovedAdmin: Объект с результатом отмены прав администратора.
"""
assert self.bot is not None
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + \

View File

@ -43,7 +43,7 @@ class RemoveMemberChat(BaseConnection):
self.user_id = user_id
self.block = block
async def request(self) -> RemovedMemberChat:
async def fetch(self) -> RemovedMemberChat:
"""
Выполняет DELETE-запрос для удаления пользователя из чата.
@ -54,6 +54,7 @@ class RemoveMemberChat(BaseConnection):
RemovedMemberChat: Результат удаления участника.
"""
assert self.bot is not None
params = self.bot.params.copy()
params['chat_id'] = self.chat_id

View File

@ -1,6 +1,6 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, Optional
from ..methods.types.sended_action import SendedAction
@ -34,14 +34,14 @@ class SendAction(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int = None,
chat_id: Optional[int] = None,
action: SenderAction = SenderAction.TYPING_ON
):
self.bot = bot
self.chat_id = chat_id
self.action = action
async def request(self) -> SendedAction:
async def fetch(self) -> SendedAction:
"""
Выполняет POST-запрос для отправки действия в указанный чат.
@ -49,8 +49,9 @@ class SendAction(BaseConnection):
Returns:
SendedAction: Результат выполнения запроса.
"""
assert self.bot is not None
json = {}
json: Dict[str, Any] = {}
json['action'] = self.action.value

View File

@ -1,5 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, Optional
from ..methods.types.sended_callback import SendedCallback
@ -36,15 +36,15 @@ class SendCallback(BaseConnection):
self,
bot: 'Bot',
callback_id: str,
message: Message = None,
notification: str = None
message: Optional[Message] = None,
notification: Optional[str] = None
):
self.bot = bot
self.callback_id = callback_id
self.message = message
self.notification = notification
async def request(self) -> SendedCallback:
async def fetch(self) -> SendedCallback:
"""
Выполняет POST-запрос для отправки callback-ответа.
@ -55,11 +55,12 @@ class SendCallback(BaseConnection):
SendedCallback: Объект с результатом отправки callback.
"""
assert self.bot is not None
params = self.bot.params.copy()
params['callback_id'] = self.callback_id
json = {}
json: Dict[str, Any] = {}
if self.message: json['message'] = self.message.model_dump()
if self.notification: json['notification'] = self.notification

View File

@ -1,10 +1,14 @@
import asyncio
from typing import List, TYPE_CHECKING, Optional
from typing import Any, Dict, List, TYPE_CHECKING, Optional
from json import loads as json_loads
from ..utils.message import process_input_media
from ..exceptions.max import MaxUploadFileFailed
from .types.sended_message import SendedMessage
from ..types.attachments.upload import AttachmentPayload, AttachmentUpload
from ..types.errors import Error
@ -39,7 +43,7 @@ class SendMessage(BaseConnection):
chat_id (int, optional): Идентификатор чата, куда отправлять сообщение.
user_id (int, optional): Идентификатор пользователя, если нужно отправить личное сообщение.
text (str, optional): Текст сообщения.
attachments (List[Attachment | InputMedia], optional): Список вложений к сообщению.
attachments (List[Attachment | InputMedia | InputMediaBuffer], optional): Список вложений к сообщению.
link (NewMessageLink, optional): Связь с другим сообщением (например, ответ или пересылка).
notify (bool, optional): Отправлять ли уведомление о сообщении. По умолчанию True.
parse_mode (ParseMode, optional): Режим разбора текста (например, Markdown, HTML).
@ -48,12 +52,12 @@ class SendMessage(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int = None,
user_id: int = None,
text: str = None,
attachments: List[Attachment | InputMedia] = None,
link: NewMessageLink = None,
notify: bool = True,
chat_id: Optional[int] = None,
user_id: Optional[int] = None,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
link: Optional[NewMessageLink] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
):
self.bot = bot
@ -65,59 +69,7 @@ class SendMessage(BaseConnection):
self.notify = notify
self.parse_mode = parse_mode
async def __process_input_media(
self,
att: InputMedia | InputMediaBuffer
):
# очень нестабильный метод независящий от модуля
# ждем обновлений MAX API
"""
Загружает файл вложения и формирует объект AttachmentUpload.
Args:
att (InputMedia): Объект вложения для загрузки.
Returns:
AttachmentUpload: Загруженное вложение с токеном.
"""
upload = await self.bot.get_upload_url(att.type)
if isinstance(att, InputMedia):
upload_file_response = await self.upload_file(
url=upload.url,
path=att.path,
type=att.type,
)
elif isinstance(att, InputMediaBuffer):
upload_file_response = await self.upload_file_buffer(
url=upload.url,
buffer=att.buffer,
type=att.type,
)
if att.type in (UploadType.VIDEO, UploadType.AUDIO):
token = upload.token
elif att.type == UploadType.FILE:
json_r = json_loads(upload_file_response)
token = json_r['token']
elif att.type == UploadType.IMAGE:
json_r = json_loads(upload_file_response)
json_r_keys = list(json_r['photos'].keys())
token = json_r['photos'][json_r_keys[0]]['token']
return AttachmentUpload(
type=att.type,
payload=AttachmentPayload(
token=token
)
)
async def request(self) -> SendedMessage:
async def fetch(self) -> Optional[SendedMessage | Error]:
"""
Отправляет сообщение с вложениями (если есть), с обработкой задержки готовности вложений.
@ -128,9 +80,10 @@ class SendMessage(BaseConnection):
SendedMessage или Error
"""
assert self.bot is not None
params = self.bot.params.copy()
json = {'attachments': []}
json: Dict[str, Any] = {'attachments': []}
if self.chat_id: params['chat_id'] = self.chat_id
elif self.user_id: params['user_id'] = self.user_id
@ -142,7 +95,11 @@ class SendMessage(BaseConnection):
for att in self.attachments:
if isinstance(att, InputMedia) or isinstance(att, InputMediaBuffer):
input_media = await self.__process_input_media(att)
input_media = await process_input_media(
base_connection=self,
bot=self.bot,
att=att
)
json['attachments'].append(
input_media.model_dump()
)

View File

@ -3,5 +3,5 @@ from pydantic import BaseModel
class GettedUploadUrl(BaseModel):
url: Optional[str] = None
url: str
token: Optional[str] = None

View File

@ -32,35 +32,35 @@ from .input_media import InputMedia
from .input_media import InputMediaBuffer
__all__ = [
CommandStart,
OpenAppButton,
Message,
Attachment,
InputMediaBuffer,
MessageButton,
UpdateUnion,
InputMedia,
BotCommand,
CallbackButton,
ChatButton,
LinkButton,
RequestContactButton,
RequestGeoLocationButton,
Command,
PhotoAttachmentPayload,
OtherAttachmentPayload,
ContactAttachmentPayload,
ButtonsPayload,
StickerAttachmentPayload,
BotAdded,
BotRemoved,
BotStarted,
ChatTitleChanged,
MessageCallback,
MessageChatCreated,
MessageCreated,
MessageEdited,
MessageRemoved,
UserAdded,
UserRemoved
'CommandStart',
'OpenAppButton',
'Message',
'Attachment',
'InputMediaBuffer',
'MessageButton',
'UpdateUnion',
'InputMedia',
'BotCommand',
'CallbackButton',
'ChatButton',
'LinkButton',
'RequestContactButton',
'RequestGeoLocationButton',
'Command',
'PhotoAttachmentPayload',
'OtherAttachmentPayload',
'ContactAttachmentPayload',
'ButtonsPayload',
'StickerAttachmentPayload',
'BotAdded',
'BotRemoved',
'BotStarted',
'ChatTitleChanged',
'MessageCallback',
'MessageChatCreated',
'MessageCreated',
'MessageEdited',
'MessageRemoved',
'UserAdded',
'UserRemoved',
]

View File

@ -112,33 +112,33 @@ class Attachment(BaseModel):
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
bot: Optional[Bot] # type: ignore
class Config:
use_enum_values = True
async def download(
self,
path: str
):
# async def download(
# self,
# path: str
# ):
"""
Скачивает медиа, сохраняя по определенному пути
# """
# Скачивает медиа, сохраняя по определенному пути
:param path: Путь сохранения медиа
# :param path: Путь сохранения медиа
:return: Числовой статус
"""
# :return: Числовой статус
# """
if not hasattr(self.payload, 'token') or \
not hasattr(self.payload, 'url'):
raise NotAvailableForDownload()
# if not hasattr(self.payload, 'token') or \
# not hasattr(self.payload, 'url'):
# raise NotAvailableForDownload()
elif not self.payload.token or not self.payload.url:
raise NotAvailableForDownload(f'Медиа типа `{self.type}` недоступно для скачивания')
# elif not self.payload.token or not self.payload.url:
# raise NotAvailableForDownload(f'Медиа типа `{self.type}` недоступно для скачивания')
return await self.bot.download_file(
path=path,
url=self.payload.url,
token=self.payload.token,
)
# return await self.bot.download_file(
# path=path,
# url=self.payload.url,
# token=self.payload.token,
# )

View File

@ -1,4 +1,6 @@
from typing import Literal, Optional
from typing import Optional
from ...enums.attachment import AttachmentType
from .attachment import Attachment
@ -13,5 +15,5 @@ class Audio(Attachment):
transcription (Optional[str]): Транскрипция аудио (если есть).
"""
type: Literal['audio'] = 'audio'
type: AttachmentType = AttachmentType.AUDIO
transcription: Optional[str] = None

View File

@ -1,4 +1,4 @@
from typing import Literal
from ...enums.attachment import AttachmentType
from .attachment import Attachment
@ -12,4 +12,4 @@ class Contact(Attachment):
type (Literal['contact']): Тип вложения, всегда 'contact'.
"""
type: Literal['contact'] = 'contact'
type: AttachmentType = AttachmentType.CONTACT

View File

@ -1,4 +1,6 @@
from typing import Literal, Optional
from typing import Optional
from ...enums.attachment import AttachmentType
from .attachment import Attachment
@ -14,6 +16,6 @@ class File(Attachment):
size (Optional[int]): Размер файла в байтах.
"""
type: Literal['file'] = 'file'
type: AttachmentType = AttachmentType.FILE
filename: Optional[str] = None
size: Optional[int] = None

View File

@ -1,7 +1,9 @@
from typing import Literal, Optional
from typing import Optional
from pydantic import BaseModel
from .attachment import Attachment
from ...enums.attachment import AttachmentType
class PhotoAttachmentRequestPayload(BaseModel):
@ -29,4 +31,4 @@ class Image(Attachment):
type (Literal['image']): Тип вложения, всегда 'image'.
"""
type: Literal['image'] = 'image'
type: AttachmentType = AttachmentType.IMAGE

View File

@ -1,4 +1,6 @@
from typing import Literal, Optional
from typing import Optional
from ...enums.attachment import AttachmentType
from .attachment import Attachment
@ -14,6 +16,6 @@ class Location(Attachment):
longitude (Optional[float]): Долгота.
"""
type: Literal['location'] = 'location'
type: AttachmentType = AttachmentType.LOCATION
latitude: Optional[float] = None
longitude: Optional[float] = None

View File

@ -1,4 +1,6 @@
from typing import Literal, Optional
from typing import Optional
from ...enums.attachment import AttachmentType
from .attachment import Attachment
@ -15,7 +17,7 @@ class Share(Attachment):
image_url (Optional[str]): URL изображения для предпросмотра.
"""
type: Literal['share'] = 'share'
type: AttachmentType = AttachmentType.SHARE
title: Optional[str] = None
description: Optional[str] = None
image_url: Optional[str] = None

View File

@ -1,4 +1,6 @@
from typing import Literal, Optional
from typing import Optional
from ...enums.attachment import AttachmentType
from .attachment import Attachment
@ -14,6 +16,6 @@ class Sticker(Attachment):
height (Optional[int]): Высота стикера в пикселях.
"""
type: Literal['sticker'] = 'sticker'
type: AttachmentType = AttachmentType.STICKER
width: Optional[int] = None
height: Optional[int] = None

View File

@ -1,6 +1,8 @@
from typing import TYPE_CHECKING, Any, Literal, Optional
from pydantic import BaseModel, Field
from ...enums.attachment import AttachmentType
from .attachment import Attachment
if TYPE_CHECKING:
@ -59,7 +61,7 @@ class Video(Attachment):
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
"""
type: Optional[Literal['video']] = 'video'
type: AttachmentType = AttachmentType.VIDEO
token: Optional[str] = None
urls: Optional[VideoUrl] = None
thumbnail: VideoThumbnail
@ -69,4 +71,4 @@ class Video(Attachment):
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional['Bot']
bot: Optional['Bot'] # type: ignore

View File

@ -70,11 +70,11 @@ class InputMediaBuffer:
Класс для представления медиафайла из буфера.
Attributes:
buffer (BytesIO): Буфер с содержимым файла.
buffer (bytes): Буфер с содержимым файла.
type (UploadType): Тип файла, определенный по содержимому.
"""
def __init__(self, buffer: BytesIO):
def __init__(self, buffer: bytes):
"""
Инициализирует объект медиафайла из буфера.
@ -84,7 +84,7 @@ class InputMediaBuffer:
self.buffer = buffer
self.type = self.__detect_file_type(buffer)
def __detect_file_type(self, buffer: BytesIO) -> UploadType:
def __detect_file_type(self, buffer: bytes) -> UploadType:
try:
matches = puremagic.magic_string(buffer)
if matches:

View File

@ -89,7 +89,7 @@ class MessageBody(BaseModel):
mid: str
seq: int
text: str = None
text: Optional[str] = None
attachments: Optional[
List[
Union[
@ -103,7 +103,7 @@ class MessageBody(BaseModel):
Location
]
]
] = Field(default_factory=list)
] = Field(default_factory=list) # type: ignore
markup: Optional[
List[
@ -111,7 +111,7 @@ class MessageBody(BaseModel):
MarkupLink, MarkupElement
]
]
] = Field(default_factory=list)
] = Field(default_factory=list) # type: ignore
class MessageStat(BaseModel):
@ -164,19 +164,19 @@ class Message(BaseModel):
recipient: Recipient
timestamp: int
link: Optional[LinkedMessage] = None
body: Optional[MessageBody] = None
body: MessageBody
stat: Optional[MessageStat] = None
url: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
bot: Optional[Bot] # type: ignore
async def answer(
self,
text: str = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
link: NewMessageLink = None,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
link: Optional[NewMessageLink] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
):
@ -195,6 +195,7 @@ class Message(BaseModel):
Any: Результат выполнения метода send_message бота.
"""
assert self.bot is not None
return await self.bot.send_message(
chat_id=self.recipient.chat_id,
user_id=self.recipient.user_id,
@ -207,8 +208,8 @@ class Message(BaseModel):
async def reply(
self,
text: str = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
):
@ -226,6 +227,7 @@ class Message(BaseModel):
Any: Результат выполнения метода send_message бота.
"""
assert self.bot is not None
return await self.bot.send_message(
chat_id=self.recipient.chat_id,
user_id=self.recipient.user_id,
@ -242,8 +244,8 @@ class Message(BaseModel):
async def forward(
self,
chat_id,
user_id: int = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
user_id: Optional[int] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
notify: Optional[bool] = None,
parse_mode: Optional[ParseMode] = None
):
@ -262,6 +264,7 @@ class Message(BaseModel):
Any: Результат выполнения метода send_message бота.
"""
assert self.bot is not None
return await self.bot.send_message(
chat_id=chat_id,
user_id=user_id,
@ -276,9 +279,9 @@ class Message(BaseModel):
async def edit(
self,
text: str = None,
attachments: List[Attachment | InputMedia | InputMediaBuffer] = None,
link: NewMessageLink = None,
text: Optional[str] = None,
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
link: Optional[NewMessageLink] = None,
notify: bool = True,
parse_mode: Optional[ParseMode] = None
):
@ -297,6 +300,7 @@ class Message(BaseModel):
Any: Результат выполнения метода edit_message бота.
"""
assert self.bot is not None
return await self.bot.edit_message(
message_id=self.body.mid,
text=text,
@ -331,6 +335,7 @@ class Message(BaseModel):
Any: Результат выполнения метода pin_message бота.
"""
assert self.bot is not None
return await self.bot.pin_message(
chat_id=self.recipient.chat_id,
message_id=self.body.mid,
@ -352,7 +357,7 @@ class Messages(BaseModel):
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
bot: Optional[Bot] # type: ignore
class NewMessageLink(BaseModel):

View File

@ -2,6 +2,8 @@ from typing import List, Optional, TYPE_CHECKING, Union
from pydantic import BaseModel, Field
from ...types.attachments.location import Location
from .update import Update
from ...enums.parse_mode import ParseMode
@ -49,10 +51,11 @@ class MessageForCallback(BaseModel):
File,
Image,
Sticker,
Share
Share,
Location
]
]
] = Field(default_factory=list)
] = Field(default_factory=list) # type: ignore
link: Optional[NewMessageLink] = None
notify: Optional[bool] = True
format: Optional[ParseMode] = None
@ -86,9 +89,9 @@ class MessageCallback(Update):
async def answer(
self,
notification: str = None,
new_text: str = None,
link: NewMessageLink = None,
notification: Optional[str] = None,
new_text: Optional[str] = None,
link: Optional[NewMessageLink] = None,
notify: bool = True,
format: Optional[ParseMode] = None,
):
@ -115,6 +118,7 @@ class MessageCallback(Update):
message.notify = notify
message.format = format
assert self.bot is not None
return await self.bot.send_callback(
callback_id=self.callback.callback_id,
message=message,

72
maxapi/utils/message.py Normal file
View File

@ -0,0 +1,72 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from json import loads
from ..types.input_media import InputMedia, InputMediaBuffer
from ..enums.upload_type import UploadType
from ..exceptions.max import MaxUploadFileFailed
from ..types.attachments.upload import AttachmentPayload, AttachmentUpload
if TYPE_CHECKING:
from ..bot import Bot
from ..connection.base import BaseConnection
async def process_input_media(
base_connection: BaseConnection,
bot: Bot,
att: InputMedia | InputMediaBuffer
):
# очень нестабильный метод независящий от модуля
# ждем обновлений MAX API
"""
Загружает файл вложения и формирует объект AttachmentUpload.
Args:
att (InputMedia): Объект вложения для загрузки.
Returns:
AttachmentUpload: Загруженное вложение с токеном.
"""
upload = await bot.get_upload_url(att.type)
if isinstance(att, InputMedia):
upload_file_response = await base_connection.upload_file(
url=upload.url,
path=att.path,
type=att.type,
)
elif isinstance(att, InputMediaBuffer):
upload_file_response = await base_connection.upload_file_buffer(
url=upload.url,
buffer=att.buffer,
type=att.type,
)
if att.type in (UploadType.VIDEO, UploadType.AUDIO):
if upload.token is None:
assert bot.session is not None
await bot.session.close()
raise MaxUploadFileFailed('По неизвестной причине token не был получен')
token = upload.token
elif att.type == UploadType.FILE:
json_r = loads(upload_file_response)
token = json_r['token']
elif att.type == UploadType.IMAGE:
json_r = loads(upload_file_response)
json_r_keys = list(json_r['photos'].keys())
token = json_r['photos'][json_r_keys[0]]['token']
return AttachmentUpload(
type=att.type,
payload=AttachmentPayload(
token=token
)
)

View File

@ -46,7 +46,7 @@ MemoryContext(chat_id: int, user_id: int)
---
### `async def set_state(state: State | str = None)`
### `async def set_state(state: Optional[Union[State, str]] = None)`
Устанавливает новое состояние пользователя или сбрасывает его.