diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7dc1af5..070b34d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -52,6 +52,9 @@ jobs: seven_zip_args="-mx=1" zip_args="-1" + # Remove useless dlls + rm -rf out/${1}/${PLUGIN_NAME}/System.IO.Hashing.dll out/${1}/${PLUGIN_NAME}/NLog.dll out/${1}/${PLUGIN_NAME}/SteamKit2.dll out/${1}/${PLUGIN_NAME}/System.IO.Hashing.dll out/${1}/${PLUGIN_NAME}/protobuf-net.Core.dll out/${1}/${PLUGIN_NAME}/protobuf-net.dll + # Include extra logic for builds marked for release case "$GITHUB_REF" in "refs/tags/"*) @@ -144,6 +147,14 @@ jobs: $compressionArgs = '-mx=9', '-mfb=258', '-mpass=15' } + # Remove useless dlls + Get-Item "$env:GITHUB_WORKSPACE\out\$variant\$env:PLUGIN_NAME\System.IO.Hashing.dll" -ErrorAction SilentlyContinue | Remove-Item + Get-Item "$env:GITHUB_WORKSPACE\out\$variant\$env:PLUGIN_NAME\NLog.dll" -ErrorAction SilentlyContinue | Remove-Item + Get-Item "$env:GITHUB_WORKSPACE\out\$variant\$env:PLUGIN_NAME\SteamKit2.dll" -ErrorAction SilentlyContinue | Remove-Item + Get-Item "$env:GITHUB_WORKSPACE\out\$variant\$env:PLUGIN_NAME\System.IO.Hashing.dll" -ErrorAction SilentlyContinue | Remove-Item + Get-Item "$env:GITHUB_WORKSPACE\out\$variant\$env:PLUGIN_NAME\protobuf-net.Core.dll" -ErrorAction SilentlyContinue | Remove-Item + Get-Item "$env:GITHUB_WORKSPACE\out\$variant\$env:PLUGIN_NAME\protobuf-net.dll" -ErrorAction SilentlyContinue | Remove-Item + # Create the final zip file 7z a -bd -slp -tzip -mm=Deflate $compressionArgs "out\$env:PLUGIN_NAME-$variant.zip" "$env:GITHUB_WORKSPACE\out\$variant\*" diff --git a/ASFFreeGames/ASFFreeGamesPlugin.cs b/ASFFreeGames/ASFFreeGamesPlugin.cs index 66efeed..1d7647e 100644 --- a/ASFFreeGames/ASFFreeGamesPlugin.cs +++ b/ASFFreeGames/ASFFreeGamesPlugin.cs @@ -14,6 +14,7 @@ using JetBrains.Annotations; using Maxisoft.ASF.ASFExtentions; using Maxisoft.ASF.Configurations; +using Maxisoft.ASF.Github; using Maxisoft.ASF.Utils; using SteamKit2; using static ArchiSteamFarm.Core.ASF; @@ -29,7 +30,7 @@ internal interface IASFFreeGamesPlugin { #pragma warning disable CA1812 // ASF uses this class during runtime [SuppressMessage("Design", "CA1001:Disposable fields")] -internal sealed class ASFFreeGamesPlugin : IASF, IBot, IBotConnection, IBotCommand2, IUpdateAware, IASFFreeGamesPlugin { +internal sealed class ASFFreeGamesPlugin : IASF, IBot, IBotConnection, IBotCommand2, IUpdateAware, IASFFreeGamesPlugin, IGitHubPluginUpdates { internal const string StaticName = nameof(ASFFreeGamesPlugin); private const int CollectGamesTimeout = 3 * 60 * 1000; @@ -43,7 +44,9 @@ internal static PluginContext Context { private static CancellationToken CancellationToken => Context.CancellationToken; public string Name => StaticName; - public Version Version => typeof(ASFFreeGamesPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); + public Version Version => GetVersion(); + + private static Version GetVersion() => typeof(ASFFreeGamesPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); private readonly ConcurrentHashSet Bots = new(new BotEqualityComparer()); private readonly Lazy CancellationTokenSourceLazy = new(static () => new CancellationTokenSource()); @@ -209,6 +212,12 @@ private async Task RemoveBot(Bot bot) { private void StartTimerIfNeeded() => CollectIntervalManager.StartTimerIfNeeded(); ~ASFFreeGamesPlugin() => CollectIntervalManager.Dispose(); + public readonly GithubPluginUpdater Updater = new(new Lazy(GetVersion)); + string IGitHubPluginUpdates.RepositoryName => GithubPluginUpdater.RepositoryName; + + bool IGitHubPluginUpdates.CanUpdate => Updater.CanUpdate; + + Task IGitHubPluginUpdates.GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, bool stable, bool forced) => Updater.GetTargetReleaseURL(asfVersion, asfVariant, asfUpdate, stable, forced); } #pragma warning restore CA1812 // ASF uses this class during runtime diff --git a/ASFFreeGames/Github/GithubPluginUpdater.cs b/ASFFreeGames/Github/GithubPluginUpdater.cs new file mode 100644 index 0000000..c343d1b --- /dev/null +++ b/ASFFreeGames/Github/GithubPluginUpdater.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using ArchiSteamFarm; +using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Web.GitHub; +using ArchiSteamFarm.Web.GitHub.Data; + +namespace Maxisoft.ASF.Github; + +public class GithubPluginUpdater(Lazy version) { + public const string RepositoryName = "maxisoft/ASFFreeGames"; + public bool CanUpdate { get; internal set; } = true; + + private Version CurrentVersion => version.Value; + + public async Task GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, bool stable, bool forced) { + ArgumentNullException.ThrowIfNull(asfVersion); + ArgumentException.ThrowIfNullOrEmpty(asfVariant); + + if (!CanUpdate) { + return null; + } + + if (string.IsNullOrEmpty(RepositoryName)) { + //ArchiSteamFarm.Core.ASF.ArchiLogger.LogGenericError(Strings.FormatWarningFailedWithError(nameof(RepositoryName))); + + return null; + } + + ReleaseResponse? releaseResponse = await GitHubService.GetLatestRelease(RepositoryName).ConfigureAwait(false); + + if (releaseResponse == null) { + return null; + } + + if (releaseResponse.IsPreRelease) { + return null; + } + + if (stable && !((releaseResponse.PublishedAt - DateTime.UtcNow).Duration() > TimeSpan.FromHours(3))) { + // Skip updates that are too recent + return null; + } + + Version newVersion = new(releaseResponse.Tag.ToUpperInvariant().TrimStart('V')); + + if (!forced && (CurrentVersion >= newVersion)) { + // Allow same version to be re-updated when we're updating ASF release and more than one asset is found - potential compatibility difference + if ((CurrentVersion > newVersion) || !asfUpdate || (releaseResponse.Assets.Count(static asset => asset.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) < 2)) { + //ASF.ArchiLogger.LogGenericInfo(Strings.FormatPluginUpdateNotFound(Name, Version, newVersion)); + + return null; + } + } + + if (releaseResponse.Assets.Count == 0) { + //ASF.ArchiLogger.LogGenericWarning(Strings.FormatPluginUpdateNoAssetFound(Name, Version, newVersion)); + + return null; + } + + ReleaseAsset? asset = releaseResponse.Assets.FirstOrDefault(static asset => asset.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) && (asset.Size > (1 << 18))); + + if ((asset == null) || !releaseResponse.Assets.Contains(asset)) { + //ASF.ArchiLogger.LogGenericWarning(Strings.FormatPluginUpdateNoAssetFound(Name, Version, newVersion)); + + return null; + } + + //.ArchiLogger.LogGenericInfo(Strings.FormatPluginUpdateFound(Name, Version, newVersion)); + + return asset.DownloadURL; + } +} diff --git a/Directory.Build.props b/Directory.Build.props index 7d67f32..8ee0ef4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ ASFFreeGames - 1.7.1.0 + 1.8.0.0 net8.0 diff --git a/README.md b/README.md index 451f635..d59ed6c 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,15 @@ The environment variable takes precedence over the config file setting. ### Log is full of `Request failed after 5 attempts!` messages is there something wrong ? - There's nothing wrong (most likely), those error messages are the result of the plugin trying to add a steam key which is unavailable. With time those errors should occurs less frequently (see [#3](https://github.com/maxisoft/ASFFreeGames/issues/3) for more details). ---- + + +### How to configure automatic updates for the plugin? +The plugin supports checking for updates on GitHub. You can enable automatic updates by modifying the `PluginsUpdateList` property in your ArchiSteamFarm configuration (refer to the [ArchiSteamFarm wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#pluginsupdatelist) for details). + +**Important note:** Enabling automatic updates for plugins can have security implications. It's recommended to thoroughly test updates in a non-production environment before enabling them on your main system. + + +------ ## Dev notes ### Compilation