Source code for quackamollie.core.bot.decorator.permissions

# -*- coding: utf-8 -*-
__all__ = ["permission_authorized", "permission_moderator", "permission_admin"]
__author__ = "QuacktorAI"
__copyright__ = "Copyright 2024, Forge of Absurd Ducks"
__credits__ = ["QuacktorAI"]

import logging

from aiogram.types import Message, CallbackQuery
from functools import wraps
from typing import Callable, Optional, Union

from quackamollie.core.cli.settings import get_settings_from_context, QuackamollieSettings
from quackamollie.core.enum.chat_type import ChatType

log = logging.getLogger(__name__)


[docs] def permission_authorized(func: Callable) -> Callable: """ Decorator to encapsulate aiogram message or query handlers in order to ensure only requests from authorized users are handled :param func: The function to encapsulate :type func: Callable :return: The encapsulated function :rtype: Callable """ @wraps(func) async def authorized_wrapper(arg: Union[Message, CallbackQuery], *args, **kwargs): """ Encapsulate aiogram message or query handlers to ensure only requests from authorized users are handled :param arg: A Message or CallbackQuery given by aiogram :type arg: Union[Message, CallbackQuery] """ # Get settings to check IDs quackamollie_settings: QuackamollieSettings = get_settings_from_context() # If the user is authorized, the request is forwarded to the encapsulated function if arg.from_user.id in quackamollie_settings.authorized_ids: return await func(arg, *args, **kwargs) else: # Log an error and return a message stating access denied # Differentiate between queries and messages in order to improve resulting error if isinstance(arg, CallbackQuery): query: Optional[CallbackQuery] = arg message: Message = arg.message else: query: Optional[CallbackQuery] = None message: Message = arg # Log error group or supergroup chats if message.chat.type == ChatType.supergroup.value or message.chat.type == ChatType.group.value: log.error(f"Unauthorized user '{arg.from_user.full_name}' with ID '{arg.from_user.id}' tried to" f" reference me in the {message.chat.type} chat '{message.chat.title}' and was rejected.") else: # Log error for private chats log.error(f"Unauthorized user '{arg.from_user.full_name}' with ID '{arg.from_user.id}' tried to" f" chat with me in its private chat and was rejected.") # Keeping the answer short for queries because they are shown as popups if query is not None: return await query.answer("Access Denied") else: return await message.answer("⚠ Access Denied\nYou're not authorized to interact with me.") return authorized_wrapper
[docs] def permission_moderator(func: Callable) -> Callable: """ Decorator to encapsulate aiogram message or query handlers in order to ensure only requests from moderator users are handled :param func: The function to encapsulate :type func: Callable :return: The encapsulated function :rtype: Callable """ @wraps(func) async def moderator_wrapper(arg: Union[Message, CallbackQuery], *args, **kwargs): """ Encapsulate aiogram message or query handlers to ensure only requests from moderator users are handled :param arg: A Message or CallbackQuery given by aiogram :type arg: Union[Message, CallbackQuery] """ # Get settings to check IDs quackamollie_settings: QuackamollieSettings = get_settings_from_context() user_id: int = arg.from_user.id # If the user is a moderator, the request is forwarded to the encapsulated function if user_id in quackamollie_settings.moderator_ids or user_id in quackamollie_settings.admin_ids: return await func(arg, *args, **kwargs) else: # Log an error and return a message stating access denied # Differentiate between queries and messages in order to improve resulting error if isinstance(arg, CallbackQuery): query: Optional[CallbackQuery] = arg message: Message = arg.message else: query: Optional[CallbackQuery] = None message: Message = arg # Log error group or supergroup chats if message.chat.type == ChatType.supergroup.value or message.chat.type == ChatType.group.value: log.error(f"Unauthorized user '{arg.from_user.full_name}' with ID '{user_id}' tried to" f" perform a moderator action in the {message.chat.type} chat '{message.chat.title}' and" f" was rejected.") else: # Log error for private chats log.error(f"Unauthorized user '{arg.from_user.full_name}' with ID '{user_id}' tried to" f" perform a moderator action in private chat and was rejected.") # Keeping the answer short for queries because they are shown as popups if query is not None: return await query.answer("Moderator Access Denied") else: return await message.answer("⚠ Moderator Access Denied\n" "You're not authorized to perform moderator actions.") return moderator_wrapper
[docs] def permission_admin(func: Callable) -> Callable: """ Decorator to encapsulate aiogram message or query handlers in order to ensure only requests from admin users are handled :param func: The function to encapsulate :type func: Callable :return: The encapsulated function :rtype: Callable """ @wraps(func) async def admin_wrapper(arg: Union[Message, CallbackQuery], *args, **kwargs): """ Encapsulate aiogram message or query handlers to ensure only requests from admin users are handled :param arg: A Message or CallbackQuery given by aiogram :type arg: Union[Message, CallbackQuery] """ # Get settings to check IDs quackamollie_settings: QuackamollieSettings = get_settings_from_context() # If the user is an admin, the request is forwarded to the encapsulated function if arg.from_user.id in quackamollie_settings.admin_ids: return await func(arg, *args, **kwargs) else: # Log an error and return a message stating access denied # Differentiate between queries and messages in order to improve resulting error if isinstance(arg, CallbackQuery): query: Optional[CallbackQuery] = arg message: Message = arg.message else: query: Optional[CallbackQuery] = None message: Message = arg # Log error group or supergroup chats if message.chat.type == ChatType.supergroup.value or message.chat.type == ChatType.group.value: log.error(f"Unauthorized user '{arg.from_user.full_name}' with ID '{arg.from_user.id}' tried to" f" perform an admin action in the {message.chat.type} chat '{message.chat.title}' and" f" was rejected.") else: # Log error for private chats log.error(f"Unauthorized user '{arg.from_user.full_name}' with ID '{arg.from_user.id}' tried to" f" perform an admin action in private chat and was rejected.") # Keeping the answer short for queries because they are shown as popups if query is not None: return await query.answer("Admin Access Denied") else: return await message.answer("⛔ Admin Access Denied\n" "You're not authorized to perform admin actions.") return admin_wrapper