diff --git a/handlers/admin.py b/handlers/admin.py index 8cf4621..9fc561a 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -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" diff --git a/handlers/fun.py b/handlers/fun.py index 33197d1..13b2174 100644 --- a/handlers/fun.py +++ b/handlers/fun.py @@ -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)