Source code for telegram_bot_discussion.handlers.buttons_handler.buttons_handler
from datetime import datetime, UTC
from typing import Dict, Tuple, Type, Union
from telegram import CallbackQuery, Update, User
from telegram.ext import CallbackContext
from ...bot.contextual import Contextual
from ...button.button import Button
from ...button.buttons_map import ButtonsMap
from ...button.coder_interface import CoderInterface
from ...button.params import Params
from ...dialogue.dialogue import Polemic
from ...functions import (
get_chat_id,
get_from_id,
)
from ...logger.logger_interface import LoggerInterface
from .exceptions.access_deny import (
ButtonClassAccessDeny as ButtonClassAccessDeny,
)
from .exceptions.life_time_overflow import (
ButtonClassLifeTimeOverflow as ButtonClassLifeTimeOverflow,
)
from .exceptions.not_registered import (
ButtonClassIsNotRegistered as ButtonClassIsNotRegistered,
)
from .exceptions.signification_error import (
ButtonDataSignificationError as ButtonDataSignificationError,
)
from .exceptions.buttons_map_is_empty import (
ButtonsMapIsEmpty as ButtonsMapIsEmpty,
)
class _DiscussionInterface:
def get_dialogues(self) -> Dict[int, Polemic]: ...
def get_logger(self) -> LoggerInterface: ...
def get_buttons_handler(self) -> "ButtonsHandler": ...
[docs]
class ButtonsHandler:
buttons_map: ButtonsMap
""" ButtonsMap: accordance of action and `Button` class."""
coder: CoderInterface
""" CoderInterface: how to serialize and deserialize stored in button data."""
def __init__(
self,
coder: CoderInterface,
buttons_map: ButtonsMap,
):
self.coder = coder
self.buttons_map = buttons_map
[docs]
def set_buttons_map(self, buttons_map: ButtonsMap):
self.buttons_map = buttons_map
return self
[docs]
async def access_control_list(
self,
context: CallbackContext,
user_id: int,
button_class: Type[Button],
) -> bool:
"""Check user access to `Button`-class within `handle()`."""
_, _, _ = context, user_id, button_class
return True
[docs]
async def middleware(
self,
update: Update,
context: CallbackContext,
) -> Union[Tuple[Update, CallbackContext], None]:
"""Middleware work within `handle()`. If it returns None, `handle()` will not work."""
return update, context
[docs]
async def handle(
self,
update: Update,
context: CallbackContext,
):
"""Catch all buttons clicks."""
if (
await self.middleware(
update,
context,
)
is None
):
return
callback_query: Union[CallbackQuery, None] = update.callback_query
if not callback_query:
return
if not callback_query.data:
return
from_user: Union[User, None] = callback_query.from_user
if not from_user:
return
if not self.buttons_map:
raise ButtonsMapIsEmpty()
discussion: _DiscussionInterface = Contextual[_DiscussionInterface](context)()
if not discussion.get_buttons_handler().coder.check_signature(
get_chat_id(update),
callback_query.data,
get_from_id(update),
):
raise ButtonDataSignificationError(from_user.id, callback_query.data)
params = Params.fetch(
callback_query=callback_query,
coder=self.coder,
)
discussion.get_logger().debug(f"Params {params}")
button_action = params.action
button_class = self.buttons_map.search(button_action)
if button_class is None:
raise ButtonClassIsNotRegistered(button_action)
if not await self.access_control_list(
context,
from_user.id,
button_class,
):
raise ButtonClassAccessDeny(from_user.id, button_action)
life_time = button_class.life_time()
if (
callback_query.message
and life_time
and datetime.now(tz=UTC) > callback_query.message.date + life_time
):
raise ButtonClassLifeTimeOverflow(button_action)
await button_class.on_click(update, context)