using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.IO; using System.Linq; using System.Reflection; namespace Kerobot { /// /// Contains instance configuration for this bot, /// including Discord connection settings and service configuration. /// class InstanceConfig { const string JBotToken = "BotToken"; readonly string _botToken; /// /// Token used for Discord authentication. /// internal string BotToken => _botToken; const string JAssemblies = "Assemblies"; readonly string[] _enabledAssemblies; /// /// List of assemblies to load, by file. Paths are always relative to the bot directory. /// internal string[] EnabledAssemblies => _enabledAssemblies; const string JPgSqlConnectionString = "SqlConnectionString"; readonly string _pgSqlConnectionString; /// /// Connection string for accessing the PostgreSQL database. /// /// /// That's right, the user can specify the -entire- thing. /// Should problems arise, this will be replaced by a full section within configuration. /// internal string PostgresConnString => _pgSqlConnectionString; const string JInstanceLogReportTarget = "LogTarget"; readonly ulong _ilReptGuild, _ilReptChannel; /// /// Guild and channel ID, respectively, for instance log reporting. /// Specified as "(guild ID)/(channel ID)". /// internal (ulong, ulong) InstanceLogReportTarget => (_ilReptGuild, _ilReptChannel); // TODO add fields for services to be configurable: DMRelay /// /// Sets up instance configuration object from file and command line parameters. /// /// Path to file from which to load configuration. If null, uses default path. internal InstanceConfig(Options options) { string path = options.ConfigFile; if (path == null) // default: config.json in working directory { path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "." + Path.DirectorySeparatorChar + "config.json"; } 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); } // Input validation - throw exception on errors. Exception messages printed as-is. _botToken = conf[JBotToken]?.Value(); if (string.IsNullOrEmpty(_botToken)) throw new Exception($"'{JBotToken}' is not properly specified in configuration."); _pgSqlConnectionString = conf[JPgSqlConnectionString]?.Value(); if (string.IsNullOrEmpty(_pgSqlConnectionString)) throw new Exception($"'{JPgSqlConnectionString}' is not properly specified in configuration."); var asmList = conf[JAssemblies]; if (asmList == null || asmList.Type != JTokenType.Array) { throw new Exception($"'{JAssemblies}' is not properly specified in configuration."); } _enabledAssemblies = asmList.Values().ToArray(); var ilInput = conf[JInstanceLogReportTarget]?.Value(); if (!string.IsNullOrWhiteSpace(ilInput)) { int idx = ilInput.IndexOf('/'); if (idx < 0) throw new Exception($"'{JInstanceLogReportTarget}' is not properly specified in configuration."); try { _ilReptGuild = ulong.Parse(ilInput.Substring(0, idx)); _ilReptChannel = ulong.Parse(ilInput.Substring(idx + 1, ilInput.Length - (idx + 1))); } catch (FormatException) { throw new Exception($"'{JInstanceLogReportTarget}' is not properly specified in configuration."); } } else { // Feature is disabled _ilReptGuild = 0; _ilReptChannel = 0; } } } }