From ef18c902dcaccfd122c36165a1ae49dc8ae9ccde Mon Sep 17 00:00:00 2001 From: DrakiaXYZ <565558+DrakiaXYZ@users.noreply.github.com> Date: Thu, 30 Oct 2025 02:05:34 -0700 Subject: [PATCH] Addd a new ReleaseCheckService to notify users of updates (#670) * Addd a new ReleaseCheckService to notify users of updates - Pulls the latest release from GitHub API to compare the tag against the users current SPT version - Runs at the very end of the startup process to avoid being pushed off screen by mod logging - Only notifies of patch version increments, not major or minor increments - Links the release notes so users can Ctrl+Click to open directly to the upgrade page - Is run on its own thread, and discards all errors, so as to not impact users without an internet connection or ability to access GitHub * Formatting * Use record for the ReleaseInformation class --------- Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> --- .../Services/ReleaseCheckService.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs diff --git a/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs new file mode 100644 index 00000000..36b50043 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs @@ -0,0 +1,76 @@ +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.DI; +using SPTarkov.Server.Core.Models.Utils; +using SPTarkov.Server.Core.Utils; +using Range = SemanticVersioning.Range; +using Version = SemanticVersioning.Version; + +namespace SPTarkov.Server.Core.Services; + +// Note: We want to run after all mods, to avoid this being lost in mod log +// spam, so we purposely use MaxValue here + +[Injectable(TypePriority = int.MaxValue)] +internal class ReleaseCheckService(ISptLogger logger) : IOnLoad +{ + public Task OnLoad() + { + // Run in a new task so we don't hold the main thread at all, this isn't super critical + _ = Task.Run(CheckForUpdate); + + return Task.CompletedTask; + } + + private async Task CheckForUpdate() + { + try + { + var httpClient = new HttpClient(); + + // These headers are _required_ by GitHub API + httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("SP-Tarkov"); + httpClient.DefaultRequestHeaders.Add("X-GitHub-Api-Version", "2022-11-28"); + + // TODO: We could probably throw this into a config somewhere, for now hard code it + var release = await httpClient.GetFromJsonAsync( + "https://api.github.com/repos/sp-tarkov/build/releases/latest" + ); + if (release != null) + { + Version latestVersion = new(release.Version); + Version currentVersion = ProgramStatics.SPT_VERSION(); + Range currentVersionRange = new($"~{currentVersion.Major}.{currentVersion.Minor}.0"); + + // First make sure the latest release is in our range, this stops "4.1.0" from being detected as a valid upgrade for "4.0.1" + if (!currentVersionRange.IsSatisfied(latestVersion)) + { + return; + } + + // Notify the user if an upgrade is available + if (latestVersion > currentVersion) + { + logger.Warning($"A new version of SPT is available! SPT v{release.Version}"); + logger.Warning($"Released {release.ReleaseDate.ToLocalTime()}"); + logger.Warning($"Release Notes: {release.DownloadUrl}"); + } + } + } + // We ignore errors, this isn't critical to run, and we don't want to scare users + catch { } + } + + private record ReleaseInformation + { + [JsonPropertyName("tag_name")] + public required string Version { get; init; } + + [JsonPropertyName("html_url")] + public required string DownloadUrl { get; init; } + + [JsonPropertyName("published_at")] + public required DateTime ReleaseDate { get; init; } + } +}