diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71b8b4b6..66552821 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,12 @@
# Changelog
## --- [4.2.3] - 2023/TBD
### New features
-TBD
+- Use Papermc Group's API for `paper` & `folia` builds in server builder ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/688))
### Bug fixes
-TBD
+- Fix bukkit and downstream fork MOTD crash ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/686))
+- Fix bug where invalid server Id leads to stack ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/690))
### Tweaks
-TBD
+- Refactor Forge server initialisation flow for newer versions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/687))
### Lang
TBD
diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py
index 88923194..86e17802 100644
--- a/app/classes/controllers/servers_controller.py
+++ b/app/classes/controllers/servers_controller.py
@@ -462,7 +462,10 @@ class ServersController(metaclass=Singleton):
@staticmethod
def server_id_exists(server_id):
- srv = ServersController().get_server_instance_by_id(server_id)
+ try:
+ srv = ServersController().get_server_instance_by_id(server_id)
+ except ValueError:
+ return False
return srv.stats_helper.server_id_exists()
@staticmethod
diff --git a/app/classes/minecraft/mc_ping.py b/app/classes/minecraft/mc_ping.py
index c5cb9916..72a91351 100644
--- a/app/classes/minecraft/mc_ping.py
+++ b/app/classes/minecraft/mc_ping.py
@@ -12,6 +12,7 @@ from app.classes.minecraft.bedrock_ping import BedrockPing
from app.classes.shared.console import Console
logger = logging.getLogger(__name__)
+MOTD_CODES = ["bold", "italic", "underlined", "strikethrough"]
class Server:
@@ -34,26 +35,27 @@ class Server:
lines = []
description = self.description
+ if "text" in description.keys():
+ lines.append(description["text"])
if "extra" in description.keys():
- for e in description["extra"]:
- # Conversion format code needed only for Java Version
- lines.append(get_code_format("reset"))
- if "bold" in e.keys():
- lines.append(get_code_format("bold"))
- if "italic" in e.keys():
- lines.append(get_code_format("italic"))
- if "underlined" in e.keys():
- lines.append(get_code_format("underlined"))
- if "strikethrough" in e.keys():
- lines.append(get_code_format("strikethrough"))
- if "color" in e.keys():
- lines.append(get_code_format(e["color"]))
- # Then append the text
- if "text" in e.keys():
- if e["text"] == "\n":
- lines.append("§§")
- else:
- lines.append(e["text"])
+ if isinstance(description["extra"], list):
+ for e in description["extra"]:
+ if not isinstance(e, dict):
+ lines.append(e)
+ continue
+ # Conversion format code needed only for Java Version
+ lines.append(get_code_format("reset"))
+ for item in MOTD_CODES:
+ if e.get(item, False):
+ lines.append(get_code_format(item))
+ if "color" in e.keys():
+ lines.append(get_code_format(e["color"]))
+ # Then append the text
+ if "text" in e.keys():
+ if e["text"] == "\n":
+ lines.append("§§")
+ else:
+ lines.append(e["text"])
total_text = " ".join(lines)
self.description = total_text
diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py
index 447cf80b..29675e8d 100644
--- a/app/classes/minecraft/serverjars.py
+++ b/app/classes/minecraft/serverjars.py
@@ -11,12 +11,18 @@ from app.classes.models.server_permissions import PermissionsServers
from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__)
+PAPERJARS = ["paper", "folia"]
class ServerJars:
def __init__(self, helper):
self.helper = helper
self.base_url = "https://serverjars.com"
+ self.paper_base = "https://api.papermc.io"
+
+ @staticmethod
+ def get_paper_jars():
+ return PAPERJARS
def _get_api_result(self, call_url: str):
full_url = f"{self.base_url}{call_url}"
@@ -38,6 +44,40 @@ class ServerJars:
return api_response
+ def get_paper_versions(self, project):
+ try:
+ response = requests.get(
+ f"{self.paper_base}/v2/projects/{project}/", timeout=2
+ )
+ response.raise_for_status()
+ api_data = json.loads(response.content)
+ except Exception as e:
+ logger.error(
+ f"Unable to load https://api.papermc.io/v2/projects/{project}/"
+ f"api due to error: {e}"
+ )
+ return {}
+ versions = api_data.get("versions", [])
+ versions.reverse()
+ return versions
+
+ def get_paper_build(self, project, version):
+ try:
+ response = requests.get(
+ f"{self.paper_base}/v2/projects/{project}/versions/{version}/builds/",
+ timeout=2,
+ )
+ response.raise_for_status()
+ api_data = json.loads(response.content)
+ except Exception as e:
+ logger.error(
+ f"Unable to load https://api.papermc.io/v2/projects/{project}/"
+ f"api due to error: {e}"
+ )
+ return {}
+ build = api_data.get("builds", [])[-1]
+ return build
+
def _read_cache(self):
cache_file = self.helper.serverjar_cache
cache = {}
@@ -95,6 +135,8 @@ class ServerJars:
for j in data["types"].get(s):
versions = self._get_jar_details(j, s)
data["types"][s].update({j: versions})
+ for item in PAPERJARS:
+ data["types"]["servers"][item] = self.get_paper_versions(item)
# save our cache
try:
with open(cache_file, "w", encoding="utf-8") as f:
@@ -133,6 +175,8 @@ class ServerJars:
for j in data["types"].get(s):
versions = self._get_jar_details(j, s)
data["types"][s].update({j: versions})
+ for item in PAPERJARS:
+ data["types"]["servers"][item] = self.get_paper_versions(item)
# save our cache
try:
with open(cache_file, "w", encoding="utf-8") as f:
@@ -171,7 +215,17 @@ class ServerJars:
def a_download_jar(self, jar, server, version, path, server_id):
# delaying download for server register to finish
time.sleep(3)
- fetch_url = f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}"
+ if server not in PAPERJARS:
+ fetch_url = f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}"
+ else:
+ build = self.get_paper_build(server, version).get("build", None)
+ if not build:
+ return
+ fetch_url = (
+ f"{self.paper_base}/v2/projects"
+ f"/{server}/versions/{version}/builds/{build}/downloads/"
+ f"{server}-{version}-{build}.jar"
+ )
server_users = PermissionsServers.get_server_user_list(server_id)
# We need to make sure the server is registered before
diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py
index ae58d988..216c13e9 100644
--- a/app/classes/shared/main_controller.py
+++ b/app/classes/shared/main_controller.py
@@ -536,10 +536,14 @@ class Controller:
if data["create_type"] == "minecraft_java":
if root_create_data["create_type"] == "download_jar":
# modded update urls from server jars will only update the installer
- if create_data["category"] != "modded":
+ if (
+ create_data["category"] != "modded"
+ and create_data["type"] not in ServerJars.get_paper_jars()
+ ):
server_obj = self.servers.get_server_obj(new_server_id)
url = (
- f"https://serverjars.com/api/fetchJar/{create_data['category']}"
+ "https://serverjars.com/api/fetchJar/"
+ f"{create_data['category']}"
f"/{create_data['type']}/{create_data['version']}"
)
server_obj.executable_update_url = url
diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py
index 8448f656..f413b0f2 100644
--- a/app/classes/shared/server.py
+++ b/app/classes/shared/server.py
@@ -43,6 +43,7 @@ with redirect_stderr(NullWriter()):
from psutil import NoSuchProcess
logger = logging.getLogger(__name__)
+SUCCESSMSG = "SUCCESS! Forge install completed"
def callback(called_func):
@@ -723,10 +724,11 @@ class ServerInstance:
f' -jar "{file_name}" nogui'
)
server_obj.execution_command = execution_command
- Console.debug("SUCCESS! Forge install completed")
+ Console.debug(SUCCESSMSG)
- else:
- # NEW VERSION >= 1.17
+ elif version_major <= 1 and version_minor < 20:
+ # NEW VERSION >= 1.17 and <= 1.20
+ # (no jar file in server dir, only run.bat and run.sh)
run_file_path = ""
if self.helper.is_os_windows():
@@ -770,7 +772,38 @@ class ServerInstance:
f" {server_command[4]}"
)
server_obj.execution_command = execution_command
- Console.debug("SUCCESS! Forge install completed")
+ Console.debug(SUCCESSMSG)
+ else:
+ # NEW VERSION >= 1.20
+ # (executable jar is back in server dir)
+
+ # Retrieving the executable jar filename
+ file_path = glob.glob(
+ f"{server_obj.path}/forge-{version[0][0]}*.jar"
+ )[0]
+ file_name = re.findall(
+ r"(forge-[\-0-9.]+-shim.jar)",
+ file_path,
+ )[0]
+
+ # Let's set the proper server executable
+ server_obj.executable = os.path.join(file_name)
+
+ # Get memory values
+ memory_values = re.findall(
+ r"-Xms([A-Z0-9\.]+) -Xmx([A-Z0-9\.]+)",
+ server_obj.execution_command,
+ )
+
+ # Now lets set up the new run command.
+ # This is based off the run.sh/bat that
+ # Forge uses in 1.17 and <
+ execution_command = (
+ f"java -Xms{memory_values[0][0]} -Xmx{memory_values[0][1]}"
+ f' -jar "{file_name}" nogui'
+ )
+ server_obj.execution_command = execution_command
+ Console.debug(SUCCESSMSG)
except:
logger.debug("Could not find run file.")
# TODO Use regex to get version and rebuild simple execution