diff --git a/README.md b/README.md
index b82af95..fffc3c2 100644
--- a/README.md
+++ b/README.md
@@ -66,12 +66,16 @@ if __name__ == '__main__':
- [Обработчик доступных событий](https://github.com/love-apples/maxapi/blob/main/examples/events/main.py)
- [Обработчики с MagicFilter](https://github.com/love-apples/maxapi/blob/main/examples/magic_filters/main.py)
- [Демонстрация роутинга, InputMedia и механика контекста](https://github.com/love-apples/maxapi/tree/main/examples/router_with_input_media) (audio.mp3 для команды /media)
+ - [Получение ID](https://github.com/love-apples/maxapi/tree/main/examples/get_ids/main.py)
+ - [Миддлварь в хендлерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_in_handlers/main.py)
+ - [Миддлварь в роутерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_for_router/main.py)
---
## 🧩 Возможности
+- ✅ Middleware
- ✅ Роутеры
- ✅ Билдер инлайн клавиатур
- ✅ Простая загрузка медиафайлов
diff --git a/examples/get_ids/main.py b/examples/get_ids/main.py
new file mode 100644
index 0000000..df26e8a
--- /dev/null
+++ b/examples/get_ids/main.py
@@ -0,0 +1,39 @@
+import asyncio
+import logging
+
+from maxapi import Bot, Dispatcher, F
+from maxapi.enums.parse_mode import ParseMode
+from maxapi.types import MessageCreated
+
+logging.basicConfig(level=logging.INFO)
+
+bot = Bot('тут_ваш_токен')
+dp = Dispatcher()
+
+
+@dp.message_created(F.message.link.type == 'forward')
+async def get_ids_from_forward(event: MessageCreated):
+ text = (
+ 'Информация о пересланном сообщении:\n\n'
+
+ f'Из чата: {event.message.link.chat_id}\n'
+ f'От пользователя: {event.message.link.sender.user_id}'
+ )
+ await event.message.reply(text)
+
+
+@dp.message_created()
+async def get_ids(event: MessageCreated):
+ text = (
+ f'Ваш ID: {event.from_user.user_id}\n'
+ f'ID этого чата: {event.chat.chat_id}'
+ )
+ await event.message.answer(text, parse_mode=ParseMode.HTML)
+
+
+async def main():
+ await dp.start_polling(bot)
+
+
+if __name__ == '__main__':
+ asyncio.run(main())
\ No newline at end of file
diff --git a/examples/middleware_for_router/main.py b/examples/middleware_for_router/main.py
new file mode 100644
index 0000000..730d18c
--- /dev/null
+++ b/examples/middleware_for_router/main.py
@@ -0,0 +1,41 @@
+import asyncio
+import logging
+
+from typing import Any, Dict
+
+from maxapi import Bot, Dispatcher
+from maxapi.types import MessageCreated, Command, UpdateUnion
+from maxapi.filters.middleware import BaseMiddleware
+
+logging.basicConfig(level=logging.INFO)
+
+bot = Bot(token='тут_ваш_токен')
+dp = Dispatcher()
+
+
+class CustomDataForRouterMiddleware(BaseMiddleware):
+ async def __call__(
+ self,
+ event: UpdateUnion,
+ data: Dict[str, Any]
+ ):
+
+ data['custom_data'] = f'Это ID того кто вызвал команду: {event.from_user.user_id}'
+
+ return data
+
+
+@dp.message_created(Command('custom_data'))
+async def custom_data(event: MessageCreated, custom_data: str):
+ await event.message.answer(custom_data)
+
+
+async def main():
+ dp.middlewares = [
+ CustomDataForRouterMiddleware()
+ ]
+ await dp.start_polling(bot)
+
+
+if __name__ == '__main__':
+ asyncio.run(main())
\ No newline at end of file
diff --git a/examples/middleware_in_handlers/main.py b/examples/middleware_in_handlers/main.py
new file mode 100644
index 0000000..3a3c3e2
--- /dev/null
+++ b/examples/middleware_in_handlers/main.py
@@ -0,0 +1,59 @@
+import asyncio
+import logging
+
+from typing import Any, Dict
+
+from maxapi import Bot, Dispatcher
+from maxapi.filters.middleware import BaseMiddleware
+from maxapi.types import MessageCreated, Command, UpdateUnion
+from maxapi.types.command import Command
+
+logging.basicConfig(level=logging.INFO)
+
+bot = Bot(token='тут_ваш_токен')
+dp = Dispatcher()
+
+
+class CheckChatTitleMiddleware(BaseMiddleware):
+ async def __call__(
+ self,
+ event: UpdateUnion,
+ ):
+
+ return event.chat.title == 'MAXApi'
+
+
+@dp.message_created(Command('start'), CheckChatTitleMiddleware())
+async def start(event: MessageCreated):
+ await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')
+
+
+class CustomDataMiddleware(BaseMiddleware):
+ async def __call__(
+ self,
+ event: UpdateUnion,
+ data: Dict[str, Any]
+ ):
+
+ data['custom_data'] = f'Это ID того кто вызвал команду: {event.from_user.user_id}'
+
+ return data
+
+
+@dp.message_created(Command('custom_data'), CustomDataMiddleware())
+async def custom_data(event: MessageCreated, custom_data: str):
+ await event.message.answer(custom_data)
+
+
+@dp.message_created(Command('many_middlewares'), CheckChatTitleMiddleware(), CustomDataMiddleware())
+async def many_middlewares(event: MessageCreated, custom_data: str):
+ await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')
+ await event.message.answer(custom_data)
+
+
+async def main():
+ await dp.start_polling(bot)
+
+
+if __name__ == '__main__':
+ asyncio.run(main())
\ No newline at end of file
diff --git a/maxapi/dispatcher.py b/maxapi/dispatcher.py
index b1ab861..4b5d83a 100644
--- a/maxapi/dispatcher.py
+++ b/maxapi/dispatcher.py
@@ -1,4 +1,4 @@
-from typing import Callable, List
+from typing import Any, Callable, Dict, List
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
@@ -6,6 +6,8 @@ from magic_filter import MagicFilter
from uvicorn import Config, Server
from aiohttp import ClientConnectorError
+from maxapi.filters.middleware import BaseMiddleware
+
from .filters.handler import Handler
from .context import MemoryContext
@@ -36,6 +38,8 @@ class Dispatcher:
self.contexts: List[MemoryContext] = []
self.routers: List[Router] = []
self.filters: List[MagicFilter] = []
+ self.middlewares: List[BaseMiddleware] = []
+
self.bot = None
self.on_started_func = None
@@ -78,7 +82,7 @@ class Dispatcher:
handlers_count = 0
for router in self.routers:
- for handler in router.event_handlers:
+ for _ in router.event_handlers:
handlers_count += 1
logger_dp.info(f'{handlers_count} событий на обработку')
@@ -105,6 +109,30 @@ class Dispatcher:
new_ctx = MemoryContext(chat_id, user_id)
self.contexts.append(new_ctx)
return new_ctx
+
+ async def process_middlewares(
+ self,
+ middlewares: List[BaseMiddleware],
+ event_object: UpdateUnion,
+ result_data_kwargs: Dict[str, Any]
+ ):
+
+ for middleware in middlewares:
+ result = await middleware.process_middleware(
+ event_object=event_object,
+ result_data_kwargs=result_data_kwargs
+ )
+
+ if result == None or result == False:
+ return
+
+ elif result == True:
+ result = {}
+
+ for key, value in result.items():
+ result_data_kwargs[key] = value
+
+ return result_data_kwargs
async def handle(self, event_object: UpdateUnion):
@@ -113,54 +141,68 @@ class Dispatcher:
Args:
event_object: Объект события для обработки
"""
- ids = event_object.get_ids()
-
- is_handled = False
-
- for router in self.routers:
+ try:
+ ids = event_object.get_ids()
+ memory_context = self.__get_memory_context(*ids)
+ kwargs = {'context': memory_context}
- if is_handled:
- break
+ is_handled = False
- if router.filters:
- if not filter_attrs(event_object, *router.filters):
- continue
-
- for handler in router.event_handlers:
+ for router in self.routers:
+
+ if is_handled:
+ break
+
+ if router.filters:
+ if not filter_attrs(event_object, *router.filters):
+ continue
+
+ kwargs = await self.process_middlewares(
+ middlewares=router.middlewares,
+ event_object=event_object,
+ result_data_kwargs=kwargs
+ )
+
+ for handler in router.event_handlers:
- if not handler.update_type == event_object.update_type:
- continue
-
- if handler.filters:
- if not filter_attrs(event_object, *handler.filters):
+ if not handler.update_type == event_object.update_type:
continue
- memory_context = self.__get_memory_context(*ids)
-
- if not handler.state == await memory_context.get_state() \
- and handler.state:
- continue
-
- func_args = handler.func_event.__annotations__.keys()
+ if handler.filters:
+ if not filter_attrs(event_object, *handler.filters):
+ continue
- kwargs = {'context': memory_context}
-
- for key in kwargs.copy().keys():
- if not key in func_args:
- del kwargs[key]
+ if not handler.state == await memory_context.get_state() \
+ and handler.state:
+ continue
+
+ func_args = handler.func_event.__annotations__.keys()
+
+ kwargs = await self.process_middlewares(
+ middlewares=handler.middlewares,
+ event_object=event_object,
+ result_data_kwargs=kwargs
+ )
+
+ if not kwargs:
+ continue
- if handler.middleware:
- await handler.middleware()
+ for key in kwargs.copy().keys():
+ if not key in func_args:
+ del kwargs[key]
+
+ await handler.func_event(event_object, **kwargs)
- await handler.func_event(event_object, **kwargs)
+ logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
- logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
+ is_handled = True
+ break
- is_handled = True
- break
-
- if not is_handled:
- logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
+ if not is_handled:
+ logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
+
+ except Exception as e:
+ logger_dp.error(f"Ошибка при обработке события: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]} | {e} ")
async def start_polling(self, bot: Bot):
@@ -187,10 +229,7 @@ class Dispatcher:
)
for event in processed_events:
- try:
- await self.handle(event)
- except Exception as e:
- logger_dp.error(f"Ошибка при обработке события: {event.update_type}: {e}")
+ await self.handle(event)
except ClientConnectorError:
logger_dp.error(f'Ошибка подключения: {e}')
except Exception as e:
diff --git a/maxapi/filters/handler.py b/maxapi/filters/handler.py
index 16f246d..72bdfd4 100644
--- a/maxapi/filters/handler.py
+++ b/maxapi/filters/handler.py
@@ -1,4 +1,4 @@
-from typing import Callable
+from typing import Callable, List
from magic_filter import F, MagicFilter
@@ -45,7 +45,7 @@ class Handler:
self.update_type: UpdateType = update_type
self.filters = []
self.state: State = None
- self.middleware: BaseMiddleware = None
+ self.middlewares: List[BaseMiddleware] = []
for arg in args:
if isinstance(arg, MagicFilter):
@@ -55,7 +55,7 @@ class Handler:
elif isinstance(arg, Command):
self.filters.insert(0, F.message.body.text.startswith(arg.command))
elif isinstance(arg, BaseMiddleware):
- self.middleware = arg
+ self.middlewares.append(arg)
else:
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
f'регистрации функции `{func_event.__name__}`')
\ No newline at end of file
diff --git a/maxapi/filters/middleware.py b/maxapi/filters/middleware.py
index a50196f..2da7d6a 100644
--- a/maxapi/filters/middleware.py
+++ b/maxapi/filters/middleware.py
@@ -1,6 +1,23 @@
+from typing import Any, Dict
from ..types.updates import UpdateUnion
class BaseMiddleware:
def __init__(self):
- ...
\ No newline at end of file
+ ...
+
+ async def process_middleware(
+ self,
+ result_data_kwargs: Dict[str, Any],
+ event_object: UpdateUnion
+ ):
+
+ kwargs_temp = {'data': result_data_kwargs.copy()}
+
+ for key in kwargs_temp.copy().keys():
+ if not key in self.__call__.__annotations__.keys():
+ del kwargs_temp[key]
+
+ result: Dict[str, Any] = await self(event_object, **kwargs_temp)
+
+ return result
\ No newline at end of file
diff --git a/maxapi/types/__init__.py b/maxapi/types/__init__.py
index 8feacfe..b6e4de7 100644
--- a/maxapi/types/__init__.py
+++ b/maxapi/types/__init__.py
@@ -9,6 +9,7 @@ from ..types.updates.message_edited import MessageEdited
from ..types.updates.message_removed import MessageRemoved
from ..types.updates.user_added import UserAdded
from ..types.updates.user_removed import UserRemoved
+from ..types.updates import UpdateUnion
from ..types.attachments.attachment import PhotoAttachmentPayload
from ..types.attachments.attachment import OtherAttachmentPayload
@@ -20,12 +21,14 @@ from ..types.attachments.buttons.chat_button import ChatButton
from ..types.attachments.buttons.link_button import LinkButton
from ..types.attachments.buttons.request_contact import RequestContact
from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
+from ..types.message import Message
from ..types.command import Command, BotCommand
from .input_media import InputMedia
__all__ = [
+ UpdateUnion,
InputMedia,
BotCommand,
CallbackButton,
diff --git a/maxapi/types/attachments/buttons/chat_button.py b/maxapi/types/attachments/buttons/chat_button.py
index 4f40610..b012515 100644
--- a/maxapi/types/attachments/buttons/chat_button.py
+++ b/maxapi/types/attachments/buttons/chat_button.py
@@ -1,5 +1,7 @@
from typing import Optional
+from maxapi.enums.button_type import ButtonType
+
from .button import Button
@@ -7,7 +9,6 @@ class ChatButton(Button):
"""
Attributes:
- type: Тип кнопки (наследуется от Button)
text: Текст кнопки (наследуется от Button)
chat_title: Название чата (до 128 символов)
chat_description: Описание чата (до 256 символов)
@@ -15,6 +16,7 @@ class ChatButton(Button):
uuid: Уникальный идентификатор чата
"""
+ type: ButtonType = ButtonType.CHAT
chat_title: Optional[str] = None
chat_description: Optional[str] = None
start_payload: Optional[str] = None