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()