- P1: bet.py赔率计算移入锁内防竞态 - P1: config.py TESTERS解析失败添加warning日志 - P2: 新增赛马取消命令(积分退还/任务取消/状态重置) - P3: bet.py清理未使用的_send_to_scope导入 - 将commands.py拆分为commands/包(access/bet/help/race/register) - OpenSpec变更提案: fix-race-conditions-and-logs
163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
import asyncio
|
||
from nonebot import on_command
|
||
from nonebot.adapters.onebot.v11 import Bot, Event
|
||
from . import (
|
||
room_store, race_engine, config, logger,
|
||
get_scope, check_access, get_event_id,
|
||
_send_to_scope, _build_race_image_message,
|
||
run_race_with_settlement, points_service,
|
||
)
|
||
from ..models import RoomState, HorseState
|
||
from nonebot.adapters.onebot.v11 import GroupMessageEvent
|
||
from ..models import RoomState
|
||
|
||
@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 - 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:
|
||
room = room_store.get_room(scope)
|
||
if not room:
|
||
await start_cmd.finish("房间不存在,请先报名")
|
||
return
|
||
|
||
if room.state != RoomState.WAITING:
|
||
await start_cmd.finish("比赛已经在进行中")
|
||
return
|
||
|
||
if len(room.horses) < 2:
|
||
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
|
||
|
||
await start_cmd.send("比赛开始!")
|
||
|
||
# Run race in background (outside command handler)
|
||
task = asyncio.create_task(run_race_with_settlement(bot, room, scope))
|
||
race_engine.register_task(scope, task)
|
||
|
||
|
||
cancel_race_cmd = on_command("赛马取消", priority=5)
|
||
|
||
|
||
@cancel_race_cmd.handle()
|
||
async def handle_cancel_race(bot: Bot, event: Event):
|
||
"""取消当前进行的比赛,退还所有下注积分。仅参赛者或管理员可操作。"""
|
||
if not await check_access(bot, event):
|
||
await cancel_race_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_race_cmd.finish("房间不存在")
|
||
return
|
||
|
||
if room.state != RoomState.RACING:
|
||
await cancel_race_cmd.finish("当前没有进行中的比赛")
|
||
return
|
||
|
||
# 权限:只有参赛者或群管理员可以取消
|
||
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 cancel_race_cmd.finish("只有参赛者或群管理员可以取消比赛")
|
||
return
|
||
|
||
# 停止后台比赛任务
|
||
race_engine.stop_race(scope)
|
||
|
||
# 退还所有下注积分
|
||
total_refund = 0
|
||
for bet in room.bets[:]: # 遍历副本
|
||
success, _ = await points_service.refund_bet_points(
|
||
bet.user_id, bet.amount, "比赛取退还下注"
|
||
)
|
||
if success:
|
||
total_refund += bet.amount
|
||
|
||
# 清空下注记录
|
||
room.bets.clear()
|
||
|
||
# 重置马匹状态为等待
|
||
for horse in room.horses.values():
|
||
horse.state = HorseState.WAITING
|
||
|
||
# 重置房间状态
|
||
room.state = RoomState.WAITING
|
||
room.tick_count = 0
|
||
|
||
await _send_to_scope(scope, f"🏇 比赛已取消!共退还 {total_refund} 积分。")
|
||
|
||
help_cmd = on_command("赛马帮助", priority=5)
|
||
|
||
|