This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user