From 9e3a72dcbc1e427c7689b39dd29b95df0ab1dfba Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Wed, 21 Dec 2022 17:42:43 -0700 Subject: [PATCH 01/17] new discord bot --- README.md | 2 +- Server/DiscordBot.cs | 480 +++++++++++++++++++++++++++++++------------ Server/Program.cs | 4 +- Server/Server.csproj | 3 +- Server/Settings.cs | 1 + 5 files changed, 359 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index e982eca..2e2cc47 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Banlist: banned people are unable to join the server, default: false PersistShines/Moons: Allows the server to remember moon progress across crashes/restarts ### Discord -Note: Token and LogChannel needs to a string puts "" around it +Note: Token and LogChannel needs to have quotes "" around it Token: the token of the bot you want to load into, default: null Prefix: the bot prefix to be used, default: $ LogChannel: logs the server console to that channel, default: null \ No newline at end of file diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index cf1457a..c5294c5 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -1,160 +1,386 @@ -using DSharpPlus; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using Discord; +using Discord.Commands; +using Discord.Net; +using Discord.WebSocket; +using Newtonsoft.Json.Linq; using Shared; namespace Server; -public class DiscordBot { - private DiscordClient? DiscordClient; - private string? Token; - private Settings.DiscordTable Config => Settings.Instance.Discord; - private string Prefix => Config.Prefix; - private readonly Logger Logger = new Logger("Discord"); - private DiscordChannel? CommandChannel; - private DiscordChannel? LogChannel; - private bool Reconnecting; +public class DiscordBot +{ + private readonly Logger logger = new Logger("Discord"); + private Settings.DiscordTable localSettings = Settings.Instance.Discord; + private DiscordSocketClient? client = null; + //private SocketTextChannel? commandChannel = null; + private SocketTextChannel? logChannel = null; + private bool firstInitTriggered = false; - public DiscordBot() { - Token = Config.Token; - Logger.AddLogHandler(Log); - CommandHandler.RegisterCommand("dscrestart", _ => { - // this should be async'ed but i'm lazy - Reconnecting = true; - Task.Run(Reconnect); - return "Restarting Discord bot"; + //check how this works with neither, one or the other, or both channels set. + + public DiscordBot() + { + CommandHandler.RegisterCommand("dscrestart", _ => + { + Stop(); +#pragma warning disable CS4014 + Init(); +#pragma warning restore CS4014 + return "Restarting Discord bot..."; }); - if (Config.Token == null) return; - if (Config.CommandChannel == null) - Logger.Warn("You probably should set your CommandChannel in settings.json"); - if (Config.LogChannel == null) - Logger.Warn("You probably should set your LogChannel in settings.json"); - Settings.LoadHandler += SettingsLoadHandler; + logger.Info("Starting discord bot (ctor)"); + Settings.LoadHandler += OnLoadSettings; + Logger.AddLogHandler(LogToDiscordLogChannel); } - private async Task Reconnect() { - if (DiscordClient != null) // usually null prop works, not here though...` - await DiscordClient.DisconnectAsync(); - await Run(); - } - - private async void SettingsLoadHandler() { - if (DiscordClient == null || Token != Config.Token) { - await Run(); + private object firstInitLockObj = new object(); + public async Task FirstInit() + { + lock (firstInitLockObj) + { + if (firstInitTriggered) + return; + firstInitTriggered = true; } + await Init(); + } - if (DiscordClient == null) { - Logger.Error(new NullReferenceException("Discord client not setup yet!")); + private async Task Init() + { + if (client != null) + { + return; //this is bad if the client ever crashes and isn't reassigned to null, but we don't want multiple instances of the bot running at the same time. + } + if (localSettings.Token == null || (localSettings.LogChannel == null && localSettings.CommandChannel == null)) + { + //no point trying to run anything if there's no discord token and/or no channel for a user to interact with the bot through. + logger.Error("Tried to run the discord bot, but the Token and/or communication channels are not specified in the settings."); return; } + client = new DiscordSocketClient( + new DiscordSocketConfig() + { + LogLevel = LogSeverity.Warning + }); - if (Config.CommandChannel != null) { - try { - CommandChannel = await DiscordClient.GetChannelAsync(ulong.Parse(Config.CommandChannel)); - } catch (Exception e) { - Logger.Error($"Failed to get command channel \"{Config.CommandChannel}\""); - Logger.Error(e); + client.Log += async (a) => await Task.Run(() => LogToDiscordLogChannel($"Discord: {a.Source}", a.Severity.ToString(), a.Exception?.ToString() ?? "null", (int)a.Severity <= 2 ? ConsoleColor.Yellow : ConsoleColor.Black)); + try + { + await client.LoginAsync(Discord.TokenType.Bot, localSettings.Token); + await client.StartAsync(); + SemaphoreSlim wait = new SemaphoreSlim(0); +#pragma warning disable CS1998 + client.Ready += async () => +#pragma warning restore CS1998 + { + wait.Release(); + }; + await wait.WaitAsync(); + //we need to wait for the ready event before we can do any of this nonsense. + logChannel = (ulong.TryParse(localSettings.LogChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; + //commandChannel = (ulong.TryParse(localSettings.CommandChannel, out ulong ccid) ? (client != null ? await client.GetChannelAsync(ccid) : null) : null) as SocketTextChannel; + client!.MessageReceived += (m) => HandleCommandAsync(m); + logger.Info("Discord bot has been initialized."); + } + catch (Exception e) + { + logger.Error(e); + } + } + + private void Stop() + { + try + { + if (client != null) + client.StopAsync().Wait(); + client?.Dispose(); + } + catch { /*lol (lmao)*/ } + client = null; + logChannel = null; + //commandChannel = null; + localSettings = Settings.Instance.Discord; + } + + private async void LogToDiscordLogChannel(string source, string level, string text, ConsoleColor color) + { + logChannel = (ulong.TryParse(localSettings.LogChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; + if (logChannel != null) + { + try + { + switch (color) + { + //I looked into other hacky methods of doing more colors, the rest seemed unreliable. + default: + foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1994)) //room for 6 '`' + await logChannel.SendMessageAsync($"```{mesg}```"); + break; + case ConsoleColor.Yellow: + foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1990)) //room for 6 '`', "fix" and "\n" + await logChannel.SendMessageAsync($"```fix\n{mesg}```"); + break; + case ConsoleColor.Red: + foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"-{level} [{source}]"), 1989)) //room for 6 '`', "diff" and "\n" + await logChannel.SendMessageAsync($"```diff\n{mesg}```"); + break; + case ConsoleColor.Green: + foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"+{level} [{source}]"), 1989)) //room for 6 '`', "diff" and "\n" + await logChannel.SendMessageAsync($"```diff\n{mesg}```"); + break; + } + } + catch (Exception e) + { + // don't log again, it'll just stack overflow the server! + await Console.Error.WriteLineAsync("Exception in discord logger"); + await Console.Error.WriteLineAsync(e.ToString()); } } + } - if (Config.LogChannel != null) { - try { - LogChannel = await DiscordClient.GetChannelAsync(ulong.Parse(Config.LogChannel)); - } catch (Exception e) { - Logger.Error($"Failed to get log channel \"{Config.LogChannel}\""); - Logger.Error(e); + private async Task HandleCommandAsync(SocketMessage arg) + { + if (arg is not SocketUserMessage) + return; //idk what to do in this circumstance. + if ((arg.Channel.Id.ToString() == localSettings.CommandChannel || arg.Channel.Id.ToString() == localSettings.LogChannel) && !arg.Author.IsBot) + { + string message = (await arg.Channel.GetMessageAsync(arg.Id)).Content; + if (localSettings.LogCommands) + { + logger.Info($"\"{arg.Author.Username}\" ran the command: \"{message}\" via discord"); + } + //run command + try + { + string? resp = null; + if (string.IsNullOrEmpty(localSettings.Prefix)) + { + await arg.Channel.TriggerTypingAsync(); + resp = string.Join('\n', CommandHandler.GetResult(message).ReturnStrings); + } + else if (message.StartsWith(localSettings.Prefix)) + { + await arg.Channel.TriggerTypingAsync(); + resp = string.Join('\n', CommandHandler.GetResult(message[localSettings.Prefix.Length..]).ReturnStrings); + } + else if (message.StartsWith($"<@{client!.CurrentUser.Id}>")) + { + await arg.Channel.TriggerTypingAsync(); + resp = string.Join('\n', CommandHandler.GetResult(message[client!.CurrentUser.Mention.Length..].TrimStart()).ReturnStrings); + } + if (resp != null) + { + foreach (string mesg in SplitMessage(resp)) + await (arg as SocketUserMessage).ReplyAsync(mesg); + } + } + catch (Exception e) + { + logger.Error(e); } } + else + { + //don't respond to commands not in these channels, and no bots + //probably don't print out error message because DDOS + } + } + + private void OnLoadSettings() + { + Settings.DiscordTable oldSettings = localSettings; + localSettings = Settings.Instance.Discord; + + if (localSettings.CommandChannel == null) + logger.Warn("You probably should set your CommandChannel in settings.json"); + if (localSettings.LogChannel == null) + logger.Warn("You probably should set your LogChannel in settings.json"); + + if (oldSettings.Token != localSettings.Token || oldSettings.LogChannel != localSettings.LogChannel || oldSettings.CommandChannel != localSettings.CommandChannel) + { + //start over fresh (there might be a more intelligent way to do this without restarting the bot if only the log/command channel changed, but I'm lazy. + Stop(); +#pragma warning disable CS4014 + Init(); +#pragma warning restore CS4014 + } + } + + private async Task WeGotRateLimitedLBozo(IRateLimitInfo info) + { + //this is spamming because apparently this is called for more than just rate limiting. + //await Console.Error.WriteLineAsync("We got rate limited!"); } private static List SplitMessage(string message, int maxSizePerElem = 2000) { List result = new List(); - for (int i = 0; i < message.Length; i += maxSizePerElem) + for (int i = 0; i < message.Length; i += maxSizePerElem) { result.Add(message.Substring(i, message.Length - i < maxSizePerElem ? message.Length - i : maxSizePerElem)); } return result; } - private async void Log(string source, string level, string text, ConsoleColor _) { - try { - if (DiscordClient != null && LogChannel != null) { - foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1994)) //room for 6 '`' - await DiscordClient.SendMessageAsync(LogChannel, $"```{mesg}```"); - } - } catch (Exception e) { - // don't log again, it'll just stack overflow the server! - if (Reconnecting) return; // skip if reconnecting - await Console.Error.WriteLineAsync("Exception in discord logger"); - await Console.Error.WriteLineAsync(e.ToString()); - } + ~DiscordBot() + { + Stop(); } - public async Task Run() { - Token = Config.Token; - DiscordClient?.Dispose(); - if (Config.Token == null) { - DiscordClient = null; - return; - } + #region Old + //private DiscordClient? DiscordClient; + //private string? Token; + //private Settings.DiscordTable Config => Settings.Instance.Discord; + //private string Prefix => Config.Prefix; + //private readonly Logger Logger = new Logger("Discord"); + //private DiscordChannel? CommandChannel; + //private DiscordChannel? LogChannel; + //private bool Reconnecting; - try { - DiscordClient = new DiscordClient(new DiscordConfiguration { - Token = Config.Token, - MinimumLogLevel = LogLevel.None - }); - await DiscordClient.ConnectAsync(new DiscordActivity("Hide and Seek", ActivityType.Competing)); - SettingsLoadHandler(); - Logger.Info( - $"Discord bot logged in as {DiscordClient.CurrentUser.Username}#{DiscordClient.CurrentUser.Discriminator}"); - Reconnecting = false; - string mentionPrefix = $"{DiscordClient.CurrentUser.Mention}"; - DiscordClient.MessageCreated += async (_, args) => { - if (args.Author.IsCurrent) return; //dont respond to commands from ourselves (prevent "sql-injection" esq attacks) - //prevent commands via dm and non-public channels - if (CommandChannel == null) { - if (args.Channel is DiscordDmChannel) - return; //no dm'ing the bot allowed! - } - else if (args.Channel.Id != CommandChannel.Id && (LogChannel != null && args.Channel.Id != LogChannel.Id)) - return; - //run command - try { - DiscordMessage msg = args.Message; - string? resp = null; - if (string.IsNullOrEmpty(Prefix)) { - await msg.Channel.TriggerTypingAsync(); - resp = string.Join('\n', CommandHandler.GetResult(msg.Content).ReturnStrings); - } else if (msg.Content.StartsWith(Prefix)) { - await msg.Channel.TriggerTypingAsync(); - resp = string.Join('\n', CommandHandler.GetResult(msg.Content[Prefix.Length..]).ReturnStrings); - } else if (msg.Content.StartsWith(mentionPrefix)) { - await msg.Channel.TriggerTypingAsync(); - resp = string.Join('\n', CommandHandler.GetResult(msg.Content[mentionPrefix.Length..].TrimStart()).ReturnStrings); - } - if (resp != null) - { - foreach (string mesg in SplitMessage(resp)) - await msg.RespondAsync(mesg); - } - } catch (Exception e) { - Logger.Error(e); - } - }; - DiscordClient.ClientErrored += (_, args) => { - Logger.Error("Discord client caught an error in handler!"); - Logger.Error(args.Exception); - return Task.CompletedTask; - }; - DiscordClient.SocketErrored += (_, args) => { - Logger.Error("Discord client caught an error on socket!"); - Logger.Error(args.Exception); - return Task.CompletedTask; - }; - } catch (Exception e) { - Logger.Error("Exception occurred in discord runner!"); - Logger.Error(e); - } - } + //public DiscordBot() { + // Token = Config.Token; + // Logger.AddLogHandler(Log); + // CommandHandler.RegisterCommand("dscrestart", _ => { + // // this should be async'ed but i'm lazy + // Reconnecting = true; + // Task.Run(Reconnect); + // return "Restarting Discord bot"; + // }); + // if (Config.Token == null) return; + // if (Config.CommandChannel == null) + // Logger.Warn("You probably should set your CommandChannel in settings.json"); + // if (Config.LogChannel == null) + // Logger.Warn("You probably should set your LogChannel in settings.json"); + // Settings.LoadHandler += SettingsLoadHandler; + //} + + //private async Task Reconnect() { + // if (DiscordClient != null) // usually null prop works, not here though...` + // await DiscordClient.DisconnectAsync(); + // await Run(); + //} + + //private async void SettingsLoadHandler() { + // if (DiscordClient == null || Token != Config.Token) { + // await Run(); + // } + + // if (DiscordClient == null) { + // Logger.Error(new NullReferenceException("Discord client not setup yet!")); + // return; + // } + + // if (Config.CommandChannel != null) { + // try { + // CommandChannel = await DiscordClient.GetChannelAsync(ulong.Parse(Config.CommandChannel)); + // } catch (Exception e) { + // Logger.Error($"Failed to get command channel \"{Config.CommandChannel}\""); + // Logger.Error(e); + // } + // } + + // if (Config.LogChannel != null) { + // try { + // LogChannel = await DiscordClient.GetChannelAsync(ulong.Parse(Config.LogChannel)); + // } catch (Exception e) { + // Logger.Error($"Failed to get log channel \"{Config.LogChannel}\""); + // Logger.Error(e); + // } + // } + //} + + //private static List SplitMessage(string message, int maxSizePerElem = 2000) + //{ + // List result = new List(); + // for (int i = 0; i < message.Length; i += maxSizePerElem) + // { + // result.Add(message.Substring(i, message.Length - i < maxSizePerElem ? message.Length - i : maxSizePerElem)); + // } + // return result; + //} + + //private async void Log(string source, string level, string text, ConsoleColor _) { + // try { + // if (DiscordClient != null && LogChannel != null) { + // foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1994)) //room for 6 '`' + // await DiscordClient.SendMessageAsync(LogChannel, $"```{mesg}```"); + // } + // } catch (Exception e) { + // // don't log again, it'll just stack overflow the server! + // if (Reconnecting) return; // skip if reconnecting + // await Console.Error.WriteLineAsync("Exception in discord logger"); + // await Console.Error.WriteLineAsync(e.ToString()); + // } + //} + + //public async Task Run() { + // Token = Config.Token; + // DiscordClient?.Dispose(); + // if (Config.Token == null) { + // DiscordClient = null; + // return; + // } + + // try { + // DiscordClient = new DiscordClient(new DiscordConfiguration { + // Token = Config.Token, + // MinimumLogLevel = LogLevel.None + // }); + // await DiscordClient.ConnectAsync(new DiscordActivity("Hide and Seek", ActivityType.Competing)); + // SettingsLoadHandler(); + // Logger.Info( + // $"Discord bot logged in as {DiscordClient.CurrentUser.Username}#{DiscordClient.CurrentUser.Discriminator}"); + // Reconnecting = false; + // string mentionPrefix = $"{DiscordClient.CurrentUser.Mention}"; + // DiscordClient.MessageCreated += async (_, args) => { + // if (args.Author.IsCurrent) return; //dont respond to commands from ourselves (prevent "sql-injection" esq attacks) + // //prevent commands via dm and non-public channels + // if (CommandChannel == null) { + // if (args.Channel is DiscordDmChannel) + // return; //no dm'ing the bot allowed! + // } + // else if (args.Channel.Id != CommandChannel.Id && (LogChannel != null && args.Channel.Id != LogChannel.Id)) + // return; + // //run command + // try { + // DiscordMessage msg = args.Message; + // string? resp = null; + // if (string.IsNullOrEmpty(Prefix)) { + // await msg.Channel.TriggerTypingAsync(); + // resp = string.Join('\n', CommandHandler.GetResult(msg.Content).ReturnStrings); + // } else if (msg.Content.StartsWith(Prefix)) { + // await msg.Channel.TriggerTypingAsync(); + // resp = string.Join('\n', CommandHandler.GetResult(msg.Content[Prefix.Length..]).ReturnStrings); + // } else if (msg.Content.StartsWith(mentionPrefix)) { + // await msg.Channel.TriggerTypingAsync(); + // resp = string.Join('\n', CommandHandler.GetResult(msg.Content[mentionPrefix.Length..].TrimStart()).ReturnStrings); + // } + // if (resp != null) + // { + // foreach (string mesg in SplitMessage(resp)) + // await msg.RespondAsync(mesg); + // } + // } catch (Exception e) { + // Logger.Error(e); + // } + // }; + // DiscordClient.ClientErrored += (_, args) => { + // Logger.Error("Discord client caught an error in handler!"); + // Logger.Error(args.Exception); + // return Task.CompletedTask; + // }; + // DiscordClient.SocketErrored += (_, args) => { + // Logger.Error("This is probably that stupid bug again!"); + // Logger.Error("Discord client caught an error on socket!"); + // Logger.Error(args.Exception); + // return Task.CompletedTask; + // }; + // } catch (Exception e) { + // Logger.Error("Exception occurred in discord runner!"); + // Logger.Error(e); + // } + //} + #endregion } diff --git a/Server/Program.cs b/Server/Program.cs index 71c0f3c..b98b6b9 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -14,7 +14,7 @@ CancellationTokenSource cts = new CancellationTokenSource(); bool restartRequested = false; Logger consoleLogger = new Logger("Console"); DiscordBot bot = new DiscordBot(); -await bot.Run(); +await bot.FirstInit(); async Task PersistShines() { @@ -394,7 +394,7 @@ CommandHandler.RegisterCommand("sendall", args => { } if (!stage.Contains("Stage") && !stage.Contains("Zone")) { - return "Invalid Stage Name! ```cap -> Cap Kingdom\ncascade -> Cascade Kingdom\nsand -> Sand Kingdom\nlake -> Lake Kingdom\nwooded -> Wooded Kingdom\ncloud -> Cloud Kingdom\nlost -> Lost Kingdom\nmetro -> Metro Kingdom\nsea -> Sea Kingdom\nsnow -> Snow Kingdom\nlunch -> Luncheon Kingdom\nruined -> Ruined Kingdom\nbowser -> Bowser's Kingdom\nmoon -> Moon Kingdom\nmush -> Mushroom Kingdom\ndark -> Dark Side\ndarker -> Darker Side```"; + return "Invalid Stage Name!\ncap -> Cap Kingdom\ncascade -> Cascade Kingdom\nsand -> Sand Kingdom\nlake -> Lake Kingdom\nwooded -> Wooded Kingdom\ncloud -> Cloud Kingdom\nlost -> Lost Kingdom\nmetro -> Metro Kingdom\nsea -> Sea Kingdom\nsnow -> Snow Kingdom\nlunch -> Luncheon Kingdom\nruined -> Ruined Kingdom\nbowser -> Bowser's Kingdom\nmoon -> Moon Kingdom\nmush -> Mushroom Kingdom\ndark -> Dark Side\ndarker -> Darker Side"; } Client[] players = server.Clients.Where(c => c.Connected).ToArray(); diff --git a/Server/Server.csproj b/Server/Server.csproj index 5710034..6608e67 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -12,7 +12,8 @@ - + + diff --git a/Server/Settings.cs b/Server/Settings.cs index fe075e5..4d7d663 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -75,6 +75,7 @@ public class Settings { public string Prefix { get; set; } = "$"; public string? CommandChannel { get; set; } public string? LogChannel { get; set; } + public bool LogCommands { get; set; } = false; } public class ShineTable { From bb02fa6e90d3b46be3d64a114c96e1c20e25df46 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 01:04:23 -0700 Subject: [PATCH 02/17] only print logged commands if it was a valid command --- Server/DiscordBot.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index c5294c5..de54e30 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -147,10 +147,6 @@ public class DiscordBot if ((arg.Channel.Id.ToString() == localSettings.CommandChannel || arg.Channel.Id.ToString() == localSettings.LogChannel) && !arg.Author.IsBot) { string message = (await arg.Channel.GetMessageAsync(arg.Id)).Content; - if (localSettings.LogCommands) - { - logger.Info($"\"{arg.Author.Username}\" ran the command: \"{message}\" via discord"); - } //run command try { @@ -172,6 +168,10 @@ public class DiscordBot } if (resp != null) { + if (localSettings.LogCommands) + { + logger.Info($"\"{arg.Author.Username}\" ran the command: \"{message}\" via discord"); + } foreach (string mesg in SplitMessage(resp)) await (arg as SocketUserMessage).ReplyAsync(mesg); } From 3a34ec20453e4119cdc1269a5b577e760c45cde0 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 05:02:21 -0700 Subject: [PATCH 03/17] Made the logging colors smarter. --- Server/DiscordBot.cs | 29 ++++++++++++++++++++++++++++- Server/Server.cs | 2 +- Shared/Logger.cs | 2 ++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index de54e30..b92bef0 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -63,7 +63,34 @@ public class DiscordBot LogLevel = LogSeverity.Warning }); - client.Log += async (a) => await Task.Run(() => LogToDiscordLogChannel($"Discord: {a.Source}", a.Severity.ToString(), a.Exception?.ToString() ?? "null", (int)a.Severity <= 2 ? ConsoleColor.Yellow : ConsoleColor.Black)); + client.Log += async (a) => await Task.Run(() => + { + //as time goes on, we may encounter logged info that we literally don't care about. Fill out an if statement to properly + //filter it out to avoid logging it to discord. + //if (a.Message.StartsWith("")) + //{ + // return; + //} + string message = a.Message + (a.Exception != null ? "Exception: " + a.Exception.ToString() : ""); + ConsoleColor col; + switch (a.Severity) + { + default: + case LogSeverity.Info: + case LogSeverity.Debug: + col = ConsoleColor.White; + break; + case LogSeverity.Critical: + case LogSeverity.Error: + col = ConsoleColor.Red; + break; + case LogSeverity.Warning: + col = ConsoleColor.Yellow; + break; + } + + LogToDiscordLogChannel($"Discord: {a.Source}", a.Severity.ToString(), message, col); + }); try { await client.LoginAsync(Discord.TokenType.Bot, localSettings.Token); diff --git a/Server/Server.cs b/Server/Server.cs index cfb7091..dd376e6 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -29,7 +29,7 @@ public class Server { Socket socket = token.HasValue ? await serverSocket.AcceptAsync(token.Value) : await serverSocket.AcceptAsync(); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - Logger.Warn($"Accepted connection for client {socket.RemoteEndPoint}"); + Logger.Notify($"Accepted connection for client {socket.RemoteEndPoint}"); try { #pragma warning disable CS4014 diff --git a/Shared/Logger.cs b/Shared/Logger.cs index aaab266..33bae18 100644 --- a/Shared/Logger.cs +++ b/Shared/Logger.cs @@ -9,6 +9,8 @@ public class Logger { public string Name { get; set; } + public void Notify(string text) => Handler?.Invoke(Name, "Info", text, ConsoleColor.Green); + public void Info(string text) => Handler?.Invoke(Name, "Info", text, ConsoleColor.White); public void Warn(string text) => Handler?.Invoke(Name, "Warn", text, ConsoleColor.Yellow); From fb26986a583542bea77ac1bff1284d30ff0c720c Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 05:54:10 -0700 Subject: [PATCH 04/17] refactoring of command parsing in discord bot --- Server/DiscordBot.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index b92bef0..ae11209 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -177,24 +177,23 @@ public class DiscordBot //run command try { - string? resp = null; + string? args = null; if (string.IsNullOrEmpty(localSettings.Prefix)) { - await arg.Channel.TriggerTypingAsync(); - resp = string.Join('\n', CommandHandler.GetResult(message).ReturnStrings); + args = message; } else if (message.StartsWith(localSettings.Prefix)) { - await arg.Channel.TriggerTypingAsync(); - resp = string.Join('\n', CommandHandler.GetResult(message[localSettings.Prefix.Length..]).ReturnStrings); + args = message[localSettings.Prefix.Length..]; } else if (message.StartsWith($"<@{client!.CurrentUser.Id}>")) { - await arg.Channel.TriggerTypingAsync(); - resp = string.Join('\n', CommandHandler.GetResult(message[client!.CurrentUser.Mention.Length..].TrimStart()).ReturnStrings); + args = message[client!.CurrentUser.Mention.Length..].TrimStart(); } - if (resp != null) + if (args != null) { + await arg.Channel.TriggerTypingAsync(); + string resp = string.Join('\n', CommandHandler.GetResult(args).ReturnStrings); if (localSettings.LogCommands) { logger.Info($"\"{arg.Author.Username}\" ran the command: \"{message}\" via discord"); From 317a495f46e698fc5e799e8797d3b9b9fae27c28 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 10:50:19 -0700 Subject: [PATCH 05/17] Fix deadlock from Stop/HandleCommandAsync --- Server/DiscordBot.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index ae11209..9af10bf 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -22,10 +22,15 @@ public class DiscordBot { CommandHandler.RegisterCommand("dscrestart", _ => { - Stop(); + //Task.Run is to fix deadlock (dispose can only be finalized if all discord callbacks are returned, + //and since this delegate is called directly from a callback, it would cause a deadlock). + Task.Run(() => + { + Stop(); #pragma warning disable CS4014 - Init(); + Init(); #pragma warning restore CS4014 + }); return "Restarting Discord bot..."; }); logger.Info("Starting discord bot (ctor)"); From 28c4cfadd616920a622ae563d957d9d076c38239 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 11:00:30 -0700 Subject: [PATCH 06/17] alias logchannel as adminchannel (code only) --- Server/DiscordBot.cs | 12 ++++++------ Server/Settings.cs | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index 9af10bf..b84c27f 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -56,7 +56,7 @@ public class DiscordBot { return; //this is bad if the client ever crashes and isn't reassigned to null, but we don't want multiple instances of the bot running at the same time. } - if (localSettings.Token == null || (localSettings.LogChannel == null && localSettings.CommandChannel == null)) + if (localSettings.Token == null || (localSettings.AdminChannel == null && localSettings.CommandChannel == null)) { //no point trying to run anything if there's no discord token and/or no channel for a user to interact with the bot through. logger.Error("Tried to run the discord bot, but the Token and/or communication channels are not specified in the settings."); @@ -109,7 +109,7 @@ public class DiscordBot }; await wait.WaitAsync(); //we need to wait for the ready event before we can do any of this nonsense. - logChannel = (ulong.TryParse(localSettings.LogChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; + logChannel = (ulong.TryParse(localSettings.AdminChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; //commandChannel = (ulong.TryParse(localSettings.CommandChannel, out ulong ccid) ? (client != null ? await client.GetChannelAsync(ccid) : null) : null) as SocketTextChannel; client!.MessageReceived += (m) => HandleCommandAsync(m); logger.Info("Discord bot has been initialized."); @@ -137,7 +137,7 @@ public class DiscordBot private async void LogToDiscordLogChannel(string source, string level, string text, ConsoleColor color) { - logChannel = (ulong.TryParse(localSettings.LogChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; + logChannel = (ulong.TryParse(localSettings.AdminChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; if (logChannel != null) { try @@ -176,7 +176,7 @@ public class DiscordBot { if (arg is not SocketUserMessage) return; //idk what to do in this circumstance. - if ((arg.Channel.Id.ToString() == localSettings.CommandChannel || arg.Channel.Id.ToString() == localSettings.LogChannel) && !arg.Author.IsBot) + if ((arg.Channel.Id.ToString() == localSettings.CommandChannel || arg.Channel.Id.ToString() == localSettings.AdminChannel) && !arg.Author.IsBot) { string message = (await arg.Channel.GetMessageAsync(arg.Id)).Content; //run command @@ -226,10 +226,10 @@ public class DiscordBot if (localSettings.CommandChannel == null) logger.Warn("You probably should set your CommandChannel in settings.json"); - if (localSettings.LogChannel == null) + if (localSettings.AdminChannel == null) logger.Warn("You probably should set your LogChannel in settings.json"); - if (oldSettings.Token != localSettings.Token || oldSettings.LogChannel != localSettings.LogChannel || oldSettings.CommandChannel != localSettings.CommandChannel) + if (oldSettings.Token != localSettings.Token || oldSettings.AdminChannel != localSettings.AdminChannel || oldSettings.CommandChannel != localSettings.CommandChannel) { //start over fresh (there might be a more intelligent way to do this without restarting the bot if only the log/command channel changed, but I'm lazy. Stop(); diff --git a/Server/Settings.cs b/Server/Settings.cs index 4d7d663..859624f 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -74,7 +74,8 @@ public class Settings { public string? Token { get; set; } public string Prefix { get; set; } = "$"; public string? CommandChannel { get; set; } - public string? LogChannel { get; set; } + [JsonProperty(PropertyName = "LogChannel")] + public string? AdminChannel { get; set; } public bool LogCommands { get; set; } = false; } From 3f6d581c8395d6b8956f96e0b68b59a6f95f5eaa Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 20:15:34 -0700 Subject: [PATCH 07/17] Filter out a non-issue warning. --- Server/DiscordBot.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index b84c27f..327ef14 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -72,10 +72,14 @@ public class DiscordBot { //as time goes on, we may encounter logged info that we literally don't care about. Fill out an if statement to properly //filter it out to avoid logging it to discord. - //if (a.Message.StartsWith("")) - //{ - // return; - //} + if (a.Message.Contains("Server requested a reconnect")) + { + //This is to filter out this message. This warning is for discord server load balancing and isn't a problem + + //Warning[Discord: Gateway] Discord.WebSocket.GatewayReconnectException: Server requested a reconnect + //Warning[Discord: Gateway] at Discord.ConnectionManager.<> c__DisplayClass29_0.<< StartAsync > b__0 > d.MoveNext() + return; + } string message = a.Message + (a.Exception != null ? "Exception: " + a.Exception.ToString() : ""); ConsoleColor col; switch (a.Severity) From 0b5d0b7c4e16098b9780a9c6753585c4f95f4f64 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sat, 24 Dec 2022 20:29:19 -0700 Subject: [PATCH 08/17] enable/disable the filtering of non-warning log messages --- Server/DiscordBot.cs | 13 ++++++++----- Server/Settings.cs | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index 327ef14..b4da11c 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -72,13 +72,16 @@ public class DiscordBot { //as time goes on, we may encounter logged info that we literally don't care about. Fill out an if statement to properly //filter it out to avoid logging it to discord. - if (a.Message.Contains("Server requested a reconnect")) + if (localSettings.FilterOutNonIssueWarnings) { - //This is to filter out this message. This warning is for discord server load balancing and isn't a problem + if (a.Message.Contains("Server requested a reconnect")) + { + //This is to filter out this message. This warning is for discord server load balancing and isn't a problem - //Warning[Discord: Gateway] Discord.WebSocket.GatewayReconnectException: Server requested a reconnect - //Warning[Discord: Gateway] at Discord.ConnectionManager.<> c__DisplayClass29_0.<< StartAsync > b__0 > d.MoveNext() - return; + //Warning[Discord: Gateway] Discord.WebSocket.GatewayReconnectException: Server requested a reconnect + //Warning[Discord: Gateway] at Discord.ConnectionManager.<> c__DisplayClass29_0.<< StartAsync > b__0 > d.MoveNext() + return; + } } string message = a.Message + (a.Exception != null ? "Exception: " + a.Exception.ToString() : ""); ConsoleColor col; diff --git a/Server/Settings.cs b/Server/Settings.cs index 859624f..2228dbc 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -77,6 +77,7 @@ public class Settings { [JsonProperty(PropertyName = "LogChannel")] public string? AdminChannel { get; set; } public bool LogCommands { get; set; } = false; + public bool FilterOutNonIssueWarnings { get; set; } = true; } public class ShineTable { From fc70cb90d8954209b0b25c9420e15d268b3a616d Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 27 Jun 2023 18:32:04 -0600 Subject: [PATCH 09/17] Updated readme with the new settings, added another exception filter to the discordbot --- README.md | 7 +++++-- Server/DiscordBot.cs | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2e2cc47..2f13ae8 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,10 @@ Banlist: banned people are unable to join the server, default: false PersistShines/Moons: Allows the server to remember moon progress across crashes/restarts ### Discord -Note: Token and LogChannel needs to have quotes "" around it +Note: Token, LogChannel and CommandChannel need to have quotes "" around it Token: the token of the bot you want to load into, default: null Prefix: the bot prefix to be used, default: $ -LogChannel: logs the server console to that channel, default: null \ No newline at end of file +CommandChannel: allows discord commands, default: null +LogChannel/(AdminChannel): allows discord commands & logs the server console to that channel, default: null +LogCommands: log all executed commands to log handlers (discord, console), default: false +FilterOutNonIssueWarnings: filter out the nonsense warnings/errors? default: true \ No newline at end of file diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index b4da11c..b34b956 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -74,7 +74,8 @@ public class DiscordBot //filter it out to avoid logging it to discord. if (localSettings.FilterOutNonIssueWarnings) { - if (a.Message.Contains("Server requested a reconnect")) + //if (a.Message.Contains("Server requested a reconnect")) + if (a.Exception?.ToString().Contains("Server requested a reconnect") ?? false) { //This is to filter out this message. This warning is for discord server load balancing and isn't a problem @@ -82,6 +83,26 @@ public class DiscordBot //Warning[Discord: Gateway] at Discord.ConnectionManager.<> c__DisplayClass29_0.<< StartAsync > b__0 > d.MoveNext() return; } + else if (a.Exception?.ToString().Contains("The remote party closed the WebSocket connection without completing the close handshake.") ?? false) + { + //From Discord.NET discord's server, support: + //Discord does this normally and it effects all bots, as long as your bot is reconnecting + //after the error it is expected and should just be ignored. + + /*{18:21:02 Gateway System.Exception: WebSocket connection was closed + ---> System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. + at System.Net.WebSockets.ManagedWebSocket.ThrowIfEOFUnexpected(Boolean throwOnPrematureClosure) + at System.Net.WebSockets.ManagedWebSocket.EnsureBufferContainsAsync(Int32 minimumRequiredBytes, CancellationToken cancellationToken, Boolean throwOnPrematureClosure) + at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) + at System.Net.WebSockets.ManagedWebSocket.ReceiveAsyncPrivate[TResult](Memory`1 payloadBuffer, CancellationToken cancellationToken) + at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) + at System.Threading.Tasks.ValueTask`1.ValueTaskSourceAsTask.<>c.<.cctor>b__4_0(Object state) + --- End of stack trace from previous location --- + at Discord.Net.WebSockets.DefaultWebSocketClient.RunAsync(CancellationToken cancelToken) + --- End of inner exception stack trace --- + at Discord.ConnectionManager.<>c__DisplayClass29_0.<b__0>d.MoveNext()}*/ + return; + } } string message = a.Message + (a.Exception != null ? "Exception: " + a.Exception.ToString() : ""); ConsoleColor col; From 67a00716f8951029bbf7db60e81213c4f73cf8ae Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 27 Jun 2023 21:34:17 -0600 Subject: [PATCH 10/17] Added garbage to attempt to update json field name. --- .../LogToAdminJsonConverter.cs | 34 +++++++++++++++++++ Server/Settings.cs | 4 ++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 Server/LegacyJsonSupport/LogToAdminJsonConverter.cs diff --git a/Server/LegacyJsonSupport/LogToAdminJsonConverter.cs b/Server/LegacyJsonSupport/LogToAdminJsonConverter.cs new file mode 100644 index 0000000..7bcd57b --- /dev/null +++ b/Server/LegacyJsonSupport/LogToAdminJsonConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft; + +namespace Server.LegacyJsonSupport +{ + public class LogToAdminJsonConverter : JsonConverter + { + public override string? ReadJson(JsonReader reader, Type objectType, string? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + //JToken t = JToken.Load(reader); + if (reader.TokenType != JsonToken.String) + return null; + //JObject j = JObject.Load(reader); + //string? k = reader.ReadAsString(); + //if (k == "LogChannel" || k == "AdminChannel") + return hasExistingValue ? existingValue : (string?)reader.Value; + //else + // return null; + } + + public override void WriteJson(JsonWriter writer, string? value, JsonSerializer serializer) + { + //writer. + //writer.WritePropertyName("AdminChannel"); + writer.WriteValue(value); + } + } +} diff --git a/Server/Settings.cs b/Server/Settings.cs index 2228dbc..19de01e 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Shared; +using Server.LegacyJsonSupport; namespace Server; @@ -74,7 +75,8 @@ public class Settings { public string? Token { get; set; } public string Prefix { get; set; } = "$"; public string? CommandChannel { get; set; } - [JsonProperty(PropertyName = "LogChannel")] + [JsonProperty(PropertyName = "AdminChannel")] + [JsonConverter(typeof(LogToAdminJsonConverter))] public string? AdminChannel { get; set; } public bool LogCommands { get; set; } = false; public bool FilterOutNonIssueWarnings { get; set; } = true; From 5b4de5ff8bf2255dae29f0a682659a1a144808e5 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 27 Jun 2023 21:34:45 -0600 Subject: [PATCH 11/17] Removed the aforementioned garbage --- .../LogToAdminJsonConverter.cs | 34 ------------------- Server/Settings.cs | 3 +- 2 files changed, 1 insertion(+), 36 deletions(-) delete mode 100644 Server/LegacyJsonSupport/LogToAdminJsonConverter.cs diff --git a/Server/LegacyJsonSupport/LogToAdminJsonConverter.cs b/Server/LegacyJsonSupport/LogToAdminJsonConverter.cs deleted file mode 100644 index 7bcd57b..0000000 --- a/Server/LegacyJsonSupport/LogToAdminJsonConverter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; -using Newtonsoft; - -namespace Server.LegacyJsonSupport -{ - public class LogToAdminJsonConverter : JsonConverter - { - public override string? ReadJson(JsonReader reader, Type objectType, string? existingValue, bool hasExistingValue, JsonSerializer serializer) - { - //JToken t = JToken.Load(reader); - if (reader.TokenType != JsonToken.String) - return null; - //JObject j = JObject.Load(reader); - //string? k = reader.ReadAsString(); - //if (k == "LogChannel" || k == "AdminChannel") - return hasExistingValue ? existingValue : (string?)reader.Value; - //else - // return null; - } - - public override void WriteJson(JsonWriter writer, string? value, JsonSerializer serializer) - { - //writer. - //writer.WritePropertyName("AdminChannel"); - writer.WriteValue(value); - } - } -} diff --git a/Server/Settings.cs b/Server/Settings.cs index 19de01e..06ce049 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -75,8 +75,7 @@ public class Settings { public string? Token { get; set; } public string Prefix { get; set; } = "$"; public string? CommandChannel { get; set; } - [JsonProperty(PropertyName = "AdminChannel")] - [JsonConverter(typeof(LogToAdminJsonConverter))] + [JsonProperty(PropertyName = "LogChannel")] public string? AdminChannel { get; set; } public bool LogCommands { get; set; } = false; public bool FilterOutNonIssueWarnings { get; set; } = true; From e549723e56d61d1d769d41f422ca930d75323234 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 27 Jun 2023 21:39:45 -0600 Subject: [PATCH 12/17] Removed bad using --- Server/Settings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/Settings.cs b/Server/Settings.cs index 06ce049..2228dbc 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Shared; -using Server.LegacyJsonSupport; namespace Server; From 3b935d1f5ba79380dbd517fbaf6227e14e542c9e Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Sun, 2 Jul 2023 21:18:52 -0600 Subject: [PATCH 13/17] Property migration of LogChannel->AdminChannel in discord settings, general cleanup. --- Server/DiscordBot.cs | 207 +++---------------------------------------- Server/Settings.cs | 7 +- 2 files changed, 20 insertions(+), 194 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index b34b956..bc68206 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -12,7 +12,6 @@ public class DiscordBot private readonly Logger logger = new Logger("Discord"); private Settings.DiscordTable localSettings = Settings.Instance.Discord; private DiscordSocketClient? client = null; - //private SocketTextChannel? commandChannel = null; private SocketTextChannel? logChannel = null; private bool firstInitTriggered = false; @@ -74,37 +73,22 @@ public class DiscordBot //filter it out to avoid logging it to discord. if (localSettings.FilterOutNonIssueWarnings) { - //if (a.Message.Contains("Server requested a reconnect")) - if (a.Exception?.ToString().Contains("Server requested a reconnect") ?? false) + string[] disinterestedMessages = + { //these messages happen sometimes, and are of no concern. + "Server requested a reconnect", + "The remote party closed the WebSocket connection without completing the close handshake", + "without listening to any events related to that intent, consider removing the intent from" + }; + foreach (string dis in disinterestedMessages) { - //This is to filter out this message. This warning is for discord server load balancing and isn't a problem - - //Warning[Discord: Gateway] Discord.WebSocket.GatewayReconnectException: Server requested a reconnect - //Warning[Discord: Gateway] at Discord.ConnectionManager.<> c__DisplayClass29_0.<< StartAsync > b__0 > d.MoveNext() - return; - } - else if (a.Exception?.ToString().Contains("The remote party closed the WebSocket connection without completing the close handshake.") ?? false) - { - //From Discord.NET discord's server, support: - //Discord does this normally and it effects all bots, as long as your bot is reconnecting - //after the error it is expected and should just be ignored. - - /*{18:21:02 Gateway System.Exception: WebSocket connection was closed - ---> System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake. - at System.Net.WebSockets.ManagedWebSocket.ThrowIfEOFUnexpected(Boolean throwOnPrematureClosure) - at System.Net.WebSockets.ManagedWebSocket.EnsureBufferContainsAsync(Int32 minimumRequiredBytes, CancellationToken cancellationToken, Boolean throwOnPrematureClosure) - at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) - at System.Net.WebSockets.ManagedWebSocket.ReceiveAsyncPrivate[TResult](Memory`1 payloadBuffer, CancellationToken cancellationToken) - at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) - at System.Threading.Tasks.ValueTask`1.ValueTaskSourceAsTask.<>c.<.cctor>b__4_0(Object state) - --- End of stack trace from previous location --- - at Discord.Net.WebSockets.DefaultWebSocketClient.RunAsync(CancellationToken cancelToken) - --- End of inner exception stack trace --- - at Discord.ConnectionManager.<>c__DisplayClass29_0.<b__0>d.MoveNext()}*/ - return; + if ((a.Exception?.ToString().Contains(dis) ?? false) || + (a.Message?.Contains(dis) ?? false)) + { + return; + } } } - string message = a.Message + (a.Exception != null ? "Exception: " + a.Exception.ToString() : ""); + string message = a.Message ?? string.Empty + (a.Exception != null ? "Exception: " + a.Exception.ToString() : ""); //TODO: this crashes ConsoleColor col; switch (a.Severity) { @@ -138,7 +122,6 @@ public class DiscordBot await wait.WaitAsync(); //we need to wait for the ready event before we can do any of this nonsense. logChannel = (ulong.TryParse(localSettings.AdminChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; - //commandChannel = (ulong.TryParse(localSettings.CommandChannel, out ulong ccid) ? (client != null ? await client.GetChannelAsync(ccid) : null) : null) as SocketTextChannel; client!.MessageReceived += (m) => HandleCommandAsync(m); logger.Info("Discord bot has been initialized."); } @@ -159,7 +142,6 @@ public class DiscordBot catch { /*lol (lmao)*/ } client = null; logChannel = null; - //commandChannel = null; localSettings = Settings.Instance.Discord; } @@ -177,7 +159,7 @@ public class DiscordBot foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1994)) //room for 6 '`' await logChannel.SendMessageAsync($"```{mesg}```"); break; - case ConsoleColor.Yellow: + case ConsoleColor.Yellow: //this is actually light blue now (discord changed it awhile ago). foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1990)) //room for 6 '`', "fix" and "\n" await logChannel.SendMessageAsync($"```fix\n{mesg}```"); break; @@ -267,12 +249,6 @@ public class DiscordBot } } - private async Task WeGotRateLimitedLBozo(IRateLimitInfo info) - { - //this is spamming because apparently this is called for more than just rate limiting. - //await Console.Error.WriteLineAsync("We got rate limited!"); - } - private static List SplitMessage(string message, int maxSizePerElem = 2000) { List result = new List(); @@ -287,159 +263,4 @@ public class DiscordBot { Stop(); } - - #region Old - //private DiscordClient? DiscordClient; - //private string? Token; - //private Settings.DiscordTable Config => Settings.Instance.Discord; - //private string Prefix => Config.Prefix; - //private readonly Logger Logger = new Logger("Discord"); - //private DiscordChannel? CommandChannel; - //private DiscordChannel? LogChannel; - //private bool Reconnecting; - - //public DiscordBot() { - // Token = Config.Token; - // Logger.AddLogHandler(Log); - // CommandHandler.RegisterCommand("dscrestart", _ => { - // // this should be async'ed but i'm lazy - // Reconnecting = true; - // Task.Run(Reconnect); - // return "Restarting Discord bot"; - // }); - // if (Config.Token == null) return; - // if (Config.CommandChannel == null) - // Logger.Warn("You probably should set your CommandChannel in settings.json"); - // if (Config.LogChannel == null) - // Logger.Warn("You probably should set your LogChannel in settings.json"); - // Settings.LoadHandler += SettingsLoadHandler; - //} - - //private async Task Reconnect() { - // if (DiscordClient != null) // usually null prop works, not here though...` - // await DiscordClient.DisconnectAsync(); - // await Run(); - //} - - //private async void SettingsLoadHandler() { - // if (DiscordClient == null || Token != Config.Token) { - // await Run(); - // } - - // if (DiscordClient == null) { - // Logger.Error(new NullReferenceException("Discord client not setup yet!")); - // return; - // } - - // if (Config.CommandChannel != null) { - // try { - // CommandChannel = await DiscordClient.GetChannelAsync(ulong.Parse(Config.CommandChannel)); - // } catch (Exception e) { - // Logger.Error($"Failed to get command channel \"{Config.CommandChannel}\""); - // Logger.Error(e); - // } - // } - - // if (Config.LogChannel != null) { - // try { - // LogChannel = await DiscordClient.GetChannelAsync(ulong.Parse(Config.LogChannel)); - // } catch (Exception e) { - // Logger.Error($"Failed to get log channel \"{Config.LogChannel}\""); - // Logger.Error(e); - // } - // } - //} - - //private static List SplitMessage(string message, int maxSizePerElem = 2000) - //{ - // List result = new List(); - // for (int i = 0; i < message.Length; i += maxSizePerElem) - // { - // result.Add(message.Substring(i, message.Length - i < maxSizePerElem ? message.Length - i : maxSizePerElem)); - // } - // return result; - //} - - //private async void Log(string source, string level, string text, ConsoleColor _) { - // try { - // if (DiscordClient != null && LogChannel != null) { - // foreach (string mesg in SplitMessage(Logger.PrefixNewLines(text, $"{level} [{source}]"), 1994)) //room for 6 '`' - // await DiscordClient.SendMessageAsync(LogChannel, $"```{mesg}```"); - // } - // } catch (Exception e) { - // // don't log again, it'll just stack overflow the server! - // if (Reconnecting) return; // skip if reconnecting - // await Console.Error.WriteLineAsync("Exception in discord logger"); - // await Console.Error.WriteLineAsync(e.ToString()); - // } - //} - - //public async Task Run() { - // Token = Config.Token; - // DiscordClient?.Dispose(); - // if (Config.Token == null) { - // DiscordClient = null; - // return; - // } - - // try { - // DiscordClient = new DiscordClient(new DiscordConfiguration { - // Token = Config.Token, - // MinimumLogLevel = LogLevel.None - // }); - // await DiscordClient.ConnectAsync(new DiscordActivity("Hide and Seek", ActivityType.Competing)); - // SettingsLoadHandler(); - // Logger.Info( - // $"Discord bot logged in as {DiscordClient.CurrentUser.Username}#{DiscordClient.CurrentUser.Discriminator}"); - // Reconnecting = false; - // string mentionPrefix = $"{DiscordClient.CurrentUser.Mention}"; - // DiscordClient.MessageCreated += async (_, args) => { - // if (args.Author.IsCurrent) return; //dont respond to commands from ourselves (prevent "sql-injection" esq attacks) - // //prevent commands via dm and non-public channels - // if (CommandChannel == null) { - // if (args.Channel is DiscordDmChannel) - // return; //no dm'ing the bot allowed! - // } - // else if (args.Channel.Id != CommandChannel.Id && (LogChannel != null && args.Channel.Id != LogChannel.Id)) - // return; - // //run command - // try { - // DiscordMessage msg = args.Message; - // string? resp = null; - // if (string.IsNullOrEmpty(Prefix)) { - // await msg.Channel.TriggerTypingAsync(); - // resp = string.Join('\n', CommandHandler.GetResult(msg.Content).ReturnStrings); - // } else if (msg.Content.StartsWith(Prefix)) { - // await msg.Channel.TriggerTypingAsync(); - // resp = string.Join('\n', CommandHandler.GetResult(msg.Content[Prefix.Length..]).ReturnStrings); - // } else if (msg.Content.StartsWith(mentionPrefix)) { - // await msg.Channel.TriggerTypingAsync(); - // resp = string.Join('\n', CommandHandler.GetResult(msg.Content[mentionPrefix.Length..].TrimStart()).ReturnStrings); - // } - // if (resp != null) - // { - // foreach (string mesg in SplitMessage(resp)) - // await msg.RespondAsync(mesg); - // } - // } catch (Exception e) { - // Logger.Error(e); - // } - // }; - // DiscordClient.ClientErrored += (_, args) => { - // Logger.Error("Discord client caught an error in handler!"); - // Logger.Error(args.Exception); - // return Task.CompletedTask; - // }; - // DiscordClient.SocketErrored += (_, args) => { - // Logger.Error("This is probably that stupid bug again!"); - // Logger.Error("Discord client caught an error on socket!"); - // Logger.Error(args.Exception); - // return Task.CompletedTask; - // }; - // } catch (Exception e) { - // Logger.Error("Exception occurred in discord runner!"); - // Logger.Error(e); - // } - //} - #endregion } diff --git a/Server/Settings.cs b/Server/Settings.cs index 2228dbc..c89b275 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -74,8 +74,13 @@ public class Settings { public string? Token { get; set; } public string Prefix { get; set; } = "$"; public string? CommandChannel { get; set; } - [JsonProperty(PropertyName = "LogChannel")] + //This funkyness is to migrate the JSON "LogChannel" to "AdminChannel" public string? AdminChannel { get; set; } + [JsonProperty(PropertyName = "LogChannel")] + public string? LogChannel + { + set => AdminChannel = value; + } public bool LogCommands { get; set; } = false; public bool FilterOutNonIssueWarnings { get; set; } = true; } From f15ded098140db1122d89aad217ed63964de2b49 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 11 Jul 2023 04:58:27 -0600 Subject: [PATCH 14/17] lgtm? --- Server/DiscordBot.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index bc68206..4109249 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -136,7 +136,10 @@ public class DiscordBot try { if (client != null) - client.StopAsync().Wait(); + { + if (!client.StopAsync().Wait(60000)) + logger.Warn("Tried to stop the discord bot, but attempt took >60 seconds, so it failed!"); + } client?.Dispose(); } catch { /*lol (lmao)*/ } @@ -237,7 +240,7 @@ public class DiscordBot if (localSettings.CommandChannel == null) logger.Warn("You probably should set your CommandChannel in settings.json"); if (localSettings.AdminChannel == null) - logger.Warn("You probably should set your LogChannel in settings.json"); + logger.Warn("You probably should set your AdminChannel in settings.json"); if (oldSettings.Token != localSettings.Token || oldSettings.AdminChannel != localSettings.AdminChannel || oldSettings.CommandChannel != localSettings.CommandChannel) { From cfe6c3eecfcd3e2e63b5b9a3854eb0d990ec9ce0 Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 11 Jul 2023 05:04:54 -0600 Subject: [PATCH 15/17] but actually now --- README.md | 2 +- Server/DiscordBot.cs | 3 +++ Server/Server.csproj | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f13ae8..89c2b5c 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Banlist: banned people are unable to join the server, default: false PersistShines/Moons: Allows the server to remember moon progress across crashes/restarts ### Discord -Note: Token, LogChannel and CommandChannel need to have quotes "" around it +Note: Token, AdminChannel (formerly known as "LogChannel") and CommandChannel need to have quotes "" around it Token: the token of the bot you want to load into, default: null Prefix: the bot prefix to be used, default: $ CommandChannel: allows discord commands, default: null diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index 4109249..89d274a 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -37,6 +37,9 @@ public class DiscordBot Logger.AddLogHandler(LogToDiscordLogChannel); } + //this nonsense is to prevent race conditions from starting multiple bots. + //this would be a great thing to instead simply have an "await Init()" put + //in the ctor (but awaits can't be there), and Task.Wait shouldn't be used that way. private object firstInitLockObj = new object(); public async Task FirstInit() { diff --git a/Server/Server.csproj b/Server/Server.csproj index 6608e67..166bc50 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -13,7 +13,6 @@ - From a5b834834c33d1fcb58d129b5c179eba1a3f643c Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Tue, 11 Jul 2023 05:09:46 -0600 Subject: [PATCH 16/17] reordered Init to make startup more clear. --- Server/DiscordBot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index 89d274a..477bdf9 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -113,8 +113,6 @@ public class DiscordBot }); try { - await client.LoginAsync(Discord.TokenType.Bot, localSettings.Token); - await client.StartAsync(); SemaphoreSlim wait = new SemaphoreSlim(0); #pragma warning disable CS1998 client.Ready += async () => @@ -122,6 +120,8 @@ public class DiscordBot { wait.Release(); }; + await client.LoginAsync(Discord.TokenType.Bot, localSettings.Token); + await client.StartAsync(); await wait.WaitAsync(); //we need to wait for the ready event before we can do any of this nonsense. logChannel = (ulong.TryParse(localSettings.AdminChannel, out ulong lcid) ? (client != null ? await client.GetChannelAsync(lcid) : null) : null) as SocketTextChannel; From ba1a283c929a1e93f44e53e741f8295672c05bce Mon Sep 17 00:00:00 2001 From: TheUbMunster Date: Wed, 12 Jul 2023 23:46:12 -0600 Subject: [PATCH 17/17] Added the ability to enable/disable the discord bot via settings.json --- Server/DiscordBot.cs | 10 ++++++---- Server/Settings.cs | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Server/DiscordBot.cs b/Server/DiscordBot.cs index 477bdf9..8200569 100644 --- a/Server/DiscordBot.cs +++ b/Server/DiscordBot.cs @@ -30,9 +30,10 @@ public class DiscordBot Init(); #pragma warning restore CS4014 }); - return "Restarting Discord bot..."; + return localSettings.Enabled ? "Restarting Discord bot..." : "The discord bot is disabled in settings.json (no action was taken)."; }); - logger.Info("Starting discord bot (ctor)"); + if (localSettings.Enabled) + logger.Info("Starting discord bot (ctor)"); Settings.LoadHandler += OnLoadSettings; Logger.AddLogHandler(LogToDiscordLogChannel); } @@ -54,9 +55,10 @@ public class DiscordBot private async Task Init() { - if (client != null) + if (client != null || !localSettings.Enabled) { - return; //this is bad if the client ever crashes and isn't reassigned to null, but we don't want multiple instances of the bot running at the same time. + return; //Either: the discord bot is disabled, or: this is bad if the client ever crashes and + //isn't reassigned to null, but we don't want multiple instances of the bot running at the same time. } if (localSettings.Token == null || (localSettings.AdminChannel == null && localSettings.CommandChannel == null)) { diff --git a/Server/Settings.cs b/Server/Settings.cs index c89b275..25a6e4c 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -71,6 +71,7 @@ public class Settings { } public class DiscordTable { + public bool Enabled { get; set; } = true; public string? Token { get; set; } public string Prefix { get; set; } = "$"; public string? CommandChannel { get; set; }