diff --git a/MiniUtils/Utilities/MscInfoProvider.cs b/MiniUtils/Utilities/MscInfoProvider.cs
new file mode 100644
index 0000000..74788dc
--- /dev/null
+++ b/MiniUtils/Utilities/MscInfoProvider.cs
@@ -0,0 +1,91 @@
+using System.Globalization;
+using System.Net.Http.Json;
+using System.Text;
+using System.Text.Json.Nodes;
+using LibMatrix.Extensions;
+
+namespace MiniUtils.Utilities;
+
+public class MscInfoProvider(MiniUtilsConfiguration config) {
+ private static Dictionary<int, MscInfo> _mscCache = new();
+ private static Dictionary<int, DateTime> _mscCacheExpiry = new();
+ private static long RatelimitRemaining = 60;
+ private static DateTime RatelimitReset = DateTime.UtcNow;
+ public async Task<MscInfo?> GetMscInfo(int id) {
+ var hc = new MatrixHttpClient() {
+ DefaultRequestHeaders = {
+ { "User-Agent", "SynapseDataMiner" }
+ }
+ };
+
+ if(!string.IsNullOrWhiteSpace(config.GithubToken))
+ hc.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.GithubToken}");
+
+ var response = await hc.GetAsync($"https://api.github.com/repos/matrix-org/matrix-spec-proposals/pulls/{id}");
+ if(response.Headers.Contains("X-RateLimit-Remaining")) {
+ RatelimitRemaining = long.Parse(response.Headers.GetValues("X-RateLimit-Remaining").First());
+ }
+
+ if (response.Headers.Contains("X-RateLimit-Reset")) {
+ var reset = long.Parse(response.Headers.GetValues("X-RateLimit-Reset").First());
+ RatelimitReset = DateTimeOffset.FromUnixTimeSeconds(reset).UtcDateTime;
+ }
+
+ if (!response.IsSuccessStatusCode) {
+ return _mscCache.GetValueOrDefault(id);
+ }
+
+ var data = await response.Content.ReadFromJsonAsync<JsonObject>();
+ var info = new MscInfo() {
+ Id = id,
+ Author = data?["user"]?["login"]?.ToString(),
+ Title = data?["title"]?.ToString(),
+ Url = data?["html_url"]?.ToString(),
+ State = data?["state"]?.ToString(),
+ Labels = data?["labels"]?.AsArray().Select(x => new MscInfo.LabelInfo() {
+ Name = x["name"]?.ToString(),
+ Color = x["color"]?.ToString()
+ }).ToList() ?? []
+ };
+ _mscCache[id] = info;
+ _mscCacheExpiry[id] = DateTime.UtcNow.AddMinutes(60);
+ return info;
+ }
+
+ public class MscInfo {
+ public int Id { get; set; }
+ public string Title { get; set; }
+ public string State { get; set; }
+ public string Author { get; set; }
+ public string Url { get; set; }
+ public List<LabelInfo> Labels { get; set; }
+
+ public class LabelInfo {
+ public string Name { get; set; }
+ public string Color { get; set; }
+ }
+
+ public string ToHtml() {
+ var sb = new StringBuilder();
+ sb.Append($"<a href=\"{Url}\">{Title}</a> by {Author} ({State})<br/>");
+
+ foreach (var label in Labels) {
+ sb.Append($"<font color=\"#{label.Color}\">" +
+ $"<span data-mx-bg-color=\"#{label.Color}\" data-mx-color=\"{GetContrastingForegroundColor(label.Color)}\">{label.Name}</span>" +
+ $"</font> \n");
+ }
+
+ return sb + "\n";
+ }
+
+ private static string GetContrastingForegroundColor(string backgroundColor) {
+ var color = backgroundColor.Replace("#", "");
+ var r = int.Parse(color.Substring(0, 2), NumberStyles.HexNumber);
+ var g = int.Parse(color.Substring(2, 2), NumberStyles.HexNumber);
+ var b = int.Parse(color.Substring(4, 2), NumberStyles.HexNumber);
+ // var brightness = Math.Sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
+ var brightness = Math.Sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
+ return brightness < 130 ? "#ffffff" : "#000000";
+ }
+ }
+}
\ No newline at end of file
|