Files
TGLotteryBot/backend/app/services/scheduler.py
T
Stardream eace48732d
Build and Push Docker Images / build (push) Successful in 17s
Initial release
2026-06-02 12:02:02 +10:00

135 lines
4.8 KiB
Python

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'<a href="tg://user?id={w["user_id"]}">{full_name}</a>'
lines.append(f"🏆 {safe_name} - {html.escape(w['prize_name'])}")
text = (
f"🎉 <b>Draw Results: {html.escape(lottery.title)}</b>\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 <b>{html.escape(lottery_title)}</b>!\n"
f"🎁 Prize: <b>{html.escape(wins[0]['prize_name'])}</b>"
)
else:
prizes_lines = "\n".join(f"🎁 {html.escape(w['prize_name'])}" for w in wins)
text = (
f"🎉 Congratulations! You won {len(wins)} prizes in <b>{html.escape(lottery_title)}</b>!\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)