Добавлен types.InputMedia для простой загрузки медиафайлов

This commit is contained in:
Денис Семёнов 2025-06-20 02:24:14 +03:00
parent 85f58913c3
commit 1374d863f0
33 changed files with 332 additions and 262 deletions

View File

@ -1,151 +0,0 @@
import asyncio
import logging
from maxapi import Bot, Dispatcher, F
from maxapi.context import MemoryContext, State, StatesGroup
from maxapi.types import Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
from for_example import router
logging.basicConfig(level=logging.INFO)
bot = Bot('f9LHodD0cOL5NY7All_9xJRh5ZhPw6bRvq_0Adm8-1bZZEHdRy6_ZHDMNVPejUYNZg7Zhty-wKHNv2X2WJBQ')
dp = Dispatcher()
dp.include_routers(router)
start_text = '''Пример чат-бота для MAX 💙
Мои команды:
/clear очищает ваш контекст
/state или /context показывают ваше контекстное состояние
/data показывает вашу контекстную память
'''
class Form(StatesGroup):
name = State()
age = State()
@dp.on_started()
async def _():
logging.info('Бот стартовал!')
@dp.message_created(Command('clear'))
async def hello(event: MessageCreated, context: MemoryContext):
await context.clear()
await event.message.answer(f"Ваш контекст был очищен!")
@dp.message_created(Command('data'))
async def hello(event: MessageCreated, context: MemoryContext):
data = await context.get_data()
await event.message.answer(f"Ваша контекстная память: {str(data)}")
@dp.message_created(Command('context'))
@dp.message_created(Command('state'))
async def hello(event: MessageCreated, context: MemoryContext):
data = await context.get_state()
await event.message.answer(f"Ваше контекстное состояние: {str(data)}")
@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
builder = InlineKeyboardBuilder()
builder.row(
CallbackButton(
text='Ввести свое имя',
payload='btn_1'
),
CallbackButton(
text='Ввести свой возраст',
payload='btn_2'
)
)
builder.row(
CallbackButton(
text='Не хочу',
payload='btn_3'
)
)
await event.message.answer(
text=start_text,
attachments=[builder.as_markup()] # Для MAX клавиатура это вложение,
) # поэтому она в списке вложений
@dp.message_callback(F.callback.payload == 'btn_1')
async def hello(event: MessageCallback, context: MemoryContext):
await context.set_state(Form.name)
await event.message.delete()
await event.message.answer(f'Отправьте свое имя:')
@dp.message_callback(F.callback.payload == 'btn_2')
async def hello(event: MessageCallback, context: MemoryContext):
await context.set_state(Form.age)
await event.message.delete()
await event.message.answer(f'Отправьте ваш возраст:')
@dp.message_callback(F.callback.payload == 'btn_3')
async def hello(event: MessageCallback, context: MemoryContext):
await event.message.delete()
await event.message.answer(f'Ну ладно 🥲')
@dp.message_created(F.message.body.text, Form.name)
async def hello(event: MessageCreated, context: MemoryContext):
await context.update_data(name=event.message.body.text)
data = await context.get_data()
await event.message.answer(f"Приятно познакомиться, {data['name'].title()}!")
@dp.message_created(F.message.body.text, Form.age)
async def hello(event: MessageCreated, context: MemoryContext):
await context.update_data(age=event.message.body.text)
await event.message.answer(f"Ого! А мне всего пару недель 😁")
async def main():
await bot.set_my_commands(
BotCommand(
name='/start',
description='Перезапустить бота'
),
BotCommand(
name='/clear',
description='Очищает ваш контекст'
),
BotCommand(
name='/state',
description='Показывают ваше контекстное состояние'
),
BotCommand(
name='/data',
description='Показывает вашу контекстную память'
),
BotCommand(
name='/context',
description='Показывают ваше контекстное состояние'
)
)
await dp.start_polling(bot)
# await dp.handle_webhook(
# bot=bot,
# host='localhost',
# port=8080
# )
asyncio.run(main())

BIN
example/audio.mp3 Normal file

Binary file not shown.

View File

@ -3,19 +3,22 @@ import logging
from maxapi import Bot, Dispatcher, F from maxapi import Bot, Dispatcher, F
from maxapi.context import MemoryContext, State, StatesGroup from maxapi.context import MemoryContext, State, StatesGroup
from maxapi.types import Command, MessageCreated, CallbackButton, MessageCallback from maxapi.types import Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
from maxapi.types.input_media import InputMedia
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
from example.for_example import router from for_example import router
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
bot = Bot('токен') bot = Bot('f9LHodD0cOL5NY7All_9xJRh5ZhPw6bRvq_0Adm8-1bZZEHdRy6_ZHDMNVPejUYNZg7Zhty-wKHNv2X2WJBQ')
dp = Dispatcher() dp = Dispatcher()
dp.include_routers(router) dp.include_routers(router)
start_text = '''Мои команды: start_text = '''Пример чат-бота для MAX 💙
Мои команды:
/clear очищает ваш контекст /clear очищает ваш контекст
/state или /context показывают ваше контекстное состояние /state или /context показывают ваше контекстное состояние
@ -34,26 +37,26 @@ async def _():
@dp.message_created(Command('clear')) @dp.message_created(Command('clear'))
async def hello(obj: MessageCreated, context: MemoryContext): async def hello(event: MessageCreated, context: MemoryContext):
await context.clear() await context.clear()
await obj.message.answer(f"Ваш контекст был очищен!") await event.message.answer(f"Ваш контекст был очищен!")
@dp.message_created(Command('data')) @dp.message_created(Command('data'))
async def hello(obj: MessageCreated, context: MemoryContext): async def hello(event: MessageCreated, context: MemoryContext):
data = await context.get_data() data = await context.get_data()
await obj.message.answer(f"Ваша контекстная память: {str(data)}") await event.message.answer(f"Ваша контекстная память: {str(data)}")
@dp.message_created(Command('context')) @dp.message_created(Command('context'))
@dp.message_created(Command('state')) @dp.message_created(Command('state'))
async def hello(obj: MessageCreated, context: MemoryContext): async def hello(event: MessageCreated, context: MemoryContext):
data = await context.get_state() data = await context.get_state()
await obj.message.answer(f"Ваше контекстное состояние: {str(data)}") await event.message.answer(f"Ваше контекстное состояние: {str(data)}")
@dp.message_created(Command('start')) @dp.message_created(Command('start'))
async def hello(obj: MessageCreated): async def hello(event: MessageCreated):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
@ -73,49 +76,73 @@ async def hello(obj: MessageCreated):
) )
) )
await obj.message.answer( await event.message.answer(
text=start_text, text=start_text,
attachments=[builder.as_markup()] # Для MAX клавиатура это вложение, attachments=[
) # поэтому она в списке вложений builder.as_markup(),
] # Для MAX клавиатура это вложение,
) # поэтому она в списке вложений
@dp.message_callback(F.callback.payload == 'btn_1') @dp.message_callback(F.callback.payload == 'btn_1')
async def hello(obj: MessageCallback, context: MemoryContext): async def hello(event: MessageCallback, context: MemoryContext):
await context.set_state(Form.name) await context.set_state(Form.name)
await obj.message.delete() await event.message.delete()
await obj.message.answer(f'Отправьте свое имя:') await event.message.answer(f'Отправьте свое имя:')
@dp.message_callback(F.callback.payload == 'btn_2') @dp.message_callback(F.callback.payload == 'btn_2')
async def hello(obj: MessageCallback, context: MemoryContext): async def hello(event: MessageCallback, context: MemoryContext):
await context.set_state(Form.age) await context.set_state(Form.age)
await obj.message.delete() await event.message.delete()
await obj.message.answer(f'Отправьте ваш возраст:') await event.message.answer(f'Отправьте ваш возраст:')
@dp.message_callback(F.callback.payload == 'btn_3') @dp.message_callback(F.callback.payload == 'btn_3')
async def hello(obj: MessageCallback, context: MemoryContext): async def hello(event: MessageCallback, context: MemoryContext):
await obj.message.delete() await event.message.delete()
await obj.message.answer(f'Ну ладно 🥲') await event.message.answer(f'Ну ладно 🥲')
@dp.message_created(F.message.body.text, Form.name) @dp.message_created(F.message.body.text, Form.name)
async def hello(obj: MessageCreated, context: MemoryContext): async def hello(event: MessageCreated, context: MemoryContext):
await context.update_data(name=obj.message.body.text) await context.update_data(name=event.message.body.text)
data = await context.get_data() data = await context.get_data()
await obj.message.answer(f"Приятно познакомиться, {data['name'].title()}!") await event.message.answer(f"Приятно познакомиться, {data['name'].title()}!")
@dp.message_created(F.message.body.text, Form.age) @dp.message_created(F.message.body.text, Form.age)
async def hello(obj: MessageCreated, context: MemoryContext): async def hello(event: MessageCreated, context: MemoryContext):
await context.update_data(age=obj.message.body.text) await context.update_data(age=event.message.body.text)
await obj.message.answer(f"Ого! А мне всего пару недель 😁") await event.message.answer(f"Ого! А мне всего пару недель 😁")
async def main(): async def main():
await bot.set_my_commands(
BotCommand(
name='/start',
description='Перезапустить бота'
),
BotCommand(
name='/clear',
description='Очищает ваш контекст'
),
BotCommand(
name='/state',
description='Показывают ваше контекстное состояние'
),
BotCommand(
name='/data',
description='Показывает вашу контекстную память'
),
BotCommand(
name='/context',
description='Показывают ваше контекстное состояние'
)
)
await dp.start_polling(bot) await dp.start_polling(bot)
# await dp.handle_webhook( # await dp.handle_webhook(
# bot=bot, # bot=bot,

View File

@ -1,10 +1,24 @@
from maxapi import F, Router from maxapi import F, Router
from maxapi.types import Command, MessageCreated from maxapi.types import Command, MessageCreated
from maxapi.types import InputMedia
router = Router() router = Router()
file = __file__.split('\\')[-1]
@router.message_created(Command('router')) @router.message_created(Command('router'))
async def hello(obj: MessageCreated): async def hello(obj: MessageCreated):
file = __file__.split('\\')[-1]
await obj.message.answer(f"Пишу тебе из роута {file}") await obj.message.answer(f"Пишу тебе из роута {file}")
# новая команда для примера, /media,
# пример использования: /media image.png (медиафайл берется указанному пути)
@router.message_created(Command('media'))
async def hello(event: MessageCreated):
await event.message.answer(
attachments=[
InputMedia(
path=event.message.body.text.replace('/media ', '')
)
]
)

View File

@ -1,10 +0,0 @@
from maxapi import F, Router
from maxapi.types import Command, MessageCreated
router = Router()
@router.message_created(Command('router'))
async def hello(obj: MessageCreated):
file = __file__.split('\\')[-1]
await obj.message.answer(f"Пишу тебе из роута {file}")

View File

@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List, TYPE_CHECKING from typing import Any, Dict, List, TYPE_CHECKING
from .methods.get_upload_url import GetUploadURL
from .methods.get_updates import GetUpdates from .methods.get_updates import GetUpdates
from .methods.remove_member_chat import RemoveMemberChat from .methods.remove_member_chat import RemoveMemberChat
from .methods.add_admin_chat import AddAdminChat from .methods.add_admin_chat import AddAdminChat
@ -30,6 +31,7 @@ from .methods.send_message import SendMessage
from .enums.parse_mode import ParseMode from .enums.parse_mode import ParseMode
from .enums.sender_action import SenderAction from .enums.sender_action import SenderAction
from .enums.upload_type import UploadType
from .types.attachments.attachment import Attachment from .types.attachments.attachment import Attachment
from .types.attachments.image import PhotoAttachmentRequestPayload from .types.attachments.image import PhotoAttachmentRequestPayload
@ -337,6 +339,15 @@ class Bot(BaseConnection):
bot=self, bot=self,
).request() ).request()
async def get_upload_url(
self,
type: UploadType
):
return await GetUploadURL(
bot=self,
type=type
).request()
async def set_my_commands( async def set_my_commands(
self, self,
*commands: BotCommand *commands: BotCommand

View File

@ -1,10 +1,17 @@
import os
from typing import TYPE_CHECKING
import aiohttp import aiohttp
from pydantic import BaseModel from pydantic import BaseModel
from ..types.errors import Error from ..types.errors import Error
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
from ..loggers import logger_bot from ..enums.upload_type import UploadType
from ..loggers import logger_bot, logger_connection
if TYPE_CHECKING:
from ..bot import Bot
class BaseConnection: class BaseConnection:
@ -12,14 +19,14 @@ class BaseConnection:
API_URL = 'https://botapi.max.ru' API_URL = 'https://botapi.max.ru'
def __init__(self): def __init__(self):
self.bot = None self.bot: 'Bot' = None
self.session = None self.session: aiohttp.ClientSession = None
async def request( async def request(
self, self,
method: HTTPMethod, method: HTTPMethod,
path: ApiPath, path: ApiPath,
model: BaseModel, model: BaseModel = None,
is_return_raw: bool = False, is_return_raw: bool = False,
**kwargs **kwargs
): ):
@ -27,15 +34,18 @@ class BaseConnection:
if not self.bot.session: if not self.bot.session:
self.bot.session = aiohttp.ClientSession(self.bot.API_URL) self.bot.session = aiohttp.ClientSession(self.bot.API_URL)
r = await self.bot.session.request( try:
method=method.value, r = await self.bot.session.request(
url=path.value if isinstance(path, ApiPath) else path, method=method.value,
**kwargs url=path.value if isinstance(path, ApiPath) else path,
) **kwargs
)
except aiohttp.ClientConnectorDNSError as e:
return logger_connection.error(f'Ошибка при отправке запроса: {e}')
if not r.ok: if not r.ok:
raw = await r.text() raw = await r.json()
error = Error(code=r.status, text=raw) error = Error(code=r.status, raw=raw)
logger_bot.error(error) logger_bot.error(error)
return error return error
@ -54,3 +64,31 @@ class BaseConnection:
model.bot = self.bot model.bot = self.bot
return model return model
async def upload_file(
self,
url: str,
path: str,
type: UploadType
):
with open(path, 'rb') as f:
file_data = f.read()
basename = os.path.basename(path)
name, ext = os.path.splitext(basename)
form = aiohttp.FormData()
form.add_field(
name='data',
value=file_data,
filename=basename,
content_type=f"{type.value}/{ext.lstrip('.')}"
)
async with aiohttp.ClientSession() as session:
response = await session.post(
url=url,
data=form
)
return await response.text()

View File

@ -3,6 +3,7 @@ from typing import Callable, List
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from uvicorn import Config, Server from uvicorn import Config, Server
from aiohttp import ClientConnectorDNSError
from .filters.handler import Handler from .filters.handler import Handler
@ -121,7 +122,9 @@ class Dispatcher:
try: try:
await self.handle(event) await self.handle(event)
except Exception as e: except Exception as e:
logger_dp.error(f"Ошибка при обработке события: {events['update_type']}: {e}") logger_dp.error(f"Ошибка при обработке события: {event.update_type}: {e}")
except ClientConnectorDNSError:
logger_dp.error(f'Ошибка подключения: {e}')
except Exception as e: except Exception as e:
logger_dp.error(f'Общая ошибка при обработке событий: {e}') logger_dp.error(f'Общая ошибка при обработке событий: {e}')

View File

@ -11,3 +11,4 @@ class ApiPath(str, Enum):
PIN = '/pin' PIN = '/pin'
MEMBERS = '/members' MEMBERS = '/members'
ADMINS = '/admins' ADMINS = '/admins'
UPLOADS = '/uploads'

View File

@ -0,0 +1,9 @@
from enum import Enum
class ChatStatus(str, Enum):
ACTIVE = 'active'
REMOVED = 'removed'
LEFT = 'left'
CLOSED = 'closed'
SUSPENDED = 'suspended'

View File

@ -0,0 +1,8 @@
from enum import Enum
class UploadType(str, Enum):
IMAGE = 'image'
VIDEO = 'video'
AUDIO = 'audio'
FILE = 'file'

View File

@ -29,7 +29,7 @@ class Handler:
elif isinstance(arg, State): elif isinstance(arg, State):
self.state = arg self.state = arg
elif isinstance(arg, Command): elif isinstance(arg, Command):
self.filters.insert(0, F.message.body.text == arg.command) self.filters.insert(0, F.message.body.text.startswith(arg.command))
else: else:
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при ' logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
f'регистрации функции `{func_event.__name__}`') f'регистрации функции `{func_event.__name__}`')

View File

@ -1,4 +1,5 @@
import logging import logging
logger_bot = logging.getLogger('bot') logger_bot = logging.getLogger('bot')
logger_connection = logging.getLogger('connection')
logger_dp = logging.getLogger('dispatcher') logger_dp = logging.getLogger('dispatcher')

View File

@ -1,6 +1,3 @@
from re import findall
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List
from .types.added_admin_chat import AddedListAdminChat from .types.added_admin_chat import AddedListAdminChat

View File

@ -1,6 +1,3 @@
from re import findall
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List
from ..methods.types.added_members_chat import AddedMembersChat from ..methods.types.added_members_chat import AddedMembersChat

View File

@ -1,5 +1,3 @@
from typing import Any, Dict, List, TYPE_CHECKING from typing import Any, Dict, List, TYPE_CHECKING
from ..types.users import User from ..types.users import User

View File

@ -1,12 +1,7 @@
from re import findall
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..types.chats import Chat from ..types.chats import Chat
from ..types.users import User
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath

View File

@ -1,12 +1,8 @@
from re import findall from re import findall
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..types.chats import Chat from ..types.chats import Chat
from ..types.users import User
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath

View File

@ -4,8 +4,6 @@ from typing import TYPE_CHECKING
from ..types.chats import Chats from ..types.chats import Chats
from ..types.users import User
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath

View File

@ -1,6 +1,3 @@
from re import findall
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..methods.types.getted_list_admin_chat import GettedListAdminChat from ..methods.types.getted_list_admin_chat import GettedListAdminChat

View File

@ -2,9 +2,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..types.chats import ChatMember, Chats from ..types.chats import ChatMember
from ..types.users import User
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath

View File

@ -1,6 +1,3 @@
from re import findall
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List
from ..methods.types.getted_members_chat import GettedMembersChat from ..methods.types.getted_members_chat import GettedMembersChat

View File

@ -0,0 +1,35 @@
from typing import TYPE_CHECKING
from ..methods.types.getted_upload_url import GettedUploadUrl
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..enums.upload_type import UploadType
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetUploadURL(BaseConnection):
def __init__(
self,
bot: 'Bot',
type: UploadType
):
self.bot = bot
self.type = type
async def request(self) -> GettedUploadUrl:
params = self.bot.params.copy()
params['type'] = self.type.value
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.UPLOADS,
model=GettedUploadUrl,
params=params,
)

View File

@ -1,20 +1,33 @@
import asyncio
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING
from json import loads as json_loads
from ..enums.upload_type import UploadType
from ..types.attachments.upload import AttachmentPayload, AttachmentUpload
from ..types.errors import Error
from .types.sended_message import SendedMessage from .types.sended_message import SendedMessage
from ..types.message import NewMessageLink from ..types.message import NewMessageLink
from ..types.input_media import InputMedia
from ..types.attachments.attachment import Attachment from ..types.attachments.attachment import Attachment
from ..enums.parse_mode import ParseMode from ..enums.parse_mode import ParseMode
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection from ..connection.base import BaseConnection
from ..loggers import logger_bot
if TYPE_CHECKING: if TYPE_CHECKING:
from ..bot import Bot from ..bot import Bot
class UploadResponse:
token: str = None
class SendMessage(BaseConnection): class SendMessage(BaseConnection):
def __init__( def __init__(
self, self,
@ -23,7 +36,7 @@ class SendMessage(BaseConnection):
user_id: int = None, user_id: int = None,
disable_link_preview: bool = False, disable_link_preview: bool = False,
text: str = None, text: str = None,
attachments: List[Attachment] = None, attachments: List[Attachment | InputMedia] = None,
link: NewMessageLink = None, link: NewMessageLink = None,
notify: bool = True, notify: bool = True,
parse_mode: ParseMode = None parse_mode: ParseMode = None
@ -38,10 +51,41 @@ class SendMessage(BaseConnection):
self.notify = notify self.notify = notify
self.parse_mode = parse_mode self.parse_mode = parse_mode
async def __process_input_media(
self,
att: InputMedia
):
upload = await self.bot.get_upload_url(att.type)
upload_file_response = await self.upload_file(
url=upload.url,
path=att.path,
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 request(self) -> SendedMessage:
params = self.bot.params.copy() params = self.bot.params.copy()
json = {} json = {'attachments': []}
if self.chat_id: params['chat_id'] = self.chat_id if self.chat_id: params['chat_id'] = self.chat_id
elif self.user_id: params['user_id'] = self.user_id elif self.user_id: params['user_id'] = self.user_id
@ -49,17 +93,37 @@ class SendMessage(BaseConnection):
json['text'] = self.text json['text'] = self.text
json['disable_link_preview'] = str(self.disable_link_preview).lower() json['disable_link_preview'] = str(self.disable_link_preview).lower()
if self.attachments: json['attachments'] = \ if self.attachments:
[att.model_dump() for att in self.attachments]
for att in self.attachments:
if isinstance(att, InputMedia):
input_media = await self.__process_input_media(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.link is None: json['link'] = self.link.model_dump()
if not self.notify is None: json['notify'] = self.notify if not self.notify is None: json['notify'] = self.notify
if not self.parse_mode is None: json['format'] = self.parse_mode.value if not self.parse_mode is None: json['format'] = self.parse_mode.value
return await super().request( response = None
method=HTTPMethod.POST, for attempt in range(5):
path=ApiPath.MESSAGES, response = await super().request(
model=SendedMessage, method=HTTPMethod.POST,
params=params, path=ApiPath.MESSAGES,
json=json model=SendedMessage,
) params=params,
json=json
)
if isinstance(response, Error):
if response.raw.get('code') == 'attachment.not.ready':
logger_bot.info(f'Ошибка при отправке загруженного медиа, попытка {attempt+1}, жду 2 секунды')
await asyncio.sleep(2)
continue
return response
return response

View File

@ -0,0 +1,9 @@
from typing import Any, Optional
from pydantic import BaseModel
from ...types.message import Message
class GettedUploadUrl(BaseModel):
url: Optional[str] = None
token: Optional[str] = None

View File

@ -0,0 +1,5 @@
from pydantic import BaseModel
class UploadFileResponse(BaseModel):
...

View File

@ -23,7 +23,10 @@ from ..types.attachments.buttons.request_geo_location_button import RequestGeoLo
from ..types.command import Command, BotCommand from ..types.command import Command, BotCommand
from input_media import InputMedia
__all__ = [ __all__ = [
InputMedia,
BotCommand, BotCommand,
CallbackButton, CallbackButton,
ChatButton, ChatButton,

View File

@ -1,6 +1,8 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from pydantic import BaseModel from pydantic import BaseModel
from ...types.attachments.upload import AttachmentUpload
from ...types.attachments.buttons import InlineButtonUnion from ...types.attachments.buttons import InlineButtonUnion
from ...types.users import User from ...types.users import User
from ...enums.attachment import AttachmentType from ...enums.attachment import AttachmentType
@ -36,6 +38,7 @@ class ButtonsPayload(BaseModel):
class Attachment(BaseModel): class Attachment(BaseModel):
type: AttachmentType type: AttachmentType
payload: Optional[Union[ payload: Optional[Union[
AttachmentUpload,
PhotoAttachmentPayload, PhotoAttachmentPayload,
OtherAttachmentPayload, OtherAttachmentPayload,
ContactAttachmentPayload, ContactAttachmentPayload,

View File

@ -0,0 +1,14 @@
from pydantic import BaseModel
from ...enums.upload_type import UploadType
class AttachmentPayload(BaseModel):
token: str
class AttachmentUpload(BaseModel):
type: UploadType
payload: AttachmentPayload

View File

@ -1,10 +1,8 @@
from typing import List, Optional, Union from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from ..types.users import User from ..types.users import User
from ..types.users import User
class Callback(BaseModel): class Callback(BaseModel):
timestamp: int timestamp: int

View File

@ -1,23 +1,14 @@
from pydantic import BaseModel, field_validator from pydantic import BaseModel, field_validator
from typing import Dict, List, Optional from typing import Dict, List, Optional
from enum import Enum
from datetime import datetime from datetime import datetime
from ..enums.chat_status import ChatStatus
from ..enums.chat_type import ChatType
from ..enums.chat_permission import ChatPermission from ..enums.chat_permission import ChatPermission
from ..types.users import User from ..types.users import User
from ..types.message import Message from ..types.message import Message
class ChatType(str, Enum):
DIALOG = "dialog"
CHAT = "chat"
class ChatStatus(str, Enum):
ACTIVE = "active"
REMOVED = "removed"
LEFT = "left"
CLOSED = "closed"
SUSPENDED = "suspended"
class Icon(BaseModel): class Icon(BaseModel):
url: str url: str

View File

@ -3,4 +3,4 @@ from pydantic import BaseModel
class Error(BaseModel): class Error(BaseModel):
code: int code: int
text: str raw: dict

View File

@ -0,0 +1,24 @@
import mimetypes
from ..enums.upload_type import UploadType
class InputMedia:
def __init__(self, path: str):
self.path = path
self.type = self.__detect_file_type(path)
def __detect_file_type(self, path: str) -> UploadType:
mime_type, _ = mimetypes.guess_type(path)
if mime_type is None:
return UploadType.FILE
if mime_type.startswith('video/'):
return UploadType.VIDEO
elif mime_type.startswith('image/'):
return UploadType.IMAGE
elif mime_type.startswith('audio/'):
return UploadType.AUDIO
else:
return UploadType.FILE