Source code for quackamollie.core.bot.handler.messages
# -*- coding: utf-8 -*-
""" Module to define commands, handle additional configuration and start the bot """
__all__ = ["message_router", "handle_message"]
__author__ = "QuacktorAI"
__copyright__ = "Copyright 2024, Forge of Absurd Ducks"
__credits__ = ["QuacktorAI"]
import logging
from aiogram import F, Router
from aiogram.types import Message
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from typing import Optional
from quackamollie.core.bot.bot_info import QuackamollieBotData
from quackamollie.core.bot.decorator.acknowledge_with_reactions import acknowledge_with_reactions
from quackamollie.core.bot.decorator.permissions import permission_authorized
from quackamollie.core.bot.decorator.user_chat_registered import ensure_user_chat_registered
from quackamollie.core.cli.settings import pass_quackamollie_settings, QuackamollieSettings
from quackamollie.core.database.model import ChatSetting
from quackamollie.core.enum.chat_type import ChatType
from quackamollie.core.model_manager_registry.model_manager_registry import QuackamollieModelManagerRegistry
log = logging.getLogger(__name__)
message_router = Router()
def is_mentioned_in_group_or_supergroup(message: Message, mention: str, chat_type: ChatType):
return (chat_type in [ChatType.group, ChatType.supergroup]
and message.text and message.text.startswith(mention))
# TODO: Improving settings will lead to using finite state machines, ensure no state is defined when calling this
# It should look like: @message_router.message(F.text, F.state == None)
# @message_router.message(F.caption, F.state == None)
[docs]
@message_router.message(F.text)
@message_router.message(F.caption)
@permission_authorized
@ensure_user_chat_registered
@acknowledge_with_reactions
@pass_quackamollie_settings
async def handle_message(quackamollie_settings: QuackamollieSettings, message: Message):
""" React on message with emojis. Answer user's message by using the model manager and model manager defined for
the current chat of the defaults from settings. Handle only messages with text or captions and no finite state
defined. Answer all messages in private chats (including chat types overridden from `/settings`). However,
it only answers messages where the bot is explicitly mentioned at the beginning of the message
in groups or supergroups.
:param quackamollie_settings: The application settings initialized from click context
:type quackamollie_settings: QuackamollieSettings
:param message: The message as given by aiogram router
:type message: Message
"""
# Get mention and bot ID from pre-initialized bot data
mention = QuackamollieBotData().bot_mention
# Get model managers
model_managers_by_entrypoint_name = QuackamollieModelManagerRegistry().model_managers
model_managers_by_class_name = QuackamollieModelManagerRegistry().model_managers_by_class_name
class_name_to_entrypoint_name = QuackamollieModelManagerRegistry().class_name_to_entrypoint_name
# Get database session from settings
async_session = quackamollie_settings.session
# Get chat info from message
chat_id: int = message.chat.id
chat_type: ChatType = ChatType[message.chat.type]
# Get the current configuration for the chat from the database or from defaults
async with async_session() as session:
# Get chat settings for the current chat
chat_setting_result = await session.execute(select(ChatSetting).where(
ChatSetting.chat_id == chat_id
).limit(1).options(selectinload(ChatSetting.model_config)))
chat_setting: Optional[ChatSetting] = chat_setting_result.scalars().first()
# Get model settings for the current chat
if chat_setting.model_config is not None:
model_name = chat_setting.model_config.model_name
model_config = chat_setting.model_config.config
model_manager = await chat_setting.model_config.awaitable_attrs.model_manager_type
if model_manager is not None:
model_manager_class = model_managers_by_class_name.get(model_manager.model_manager_class, None)
else:
model_manager_class = None
if model_manager_class is not None:
model_manager_name = class_name_to_entrypoint_name.get(model_manager_class.__name__, None)
else:
model_manager_name = None
else:
model_name = quackamollie_settings.default_model
model_config = quackamollie_settings.default_model_config
model_manager = quackamollie_settings.default_model_manager
model_manager_name = model_manager
if model_manager:
model_manager_class = model_managers_by_entrypoint_name.get(model_manager, None)
else:
model_manager_class = None
# Get possible chat type override from database, if not already in a private chat
if chat_type != ChatType.private:
chat_type_override: Optional[ChatType] = chat_setting.chat_type_override
if chat_type_override is not None:
chat_type = chat_type_override
if model_manager_class is None or model_manager_name is None:
await message.answer("❌ No valid model manager is set for this chat. Please use /settings to set a model.")
return
if model_name is None:
await message.answer("❌ No valid model name is set for this chat. Please use /settings to set a model.")
return
# Call the model manager to answer the message
if chat_type == ChatType.private:
await model_manager_class.request_model(model_manager_name, model_name, model_config, message)
elif is_mentioned_in_group_or_supergroup(message, mention, chat_type):
await model_manager_class.request_model(model_manager_name, model_name, model_config, message)