I just set up the cmd feature in rustcord.  I've used rustcord for 7 years or so on my server?  and it's awesome.  But one shortcoming I've recentlly found, is console output isn't returned to a discord channel when using a command.  for instance, when adding a user to a oxide group, while discord said "success", the console said "player not found"

So, I am not a coder, but ai-fixed it, and offering it here for review and hopeful addition as a feature.

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core.Plugins;
using UnityEngine;
using Facepunch;
using Oxide.Core.Libraries.Covalence;
using Oxide.Ext.Discord.Builders;
using Oxide.Ext.Discord.Clients;
using Oxide.Ext.Discord.Connections;
using Oxide.Ext.Discord.Constants;
using Oxide.Ext.Discord.Entities;
using Oxide.Ext.Discord.Interfaces;
using Oxide.Ext.Discord.Logging;

namespace Oxide.Plugins
{
    [Info("Rustcord", "Kirollos & OuTSMoKE", "3.4.1")]
    [Description("Complete game server monitoring through discord.")]
    internal class Rustcord : RustPlugin, IDiscordPlugin
    {
        // --- BEGIN ADDED FIELDS FOR CONSOLE CAPTURE ---
        private bool _captureActive = false;
        private List<string> _captureBuffer = new List<string>();
        private Snowflake _captureChannel;
// --- END ADDED FIELDS ---

        [PluginReference] Plugin PrivateMessages, BetterChatMute, Clans, AdminChat, DiscordAuth, AdminHammer, AdminRadar, Kits, Vanish, RaidableBases, DangerousTreasures, NoGiveNotices, Give, AirEvent, HarborEvent, JunkyardEvent, PowerPlantEvent;
        public DiscordClient Client { get; set; }

        #region Back End Shit

        private Settings _settings;

        private int? _channelCount;

        private Snowflake _botId;

        private UpdatePresenceCommand DiscordPresence = new UpdatePresenceCommand
        {
            Activities = new List<DiscordActivity>
            {
                new DiscordActivity
                {
                    Type = ActivityType.Game,
                    Name = "Rustcord Initializing..."
                }
            }
        };

        private Timer StatusTimer = null;

        private object FindUserByID(DiscordUser user)
        {
            throw new NotImplementedException();
        }

        private static string FormatTime(TimeSpan time)
        {
            var values = new List<string>();

            if (time.Days != 0)
                values.Add($"{time.Days} day(s)");

            if (time.Hours != 0)
                values.Add($"{time.Hours} hour(s)");

            if (time.Minutes != 0)
                values.Add($"{time.Minutes} minute(s)");

            if (time.Seconds != 0)
                values.Add($"{time.Seconds} second(s)");

            return values.ToSentence();
        }

        private string GetPlayerFormattedField(IPlayer player)
        {
            return $"{player.Name} ([{player.Id}](https://steamcommunity.com/profiles/{player.Id}))";
        }

        private string GetFormattedSteamID(string id)
        {
            return $"[{id}](https://steamcommunity.com/profiles/{id})";
        }

        private string FindGridPosition(Vector3 position) => MapHelper.GridToString(MapHelper.PositionToGrid(position));

        enum CacheType
        {
            OnPlayerChat = 0,
            OnPlayerConnected = 1,
            OnPlayerDisconnected = 2,
            OnPlayerJoin = 3
        }

        // --- BEGIN ADDED: run a server command and send its output back to Discord (no Unity dependency) ---
        
        // --- BEGIN ADDED: run a server command and send its console output back to Discord using capture window ---
        private void ExecuteCommandAndReturn(string cmd, string[] args, Snowflake channelId, float captureSeconds = 5.0f)
        {
            string fullCmd = cmd + ((args != null && args.Length > 0) ? " " + string.Join(" ", args) : "");

            // Prepare capture
            _captureBuffer.Clear();
            _captureChannel = channelId;
            _captureActive = true;
            Puts($"[Rustcord] Capture START for: {fullCmd} (window={captureSeconds}s)");

            try
            {
                if (args != null && args.Length > 0)
                    this.Server.Command(cmd, args);
                else
                    this.Server.Command(cmd);
            }
            catch (Exception e)
            {
                _captureBuffer.Add($"[Rustcord] Exception while executing command: {e.Message}");
            }

            // Stop capture shortly after to collect emitted console lines
            timer.Once(captureSeconds, () =>
            {
                _captureActive = false;
                Puts($"[Rustcord] Capture END for: {fullCmd}. Lines captured: {_captureBuffer.Count}");

                string output = _captureBuffer.Count > 0 ? string.Join("\n", _captureBuffer) : "(no console output)";
                if (output.Length > 1900)
                    output = output.Substring(0, 1900) + "\n... (truncated)";

                string msg = $"↩️ **Console output for:** `{fullCmd}`\n```{output}```";

                GetChannel(Client, _captureChannel, chan =>
                {
                    chan.CreateMessage(Client, msg);
                });
            });
        }
        // --- END ADDED ---

Dictionary<CacheType, Dictionary<BasePlayer, Dictionary<string, string>>> cache = new Dictionary<CacheType, Dictionary<BasePlayer, Dictionary<string, string>>>();
        private string rbdiff;

        Dictionary<string, string> GetPlayerCache(BasePlayer player, string message, CacheType type)
        {
            switch (type)
            {
                case CacheType.OnPlayerChat:
                    {
                        Dictionary<string, string> dict;
                        if (!cache[CacheType.OnPlayerChat].TryGetValue(player, out dict))
                        {
                            cache[CacheType.OnPlayerChat].Add(player, dict = new Dictionary<string, string>
                            {
                                ["playername"] = player.displayName,
                                ["message"] = message,
                                ["playersteamid"] = player.UserIDString,
                                ["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt")
                            });
                        }

                        dict["playername"] = player.displayName;
                        dict["message"] = message;
                        dict["playersteamid"] = player.UserIDString;
                        dict["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt");
                        return dict;
                    }
                case CacheType.OnPlayerConnected:
                    {
                        Dictionary<string, string> dict;
                        if (!cache[CacheType.OnPlayerConnected].TryGetValue(player, out dict))
                        {
                            cache[CacheType.OnPlayerConnected].Add(player, dict = new Dictionary<string, string>
                            {
                                ["playername"] = player.displayName,
                                ["playerip"] = message.Substring(0, message.IndexOf(":")),
                                ["playersteamid"] = player.UserIDString,
                                ["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt")
                            });
                        }

                        dict["playername"] = player.displayName;
                        dict["playerip"] = message.Substring(0, message.IndexOf(":"));
                        dict["playersteamid"] = player.UserIDString;
                        dict["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt");
                        return dict;
                    }
                case CacheType.OnPlayerJoin:
                    {
                        Dictionary<string, string> dict;
                        if (!cache[CacheType.OnPlayerJoin].TryGetValue(player, out dict))
                        {
                            cache[CacheType.OnPlayerDisconnected].Add(player, dict = new Dictionary<string, string>
                            {
                                ["playername"] = player.displayName,
                                ["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt")
                            });
                        }

                        dict["playername"] = player.displayName;
                        dict["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt");
                        return dict;
                    }
                case CacheType.OnPlayerDisconnected:
                default:
                    {
                        Dictionary<string, string> dict;
                        if (!cache[CacheType.OnPlayerDisconnected].TryGetValue(player, out dict))
                        {
                            cache[CacheType.OnPlayerDisconnected].Add(player, dict = new Dictionary<string, string>
                            {
                                ["playername"] = player.displayName,
                                ["reason"] = message,
                                ["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt")
                            });
                        }

                        dict["playername"] = player.displayName;
                        dict["reason"] = message;
                        dict["time"] = DateTimeOffset.UtcNow.DateTime.ToLocalTime().ToString("hh:mm tt");
                        return dict;
                    }
            }
        }

        private void OnDiscordClientCreated()
        {
            if (string.IsNullOrEmpty(_settings.General.Apikey) || _settings.General.Apikey == null || _settings.General.Apikey == "BotToken")
            {
                PrintError("API key is empty or invalid!");
                return;
            }

            bool flag = true;
            try
            {
                BotConnection settings = new BotConnection
                {
                    ApiToken = _settings.General.Apikey,
                    Intents = GatewayIntents.Guilds | GatewayIntents.GuildMembers | GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.MessageContent,
                    LogLevel = _settings.General.ExtensionDebugging
                };
                Client.Connect(settings);
            }
            catch (Exception e)
            {
                flag = false;
                PrintError($"Rustcord failed to create client! Exception message: {e}");
            }

            if (flag)
            {
                cmd.AddChatCommand(_settings.General.ReportCommand, this, "cmdReport");
                cmd.AddChatCommand("bug", this, "cmdBug");
                SubscribeHooks();
            }

            UnityEngine.Application.logMessageReceived += ConsoleLog;
        }

        
        // --- BEGIN ADDED: Hook to capture server console output while active ---
        private void OnServerMessage(string message)
        {
            if (!_captureActive) return;
            if (string.IsNullOrEmpty(message)) return;

            // Filter out our own Discord echo to avoid loops
            if (message.StartsWith("[DISCORD]") || message.StartsWith("[Discord]"))
                return;

            _captureBuffer.Add(message);
        }
        // --- END ADDED ---
private void Reload()
        {
            rust.RunServerCommand("oxide.reload Rustcord");
        }

        void OnDiscordGatewayReady(GatewayReadyEvent rdy)
        {
            _botId = rdy.User.Id;
            SubscribeHooks();
            _channelCount = _settings?.Channels.Count;
            if (_settings.General.EnableBotStatus)
            {
                NextFrame(() =>
                {
                    if (StatusTimer != null && !StatusTimer.Destroyed)
                    {
                        StatusTimer.Destroy();
                    }
                    StatusTimer = timer.Every(6f, () =>
                    {
                        var text = new Dictionary<string, string>
                        {
                            ["playercount"] = Convert.ToString(BasePlayer.activePlayerList.Count),
                            ["maxplayers"] = Convert.ToString(ConVar.Server.maxplayers),
                            ["sleepercount"] = Convert.ToString(BasePlayer.sleepingPlayerList.Count)
                        };
                        var msg = Translate("Discord_Status", text);
                        DiscordPresence.Activities[0].Name = string.IsNullOrEmpty(msg) ? "Rustcord initializing...." : msg;
                        Client.UpdateStatus(DiscordPresence);
                    });
                });
            }
        }

        void OnDiscordGuildCreated(DiscordGuild newguild)
        {

            for (int i = 0; i < _channelCount; i++)
            {
                if (_settings.Channels[i].perms.Contains("msg_plugininit"))
                {
                    GetChannel(Client, _settings.Channels[i].Channelid, c => {
                        c.CreateMessage(Client, "Rustcord Initialized!");
                    }, newguild.Id);
                }
            }
        }

        private void Unload()
        {
            if (StatusTimer != null && !StatusTimer.Destroyed)
            {
                StatusTimer.Destroy();
            }
            UnityEngine.Application.logMessageReceived -= ConsoleLog;
        }

        [HookMethod(DiscordExtHooks.OnDiscordGuildMessageCreated)]
        private void OnDiscordGuildMessageCreated(DiscordMessage message)
        {
            //Puts($"{nameof(OnDiscordGuildMessageCreated)} New Message: {message.Author.FullUserName}: {message.Content}.");
            if ((message.Content?.Length ?? 0) == 0)
            {
                //Puts($"{nameof(OnDiscordGuildMessageCreated)} Skipping Message {message.Author.FullUserName}: {message.Content}. Content Length is 0");
                return;
            }
            
            Settings.Channel channelidx = FindChannelById(message.ChannelId);
            if (channelidx == null)
            {
                //Puts($"{nameof(OnDiscordGuildMessageCreated)} Skipping Message {message.Author.FullUserName}: {message.Content}. Channel Not Found");
                return;
            }

            if (message.Author.Id == _botId)
            {
                return;
            }
            
            if (message.Content[0] == _settings.DiscordSide.Commandprefix[0])
            {
                if (!channelidx.perms.Contains("cmd_allow"))
                    return;
                string cmd;
                string msg;
                try
                {
                    cmd = message.Content.Split(' ')[0].ToLower();
                    if (string.IsNullOrEmpty(cmd.Trim()))
                        cmd = message.Content.Trim().ToLower();
                }
                catch
                {
                    cmd = message.Content.Trim().ToLower();
                }

                cmd = cmd.Remove(0, 1);

                msg = message.Content.Remove(0, 1 + cmd.Length).Trim();
                cmd = cmd.Trim();
                cmd = cmd.ToLower();

                if (!channelidx.perms.Contains("cmd_" + cmd))
                    return;
                if (!_settings.Commandroles.ContainsKey(cmd))
                {
                    DiscordToGameCmd(cmd, msg, message.Author, message.ChannelId);
                    return;
                }
                var roles = _settings.Commandroles[cmd];
                if (roles.Count == 0)
                {
                    DiscordToGameCmd(cmd, msg, message.Author, message.ChannelId);
                    return;
                }

                foreach (var roleid in message.Member.Roles)
                {
                    var rolename = GetRoleNameById(roleid);
                    if (roles.Contains(rolename))
                    {
                        DiscordToGameCmd(cmd, msg, message.Author, message.ChannelId);
                        break;
                    }
                }
            }
            else
            {
                var chattag = _settings.DiscordSide.GameChatTag;
                var chattagcolor = _settings.DiscordSide.GameChatTagColor;
                var chatnamecolor = _settings.DiscordSide.GameChatNameColor;
                var chattextcolor = _settings.DiscordSide.GameChatTextColor;
                if (!channelidx.perms.Contains("msg_chat")) return;
                string nickname = message.Member?.Nickname ?? "";
                if (nickname.Length == 0)
                    nickname = message.Author.Username;
                //PrintToChat("<color=" + chattagcolor + ">" + chattag + "</color> " + "<color=" + chatnamecolor + ">" + nickname + ":</color> " + "<color=" + chattextcolor + ">" + message.content + "</color>");
                string text = $"<color={chattagcolor}>{chattag}</color> <color={chatnamecolor}>{nickname}:</color> <color={chattextcolor}>{message.Content}</color>";
                foreach (var player in BasePlayer.activePlayerList) Player.Message(player, text, _settings.DiscordSide.GameChatIconSteamID);
                Puts("[DISCORD] " + nickname + ": " + message.Content);
            }
        }

        private string Translate(string msg, Dictionary<string, string> parameters = null)
        {
            if (string.IsNullOrEmpty(msg))
                return string.Empty;

            msg = lang.GetMessage(msg, this);

            if (parameters != null)
            {
                foreach (var lekey in parameters)
                {
                    if (msg.Contains("{" + lekey.Key + "}"))
                        msg = msg.Replace("{" + lekey.Key + "}", lekey.Value);
                }
            }

            return msg;
        }

        private Settings.Channel FindChannelById(Snowflake id)
        {
            for (int i = 0; i < _channelCount; i++)
            {
                if (_settings.Channels[i].Channelid == id)
                    return _settings.Channels[i];
            }

            return null;
        }

        private void GetChannel(DiscordClient c, Snowflake chan_id, Action<DiscordChannel> cb, Snowflake guildid = default(Snowflake))
        {
            //Guild g = guildid == null ? c.DiscordServers.FirstOrDefault(x => x.channels.FirstOrDefault(y => y.id == chan_id) != null) : c.GetGuild(guildid);
            DiscordGuild g = null;
            DiscordChannel foundchan = null;
            if (guildid.IsValid())
                g = c.Bot.GetGuild(guildid);
            else
                foreach (var G in c.Bot.Servers.Values)
                {
                    foundchan = G.Channels[chan_id];
                    if (foundchan != null)
                    {
                        g = G;
                        break;
                    }
                }
            if (g == null)
            {
                PrintWarning($"Rustcord failed to fetch channel! (chan_id={chan_id}). Guild is invalid.");
                return;
            }
            if (g.Unavailable ?? false == true)
            {
                PrintWarning($"Rustcord failed to fetch channel! (chan_id={chan_id}). Guild is possibly invalid or not available yet.");
                return;
            }
            //Channel foundchan = g?.channels?.FirstOrDefault(z => z.id == chan_id);
            if (foundchan == null)
            {
                if (guildid.IsValid()) return; // Ignore printing error
                PrintWarning($"Rustcord failed to fetch channel! (chan_id={chan_id}).");
                return;
            }
            if (foundchan.Id != chan_id) return;
            cb?.Invoke(foundchan);
        }

        private string GetRoleNameById(Snowflake id)
        {
            //var role = _client.DiscordServers.FirstOrDefault(x => x.roles.FirstOrDefault(y => y.id == id) != null)?.roles.FirstOrDefault(z => z.id == id);
            //return role?.name ?? "";
            foreach (var r in Client.Bot.Servers.Values)
            {
                var role = r.Roles[id];
                if (role != null)
                {
                    return role.Name;
                }
            }
            return string.Empty;
        }

        private IPlayer FindPlayer(string nameorId)
        {
            foreach (var player in covalence.Players.Connected)
            {
                if (player.Id == nameorId)
                    return player;

                if (player.Name == nameorId)
                    return player;
            }

            return null;
        }

        private DiscordUser FindUserByID(Snowflake id)
        {
            foreach (DiscordGuild guild in Client.Bot.Servers.Values)
            {
                var member = guild.Members[id];
                if (member != null)
                {
                    return member.User;
                }
            }

            return null;
        }

        private BasePlayer FindPlayerByID(string Id)
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player.UserIDString == Id)
                    return player;
            }

            return null;
        }

        private IPlayer GetPlayer(string id)
        {
            return covalence.Players.FindPlayerById(id);
        }

        private IPlayer GetPlayer(ulong id)
        {
            return GetPlayer(id.ToString());
        }

        #endregion

        #region Config Layout
        private class Settings
        {
            [JsonProperty(PropertyName = "General Settings")]
            public GeneralSettings General { get; set; }

            [JsonProperty(PropertyName = "Discord to Game Settings")]
            public DiscordSideSettings DiscordSide { get; set; }

            [JsonProperty(PropertyName = "Rust Logging Settings")]
            public GameLogSettings GameLog { get; set; }

            [JsonProperty(PropertyName = "Plugin Logging Settings")]
            public PluginLogSettings PluginLog { get; set; }

            [JsonProperty(PropertyName = "Premium Plugin Logging Settings")]
            public PremiumPluginLogSettings PremiumPluginLog { get; set; }

            [JsonProperty(PropertyName = "Discord Output Formatting")]
            public OutputSettings OutputFormat { get; set; }

            [JsonProperty(PropertyName = "Logging Exclusions")]
            public ExcludedSettings Excluded { get; set; }

            [JsonProperty(PropertyName = "Filter Settings")]
            public FilterSettings Filters { get; set; }

            [JsonProperty(PropertyName = "Discord Logging Channels")]
            public List<Channel> Channels { get; set; }

            [JsonProperty(PropertyName = "Discord Command Role Assignment (Empty = All roles can use command.)")]
            public Dictionary<string, List<string>> Commandroles { get; set; }

            public class Channel
            {
                [JsonProperty(PropertyName = "Discord Channel ID #")]
                public Snowflake Channelid { get; set; }

                [JsonProperty(PropertyName = "Channel Flags")]
                public List<string> perms { get; set; }

                [JsonProperty(PropertyName = "Custom: Words/Phrases to Log")]
                public List<string> CustomFilter { get; set; }

                [JsonIgnore]
                public readonly StringBuilder FilterBuilder = new StringBuilder();

                [JsonIgnore]
                public Timer Timer;
            }
        }

        public class GeneralSettings
        {
            [JsonProperty(PropertyName = "API Key (Bot Token)")]
            public string Apikey { get; set; }

            [JsonProperty(PropertyName = "Auto Reload Plugin")]
            public bool AutoReloadPlugin { get; set; }

            [JsonProperty(PropertyName = "Auto Reload Time (Seconds)")]
            public int AutoReloadTime { get; set; }

            [JsonProperty(PropertyName = "Enable Bot Status")]
            public bool EnableBotStatus { get; set; }

            [JsonProperty(PropertyName = "In-Game Report Command")]
            public string ReportCommand { get; set; }

            [JsonConverter(typeof(StringEnumConverter))]
            [DefaultValue(DiscordLogLevel.Info)]
            [JsonProperty(PropertyName = "Discord Extension Log Level (Verbose/Debug/Info/Warning/Error/Exception/Off)")]
            public DiscordLogLevel ExtensionDebugging { get; set; } = DiscordLogLevel.Info;
        }

        public class DiscordSideSettings
        {
            [JsonProperty(PropertyName = "Discord Command Prefix")]
            public string Commandprefix { get; set; }

            [JsonProperty(PropertyName = "Discord to Game Chat: Icon (Steam ID)")]
            public ulong GameChatIconSteamID { get; set; }

            [JsonProperty(PropertyName = "Discord to Game Chat: Tag")]
            public string GameChatTag { get; set; }

            [JsonProperty(PropertyName = "Discord to Game Chat: Tag Color (Hex)")]
            public string GameChatTagColor { get; set; }

            [JsonProperty(PropertyName = "Discord to Game Chat: Player Name Color (Hex)")]
            public string GameChatNameColor { get; set; }

            [JsonProperty(PropertyName = "Discord to Game Chat: Message Color (Hex)")]
            public string GameChatTextColor { get; set; }
        }

        public class GameLogSettings
        {
            [JsonProperty(PropertyName = "Enable Logging: Player Chat")]
            public bool LogChat { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Joins & Quits")]
            public bool LogJoinQuits { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Deaths")]
            public bool LogDeaths { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Vehicle Spawns (Heli/APC/Plane/Ship)")]
            public bool LogVehicleSpawns { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Crate Drops (Hackable/Supply)")]
            public bool LogCrateDrops { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Usergroup Changes")]
            public bool LogUserGroups { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Permission Changes")]
            public bool LogPermissions { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Kicks & Bans")]
            public bool LogKickBans { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Player Name Changes")]
            public bool LogNameChanges { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Server Commands (Gestures/Note Edits)")]
            public bool LogServerCommands { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Server Messages (Give/Item Spawns)")]
            public bool LogServerMessages { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Player F7 Reports")]
            public bool LogF7Reports { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Team Changes")]
            public bool LogTeams { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: RCON Connections")]
            public bool LogRCON { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Spectates")]
            public bool LogSpectates { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Server Wipe")]
            public bool LogServerWipe { get; set; }

            [JsonProperty(PropertyName = "Enable Custom Logging")]
            public bool EnableCustomLogging { get; set; }
        }

        public class PluginLogSettings
        {
            [JsonProperty(PropertyName = "Enable Logging: AdminHammer")]
            public bool LogPluginAdminHammer { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Admin Radar")]
            public bool LogPluginAdminRadar { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Better Chat Mute")]
            public bool LogPluginBetterChatMute { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Clans")]
            public bool LogPluginClans { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Dangerous Treasures")]
            public bool LogPluginDangerousTreasures { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Discord Auth")]
            public bool LogPluginDiscordAuth { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Godmode")]
            public bool LogPluginGodmode { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Kits")]
            public bool LogPluginKits { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Private Messages")]
            public bool LogPluginPrivateMessages { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Raidable Bases")]
            public bool LogPluginRaidableBases { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Sign Artist")]
            public bool LogPluginSignArtist { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Vanish")]
            public bool LogPluginVanish { get; set; }
        }
        public class PremiumPluginLogSettings
        {
            [JsonProperty(PropertyName = "Enable Logging: Air Event")]
            public bool LogPluginAirEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Armored Train Event")]
            public bool LogPluginArmoredTrainEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Cargo Train Event")]
            public bool LogPluginCargoTrainEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Convoy Event")]
            public bool LogPluginConvoyEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Harbor Event")]
            public bool LogPluginHarborEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Junkyard Event")]
            public bool LogPluginJunkyardEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Power Plant Event")]
            public bool LogPluginPowerPlantEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Satellite Dish Event")]
            public bool LogPluginSatDishEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Sputnik Event")]
            public bool LogPluginSputnikEvent { get; set; }

            [JsonProperty(PropertyName = "Enable Logging: Water Event")]
            public bool LogPluginWaterEvent { get; set; }
        }

        public class OutputSettings
        {
            [JsonProperty(PropertyName = "Output Type: Bans (Simple/Embed)")]
            public string OutputTypeBans { get; set; }

            [JsonProperty(PropertyName = "Output Type: Bug Report (Simple/Embed)")]
            public string OutputTypeBugs { get; set; }

            [JsonProperty(PropertyName = "Output Type: Deaths (Simple/Embed/DeathNotes)")]
            public string OutputTypeDeaths { get; set; }

            [JsonProperty(PropertyName = "Output Type: F7 Reports (Simple/Embed)")]
            public string OutputTypeF7Report { get; set; }

            [JsonProperty(PropertyName = "Output Type: Join/Quit (Simple/Embed)")]
            public string OutputTypeJoinQuit { get; set; }

            [JsonProperty(PropertyName = "Output Type: Join Player Info (Admin Channel) (Simple/Embed)")]
            public string OutputTypeJoinAdminChan { get; set; }

            [JsonProperty(PropertyName = "Output Type: Kicks (Simple/Embed)")]
            public string OutputTypeKicks { get; set; }

            [JsonProperty(PropertyName = "Output Type: Note Logging (Simple/Embed)")]
            public string OutputTypeNoteLog { get; set; }

            [JsonProperty(PropertyName = "Output Type: Player Name Change (Simple/Embed)")]
            public string OutputTypeNameChange { get; set; }

            [JsonProperty(PropertyName = "Output Type: /Report (Simple/Embed)")]
            public string OutputTypeReports { get; set; }

            [JsonProperty(PropertyName = "Output Type: Server Wipe (Simple/Embed)")]
            public string OutputTypeServerWipe { get; set; }

            [JsonProperty(PropertyName = "Output Type: Teams (Simple/Embed)")]
            public string OutputTypeTeams { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Admin Hammer (Simple/Embed)")]
            public string OutputTypeAdminHammer { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Admin Radar (Simple/Embed)")]
            public string OutputTypeAdminRadar { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Better Chat Mute (Simple/Embed)")]
            public string OutputTypeBetterChatMute { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Clans (Simple/Embed)")]
            public string OutputTypeClans { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Dangerous Treasures (Simple/Embed)")]
            public string OutputTypeDangerousTreasures { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Discord Auth (Simple/Embed)")]
            public string OutputTypeDiscordAuth { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Godmode (Simple/Embed)")]
            public string OutputTypeGodmode { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Kits (Simple/Embed)")]
            public string OutputTypeKits { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Private Messages (Simple/Embed)")]
            public string OutputTypePMs { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Raidable Bases (Simple/Embed)")]
            public string OutputTypeRaidableBases { get; set; }

            [JsonProperty(PropertyName = "Output Type (Plugin): Vanish (Simple/Embed)")]
            public string OutputTypeVanish { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): AirEvent (Simple/Embed)")]
            public string OutputTypeAirEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): ArmoredTrainEvent (Simple/Embed)")]
            public string OutputTypeArmoredTrainEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): CargoTrainEvent (Simple/Embed)")]
            public string OutputTypeCargoTrainEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): ConvoyEvent (Simple/Embed)")]
            public string OutputTypeConvoyEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): HarborEvent (Simple/Embed)")]
            public string OutputTypeHarborEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): JunkyardEvent (Simple/Embed)")]
            public string OutputTypeJunkyardEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): PowerPlantEvent (Simple/Embed)")]
            public string OutputTypePowerPlantEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): SatDishEvent (Simple/Embed)")]
            public string OutputTypeSatDishEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): SputnikEvent (Simple/Embed)")]
            public string OutputTypeSputnikEvent { get; set; }

            [JsonProperty(PropertyName = "Output Type (Premium Plugin): WaterEvent (Simple/Embed)")]
            public string OutputTypeWaterEvent { get; set; }
        }

        public class ExcludedSettings
        {
            [JsonProperty(PropertyName = "Exclude Listed Groups From log_groups")]
            public List<string> LogExcludeGroups { get; set; }

            [JsonProperty(PropertyName = "Exclude Listed Permissions From log_perms")]
            public List<string> LogExcludePerms { get; set; }
        }

        public class FilterSettings
        {
            [JsonProperty(PropertyName = "Chat Filter: Replacement Word")]
            public string FilteredWord { get; set; }

            [JsonProperty(PropertyName = "Chat Filter: Words to Filter")]
            public List<string> FilterWords { get; set; }
        }
        #endregion

        #region Default Config
        private Settings GetDefaultSettings()
        {
            return new Settings
            {
                General = new GeneralSettings
                {
                    Apikey = "BotToken",
                    AutoReloadPlugin = false,
                    AutoReloadTime = 901,
                    EnableBotStatus = false,
                    ReportCommand = "report",
                    ExtensionDebugging = DiscordLogLevel.Info
                },
                DiscordSide = new DiscordSideSettings
                {
                    Commandprefix = "!",
                    GameChatIconSteamID = 76561############,
                    GameChatTag = "[RUSTCORD]",
                    GameChatTagColor = "#7289DA",
                    GameChatNameColor = "#55aaff",
                    GameChatTextColor = "#ffffff",
                },
                GameLog = new GameLogSettings
                {
                    LogChat = true,
                    LogJoinQuits = true,
                    LogDeaths = false,
                    LogVehicleSpawns = false,
                    LogCrateDrops = false,
                    LogUserGroups = false,
                    LogPermissions = false,
                    LogKickBans = true,
                    LogNameChanges = false,
                    LogServerCommands = false,
                    LogServerMessages = false,
                    LogF7Reports = false,
                    LogTeams = false,
                    LogRCON = false,
                    LogSpectates = false,
                    LogServerWipe = false,
                    EnableCustomLogging = false
                },
                PluginLog = new PluginLogSettings
                {
                    LogPluginAdminHammer = false,
                    LogPluginAdminRadar = false,
                    LogPluginBetterChatMute = false,
                    LogPluginClans = false,
                    LogPluginDangerousTreasures = false,
                    LogPluginDiscordAuth = false,
                    LogPluginGodmode = false,
                    LogPluginKits = false,
                    LogPluginPrivateMessages = false,
                    LogPluginRaidableBases = false,
                    LogPluginSignArtist = false,
                    LogPluginVanish = false
                },
                PremiumPluginLog = new PremiumPluginLogSettings
                {
                    LogPluginAirEvent = false,
                    LogPluginArmoredTrainEvent = false,
                    LogPluginCargoTrainEvent = false,
                    LogPluginConvoyEvent = false,
                    LogPluginHarborEvent = false,
                    LogPluginJunkyardEvent = false,
                    LogPluginPowerPlantEvent = false,
                    LogPluginSputnikEvent = false,
                    LogPluginSatDishEvent = false,
                    LogPluginWaterEvent = false
                },
                OutputFormat = new OutputSettings
                {
                    OutputTypeBans = "Simple",
                    OutputTypeBugs = "Simple",
                    OutputTypeDeaths = "Simple",
                    OutputTypeF7Report = "Simple",
                    OutputTypeJoinQuit = "Simple",
                    OutputTypeJoinAdminChan = "Simple",
                    OutputTypeKicks = "Simple",
                    OutputTypeNameChange = "Simple",
                    OutputTypeNoteLog = "Simple",
                    OutputTypeReports = "Simple",
                    OutputTypeServerWipe = "Simple",
                    OutputTypeTeams = "Simple",
                    OutputTypeAdminHammer = "Simple",
                    OutputTypeAdminRadar = "Simple",
                    OutputTypeBetterChatMute = "Simple",
                    OutputTypeClans = "Simple",
                    OutputTypeDangerousTreasures = "Simple",
                    OutputTypeDiscordAuth = "Simple",
                    OutputTypeGodmode = "Simple",
                    OutputTypeKits = "Simple",
                    OutputTypePMs = "Simple",
                    OutputTypeRaidableBases = "Simple",
                    OutputTypeVanish = "Simple",
                    OutputTypeAirEvent = "Simple",
                    OutputTypeArmoredTrainEvent = "Simple",
                    OutputTypeCargoTrainEvent = "Simple",
                    OutputTypeConvoyEvent = "Simple",
                    OutputTypeHarborEvent = "Simple",
                    OutputTypeJunkyardEvent = "Simple",
                    OutputTypePowerPlantEvent = "Simple",
                    OutputTypeSputnikEvent = "Simple",
                    OutputTypeSatDishEvent = "Simple",
                    OutputTypeWaterEvent = "Simple"
                },

                Channels = new List<Settings.Channel>
                    {
                        new Settings.Channel
                            {
                                perms = new List<string>
                                {
                                    "cmd_allow",
                                    "cmd_players",
                                    "cmd_kick",
                                    "cmd_com",
                                    "cmd_mute",
                                    "cmd_unmute",
                                    "msg_join",
                                    "msg_quit",
                                    "death_pvp",
                                    "msg_chat",
                                    "game_bug",
                                    "msg_serverinit"
                                },
                                CustomFilter = new List<string>
                                {
                                    "keyword1",
                                    "keyword2"
                                }
                        },
                        new Settings.Channel
                        {
                            perms = new List<string>
                            {
                                "msg_joinlog",
                                "game_report",
                                "msg_teamchat",
                                "game_bug"
                            },
                            CustomFilter = new List<string>
                            {
                                "keyword1",
                                "keyword2"
                            }
                        }
                    },
                Commandroles = new Dictionary<string, List<string>>
                {
                    {
                        "players", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "mute", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "unmute", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "kick", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "ban", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "timeban", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "unban", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    },
                    {
                        "com", new List<string>()
                        {
                            "DiscordRoleName",
                            "DiscordRoleName2"
                        }
                    }
                },
                Filters = new FilterSettings
                {
                    FilterWords = new List<string>
                    {
                        "badword1",
                        "badword2"
                    },
                    FilteredWord = "<censored>",
                },
                Excluded = new ExcludedSettings
                {
                    LogExcludeGroups = new List<string>
                    {
                        "example-group1",
                        "example-group2"
                    },
                    LogExcludePerms = new List<string>
                    {
                        "example.permission1",
                        "example.permission2"
                    }
                },

            };
        }

        protected override void LoadDefaultConfig()
        {
            PrintWarning("Attempting to create default config...");
            Config.Clear();
            Config.WriteObject(GetDefaultSettings(), true);
            Config.Save();
        }
        #endregion

        #region Hooks
        void SubscribeHooks()
        {
            if (_settings.GameLog.LogChat) Subscribe(nameof(OnPlayerChat));
            if (_settings.GameLog.LogJoinQuits)
            {
                Subscribe(nameof(OnPlayerConnected));
                Subscribe(nameof(OnPlayerDisconnected));
            }

            if (_settings.GameLog.LogDeaths)
            {
                if (_settings.OutputFormat.OutputTypeDeaths == "DeathNotes") Subscribe(nameof(OnDeathNotice));
                if ((_settings.OutputFormat.OutputTypeDeaths == "Simple") || (_settings.OutputFormat.OutputTypeDeaths == "Embed")) Subscribe(nameof(OnPlayerDeath));
            }

            if (_settings.GameLog.LogVehicleSpawns) Subscribe(nameof(OnEntitySpawned));
            if (_settings.GameLog.LogCrateDrops)
            {
                Subscribe(nameof(OnCrateDropped));
                Subscribe(nameof(OnSupplyDropLanded));
            }
            if (_settings.GameLog.LogUserGroups)
            {
                Subscribe(nameof(OnGroupCreated));
                Subscribe(nameof(OnGroupDeleted));
                Subscribe(nameof(OnUserGroupAdded));
                Subscribe(nameof(OnUserGroupRemoved));
            }
            if (_settings.GameLog.LogPermissions)
            {
                Subscribe(nameof(OnUserPermissionGranted));
                Subscribe(nameof(OnGroupPermissionGranted));
                Subscribe(nameof(OnUserPermissionRevoked));
                Subscribe(nameof(OnGroupPermissionRevoked));
            }
            if (_settings.GameLog.LogKickBans)
            {
                Subscribe(nameof(OnUserKicked));
                Subscribe(nameof(OnUserBanned));
                Subscribe(nameof(OnUserUnbanned));
            }
            if (_settings.GameLog.LogNameChanges) Subscribe(nameof(OnUserNameUpdated));
            if (_settings.GameLog.LogServerMessages) Subscribe(nameof(OnServerMessage));
            if (_settings.GameLog.LogServerCommands) Subscribe(nameof(OnServerCommand));
            if (_settings.GameLog.LogF7Reports) Subscribe(nameof(OnPlayerReported));
            if (_settings.GameLog.LogServerWipe) Subscribe(nameof(OnNewSave));
            if (_settings.GameLog.LogTeams)
            {
                Subscribe(nameof(OnTeamCreated));
                Subscribe(nameof(OnTeamAcceptInvite));
                Subscribe(nameof(OnTeamLeave));
                Subscribe(nameof(OnTeamKick));
                Subscribe(nameof(OnTeamDisbanded));
            }
            if (_settings.GameLog.LogRCON)
            {
                Subscribe(nameof(OnRconConnection));
            }
            if (_settings.GameLog.LogSpectates)
            {
                Subscribe(nameof(OnPlayerSpectate));
                Subscribe(nameof(OnPlayerSpectateEnd));
            }
            if (_settings.PluginLog.LogPluginAdminHammer)
            {
                Subscribe(nameof(OnAdminHammerEnabled));
                Subscribe(nameof(OnAdminHammerDisabled));
            }
            if (_settings.PluginLog.LogPluginAdminRadar)
            {
                Subscribe(nameof(OnRadarActivated));
                Subscribe(nameof(OnRadarDeactivated));
            }
            if (_settings.PluginLog.LogPluginBetterChatMute)
            {
                Subscribe(nameof(OnBetterChatMuted));
                Subscribe(nameof(OnBetterChatTimeMuted));
                Subscribe(nameof(OnBetterChatUnmuted));
                Subscribe(nameof(OnBetterChatMuteExpired));
            }
            if (_settings.PluginLog.LogPluginClans)
            {
                Subscribe(nameof(OnClanCreate));
                Subscribe(nameof(OnClanDisbanded));
                Subscribe(nameof(OnClanChat));
            }
            if (_settings.PluginLog.LogPluginDangerousTreasures)
            {
                Subscribe(nameof(OnDangerousEventStarted));
                Subscribe(nameof(OnDangerousEventEnded));
            }
            if (_settings.PluginLog.LogPluginGodmode)
            {
                Subscribe(nameof(OnGodmodeToggled));
            }
            if (_settings.PluginLog.LogPluginKits)
            {
                Subscribe(nameof(OnKitRedeemed));
            }
            if (_settings.PluginLog.LogPluginPrivateMessages) Subscribe(nameof(OnPMProcessed));
            if (_settings.PluginLog.LogPluginRaidableBases)
            {
                Subscribe(nameof(OnRaidableBaseStarted));
                Subscribe(nameof(OnRaidableBaseEnded));
            }
            if (_settings.PluginLog.LogPluginSignArtist) Subscribe(nameof(OnImagePost));
            if (_settings.PluginLog.LogPluginDiscordAuth)
            {
                Subscribe(nameof(OnDiscordPlayerLinked));
                Subscribe(nameof(OnDiscordPlayerUnlinked));
            }
            if (_settings.PluginLog.LogPluginVanish)
            {
                Subscribe(nameof(OnVanishDisappear));
                Subscribe(nameof(OnVanishReappear));
            }
            if (_settings.PremiumPluginLog.LogPluginAirEvent)
            {
                Subscribe(nameof(OnAirEventStart));
                Subscribe(nameof(OnAirEventEnd));
            }
            if (_settings.PremiumPluginLog.LogPluginArmoredTrainEvent)
            {
                Subscribe(nameof(OnArmoredTrainEventStart));
                Subscribe(nameof(OnArmoredTrainEventStop));
            }
            if (_settings.PremiumPluginLog.LogPluginCargoTrainEvent)
            {
                Subscribe(nameof(OnTrainEventStarted));
                Subscribe(nameof(OnTrainEventEnded));
            }
            if (_settings.PremiumPluginLog.LogPluginConvoyEvent)
            {
                Subscribe(nameof(OnConvoyStart));
                Subscribe(nameof(OnConvoyStop));
            }
            if (_settings.PremiumPluginLog.LogPluginHarborEvent)
            {
                Subscribe(nameof(OnHarborEventStart));
                Subscribe(nameof(OnHarborEventEnd));
            }
            if (_settings.PremiumPluginLog.LogPluginJunkyardEvent)
            {
                Subscribe(nameof(OnJunkyardEventStart));
                Subscribe(nameof(OnJunkyardEventEnd));
            }
            if (_settings.PremiumPluginLog.LogPluginPowerPlantEvent)
            {
                Subscribe(nameof(OnPowerPlantEventStart));
                Subscribe(nameof(OnPowerPlantEventEnd));
            }
            if (_settings.PremiumPluginLog.LogPluginSputnikEvent)
            {
                Subscribe(nameof(OnSputnikEventStart));
                Subscribe(nameof(OnSputnikEventStop));
            }
            if (_settings.PremiumPluginLog.LogPluginSatDishEvent)
            {
                Subscribe(nameof(OnSatDishEventStart));
                Subscribe(nameof(OnSatDishEventEnd));
            }
            if (_settings.PremiumPluginLog.LogPluginWaterEvent)
            {
                Subscribe(nameof(OnWaterEventStart));
                Subscribe(nameof(OnWaterEventEnd));
            }
        }
        private void Init()
        {
            cache[CacheType.OnPlayerChat] = new Dictionary<BasePlayer, Dictionary<string, string>>();
            cache[CacheType.OnPlayerConnected] = new Dictionary<BasePlayer, Dictionary<string, string>>();
            cache[CacheType.OnPlayerDisconnected] = new Dictionary<BasePlayer, Dictionary<string, string>>();
            cache[CacheType.OnPlayerJoin] = new Dictionary<BasePlayer, Dictionary<string, string>>();
            UnsubscribeHooks();
        }

        void UnsubscribeHooks()
        {
            Unsubscribe(nameof(OnPlayerChat));
            Unsubscribe(nameof(OnPlayerConnected));
            Unsubscribe(nameof(OnPlayerDisconnected));
            Unsubscribe(nameof(OnDeathNotice));
            Unsubscribe(nameof(OnPlayerDeath));
            Unsubscribe(nameof(OnEntitySpawned));
            Unsubscribe(nameof(OnCrateDropped));
            Unsubscribe(nameof(OnSupplyDropLanded));
            Unsubscribe(nameof(OnGroupCreated));
            Unsubscribe(nameof(OnGroupDeleted));
            Unsubscribe(nameof(OnUserGroupAdded));
            Unsubscribe(nameof(OnUserGroupRemoved));
            Unsubscribe(nameof(OnUserPermissionGranted));
            Unsubscribe(nameof(OnGroupPermissionGranted));
            Unsubscribe(nameof(OnUserPermissionRevoked));
            Unsubscribe(nameof(OnGroupPermissionRevoked));
            Unsubscribe(nameof(OnUserKicked));
            Unsubscribe(nameof(OnUserBanned));
            Unsubscribe(nameof(OnUserUnbanned));
            Unsubscribe(nameof(OnUserNameUpdated));
            Unsubscribe(nameof(OnServerMessage));
            Unsubscribe(nameof(OnServerCommand));
            Unsubscribe(nameof(OnPlayerReported));
            Unsubscribe(nameof(OnTeamCreated));
            Unsubscribe(nameof(OnTeamAcceptInvite));
            Unsubscribe(nameof(OnTeamLeave));
            Unsubscribe(nameof(OnTeamKick));
            Unsubscribe(nameof(OnTeamDisbanded));
            Unsubscribe(nameof(OnRconConnection));
            Unsubscribe(nameof(OnPlayerSpectate));
            Unsubscribe(nameof(OnPlayerSpectateEnd));
            Unsubscribe(nameof(OnAdminHammerEnabled));
            Unsubscribe(nameof(OnAdminHammerDisabled));
            Unsubscribe(nameof(OnRadarActivated));
            Unsubscribe(nameof(OnRadarDeactivated));
            Unsubscribe(nameof(OnBetterChatTimeMuted));
            Unsubscribe(nameof(OnBetterChatMuted));
            Unsubscribe(nameof(OnBetterChatTimeMuted));
            Unsubscribe(nameof(OnBetterChatUnmuted));
            Unsubscribe(nameof(OnBetterChatMuteExpired));
            Unsubscribe(nameof(OnClanCreate));
            Unsubscribe(nameof(OnClanDisbanded));
            Unsubscribe(nameof(OnClanChat));
            Unsubscribe(nameof(OnPMProcessed));
            Unsubscribe(nameof(OnImagePost));
            Unsubscribe(nameof(OnDiscordPlayerLinked));
            Unsubscribe(nameof(OnDiscordPlayerUnlinked));
            Unsubscribe(nameof(OnRaidableBaseStarted));
            Unsubscribe(nameof(OnRaidableBaseEnded));
            Unsubscribe(nameof(OnGodmodeToggled));
            Unsubscribe(nameof(OnKitRedeemed));
            Unsubscribe(nameof(OnDangerousEventStarted));
            Unsubscribe(nameof(OnDangerousEventEnded));
            Unsubscribe(nameof(OnAirEventStart));
            Unsubscribe(nameof(OnAirEventEnd));
            Unsubscribe(nameof(OnArmoredTrainEventStart));
            Unsubscribe(nameof(OnArmoredTrainEventStop));
            Unsubscribe(nameof(OnTrainEventStarted));
            Unsubscribe(nameof(OnTrainEventEnded));
            Unsubscribe(nameof(OnConvoyStart));
            Unsubscribe(nameof(OnConvoyStop));
            Unsubscribe(nameof(OnHarborEventStart));
            Unsubscribe(nameof(OnHarborEventEnd));
            Unsubscribe(nameof(OnJunkyardEventStart));
            Unsubscribe(nameof(OnJunkyardEventEnd));
            Unsubscribe(nameof(OnPowerPlantEventStart));
            Unsubscribe(nameof(OnPowerPlantEventEnd));
            Unsubscribe(nameof(OnSatDishEventStart));
            Unsubscribe(nameof(OnSatDishEventEnd));
            Unsubscribe(nameof(OnSputnikEventStart));
            Unsubscribe(nameof(OnSputnikEventStop));
            Unsubscribe(nameof(OnWaterEventStart));
            Unsubscribe(nameof(OnWaterEventEnd));
        }
        #endregion

        #region Null Config Check
        private void Loaded()
        {
            _settings = Config.ReadObject<Settings>();

            // Make sure objects are not taken off the config, otherwise some parts of code will release NRE.

            if (_settings.OutputFormat.OutputTypeBans == null)
                _settings.OutputFormat.OutputTypeBans = "Simple";
            if (_settings.OutputFormat.OutputTypeBugs == null)
                _settings.OutputFormat.OutputTypeBugs = "Simple";
            if (_settings.OutputFormat.OutputTypeDeaths == null)
                _settings.OutputFormat.OutputTypeDeaths = "Simple";
            if (_settings.OutputFormat.OutputTypeF7Report == null)
                _settings.OutputFormat.OutputTypeF7Report = "Simple";
            if (_settings.OutputFormat.OutputTypeJoinQuit == null)
                _settings.OutputFormat.OutputTypeJoinQuit = "Simple";
            if (_settings.OutputFormat.OutputTypeJoinAdminChan == null)
                _settings.OutputFormat.OutputTypeJoinAdminChan = "Simple";
            if (_settings.OutputFormat.OutputTypeKicks == null)
                _settings.OutputFormat.OutputTypeKicks = "Simple";
            if (_settings.OutputFormat.OutputTypeNameChange == null)
                _settings.OutputFormat.OutputTypeNameChange = "Simple";
            if (_settings.OutputFormat.OutputTypeNoteLog == null)
                _settings.OutputFormat.OutputTypeNoteLog = "Simple";
            if (_settings.OutputFormat.OutputTypeReports == null)
                _settings.OutputFormat.OutputTypeReports = "Simple";
            if (_settings.OutputFormat.OutputTypeServerWipe == null)
                _settings.OutputFormat.OutputTypeServerWipe = "Simple";
            if (_settings.OutputFormat.OutputTypeTeams == null)
                _settings.OutputFormat.OutputTypeTeams = "Simple";
            if (_settings.OutputFormat.OutputTypeAdminHammer == null)
                _settings.OutputFormat.OutputTypeAdminHammer = "Simple";
            if (_settings.OutputFormat.OutputTypeAdminRadar == null)
                _settings.OutputFormat.OutputTypeAdminRadar = "Simple";
            if (_settings.OutputFormat.OutputTypeBetterChatMute == null)
                _settings.OutputFormat.OutputTypeBetterChatMute = "Simple";
            if (_settings.OutputFormat.OutputTypeClans == null)
                _settings.OutputFormat.OutputTypeClans = "Simple";
            if (_settings.OutputFormat.OutputTypeDangerousTreasures == null)
                _settings.OutputFormat.OutputTypeDangerousTreasures = "Simple";
            if (_settings.OutputFormat.OutputTypeDiscordAuth == null)
                _settings.OutputFormat.OutputTypeDiscordAuth = "Simple";
            if (_settings.OutputFormat.OutputTypeKits == null)
                _settings.OutputFormat.OutputTypeKits = "Simple";
            if (_settings.OutputFormat.OutputTypePMs == null)
                _settings.OutputFormat.OutputTypePMs = "Simple";
            if (_settings.OutputFormat.OutputTypeRaidableBases == null)
                _settings.OutputFormat.OutputTypeRaidableBases = "Simple";
            if (_settings.OutputFormat.OutputTypeVanish == null)
                _settings.OutputFormat.OutputTypeVanish = "Simple";
            if (_settings.OutputFormat.OutputTypeAirEvent == null)
                _settings.OutputFormat.OutputTypeAirEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeArmoredTrainEvent == null)
                _settings.OutputFormat.OutputTypeArmoredTrainEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeCargoTrainEvent == null)
                _settings.OutputFormat.OutputTypeCargoTrainEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeConvoyEvent == null)
                _settings.OutputFormat.OutputTypeConvoyEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeHarborEvent == null)
                _settings.OutputFormat.OutputTypeHarborEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeJunkyardEvent == null)
                _settings.OutputFormat.OutputTypeJunkyardEvent = "Simple";
            if (_settings.OutputFormat.OutputTypePowerPlantEvent == null)
                _settings.OutputFormat.OutputTypePowerPlantEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeSatDishEvent == null)
                _settings.OutputFormat.OutputTypeSatDishEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeSputnikEvent == null)
                _settings.OutputFormat.OutputTypeSputnikEvent = "Simple";
            if (_settings.OutputFormat.OutputTypeWaterEvent == null)
                _settings.OutputFormat.OutputTypeWaterEvent = "Simple";

            foreach (var channel in _settings.Channels)
            {
                if (channel.CustomFilter == null)
                {
                    channel.CustomFilter = new List<string>();
                }
            }
            if (_settings.General.ReportCommand == null)
                _settings.General.ReportCommand = "report";