diff --git a/MinecraftDiscordBot/BotConfiguration.cs b/MinecraftDiscordBot/BotConfiguration.cs index 1a3af65..881f039 100644 --- a/MinecraftDiscordBot/BotConfiguration.cs +++ b/MinecraftDiscordBot/BotConfiguration.cs @@ -1,14 +1,46 @@ -using Newtonsoft.Json; +using CommandLine; +using Newtonsoft.Json; +using System.Text; namespace MinecraftDiscordBot; -public class BotConfiguration { +[Verb("config", HelpText = "Manually configure the bot with CLI arguments.")] +public class BotConfiguration : IBotConfiguration, IBotConfigurator { + private const string DEFAULT_PREFIX = "!"; + private const int DEFAULT_PORT = 8080; [JsonProperty("token", Required = Required.Always)] - public string Token { get; set; } = default!; - [JsonProperty("port", Required = Required.Always)] - public int Port { get; set; } = default!; + [Option('t', "token", HelpText = "The Discord bot token", Required = true)] + public string Token { get; init; } = default!; + [JsonProperty("port", Required = Required.DisallowNull)] + [Option('p', "port", Default = DEFAULT_PORT, HelpText = "The websocket server port")] + public int Port { get; init; } = DEFAULT_PORT; [JsonProperty("channels", Required = Required.Always)] - public IEnumerable Channels { get; set; } = default!; - [JsonProperty("prefix", Required = Required.Always)] - public string Prefix { get; set; } = default!; + [Option('c', "channel", HelpText = "The list of whitelisted channels", Required = true, Min = 1)] + public IEnumerable Channels { get; init; } = default!; + [JsonProperty("prefix", Required = Required.DisallowNull)] + [Option("prefix", Default = DEFAULT_PREFIX, HelpText = "The Discord bot command prefix")] + public string Prefix { get; init; } = DEFAULT_PREFIX; + [JsonIgnore] + public BotConfiguration Config => this; } + +public interface IBotConfigurator { + BotConfiguration Config { get; } +} + +public interface IBotConfiguration { + string Token { get; } + int Port { get; } + IEnumerable Channels { get; } + string Prefix { get; } +} + +[Verb("file", true, HelpText = "Load a bot configuration file.")] +public class ConfigFile : IBotConfigurator { + private const string DEFAULT_CONFIGPATH = "config.json"; + [Option('f', "file", Default = DEFAULT_CONFIGPATH, HelpText = "The path of the configuration file")] + public string ConfigPath { get; set; } = DEFAULT_CONFIGPATH; + public BotConfiguration Config + => JsonConvert.DeserializeObject(File.ReadAllText(ConfigPath, Encoding.UTF8)) + ?? throw new InvalidProgramException("Invalid empty config file!"); +} \ No newline at end of file diff --git a/MinecraftDiscordBot/Program.cs b/MinecraftDiscordBot/Program.cs index 226db04..8c01e7d 100644 --- a/MinecraftDiscordBot/Program.cs +++ b/MinecraftDiscordBot/Program.cs @@ -1,3 +1,4 @@ +using CommandLine; using Discord; using Discord.Commands; using Discord.Rest; @@ -60,14 +61,19 @@ public class Program : IDisposable { public static Task Main(string[] args) - => JsonConvert.DeserializeObject(File.ReadAllText("config.json")) is BotConfiguration config - ? new Program(config).RunAsync() - : throw new InvalidProgramException("Configuration file missing!"); + => Parser.Default.ParseArguments(args) + .MapResult>( + RunWithConfig, + RunWithConfig, + errs => Task.FromResult(1)); + + private static Task RunWithConfig(IBotConfigurator arg) => new Program(arg.Config).RunAsync(); public async Task RunAsync() { await _client.LoginAsync(TokenType.Bot, _config.Token); await _client.StartAsync(); - await VerifyTextChannels(); + if (!await HasValidChannels()) + return 1; StartWebSocketServer(); // Block this task until the program is closed. @@ -75,11 +81,21 @@ public class Program : IDisposable { return 0; } + private async Task HasValidChannels() { + if (await GetValidChannels(_whitelistedChannels).ToArrayAsync() is not { Length: > 0 } channels) { + await LogError(BotSource, new InvalidOperationException("No valid textchannel was whitelisted!")); + return false; + } + _channels = channels; + return true; + } + private void StartWebSocketServer() => _wssv.Start(socket => { socket.OnOpen = async () => await SocketOpened(socket); socket.OnClose = async () => await SocketClosed(socket); socket.OnMessage = async message => await SocketReceived(socket, message); }); + private async IAsyncEnumerable GetValidChannels(IEnumerable ids) { foreach (var channelId in ids) { var channel = await _client.GetChannelAsync(channelId); @@ -99,8 +115,6 @@ public class Program : IDisposable { } } - private async Task VerifyTextChannels() => _channels = await GetValidChannels(_whitelistedChannels).ToArrayAsync(); - private async Task SocketReceived(IWebSocketConnection socket, string message) { if (JsonConvert.DeserializeObject(message) is not CapabilityMessage capability) return;