feat(horse-racing): 新增赛马列表/取消下注/开赛权限限制 + 修复退还异常保护 + 文档同步

This commit is contained in:
2026-05-02 11:50:34 +08:00
parent 9566920866
commit 5869618a9c
2 changed files with 324 additions and 217 deletions

View File

@@ -444,6 +444,57 @@ async def handle_cancel(bot: Bot, event: Event):
bet_cmd = on_command("赛马下注", priority=5)
cancel_bet_cmd = on_command("赛马取消下注", priority=5)
@cancel_bet_cmd.handle()
async def handle_cancel_bet(bot: Bot, event: Event):
"""Handle cancel bet - refund all bets placed by the user in current room."""
if not await check_access(bot, event):
await cancel_bet_cmd.finish("无权限访问此功能")
return
scope = get_scope(event)
user_id = get_event_id(event)
lock = room_store.get_lock(scope)
async with lock:
room = room_store.get_room(scope)
if not room:
await cancel_bet_cmd.finish("房间不存在")
return
if room.state != RoomState.WAITING:
await cancel_bet_cmd.finish("比赛已开始,无法取消下注")
return
user_bets = [b for b in room.bets if b.user_id == user_id]
if not user_bets:
await cancel_bet_cmd.finish("你还没有下注")
return
total_refund = 0
refund_errors = []
for bet in user_bets:
try:
await points_service.refund_bet_points(bet.user_id, bet.amount, "取消下注退还")
total_refund += bet.amount
except Exception as e:
logger.error(f"退还下注失败 user={bet.user_id} amount={bet.amount}: {e}")
refund_errors.append(bet)
# 只移除已成功退还的下注
if refund_errors:
failed_amount = sum(b.amount for b in refund_errors)
room.bets = [b for b in room.bets if b.user_id != user_id or b in refund_errors]
await cancel_bet_cmd.finish(f"退还部分失败:成功退还 {total_refund} 积分,{len(refund_errors)} 笔退还失败({failed_amount} 积分),请联系管理员")
return
else:
room.bets = [b for b in room.bets if b.user_id != user_id]
await cancel_bet_cmd.finish(f"已取消 {len(user_bets)} 笔下注,退还 {total_refund} 积分")
@bet_cmd.handle()
async def handle_bet(bot: Bot, event: Event):
"""Handle bet placement."""
@@ -530,17 +581,43 @@ async def handle_odds(bot: Bot, event: Event):
await odds_cmd.finish("\n".join(lines))
race_list_cmd = on_command("赛马列表", priority=5)
@race_list_cmd.handle()
async def handle_race_list(bot: Bot, event: Event):
"""显示当前房间所有报名马匹信息。"""
if not await check_access(bot, event):
await race_list_cmd.finish("无权限访问此功能")
return
scope = get_scope(event)
room = room_store.get_room(scope)
if not room or not room.horses:
await race_list_cmd.finish("暂无报名马匹")
return
lines = ["🏇 当前报名马匹:"]
for horse in _get_horses_in_order(room):
owner_display = await _get_user_name(bot, scope, horse.owner_id)
lines.append(f" {horse.index}. {horse.name} - 主人: {owner_display}")
lines.append(f"\n{len(room.horses)} 匹马")
await race_list_cmd.finish("\n".join(lines))
start_cmd = on_command("赛马开赛", priority=5)
@start_cmd.handle()
async def handle_start(bot: Bot, event: Event):
"""Handle race start."""
"""Handle race start - only participants or admins can start."""
if not await check_access(bot, event):
await start_cmd.finish("无权限访问此功能")
return
scope = get_scope(event)
user_id = get_event_id(event)
lock = room_store.get_lock(scope)
async with lock:
@@ -557,6 +634,24 @@ async def handle_start(bot: Bot, event: Event):
await start_cmd.finish("至少需要2匹马才能开赛")
return
# 开赛权限限制仅参赛者或群管理员可手动开赛满8匹自动开赛不受影响
is_participant = user_id in [h.owner_id for h in room.horses.values()]
is_admin = False
if isinstance(event, GroupMessageEvent):
try:
member_info = await bot.get_group_member_info(
group_id=event.group_id,
user_id=int(user_id)
)
role = member_info.get("role", "")
is_admin = role in ("admin", "owner")
except Exception:
pass
if not is_participant and not is_admin:
await start_cmd.finish("只有参赛者或群管理员可以开赛")
return
# Set all horses to racing state
for horse in room.horses.values():
horse.state = HorseState.RACING
@@ -581,7 +676,9 @@ async def handle_help(bot: Bot, event: Event):
/赛马报名 - 复用上次绑定的马名,若无则使用群昵称
/赛马取消报名 - 取消报名并退还下注
/赛马下注 <序号|马匹名> <金额> - 下注
/赛马取消下注 - 取消本人在当前房间的所有下注并退还积分
/赛马赔率 - 查看当前赔率和下注池
/赛马列表 - 查看当前报名马匹列表
/赛马开赛 - 开始比赛至少2匹马
/赛马帮助 - 显示此帮助
@@ -589,8 +686,10 @@ async def handle_help(bot: Bot, event: Event):
• 最低下注金额:{config.MIN_BET} 积分
• 参赛马匹上限8匹
• 开赛要求至少2匹马报名
• 手动开赛权限:仅当前参赛者或群管理员可操作
💰 奖励机制:
• 参赛奖励:参赛者均可获得 {config.PARTICIPANT_REWARD} 积分
• 冠军马主:获得 {config.CHAMPION_REWARD} 积分
• 下注中奖:下注金额 × 赔率
@@ -602,7 +701,7 @@ async def handle_help(bot: Bot, event: Event):
🎮 游戏流程:
1⃣ 玩家报名并绑定马匹名
2⃣ 玩家可以给任意马匹下注
3⃣ 满足开赛条件后,任意玩家可开赛
3⃣ 满足开赛后,由参赛者或管理员开赛
4⃣ 比赛实时进行,定期播报进度
5⃣ 比赛结束后结算积分和奖金"""
await help_cmd.finish(help_text)