From bacc0ca3a93aa9d8b43d4984e7c046862490529c Mon Sep 17 00:00:00 2001 From: clienthax Date: Mon, 30 Aug 2021 14:39:05 +0100 Subject: [PATCH] Use Octokit for Github lookups. --- Clients/CompatApiClient/ApiConfig.cs | 4 +- Clients/GithubClient/Client.cs | 181 ++++++------------ Clients/GithubClient/GithubClient.csproj | 1 + Clients/GithubClient/POCOs/PrInfo.cs | 64 ------- Clients/GithubClient/POCOs/StatusInfo.cs | 14 -- CompatBot/Commands/BotStats.cs | 1 + CompatBot/Commands/CompatList.cs | 4 +- CompatBot/Commands/Pr.cs | 6 +- CompatBot/Config.cs | 1 + .../Utils/ResultFormatters/PrInfoFormatter.cs | 17 +- .../ResultFormatters/UpdateInfoFormatter.cs | 13 +- 11 files changed, 81 insertions(+), 225 deletions(-) delete mode 100644 Clients/GithubClient/POCOs/PrInfo.cs delete mode 100644 Clients/GithubClient/POCOs/StatusInfo.cs diff --git a/Clients/CompatApiClient/ApiConfig.cs b/Clients/CompatApiClient/ApiConfig.cs index 2073e13e..62e3acde 100644 --- a/Clients/CompatApiClient/ApiConfig.cs +++ b/Clients/CompatApiClient/ApiConfig.cs @@ -11,7 +11,9 @@ namespace CompatApiClient public static class ApiConfig { - public static readonly ProductInfoHeaderValue ProductInfoHeader = new("RPCS3CompatibilityBot", "2.0"); + public static readonly string ProductName = "RPCS3CompatibilityBot"; + public static readonly string ProductVersion = "2.0"; + public static readonly ProductInfoHeaderValue ProductInfoHeader = new(ProductName, ProductVersion); public static int Version { get; } = 1; public static Uri BaseUrl { get; } = new("https://rpcs3.net/compatibility"); public static string DateInputFormat { get; } = "yyyy-M-d"; diff --git a/Clients/GithubClient/Client.cs b/Clients/GithubClient/Client.cs index 21010cdf..e52943d6 100644 --- a/Clients/GithubClient/Client.cs +++ b/Clients/GithubClient/Client.cs @@ -1,25 +1,15 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Json; -using System.Net.Http.Headers; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using CompatApiClient; -using CompatApiClient.Compression; -using CompatApiClient.Formatters; -using CompatApiClient.Utils; -using GithubClient.POCOs; using Microsoft.Extensions.Caching.Memory; namespace GithubClient { public class Client { - private readonly HttpClient client; - private readonly JsonSerializerOptions jsonOptions; + private readonly Octokit.GitHubClient client; private static readonly TimeSpan PrStatusCacheTime = TimeSpan.FromMinutes(3); private static readonly TimeSpan IssueStatusCacheTime = TimeSpan.FromMinutes(30); @@ -30,40 +20,29 @@ namespace GithubClient public static int RateLimitRemaining { get; private set; } public static DateTime RateLimitResetTime { get; private set; } - public Client() + public Client(string? githubToken) { - client = HttpClientFactory.Create(new CompressionMessageHandler()); - jsonOptions = new() + client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(ApiConfig.ProductName, ApiConfig.ProductVersion)); + if (!string.IsNullOrEmpty(githubToken)) { - PropertyNamingPolicy = SpecialJsonNamingPolicy.SnakeCase, - IgnoreNullValues = true, - IncludeFields = true, - }; + client.Credentials = new Octokit.Credentials(githubToken); + } } - public async Task GetPrInfoAsync(int pr, CancellationToken cancellationToken) + public async Task GetPrInfoAsync(int pr, CancellationToken cancellationToken) { - if (StatusesCache.TryGetValue(pr, out PrInfo? result)) + if (StatusesCache.TryGetValue(pr, out Octokit.PullRequest? result)) { - ApiConfig.Log.Debug($"Returned {nameof(PrInfo)} for {pr} from cache"); + ApiConfig.Log.Debug($"Returned {nameof(Octokit.PullRequest)} for {pr} from cache"); return result; } try { - using var message = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/RPCS3/rpcs3/pulls/" + pr); - message.Headers.UserAgent.Add(ApiConfig.ProductInfoHeader); - using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - try - { - await response.Content.LoadIntoBufferAsync().ConfigureAwait(false); - UpdateRateLimitStats(response.Headers); - result = await response.Content.ReadFromJsonAsync(jsonOptions, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - ConsoleLogger.PrintError(e, response); - } + var request = client.PullRequest.Get("RPCS3", "rpcs3", pr); + request.Wait(cancellationToken); + result = (await request.ConfigureAwait(false)); + UpdateRateLimitStats(); } catch (Exception e) { @@ -71,38 +50,29 @@ namespace GithubClient } if (result == null) { - ApiConfig.Log.Debug($"Failed to get {nameof(PrInfo)}, returning empty result"); - return new() { Number = pr }; + ApiConfig.Log.Debug($"Failed to get {nameof(Octokit.PullRequest)}, returning empty result"); + return new(pr); } StatusesCache.Set(pr, result, PrStatusCacheTime); - ApiConfig.Log.Debug($"Cached {nameof(PrInfo)} for {pr} for {PrStatusCacheTime}"); + ApiConfig.Log.Debug($"Cached {nameof(Octokit.PullRequest)} for {pr} for {PrStatusCacheTime}"); return result; } - public async Task GetIssueInfoAsync(int issue, CancellationToken cancellationToken) + public async Task GetIssueInfoAsync(int issue, CancellationToken cancellationToken) { - if (IssuesCache.TryGetValue(issue, out IssueInfo? result)) + if (IssuesCache.TryGetValue(issue, out Octokit.Issue? result)) { - ApiConfig.Log.Debug($"Returned {nameof(IssueInfo)} for {issue} from cache"); + ApiConfig.Log.Debug($"Returned {nameof(Octokit.Issue)} for {issue} from cache"); return result; } try { - using var message = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/repos/RPCS3/rpcs3/issues/" + issue); - message.Headers.UserAgent.Add(ApiConfig.ProductInfoHeader); - using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - try - { - await response.Content.LoadIntoBufferAsync().ConfigureAwait(false); - UpdateRateLimitStats(response.Headers); - result = await response.Content.ReadFromJsonAsync(jsonOptions, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - ConsoleLogger.PrintError(e, response); - } + var request = client.Issue.Get("RPCS3", "rpcs3", issue); + request.Wait(cancellationToken); + result = (await request.ConfigureAwait(false)); + UpdateRateLimitStats(); } catch (Exception e) { @@ -110,22 +80,31 @@ namespace GithubClient } if (result == null) { - ApiConfig.Log.Debug($"Failed to get {nameof(IssueInfo)}, returning empty result"); - return new() { Number = issue }; + ApiConfig.Log.Debug($"Failed to get {nameof(Octokit.Issue)}, returning empty result"); + return new() { }; } IssuesCache.Set(issue, result, IssueStatusCacheTime); - ApiConfig.Log.Debug($"Cached {nameof(IssueInfo)} for {issue} for {IssueStatusCacheTime}"); + ApiConfig.Log.Debug($"Cached {nameof(Octokit.Issue)} for {issue} for {IssueStatusCacheTime}"); return result; } - public Task?> GetOpenPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync("open", cancellationToken); - public Task?> GetClosedPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync("closed&sort=updated&direction=desc", cancellationToken); - - private async Task?> GetPrsWithStatusAsync(string status, CancellationToken cancellationToken) + public Task?> GetOpenPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new Octokit.PullRequestRequest { - var requestUri = "https://api.github.com/repos/RPCS3/rpcs3/pulls?state=" + status; - if (StatusesCache.TryGetValue(requestUri, out List? result)) + State = Octokit.ItemStateFilter.Open + }, cancellationToken); + + public Task?> GetClosedPrsAsync(CancellationToken cancellationToken) => GetPrsWithStatusAsync(new Octokit.PullRequestRequest + { + State = Octokit.ItemStateFilter.Closed, + SortProperty = Octokit.PullRequestSort.Updated, + SortDirection = Octokit.SortDirection.Descending + }, cancellationToken); + + private async Task?> GetPrsWithStatusAsync(Octokit.PullRequestRequest filter, CancellationToken cancellationToken) + { + var statusURI = "https://api.github.com/repos/RPCS3/rpcs3/pulls?state=" + filter.ToString(); + if (StatusesCache.TryGetValue(statusURI, out IReadOnlyList? result)) { ApiConfig.Log.Debug("Returned list of opened PRs from cache"); return result; @@ -133,19 +112,11 @@ namespace GithubClient try { - using var message = new HttpRequestMessage(HttpMethod.Get, requestUri); - message.Headers.UserAgent.Add(ApiConfig.ProductInfoHeader); - using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - try - { - await response.Content.LoadIntoBufferAsync().ConfigureAwait(false); - UpdateRateLimitStats(response.Headers); - result = await response.Content.ReadFromJsonAsync>(jsonOptions, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - ConsoleLogger.PrintError(e, response); - } + var request = client.PullRequest.GetAllForRepository("RPCS3", "rpcs3", filter); + request.Wait(cancellationToken); + + result = (await request.ConfigureAwait(false)); + UpdateRateLimitStats(); } catch (Exception e) { @@ -153,7 +124,7 @@ namespace GithubClient } if (result != null) { - StatusesCache.Set(requestUri, result, PrStatusCacheTime); + StatusesCache.Set(statusURI, result, PrStatusCacheTime); foreach (var prInfo in result) StatusesCache.Set(prInfo.Number, prInfo, PrStatusCacheTime); ApiConfig.Log.Debug($"Cached list of open PRs for {PrStatusCacheTime}"); @@ -161,62 +132,22 @@ namespace GithubClient return result; } - public async Task?> GetStatusesAsync(string statusesUrl, CancellationToken cancellationToken) + private void UpdateRateLimitStats() { - if (StatusesCache.TryGetValue(statusesUrl, out List? result)) + var apiInfo = client.GetLastApiInfo(); + if (apiInfo == null) { - ApiConfig.Log.Debug($"Returned cached item for {statusesUrl}"); - return result; + return; } - try - { - using var message = new HttpRequestMessage(HttpMethod.Get, statusesUrl); - message.Headers.UserAgent.Add(ApiConfig.ProductInfoHeader); - using var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); - try - { - await response.Content.LoadIntoBufferAsync().ConfigureAwait(false); - UpdateRateLimitStats(response.Headers); - result = await response.Content.ReadFromJsonAsync>(jsonOptions, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - ConsoleLogger.PrintError(e, response); - } - } - catch (Exception e) - { - ApiConfig.Log.Error(e); - } + RateLimit = apiInfo.RateLimit.Limit; + RateLimitRemaining = apiInfo.RateLimit.Remaining; + RateLimitResetTime = DateTimeOffset.FromUnixTimeSeconds(apiInfo.RateLimit.ResetAsUtcEpochSeconds).UtcDateTime; - if (result != null) - { - StatusesCache.Set(statusesUrl, result, PrStatusCacheTime); - ApiConfig.Log.Debug($"Cached item for {statusesUrl} for {PrStatusCacheTime}"); - } - return result; - } - - private static void UpdateRateLimitStats(HttpResponseHeaders headers) - { - if (headers.TryGetValues("X-RateLimit-Limit", out var rateLimitValues) - && rateLimitValues.FirstOrDefault() is string limitValue - && int.TryParse(limitValue, out var limit) - && limit > 0) - RateLimit = limit; - if (headers.TryGetValues("X-RateLimit-Remaining", out var rateLimitRemainingValues) - && rateLimitRemainingValues.FirstOrDefault() is string remainingValue - && int.TryParse(remainingValue, out var remaining) - && remaining > 0) - RateLimitRemaining = remaining; - if (headers.TryGetValues("X-RateLimit-Reset", out var rateLimitResetValues) - && rateLimitResetValues.FirstOrDefault() is string resetValue - && long.TryParse(resetValue, out var resetSeconds) - && resetSeconds > 0) - RateLimitResetTime = DateTimeOffset.FromUnixTimeSeconds(resetSeconds).UtcDateTime; if (RateLimitRemaining < 10) ApiConfig.Log.Warn($"Github rate limit is low: {RateLimitRemaining} out of {RateLimit}, will be reset on {RateLimitResetTime:u}"); } + } + } diff --git a/Clients/GithubClient/GithubClient.csproj b/Clients/GithubClient/GithubClient.csproj index 1ee2f5eb..48c5d189 100644 --- a/Clients/GithubClient/GithubClient.csproj +++ b/Clients/GithubClient/GithubClient.csproj @@ -8,6 +8,7 @@ + diff --git a/Clients/GithubClient/POCOs/PrInfo.cs b/Clients/GithubClient/POCOs/PrInfo.cs deleted file mode 100644 index ffcdfc2c..00000000 --- a/Clients/GithubClient/POCOs/PrInfo.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Diagnostics; - -namespace GithubClient.POCOs -{ - [DebuggerDisplay("{Body}", Name = "#{Number}")] - public sealed class PrInfo - { - public string? HtmlUrl; - public int Number; - public string? State; - public string? Title; - public GithubUser? User; - public string? Body; - public DateTime CreatedAt; - public DateTime? UpdatedAt; - public DateTime? ClosedAt; - public DateTime? MergedAt; - public string? MergeCommitSha; - public string? StatusesUrl; - public RefInfo? Head; - public RefInfo? Base; - public int Additions; - public int Deletions; - public int ChangedFiles; - public string? Message; - } - - public sealed class IssueInfo - { - public string? HtmlUrl; - public int Number; - public string? State; - public string? Title; - public GithubUser? User; - public DateTime CreatedAt; - public DateTime? UpdatedAt; - public DateTime? ClosedAt; - public DateTime? MergedAt; - public string? Body; - public PullRequestReference? PullRequest; - } - - public sealed class GithubUser - { - public string? Login; - } - - public sealed class PullRequestReference - { - public string? Url; - public string? HtmlUrl; - public string? DiffUrl; - public string? PatchUrl; - } - - public sealed class RefInfo - { - public string? Label; - public string? Ref; - public GithubUser? User; - public string? Sha; - } -} diff --git a/Clients/GithubClient/POCOs/StatusInfo.cs b/Clients/GithubClient/POCOs/StatusInfo.cs deleted file mode 100644 index 4eb59f99..00000000 --- a/Clients/GithubClient/POCOs/StatusInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace GithubClient.POCOs -{ - public sealed class StatusInfo - { - public string? State; // success - public string? Description; - public string? TargetUrl; - public string? Context; // continuous-integration/appveyor/pr - public DateTime? CreatedAt; - public DateTime? UpdatedAt; - } -} \ No newline at end of file diff --git a/CompatBot/Commands/BotStats.cs b/CompatBot/Commands/BotStats.cs index 83929549..a81af70f 100644 --- a/CompatBot/Commands/BotStats.cs +++ b/CompatBot/Commands/BotStats.cs @@ -67,6 +67,7 @@ namespace CompatBot.Commands .Append(string.IsNullOrEmpty(Config.AzureDevOpsToken) ? "❌" : "✅").AppendLine(" Azure DevOps") .Append(string.IsNullOrEmpty(Config.AzureComputerVisionKey) ? "❌" : "✅").AppendLine(" Computer Vision") .Append(string.IsNullOrEmpty(Config.AzureAppInsightsKey) ? "❌" : "✅").AppendLine(" AppInsights") + .Append(string.IsNullOrEmpty(Config.GithubToken) ? "❌" : "✅").AppendLine(" Github") .ToString() .Trim(); diff --git a/CompatBot/Commands/CompatList.cs b/CompatBot/Commands/CompatList.cs index 1da41d9a..c206c8b4 100644 --- a/CompatBot/Commands/CompatList.cs +++ b/CompatBot/Commands/CompatList.cs @@ -35,7 +35,7 @@ namespace CompatBot.Commands internal sealed class CompatList : BaseCommandModuleCustom { private static readonly Client Client = new(); - private static readonly GithubClient.Client GithubClient = new(); + private static readonly GithubClient.Client GithubClient = new(Config.GithubToken); private static readonly SemaphoreSlim UpdateCheck = new(1, 1); private static string? lastUpdateInfo, lastFullBuildNumber; private const string Rpcs3UpdateStateKey = "Rpcs3UpdateState"; @@ -338,7 +338,7 @@ namespace CompatBot.Commands var failedBuilds = await Config.GetAzureDevOpsClient().GetMasterBuildsAsync( oldestPrCommit.MergeCommitSha, newestPrCommit.MergeCommitSha, - oldestPrCommit.MergedAt, + oldestPrCommit.MergedAt?.DateTime, cancellationToken ).ConfigureAwait(false); foreach (var mergedPr in mergedPrs) diff --git a/CompatBot/Commands/Pr.cs b/CompatBot/Commands/Pr.cs index b86ba73b..d7eec15a 100644 --- a/CompatBot/Commands/Pr.cs +++ b/CompatBot/Commands/Pr.cs @@ -21,7 +21,7 @@ namespace CompatBot.Commands [Description("Commands to list opened pull requests information")] internal sealed class Pr: BaseCommandModuleCustom { - private static readonly GithubClient.Client GithubClient = new(); + private static readonly GithubClient.Client GithubClient = new(Config.GithubToken); private static readonly CompatApiClient.Client CompatApiClient = new(); [GroupCommand] @@ -114,7 +114,7 @@ namespace CompatBot.Commands var prInfo = await GithubClient.GetPrInfoAsync(pr, Config.Cts.Token).ConfigureAwait(false); if (prInfo is null or {Number: 0}) { - await message.ReactWithAsync(Config.Reactions.Failure, prInfo?.Message ?? "PR not found").ConfigureAwait(false); + await message.ReactWithAsync(Config.Reactions.Failure, prInfo?.Title ?? "PR not found").ConfigureAwait(false); return; } @@ -134,7 +134,7 @@ namespace CompatBot.Commands { windowsDownloadText = "⏳ Pending..."; linuxDownloadText = "⏳ Pending..."; - var latestBuild = await CirrusCi.GetPrBuildInfoAsync(commit, prInfo.MergedAt, pr, Config.Cts.Token).ConfigureAwait(false); + var latestBuild = await CirrusCi.GetPrBuildInfoAsync(commit, prInfo.MergedAt?.DateTime, pr, Config.Cts.Token).ConfigureAwait(false); if (latestBuild == null) { if (state == "Open") diff --git a/CompatBot/Config.cs b/CompatBot/Config.cs index a7a05dd1..f8b5fa36 100644 --- a/CompatBot/Config.cs +++ b/CompatBot/Config.cs @@ -82,6 +82,7 @@ namespace CompatBot public static string AzureComputerVisionEndpoint => config.GetValue(nameof(AzureComputerVisionEndpoint), "https://westeurope.api.cognitive.microsoft.com/"); public static Guid AzureDevOpsProjectId => config.GetValue(nameof(AzureDevOpsProjectId), new Guid("3598951b-4d39-4fad-ad3b-ff2386a649de")); public static string AzureAppInsightsKey => config.GetValue(nameof(AzureAppInsightsKey), ""); + public static string GithubToken => config.GetValue(nameof(GithubToken), ""); public static string PreferredFontFamily => config.GetValue(nameof(PreferredFontFamily), ""); public static string LogPath => config.GetValue(nameof(LogPath), "./logs/"); // paths are relative to the working directory public static string IrdCachePath => config.GetValue(nameof(IrdCachePath), "./ird/"); diff --git a/CompatBot/Utils/ResultFormatters/PrInfoFormatter.cs b/CompatBot/Utils/ResultFormatters/PrInfoFormatter.cs index 5cf6abeb..10071cbd 100644 --- a/CompatBot/Utils/ResultFormatters/PrInfoFormatter.cs +++ b/CompatBot/Utils/ResultFormatters/PrInfoFormatter.cs @@ -1,11 +1,10 @@ using DSharpPlus.Entities; -using GithubClient.POCOs; namespace CompatBot.Utils.ResultFormatters { internal static class PrInfoFormatter { - public static DiscordEmbedBuilder AsEmbed(this PrInfo prInfo) + public static DiscordEmbedBuilder AsEmbed(this Octokit.PullRequest prInfo) { var state = prInfo.GetState(); var stateLabel = state.state == null ? null : $"[{state.state}] "; @@ -13,7 +12,7 @@ namespace CompatBot.Utils.ResultFormatters return new DiscordEmbedBuilder {Title = title, Url = prInfo.HtmlUrl, Description = prInfo.Title, Color = state.color}; } - public static DiscordEmbedBuilder AsEmbed(this IssueInfo issueInfo) + public static DiscordEmbedBuilder AsEmbed(this Octokit.Issue issueInfo) { var state = issueInfo.GetState(); var stateLabel = state.state == null ? null : $"[{state.state}] "; @@ -21,12 +20,12 @@ namespace CompatBot.Utils.ResultFormatters return new DiscordEmbedBuilder {Title = title, Url = issueInfo.HtmlUrl, Description = issueInfo.Title, Color = state.color}; } - public static (string? state, DiscordColor color) GetState(this PrInfo prInfo) + public static (string? state, DiscordColor color) GetState(this Octokit.PullRequest prInfo) { - if (prInfo.State == "open") + if (prInfo.State == Octokit.ItemState.Open) return ("Open", Config.Colors.PrOpen); - if (prInfo.State == "closed") + if (prInfo.State == Octokit.ItemState.Closed) { if (prInfo.MergedAt.HasValue) return ("Merged", Config.Colors.PrMerged); @@ -37,12 +36,12 @@ namespace CompatBot.Utils.ResultFormatters return (null, Config.Colors.DownloadLinks); } - public static (string? state, DiscordColor color) GetState(this IssueInfo issueInfo) + public static (string? state, DiscordColor color) GetState(this Octokit.Issue issueInfo) { - if (issueInfo.State == "open") + if (issueInfo.State == Octokit.ItemState.Open) return ("Open", Config.Colors.PrOpen); - if (issueInfo.State == "closed") + if (issueInfo.State == Octokit.ItemState.Closed) return ("Closed", Config.Colors.PrClosed); return (null, Config.Colors.DownloadLinks); diff --git a/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs b/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs index 2625d6a5..5c54aad5 100644 --- a/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs +++ b/CompatBot/Utils/ResultFormatters/UpdateInfoFormatter.cs @@ -10,15 +10,14 @@ using CompatBot.EventHandlers; using CompatBot.Utils.Extensions; using DSharpPlus; using DSharpPlus.Entities; -using GithubClient.POCOs; namespace CompatBot.Utils.ResultFormatters { internal static class UpdateInfoFormatter { - private static readonly GithubClient.Client GithubClient = new(); + private static readonly GithubClient.Client GithubClient = new(Config.GithubToken); - public static async Task AsEmbedAsync(this UpdateInfo? info, DiscordClient client, bool includePrBody = false, DiscordEmbedBuilder? builder = null, PrInfo? currentPrInfo = null) + public static async Task AsEmbedAsync(this UpdateInfo? info, DiscordClient client, bool includePrBody = false, DiscordEmbedBuilder? builder = null, Octokit.PullRequest? currentPrInfo = null) { if ((info?.LatestBuild?.Windows?.Download ?? info?.LatestBuild?.Linux?.Download) is null) return builder ?? new DiscordEmbedBuilder {Title = "Error", Description = "Error communicating with the update API. Try again later.", Color = Config.Colors.Maintenance}; @@ -28,7 +27,7 @@ namespace CompatBot.Utils.ResultFormatters var latestPr = latestBuild?.Pr; var currentPr = info.CurrentBuild?.Pr; string? url = null; - PrInfo? latestPrInfo = null; + Octokit.PullRequest? latestPrInfo = null; string prDesc = ""; if (!justAppend) @@ -134,14 +133,14 @@ namespace CompatBot.Utils.ResultFormatters DateTime? latestBuildTimestamp = null, currentBuildTimestamp = null; if (Config.GetAzureDevOpsClient() is {} azureClient) { - var currentAppveyorBuild = await azureClient.GetMasterBuildInfoAsync(currentCommit, currentPrInfo?.MergedAt, Config.Cts.Token).ConfigureAwait(false); - var latestAppveyorBuild = await azureClient.GetMasterBuildInfoAsync(latestCommit, latestPrInfo?.MergedAt, Config.Cts.Token).ConfigureAwait(false); + var currentAppveyorBuild = await azureClient.GetMasterBuildInfoAsync(currentCommit, currentPrInfo?.MergedAt?.DateTime, Config.Cts.Token).ConfigureAwait(false); + var latestAppveyorBuild = await azureClient.GetMasterBuildInfoAsync(latestCommit, latestPrInfo?.MergedAt?.DateTime, Config.Cts.Token).ConfigureAwait(false); latestBuildTimestamp = latestAppveyorBuild?.FinishTime; currentBuildTimestamp = currentAppveyorBuild?.FinishTime; if (!latestBuildTimestamp.HasValue) { buildTimestampKind = "Merged"; - latestBuildTimestamp = currentPrInfo?.MergedAt; + latestBuildTimestamp = currentPrInfo?.MergedAt?.DateTime; } }