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}{tag}>", 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).strip()
def require_user_id(update: Update) -> int | None:
if update.effective_user:
return int(update.effective_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