This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import ChatMemberUpdated, Message
|
||||
|
||||
from app.core.database import AsyncSessionLocal
|
||||
from app.models.managed_chat import ManagedChat
|
||||
from app.models.lottery import Lottery, LotteryStatus
|
||||
from app.models.passphrase_verification import PassphraseVerification
|
||||
from sqlalchemy import select
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.my_chat_member(F.chat.type.in_({"group", "supergroup", "channel"}))
|
||||
async def bot_chat_member_updated(event: ChatMemberUpdated) -> None:
|
||||
"""Record or update the bot's presence in a group/channel."""
|
||||
async with AsyncSessionLocal() as db:
|
||||
chat = event.chat
|
||||
new_status = event.new_chat_member.status
|
||||
|
||||
result = await db.execute(
|
||||
select(ManagedChat).where(ManagedChat.id == chat.id)
|
||||
)
|
||||
managed = result.scalar_one_or_none()
|
||||
|
||||
if new_status in ("member", "administrator"):
|
||||
if managed is None:
|
||||
managed = ManagedChat(
|
||||
id=chat.id,
|
||||
title=chat.title or "",
|
||||
username=getattr(chat, 'username', None),
|
||||
chat_type=chat.type,
|
||||
)
|
||||
db.add(managed)
|
||||
else:
|
||||
managed.is_active = True
|
||||
managed.title = chat.title or managed.title
|
||||
managed.username = getattr(chat, 'username', None)
|
||||
await db.commit()
|
||||
elif new_status in ("left", "kicked") and managed is not None:
|
||||
managed.is_active = False
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.channel_post(F.chat.type == "channel")
|
||||
@router.message(F.chat.type.in_({"group", "supergroup"}))
|
||||
async def on_chat_message(message: Message) -> None:
|
||||
"""Track latest message ID and detect passphrase submissions."""
|
||||
async with AsyncSessionLocal() as db:
|
||||
result = await db.execute(select(ManagedChat).where(ManagedChat.id == message.chat.id))
|
||||
managed = result.scalar_one_or_none()
|
||||
if managed is None:
|
||||
managed = ManagedChat(
|
||||
id=message.chat.id,
|
||||
title=message.chat.title or "",
|
||||
username=getattr(message.chat, "username", None),
|
||||
chat_type=message.chat.type,
|
||||
last_message_id=message.message_id,
|
||||
)
|
||||
db.add(managed)
|
||||
elif message.message_id > (managed.last_message_id or 0):
|
||||
managed.last_message_id = message.message_id
|
||||
await db.commit()
|
||||
|
||||
if message.text and message.from_user:
|
||||
text = message.text.strip()
|
||||
uid = message.from_user.id
|
||||
cid = message.chat.id
|
||||
async with AsyncSessionLocal() as db2:
|
||||
r = await db2.execute(
|
||||
select(Lottery).where(
|
||||
Lottery.status == LotteryStatus.ACTIVE,
|
||||
Lottery.require_membership == True,
|
||||
)
|
||||
)
|
||||
for lottery in r.scalars().all():
|
||||
ids = lottery.required_chat_ids or []
|
||||
phrases = lottery.required_chat_passphrases or []
|
||||
for i, chat_id in enumerate(ids):
|
||||
if chat_id == cid and i < len(phrases) and phrases[i] and text == phrases[i]:
|
||||
existing = await db2.execute(
|
||||
select(PassphraseVerification).where(
|
||||
PassphraseVerification.lottery_id == lottery.id,
|
||||
PassphraseVerification.chat_id == cid,
|
||||
PassphraseVerification.user_id == uid,
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none() is None:
|
||||
db2.add(PassphraseVerification(
|
||||
lottery_id=lottery.id, chat_id=cid, user_id=uid
|
||||
))
|
||||
await db2.commit()
|
||||
break
|
||||
@@ -0,0 +1,50 @@
|
||||
from aiogram import Router
|
||||
from aiogram.filters import Command, CommandObject, CommandStart
|
||||
from aiogram.types import Message
|
||||
|
||||
from app.bot.keyboards.inline import create_lottery_keyboard, lottery_detail_keyboard, main_menu_keyboard
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
async def cmd_start(message: Message, command: CommandObject) -> None:
|
||||
# Deep-link from group announcement: /start lottery_<uuid>
|
||||
if command.args and command.args.startswith("lottery_"):
|
||||
lottery_id = command.args[len("lottery_"):]
|
||||
await message.answer(
|
||||
"🎰 Tap the button below to join the lottery:",
|
||||
reply_markup=lottery_detail_keyboard(lottery_id),
|
||||
)
|
||||
return
|
||||
|
||||
await message.answer(
|
||||
"👋 Welcome to <b>TG Lottery Bot</b>!\n\n"
|
||||
"Tap the button below to get started. Use /help for more information.",
|
||||
reply_markup=main_menu_keyboard(),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("newlottery"))
|
||||
async def cmd_new_lottery(message: Message) -> None:
|
||||
await message.answer("Create a new lottery 👇", reply_markup=create_lottery_keyboard())
|
||||
|
||||
|
||||
@router.message(Command("mylotteries"))
|
||||
async def cmd_my_lotteries(message: Message) -> None:
|
||||
await message.answer("View my lotteries 👇", reply_markup=main_menu_keyboard())
|
||||
|
||||
|
||||
@router.message(Command("help"))
|
||||
async def cmd_help(message: Message) -> None:
|
||||
await message.answer(
|
||||
"📖 <b>TG Lottery Bot</b>\n\n"
|
||||
"<b>Commands</b>\n"
|
||||
"/start - Open lottery manager\n"
|
||||
"/newlottery - Create a new lottery\n"
|
||||
"/mylotteries - View my lotteries\n"
|
||||
"/help - Show this help message\n\n"
|
||||
"📦 <b>Powered by</b> git.stdm.moe/Stardream/TGLotteryBot",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def _mini_app_url(path: str = "") -> str:
|
||||
base = settings.mini_app_url.rstrip("/") + "/"
|
||||
return urljoin(base, path.lstrip("/"))
|
||||
|
||||
|
||||
def main_menu_keyboard() -> InlineKeyboardMarkup:
|
||||
return InlineKeyboardMarkup(inline_keyboard=[[
|
||||
InlineKeyboardButton(
|
||||
text="Open lottery app",
|
||||
web_app=WebAppInfo(url=_mini_app_url()),
|
||||
)
|
||||
]])
|
||||
|
||||
|
||||
def create_lottery_keyboard() -> InlineKeyboardMarkup:
|
||||
return InlineKeyboardMarkup(inline_keyboard=[[
|
||||
InlineKeyboardButton(
|
||||
text="Create lottery",
|
||||
web_app=WebAppInfo(url=_mini_app_url("/create")),
|
||||
)
|
||||
]])
|
||||
|
||||
|
||||
def lottery_detail_keyboard(lottery_id: str, text: str = "🎰 Join lottery") -> InlineKeyboardMarkup:
|
||||
"""For private chat messages - web_app is allowed here."""
|
||||
return InlineKeyboardMarkup(inline_keyboard=[[
|
||||
InlineKeyboardButton(
|
||||
text=text,
|
||||
web_app=WebAppInfo(url=_mini_app_url(f"/lottery/{lottery_id}")),
|
||||
)
|
||||
]])
|
||||
|
||||
|
||||
def lottery_announce_keyboard(lottery_id: str) -> InlineKeyboardMarkup:
|
||||
"""For group/channel announcements - web_app is not allowed; use t.me deep link."""
|
||||
from app.bot.router import bot_username
|
||||
if bot_username:
|
||||
url = f"https://t.me/{bot_username}?start=lottery_{lottery_id}"
|
||||
else:
|
||||
url = _mini_app_url(f"/lottery/{lottery_id}")
|
||||
return InlineKeyboardMarkup(inline_keyboard=[[
|
||||
InlineKeyboardButton(text="🎰 Join lottery", url=url)
|
||||
]])
|
||||
@@ -0,0 +1,17 @@
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
|
||||
from app.core.config import settings
|
||||
from app.bot.handlers import start, member
|
||||
|
||||
bot = Bot(
|
||||
token=settings.bot_token,
|
||||
default=DefaultBotProperties(parse_mode="HTML"),
|
||||
)
|
||||
dp = Dispatcher()
|
||||
|
||||
dp.include_router(start.router)
|
||||
dp.include_router(member.router)
|
||||
|
||||
# Populated at startup by main.py after bot.get_me()
|
||||
bot_username: str = ""
|
||||
Reference in New Issue
Block a user