From b63c38bf3352ef9f6a8852cce0ab2a17ee2ddcef Mon Sep 17 00:00:00 2001 From: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:54:47 -0700 Subject: [PATCH 1/3] 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 --- .../Services/ReleaseCheckService.cs | 77 +++++++++++++++++++ 1 file changed, 77 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 000000000..e251321b9 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs @@ -0,0 +1,77 @@ +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 class 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; } + } +} From 09b3963fcd4163c30b24cc12d8349ff018113fd3 Mon Sep 17 00:00:00 2001 From: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:55:30 -0700 Subject: [PATCH 2/3] Formatting --- .../SPTarkov.Server.Core/Services/ReleaseCheckService.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs index e251321b9..6c5a20bee 100644 --- a/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs @@ -4,7 +4,6 @@ using SPTarkov.Server.Core.DI; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils; - using Range = SemanticVersioning.Range; using Version = SemanticVersioning.Version; @@ -14,9 +13,7 @@ namespace SPTarkov.Server.Core.Services; // spam, so we purposely use MaxValue here [Injectable(TypePriority = int.MaxValue)] -internal class ReleaseCheckService( - ISptLogger logger -) : IOnLoad +internal class ReleaseCheckService(ISptLogger logger) : IOnLoad { public Task OnLoad() { @@ -37,7 +34,9 @@ private async Task CheckForUpdate() 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"); + var release = await httpClient.GetFromJsonAsync( + "https://api.github.com/repos/sp-tarkov/build/releases/latest" + ); if (release != null) { Version latestVersion = new(release.Version); From 126c4b6dd3d69f89546b910840668e3937c27718 Mon Sep 17 00:00:00 2001 From: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:10:03 -0700 Subject: [PATCH 3/3] Use record for the ReleaseInformation class --- Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs index 6c5a20bee..36b500438 100644 --- a/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/ReleaseCheckService.cs @@ -62,7 +62,7 @@ private async Task CheckForUpdate() catch { } } - private class ReleaseInformation + private record ReleaseInformation { [JsonPropertyName("tag_name")] public required string Version { get; init; }