diff --git a/config.py b/config.py index 38f7a70..6465f00 100644 --- a/config.py +++ b/config.py @@ -39,46 +39,4 @@ MATRIX_ROOM = at(['matrix', 'room_id']) or "!channel_id:example.org" SERVER_IP = os.getenv('SERVER_ADDRESS') or 'unknown ip' -# Matrix users who are allowed to run OP commands in Minecraft through Matrix -MC_ADMINS = [ - "@bramvdnheuvel:nltrix.net", # Bram on Matrix (example, feel free to remove) - "@_discord_625632515314548736:t2bot.io" # Bram on Discord (example, feel free to remove) - # Your username on Matrix -] -if os.getenv('MATRIX_ADMINS') is not None: - MC_ADMINS = os.getenv('MATRIX_ADMINS').split(',') - -make_bool = lambda os_value, default_value : default_value if not os_value else ( - False if os_value.lower() == 'false' else True -) - -SERVER_SETTINGS = { - 'level-name': os.getenv('WORLD') or 'world', - - # Server settings - 'port' : 25565 if os.getenv('PORT') == None else int(os.getenv('PORT')), - 'query.port' : 25565 if os.getenv('PORT') == None else int(os.getenv('PORT')), - 'max-players' : 7 if os.getenv('MAX_PLAYERS') == None else int(os.getenv('MAX_PLAYERS')), - - # Server temperature >:3 - 'view-distance' : 10 if os.getenv('RENDER_DISTANCE') == None else int(os.getenv('RENDER_DISTANCE')), - 'enable-command-block' : make_bool(os.getenv('COMMAND_BLOCKS'), True), - - # Environment - 'allow-nether' : make_bool(os.getenv('NETHER'), True), - 'spawn-npcs' : make_bool(os.getenv('NPCS'), True), - 'spawn-animals' : make_bool(os.getenv('ANIMALS'), True), - 'spawn-monsters' : make_bool(os.getenv('MONSTERS'), True), - - # Gamemode - 'pvp' : make_bool(os.getenv('PVP'), True), - 'gamemode' : os.getenv('GAMEMODE') or 'survival', - 'difficulty': os.getenv('DIFFICULTY') or 'medium', - 'hardcore' : make_bool(os.getenv('HARDCORE'), False), - - # Grief protection - 'online-mode' : make_bool(os.getenv('VERIFY_ACCOUNTS'), True), - 'white-list' : make_bool(os.getenv('WHITELIST'), True), - 'enforce-whitelist' : make_bool(os.getenv('WHITELIST'), True), - 'spawn-protection' : 16 if os.getenv('SPAWN_PROTECTION') == None else os.getenv('SPAWN_PROTECTION'), -} +MATRIX_ADMINS = at(['matrix', 'mc-admins']) or [] diff --git a/config.yaml b/config.yaml index d15db24..73eed00 100644 --- a/config.yaml +++ b/config.yaml @@ -1,3 +1,12 @@ +config: + # How much memory the Minecraft server is allowed to take up + # Number in megabytes. Defaults to 1024. (= 1 GB) + ram: 1024 + + # File location of the server jar. + # To be downloaded at: https://www.minecraft.net/en-us/download/server + server_jar: server.jar + # Matrix bridge configurations matrix: # Homeserver URL @@ -14,14 +23,16 @@ matrix: server_address: unknown ip # List of Matrix users that can send commands to the bridge. - # When a message starts with a slash, (/) the bridge will interpret it as a - # Minecraft command and will put that as a command into the console. + # When a message starts with an exclamation mark, (!) the bridge will + # interpret it as a Minecraft command and will put that as a command + # into the console. mc-admins: - "@bram:matrix.directory" # - "@alice:example.org" # - # When users have bridged from other platforms, you can indicate accordingly. + # When multiple RegEx strings apply, all are included. alternative_platforms: Discord: match: "@_?discord_\d+:.+" diff --git a/main.py b/main.py index 1583264..651aef6 100644 --- a/main.py +++ b/main.py @@ -20,11 +20,6 @@ async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None: if int(event.server_timestamp) < STARTUP_TIME: return - # Determine platform - platform = 'Matrix' - if re.fullmatch(r"@_discord_\d+:t2bot\.io", event.sender): - platform = 'Discord' - # Determine how to display username name = room.users[event.sender].display_name for user in room.users: @@ -35,11 +30,7 @@ async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None: name = room.users[event.sender].disambiguated_name break - mc_wrapper.reply_to_mc( - event.body, name, - admin=(event.sender in config.MC_ADMINS), - platform=platform - ) + mc_wrapper.reply_to_mc(event.body, name, event.sender) client.add_event_callback(message_callback, RoomMessageText) @@ -47,7 +38,7 @@ async def activate_client() -> None: print(await client.login(config.MATRIX_PASSWORD)) await client.room_send( - room_id=config.MC_CHANNEL, + room_id=config.MATRIX_ROOM, message_type="m.room.message", content = { "msgtype": "m.text", @@ -60,8 +51,11 @@ async def activate_client() -> None: async def start(): await asyncio.gather( + # Start the Matrix client activate_client(), - mc_wrapper.start(client, config.MC_CHANNEL) + + # Start the Minecraft subprocess + mc_wrapper.start(client, config.MATRIX_ROOM) ) asyncio.run(start()) \ No newline at end of file diff --git a/mc_wrapper.py b/mc_wrapper.py index 6227527..d12f4ab 100644 --- a/mc_wrapper.py +++ b/mc_wrapper.py @@ -14,6 +14,9 @@ p = Popen(sys.argv[1:], nbsr = NBSR(p.stdout) async def start(client, mc_channel): + """ + Start reading from the Minecraft subprocess. + """ await asyncio.sleep(3) while True: @@ -49,6 +52,12 @@ async def start(client, mc_channel): server_live = False def process_message(sentence : str) -> Union[str, None]: + """ + Process a message that is sent to stdout in the Minecraft terminal. + + If this function deems it relevant, it returns a string that can be + sent to Matrix. + """ global server_live if (match := re.fullmatch( @@ -83,7 +92,8 @@ def process_message(sentence : str) -> Union[str, None]: sentence)): username, = match.groups() return username + " left the Minecraft server", ( - "" + username + " left the Minecraft server") + "" + username + " left the Minecraft server" + ) if (match := re.fullmatch( r"\[[\d:]+\] \[Server thread\/INFO\]: <([A-Za-z0-9_]{3,16})> (.+)", @@ -101,24 +111,39 @@ def process_message(sentence : str) -> Union[str, None]: return None, None -def reply_to_mc(message : str, author : str, - admin : bool = False, platform : str = 'Matrix'): +def reply_to_mc(message : str, display_name : str, sender : str): """ Send something back to the Minecraft terminal. """ - if admin and message.startswith('!'): + if sender in config.MATRIX_ADMINS and message.startswith('!'): p.stdin.write((message[1:] + "\r\n").encode()) else: - msg = [ - "", - dict(text="M", color="red"), - dict(text="D", color="aqua") if platform == 'Discord' else None, - dict(text=f" <{author}> {message}") - ] p.stdin.write( - ("execute as @a run tellraw @s " + json.dumps([m for m in msg if m is not None]) + "\r\n").encode() + ("execute as @a run tellraw @s " + format(sender, display_name, message) + "\r\n").encode() ) p.stdin.flush() +def format(sender : str, display_name : str, message : str) -> str: + """ + Create a string used to format the user's message. + """ + start = [ "", dict(text="M", color="red" ) ] + end = [ dict(text=f" <{display_name}> {message}") ] + + options = config.at(['matrix', 'alternative_platforms']) or {} + + for platform, details in options.items(): + try: + regex = details['match'] + text = details['text'] + color = details['color'] + except KeyError: + print("WARNING: Platform `" + platform + "` is missing some configurations.") + else: + if re.fullmatch(regex, sender): + start.append(dict(text=text, color=color)) + + return json.dumps(start + end) + if __name__ == '__main__': asyncio.run(start()) \ No newline at end of file