Skip to content

feat(mp): add !mp command handling support#149

Open
CloneWith wants to merge 26 commits into
GooGuTeam:mainfrom
CloneWith:feat/mp-commands
Open

feat(mp): add !mp command handling support#149
CloneWith wants to merge 26 commits into
GooGuTeam:mainfrom
CloneWith:feat/mp-commands

Conversation

@CloneWith

@CloneWith CloneWith commented May 11, 2026

Copy link
Copy Markdown
Contributor

Resolves #147.

TODO: The mechanics behind all of this would get explained later on.

Tasks

  • Main logic in banchobot.py
  • Message rewording
  • Testing

@CloneWith CloneWith added scope/signalr scope/room Multiplayer, playlist, spotlight, daily-challenge & matchmaking labels May 11, 2026
@CloneWith CloneWith marked this pull request as ready for review June 20, 2026 06:55
Copilot AI review requested due to automatic review settings June 20, 2026 06:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread app/models/mp_messages.py Outdated
Comment thread main.py Outdated
Comment thread main.py Outdated
Comment on lines +24 to +100
class MultiplayerTaskAwaiter:
"""Manages the communication process with the spectator server using tasks."""

def __init__(self, default_timeout: float = 10.0):
self._futures: dict[str, asyncio.Future[MultiplayerCallbackMessage]] = {}
self._default_timeout = default_timeout
self._lock = asyncio.Lock()

async def create_task(self, task_id: str | None = None) -> str:
"""Creates a new async task.

Args:
task_id (str | None): Optional task ID to use. Defaults to UUID4 if not given.

Returns:
str: The ID of the new task.
"""
tid = task_id or str(uuid.uuid4())
async with self._lock:
# 清理已存在的同名任务(防御性)
if tid in self._futures and not self._futures[tid].done():
self._futures[tid].cancel()

self._futures[tid] = asyncio.get_running_loop().create_future()
return tid

async def wait_for_result(
self,
task_id: str,
timeout: float | None = None, # noqa: ASYNC109
) -> MultiplayerCallbackMessage:
"""Wait for the result of a specific task.

Returns:
MultiplayerCallbackMessage: The callback event message from the spectator server.
If the task runs out of time, return a placeholder message with the timeout prompt.
"""
future = self._futures.get(task_id)
if not future:
raise ValueError(f"Task {task_id} not found")

logger.info(f"Waiting for result of task #{task_id}")

try:
result = await asyncio.wait_for(future, timeout=timeout or self._default_timeout)
logger.info(f"Got result for #{task_id}: {result}")
return result
except TimeoutError:
logger.warning(f"Task {task_id} timed out")
return MultiplayerCallbackMessage(
id="0",
type="TaskResult",
success=False,
message="Server-side request timeout. Please try again later.",
details=MultiplayerCallbackDetails(),
)
finally:
# 清理
async with self._lock:
self._futures.pop(task_id, None)

def resolve_task(self, task_id: str, result: MultiplayerCallbackMessage) -> bool:
"""Sets a specified task as resolved with given callback message.

Args:
task_id (str): The task ID.
result (MultiplayerCallbackMessage): The callback message as a result.

Returns:
bool: `True` if the task was resolved, `False` otherwise.
"""
future = self._futures.get(task_id)
if not future or future.done():
return False

future.set_result(result)
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v2_ipc provides a similar waiter. Consider merging them into a general waiter?

task_awaiter = MultiplayerTaskAwaiter(default_timeout=10.0)


class MultiplayerEventDispatcher:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider RedisSubscriber?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These classes (including the one above, the MultiplayerTaskAwaiter) are still too specific for a general name like this. Renaming could be considered when the generic messager part is extracted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope/room Multiplayer, playlist, spotlight, daily-challenge & matchmaking scope/signalr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(multiplayer): tournament management command support

3 participants