from html import escape from uuid import uuid4 from zoneinfo import ZoneInfo from telegram import InlineQueryResultArticle, InputTextMessageContent, Message, Update from telegram.constants import ParseMode from telegram.ext import ContextTypes from .ai import AIClient from .config import HTML_FORMATS, MAX_TELEGRAM_MESSAGE_LENGTH from .speech import SpeechTranscriber from .storage import AssistantStorage def make_article( title: str, text: str, parse_mode: str | None = None, ) -> InlineQueryResultArticle: return InlineQueryResultArticle( id=f"inline_{uuid4()}", title=title, input_message_content=InputTextMessageContent(text, parse_mode=parse_mode), ) def build_inline_results(query: str) -> list[InlineQueryResultArticle]: escaped_query = escape(query) return [ make_article("Caps", query.upper()), *[ make_article(title, f"<{tag}>{escaped_query}", ParseMode.HTML) for title, tag in HTML_FORMATS ], ] def get_storage(context: ContextTypes.DEFAULT_TYPE) -> AssistantStorage: return context.application.bot_data["storage"] def get_ai_client(context: ContextTypes.DEFAULT_TYPE) -> AIClient: return context.application.bot_data["ai_client"] def get_tz(context: ContextTypes.DEFAULT_TYPE) -> ZoneInfo: return context.application.bot_data["timezone"] def get_speech_recognizer(context: ContextTypes.DEFAULT_TYPE) -> SpeechTranscriber: return context.application.bot_data["speech_recognizer"] def get_voice_max_duration(context: ContextTypes.DEFAULT_TYPE) -> int: return int(context.application.bot_data["voice_max_duration"]) def command_text(context: ContextTypes.DEFAULT_TYPE) -> str: return " ".join(context.args or []).strip() def require_user_id(update: Update) -> int | None: user = update.effective_user if user: return int(user.id) return None async def reply_long(message: Message, text: str) -> None: chunks = [ text[index : index + MAX_TELEGRAM_MESSAGE_LENGTH] for index in range(0, len(text), MAX_TELEGRAM_MESSAGE_LENGTH) ] or [""] for chunk in chunks: await message.reply_text(chunk) async def edit_or_reply(message: Message, text: str) -> None: chunks = [ text[index : index + MAX_TELEGRAM_MESSAGE_LENGTH] for index in range(0, len(text), MAX_TELEGRAM_MESSAGE_LENGTH) ] or [""] await message.edit_text(chunks[0]) for chunk in chunks[1:]: await message.reply_text(chunk) def parse_positive_int(value: str) -> int | None: try: parsed = int(value) except ValueError: return None return parsed if parsed > 0 else None