Last active
September 17, 2025 13:23
-
-
Save AdamG100/ac1906d53194796db44dd86234ff184c to your computer and use it in GitHub Desktop.
This script is used in TCAdmin it is designed to fetch all bedrock stable and preview builds from the mcjarfiles API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import re | |
| import json | |
| import gzip | |
| import clr | |
| clr.AddReference('TCAdmin.GameHosting.SDK') | |
| from TCAdmin.GameHosting.SDK.Objects import GameUpdate, GameScript, ServiceEvent | |
| try: | |
| # Python 3 style imports | |
| from urllib.request import Request, urlopen | |
| from urllib.error import URLError, HTTPError | |
| except ImportError: | |
| # Python 2 fallback | |
| from urllib2 import Request, urlopen, URLError, HTTPError | |
| # Configuration | |
| GAME_ID = 193 | |
| API_BASE_URL = 'https://mcjarfiles.com/api' | |
| IMAGE_URL = "https://media.hyperlayer.net/creeper-icon.png" | |
| # Script configurations | |
| SCRIPTS = [ | |
| { | |
| "Name": "Backup server.properties", | |
| "Event": ServiceEvent.BeforeUpdateInstall, | |
| "Contents": ( | |
| "from os import path, rename, remove\n" | |
| "serverproperties = path.join(ThisService.RootDirectory, 'server.properties')\n" | |
| "if path.isfile(serverproperties):\n" | |
| " if path.isfile(serverproperties+'.backup'):\n" | |
| " remove(serverproperties+'.backup')\n" | |
| " rename(serverproperties, serverproperties+'.backup')" | |
| ) | |
| }, | |
| { | |
| "Name": "Delete new server.properties, use old file instead", | |
| "Event": ServiceEvent.AfterUpdateInstall, | |
| "Contents": ( | |
| "from os import path, rename, remove\n" | |
| "serverproperties = path.join(ThisService.RootDirectory, 'server.properties')\n" | |
| "if path.isfile(serverproperties):\n" | |
| " remove(serverproperties)\n" | |
| "rename(serverproperties+'.backup', serverproperties)" | |
| ) | |
| } | |
| ] | |
| def log(message): | |
| """Helper function for consistent logging""" | |
| Script.WriteToConsole(message) | |
| def fetch_versions_from_api(url): | |
| """ | |
| Fetch and decode versions from API, handling gzipped responses | |
| Args: | |
| url (str): The API URL to fetch from | |
| Returns: | |
| list: List of versions or None if error occurred | |
| """ | |
| try: | |
| req = Request(url) | |
| req.add_header('User-Agent', 'TCAdmin-Script/1.0') | |
| req.add_header('Accept-Encoding', 'gzip, deflate') | |
| response = urlopen(req) | |
| # Check if response is gzipped | |
| if response.info().get('Content-Encoding') == 'gzip': | |
| content = gzip.decompress(response.read()).decode('utf-8') | |
| else: | |
| content = response.read().decode('utf-8') | |
| return json.loads(content) | |
| except (URLError, HTTPError, Exception) as e: | |
| log('Error fetching from {}: {}'.format(url, str(e))) | |
| return None | |
| def get_all_versions_for_type(version_type): | |
| """ | |
| Fetch versions for both Windows and Linux platforms | |
| Args: | |
| version_type (str): 'latest' or 'preview' | |
| Returns: | |
| set: Set of all unique versions found | |
| """ | |
| platforms = ['windows', 'linux'] | |
| all_versions = set() | |
| for platform in platforms: | |
| url = '{}/get-versions/bedrock/{}/{}'.format(API_BASE_URL, version_type, platform) | |
| log('Fetching versions from: {}'.format(url)) | |
| versions_list = fetch_versions_from_api(url) | |
| if versions_list and isinstance(versions_list, list): | |
| all_versions.update(versions_list) | |
| log('Found {} versions for {} {}'.format(len(versions_list), version_type, platform)) | |
| else: | |
| log('No versions found for {} {}'.format(version_type, platform)) | |
| return all_versions | |
| def calculate_view_order(version): | |
| """ | |
| Calculate view order for version sorting (newest first) | |
| Args: | |
| version (str): Version string (e.g., "1.19.40.2") | |
| Returns: | |
| int: Negative integer for sorting (larger absolute value = newer) | |
| """ | |
| parts = version.split('.') | |
| # Ensure we have 4 parts for consistent ordering | |
| while len(parts) < 4: | |
| parts.append('0') | |
| # Zero-pad each part to 3 digits for proper string comparison | |
| padded_parts = [part.zfill(3) for part in parts] | |
| return -int(''.join(padded_parts)) | |
| def version_exists(existing_updates, version_type, version): | |
| """ | |
| Check if a version update already exists | |
| Args: | |
| existing_updates: List of existing GameUpdate objects | |
| version_type (str): 'latest' or 'preview' | |
| version (str): Version string | |
| Returns: | |
| bool: True if version already exists | |
| """ | |
| update_name = "Bedrock Edition {} {}".format(version_type.title(), version) | |
| return any(update.Name == update_name for update in existing_updates) | |
| def create_game_update(version_type, version, group_name, view_order): | |
| """ | |
| Create a new GameUpdate object | |
| Args: | |
| version_type (str): 'latest' or 'preview' | |
| version (str): Version string | |
| group_name (str): Update group name | |
| view_order (int): Sort order value | |
| Returns: | |
| GameUpdate: Configured update object | |
| """ | |
| update = GameUpdate() | |
| update.Name = "Bedrock Edition {} {}".format(version_type.title(), version) | |
| update.Comments = "Updates every 1 hour. Data sourced from mcjarfiles.com" | |
| update.ImageUrl = IMAGE_URL | |
| update.GameId = GAME_ID | |
| update.GroupName = group_name | |
| update.ExtractPath = "/" | |
| # Fixed URLs - removed .zip from the base URL, kept it only after the space | |
| update.WindowsFileName = "{}/get-jar/bedrock/{}/windows/{} windows.zip".format(API_BASE_URL, version_type, version) | |
| update.LinuxFileName = "{}/get-jar/bedrock/{}/linux/{} linux.zip".format(API_BASE_URL, version_type, version) | |
| update.Reinstallable = True | |
| update.DefaultInstall = False | |
| update.UserAccess = True | |
| update.SubAdminAccess = True | |
| update.ResellerAccess = True | |
| update.ViewOrder = view_order | |
| update.GenerateKey() | |
| return update | |
| def get_existing_scripts(): | |
| """ | |
| Get existing scripts for the game, with error handling | |
| Returns: | |
| list: List of existing GameScript objects, empty list if error | |
| """ | |
| try: | |
| # Try different methods to get existing scripts | |
| if hasattr(GameScript, 'GetScripts'): | |
| return GameScript.GetScripts(GAME_ID) | |
| elif hasattr(GameScript, 'GetGameScripts'): | |
| return GameScript.GetGameScripts(GAME_ID) | |
| else: | |
| log('Warning: Could not find method to get existing scripts') | |
| return [] | |
| except Exception as e: | |
| log('Error getting existing scripts: {}'.format(str(e))) | |
| return [] | |
| def script_exists_for_update(update_id, script_name, existing_scripts): | |
| """ | |
| Check if a script already exists for an update | |
| Args: | |
| update_id: The update ID to check | |
| script_name (str): Name of the script | |
| existing_scripts (list): List of existing GameScript objects | |
| Returns: | |
| bool: True if script already exists | |
| """ | |
| return any( | |
| script.UpdateId == update_id and script.Description == script_name | |
| for script in existing_scripts | |
| ) | |
| def create_game_script(script_config, update_id): | |
| """ | |
| Create a new GameScript object | |
| Args: | |
| script_config (dict): Script configuration | |
| update_id: ID of the associated update | |
| Returns: | |
| GameScript: Configured script object | |
| """ | |
| script = GameScript() | |
| script.Description = script_config["Name"] | |
| script.ScriptContents = script_config["Contents"] | |
| script.ServiceEvent = script_config["Event"] | |
| script.ExecuteAsServiceUser = False | |
| script.ExecuteInPopup = False | |
| script.ExecutionOrder = 0 | |
| script.GameId = GAME_ID | |
| script.IgnoreErrors = False | |
| script.OperatingSystem = 0 # 0 = Any, 2 = Windows, 4 = Linux | |
| script.PromptVariables = False | |
| script.UserAccess = True | |
| script.SubAdminAccess = True | |
| script.ResellerAccess = True | |
| script.ScriptEngineId = 1 # IronPython | |
| script.UpdateId = update_id | |
| script.GenerateKey() | |
| return script | |
| def add_scripts_to_update(update): | |
| """ | |
| Add custom scripts to an update, avoiding duplicates | |
| Args: | |
| update: GameUpdate object | |
| """ | |
| log('Adding scripts to update: {}'.format(update.Name)) | |
| # Get existing scripts once per update | |
| existing_scripts = get_existing_scripts() | |
| scripts_added = 0 | |
| scripts_skipped = 0 | |
| for script_config in SCRIPTS: | |
| try: | |
| # Check if script already exists for this update | |
| if script_exists_for_update(update.UpdateId, script_config["Name"], existing_scripts): | |
| log("Script '{}' already exists for update {}, skipping".format( | |
| script_config["Name"], update.Name)) | |
| scripts_skipped += 1 | |
| continue | |
| # Create and save the script | |
| script = create_game_script(script_config, update.UpdateId) | |
| script.Save() | |
| log("Added script '{}' for update {}".format(script_config["Name"], update.Name)) | |
| scripts_added += 1 | |
| except Exception as e: | |
| log("Error adding script '{}' for update {}: {}".format( | |
| script_config["Name"], update.Name, str(e))) | |
| log('Added {} scripts to update {}, skipped {} existing scripts'.format( | |
| scripts_added, update.Name, scripts_skipped)) | |
| def update_latest_version(version_type, latest_version): | |
| """ | |
| Update the existing "Latest" update with the newest version | |
| Args: | |
| version_type (str): 'latest' or 'preview' | |
| latest_version (str): The newest version string | |
| """ | |
| updates = GameUpdate.GetUpdates(GAME_ID) | |
| search_text = 'Latest {} Update'.format(version_type.title()) | |
| for update in updates: | |
| if search_text in update.Notes: | |
| log('Updating latest {} version to {}'.format(version_type, latest_version)) | |
| # Fixed URLs - removed .zip from the base URL, kept it only after the space | |
| update.WindowsFileName = "{}/get-jar/bedrock/{}/windows/{} windows.zip".format(API_BASE_URL, version_type, latest_version) | |
| update.LinuxFileName = "{}/get-jar/bedrock/{}/linux/{} linux.zip".format(API_BASE_URL, version_type, latest_version) | |
| update.Save() | |
| return | |
| log('No existing latest {} update found to update'.format(version_type)) | |
| def process_versions(version_type): | |
| """ | |
| Process versions for a given type (latest or preview) | |
| Args: | |
| version_type (str): 'latest' or 'preview' | |
| """ | |
| log('Processing {} versions...'.format(version_type)) | |
| # Get existing updates once at the beginning | |
| existing_updates = GameUpdate.GetUpdates(GAME_ID) | |
| log('Found {} existing updates in database'.format(len(existing_updates))) | |
| # Fetch all versions for this type | |
| all_versions = get_all_versions_for_type(version_type) | |
| if not all_versions: | |
| log('No versions found for {}'.format(version_type)) | |
| return | |
| log('Found {} total versions for {}'.format(len(all_versions), version_type)) | |
| # Sort versions (newest first) | |
| sorted_versions = sorted( | |
| list(all_versions), | |
| key=lambda x: [int(i) for i in x.split('.')], | |
| reverse=True | |
| ) | |
| log('Processing {} sorted versions for {}...'.format(len(sorted_versions), version_type)) | |
| # Determine group name | |
| group_name = "Minecraft: Bedrock Edition {}".format( | |
| "Latest" if version_type == 'latest' else "Preview" | |
| ) | |
| # Process each version | |
| updates_created = 0 | |
| updates_skipped = 0 | |
| for i, version in enumerate(sorted_versions): | |
| log('Processing version {}/{}: {}'.format(i + 1, len(sorted_versions), version)) | |
| if version_exists(existing_updates, version_type, version): | |
| log('Skipping version {} - already exists'.format(version)) | |
| updates_skipped += 1 | |
| continue | |
| try: | |
| # Create and save the update | |
| view_order = calculate_view_order(version) | |
| log('Creating update for version {} with view order {}'.format(version, view_order)) | |
| update = create_game_update(version_type, version, group_name, view_order) | |
| update.Save() | |
| log("Added update for Minecraft: Bedrock Edition {} v{}".format(version_type.title(), version)) | |
| updates_created += 1 | |
| # Add custom scripts (now with duplicate checking) | |
| add_scripts_to_update(update) | |
| except Exception as e: | |
| log('Error creating update for version {}: {}'.format(version, str(e))) | |
| log('Summary for {}: Created {} new updates, skipped {} existing updates'.format( | |
| version_type, updates_created, updates_skipped)) | |
| # Update the latest version reference | |
| if sorted_versions: | |
| log('Updating latest version reference...') | |
| update_latest_version(version_type, sorted_versions[0]) | |
| def main(): | |
| """Main execution function""" | |
| try: | |
| log('=' * 60) | |
| log('Starting Minecraft Bedrock Edition update script...') | |
| log('Game ID: {}'.format(GAME_ID)) | |
| log('API Base URL: {}'.format(API_BASE_URL)) | |
| log('=' * 60) | |
| # Process both version types | |
| log('Beginning processing of latest versions...') | |
| process_versions('latest') | |
| log('Beginning processing of preview versions...') | |
| process_versions('preview') | |
| log('=' * 60) | |
| log('Script execution completed successfully.') | |
| log('=' * 60) | |
| except Exception as e: | |
| log('=' * 60) | |
| log('CRITICAL ERROR during script execution: {}'.format(str(e))) | |
| log('=' * 60) | |
| raise | |
| # Execute main function | |
| if __name__ == '__main__': | |
| main() | |
| else: | |
| # If not running as main module, call main() directly for TCAdmin environment | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fandom wiki is no longer regularly updated.
Unfortunately the other wiki blocks urllib2: https://minecraft.wiki/w/Bedrock_Dedicated_Server
You can use the official site and replace urllib2 with .NET HttpWebRequest