diff --git a/danding_bot/plugins/group_horse_racing/commands.py b/danding_bot/plugins/group_horse_racing/commands.py index ec07346..724920a 100644 --- a/danding_bot/plugins/group_horse_racing/commands.py +++ b/danding_bot/plugins/group_horse_racing/commands.py @@ -260,18 +260,14 @@ async def settle_race(room: Room) -> RaceResult | None: return result -async def _send_to_scope(bot: Bot, scope: str, message: str): +async def _send_to_scope(bot: Bot, scope: str, message: str, message_type: str = "race_update"): """Send message to group or private chat based on scope.""" try: outbound_message: str | Message = message if config.RACE_RENDER_AS_IMAGE: outbound_message = _build_race_image_message(message) - await bot.send_msg( - message_type="group" if scope.startswith("group_") else "private", - group_id=int(scope.split("_", 1)[1]) if scope.startswith("group_") else None, - user_id=int(scope.split("_", 1)[1]) if scope.startswith("test_") else None, - message=outbound_message, - ) + + await message_service.send_with_recall(bot, scope, message_type, outbound_message) except Exception: pass @@ -281,7 +277,7 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str): room.state = RoomState.RUNNING horse_list = "\n".join(f" {_format_horse_label(h)}" for h in _get_horses_in_order(room)) - await _send_to_scope(bot, scope, f"比赛开始!\n{horse_list}") + await _send_to_scope(bot, scope, f"比赛开始!\n{horse_list}", "race_update") # Race loop with progress updates try: @@ -290,7 +286,7 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str): finished = race_engine.tick(room) progress = race_engine.format_progress(room) - await _send_to_scope(bot, scope, progress) + await _send_to_scope(bot, scope, progress, "race_update") if finished: champion = race_engine.determine_champion(finished) @@ -327,7 +323,9 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str): if result: result_lines.extend(await _format_point_change_lines(room, result.point_changes, result.point_change_summaries, name_map)) - await _send_to_scope(bot, scope, "\n".join(result_lines)) + # Before sending result, we can recall the last update + await message_service.recall_previous_of_type(bot, scope, "race_update") + await _send_to_scope(bot, scope, "\n".join(result_lines), "race_result") # Cleanup race_engine.stop_race(scope) diff --git a/danding_bot/plugins/group_horse_racing/message_service.py b/danding_bot/plugins/group_horse_racing/message_service.py index db36839..71c1b66 100644 --- a/danding_bot/plugins/group_horse_racing/message_service.py +++ b/danding_bot/plugins/group_horse_racing/message_service.py @@ -1,6 +1,6 @@ import asyncio -from typing import Optional, Callable -from datetime import datetime, timedelta +from typing import Optional, Any +from nonebot.adapters.onebot.v11 import Bot, Message from .config import Config @@ -9,25 +9,45 @@ class MessageService: def __init__(self, config: Config): self.config = config self.pending_recalls: dict[str, list[asyncio.Task]] = {} + self.last_messages: dict[str, dict[str, str]] = {} # scope -> {message_type -> message_id} async def send_with_recall( self, + bot: Bot, scope: str, message_type: str, - send_func: Callable, - *args, - **kwargs, + message: str | Message, ) -> Optional[str]: - """Send message and schedule recall if configured.""" + """Send message and schedule recall if configured. + If it's a 'race_update', recall the previous one first.""" try: - message_id = await send_func(*args, **kwargs) + # For race_update, recall the previous one in the same scope + if message_type == "race_update": + await self.recall_previous_of_type(bot, scope, "race_update") + + # Send the message + is_group = scope.startswith("group_") + result = await bot.send_msg( + message_type="group" if is_group else "private", + group_id=int(scope.split("_", 1)[1]) if is_group else None, + user_id=int(scope.split("_", 1)[1]) if not is_group else None, + message=message, + ) + + message_id = result.get("message_id") if isinstance(result, dict) else None if not message_id: return None - recall_time = self.config.MESSAGE_RECALL.get(message_type, 0) - if recall_time > 0: + # Track the last message of this type + if scope not in self.last_messages: + self.last_messages[scope] = {} + self.last_messages[scope][message_type] = message_id + + # Schedule auto-recall if configured + recall_delay = self.config.MESSAGE_RECALL.get(message_type, 0) + if recall_delay > 0: task = asyncio.create_task( - self._schedule_recall(scope, message_id, recall_time, send_func) + self._schedule_recall(bot, scope, message_id, recall_delay) ) if scope not in self.pending_recalls: self.pending_recalls[scope] = [] @@ -37,27 +57,40 @@ class MessageService: except Exception as e: return None + async def recall_previous_of_type(self, bot: Bot, scope: str, message_type: str): + """Recall the previous message of a specific type in a scope.""" + if scope in self.last_messages and message_type in self.last_messages[scope]: + old_message_id = self.last_messages[scope][message_type] + try: + await bot.delete_msg(message_id=old_message_id) + except Exception: + pass + del self.last_messages[scope][message_type] + async def _schedule_recall( self, + bot: Bot, scope: str, message_id: str, delay: int, - recall_func: Callable, ): - """Schedule message recall.""" + """Schedule message recall after a delay.""" try: await asyncio.sleep(delay) - await recall_func(message_id) + await bot.delete_msg(message_id=message_id) except Exception: pass def clear_pending_recalls(self, scope: str): - """Cancel all pending recall tasks for a scope.""" + """Cancel all pending recall tasks for a scope and clear last messages.""" if scope in self.pending_recalls: for task in self.pending_recalls[scope]: if not task.done(): task.cancel() del self.pending_recalls[scope] + + if scope in self.last_messages: + del self.last_messages[scope] def clear_all_recalls(self): """Cancel all pending recall tasks.""" diff --git a/danding_bot/plugins/group_horse_racing/test_commands.py b/danding_bot/plugins/group_horse_racing/test_commands.py index baaf26d..32f20d4 100644 --- a/danding_bot/plugins/group_horse_racing/test_commands.py +++ b/danding_bot/plugins/group_horse_racing/test_commands.py @@ -195,6 +195,12 @@ class _NoopMessageService: def clear_pending_recalls(self, scope: str): return + async def send_with_recall(self, bot, scope, message_type, message): + return "fake_msg_id" + + async def recall_previous_of_type(self, bot, scope, message_type): + return + @test_simulate_race_cmd.handle() async def handle_test_simulate_race(bot: Bot, event: Event): @@ -240,7 +246,7 @@ async def handle_test_simulate_race(bot: Bot, event: Event): progress_count = 0 max_progress = 30 - async def _test_send_to_scope(_bot: Bot, _scope: str, message: str): + async def _test_send_to_scope(_bot: Bot, _scope: str, message: str, *args, **kwargs): nonlocal progress_count await fake_bot.send_msg(message_type="private", user_id=event.user_id, message=message) if not stream_progress: @@ -250,7 +256,7 @@ async def handle_test_simulate_race(bot: Bot, event: Event): progress_count += 1 if progress_count > max_progress: return - await original_send_to_scope(bot, scope, message) + await original_send_to_scope(bot, scope, message, *args, **kwargs) commands_mod._send_to_scope = _test_send_to_scope