diff --git a/services/grid-bot/lib/commands/PrivateModules/Commands/Settings.cs b/services/grid-bot/lib/commands/PrivateModules/Commands/Settings.cs new file mode 100755 index 00000000..7d7e8ba7 --- /dev/null +++ b/services/grid-bot/lib/commands/PrivateModules/Commands/Settings.cs @@ -0,0 +1,311 @@ +namespace Grid.Bot.Commands.Private; + +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +using Discord; +using Discord.Commands; + +using Newtonsoft.Json; + +using Vault; +using Configuration; + +using Utility; +using Extensions; + +/// +/// Represents the interaction for settings. +/// +[LockDownCommand(BotRole.Owner)] +[RequireBotRole(BotRole.Owner)] +[Group("settings"), Summary("Commands used for managing app settings.")] +public partial class Settings(IServiceProvider services) : ModuleBase +{ + private class ProviderStringConverter : BaseProvider + { + public static ProviderStringConverter Singleton = new(); + + public object ConvertToPub(string value, Type type) => ConvertTo(value, type); + public string ConvertFromPub(object value, Type type) => ConvertFrom(value, type); + + protected override bool GetRawValue(string key, out string value) + { + throw new NotImplementedException(); + } + protected override void SetRawValue(string key, T value) + { + throw new NotImplementedException(); + } + } + + private readonly IServiceProvider _services = services ?? throw new ArgumentNullException(nameof(services)); + + private const string _namespace = "Grid.Bot"; + private static readonly Assembly _settingsAssembly = Assembly.Load("Shared.Settings"); + private static readonly Assembly _configAssembly = Assembly.Load("Configuration"); + + [GeneratedRegex(@"^([a-zA-Z]+)Settings$", RegexOptions.IgnoreCase | RegexOptions.Compiled)] + private partial Regex GetProviderNameRegex(); + + /// + /// Gets a list of settings providers that can be modified. + /// + [Command("info"), Summary("Gets information about settings such as environment and versions.")] + public async Task GetInformationAsync() + { + var builder = new EmbedBuilder() + .WithTitle("Configuration Information") + .WithAuthor(Context.User) + .WithCurrentTimestamp() + .WithColor(Color.Green); + + var isUsingVault = VaultClientFactory.Singleton.GetClient() != null ? "yes" : "no"; + var settingsAssemblyVersion = _settingsAssembly.GetName().Version; + var configurationAssemblyVersion = _configAssembly.GetName().Version; + + var environment = Grid.Bot.EnvironmentProvider.EnvironmentName; + + builder.AddField("Settings Version", settingsAssemblyVersion, true) + .AddField("Configuration Version", configurationAssemblyVersion, true) + .AddField("Environment", environment, true) + .AddField("Is using Vault", isUsingVault, true); + + await this.ReplyWithReferenceAsync(embed: builder.Build()); + } + + /// + /// Gets a list of settings providers that can be modified. + /// + [Command("providers"), Summary("Lists the names of all available providers.")] + public async Task ListProvidersAsync() + { + var providerTypes = _settingsAssembly.GetTypes().Where(type => type.BaseType == typeof(BaseSettingsProvider)); + + var builder = new EmbedBuilder() + .WithTitle("Providers list") + .WithAuthor(Context.User) + .WithCurrentTimestamp() + .WithColor(Color.Green); + + var desc = "```\n"; + + foreach (var provider in providerTypes) + desc += $"{GetProviderNameRegex().Match(provider.Name).Groups[1]}\n"; + + desc += "```"; + + builder.WithDescription(desc); + + await this.ReplyWithReferenceAsync(embed: builder.Build()); + } + + + /// + /// Gets the settings for the specified provider. + /// + /// The name of the provider. + /// Should the prvoider be refreshed beforehand? + [Command("all"), Summary("Gets all settings for the specified provider."), Alias("list")] + public async Task GetAllAsync(string provider, bool refresh = true) + { + using var _ = Context.Channel.EnterTypingState(); + + var fullName = $"{provider}settings"; + + var type = _settingsAssembly.GetType($"{_namespace}.{fullName}", false, true); + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + var instance = _services.GetService(type) as BaseSettingsProvider; + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + if (refresh) instance.Refresh(); + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(instance.GetRawValues(), Formatting.Indented))); + + await this.ReplyWithFileAsync(stream, $"{provider}.json", "Here are the settings for the specified provider."); + } + + /// + /// Get the value of the specified setting. + /// + /// The name of the provider + /// The name of the setting + /// Should the prvoider be refreshed beforehand? + [Command("get"), Summary("Get the value of the specified setting.")] + public async Task GetSettingAsync(string provider, string settingName, bool refresh = true) + { + using var _ = Context.Channel.EnterTypingState(); + + var fullName = $"{provider}settings"; + + var type = _settingsAssembly.GetType($"{_namespace}.{fullName}", false, true); + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + var instance = _services.GetService(type) as BaseSettingsProvider; + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + var property = type.GetProperty(settingName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public); + if (property == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} does not define the setting {settingName}!"); + + return; + } + + if (refresh) instance.Refresh(); + + var value = property.GetMethod.Invoke(instance, []); + if (value is not string) value = JsonConvert.SerializeObject(value); + if (string.IsNullOrEmpty(value as string)) value = "(empty)"; + + var embed = new EmbedBuilder() + .WithTitle($"{type.Name}.{property.Name} ({property.PropertyType.Name})") + .WithDescription($"```{value}```") + .WithAuthor(Context.User) + .WithCurrentTimestamp() + .WithColor(Color.Green) + .Build(); + + await this.ReplyWithReferenceAsync(embed: embed); + } + + /// + /// Sets the specified setting to the specified value. + /// + /// The name of the provider + /// The name of the setting + /// The new value of the setting + /// Should the prvoider be refreshed beforehand? + [Command("set"), Summary("Sets the specified setting to the specified value.")] + public async Task SetSettingAsync(string provider, string settingName, string newValue = "", bool refresh = true) + { + using var _ = Context.Channel.EnterTypingState(); + + var fullName = $"{provider}settings"; + + var type = _settingsAssembly.GetType($"{_namespace}.{fullName}", false, true); + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + var instance = _services.GetService(type) as BaseSettingsProvider; + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + var property = type.GetProperty(settingName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public); + if (property == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} does not define the setting {settingName}!"); + + return; + } + + if (refresh) instance.Refresh(); + + var value = property.GetMethod.Invoke(instance, []); + var converted = ProviderStringConverter.Singleton.ConvertToPub(newValue, property.PropertyType); + + if (value.Equals(converted)) + { + await this.ReplyWithReferenceAsync("The value is identical to the current value, not changing!"); + + return; + } + + var attribute = property.GetCustomAttribute(); + var name = attribute?.Name ?? property.Name; + + var genericSet = type.GetMethod("Set", BindingFlags.Instance | BindingFlags.Public).MakeGenericMethod([ property.PropertyType ]); + + genericSet.Invoke(instance, [name, converted]); + + if (value is not string) value = JsonConvert.SerializeObject(value); + if (string.IsNullOrEmpty(value as string)) value = "(empty)"; + + var embed = new EmbedBuilder() + .WithTitle($"{type.Name}.{property.Name} ({property.PropertyType.Name})") + .AddField("Before", $"```{value}```") + .AddField("After", $"```{newValue}```") + .WithAuthor(Context.User) + .WithCurrentTimestamp() + .WithColor(Color.Green) + .Build(); + + await this.ReplyWithReferenceAsync(embed: embed); + } + + /// + /// Refreshes the specified provider or all registered providers. + /// + /// The name of the provider. + [Command("refresh"), Summary("Refreshes the specified provider or all registered providers.")] + public async Task RefreshAsync(string provider = "") + { + using var _ = Context.Channel.EnterTypingState(); + + if (string.IsNullOrEmpty(provider)) + { + VaultProvider.RefreshAllProviders(); + + await this.ReplyWithReferenceAsync("Refreshed all registered providers!"); + + return; + } + + var fullName = $"{provider}settings"; + + var type = _settingsAssembly.GetType($"{_namespace}.{fullName}", false, true); + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + var instance = _services.GetService(type) as BaseSettingsProvider; + if (type == null) + { + await this.ReplyWithReferenceAsync($"The settings provider with the name {provider} was not found!"); + + return; + } + + instance.Refresh(); + + await this.ReplyWithReferenceAsync($"Successfully refreshed the {provider} settings provider!"); + } +} diff --git a/services/grid-bot/lib/settings/Providers/BaseSettingsProvider.cs b/services/grid-bot/lib/settings/Providers/BaseSettingsProvider.cs index 7b09e472..b1446db7 100755 --- a/services/grid-bot/lib/settings/Providers/BaseSettingsProvider.cs +++ b/services/grid-bot/lib/settings/Providers/BaseSettingsProvider.cs @@ -1,5 +1,7 @@ namespace Grid.Bot; +using System.Collections.Generic; + using Configuration; /// @@ -17,4 +19,10 @@ protected BaseSettingsProvider() : base(Logging.Logger.Singleton) { } + + /// + /// Gets the raw values associated with this settings provider. + /// + /// Te raw values. + public IDictionary GetRawValues() => _CachedValues; }