using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Newtonsoft.Json; namespace Dalamud.Plugin.Internal { /// /// This class represents a single plugin repository. /// internal class PluginRepository { private const string DalamudPluginsMasterUrl = "https://kamori.goats.dev/Plugin/PluginMaster"; private static readonly ModuleLog Log = new("PLUGINR"); private static readonly HttpClient HttpClient = new() { DefaultRequestHeaders = { CacheControl = new CacheControlHeaderValue { NoCache = true, }, }, }; /// /// Initializes a new instance of the class. /// /// The plugin master URL. /// Whether the plugin repo is enabled. public PluginRepository(string pluginMasterUrl, bool isEnabled) { this.PluginMasterUrl = pluginMasterUrl; this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl; this.IsEnabled = isEnabled; } /// /// Gets a new instance of the class for the main repo. /// public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true); /// /// Gets the pluginmaster.json URL. /// public string PluginMasterUrl { get; } /// /// Gets a value indicating whether this plugin repository is from a third party. /// public bool IsThirdParty { get; } /// /// Gets a value indicating whether this repo is enabled. /// public bool IsEnabled { get; } /// /// Gets the plugin master list of available plugins. /// public ReadOnlyCollection? PluginMaster { get; private set; } /// /// Gets the initialization state of the plugin repository. /// public PluginRepositoryState State { get; private set; } /// /// Reload the plugin master asynchronously in a task. /// /// The new state. public async Task ReloadPluginMasterAsync() { this.State = PluginRepositoryState.InProgress; this.PluginMaster = new List().AsReadOnly(); try { Log.Information($"Fetching repo: {this.PluginMasterUrl}"); using var response = await HttpClient.GetAsync(this.PluginMasterUrl); response.EnsureSuccessStatusCode(); var data = await response.Content.ReadAsStringAsync(); var pluginMaster = JsonConvert.DeserializeObject>(data); if (pluginMaster == null) { throw new Exception("Deserialized PluginMaster was null."); } pluginMaster.Sort((pm1, pm2) => pm1.Name.CompareTo(pm2.Name)); // Set the source for each remote manifest. Allows for checking if is 3rd party. foreach (var manifest in pluginMaster) { manifest.SourceRepo = this; } this.PluginMaster = pluginMaster.AsReadOnly(); Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}"); this.State = PluginRepositoryState.Success; } catch (Exception ex) { Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}"); this.State = PluginRepositoryState.Fail; } } } }