import asyncio import html import logging import uuid from datetime import datetime from sqlalchemy import select from app.core.database import AsyncSessionLocal from app.models.lottery import Lottery, LotteryStatus from app.services.draw_service import execute_draw logger = logging.getLogger(__name__) async def _announce_winners(lottery: Lottery, winners_data: list[dict]) -> None: if not winners_data: return targets = lottery.announcement_targets or ( [{"chat_id": lottery.target_chat_id, "message_id": lottery.announcement_message_id}] if lottery.target_chat_id else [] ) if not targets: return from app.bot.router import bot from aiogram.exceptions import TelegramAPIError lines = [] for w in winners_data: full_name = html.escape( w["first_name"] + (f" {w['last_name']}" if w.get("last_name") else "") ) safe_name = f'{full_name}' lines.append(f"šŸ† {safe_name} - {html.escape(w['prize_name'])}") text = ( f"šŸŽ‰ Draw Results: {html.escape(lottery.title)}\n\n" + "\n".join(lines) + "\n\nCongratulations to all winners! šŸŽŠ" ) from aiogram.types import ReplyParameters for t in targets: try: reply = ( ReplyParameters(message_id=t["message_id"], allow_sending_without_reply=True) if t.get("message_id") else None ) await bot.send_message( t["chat_id"], text, parse_mode="HTML", reply_parameters=reply, ) except TelegramAPIError as e: logger.warning("Failed to announce auto-draw winners to %s: %s", t["chat_id"], e) async def notify_winners( lottery_title: str, winners_data: list[dict], contact_info: str | None = None, lottery_id: uuid.UUID | None = None, ) -> None: """Send a private congratulations message to each winner via the bot.""" from app.bot.router import bot from aiogram.exceptions import TelegramAPIError from app.bot.keyboards.inline import lottery_detail_keyboard from collections import defaultdict user_wins: dict[int, list[dict]] = defaultdict(list) for w in winners_data: user_wins[w["user_id"]].append(w) keyboard = lottery_detail_keyboard(str(lottery_id), text="šŸ† View results") if lottery_id else None for user_id, wins in user_wins.items(): if len(wins) == 1: text = ( f"šŸŽ‰ Congratulations! You won in {html.escape(lottery_title)}!\n" f"šŸŽ Prize: {html.escape(wins[0]['prize_name'])}" ) else: prizes_lines = "\n".join(f"šŸŽ {html.escape(w['prize_name'])}" for w in wins) text = ( f"šŸŽ‰ Congratulations! You won {len(wins)} prizes in {html.escape(lottery_title)}!\n" + prizes_lines ) if contact_info: text += f"\n\nšŸ“¬ Contact to claim your prize: {html.escape(contact_info)}" else: text += "\n\nPlease contact the lottery organizer to claim your prize." try: await bot.send_message(user_id, text, parse_mode="HTML", reply_markup=keyboard) except TelegramAPIError: pass async def run_auto_draw(lottery_id: uuid.UUID, trigger: str = "scheduled") -> None: async with AsyncSessionLocal() as db: try: winners_data = await execute_draw(db, lottery_id, trigger=trigger) except ValueError as e: logger.info("Auto-draw skipped for %s: %s", lottery_id, e) return except Exception as e: logger.error("Auto-draw error for %s: %s", lottery_id, e) return result = await db.execute(select(Lottery).where(Lottery.id == lottery_id)) lottery = result.scalar_one_or_none() if lottery: await _announce_winners(lottery, winners_data) await notify_winners(lottery.title, winners_data, lottery.contact_info, lottery_id=lottery.id) async def auto_draw_scheduler() -> None: """Poll every 30 s for lotteries whose draw_at has passed.""" while True: await asyncio.sleep(30) try: now = datetime.utcnow() async with AsyncSessionLocal() as db: result = await db.execute( select(Lottery.id).where( Lottery.status == LotteryStatus.ACTIVE, Lottery.draw_at.isnot(None), Lottery.draw_at <= now, ) ) ids = result.scalars().all() for lottery_id in ids: asyncio.create_task(run_auto_draw(lottery_id)) except Exception as e: logger.error("Scheduler error: %s", e)