import argparse import logging import json import re import subprocess import threading from typing import List import message_queue LOG = logging.getLogger(__name__) class ProcessWrapper: """Iterator that spawns a process and yields lines from its stdout.""" def __init__(self, command: List[str]): self.proc = subprocess.Popen( " ".join(command), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True, ) def __iter__(self): return iter(self.proc.stdout.readline, "") def send(self, msg): self.proc.stdin.write(msg) self.proc.stdin.flush() def wait(self): return self.proc.wait() @property def closed(self) -> bool: return self.proc.returncode is not None def send_process_output( process: ProcessWrapper, msg_queue: message_queue.MessageQueue ): log = LOG.getChild("process_output") # "[07:36:28] [Server thread/INFO] [minecraft/DedicatedServer]: test" chat_pattern = re.compile(r"(\[.*\] )?\[(.*)\] \[(.*)\]: <(.*)> (.*)") # "[05:09:54] [Server thread/INFO] [minecraft/DedicatedServer]: khr_ joined the game" login_pattern = re.compile(r"\[.*\] \[(.*)\] \[(.*)\]: (\S*) joined the game") # "[05:12:24] [Server thread/INFO] [minecraft/DedicatedServer]: khr_ left the game" logout_pattern = re.compile(r"\[.*\] \[(.*)\] \[(.*)\]: (\S*) left the game") for line in process: log.info(line.rstrip("\n")) chat_result = chat_pattern.search(line) login_result = login_pattern.search(line) logout_result = logout_pattern.search(line) if chat_result: msg_queue.add( { "user": chat_result.group(4), "action": "chat", "msg": chat_result.group(5).rstrip("\n"), }, ) elif login_result: msg_queue.add({"user": login_result.group(3), "action": "joined"}) elif logout_result: msg_queue.add({"user": logout_result.group(3), "action": "left"}) def relay_queue_input( process: ProcessWrapper, msg_queue: message_queue.MessageQueue ): log = LOG.getChild("relay_input") while not process.closed: try: for message in msg_queue: log.debug(message) if "msg" in message: tellraw_params = { "text": "<{}> {}".format( message["user"], message["msg"] ), "insertion": "/tellraw @p %s", } command = "/tellraw @a {}\n".format( json.dumps(tellraw_params) ) log.debug("forwarding to process: {!r}".format(command)) process.send(command) else: log.debug(message) except Exception as e: log.exception(e) def main(): logging.basicConfig(level=logging.DEBUG) parser = argparse.ArgumentParser() parser.add_argument("--matrix_server", required=True) parser.add_argument("--matrix_server_port", type=int, default=5001) parser.add_argument("command", nargs=argparse.REMAINDER) args = parser.parse_args() LOG.info("Running Minecraft Server Wrapper") wrapper = ProcessWrapper(args.command) queue = message_queue.MessageQueue( host=args.matrix_server, port=args.matrix_server_port, side=message_queue.Side.CLIENT, ) send_worker = threading.Thread( target=send_process_output, args=(wrapper, queue), daemon=True, ) receive_worker = threading.Thread( target=relay_queue_input, args=(wrapper, queue), daemon=True, ) send_worker.start() receive_worker.start() LOG.info("All threads created") send_worker.join() receive_worker.join() queue.close() LOG.info("All threads terminated") return wrapper.wait() if __name__ == "__main__": main()