Browse Source

!mp start and !mp abort

master
Giuseppe Guerra 10 months ago
parent
commit
14e982b129
Signed by: Nyo <to@nyo.zz.mu> GPG Key ID: 0DD8D9F00EC3C78D
6 changed files with 103 additions and 7 deletions
  1. +6
    -2
      events.py
  2. +1
    -2
      internal_api/handlers.py
  3. +75
    -1
      plugins/multiplayer.py
  4. +1
    -2
      pubsub/handlers/message.py
  5. +12
    -0
      singletons/bot.py
  6. +8
    -0
      utils/rippleapi.py

+ 6
- 2
events.py View File

@@ -4,6 +4,7 @@ import asyncio

from constants.events import WsEvent
from singletons.bot import Bot
from utils.rippleapi import BanchoClientType
from ws.client import LoginFailedError
from ws.messages import WsSubscribe, WsAuth, WsJoinChatChannel, WsPong, WsChatMessage

@@ -70,6 +71,9 @@ async def ping():
async def on_message(sender: Dict[str, Any], recipient: Dict[str, Any], pm: bool, message: str, **kwargs) -> None:
is_command = message.startswith(bot.command_prefix)
is_action = message.startswith("\x01ACTION")
if sender["type"] == BanchoClientType.FAKE:
# Do not process messages by fake Foka
return
bot.logger.debug(f"{sender['username']}{sender['api_identifier']}: {message} (cmd:{is_command}, act:{is_action})")
# nick = sender["username"]
if sender["username"].lower() == bot.nickname.lower() or (not is_command and not is_action):
@@ -91,7 +95,7 @@ async def on_message(sender: Dict[str, Any], recipient: Dict[str, Any], pm: bool
if type(result) not in (tuple, list):
result = (result,)
for x in result:
bot.client.send(WsChatMessage(x, final_recipient))
bot.send_message(x, final_recipient)


@bot.client.on("disconnected")
@@ -117,7 +121,7 @@ async def on_disconnect(*args, **kwargs):
"""
await bot.client.start()
await bot.client.wait("ready")
bot.client.send(WsChatMessage("Reconnected.", "#admin"))
bot.send_message("Reconnected.", "#admin")

bot.reset()
bot.reconnecting = True


+ 1
- 2
internal_api/handlers.py View File

@@ -4,7 +4,6 @@ from aiohttp import web

from singletons.bot import Bot
from singletons.config import Config
from ws.messages import WsChatMessage


class FokaAPIError(Exception):
@@ -22,7 +21,7 @@ async def send_message(request):
request_data = await request.json()
if "message" not in request_data or "target" not in request_data:
raise FokaAPIError(400, "Missing required arguments.")
Bot().client.send(WsChatMessage(request_data["message"], request_data["target"]))
Bot().send_message(request_data["message"], request_data["target"])
resp = {"code": 200, "message": "ok"}
except FokaAPIError as e:
resp = {"code": e.status, "message": e.message}


+ 75
- 1
plugins/multiplayer.py View File

@@ -1,8 +1,12 @@
from typing import Optional, Dict, Any, Callable

import asyncio

import logging
from schema import Schema, Use, And

import plugins.base
import utils
from plugins.base import Arg
from constants.privileges import Privileges
from singletons.bot import Bot
@@ -15,7 +19,7 @@ def resolve_mp(f: Callable) -> Callable:
async def wrapper(*, recipient: Dict[str, Any], **kwargs):
assert recipient["display_name"] == "#multiplayer"
match_id = int(recipient["name"].split("_")[1])
return await f(match_id=match_id, **kwargs)
return await f(match_id=match_id, recipient=recipient, **kwargs)
return wrapper


@@ -100,3 +104,73 @@ async def move(username: str, match_id: int) -> str:
async def clear_host(match_id: int) -> str:
await bot.bancho_api_client.clear_host(match_id)
return f"Host removed."


@bot.command("mp start")
@plugins.base.protected(Privileges.USER_TOURNAMENT_STAFF)
@plugins.base.multiplayer_only
@resolve_mp
@plugins.base.arguments(
Arg("seconds", And(Use(int), lambda x: x >= 0), default=0, optional=True),
Arg("force", And(str, Use(lambda x: x == "force")), default=False, optional=True)
)
async def start(match_id: int, seconds: int, recipient: Dict[str, Any], force: bool) -> str:
async def start_after(timer_seconds: int):
try:
logging.debug("Start after task started")
while timer_seconds > 0:
if timer_seconds % 10 == 0 or timer_seconds < 10:
bot.send_message(f"Match starts in {timer_seconds} seconds.", recipient["name"])
await asyncio.sleep(1)
timer_seconds -= 1
logging.debug("Starting")
try:
await bot.bancho_api_client.start_match(match_id, force=force)
except utils.rippleapi.RippleApiResponseError as e:
if e.data.get("code", None) == 409:
bot.send_message(
"Cannot start the match. There may be not enough players ready, invalid teams or the match"
"may already be in progress. Use '!mp start x force' to start the match anyways.",
recipient["name"]
)
else:
bot.send_message(e.data.get("message", "Unknown API error"), recipient["name"])
else:
bot.send_message("Match started!", recipient["name"])
except asyncio.CancelledError:
bot.send_message("Match timer start cancelled!", recipient["name"])
finally:
# wtf pycharm
bot.match_delayed_start_tasks.pop(match_id, None)
if match_id in bot.match_delayed_start_tasks:
return "This match is starting soon."
logging.debug(f"Seconds: {seconds}")
bot.match_delayed_start_tasks[match_id] = asyncio.ensure_future(start_after(seconds))
if seconds > 0:
# TODO: lock match for real
return f"Match starts in {seconds} seconds. The match has been locked. " \
f"Please don't leave the match during the " \
f"countdown or you might receive a penality."


@bot.command("mp abort")
@plugins.base.protected(Privileges.USER_TOURNAMENT_STAFF)
@plugins.base.multiplayer_only
@resolve_mp
@plugins.base.base
async def abort(match_id: int) -> str:
had_timer = True
try:
bot.match_delayed_start_tasks.pop(match_id).cancel()
except KeyError:
had_timer = False

try:
await bot.bancho_api_client.abort_match(match_id)
return "Match aborted!"
except utils.rippleapi.RippleApiResponseError as e:
# 409 = match not in progress
# and it is prefectly acceptable if we had a timer
# (match not started yet)
if e.data.get("code", None) != 409 or not had_timer:
raise e

+ 1
- 2
pubsub/handlers/message.py View File

@@ -3,11 +3,10 @@ from typing import Dict, Any
import pubsub
from singletons.bot import Bot
from utils.schema import NonEmptyString
from ws.messages import WsChatMessage

bind = Bot().pubsub_binding_manager

@bind.register_pubsub_handler("fokabot:message")
@pubsub.schema({"recipient": NonEmptyString, "message": NonEmptyString})
async def handle(data: Dict[str, Any]) -> None:
Bot().client.send(WsChatMessage(data["message"], data["recipient"]))
Bot().send_message(data["message"], data["recipient"])

+ 12
- 0
singletons/bot.py View File

@@ -3,6 +3,7 @@ import signal

import plugins.base
from ws.client import WsClient
from ws.messages import WsChatMessage

try:
import ujson as json
@@ -81,6 +82,17 @@ class Bot:
self.pubsub_binding_manager: PubSubBindingManager = PubSubBindingManager()

self.login_channels_left = set()
self.match_delayed_start_tasks: Dict[int, asyncio.Task] = {}

def send_message(self, message: str, recipient: str) -> None:
"""
Shorthand to send a message

:param message:
:param recipient:
:return:
"""
self.client.send(WsChatMessage(message, recipient))

@property
def loop(self) -> asyncio.AbstractEventLoop:


+ 8
- 0
utils/rippleapi.py View File

@@ -229,6 +229,8 @@ class BanchoClientType(IntEnum):
"""
OSU = 0
IRC = auto()
WS = auto()
FAKE = auto()


class BanchoApiClient(RippleApiBaseClient):
@@ -402,6 +404,12 @@ class BanchoApiClient(RippleApiBaseClient):
async def clear_host(self, match_id: int) -> None:
await self.transfer_host(match_id, api_identifier=None)

async def start_match(self, match_id: int, force: Optional[bool] = False) -> None:
await self._request(f"multiplayer/{match_id}/start", "POST", {"force": force})

async def abort_match(self, match_id: int, force: Optional[bool] = False) -> None:
await self._request(f"multiplayer/{match_id}/abort", "POST")

async def get_all_channels(self) -> List[Dict[str, Any]]:
return (await self._request("chat_channels")).get("channels")



Loading…
Cancel
Save