9bb4f85802
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
102 lines
4.7 KiB
C#
102 lines
4.7 KiB
C#
using System.Net.Http.Headers;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using CVRManager.Models;
|
|
|
|
namespace CVRManager.Services;
|
|
|
|
public record CvrmgMod(
|
|
int Id, string Name, string[] Aliases, string Category, string ModVersion, string GameVersion, string LoaderVersion,
|
|
string Type, string Author, string Description, string DownloadLink, string SourceLink, string Hash, int ApprovalStatus, DateTime Updated)
|
|
{
|
|
public string StatusText => ApprovalStatus switch { 1 => "approved", 2 => "outdated", 3 => "obsolete", _ => "unknown" };
|
|
public string ModType => Type.Equals("Plugin", StringComparison.OrdinalIgnoreCase) ? "Plugins" : "Mods";
|
|
}
|
|
|
|
public class CvrmgService
|
|
{
|
|
const string ListUrl = "https://api.cvrmg.com/v1/mods";
|
|
readonly HttpClient http = new();
|
|
List<CvrmgMod>? cache;
|
|
DateTime fetched;
|
|
|
|
public CvrmgService()
|
|
{
|
|
http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("CVRManager", "1.0"));
|
|
}
|
|
|
|
public static string Normalize(string s) => Regex.Replace(s ?? "", "[^a-z0-9]", "", RegexOptions.IgnoreCase).ToLowerInvariant();
|
|
|
|
public async Task<List<CvrmgMod>> GetAllAsync(CancellationToken ct, bool force = false)
|
|
{
|
|
if (!force && cache != null && DateTime.UtcNow - fetched < TimeSpan.FromMinutes(10)) return cache;
|
|
var path = Path.Combine(Paths.Root, "sources", "cvrmg.json");
|
|
string? json = null;
|
|
if (!force && File.Exists(path) && DateTime.UtcNow - File.GetLastWriteTimeUtc(path) < TimeSpan.FromHours(1))
|
|
json = await File.ReadAllTextAsync(path, ct);
|
|
if (json == null)
|
|
{
|
|
try
|
|
{
|
|
using var resp = await http.GetAsync(ListUrl, ct);
|
|
if (!resp.IsSuccessStatusCode) throw new InvalidOperationException($"CVRMG returned {(int)resp.StatusCode}.");
|
|
json = await resp.Content.ReadAsStringAsync(ct);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
await File.WriteAllTextAsync(path, json, ct);
|
|
}
|
|
catch (Exception e) when (e is not OperationCanceledException && File.Exists(path))
|
|
{
|
|
json = await File.ReadAllTextAsync(path, ct);
|
|
}
|
|
}
|
|
using var doc = JsonDocument.Parse(json);
|
|
var list = new List<CvrmgMod>();
|
|
foreach (var m in doc.RootElement.EnumerateArray())
|
|
{
|
|
if (!m.TryGetProperty("versions", out var versions) || versions.ValueKind != JsonValueKind.Array) continue;
|
|
JsonElement? best = null;
|
|
var bestNo = int.MinValue;
|
|
foreach (var v in versions.EnumerateArray())
|
|
{
|
|
var no = v.TryGetProperty("_version", out var n) && n.ValueKind == JsonValueKind.Number ? n.GetInt32() : 0;
|
|
if (no >= bestNo) { best = v; bestNo = no; }
|
|
}
|
|
if (best is not { } ver) continue;
|
|
|
|
string S(JsonElement e, string k) => e.TryGetProperty(k, out var p) && p.ValueKind == JsonValueKind.String ? p.GetString() ?? "" : "";
|
|
var aliases = m.TryGetProperty("aliases", out var al) && al.ValueKind == JsonValueKind.Array
|
|
? al.EnumerateArray().Where(a => a.ValueKind == JsonValueKind.String).Select(a => a.GetString()!).ToArray()
|
|
: Array.Empty<string>();
|
|
DateTime.TryParse(S(ver, "updateDate"), null, System.Globalization.DateTimeStyles.AdjustToUniversal, out var updated);
|
|
list.Add(new CvrmgMod(
|
|
m.GetProperty("_id").GetInt32(), S(ver, "name"), aliases, S(m, "category"),
|
|
S(ver, "modVersion"), S(ver, "gameVersion"), S(ver, "loaderVersion").TrimStart('v'), S(ver, "modType"),
|
|
S(ver, "author"), S(ver, "description"), S(ver, "downloadLink"), S(ver, "sourceLink"), S(ver, "hash"),
|
|
ver.TryGetProperty("approvalStatus", out var st) && st.ValueKind == JsonValueKind.Number ? st.GetInt32() : 0, updated));
|
|
}
|
|
cache = list;
|
|
fetched = DateTime.UtcNow;
|
|
return list;
|
|
}
|
|
|
|
public static ReleaseInfo ToRelease(CvrmgMod m, string fileName) => new()
|
|
{
|
|
Tag = m.ModVersion,
|
|
Published = m.Updated,
|
|
GameVersion = m.GameVersion,
|
|
LoaderVersion = m.LoaderVersion,
|
|
Assets = { new AssetInfo { Name = fileName, Url = m.DownloadLink, Hash = m.Hash } },
|
|
};
|
|
|
|
public static void ApplyMeta(ModDef def, CvrmgMod m)
|
|
{
|
|
def.CvrmgId = m.Id;
|
|
if (def.Author.Length == 0) def.Author = m.Author;
|
|
def.Description = m.Description;
|
|
def.LatestVersion = m.ModVersion;
|
|
def.Category = m.Category;
|
|
def.Status = m.StatusText;
|
|
if (def.SourceLink.Length == 0) def.SourceLink = m.SourceLink;
|
|
}
|
|
}
|