Добавлен фильтр-обработка CallbackPayload
This commit is contained in:
parent
e922132319
commit
ff4575fe84
175
maxapi/filters/callback_payload.py
Normal file
175
maxapi/filters/callback_payload.py
Normal file
@ -0,0 +1,175 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, ClassVar, List, Optional, Type, TYPE_CHECKING
|
||||
from magic_filter import MagicFilter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..types.updates.message_callback import MessageCallback
|
||||
from ..types.updates import UpdateUnion
|
||||
from .filter import BaseFilter
|
||||
|
||||
PAYLOAD_MAX = 1024
|
||||
|
||||
|
||||
class CallbackPayload(BaseModel):
|
||||
|
||||
"""
|
||||
Базовый класс для сериализации/десериализации callback payload.
|
||||
|
||||
Атрибуты:
|
||||
prefix (str): Префикс для payload (используется при pack/unpack) (по умолчанию название класса).
|
||||
separator (str): Разделитель между значениями (по умолчанию '|').
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
prefix: ClassVar[str]
|
||||
separator: ClassVar[str]
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
|
||||
"""
|
||||
Автоматически проставляет prefix и separator при наследовании.
|
||||
"""
|
||||
|
||||
cls.prefix = kwargs.get('prefix', str(cls.__name__))
|
||||
cls.separator = kwargs.get('separator', '|')
|
||||
|
||||
def pack(self) -> str:
|
||||
|
||||
"""
|
||||
Собирает данные payload в строку для передачи в callback payload.
|
||||
|
||||
Raises:
|
||||
ValueError: Если в значении встречается разделитель или payload слишком длинный.
|
||||
|
||||
Returns:
|
||||
str: Сериализованный payload.
|
||||
"""
|
||||
|
||||
values = [self.prefix]
|
||||
|
||||
for name in self.attrs():
|
||||
value = getattr(self, name)
|
||||
str_value = '' if value is None else str(value)
|
||||
if self.separator in str_value:
|
||||
raise ValueError(
|
||||
f'Символ разделителя "{self.separator}" не должен встречаться в значении поля {name}'
|
||||
)
|
||||
|
||||
values.append(str_value)
|
||||
|
||||
data = self.separator.join(values)
|
||||
|
||||
if len(data.encode()) > PAYLOAD_MAX:
|
||||
raise ValueError(
|
||||
f'Payload слишком длинный! Максимум: {PAYLOAD_MAX} байт'
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, data: str):
|
||||
|
||||
"""
|
||||
Десериализует payload из строки.
|
||||
|
||||
Args:
|
||||
data (str): Строка payload (из callback payload).
|
||||
|
||||
Raises:
|
||||
ValueError: Некорректный prefix или количество аргументов.
|
||||
|
||||
Returns:
|
||||
CallbackPayload: Экземпляр payload с заполненными полями.
|
||||
"""
|
||||
|
||||
parts = data.split(cls.separator)
|
||||
|
||||
if not parts[0] == cls.prefix:
|
||||
raise ValueError('Некорректный prefix')
|
||||
|
||||
field_names = cls.attrs()
|
||||
|
||||
if not len(parts) - 1 == len(field_names):
|
||||
raise ValueError(
|
||||
f'Ожидалось {len(field_names)} аргументов, получено {len(parts) - 1}'
|
||||
)
|
||||
|
||||
kwargs = dict(zip(field_names, parts[1:]))
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def attrs(cls) -> List[str]:
|
||||
|
||||
"""
|
||||
Возвращает список полей для сериализации/десериализации (исключая prefix и separator).
|
||||
|
||||
Returns:
|
||||
List[str]: Имена полей модели.
|
||||
"""
|
||||
|
||||
return [
|
||||
k for k in cls.model_fields.keys()
|
||||
if k not in ('prefix', 'separator')
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def filter(cls, rule: Optional[MagicFilter] = None) -> PayloadFilter:
|
||||
|
||||
"""
|
||||
Создаёт PayloadFilter для фильтрации callback-ивентов по payload.
|
||||
|
||||
Args:
|
||||
rule (Optional[MagicFilter]): Фильтр на payload.
|
||||
|
||||
Returns:
|
||||
PayloadFilter: Экземпляр фильтра для хэндлера.
|
||||
"""
|
||||
|
||||
return PayloadFilter(model=cls, rule=rule)
|
||||
|
||||
|
||||
class PayloadFilter(BaseFilter):
|
||||
|
||||
"""
|
||||
Фильтр для MessageCallback по payload.
|
||||
"""
|
||||
|
||||
def __init__(self, model: Type[CallbackPayload], rule: Optional[MagicFilter]):
|
||||
|
||||
"""
|
||||
Args:
|
||||
model (Type[CallbackPayload]): Класс payload для распаковки.
|
||||
rule (Optional[MagicFilter]): Фильтр (условие) для payload.
|
||||
"""
|
||||
|
||||
self.model = model
|
||||
self.rule = rule
|
||||
|
||||
async def __call__(self, event: UpdateUnion):
|
||||
|
||||
"""
|
||||
Проверяет event на MessageCallback и применяет фильтр к payload.
|
||||
|
||||
Args:
|
||||
event (UpdateUnion): Обновление/событие.
|
||||
|
||||
Returns:
|
||||
dict | bool: dict с payload при совпадении, иначе False.
|
||||
"""
|
||||
|
||||
if not isinstance(event, MessageCallback):
|
||||
return False
|
||||
|
||||
if not event.callback.payload:
|
||||
return False
|
||||
|
||||
try:
|
||||
payload = self.model.unpack(event.callback.payload)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
if not self.rule or self.rule.resolve(payload):
|
||||
return {'payload': payload}
|
||||
|
||||
return False
|
Loading…
x
Reference in New Issue
Block a user