Files
CVRManager/Services/CvrmgService.cs
T
Exterrata 9bb4f85802 CVR Manager 1.0.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 02:46:35 -04:00

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;
}
}