Compare commits
14 Commits
1b0b118239
...
fe68e41b7a
| Author | SHA1 | Date | |
|---|---|---|---|
| fe68e41b7a | |||
| 50980dfc77 | |||
| 036c92d072 | |||
| cb2226eee5 | |||
| 3855f93862 | |||
| 01e9cdd2fd | |||
| 338d9c4089 | |||
| 3fa34079ae | |||
| 5bc5fb45c8 | |||
| 95313ad3dc | |||
| 42690d24ee | |||
| 6ad3df5829 | |||
| 622d3a3eb3 | |||
| 67de8aae1f |
@@ -8,4 +8,5 @@
|
||||
- [Миддлварь в хендлерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_in_handlers/main.py)
|
||||
- [Вебхуки](https://github.com/love-apples/maxapi/tree/main/examples/webhook)
|
||||
- [Клавиатуры](https://github.com/love-apples/maxapi/tree/main/examples/keyboard/main.py)
|
||||
- [Миддлварь в роутерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_for_router/main.py)
|
||||
- [Миддлварь в роутерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_for_router/main.py)
|
||||
- [BaseFilter](https://github.com/love-apples/maxapi/tree/main/examples/base_filter/main.py)
|
||||
38
examples/base_filter/main.py
Normal file
38
examples/base_filter/main.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.types import MessageCreated, CommandStart, UpdateUnion
|
||||
from maxapi.filters import BaseFilter
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token='тут_ваш_токен')
|
||||
dp = Dispatcher()
|
||||
|
||||
|
||||
class FilterChat(BaseFilter):
|
||||
|
||||
"""
|
||||
Фильтр, который срабатывает только в чате с названием `Test`
|
||||
"""
|
||||
|
||||
async def __call__(self, event: UpdateUnion):
|
||||
|
||||
if not event.chat:
|
||||
return False
|
||||
|
||||
return event.chat == 'Test'
|
||||
|
||||
|
||||
@dp.message_created(CommandStart(), FilterChat())
|
||||
async def custom_data(event: MessageCreated):
|
||||
await event.message.answer('Привет!')
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -55,12 +55,17 @@ async def hello(event: MessageCreated):
|
||||
attachments=[
|
||||
builder.as_markup(),
|
||||
] # Для MAX клавиатура это вложение,
|
||||
) # поэтому она в списке вложений
|
||||
) # поэтому она в attachments
|
||||
|
||||
|
||||
@dp.bot_added()
|
||||
async def bot_added(event: BotAdded):
|
||||
await event.bot.send_message(
|
||||
|
||||
if not event.chat:
|
||||
logging.info('Не удалось получить chat, возможно отключен auto_requests!')
|
||||
return
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=event.chat.id,
|
||||
text=f'Привет чат {event.chat.title}!'
|
||||
)
|
||||
@@ -68,7 +73,7 @@ async def bot_added(event: BotAdded):
|
||||
|
||||
@dp.message_removed()
|
||||
async def message_removed(event: MessageRemoved):
|
||||
await event.bot.send_message(
|
||||
await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text='Я всё видел!'
|
||||
)
|
||||
@@ -76,7 +81,7 @@ async def message_removed(event: MessageRemoved):
|
||||
|
||||
@dp.bot_started()
|
||||
async def bot_started(event: BotStarted):
|
||||
await event.bot.send_message(
|
||||
await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text='Привет! Отправь мне /start'
|
||||
)
|
||||
@@ -84,9 +89,9 @@ async def bot_started(event: BotStarted):
|
||||
|
||||
@dp.chat_title_changed()
|
||||
async def chat_title_changed(event: ChatTitleChanged):
|
||||
await event.bot.send_message(
|
||||
await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Крутое новое название "{event.chat.title}"!'
|
||||
text=f'Крутое новое название "{event.title}"!'
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +111,14 @@ async def message_edited(event: MessageEdited):
|
||||
|
||||
@dp.user_removed()
|
||||
async def user_removed(event: UserRemoved):
|
||||
await event.bot.send_message(
|
||||
|
||||
if not event.from_user:
|
||||
return await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Неизвестный кикнул {event.user.first_name} 😢'
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
|
||||
)
|
||||
@@ -114,7 +126,14 @@ async def user_removed(event: UserRemoved):
|
||||
|
||||
@dp.user_added()
|
||||
async def user_added(event: UserAdded):
|
||||
await event.bot.send_message(
|
||||
|
||||
if not event.chat:
|
||||
return await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Чат приветствует вас, {event.user.first_name}!'
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
|
||||
)
|
||||
@@ -122,27 +141,32 @@ async def user_added(event: UserAdded):
|
||||
|
||||
@dp.bot_stopped()
|
||||
async def bot_stopped(event: BotStopped):
|
||||
print(event.from_user.full_name, 'остановил бота') # type: ignore
|
||||
logging.info(event.from_user.full_name, 'остановил бота') # type: ignore
|
||||
|
||||
|
||||
@dp.dialog_cleared()
|
||||
async def dialog_cleared(event: DialogCleared):
|
||||
print(event.from_user.full_name, 'очистил историю чата с ботом') # type: ignore
|
||||
logging.info(event.from_user.full_name, 'очистил историю чата с ботом') # type: ignore
|
||||
|
||||
|
||||
@dp.dialog_muted()
|
||||
async def dialog_muted(event: DialogMuted):
|
||||
print(event.from_user.full_name, 'отключил оповещения от чата бота до ', event.muted_until_datetime) # type: ignore
|
||||
logging.info(event.from_user.full_name, 'отключил оповещения от чата бота до ', event.muted_until_datetime) # type: ignore
|
||||
|
||||
|
||||
@dp.dialog_unmuted()
|
||||
async def dialog_unmuted(event: DialogUnmuted):
|
||||
print(event.from_user.full_name, 'включил оповещения от чата бота') # type: ignore
|
||||
logging.info(event.from_user.full_name, 'включил оповещения от чата бота') # type: ignore
|
||||
|
||||
|
||||
@dp.dialog_unmuted()
|
||||
async def dialog_removed(event: DialogUnmuted):
|
||||
logging.info(event.from_user.full_name, 'удалил диалог с ботом') # type: ignore
|
||||
|
||||
|
||||
@dp.message_chat_created()
|
||||
async def message_chat_created(event: MessageChatCreated):
|
||||
await event.bot.send_message(
|
||||
await bot.send_message(
|
||||
chat_id=event.chat.chat_id,
|
||||
text=f'Чат создан! Ссылка: {event.chat.link}'
|
||||
)
|
||||
|
||||
@@ -54,8 +54,8 @@ async def builder(event: MessageCreated):
|
||||
chat_description='Test desc'
|
||||
),
|
||||
LinkButton(
|
||||
text="Канал разработчика",
|
||||
url="https://t.me/loveapples_dev"
|
||||
text="Документация MAX",
|
||||
url="https://dev.max.ru/docs"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -99,8 +99,8 @@ async def payload(event: MessageCreated):
|
||||
chat_description='Test desc'
|
||||
),
|
||||
LinkButton(
|
||||
text="Канал разработчика",
|
||||
url="https://t.me/loveapples_dev"
|
||||
text="Документация MAX",
|
||||
url="https://dev.max.ru/docs"
|
||||
),
|
||||
],
|
||||
[
|
||||
|
||||
@@ -32,9 +32,8 @@ async def custom_data(event: MessageCreated, custom_data: str):
|
||||
|
||||
|
||||
async def main():
|
||||
dp.middlewares = [
|
||||
CustomDataForRouterMiddleware()
|
||||
]
|
||||
dp.middleware(CustomDataForRouterMiddleware())
|
||||
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ from .methods.subscribe_webhook import SubscribeWebhook
|
||||
from .methods.types.subscribed import Subscribed
|
||||
from .methods.types.unsubscribed import Unsubscribed
|
||||
from .methods.unsubscribe_webhook import UnsubscribeWebhook
|
||||
from .methods.get_message import GetMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types.attachments.attachment import Attachment
|
||||
@@ -300,7 +301,7 @@ class Bot(BaseConnection):
|
||||
async def get_message(
|
||||
self,
|
||||
message_id: str
|
||||
) -> Messages:
|
||||
) -> Message:
|
||||
|
||||
"""
|
||||
Получает одно сообщение по ID.
|
||||
@@ -310,13 +311,15 @@ class Bot(BaseConnection):
|
||||
:return: Объект сообщения
|
||||
"""
|
||||
|
||||
return await self.get_messages(
|
||||
message_ids=[message_id]
|
||||
)
|
||||
return await GetMessage(
|
||||
bot=self,
|
||||
message_id=message_id
|
||||
).fetch()
|
||||
|
||||
async def get_me(self) -> User:
|
||||
|
||||
"""
|
||||
https://dev.max.ru/docs-api/methods/GET/me\n
|
||||
Получает информацию о текущем боте.
|
||||
|
||||
:return: Объект пользователя бота
|
||||
|
||||
@@ -3,11 +3,12 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
|
||||
import functools
|
||||
from typing import Any, Awaitable, Callable, Dict, List, TYPE_CHECKING, Optional
|
||||
from typing import Any, Awaitable, Callable, Dict, List, TYPE_CHECKING, Literal, Optional
|
||||
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
|
||||
|
||||
from aiohttp import ClientConnectorError
|
||||
|
||||
from .filters.filter import BaseFilter
|
||||
from .filters.middleware import BaseMiddleware
|
||||
from .filters.handler import Handler
|
||||
|
||||
@@ -70,6 +71,7 @@ class Dispatcher:
|
||||
self.contexts: List[MemoryContext] = []
|
||||
self.routers: List[Router | Dispatcher] = []
|
||||
self.filters: List[MagicFilter] = []
|
||||
self.base_filters: List[BaseFilter] = []
|
||||
self.middlewares: List[BaseMiddleware] = []
|
||||
|
||||
self.bot: Optional[Bot] = None
|
||||
@@ -85,6 +87,7 @@ class Dispatcher:
|
||||
self.dialog_cleared = Event(update_type=UpdateType.DIALOG_CLEARED, router=self)
|
||||
self.dialog_muted = Event(update_type=UpdateType.DIALOG_MUTED, router=self)
|
||||
self.dialog_unmuted = Event(update_type=UpdateType.DIALOG_UNMUTED, router=self)
|
||||
self.dialog_removed = Event(update_type=UpdateType.DIALOG_REMOVED, router=self)
|
||||
self.chat_title_changed = Event(update_type=UpdateType.CHAT_TITLE_CHANGED, router=self)
|
||||
self.message_callback = Event(update_type=UpdateType.MESSAGE_CALLBACK, router=self)
|
||||
self.message_chat_created = Event(update_type=UpdateType.MESSAGE_CHAT_CREATED, router=self)
|
||||
@@ -143,6 +146,36 @@ class Dispatcher:
|
||||
"""
|
||||
|
||||
self.routers += [r for r in routers]
|
||||
|
||||
def outer_middleware(self, middleware: BaseMiddleware) -> None:
|
||||
|
||||
"""
|
||||
Добавляет Middleware на первое место в списке
|
||||
|
||||
:param: middleware: Middleware
|
||||
"""
|
||||
|
||||
self.middlewares.insert(0, middleware)
|
||||
|
||||
def middleware(self, middleware: BaseMiddleware) -> None:
|
||||
|
||||
"""
|
||||
Добавляет Middleware в список
|
||||
|
||||
:param middleware: Middleware
|
||||
"""
|
||||
|
||||
self.middlewares.append(middleware)
|
||||
|
||||
def filter(self, base_filter: BaseFilter) -> None:
|
||||
|
||||
"""
|
||||
Добавляет фильтр в список
|
||||
|
||||
:param base_filter: Фильтр
|
||||
"""
|
||||
|
||||
self.base_filters.append(base_filter)
|
||||
|
||||
async def __ready(self, bot: Bot):
|
||||
|
||||
@@ -199,8 +232,29 @@ class Dispatcher:
|
||||
func_args = handler.func_event.__annotations__.keys()
|
||||
kwargs_filtered = {k: v for k, v in data.items() if k in func_args}
|
||||
|
||||
await handler.func_event(event_object, **kwargs_filtered)
|
||||
if kwargs_filtered:
|
||||
await handler.func_event(event_object, **kwargs_filtered)
|
||||
else:
|
||||
await handler.func_event(event_object)
|
||||
|
||||
async def process_base_filters(
|
||||
self,
|
||||
event: UpdateUnion,
|
||||
filters: List[BaseFilter]
|
||||
) -> Optional[Dict[str, Any]] | Literal[False]:
|
||||
|
||||
data = {}
|
||||
|
||||
for _filter in filters:
|
||||
result = await _filter(event)
|
||||
|
||||
if isinstance(result, dict):
|
||||
data.update(result)
|
||||
|
||||
elif not result:
|
||||
return result
|
||||
|
||||
return data
|
||||
|
||||
async def handle(self, event_object: UpdateUnion):
|
||||
|
||||
@@ -232,6 +286,17 @@ class Dispatcher:
|
||||
if not filter_attrs(event_object, *router.filters):
|
||||
continue
|
||||
|
||||
result_router_filter = await self.process_base_filters(
|
||||
event=event_object,
|
||||
filters=router.base_filters
|
||||
)
|
||||
|
||||
if isinstance(result_router_filter, dict):
|
||||
kwargs.update(result_router_filter)
|
||||
|
||||
elif not result_router_filter:
|
||||
continue
|
||||
|
||||
for handler in router.event_handlers:
|
||||
|
||||
if not handler.update_type == event_object.update_type:
|
||||
@@ -244,9 +309,21 @@ class Dispatcher:
|
||||
if handler.states:
|
||||
if current_state not in handler.states:
|
||||
continue
|
||||
|
||||
|
||||
func_args = handler.func_event.__annotations__.keys()
|
||||
|
||||
|
||||
if handler.base_filters:
|
||||
result_filter = await self.process_base_filters(
|
||||
event=event_object,
|
||||
filters=handler.base_filters
|
||||
)
|
||||
|
||||
if isinstance(result_filter, dict):
|
||||
kwargs.update(result_filter)
|
||||
|
||||
elif not result_filter:
|
||||
continue
|
||||
|
||||
if isinstance(router, Router):
|
||||
full_middlewares = self.middlewares + router.middlewares + handler.middlewares
|
||||
elif isinstance(router, Dispatcher):
|
||||
@@ -256,7 +333,7 @@ class Dispatcher:
|
||||
full_middlewares,
|
||||
functools.partial(self.call_handler, handler)
|
||||
)
|
||||
|
||||
|
||||
kwargs_filtered = {k: v for k, v in kwargs.items() if k in func_args}
|
||||
|
||||
await handler_chain(event_object, kwargs_filtered)
|
||||
|
||||
@@ -24,6 +24,7 @@ class UpdateType(str, Enum):
|
||||
DIALOG_CLEARED = 'dialog_cleared'
|
||||
DIALOG_MUTED = 'dialog_muted'
|
||||
DIALOG_UNMUTED = 'dialog_unmuted'
|
||||
DIALOG_REMOVED = 'dialog_removed'
|
||||
|
||||
# Для начинки диспатчера
|
||||
ON_STARTED = 'on_started'
|
||||
@@ -1,7 +1,12 @@
|
||||
from magic_filter import MagicFilter
|
||||
from .filter import BaseFilter
|
||||
|
||||
F = MagicFilter()
|
||||
|
||||
__all__ = [
|
||||
'BaseFilter'
|
||||
]
|
||||
|
||||
|
||||
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
|
||||
"""
|
||||
|
||||
116
maxapi/filters/command.py
Normal file
116
maxapi/filters/command.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
from ..types.updates import UpdateUnion
|
||||
from ..filters.filter import BaseFilter
|
||||
|
||||
from ..types.updates.message_created import MessageCreated
|
||||
|
||||
|
||||
class Command(BaseFilter):
|
||||
|
||||
"""
|
||||
Фильтр сообщений на соответствие команде.
|
||||
|
||||
Args:
|
||||
commands (str | list[str]): Ожидаемая команда или список команд без префикса.
|
||||
prefix (str, optional): Префикс команды (по умолчанию '/').
|
||||
check_case (bool, optional): Учитывать регистр при сравнении (по умолчанию False).
|
||||
|
||||
Attributes:
|
||||
commands (list[str]): Список команд без префикса.
|
||||
prefix (str): Префикс команды.
|
||||
check_case (bool): Флаг чувствительности к регистру.
|
||||
"""
|
||||
|
||||
def __init__(self, commands: str | List[str], prefix: str = '/', check_case: bool = False):
|
||||
|
||||
"""
|
||||
Инициализация фильтра команд.
|
||||
"""
|
||||
|
||||
if isinstance(commands, str):
|
||||
self.commands = [commands]
|
||||
else:
|
||||
self.commands = commands
|
||||
|
||||
self.prefix = prefix
|
||||
self.check_case = check_case
|
||||
|
||||
if not check_case:
|
||||
self.commands = [cmd.lower() for cmd in self.commands]
|
||||
|
||||
def parse_command(self, text: str) -> Tuple[str, List[str]]:
|
||||
|
||||
"""
|
||||
Извлекает команду из текста.
|
||||
|
||||
Args:
|
||||
text (str): Текст сообщения.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Найденная команда с префиксом, либо None.
|
||||
"""
|
||||
|
||||
args = text.split()
|
||||
first = args[0]
|
||||
|
||||
if not first.startswith(self.prefix):
|
||||
return '', []
|
||||
|
||||
return first[len(self.prefix):], args
|
||||
|
||||
async def __call__(self, event: UpdateUnion):
|
||||
|
||||
"""
|
||||
Проверяет, соответствует ли сообщение заданной(ым) команде(ам).
|
||||
|
||||
Args:
|
||||
event (MessageCreated): Событие сообщения.
|
||||
|
||||
Returns:
|
||||
bool: True, если команда совпадает, иначе False.
|
||||
"""
|
||||
|
||||
if not isinstance(event, MessageCreated):
|
||||
return False
|
||||
|
||||
text = event.message.body.text
|
||||
|
||||
if not text:
|
||||
return False
|
||||
|
||||
parsed_command, args = self.parse_command(text)
|
||||
if not parsed_command:
|
||||
return False
|
||||
|
||||
if not self.check_case:
|
||||
if parsed_command.lower() in [commands.lower() for commands in self.commands]:
|
||||
return {'args': args}
|
||||
else:
|
||||
return False
|
||||
|
||||
if parsed_command in self.commands:
|
||||
return {'args': args}
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class CommandStart(Command):
|
||||
|
||||
"""
|
||||
Фильтр для команды /start.
|
||||
|
||||
Args:
|
||||
prefix (str, optional): Префикс команды (по умолчанию '/').
|
||||
check_case (bool, optional): Учитывать регистр (по умолчанию False).
|
||||
"""
|
||||
|
||||
def __init__(self, prefix = '/', check_case = False):
|
||||
super().__init__(
|
||||
'start',
|
||||
prefix,
|
||||
check_case
|
||||
)
|
||||
|
||||
async def __call__(self, event):
|
||||
return await super().__call__(event)
|
||||
10
maxapi/filters/filter.py
Normal file
10
maxapi/filters/filter.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..types.updates import UpdateUnion
|
||||
|
||||
|
||||
class BaseFilter:
|
||||
async def __call__(self, event: UpdateUnion) -> bool | dict:
|
||||
return True
|
||||
@@ -1,11 +1,10 @@
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from magic_filter import F, MagicFilter
|
||||
from magic_filter import MagicFilter
|
||||
|
||||
from ..filters.filter import BaseFilter
|
||||
from ..filters.middleware import BaseMiddleware
|
||||
|
||||
from ..types.command import Command, CommandStart
|
||||
|
||||
from ..context.state_machine import State
|
||||
|
||||
from ..enums.update import UpdateType
|
||||
@@ -43,7 +42,8 @@ class Handler:
|
||||
|
||||
self.func_event: Callable = func_event
|
||||
self.update_type: UpdateType = update_type
|
||||
self.filters = []
|
||||
self.filters: Optional[List[MagicFilter]] = []
|
||||
self.base_filters: Optional[List[BaseFilter]] = []
|
||||
self.states: Optional[List[State]] = []
|
||||
self.middlewares: List[BaseMiddleware] = []
|
||||
|
||||
@@ -52,10 +52,10 @@ class Handler:
|
||||
self.filters.append(arg)
|
||||
elif isinstance(arg, State):
|
||||
self.states.append(arg)
|
||||
elif isinstance(arg, (Command, CommandStart)):
|
||||
self.filters.insert(0, F.message.body.text.split()[0] == arg.command)
|
||||
elif isinstance(arg, BaseMiddleware):
|
||||
self.middlewares.append(arg)
|
||||
elif isinstance(arg, BaseFilter):
|
||||
self.base_filters.append(arg)
|
||||
else:
|
||||
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
|
||||
f'регистрации функции `{func_event.__name__}`')
|
||||
49
maxapi/methods/get_message.py
Normal file
49
maxapi/methods/get_message.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, List, Optional, Union
|
||||
|
||||
from ..types.message import Message
|
||||
from ..enums.http_method import HTTPMethod
|
||||
from ..enums.api_path import ApiPath
|
||||
from ..connection.base import BaseConnection
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bot import Bot
|
||||
|
||||
|
||||
class GetMessage(BaseConnection):
|
||||
|
||||
"""
|
||||
Класс для получения сообщения.
|
||||
|
||||
Args:
|
||||
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||
message_id (str, optional): ID сообщения (mid), чтобы получить одно сообщение в чате.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'Bot',
|
||||
message_id: Optional[str] = None,
|
||||
):
|
||||
self.bot = bot
|
||||
self.message_id = message_id
|
||||
|
||||
async def fetch(self) -> Message:
|
||||
|
||||
"""
|
||||
Выполняет GET-запрос для получения сообщения.
|
||||
|
||||
Returns:
|
||||
Message: Объект с полученным сообщением.
|
||||
"""
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.MESSAGES + '/' + self.message_id,
|
||||
model=Message,
|
||||
params=self.bot.params
|
||||
)
|
||||
@@ -18,6 +18,7 @@ from ...types.updates.user_removed import UserRemoved
|
||||
from ...types.updates.dialog_cleared import DialogCleared
|
||||
from ...types.updates.dialog_muted import DialogMuted
|
||||
from ...types.updates.dialog_unmuted import DialogUnmuted
|
||||
from ...types.updates.dialog_removed import DialogRemoved
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
@@ -38,7 +39,8 @@ UPDATE_MODEL_MAPPING = {
|
||||
UpdateType.BOT_STOPPED: BotStopped,
|
||||
UpdateType.DIALOG_CLEARED: DialogCleared,
|
||||
UpdateType.DIALOG_MUTED: DialogMuted,
|
||||
UpdateType.DIALOG_UNMUTED: DialogUnmuted
|
||||
UpdateType.DIALOG_UNMUTED: DialogUnmuted,
|
||||
UpdateType.DIALOG_REMOVED: DialogRemoved
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,14 +29,16 @@ from ..types.attachments.buttons.open_app_button import OpenAppButton
|
||||
from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
|
||||
from ..types.attachments.buttons.message_button import MessageButton
|
||||
from ..types.attachments.image import PhotoAttachmentRequestPayload
|
||||
from ..types.message import Message
|
||||
from ..types.message import Message, NewMessageLink
|
||||
|
||||
from ..types.command import Command, BotCommand, CommandStart
|
||||
from ..filters.command import Command, CommandStart
|
||||
from ..types.command import BotCommand
|
||||
|
||||
from .input_media import InputMedia
|
||||
from .input_media import InputMediaBuffer
|
||||
|
||||
__all__ = [
|
||||
'NewMessageLink',
|
||||
'PhotoAttachmentRequestPayload',
|
||||
'DialogUnmuted',
|
||||
'DialogMuted',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
@@ -15,5 +15,5 @@ class Audio(Attachment):
|
||||
transcription (Optional[str]): Транскрипция аудио (если есть).
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.AUDIO
|
||||
type: Literal[AttachmentType.AUDIO]
|
||||
transcription: Optional[str] = None
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import Literal
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..attachment import ButtonsPayload
|
||||
from ....enums.attachment import AttachmentType
|
||||
|
||||
from ..attachment import Attachment
|
||||
|
||||
|
||||
class AttachmentButton(BaseModel):
|
||||
class AttachmentButton(Attachment):
|
||||
|
||||
"""
|
||||
Модель кнопки вложения для сообщения.
|
||||
@@ -14,5 +15,4 @@ class AttachmentButton(BaseModel):
|
||||
payload: Полезная нагрузка кнопки (массив рядов кнопок)
|
||||
"""
|
||||
|
||||
type: Literal['inline_keyboard'] = 'inline_keyboard'
|
||||
payload: ButtonsPayload
|
||||
type: Literal[AttachmentType.INLINE_KEYBOARD]
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import Literal
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
from .attachment import Attachment
|
||||
@@ -12,4 +13,4 @@ class Contact(Attachment):
|
||||
type (Literal['contact']): Тип вложения, всегда 'contact'.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.CONTACT
|
||||
type: Literal[AttachmentType.CONTACT]
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
@@ -16,6 +16,6 @@ class File(Attachment):
|
||||
size (Optional[int]): Размер файла в байтах.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.FILE
|
||||
type: Literal[AttachmentType.FILE]
|
||||
filename: Optional[str] = None
|
||||
size: Optional[int] = None
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -31,4 +31,4 @@ class Image(Attachment):
|
||||
type (Literal['image']): Тип вложения, всегда 'image'.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.IMAGE
|
||||
type: Literal[AttachmentType.IMAGE]
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
@@ -16,6 +16,6 @@ class Location(Attachment):
|
||||
longitude (Optional[float]): Долгота.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.LOCATION
|
||||
type: Literal[AttachmentType.LOCATION]
|
||||
latitude: Optional[float] = None
|
||||
longitude: Optional[float] = None
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
@@ -17,7 +17,7 @@ class Share(Attachment):
|
||||
image_url (Optional[str]): URL изображения для предпросмотра.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.SHARE
|
||||
type: Literal[AttachmentType.SHARE]
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
image_url: Optional[str] = None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
@@ -16,6 +16,6 @@ class Sticker(Attachment):
|
||||
height (Optional[int]): Высота стикера в пикселях.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.STICKER
|
||||
type: Literal[AttachmentType.STICKER]
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
@@ -61,7 +61,7 @@ class Video(Attachment):
|
||||
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
type: AttachmentType = AttachmentType.VIDEO
|
||||
type: Literal[AttachmentType.VIDEO]
|
||||
token: Optional[str] = None
|
||||
urls: Optional[VideoUrl] = None
|
||||
thumbnail: VideoThumbnail
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Command:
|
||||
|
||||
"""
|
||||
Класс для представления команды бота.
|
||||
|
||||
Attributes:
|
||||
text (str): Текст команды без префикса.
|
||||
prefix (str): Префикс команды. По умолчанию '/'.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str, prefix: str = '/'):
|
||||
self.text = text
|
||||
self.prefix = prefix
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
|
||||
"""
|
||||
Возвращает полную команду с префиксом.
|
||||
|
||||
Returns:
|
||||
str: Команда, состоящая из префикса и текста.
|
||||
"""
|
||||
|
||||
return self.prefix + self.text
|
||||
|
||||
|
||||
class BotCommand(BaseModel):
|
||||
@@ -40,19 +13,4 @@ class BotCommand(BaseModel):
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class CommandStart(Command):
|
||||
|
||||
"""
|
||||
Класс для представления команды /start бота.
|
||||
|
||||
Attributes:
|
||||
prefix (str): Префикс команды. По умолчанию '/'.
|
||||
"""
|
||||
|
||||
text = 'start'
|
||||
|
||||
def __init__(self, prefix: str = '/'):
|
||||
self.prefix = prefix
|
||||
description: Optional[str] = None
|
||||
@@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Any, Optional, List, Union, TYPE_CHECKING
|
||||
from typing import Annotated, Any, Optional, List, Union, TYPE_CHECKING
|
||||
|
||||
from ..types.attachments.contact import Contact
|
||||
|
||||
from ..enums.text_style import TextStyle
|
||||
from ..enums.parse_mode import ParseMode
|
||||
@@ -24,6 +26,19 @@ from .users import User
|
||||
if TYPE_CHECKING:
|
||||
from ..bot import Bot
|
||||
from ..types.input_media import InputMedia, InputMediaBuffer
|
||||
|
||||
|
||||
Attachments = Annotated[Union[
|
||||
Audio,
|
||||
Video,
|
||||
File,
|
||||
Image,
|
||||
Sticker,
|
||||
Share,
|
||||
Location,
|
||||
AttachmentButton,
|
||||
Contact
|
||||
], Field(discriminator='type')]
|
||||
|
||||
|
||||
class MarkupElement(BaseModel):
|
||||
@@ -91,18 +106,7 @@ class MessageBody(BaseModel):
|
||||
seq: int
|
||||
text: Optional[str] = None
|
||||
attachments: Optional[
|
||||
List[
|
||||
Union[
|
||||
AttachmentButton,
|
||||
Audio,
|
||||
Video,
|
||||
File,
|
||||
Image,
|
||||
Sticker,
|
||||
Share,
|
||||
Location
|
||||
]
|
||||
]
|
||||
List[Attachments]
|
||||
] = Field(default_factory=list) # type: ignore
|
||||
|
||||
markup: Optional[
|
||||
|
||||
30
maxapi/types/updates/dialog_removed.py
Normal file
30
maxapi/types/updates/dialog_removed.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class DialogRemoved(Update):
|
||||
|
||||
"""
|
||||
Обновление, сигнализирующее об удалении диалога с ботом.
|
||||
|
||||
Attributes:
|
||||
chat_id (int): Идентификатор чата.
|
||||
user (User): Пользователь (бот).
|
||||
user_locale (Optional[str]): Локаль пользователя.
|
||||
"""
|
||||
|
||||
chat_id: int
|
||||
user: User
|
||||
user_locale: Optional[str] = None
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
def get_ids(self):
|
||||
return (self.chat_id, self.user.user_id)
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "maxapi"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
description = "Библиотека для разработки чат-ботов с помощью API мессенджера MAX"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
@@ -13,7 +13,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
dependencies = [
|
||||
"aiohttp>=3.8.0",
|
||||
"aiohttp>=3.12.14",
|
||||
"magic_filter>=1.0.0",
|
||||
"pydantic>=1.8.0",
|
||||
"aiofiles==24.1.0",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
| `dialog_cleared` | Пользователь очистил историю диалога с ботом |
|
||||
| `dialog_muted` | Пользователь отключил оповещения от чата бота |
|
||||
| `dialog_unmuted` | Пользователь включил оповещения от чата бота |
|
||||
| `dialog_removed` | Пользователь удалил диалог с ботом |
|
||||
| `chat_title_changed` | Изменено название чата |
|
||||
| `message_callback` | Пользователь нажал на callback-кнопку (inline button) |
|
||||
| `message_chat_created`| Срабатывает когда пользователь нажал на кнопку с действием "Создать чат" (работает некорректно со стороны API MAX, ждем исправлений) |
|
||||
|
||||
Reference in New Issue
Block a user