using CommandLine; using Newtonsoft.Json; using System.Diagnostics.CodeAnalysis; namespace RegexBot; class Configuration { /// /// Token used for Discord authentication. /// internal string BotToken { get; } /// /// List of assemblies to load, by file. Paths are always relative to the bot directory. /// internal IReadOnlyList Assemblies { get; } public JObject ServerConfigs { get; } // SQL properties: public string? Host { get; } public string? Database { get; } public string Username { get; } public string Password { get; } /// /// Sets up instance configuration object from file and command line parameters. /// internal Configuration() { var args = CommandLineParameters.Parse(Environment.GetCommandLineArgs()); var path = args?.ConfigFile!; JObject conf; try { var conftxt = File.ReadAllText(path); conf = JObject.Parse(conftxt); } catch (Exception ex) { string pfx; if (ex is JsonException) pfx = "Unable to parse configuration: "; else pfx = "Unable to access configuration: "; throw new Exception(pfx + ex.Message, ex); } BotToken = ReadConfKey(conf, nameof(BotToken), true); try { Assemblies = Common.Utilities.LoadStringOrStringArray(conf[nameof(Assemblies)]).AsReadOnly(); } catch (ArgumentNullException) { Assemblies = Array.Empty(); } catch (ArgumentException) { throw new Exception($"'{nameof(Assemblies)}' is not properly specified in configuration."); } var dbconf = conf["DatabaseOptions"]?.Value(); if (dbconf == null) throw new Exception("Database settings were not specified in configuration."); // TODO more detailed database configuration? password file, other advanced authentication settings... look into this. Host = ReadConfKey(dbconf, nameof(Host), false); Database = ReadConfKey(dbconf, nameof(Database), false); Username = ReadConfKey(dbconf, nameof(Username), true); Password = ReadConfKey(dbconf, nameof(Password), true); ServerConfigs = conf["Servers"]?.Value(); if (ServerConfigs == null) throw new Exception("No server configurations were specified."); } private static T? ReadConfKey(JObject jc, string key, [DoesNotReturnIf(true)] bool failOnEmpty) { if (jc.ContainsKey(key)) return jc[key]!.Value(); if (failOnEmpty) throw new Exception($"'{key}' must be specified in the instance configuration."); return default; } class CommandLineParameters { [Option('c', "config", Default = "config.json")] public string? ConfigFile { get; set; } = null; public static CommandLineParameters? Parse(string[] args) { CommandLineParameters? result = null; new Parser(settings => { settings.IgnoreUnknownArguments = true; settings.AutoHelp = false; settings.AutoVersion = false; }).ParseArguments(args) .WithParsed(p => result = p) .WithNotParsed(e => { /* ignore */ }); return result; } } }