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