using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using Pathoschild.Http.Client; using StardewModdingAPI.Toolkit.Framework.UpdateData; using StardewModdingAPI.Web.Framework.Clients.CurseForge.ResponseModels; namespace StardewModdingAPI.Web.Framework.Clients.CurseForge { /// An HTTP client for fetching mod metadata from the CurseForge API. internal class CurseForgeClient : ICurseForgeClient { /********* ** Fields *********/ /// The underlying HTTP client. private readonly IClient Client; /// A regex pattern which matches a version number in a CurseForge mod file name. private readonly Regex VersionInNamePattern = new(@"^(?:.+? | *)v?(\d+\.\d+(?:\.\d+)?(?:-.+?)?) *(?:\.(?:zip|rar|7z))?$", RegexOptions.Compiled); /********* ** Accessors *********/ /// The unique key for the mod site. public ModSiteKey SiteKey => ModSiteKey.CurseForge; /********* ** Public methods *********/ /// Construct an instance. /// The user agent for the API client. /// The base URL for the CurseForge API. /// The API authentication key. public CurseForgeClient(string userAgent, string apiUrl, string apiKey) { this.Client = new FluentClient(apiUrl) .SetUserAgent(userAgent) .AddDefault(request => request.WithHeader("x-api-key", apiKey)); } /// Get update check info about a mod. /// The mod ID. public async Task GetModData(string id) { IModPage page = new GenericModPage(this.SiteKey, id); // get ID if (!uint.TryParse(id, out uint parsedId)) return page.SetError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid CurseForge mod ID, must be an integer ID."); // get raw data ModModel? mod; try { ResponseModel response = await this.Client .GetAsync($"mods/{parsedId}") .As>(); mod = response.Data; } catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound) { return page.SetError(RemoteModStatus.DoesNotExist, "Found no CurseForge mod with this ID."); } // get downloads List downloads = new List(); foreach (ModFileModel file in mod.LatestFiles) { downloads.Add( new GenericModDownload(name: file.DisplayName ?? file.FileName, description: null, version: this.GetRawVersion(file)) ); } // return info return page.SetInfo(name: mod.Name, version: null, url: mod.Links.WebsiteUrl, downloads: downloads); } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { this.Client.Dispose(); } /********* ** Private methods *********/ /// Get a raw version string for a mod file, if available. /// The file whose version to get. private string? GetRawVersion(ModFileModel file) { Match match = this.VersionInNamePattern.Match(file.DisplayName ?? ""); if (!match.Success) match = this.VersionInNamePattern.Match(file.FileName); return match.Success ? match.Groups[1].Value : null; } } }