Правки по 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]:
"""
Получает участника чата.
@ -659,11 +664,13 @@ 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

@ -16,4 +16,5 @@ class AttachmentType(str, Enum):
STICKER = 'sticker'
CONTACT = 'contact'
INLINE_KEYBOARD = 'inline_keyboard'
LOCATION = 'location'
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,11 +46,11 @@ 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) + \
ApiPath.MEMBERS + ApiPath.ADMINS + '/' + str(self.user_id),
model=RemovedAdmin,
params=self.bot.params,
)
)

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)`
Устанавливает новое состояние пользователя или сбрасывает его.