diff --git a/README.md b/README.md index bb6c14a..e84342f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ export Q3RCON_CLI_PASSWORD="" Usage: q3rcon-cli [OPTIONS] COMMAND ┏━ Subcommands ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ fastrestart Executes a fast restart of the server ┃ +┃ fastrestart Executes a fast restart of the map ┃ ┃ gametype Get or set the current gametype of the server ┃ ┃ hostname Get or set the current hostname of the server ┃ ┃ map Get the current map or change to a new one ┃ diff --git a/src/q3rcon_cli/cli.py b/src/q3rcon_cli/cli.py index c4598dd..1c3ecc1 100644 --- a/src/q3rcon_cli/cli.py +++ b/src/q3rcon_cli/cli.py @@ -4,7 +4,7 @@ from clypi import Command, Spinner, arg from clypi import parsers as cp from typing_extensions import override -from . import console +from . import config, console from .__about__ import __version__ from .commands import ( Fastrestart, @@ -68,6 +68,23 @@ class Q3rconCli(Command): help='Show the version and exit', ) + @override + async def pre_run_hook(self): + if self.subcommand is not None and self.interactive: + console.err.print( + 'Cannot use subcommands in interactive mode.', + style='yellow', + ) + self.print_help() + raise SystemExit(1) + + if self.subcommand: + ( + self.subcommand.timeout, + self.subcommand.fragment_read_timeout, + self.subcommand.interpret, + ) = config.get(self.subcommand.prog().split()[0].lower()) + @override async def run(self): if self.version: @@ -76,8 +93,13 @@ class Q3rconCli(Command): if self.interactive: await self.run_interactive() - else: + return + + if self.subcommand is None: await Status(self.host, self.port, self.password).run() + return + + self.print_help() async def run_interactive(self): print( @@ -85,23 +107,12 @@ class Q3rconCli(Command): clypi.style("'Q'", fg='yellow'), clypi.style('to quit.', fg='blue'), ) - - CMD_CONFIG = { - 'status': (2, 1, False), - 'fast_restart': (3, 1, True), - 'map_restart': (3, 1, True), - 'map': (3, 1, True), - 'map_rotate': (3, 1, True), - } - DEFAULT_TIMEOUT = 2 - DEFAULT_FRAGMENT_READ_TIMEOUT = 0.25 while command := input(clypi.style('cmd: ', fg='green')): if command.lower() == 'q': break - timeout, fragment_read_timeout, interpret = CMD_CONFIG.get( - command.split()[0].lower(), - (DEFAULT_TIMEOUT, DEFAULT_FRAGMENT_READ_TIMEOUT, False), + timeout, fragment_read_timeout, interpret = config.get( + command.split()[0].lower() ) async with Spinner(f"Sending command: '{command}'", suffix='...'): diff --git a/src/q3rcon_cli/commands/fastrestart.py b/src/q3rcon_cli/commands/fastrestart.py index 4554b60..a6cfa9d 100644 --- a/src/q3rcon_cli/commands/fastrestart.py +++ b/src/q3rcon_cli/commands/fastrestart.py @@ -6,7 +6,7 @@ from q3rcon_cli import console class Fastrestart(Command): - """Executes a fast restart of the server.""" + """Executes a fast restart of the map.""" host: str = arg(inherited=True) port: int = arg(inherited=True) @@ -15,7 +15,15 @@ class Fastrestart(Command): @override async def run(self): async with Spinner('Executing fast restart', suffix='...'): - async with Client(self.host, self.port, self.password) as client: - response = await client.send_command('fast_restart', interpret=True) + async with Client( + self.host, + self.port, + self.password, + timeout=self.timeout, + fragment_read_timeout=self.fragment_read_timeout, + ) as client: + response = await client.send_command( + 'fast_restart', interpret=self.interpret + ) console.out.print_response(response) diff --git a/src/q3rcon_cli/commands/gametype.py b/src/q3rcon_cli/commands/gametype.py index 0579e62..1bb6009 100644 --- a/src/q3rcon_cli/commands/gametype.py +++ b/src/q3rcon_cli/commands/gametype.py @@ -4,6 +4,8 @@ from typing_extensions import override from q3rcon_cli import console +from .maprestart import Maprestart + class Gametype(Command): """Get or set the current gametype of the server.""" @@ -35,11 +37,7 @@ class Gametype(Command): await client.send_command(f'g_gametype {self.new_gametype}') if self.force: - async with Spinner('Forcing gametype change', suffix='...'): - async with Client(self.host, self.port, self.password) as client: - client.timeout = 3 - client.fragment_read_timeout = 1 - await client.send_command('map_restart') + await Maprestart(self.host, self.port, self.password).configure_and_run() console.out.print( f'Gametype changed successfully to {self.new_gametype}.', style='green' diff --git a/src/q3rcon_cli/commands/map.py b/src/q3rcon_cli/commands/map.py index 2cb6efd..9ec2e27 100644 --- a/src/q3rcon_cli/commands/map.py +++ b/src/q3rcon_cli/commands/map.py @@ -27,9 +27,16 @@ class Map(Command): async with Spinner('Changing map', suffix='...'): async with Client( - self.host, self.port, self.password, fragment_read_timeout=1 + self.host, + self.port, + self.password, + timeout=self.timeout, + fragment_read_timeout=self.fragment_read_timeout, ) as client: - await client.send_command(f'map mp_{self.new_map.removeprefix("mp_")}') + await client.send_command( + f'map mp_{self.new_map.removeprefix("mp_")}', + interpret=self.interpret, + ) console.out.print( f'Map changed to {self.new_map.removeprefix("mp_")}', style='green' diff --git a/src/q3rcon_cli/commands/maprestart.py b/src/q3rcon_cli/commands/maprestart.py index 38c36c1..b85e456 100644 --- a/src/q3rcon_cli/commands/maprestart.py +++ b/src/q3rcon_cli/commands/maprestart.py @@ -2,7 +2,7 @@ from aioq3rcon import Client from clypi import Command, Spinner, arg from typing_extensions import override -from q3rcon_cli import console +from q3rcon_cli import config, console class Maprestart(Command): @@ -12,12 +12,32 @@ class Maprestart(Command): port: int = arg(inherited=True) password: str = arg(inherited=True) + async def configure_and_run(self): + """Configures the command with the appropriate configuration and runs it. + + + This method is used if we invoke the maprestart command from another command (e.g. gametype), + since the pre_run_hook is not called in that case. + """ + ( + self.timeout, + self.fragment_read_timeout, + self.interpret, + ) = config.get(self.prog().split()[0].lower()) + await self.run() + @override async def run(self): async with Spinner('Restarting map', suffix='...'): async with Client( - self.host, self.port, self.password, timeout=3, fragment_read_timeout=1 + self.host, + self.port, + self.password, + timeout=self.timeout, + fragment_read_timeout=self.fragment_read_timeout, ) as client: - response = await client.send_command('map_restart', interpret=True) + response = await client.send_command( + 'map_restart', interpret=self.interpret + ) console.out.print_response(response) diff --git a/src/q3rcon_cli/commands/maprotate.py b/src/q3rcon_cli/commands/maprotate.py index fb1c9d7..e789339 100644 --- a/src/q3rcon_cli/commands/maprotate.py +++ b/src/q3rcon_cli/commands/maprotate.py @@ -19,9 +19,11 @@ class Maprotate(Command): self.host, self.port, self.password, - timeout=3, - fragment_read_timeout=1, + timeout=self.timeout, + fragment_read_timeout=self.fragment_read_timeout, ) as client: - response = await client.send_command('map_rotate', interpret=True) + response = await client.send_command( + 'map_rotate', interpret=self.interpret + ) console.out.print_response(response) diff --git a/src/q3rcon_cli/commands/status.py b/src/q3rcon_cli/commands/status.py index 3f33415..235df01 100644 --- a/src/q3rcon_cli/commands/status.py +++ b/src/q3rcon_cli/commands/status.py @@ -15,9 +15,7 @@ class Status(Command): @override async def run(self): async with Spinner('Fetching status', suffix='...'): - async with Client( - self.host, self.port, self.password, fragment_read_timeout=1 - ) as client: + async with Client(self.host, self.port, self.password) as client: response = await client.send_command('status') console.out.print_status(response) diff --git a/src/q3rcon_cli/config.py b/src/q3rcon_cli/config.py new file mode 100644 index 0000000..e856164 --- /dev/null +++ b/src/q3rcon_cli/config.py @@ -0,0 +1,35 @@ +from collections import UserDict + + +class Config(UserDict): + DEFAULT_TIMEOUT: int = 2 + DEFAULT_FRAGMENT_READ_TIMEOUT: float = 0.25 + + def __init__(self): + self.data = { + 'status': (2, 0.25, False), + 'fast_restart': (3, 1, True), + 'map_restart': (3, 1, True), + 'map': (3, 1, True), + 'map_rotate': (3, 1, True), + } + + def __getitem__(self, key): + return self.data.get( + key, (self.DEFAULT_TIMEOUT, self.DEFAULT_FRAGMENT_READ_TIMEOUT, False) + ) + + +_config = Config() + + +def get(key: str): + match key: + case 'fast_restart' | 'fastrestart': + return _config['fast_restart'] + case 'map_restart' | 'maprestart': + return _config['map_restart'] + case 'map_rotate' | 'maprotate': + return _config['map_rotate'] + case _: + return _config[key]