diff --git a/CompatApiClient/Client.cs b/CompatApiClient/Client.cs index eac1630b..715b6670 100644 --- a/CompatApiClient/Client.cs +++ b/CompatApiClient/Client.cs @@ -64,8 +64,10 @@ namespace CompatApiClient throw new HttpRequestException("Couldn't communicate with the API"); } - public async Task GetUpdateAsync(CancellationToken cancellationToken, string commit = "somecommit") + public async Task GetUpdateAsync(CancellationToken cancellationToken, string commit = null) { + if (string.IsNullOrEmpty(commit)) + commit = "somecommit"; var tries = 3; do { diff --git a/CompatBot/Commands/CompatList.cs b/CompatBot/Commands/CompatList.cs index dafbb245..61d1bb40 100644 --- a/CompatBot/Commands/CompatList.cs +++ b/CompatBot/Commands/CompatList.cs @@ -131,9 +131,16 @@ Example usage: return CheckForRpcs3Updates(ctx.Client, ctx.Channel); } - public static async Task CheckForRpcs3Updates(DiscordClient discordClient, DiscordChannel channel) + [Command("since")] + [Description("Show additinal info about changes since specified update")] + public Task Since(CommandContext ctx, [Description("Commit hash of the update, such as `46abe0f31`")] string commit) { - var info = await client.GetUpdateAsync(Config.Cts.Token).ConfigureAwait(false); + return CheckForRpcs3Updates(ctx.Client, ctx.Channel, commit); + } + + public static async Task CheckForRpcs3Updates(DiscordClient discordClient, DiscordChannel channel, string sinceCommit = null) + { + var info = await client.GetUpdateAsync(Config.Cts.Token, sinceCommit).ConfigureAwait(false); var embed = await info.AsEmbedAsync().ConfigureAwait(false); if (info == null || embed.Color == Config.Colors.Maintenance) embed = await CachedUpdateInfo.AsEmbedAsync().ConfigureAwait(false); diff --git a/CompatBot/Config.cs b/CompatBot/Config.cs index 2eb562d6..4ccbcb01 100644 --- a/CompatBot/Config.cs +++ b/CompatBot/Config.cs @@ -26,7 +26,8 @@ namespace CompatBot public static readonly int AttachmentSizeLimit = 8 * 1024 * 1024; public static readonly int LogSizeLimit = 64 * 1024 * 1024; public static readonly int MinimumBufferSize = 512; - public static readonly int MaxBuildNumberDifferenceForOutdatedBuilds = 10; + public static readonly int BuildNumberDifferenceForOutdatedBuilds = 10; + public static readonly TimeSpan BuildTimeDifferenceForOutdatedBuilds = TimeSpan.FromDays(3); public static readonly string Token; public static readonly string LogPath = "../../../logs/bot.log"; // paths are relative to the assembly, so this will put it in the project's root diff --git a/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs b/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs index 478f8ea3..0698e510 100644 --- a/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs +++ b/CompatBot/Utils/ResultFormatters/LogParserResultFormatter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -369,7 +370,7 @@ namespace CompatBot.Utils.ResultFormatters if (items["fatal_error"] is string fatalError) { builder.AddField("Fatal Error", $"```{fatalError.Trim(1022)}```"); - if (fatalError.Contains("psf.cpp")) + if (fatalError.Contains("psf.cpp") || fatalError.Contains("invalid map")) notes.AppendLine("Game save data might be corrupted"); } if (items["failed_to_decrypt"] is string _) @@ -412,7 +413,10 @@ namespace CompatBot.Utils.ResultFormatters // should be last check here var updateInfo = await CheckForUpdateAsync(items).ConfigureAwait(false); if (updateInfo != null) - notes.AppendLine("Outdated RPCS3 build detected, please consider updating it"); + { + var timeDeltaStr = updateInfo.GetUpdateDelta() is TimeSpan timeDelta ? timeDelta.GetTimeDeltaDescription(): "outdated"; + notes.AppendLine($"This RPCS3 build is {timeDeltaStr} old, please consider updating it"); + } var notesContent = notes.ToString().Trim(); PageSection(builder, notesContent, "Notes"); //if (updateInfo != null) @@ -441,28 +445,31 @@ namespace CompatBot.Utils.ResultFormatters if (!buildInfo.Success || buildInfo.Groups["branch"].Value != "head") return null; - var updateInfo = await compatClient.GetUpdateAsync(Config.Cts.Token).ConfigureAwait(false); + var updateInfo = await compatClient.GetUpdateAsync(Config.Cts.Token, buildInfo.Groups["commit"].Value).ConfigureAwait(false); var link = updateInfo?.LatestBuild?.Windows?.Download ?? updateInfo?.LatestBuild?.Linux?.Download; if (string.IsNullOrEmpty(link)) return null; var latestBuildInfo = BuildInfoInUpdate.Match(link.ToLowerInvariant()); - if (latestBuildInfo.Success && VersionIsTooOld(buildInfo, latestBuildInfo)) + if (latestBuildInfo.Success && VersionIsTooOld(buildInfo, latestBuildInfo, updateInfo)) return updateInfo; return null; } - private static bool VersionIsTooOld(Match log, Match update) + private static bool VersionIsTooOld(Match log, Match update, UpdateInfo updateInfo) { if (Version.TryParse(log.Groups["version"].Value, out var logVersion) && Version.TryParse(update.Groups["version"].Value, out var updateVersion)) { if (logVersion < updateVersion) return true; + if (UpdateInfoFormatter.GetUpdateDelta(updateInfo) is TimeSpan updateTimeDelta && updateTimeDelta > Config.BuildTimeDifferenceForOutdatedBuilds) + return true; + if (int.TryParse(log.Groups["build"].Value, out var logBuild) && int.TryParse(update.Groups["build"].Value, out var updateBuild)) { - if (logBuild + Config.MaxBuildNumberDifferenceForOutdatedBuilds < updateBuild) + if (logBuild + Config.BuildNumberDifferenceForOutdatedBuilds < updateBuild) return true; } return false; diff --git a/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs b/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs index bb384d9c..0cbd6b8e 100644 --- a/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs +++ b/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs @@ -33,10 +33,21 @@ namespace CompatBot.Utils.ResultFormatters prInfo = await client.GetPrInfoAsync(pr, Config.Cts.Token).ConfigureAwait(false); pr = $"PR #{pr} by {prInfo?.User?.Login ?? "???"}"; } - if (!string.IsNullOrEmpty(build?.Datetime)) - pr += $" (built on {build.Datetime})"; } builder = builder ?? new DiscordEmbedBuilder {Title = pr, Url = url, Description = prInfo?.Title, Color = Config.Colors.DownloadLinks}; + if (!justAppend) + { + if (!string.IsNullOrEmpty(build?.Datetime)) + { + var timestampInfo = build.Datetime; + if (info.CurrentBuild?.Pr is string buildPr + && buildPr != pr + && GetUpdateDelta(info) is TimeSpan timeDelta) + timestampInfo += $" ({timeDelta.GetTimeDeltaDescription()} newer)"; + + builder.AddField("Build timestamp", timestampInfo); + } + } return builder .AddField($"Windows ".FixSpaces(), GetLinkMessage(build?.Windows?.Download, true), true) .AddField($"Linux ".FixSpaces(), GetLinkMessage(build?.Linux?.Download, true), true); @@ -56,5 +67,48 @@ namespace CompatBot.Utils.ResultFormatters return $"[⏬ {text}]({link}){" ".FixSpaces()}"; } + public static TimeSpan? GetUpdateDelta(this UpdateInfo updateInfo) + { + if (updateInfo?.LatestBuild?.Datetime is string latestDateTimeStr + && DateTime.TryParse(latestDateTimeStr, out var latestDateTime) + && updateInfo.CurrentBuild?.Datetime is string dateTimeBuildStr + && DateTime.TryParse(dateTimeBuildStr, out var dateTimeBuild)) + return latestDateTime - dateTimeBuild; + return null; + } + + public static string GetTimeDeltaDescription(this TimeSpan delta) + { + if (delta.TotalHours < 1) + { + var minutes = (int)delta.TotalMinutes; + return $"{minutes} minute{(minutes == 1 ? "" : "s")}"; + } + else if (delta.TotalDays < 1) + { + var hours = (int) delta.TotalHours; + return $"{hours} hour{(hours == 1 ? "": "s")}"; + } + else if (delta.TotalDays < 7) + { + var days = (int) delta.TotalDays; + return $"{days} day{(days == 1 ? "": "s")}"; + } + else if (delta.TotalDays < 30) + { + var weeks = (int)(delta.TotalDays/7); + return $"{weeks} week{(weeks == 1 ? "" : "s")}"; + } + else if (delta.TotalDays < 365) + { + var months = (int)(delta.TotalDays/30); + return $"{months} month{(months == 1 ? "" : "s")}"; + } + else + { + var years = (int)(delta.TotalDays/365); + return $"{years} year{(years == 1 ? "" : "s")}"; + } + } } }