class_name UpdateManager extends Node signal update_available(info: Dictionary) const RELEASES_API := "https://git.dominicspringer.de/api/v1/repos/Emporex/Void-Bastion-Releases/releases" const PUBLIC_RELEASE_BASE := "https://git.dominicspringer.de/Emporex/Void-Bastion-Releases/releases/tag/" var _request: HTTPRequest func _ready() -> void: if DisplayServer.get_name() == "headless" or Engine.is_editor_hint(): return _request = HTTPRequest.new() add_child(_request) _request.request_completed.connect(_on_request_completed) call_deferred("check_for_update") func check_for_update() -> void: if _request == null or _request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: return _request.request(RELEASES_API, ["Accept: application/json", "User-Agent: Void-Bastion/%s" % _current_version()]) func _on_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300: return var releases = JSON.parse_string(body.get_string_from_utf8()) if not releases is Array: return for release in releases: if not release is Dictionary or bool(release.get("draft", false)): continue var version := str(release.get("tag_name", "")).trim_prefix("v") if version.is_empty() or not _is_newer_version(version, _current_version()): return var download_url := PUBLIC_RELEASE_BASE + "v" + version var wanted_extension := ".apk" if OS.get_name() == "Android" else ".exe" for asset in release.get("assets", []): if asset is Dictionary and str(asset.get("name", "")).to_lower().ends_with(wanted_extension): download_url = str(asset.get("browser_download_url", download_url)) break update_available.emit({ "version": version, "name": str(release.get("name", version)), "url": download_url }) return func _current_version() -> String: return str(ProjectSettings.get_setting("application/config/version", "0.0.0")) func _is_newer_version(remote: String, local: String) -> bool: var remote_parts := _version_parts(remote) var local_parts := _version_parts(local) for index in 3: if remote_parts[index] != local_parts[index]: return remote_parts[index] > local_parts[index] var remote_suffix: String = remote_parts[3] var local_suffix: String = local_parts[3] if remote_suffix.is_empty(): return not local_suffix.is_empty() if local_suffix.is_empty(): return false return _prerelease_weight(remote_suffix) > _prerelease_weight(local_suffix) func _version_parts(value: String) -> Array: var normalized := value.trim_prefix("v").strip_edges().to_lower() var sections := normalized.split("-", true, 1) var numbers := sections[0].split(".") return [ int(numbers[0]) if numbers.size() > 0 else 0, int(numbers[1]) if numbers.size() > 1 else 0, int(numbers[2]) if numbers.size() > 2 else 0, sections[1] if sections.size() > 1 else "" ] func _prerelease_weight(value: String) -> int: var stage := 0 if value.begins_with("beta"): stage = 100000 elif value.begins_with("rc"): stage = 200000 var sequence := 0 for part in value.split("."): if part.is_valid_int(): sequence = int(part) return stage + sequence