Skip to content

Instantly share code, notes, and snippets.

@AdamG100
Last active September 17, 2025 13:23
Show Gist options
  • Select an option

  • Save AdamG100/ac1906d53194796db44dd86234ff184c to your computer and use it in GitHub Desktop.

Select an option

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
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()
@YawnyNZ
Copy link

YawnyNZ commented Apr 5, 2025

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

import re, clr
clr.AddReference('System')
clr.AddReference('TCAdmin.GameHosting.SDK')
from System.Net import HttpWebRequest
from System.IO import StreamReader
from TCAdmin.GameHosting.SDK.Objects import GameUpdate, GameScript, ServiceEvent

gameID = 193
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)"
        )
    }
]

# Fetch existing updates
updates = GameUpdate.GetUpdates(gameID)

# Fetch version page using .NET HttpWebRequest
try:
    url = "https://www.minecraft.net/en-us/download/server/bedrock"
    request = HttpWebRequest.Create(url)
    request.UserAgent = "Mozilla/5.0"
    response = request.GetResponse()
    stream = response.GetResponseStream()
    reader = StreamReader(stream)
    html = reader.ReadToEnd()
except Exception as ex:
    Script.WriteToConsole("Failed to fetch update page: {}".format(str(ex)))
    raise

# Extract versions from the HTML
regex_pattern = r'https://www\.minecraft\.net/bedrockdedicatedserver/bin-win/bedrock-server-(?P<version>[\d\.]+)\.zip'
matches = re.findall(regex_pattern, html)

if len(matches) > 0:
    for version in matches:
        release = ''  # No preview flag in this format

        # Generate view order
        parts = version.split('.')
        for i in range(4):
            parts[i] = parts[i].zfill(3)
        view_order = -int(''.join(parts))

        # Check if update already exists
        skip = False
        for gameUpdate in updates:
            if gameUpdate.Name == "Bedrock Edition " + version:
                Script.WriteToConsole('Skipping version {}'.format(version))
                skip = True
                break

        if not skip:
            update = GameUpdate()
            update.Name = "Bedrock Edition " + version
            update.Comments = ""
            update.ImageUrl = "https://www.minecraft.net/etc.clientlibs/minecraft/clientlibs/main/resources/img/header/logo.png"
            update.GameId = gameID
            update.GroupName = "Minecraft: Bedrock Edition"
            update.ExtractPath = "/"
            update.WindowsFileName = "https://www.minecraft.net/bedrockdedicatedserver/bin-win/bedrock-server-{}.zip".format(version)
            update.LinuxFileName = "https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-{}.zip".format(version)
            update.Reinstallable = True
            update.DefaultInstall = False
            update.UserAccess = True
            update.SubAdminAccess = True
            update.ResellerAccess = True
            update.ViewOrder = view_order
            update.GenerateKey()
            update.Save()
            Script.WriteToConsole("Added update for Minecraft: Bedrock Edition v{}".format(version))

            # Attach the event scripts
            for script in scripts:
                updateScript = GameScript()
                updateScript.Description = script["Name"]
                updateScript.ScriptContents = script["Contents"]
                updateScript.ServiceEvent = script["Event"]
                updateScript.ExecuteAsServiceUser = False
                updateScript.ExecuteInPopup = False
                updateScript.ExecutionOrder = 0
                updateScript.GameId = gameID
                updateScript.IgnoreErrors = False
                updateScript.OperatingSystem = 0  # 0 = Any
                updateScript.PromptVariables = False
                updateScript.UserAccess = True
                updateScript.SubAdminAccess = True
                updateScript.ResellerAccess = True
                updateScript.ScriptEngineId = 1  # IronPython
                updateScript.UpdateId = update.UpdateId
                updateScript.GenerateKey()
                updateScript.Save()

            # Update latest pointer if needed
            for existing in updates:
                if 'Latest Bedrock Update' in existing.Notes:
                    Script.WriteToConsole('Changing the links for the latest Bedrock update')
                    existing.WindowsFileName = "https://www.minecraft.net/bedrockdedicatedserver/bin-win/bedrock-server-{}.zip".format(version)
                    existing.LinuxFileName = "https://www.minecraft.net/bedrockdedicatedserver/bin-linux/bedrock-server-{}.zip".format(version)
                    existing.Save()
else:
    Script.WriteToConsole("No versions found.")

@AdamG100
Copy link
Author

AdamG100 commented Aug 8, 2025

updated script to use https://mcjarfiles.com/ API instead of scraping data from wiki

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment