import asyncio from typing import Optional, Any from nonebot.adapters.onebot.v11 import Bot, Message from .config import Config 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, message: str | Message, ) -> Optional[str]: """Send message and schedule recall if configured. If it's a 'race_update', recall the previous one first.""" try: # 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 # 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(bot, scope, message_id, recall_delay) ) if scope not in self.pending_recalls: self.pending_recalls[scope] = [] self.pending_recalls[scope].append(task) return message_id 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, ): """Schedule message recall after a delay.""" try: await asyncio.sleep(delay) 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 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.""" for scope in list(self.pending_recalls.keys()): self.clear_pending_recalls(scope)