Initial release
Build and Push Docker Images / build (push) Successful in 17s

This commit is contained in:
Stardream
2026-06-02 10:58:24 +10:00
commit eace48732d
88 changed files with 11140 additions and 0 deletions
+134
View File
@@ -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)