Files
WiRCON/utils.py
T
2026-09-05 11:32:37 -05:00

59 lines
2.9 KiB
Python

import argparse
import readline
from importlib.util import spec_from_file_location, module_from_spec
def parse_args():
parser = argparse.ArgumentParser(
prog="Minecraft RCON Client",
description="A simple RCON client for remotely controlling a Minecraft server. Can be used interactively or to run prespecified commands. Also supports running arbitrary Python scripts for more complex logic.")
parser.add_argument("-H", "--host", help="The hostname of the RCON server.")
parser.add_argument("-P", "--port", help="The port number of the RCON server. Default is 25575 if omitted and -H is specified. ", type=int)
parser.add_argument("-p", "--password", help="The password to the RCON server.")
execute_arg_group = parser.add_mutually_exclusive_group()
execute_arg_group.add_argument("-e", "--execute-command", help="Send a single command on the RCON server. Can be passed multiple times to run multiple commands. Does not abort on server-side command failures. Requires -H and -p to be specified. Mutually exclusive with -E.", metavar="COMMAND", action='append')
execute_arg_group.add_argument("-E", "--execute-file", help="Execute a Python file. Requires -H and -p to be specified. Mutually exclusive with -e.", metavar="FILEPATH")
parser.add_argument("--echo", help="Echo a command before sending it. Does nothing when neither -e nor -E are not present.", action="store_true")
parser.add_argument("--socket-timeout", help="Set the timeout for the socket when waiting to receive data. Default 30 seconds.", type=float, metavar="TIMEOUT_SECONDS")
args = parser.parse_args()
if (args.execute_command or args.execute_file) and not (args.host and args.password):
parser.error("-e/-E require -H and -p to be specified")
return args
def safe_input(prompt: str) -> str:
"""Like `input()`, but Ctrl-C prints a fresh prompt and Ctrl-D cleanly exits"""
while True:
try:
return input(prompt)
except KeyboardInterrupt:
print('^C')
except EOFError:
print()
exit(0)
def load_script(script_path: str):
# three stages to importing here: find the thing to import, initialize an empty module, and execute the top-level code in that module
# here, we're essentially deciding which tool to use to read the script
spec = spec_from_file_location("user_script", script_path)
if spec is None:
raise ValueError(f"{script_path} is not a valid Python file path")
# initialize an empty "box" that we'll populate with all of our code
module = module_from_spec(spec)
# execute the top-level code (function definitions, global variables, etc.)
spec.loader.exec_module(module) # pyright: ignore
if not hasattr(module, "run"):
raise ValueError(f"Provided script {script_path} must define a run(sock) function")
return module