diff --git a/AppveyorClient/AppveyorClient.csproj b/AppveyorClient/AppveyorClient.csproj
new file mode 100644
index 00000000..8d71c3a2
--- /dev/null
+++ b/AppveyorClient/AppveyorClient.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+ latest
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AppveyorClient/Client.cs b/AppveyorClient/Client.cs
new file mode 100644
index 00000000..f44281e4
--- /dev/null
+++ b/AppveyorClient/Client.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using AppveyorClient.POCOs;
+using CompatApiClient;
+using CompatApiClient.Compression;
+using CompatApiClient.Utils;
+using Microsoft.Extensions.Caching.Memory;
+using Newtonsoft.Json;
+using JsonContractResolver = CompatApiClient.JsonContractResolver;
+
+namespace AppveyorClient
+{
+ public class Client
+ {
+ private readonly HttpClient client;
+ private readonly MediaTypeFormatterCollection formatters;
+
+ private static readonly ProductInfoHeaderValue ProductInfoHeader = new ProductInfoHeaderValue("RPCS3CompatibilityBot", "2.0");
+ private static readonly TimeSpan CacheTime = TimeSpan.FromDays(1);
+ private static readonly MemoryCache ResponseCache = new MemoryCache(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromHours(1) });
+
+ public Client()
+ {
+ client = HttpClientFactory.Create(new CompressionMessageHandler());
+ var settings = new JsonSerializerSettings
+ {
+ ContractResolver = new JsonContractResolver(NamingStyles.CamelCase),
+ NullValueHandling = NullValueHandling.Ignore
+ };
+ formatters = new MediaTypeFormatterCollection(new[] { new JsonMediaTypeFormatter { SerializerSettings = settings } });
+ }
+
+ public async Task GetPrDownloadAsync(string githubStatusTargetUrl, CancellationToken cancellationToken)
+ {
+ try
+ {
+ if (!int.TryParse(new Uri(githubStatusTargetUrl).Segments.Last(), out var buildNumber))
+ return null;
+
+ var buildInfo = await GetBuildInfoAsync(buildNumber, cancellationToken).ConfigureAwait(false);
+ var job = buildInfo?.Build.Jobs?.FirstOrDefault(j => j.Status == "success");
+ if (string.IsNullOrEmpty(job?.JobId))
+ return null;
+
+ var artifacts = await GetJobArtifactsAsync(job.JobId, cancellationToken).ConfigureAwait(false);
+ var rpcs3Build = artifacts?.FirstOrDefault(a => a.Name == "rpcs3");
+ if (rpcs3Build == null)
+ return null;
+
+ var result = new ArtifactInfo
+ {
+ Artifact = rpcs3Build,
+ DownloadUrl = $"https://ci.appveyor.com/api/buildjobs/{job.JobId}/artifacts/{rpcs3Build.FileName}",
+ };
+ ResponseCache.Set(githubStatusTargetUrl, result, CacheTime);
+ return result;
+ }
+ catch (Exception e)
+ {
+ ApiConfig.Log.Error(e);
+ }
+ ResponseCache.TryGetValue(githubStatusTargetUrl, out var o);
+ return o as ArtifactInfo;
+ }
+
+ public async Task GetBuildInfoAsync(int buildNumber, CancellationToken cancellationToken)
+ {
+ var requestUri = "https://ci.appveyor.com/api/projects/rpcs3/rpcs3/builds/" + buildNumber;
+ try
+ {
+ using (var message = new HttpRequestMessage(HttpMethod.Get, requestUri))
+ {
+ message.Headers.UserAgent.Add(ProductInfoHeader);
+ using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
+ {
+ try
+ {
+ await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
+ var result = await response.Content.ReadAsAsync(formatters, cancellationToken).ConfigureAwait(false);
+ ResponseCache.Set(requestUri, result, CacheTime);
+ return result;
+ }
+ catch (Exception e)
+ {
+ ConsoleLogger.PrintError(e, response);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ApiConfig.Log.Error(e);
+ }
+ ResponseCache.TryGetValue(requestUri, out var o);
+ return o as BuildInfo;
+ }
+
+ public async Task> GetJobArtifactsAsync(string jobId, CancellationToken cancellationToken)
+ {
+ var requestUri = $"https://ci.appveyor.com/api/buildjobs/{jobId}/artifacts";
+ try
+ {
+ using (var message = new HttpRequestMessage(HttpMethod.Get, requestUri))
+ {
+ message.Headers.UserAgent.Add(ProductInfoHeader);
+ using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
+ {
+ try
+ {
+ await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
+ var result = await response.Content.ReadAsAsync>(formatters, cancellationToken).ConfigureAwait(false);
+ ResponseCache.Set(requestUri, result, CacheTime);
+ return result;
+ }
+ catch (Exception e)
+ {
+ ConsoleLogger.PrintError(e, response);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ApiConfig.Log.Error(e);
+ }
+ ResponseCache.TryGetValue(requestUri, out var o);
+ return o as List;
+ }
+ }
+}
diff --git a/AppveyorClient/POCOs/Artifact.cs b/AppveyorClient/POCOs/Artifact.cs
new file mode 100644
index 00000000..9356f2a1
--- /dev/null
+++ b/AppveyorClient/POCOs/Artifact.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace AppveyorClient.POCOs
+{
+ public class Artifact
+ {
+ public DateTime? Created;
+ public string FileName;
+ public string Name;
+ public long Size;
+ public string Type;
+ }
+}
\ No newline at end of file
diff --git a/AppveyorClient/POCOs/ArtifactInfo.cs b/AppveyorClient/POCOs/ArtifactInfo.cs
new file mode 100644
index 00000000..367d7e3b
--- /dev/null
+++ b/AppveyorClient/POCOs/ArtifactInfo.cs
@@ -0,0 +1,8 @@
+namespace AppveyorClient.POCOs
+{
+ public class ArtifactInfo
+ {
+ public Artifact Artifact;
+ public string DownloadUrl;
+ }
+}
\ No newline at end of file
diff --git a/AppveyorClient/POCOs/Build.cs b/AppveyorClient/POCOs/Build.cs
new file mode 100644
index 00000000..fd10c82d
--- /dev/null
+++ b/AppveyorClient/POCOs/Build.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace AppveyorClient.POCOs
+{
+ public class Build
+ {
+ public string AuthorName;
+ public string AuthorUsername;
+ public string Branch;
+ public int BuildId;
+ public int BuildNumber;
+ public DateTime? Created;
+ public DateTime? Started;
+ public DateTime? Updated;
+ public DateTime? Finished;
+ public List Jobs;
+ public string Message;
+ public string PullRequestHeadBranch;
+ public string PullRequestHeadCommitId;
+ public string PullRequestHeadRepository;
+ public string PullRequestId;
+ public string PullRequestName;
+ public string Status;
+ public string Version;
+ }
+}
\ No newline at end of file
diff --git a/AppveyorClient/POCOs/BuildInfo.cs b/AppveyorClient/POCOs/BuildInfo.cs
new file mode 100644
index 00000000..a671020c
--- /dev/null
+++ b/AppveyorClient/POCOs/BuildInfo.cs
@@ -0,0 +1,10 @@
+using System.Text;
+
+namespace AppveyorClient.POCOs
+{
+ public class BuildInfo
+ {
+ public Build Build;
+ public Project Project;
+ }
+}
diff --git a/AppveyorClient/POCOs/Job.cs b/AppveyorClient/POCOs/Job.cs
new file mode 100644
index 00000000..15d4bd58
--- /dev/null
+++ b/AppveyorClient/POCOs/Job.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace AppveyorClient.POCOs
+{
+ public class Job
+ {
+ public int ArtifactsCount;
+ public int CompilationErrorsCount;
+ public DateTime? Created;
+ public DateTime? Started;
+ public DateTime? Updated;
+ public DateTime? Finished;
+ public string OsType;
+ public string Status;
+ public string JobId;
+ }
+}
\ No newline at end of file
diff --git a/AppveyorClient/POCOs/Project.cs b/AppveyorClient/POCOs/Project.cs
new file mode 100644
index 00000000..128d5458
--- /dev/null
+++ b/AppveyorClient/POCOs/Project.cs
@@ -0,0 +1,4 @@
+namespace AppveyorClient.POCOs
+{
+ public class Project { }
+}
\ No newline at end of file
diff --git a/CompatApiClient/Utils/Utils.cs b/CompatApiClient/Utils/Utils.cs
index ae9bc80c..dbbe0bbb 100644
--- a/CompatApiClient/Utils/Utils.cs
+++ b/CompatApiClient/Utils/Utils.cs
@@ -4,6 +4,10 @@ namespace CompatApiClient.Utils
{
public static class Utils
{
+ private const long UnderKB = 1000;
+ private const long UnderMB = 1000 * 1024;
+ private const long UnderGB = 1000 * 1024 * 1024;
+
public static string Trim(this string str, int maxLength)
{
const int minSaneLimit = 4;
@@ -43,5 +47,16 @@ namespace CompatApiClient.Utils
{
return Math.Min(high, Math.Max(amount, low));
}
+
+ public static string AsStorageUnit(this long bytes)
+ {
+ if (bytes < UnderKB)
+ return $"{bytes} byte{(bytes == 1 ? "" : "s")}";
+ if (bytes < UnderMB)
+ return $"{bytes / 1024.0:0.##} KB";
+ if (bytes < UnderGB)
+ return $"{bytes / 1024.0 / 1024:0.##} MB";
+ return $"{bytes / 1024.0 / 1024 / 1024:0.##} GB";
+ }
}
}
diff --git a/CompatBot/Commands/Pr.cs b/CompatBot/Commands/Pr.cs
index 1ac00c27..1d9cff0f 100644
--- a/CompatBot/Commands/Pr.cs
+++ b/CompatBot/Commands/Pr.cs
@@ -1,5 +1,8 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Threading.Tasks;
+using CompatApiClient.Utils;
+using CompatBot.Utils;
using CompatBot.Utils.ResultFormatters;
using DSharpPlus.CommandsNext;
using DSharpPlus.CommandsNext.Attributes;
@@ -12,12 +15,19 @@ namespace CompatBot.Commands
internal sealed class Pr: BaseCommandModuleCustom
{
private static readonly GithubClient.Client githubClient = new GithubClient.Client();
+ private static readonly AppveyorClient.Client appveyorClient = new AppveyorClient.Client();
private const string appveyorContext = "continuous-integration/appveyor/pr";
[GroupCommand]
public async Task List(CommandContext ctx, [Description("Get information for specific PR number")] int pr)
{
var prInfo = await githubClient.GetPrInfoAsync(pr.ToString(), Config.Cts.Token).ConfigureAwait(false);
+ if (prInfo.Number == 0)
+ {
+ await ctx.ReactWithAsync(Config.Reactions.Failure, prInfo.Message ?? "PR not found").ConfigureAwait(false);
+ return;
+ }
+
var embed = prInfo.AsEmbed();
if (prInfo.State == "open")
{
@@ -25,16 +35,27 @@ namespace CompatBot.Commands
{
var statuses = await githubClient.GetStatusesAsync(statusesUrl, Config.Cts.Token).ConfigureAwait(false);
statuses = statuses?.Where(s => s.Context == appveyorContext).ToList();
+ var downloadHeader = "PR Build Download";
var downloadText = "";
if (statuses?.Count > 0)
{
if (statuses.FirstOrDefault(s => s.State == "success") is StatusInfo statusSuccess)
- downloadText = $"[⏬ {statusSuccess.Description}]({statusSuccess.TargetUrl})";
+ {
+ var artifactInfo = await appveyorClient.GetPrDownloadAsync(statusSuccess.TargetUrl, Config.Cts.Token).ConfigureAwait(false);
+ if (artifactInfo == null)
+ downloadText = $"[⏬ {statusSuccess.Description}]({statusSuccess.TargetUrl})";
+ else
+ {
+ if (artifactInfo.Artifact.Created is DateTime buildTime)
+ downloadHeader = $"{downloadHeader} ({buildTime:u})";
+ downloadText = $"[⏬ {artifactInfo.Artifact.FileName}]({artifactInfo.DownloadUrl})";
+ }
+ }
else
downloadText = statuses.First().Description;
}
if (!string.IsNullOrEmpty(downloadText))
- embed.AddField("AppVeyor Download", downloadText);
+ embed.AddField(downloadHeader, downloadText);
}
}
await ctx.RespondAsync(embed: embed).ConfigureAwait(false);
diff --git a/CompatBot/CompatBot.csproj b/CompatBot/CompatBot.csproj
index 87c7ff6b..2ed8758c 100644
--- a/CompatBot/CompatBot.csproj
+++ b/CompatBot/CompatBot.csproj
@@ -32,6 +32,7 @@
+
diff --git a/CompatBot/Utils/ResultFormatters/TitlePatchFormatter.cs b/CompatBot/Utils/ResultFormatters/TitlePatchFormatter.cs
index 992e1ddd..3aa5d6b4 100644
--- a/CompatBot/Utils/ResultFormatters/TitlePatchFormatter.cs
+++ b/CompatBot/Utils/ResultFormatters/TitlePatchFormatter.cs
@@ -13,10 +13,6 @@ namespace CompatBot.Utils.ResultFormatters
{
internal static class TitlePatchFormatter
{
- private const long UnderKB = 1000;
- private const long UnderMB = 1000 * 1024;
- private const long UnderGB = 1000 * 1024 * 1024;
-
// thanks BCES00569
public static async Task> AsEmbedAsync(this TitlePatch patch, DiscordClient client, string productCode)
{
@@ -77,16 +73,5 @@ namespace CompatBot.Utils.ResultFormatters
catch { }
return fname;
}
-
- private static string AsStorageUnit(this long bytes)
- {
- if (bytes < UnderKB)
- return $"{bytes} byte{StringUtils.GetSuffix(bytes)}";
- if (bytes < UnderMB)
- return $"{bytes / 1024.0:0.##} KB";
- if (bytes < UnderGB)
- return $"{bytes / 1024.0 / 1024:0.##} MB";
- return $"{bytes / 1024.0 / 1024 / 1024:0.##} GB";
- }
}
}
diff --git a/GithubClient/POCOs/PrInfo.cs b/GithubClient/POCOs/PrInfo.cs
index ea42c4eb..6ed5aa7f 100644
--- a/GithubClient/POCOs/PrInfo.cs
+++ b/GithubClient/POCOs/PrInfo.cs
@@ -17,6 +17,7 @@ namespace GithubClient.POCOs
public int Additions;
public int Deletions;
public int ChangedFiles;
+ public string Message;
}
public class GithubUser
diff --git a/discord-bot-net.sln b/discord-bot-net.sln
index 0a9a4d47..8bbce05c 100644
--- a/discord-bot-net.sln
+++ b/discord-bot-net.sln
@@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Clients", "Clients", "{E7FE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GithubClient", "GithubClient\GithubClient.csproj", "{AF8FDA29-864E-4A1C-9568-99DECB7E4B36}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppveyorClient", "AppveyorClient\AppveyorClient.csproj", "{595ED201-1456-49F9-AD60-54B08499A5C1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +54,10 @@ Global
{AF8FDA29-864E-4A1C-9568-99DECB7E4B36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF8FDA29-864E-4A1C-9568-99DECB7E4B36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF8FDA29-864E-4A1C-9568-99DECB7E4B36}.Release|Any CPU.Build.0 = Release|Any CPU
+ {595ED201-1456-49F9-AD60-54B08499A5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {595ED201-1456-49F9-AD60-54B08499A5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {595ED201-1456-49F9-AD60-54B08499A5C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {595ED201-1456-49F9-AD60-54B08499A5C1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -61,6 +67,7 @@ Global
{AA5FF441-BD1D-4444-9178-7DC7BFF3C139} = {E7FE0ADD-CBA6-4321-8A1C-0A3B5C3F54C2}
{AA2A333B-CD30-41A5-A680-CC9BCB2D726B} = {E7FE0ADD-CBA6-4321-8A1C-0A3B5C3F54C2}
{AF8FDA29-864E-4A1C-9568-99DECB7E4B36} = {E7FE0ADD-CBA6-4321-8A1C-0A3B5C3F54C2}
+ {595ED201-1456-49F9-AD60-54B08499A5C1} = {E7FE0ADD-CBA6-4321-8A1C-0A3B5C3F54C2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D7696F56-AEAC-4D83-9BD8-BE0C122A5DCE}