Compare commits
No commits in common. "b3ab94ef7867f559d7ab5429ec84bf8b7b971a05" and "947b454faff11bbd777a63576aaa9ba87818aef8" have entirely different histories.
b3ab94ef78
...
947b454faf
|
@ -1,7 +1,3 @@
|
||||||
# Minecraft-specific
|
|
||||||
server.jar
|
|
||||||
world/
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -7,14 +7,13 @@ WORKDIR /usr/src/app
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# # Prepare MC server
|
# Prepare MC server
|
||||||
# COPY server.jar ./
|
COPY server.jar ./
|
||||||
# RUN java -jar server.jar --nogui
|
RUN java -jar server.jar --nogui
|
||||||
|
|
||||||
COPY src/ ./src/
|
COPY . .
|
||||||
COPY main.py ./
|
|
||||||
|
|
||||||
# Buffer Python's stdout for debugging during runtime
|
# Buffer Python's stdout for debugging during runtime
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
CMD ["python", "main.py"]
|
CMD ["python", "main.py", "java", "-Xmx1024M", "-Xms1024M", "-jar", "server.jar", "nogui"]
|
||||||
|
|
41
README.md
41
README.md
|
@ -6,36 +6,25 @@ This way, communication is enabled between a Minecraft server and a Matrix room.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Create a Docker image using the following command:
|
To create a Docker image, go to this folder and run the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker build -t matrix-mc-server ./
|
docker build -t matrix-mc-server ./
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Download the [latest Minecraft server jar file](https://www.minecraft.net/en-us/download/server).
|
Then, once a Docker image has been created, you can run the following command to run a Minecraft server. In `config.py`, you can find additional environment variables you can insert to alter settings on the Minecraft server.
|
||||||
|
|
||||||
3. Open `config.yaml` and adjust it to however you like.
|
|
||||||
|
|
||||||
**NOTE:** Make sure to set EULA to `true` otherwise the server will refuse to start.
|
|
||||||
|
|
||||||
4. Start the Docker container. You will need a few volumes:
|
|
||||||
|
|
||||||
- `/usr/src/app/config.yaml` - Location of your custom `config.yaml`
|
|
||||||
- `/usr/src/app/whitelist.json` - Location of your whitelist, if any.
|
|
||||||
- `/usr/src/app/world/` - Folder of your custom Minecraft world to load
|
|
||||||
- `/usr/src/app/server.jar` - (Default) location of the server jar file. Can be changed in `config.yaml`.
|
|
||||||
|
|
||||||
For example, your Docker compose file could look like the following:
|
|
||||||
|
|
||||||
```docker-compose
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
matrix-mc-server:
|
|
||||||
image: matrix-mc-server:latest
|
|
||||||
volumes:
|
|
||||||
- <your config>:/usr/src/app/config.yaml
|
|
||||||
- <your whitelist>:/usr/src/app/whitelist.json
|
|
||||||
- <your world folder>:/usr/src/app/world
|
|
||||||
- <your server jar file>:/usr/src/app/server.jar
|
|
||||||
```
|
```
|
||||||
|
docker run --name mc-server \
|
||||||
|
-p 25565:25565 \
|
||||||
|
-v "<folder in which your store your Minecraft world>":/usr/src/app/world \
|
||||||
|
-e EULA=true \
|
||||||
|
-e MATRIX_HOMESERVER='<your matrix homeserver>' \
|
||||||
|
-e MATRIX_USERNAME='<matrix bridge client username>' \
|
||||||
|
-e MATRIX_PASSWORD='<matrix bridge client password>' \
|
||||||
|
-e MC_CHANNEL='<channel in which you communicate>' \
|
||||||
|
-e SERVER_ADDRESS='<ip address where players can connect to the server>' \
|
||||||
|
mc-bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
This should successfully launch your bridged Minecraft server.
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import os
|
||||||
|
import config
|
||||||
|
|
||||||
|
def rewriter(file_name):
|
||||||
|
"""Create a decorator that rewrites a file based on given settings."""
|
||||||
|
|
||||||
|
def exec(func):
|
||||||
|
"""Rewrite a file"""
|
||||||
|
new_lines = []
|
||||||
|
|
||||||
|
info_to_remember = {}
|
||||||
|
line_no = 0
|
||||||
|
|
||||||
|
with open(file_name, 'r', encoding='utf-8') as open_file:
|
||||||
|
for line in open_file:
|
||||||
|
line_no += 1
|
||||||
|
new_line = func(line, line_no, data=info_to_remember)
|
||||||
|
new_lines.append(new_line)
|
||||||
|
|
||||||
|
with open(file_name, 'w', encoding='utf-8') as write_file:
|
||||||
|
for line in new_lines:
|
||||||
|
write_file.write(line)
|
||||||
|
return exec
|
||||||
|
|
||||||
|
@rewriter('eula.txt')
|
||||||
|
def confirm_eula(line : str, line_no : int, data):
|
||||||
|
"""Confirm the Minecraft EULA"""
|
||||||
|
if not config.EULA:
|
||||||
|
return line
|
||||||
|
else:
|
||||||
|
return line.replace('eula=false', 'eula=true')
|
||||||
|
|
||||||
|
@rewriter('server.properties')
|
||||||
|
def fill_in_server_settings(line : str, line_no : int, data):
|
||||||
|
"""Set up the server based on our chosen properties"""
|
||||||
|
if line.strip().startswith('#'):
|
||||||
|
return line
|
||||||
|
|
||||||
|
key = line.split('=')[0]
|
||||||
|
|
||||||
|
server_settings = config.at(['minecraft']) or {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = server_settings[key]
|
||||||
|
except IndexError:
|
||||||
|
return line
|
||||||
|
else:
|
||||||
|
return key + '=' + str(value) + '\n'
|
|
@ -0,0 +1,84 @@
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
|
with open('config.yaml', 'r') as open_file:
|
||||||
|
SETTINGS = yaml.load(open_file)
|
||||||
|
|
||||||
|
def at(keys : List[str]) -> Optional[Any]:
|
||||||
|
"""
|
||||||
|
Potentially get a value. If it doesn't exist, return None.
|
||||||
|
"""
|
||||||
|
return at_value(keys, SETTINGS)
|
||||||
|
|
||||||
|
def at_value(keys : List[str], value : Any) -> Optional[Any]:
|
||||||
|
try:
|
||||||
|
head, tail = keys[0], keys[1:]
|
||||||
|
except IndexError:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
new_value = value[head]
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return at_value(tail, new_value)
|
||||||
|
|
||||||
|
# EULA
|
||||||
|
EULA = at(['minecraft', 'eula']) or False
|
||||||
|
|
||||||
|
# Minecraft bridge credentials
|
||||||
|
MATRIX_HOMESERVER = at(['matrix', 'homeserver']) or "https://matrix.example.org/"
|
||||||
|
MATRIX_USERNAME = at(['matrix', 'username']) or "@alice:example.org"
|
||||||
|
MATRIX_PASSWORD = at(['matrix', 'password']) or "bridge_password"
|
||||||
|
|
||||||
|
# Matrix bridge room
|
||||||
|
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'),
|
||||||
|
}
|
21
config.yaml
21
config.yaml
|
@ -1,16 +1,3 @@
|
||||||
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
|
|
||||||
|
|
||||||
# Confirm the Minecraft EULA. https://account.mojang.com/documents/minecraft_eula
|
|
||||||
# Defaults to false, but is required to run the server.
|
|
||||||
# eula: true
|
|
||||||
|
|
||||||
# Matrix bridge configurations
|
# Matrix bridge configurations
|
||||||
matrix:
|
matrix:
|
||||||
# Homeserver URL
|
# Homeserver URL
|
||||||
|
@ -27,16 +14,14 @@ matrix:
|
||||||
server_address: unknown ip
|
server_address: unknown ip
|
||||||
|
|
||||||
# List of Matrix users that can send commands to the bridge.
|
# List of Matrix users that can send commands to the bridge.
|
||||||
# When a message starts with an exclamation mark, (!) the bridge will
|
# When a message starts with a slash, (/) the bridge will interpret it as a
|
||||||
# interpret it as a Minecraft command and will put that as a command
|
# Minecraft command and will put that as a command into the console.
|
||||||
# into the console.
|
|
||||||
mc-admins:
|
mc-admins:
|
||||||
- "@bram:matrix.directory"
|
- "@bram:matrix.directory"
|
||||||
# - "@alice:example.org"
|
# - "@alice:example.org"
|
||||||
# -
|
# -
|
||||||
|
|
||||||
# When users have bridged from other platforms, you can indicate accordingly.
|
# When users have bridged from other platforms, you can indicate accordingly.
|
||||||
# When multiple RegEx strings apply, all are included.
|
|
||||||
alternative_platforms:
|
alternative_platforms:
|
||||||
Discord:
|
Discord:
|
||||||
match: "@_?discord_\d+:.+"
|
match: "@_?discord_\d+:.+"
|
||||||
|
@ -49,6 +34,8 @@ matrix:
|
||||||
|
|
||||||
# Settings that directly affect running the Minecraft server.
|
# Settings that directly affect running the Minecraft server.
|
||||||
minecraft:
|
minecraft:
|
||||||
|
# Confirm the Minecraft EULA. Defaults to false.
|
||||||
|
# eula: true
|
||||||
|
|
||||||
# --------------------
|
# --------------------
|
||||||
# MINECRAFT SERVER
|
# MINECRAFT SERVER
|
||||||
|
|
70
main.py
70
main.py
|
@ -1,7 +1,67 @@
|
||||||
import src.mc_wrapper as mc_wrapper
|
|
||||||
import src.mxclient as matrix_client
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
# Start the Minecraft process
|
from nio import AsyncClient, MatrixRoom, RoomMessageText
|
||||||
asyncio.run(matrix_client.start())
|
import mc_wrapper
|
||||||
|
|
||||||
|
import config
|
||||||
|
import build_server
|
||||||
|
|
||||||
|
STARTUP_TIME = time.time()
|
||||||
|
|
||||||
|
client = AsyncClient(config.MATRIX_HOMESERVER, config.MATRIX_USERNAME)
|
||||||
|
|
||||||
|
async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None:
|
||||||
|
if room.machine_name != config.MATRIX_ROOM:
|
||||||
|
return
|
||||||
|
if event.sender == client.user_id:
|
||||||
|
return
|
||||||
|
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:
|
||||||
|
if user == event.sender:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if room.users[user].display_name == name:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
client.add_event_callback(message_callback, RoomMessageText)
|
||||||
|
|
||||||
|
|
||||||
|
async def activate_client() -> None:
|
||||||
|
print(await client.login(config.MATRIX_PASSWORD))
|
||||||
|
|
||||||
|
await client.room_send(
|
||||||
|
room_id=config.MC_CHANNEL,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content = {
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "Starting Minecraft-Matrix bridge...",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<strong>Starting Minecraft-Matrix bridge...</strong>"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await client.sync_forever(timeout=30000) # milliseconds
|
||||||
|
|
||||||
|
async def start():
|
||||||
|
await asyncio.gather(
|
||||||
|
activate_client(),
|
||||||
|
mc_wrapper.start(client, config.MC_CHANNEL)
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.run(start())
|
|
@ -1,33 +1,19 @@
|
||||||
"""
|
|
||||||
The mc_wrapper modules handles the process that runs the Minecraft server.
|
|
||||||
|
|
||||||
The server is run in a subprocess, and live communication between the
|
|
||||||
game and the Python script is facilitated.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
import re
|
import re
|
||||||
from src.nbsr import NonBlockingStreamReader as NBSR
|
from nbsr import NonBlockingStreamReader as NBSR
|
||||||
import src.config as config
|
import config
|
||||||
import src.build_server as build
|
|
||||||
|
|
||||||
# Write the appropriate files
|
|
||||||
build.write_eula()
|
|
||||||
build.write_server_properties()
|
|
||||||
|
|
||||||
# run the shell as a subprocess:
|
# run the shell as a subprocess:
|
||||||
p = Popen(config.RUN_COMMAND,
|
p = Popen(sys.argv[1:],
|
||||||
stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = False)
|
stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = False)
|
||||||
# wrap p.stdout with a NonBlockingStreamReader object:
|
# wrap p.stdout with a NonBlockingStreamReader object:
|
||||||
nbsr = NBSR(p.stdout)
|
nbsr = NBSR(p.stdout)
|
||||||
|
|
||||||
async def start(client, mc_channel):
|
async def start(client, mc_channel):
|
||||||
"""
|
|
||||||
Start reading from the Minecraft subprocess.
|
|
||||||
"""
|
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -63,12 +49,6 @@ async def start(client, mc_channel):
|
||||||
server_live = False
|
server_live = False
|
||||||
|
|
||||||
def process_message(sentence : str) -> Union[str, None]:
|
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
|
global server_live
|
||||||
|
|
||||||
if (match := re.fullmatch(
|
if (match := re.fullmatch(
|
||||||
|
@ -103,8 +83,7 @@ def process_message(sentence : str) -> Union[str, None]:
|
||||||
sentence)):
|
sentence)):
|
||||||
username, = match.groups()
|
username, = match.groups()
|
||||||
return username + " left the Minecraft server", (
|
return username + " left the Minecraft server", (
|
||||||
"<strong>" + username + " left the Minecraft server</strong>"
|
"<strong>" + username + " left the Minecraft server</strong>")
|
||||||
)
|
|
||||||
|
|
||||||
if (match := re.fullmatch(
|
if (match := re.fullmatch(
|
||||||
r"\[[\d:]+\] \[Server thread\/INFO\]: <([A-Za-z0-9_]{3,16})> (.+)",
|
r"\[[\d:]+\] \[Server thread\/INFO\]: <([A-Za-z0-9_]{3,16})> (.+)",
|
||||||
|
@ -122,39 +101,24 @@ def process_message(sentence : str) -> Union[str, None]:
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
def reply_to_mc(message : str, display_name : str, sender : str):
|
def reply_to_mc(message : str, author : str,
|
||||||
|
admin : bool = False, platform : str = 'Matrix'):
|
||||||
"""
|
"""
|
||||||
Send something back to the Minecraft terminal.
|
Send something back to the Minecraft terminal.
|
||||||
"""
|
"""
|
||||||
if sender in config.MATRIX_ADMINS and message.startswith('!'):
|
if admin and message.startswith('!'):
|
||||||
p.stdin.write((message[1:] + "\r\n").encode())
|
p.stdin.write((message[1:] + "\r\n").encode())
|
||||||
else:
|
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(
|
p.stdin.write(
|
||||||
("execute as @a run tellraw @s " + format(sender, display_name, message) + "\r\n").encode()
|
("execute as @a run tellraw @s " + json.dumps([m for m in msg if m is not None]) + "\r\n").encode()
|
||||||
)
|
)
|
||||||
p.stdin.flush()
|
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__':
|
if __name__ == '__main__':
|
||||||
asyncio.run(start())
|
asyncio.run(start())
|
|
@ -1,15 +1,3 @@
|
||||||
"""
|
|
||||||
The NBSR module defines a Non-blocking stream reader (NBSR class).
|
|
||||||
|
|
||||||
In short, the Minecraft stdout is a stream of data that doesn't end until
|
|
||||||
the server shuts down. Traditionally, Python would not run any code until
|
|
||||||
the server has shut down and returns its entire output.
|
|
||||||
|
|
||||||
The NBSR class allows us to read from the stream without blocking the entire
|
|
||||||
Python script. We will occasionally ask the NBSR for any updates, and it
|
|
||||||
will give us the latest output, if it exists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from queue import Queue, Empty
|
from queue import Queue, Empty
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
"""
|
|
||||||
This module prepares the necessary files for running the server in the
|
|
||||||
correct configuration.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import src.config as config
|
|
||||||
|
|
||||||
def write_eula():
|
|
||||||
"""
|
|
||||||
Write whether the user accepts to Minecraft's EULA.
|
|
||||||
The server refuses to run unless explicitly accepted.
|
|
||||||
"""
|
|
||||||
with open("eula.txt", 'w') as fp:
|
|
||||||
if config.EULA == True:
|
|
||||||
fp.write("eula=true")
|
|
||||||
else:
|
|
||||||
fp.write("eula=false")
|
|
||||||
|
|
||||||
def write_server_properties():
|
|
||||||
"""
|
|
||||||
Write the configuration for the Minecraft world.
|
|
||||||
"""
|
|
||||||
with open("server.properties", 'w') as fp:
|
|
||||||
for key, value in config.at(['minecraft']).items():
|
|
||||||
fp.write(f"{key}={value}\n")
|
|
|
@ -1,62 +0,0 @@
|
||||||
"""
|
|
||||||
This module loads and parses the config.yaml file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Any, List, Optional
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
with open('config.yaml', 'r') as open_file:
|
|
||||||
SETTINGS = yaml.load(open_file)
|
|
||||||
|
|
||||||
def at(keys : List[str]) -> Optional[Any]:
|
|
||||||
"""
|
|
||||||
Potentially get a value. If it doesn't exist, return None.
|
|
||||||
"""
|
|
||||||
return at_value(keys, SETTINGS)
|
|
||||||
|
|
||||||
def at_value(keys : List[str], value : Any) -> Optional[Any]:
|
|
||||||
try:
|
|
||||||
head, tail = keys[0], keys[1:]
|
|
||||||
except IndexError:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
new_value = value[head]
|
|
||||||
except TypeError:
|
|
||||||
return None
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return at_value(tail, new_value)
|
|
||||||
|
|
||||||
# EULA
|
|
||||||
EULA = at(['config', 'eula']) or False
|
|
||||||
|
|
||||||
# Minecraft bridge credentials
|
|
||||||
MATRIX_HOMESERVER = at(['matrix', 'homeserver']) or "https://matrix.example.org/"
|
|
||||||
MATRIX_USERNAME = at(['matrix', 'username']) or "@alice:example.org"
|
|
||||||
MATRIX_PASSWORD = at(['matrix', 'password']) or "bridge_password"
|
|
||||||
|
|
||||||
# Matrix bridge room
|
|
||||||
MATRIX_ROOM = at(['matrix', 'room_id']) or "!channel_id:example.org"
|
|
||||||
|
|
||||||
SERVER_IP = at(['matrix', 'server_address']) or 'unknown ip'
|
|
||||||
|
|
||||||
MATRIX_ADMINS = at(['matrix', 'mc-admins']) or []
|
|
||||||
|
|
||||||
try:
|
|
||||||
RAM_SIZE = int(at(['config', 'ram']))
|
|
||||||
except TypeError:
|
|
||||||
RAM_SIZE = 1024
|
|
||||||
except ValueError:
|
|
||||||
RAM_SIZE = 1024
|
|
||||||
|
|
||||||
SERVER_JAR_LOCATION = at(['config', 'server_jar']) or 'server.jar'
|
|
||||||
|
|
||||||
RUN_COMMAND = [
|
|
||||||
'java',
|
|
||||||
f'-Xmx{RAM_SIZE}M',
|
|
||||||
f'-Xms{RAM_SIZE}M',
|
|
||||||
'-jar', SERVER_JAR_LOCATION,
|
|
||||||
'nogui'
|
|
||||||
]
|
|
|
@ -1,61 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
|
|
||||||
from nio import AsyncClient, MatrixRoom, RoomMessageText
|
|
||||||
import src.mc_wrapper as mc_wrapper
|
|
||||||
|
|
||||||
import src.config as config
|
|
||||||
|
|
||||||
STARTUP_TIME = time.time()
|
|
||||||
|
|
||||||
client = AsyncClient(config.MATRIX_HOMESERVER, config.MATRIX_USERNAME)
|
|
||||||
|
|
||||||
async def message_callback(room: MatrixRoom, event: RoomMessageText) -> None:
|
|
||||||
if room.machine_name != config.MATRIX_ROOM:
|
|
||||||
return
|
|
||||||
if event.sender == client.user_id:
|
|
||||||
return
|
|
||||||
if int(event.server_timestamp) < STARTUP_TIME:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Determine how to display username
|
|
||||||
name = room.users[event.sender].display_name
|
|
||||||
for user in room.users:
|
|
||||||
if user == event.sender:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if room.users[user].display_name == name:
|
|
||||||
name = room.users[event.sender].disambiguated_name
|
|
||||||
break
|
|
||||||
|
|
||||||
mc_wrapper.reply_to_mc(event.body, name, event.sender)
|
|
||||||
client.add_event_callback(message_callback, RoomMessageText)
|
|
||||||
|
|
||||||
|
|
||||||
async def activate_client() -> None:
|
|
||||||
print(await client.login(config.MATRIX_PASSWORD))
|
|
||||||
|
|
||||||
await client.room_send(
|
|
||||||
room_id=config.MATRIX_ROOM,
|
|
||||||
message_type="m.room.message",
|
|
||||||
content = {
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"body": "Starting Minecraft-Matrix bridge...",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<strong>Starting Minecraft-Matrix bridge...</strong>"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await client.sync_forever(timeout=30000) # milliseconds
|
|
||||||
|
|
||||||
async def start():
|
|
||||||
await asyncio.gather(
|
|
||||||
# Start the Matrix client
|
|
||||||
activate_client(),
|
|
||||||
|
|
||||||
# Start the Minecraft subprocess
|
|
||||||
mc_wrapper.start(client, config.MATRIX_ROOM)
|
|
||||||
)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
asyncio.run(start())
|
|
Loading…
Reference in New Issue