feat: configure lottery events in private chat

This commit is contained in:
ngfchl
2026-07-07 12:34:34 +08:00
parent 19971b2e82
commit 1f3183345c
2 changed files with 101 additions and 44 deletions
+3 -3
View File
@@ -292,9 +292,9 @@ async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
"✅ /checkin - 每日签到\n"
"🎮 /trivia - 知识答题\n"
"🏆 /rank - 查看积分排行\n"
"🎁 /lottery - 发起群内抽奖\n"
" 格式:/lottery 奖品 | 参与条件 | 结束条件\n"
" 示例:/lottery 月卡一张 | all | people:3\n"
"🎁 /lottery - 发起群内抽奖,进入私聊设置奖品和条件\n"
" 私聊格式:奖品 | 参与条件 | 结束条件\n"
" 示例:月卡一张 | all | people:3\n"
"🔗 /bind - 绑定账号\n\n"
"管理员操作:\n"
"🌙 /night on/off - 开启/关闭夜间模式\n"
+98 -41
View File
@@ -2,6 +2,8 @@
import asyncio
import logging
import config
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import ApplicationHandlerStop, CallbackQueryHandler, ContextTypes, CommandHandler, MessageHandler, filters
@@ -182,46 +184,60 @@ async def schedule_lottery_draw(context: ContextTypes.DEFAULT_TYPE, event_id: in
asyncio.create_task(_run())
async def create_lottery_from_text(update: Update, context: ContextTypes.DEFAULT_TYPE, spec_text: str):
async def create_lottery_from_text(update: Update, context: ContextTypes.DEFAULT_TYPE, spec_text: str, publish_chat_id: int | None = None):
user = update.effective_user
msg = update.effective_message
spec = parse_lottery_spec(spec_text)
target_chat_id = publish_chat_id or msg.chat_id
event = await create_lottery_event(
chat_id=msg.chat_id,
chat_id=target_chat_id,
creator_id=user.id,
creator_name=user.username or user.first_name or str(user.id),
spec=spec,
)
sent = await msg.reply_text(await build_lottery_text(event), reply_markup=lottery_keyboard(event.id))
sent = await context.bot.send_message(
chat_id=target_chat_id,
text=await build_lottery_text(event),
reply_markup=lottery_keyboard(event.id),
)
event.message_id = sent.message_id
await event.save()
if event.end_type == "minutes":
await schedule_lottery_draw(context, event.id, event.end_value * 60)
return sent
return event
async def send_lottery_private入口(update: Update, context: ContextTypes.DEFAULT_TYPE):
me = await context.bot.get_me()
bot_username = me.username
target_chat_id = update.effective_chat.id
url = f"https://t.me/{bot_username}?start=lottery_{target_chat_id}"
keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("去私聊设置抽奖", url=url)]])
text = (
"🎁 发起群内抽奖\n"
"为了避免奖品和条件刷屏,请到 Bot 私聊界面设置。\n\n"
f"👉 点击进入:{url}"
)
reply = await update.effective_message.reply_text(text, reply_markup=keyboard, disable_web_page_preview=True)
asyncio.create_task(auto_delete(update.effective_message, seconds=60))
asyncio.create_task(auto_delete(reply, seconds=60))
return reply
async def cmd_lottery(update: Update, context: ContextTypes.DEFAULT_TYPE):
parts = (update.message.text or "").split(maxsplit=1)
if len(parts) > 1:
try:
await create_lottery_from_text(update, context, parts[1])
except Exception as e:
reply = await update.message.reply_text(f"❌ 抽奖格式错误:{e}\n\n示例:/lottery 月卡一张 | all | people:3")
asyncio.create_task(auto_delete(reply, seconds=90))
else:
context.user_data["awaiting_lottery_spec"] = True
reply = await update.message.reply_text(
"🎁 发起群内抽奖\n"
"请发送:奖品 | 参与条件 | 结束条件\n\n"
"示例:\n"
"月卡一张 | all | people:3\n"
"邀请码 | checkin | minutes:10\n"
"徽章 | points:100 | people:5\n\n"
"参与条件:all / checkin / points:N\n"
"结束条件:people:N / minutes:N"
)
asyncio.create_task(auto_delete(reply, seconds=120))
asyncio.create_task(auto_delete(update.message, seconds=60))
if update.effective_chat.type != "private":
await send_lottery_private入口(update, context)
return
context.user_data["awaiting_lottery_spec"] = True
context.user_data["lottery_target_chat_id"] = config.TG_CHAT_ID
await update.message.reply_text(
"🎁 发起群内抽奖\n"
"请发送:奖品 | 参与条件 | 结束条件\n\n"
"示例:月卡一张 | all | people:3\n"
"参与条件:all / checkin / points:N\n"
"结束条件:people:N / minutes:N"
)
async def cmd_shop(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -271,14 +287,19 @@ async def handle_panel_callback(update: Update, context: ContextTypes.DEFAULT_TY
result = await daily_checkin(user.id, user.username or user.first_name or str(user.id))
text = "❌ 你今天已经签到过了" if not result["ok"] else f"✅ 签到成功!奖励 +{result['bonus']} 分,连续 {result['streak']}"
elif action == "lottery":
context.user_data["awaiting_lottery_spec"] = True
text = (
me = await context.bot.get_me()
url = f"https://t.me/{me.username}?start=lottery_{query.message.chat_id}"
keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("去私聊设置抽奖", url=url)]])
await query.edit_message_text(
"🎁 发起群内抽奖\n"
"在群里发送:奖品 | 参与条件 | 结束条件\n\n"
"示例:月卡一张 | all | people:3\n"
"参与条件:all / checkin / points:N\n"
"结束条件:people:N / minutes:N"
"到 Bot 私聊界面设置奖品、参与条件结束条件\n\n"
f"👉 点击进入:{url}\n\n"
"⏱️ 60 秒无操作后自动消失。",
reply_markup=keyboard,
disable_web_page_preview=True,
)
schedule_panel_idle_delete(context, query.message)
return
elif action == "shop":
summary = await get_scores_summary(user.id)
text = f"🛒 积分商城\n当前总积分:{summary['total']}\n" + "\n".join([f"{k}. {v['name']}{v['cost']}" for k, v in SHOP_ITEMS.items()]) + "\n\n购买请发送 /buy 商品编号"
@@ -301,15 +322,6 @@ async def handle_trivia_answer(update: Update, context: ContextTypes.DEFAULT_TYP
if not msg or not msg.text or not user or user.is_bot:
return
if context.user_data.get("awaiting_lottery_spec"):
try:
await create_lottery_from_text(update, context, msg.text.strip())
context.user_data.pop("awaiting_lottery_spec", None)
asyncio.create_task(auto_delete(msg, seconds=60))
except Exception as e:
reply = await msg.reply_text(f"❌ 抽奖格式错误:{e}\n请重新发送,或发送 /panel 重新开始。")
asyncio.create_task(auto_delete(reply, seconds=90))
raise ApplicationHandlerStop
correct_answer = context.user_data.get("trivia_answer")
options = context.user_data.get("trivia_options") or []
@@ -376,6 +388,49 @@ async def handle_lottery_callback(update: Update, context: ContextTypes.DEFAULT_
await query.answer(f"参与成功,当前 {result['count']}")
async def cmd_lottery_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_chat.type != "private" or not context.args:
return
arg = context.args[0]
if not arg.startswith("lottery_"):
return
try:
target_chat_id = int(arg.split("_", 1)[1])
except Exception:
target_chat_id = config.TG_CHAT_ID
context.user_data["awaiting_lottery_spec"] = True
context.user_data["lottery_target_chat_id"] = target_chat_id
await update.message.reply_text(
"🎁 发起群内抽奖\n"
"请发送:奖品 | 参与条件 | 结束条件\n\n"
"示例:月卡一张 | all | people:3\n"
"参与条件:all / checkin / points:N\n"
"结束条件:people:N / minutes:N"
)
async def handle_private_lottery_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_chat.type != "private" or not context.user_data.get("awaiting_lottery_spec"):
return
try:
target_chat_id = int(context.user_data.get("lottery_target_chat_id") or config.TG_CHAT_ID)
event = await create_lottery_from_text(update, context, update.message.text.strip(), publish_chat_id=target_chat_id)
context.user_data.pop("awaiting_lottery_spec", None)
context.user_data.pop("lottery_target_chat_id", None)
await update.message.reply_text(
"✅ 抽奖活动已发布到群里\n"
f"🎯 奖品:{event.prize}\n"
f"✅ 参与条件:{await lottery_condition_text(event)}\n"
f"⏱️ 结束条件:{await lottery_end_text(event)}"
)
except Exception as e:
await update.message.reply_text(
f"❌ 抽奖格式错误:{e}\n\n"
"请重新发送:奖品 | 参与条件 | 结束条件\n"
"示例:月卡一张 | all | people:3"
)
async def auto_delete(message, seconds=60):
try:
await asyncio.sleep(seconds)
@@ -389,11 +444,13 @@ def register_fun(app):
app.add_handler(CommandHandler("trivia", cmd_trivia, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("score", cmd_score, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("rank", cmd_rank, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("lottery", cmd_lottery, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("lottery", cmd_lottery))
app.add_handler(CommandHandler("shop", cmd_shop, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("buy", cmd_buy, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("panel", cmd_panel, filters.ChatType.GROUPS))
app.add_handler(CommandHandler("start", cmd_lottery_start, filters.ChatType.PRIVATE), group=1)
app.add_handler(CallbackQueryHandler(handle_panel_callback, pattern=r"^panel:"))
app.add_handler(CallbackQueryHandler(handle_lottery_callback, pattern=r"^lottery:"))
app.add_handler(MessageHandler(filters.TEXT & filters.ChatType.PRIVATE & ~filters.COMMAND, handle_private_lottery_text), group=1)
app.add_handler(MessageHandler(filters.TEXT & filters.ChatType.GROUPS & ~filters.COMMAND, handle_trivia_answer), group=-1)
app.add_handler(MessageHandler(filters.TEXT & filters.ChatType.GROUPS, handle_trigger), group=2)