"""Telegram bot command sync service. Synchronises @bot_command-decorated handlers with BotCommand DB table and Telegram Bot API (set_my_commands) on boot or manual trigger. """ from __future__ import annotations import logging from typing import Any from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from app.integrations.telegram.client import TelegramBotClient from app.integrations.telegram.decorators import get_registered_commands from app.models.bot_command import BotCommand logger = logging.getLogger("voidea.telegram.sync") class TelegramBotSyncService: """Syncs registered bot commands to DB and Telegram API.""" def __init__(self, db: AsyncSession, bot_client: TelegramBotClient | None = None): self.db = db self.bot_client = bot_client async def sync_to_db(self) -> list[BotCommand]: """Ensure all @bot_command handlers have matching BotCommand records.""" registered = get_registered_commands() result = await self.db.execute(select(BotCommand)) existing = {cmd.name: cmd for cmd in result.scalars().all()} synced: list[BotCommand] = [] for reg in registered: if reg.name in existing: cmd = existing[reg.name] if cmd.description != reg.description or cmd.requires_auth != reg.requires_auth: cmd.description = reg.description cmd.requires_auth = reg.requires_auth synced.append(cmd) else: cmd = BotCommand( name=reg.name, description=reg.description, enabled=True, requires_auth=reg.requires_auth, ) self.db.add(cmd) synced.append(cmd) await self.db.commit() for cmd in synced: await self.db.refresh(cmd) logger.info("Synced %d bot commands to DB", len(synced)) return synced async def sync_to_telegram(self) -> bool: """Push enabled commands to Telegram Bot API via set_my_commands.""" if not self.bot_client or not self.bot_client.available: logger.info("Telegram client not available — skipping set_my_commands") return False result = await self.db.execute( select(BotCommand).where(BotCommand.enabled == True) ) enabled = result.scalars().all() commands = [ {"command": cmd.name.lstrip("/"), "description": cmd.description} for cmd in enabled ] ok = await self.bot_client.set_commands(commands) if ok: logger.info("Pushed %d commands to Telegram", len(commands)) else: logger.warning("Failed to push commands to Telegram") return ok async def full_sync(self) -> dict[str, Any]: """Run full sync: DB → Telegram.""" synced = await self.sync_to_db() telegram_ok = await self.sync_to_telegram() return { "synced_count": len(synced), "telegram_sync": telegram_ok, }