using CommandLine; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.RegularExpressions; namespace BirthdayBot; /// /// Loads and holds configuration values. /// class Configuration { const string KeyShardRange = "ShardRange"; public string BotToken { get; } public string? DBotsToken { get; } public int ShardStart { get; } public int ShardAmount { get; } public int ShardTotal { get; } public string? SqlHost { get; } public string? SqlDatabase { get; } public string SqlUsername { get; } public string SqlPassword { get; } internal string SqlApplicationName { get; } public Configuration() { var args = CommandLineParameters.Parse(Environment.GetCommandLineArgs()); var path = args?.ConfigFile ?? Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) + Path.DirectorySeparatorChar + "." + Path.DirectorySeparatorChar + "settings.json"; // Looks for configuration file JObject jc; try { var conftxt = File.ReadAllText(path); jc = 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(jc, nameof(BotToken), true); DBotsToken = ReadConfKey(jc, nameof(DBotsToken), false); ShardTotal = args.ShardTotal ?? ReadConfKey(jc, nameof(ShardTotal), false) ?? 1; if (ShardTotal < 1) throw new Exception($"'{nameof(ShardTotal)}' must be a positive integer."); var shardRangeInput = args.ShardRange ?? ReadConfKey(jc, KeyShardRange, false); if (!string.IsNullOrWhiteSpace(shardRangeInput)) { Regex srPicker = new(@"(?\d{1,2})[-,]{1}(?\d{1,2})"); var m = srPicker.Match(shardRangeInput); if (m.Success) { ShardStart = int.Parse(m.Groups["low"].Value); var high = int.Parse(m.Groups["high"].Value); ShardAmount = high - (ShardStart - 1); } else { throw new Exception($"Shard range not properly formatted in '{KeyShardRange}'."); } } else { // Default: this instance handles all shards ShardStart = 0; ShardAmount = ShardTotal; } SqlHost = ReadConfKey(jc, nameof(SqlHost), false); SqlDatabase = ReadConfKey(jc, nameof(SqlDatabase), false); SqlUsername = ReadConfKey(jc, nameof(SqlUsername), true); SqlPassword = ReadConfKey(jc, nameof(SqlPassword), true); SqlApplicationName = $"Shard{ShardStart:00}-{ShardStart + ShardAmount - 1:00}"; } 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."); return default; } class CommandLineParameters { [Option('c', "config")] public string? ConfigFile { get; set; } [Option("shardtotal")] public int? ShardTotal { get; set; } [Option("shardrange")] public string? ShardRange { get; set; } 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; } } }