Example #1
0
        public async Task Update(CommandContext ctx, [RemainingText, Description("An invite link or an invite token")] string invite)
        {
            var(_, _, invites) = await ctx.Client.GetInvitesAsync(invite, tryMessageAsACode : true).ConfigureAwait(false);

            if (invites.Count == 0)
            {
                await ctx.ReactWithAsync(Config.Reactions.Failure, "Need to specify an invite link or token").ConfigureAwait(false);

                return;
            }

            var errors = 0;

            foreach (var i in invites)
            {
                if (!await InviteWhitelistProvider.IsWhitelistedAsync(i).ConfigureAwait(false))
                {
                    errors++;
                }
            }

            if (errors == 0)
            {
                await ctx.ReactWithAsync(Config.Reactions.Success, "Invite whitelist was successfully updated!").ConfigureAwait(false);
            }
            else
            {
                await ctx.ReactWithAsync(Config.Reactions.Failure, $"Failed to update {errors} invite{StringUtils.GetSuffix(errors)}").ConfigureAwait(false);
            }
            await List(ctx).ConfigureAwait(false);
        }
Example #2
0
        public async Task Remove(CommandContext ctx, [Description("Filter IDs to remove, separated with spaces")] params int[] ids)
        {
            var failedIds = new List <int>();

            foreach (var id in ids)
            {
                if (!await InviteWhitelistProvider.RemoveAsync(id).ConfigureAwait(false))
                {
                    failedIds.Add(id);
                }
            }
            if (failedIds.Count > 0)
            {
                await ctx.Channel.SendMessageAsync("Some IDs couldn't be removed: " + string.Join(", ", failedIds)).ConfigureAwait(false);
            }
            else
            {
                await ctx.ReactWithAsync(Config.Reactions.Success, $"Invite{StringUtils.GetSuffix(ids.Length)} successfully removed!").ConfigureAwait(false);
            }
            await List(ctx).ConfigureAwait(false);
        }
Example #3
0
        public async Task Add(CommandContext ctx, [Description("A Discord server IDs to whitelist")] params ulong[] guildIds)
        {
            var errors = 0;

            foreach (var guildId in guildIds)
            {
                if (!await InviteWhitelistProvider.AddAsync(guildId).ConfigureAwait(false))
                {
                    errors++;
                }
            }

            if (errors == 0)
            {
                await ctx.ReactWithAsync(Config.Reactions.Success, "Invite whitelist was successfully updated!").ConfigureAwait(false);
            }
            else
            {
                await ctx.ReactWithAsync(Config.Reactions.Failure, $"Failed to add {errors} invite{StringUtils.GetSuffix(errors)} to the whitelist").ConfigureAwait(false);
            }
        }
Example #4
0
        internal static async Task Main(string[] args)
        {
            Config.TelemetryClient?.TrackEvent("startup");

            Console.WriteLine("Confinement: " + SandboxDetector.Detect());
            if (args.Length > 0 && args[0] == "--dry-run")
            {
                Console.WriteLine("Database path: " + Path.GetDirectoryName(Path.GetFullPath(DbImporter.GetDbPath("fake.db", Environment.SpecialFolder.ApplicationData))));
                if (Assembly.GetEntryAssembly().GetCustomAttribute <UserSecretsIdAttribute>() != null)
                {
                    Console.WriteLine("Bot config path: " + Path.GetDirectoryName(Path.GetFullPath(Config.GoogleApiConfigPath)));
                }
                return;
            }

            if (Process.GetCurrentProcess().Id == 0)
            {
                Config.Log.Info("Well, this was unexpected");
            }
            var singleInstanceCheckThread = new Thread(() =>
            {
                using var instanceLock = new Mutex(false, @"Global\RPCS3 Compatibility Bot");
                if (instanceLock.WaitOne(1000))
                {
                    try
                    {
                        InstanceCheck.Release();
                        ShutdownCheck.Wait();
                    }
                    finally
                    {
                        instanceLock.ReleaseMutex();
                    }
                }
            });

            try
            {
                singleInstanceCheckThread.Start();
                if (!await InstanceCheck.WaitAsync(1000).ConfigureAwait(false))
                {
                    Config.Log.Fatal("Another instance is already running.");
                    return;
                }

                if (string.IsNullOrEmpty(Config.Token) || Config.Token.Length < 16)
                {
                    Config.Log.Fatal("No token was specified.");
                    return;
                }

                if (SandboxDetector.Detect() == SandboxType.Docker)
                {
                    Config.Log.Info("Checking for updates...");
                    try
                    {
                        var(updated, stdout) = await Sudo.Bot.UpdateAsync().ConfigureAwait(false);

                        if (!string.IsNullOrEmpty(stdout) && updated)
                        {
                            Config.Log.Debug(stdout);
                        }
                        if (updated)
                        {
                            Sudo.Bot.Restart(InvalidChannelId, "Restarted due to new bot updates not present in this Docker image");
                            return;
                        }
                    }
                    catch (Exception e)
                    {
                        Config.Log.Error(e, "Failed to check for updates");
                    }
                }

                using (var db = new BotDb())
                    if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
                    {
                        return;
                    }

                using (var db = new ThumbnailDb())
                    if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
                    {
                        return;
                    }

                await SqlConfiguration.RestoreAsync().ConfigureAwait(false);

                Config.Log.Debug("Restored configuration variables from persistent storage");

                await StatsStorage.RestoreAsync().ConfigureAwait(false);

                Config.Log.Debug("Restored stats from persistent storage");

                var backgroundTasks = Task.WhenAll(
                    AmdDriverVersionProvider.RefreshAsync(),
#if !DEBUG
                    new PsnScraper().RunAsync(Config.Cts.Token),
                    GameTdbScraper.RunAsync(Config.Cts.Token),
#endif
                    StatsStorage.BackgroundSaveAsync(),
                    CompatList.ImportCompatListAsync()
                    );

                try
                {
                    if (!Directory.Exists(Config.IrdCachePath))
                    {
                        Directory.CreateDirectory(Config.IrdCachePath);
                    }
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e, $"Failed to create new folder {Config.IrdCachePath}: {e.Message}");
                }

                var config = new DiscordConfiguration
                {
                    Token            = Config.Token,
                    TokenType        = TokenType.Bot,
                    MessageCacheSize = Config.MessageCacheSize,
                    LoggerFactory    = Config.LoggerFactory,
                };
                using var client = new DiscordClient(config);
                var commands = client.UseCommandsNext(new CommandsNextConfiguration
                {
                    StringPrefixes = new[] { Config.CommandPrefix, Config.AutoRemoveCommandPrefix },
                    Services       = new ServiceCollection().BuildServiceProvider(),
                });
                commands.RegisterConverter(new TextOnlyDiscordChannelConverter());
                commands.RegisterCommands <Misc>();
                commands.RegisterCommands <CompatList>();
                commands.RegisterCommands <Sudo>();
                commands.RegisterCommands <CommandsManagement>();
                commands.RegisterCommands <ContentFilters>();
                commands.RegisterCommands <Warnings>();
                commands.RegisterCommands <Explain>();
                commands.RegisterCommands <Psn>();
                commands.RegisterCommands <Invites>();
                commands.RegisterCommands <Moderation>();
                commands.RegisterCommands <Ird>();
                commands.RegisterCommands <BotMath>();
                commands.RegisterCommands <Pr>();
                commands.RegisterCommands <Events>();
                commands.RegisterCommands <E3>();
                commands.RegisterCommands <Cyberpunk2077>();
                commands.RegisterCommands <Rpcs3Ama>();
                commands.RegisterCommands <BotStats>();
                commands.RegisterCommands <Syscall>();
                commands.RegisterCommands <ForcedNicknames>();
                commands.RegisterCommands <Minesweeper>();

                if (!string.IsNullOrEmpty(Config.AzureComputerVisionKey))
                {
                    commands.RegisterCommands <Vision>();
                }

                commands.CommandErrored += UnknownCommandHandler.OnError;

                client.UseInteractivity(new InteractivityConfiguration());

                client.Ready += async(c, _) =>
                {
                    var admin = await c.GetUserAsync(Config.BotAdminId).ConfigureAwait(false);

                    Config.Log.Info("Bot is ready to serve!");
                    Config.Log.Info("");
                    Config.Log.Info($"Bot user id : {c.CurrentUser.Id} ({c.CurrentUser.Username})");
                    Config.Log.Info($"Bot admin id : {Config.BotAdminId} ({admin.Username ?? "???"}#{admin.Discriminator ?? "????"})");
                    Config.Log.Info("");
                };
                client.GuildAvailable += async(c, gaArgs) =>
                {
                    await BotStatusMonitor.RefreshAsync(c).ConfigureAwait(false);

                    Watchdog.DisconnectTimestamps.Clear();
                    Watchdog.TimeSinceLastIncomingMessage.Restart();
                    if (gaArgs.Guild.Id != Config.BotGuildId)
                    {
#if DEBUG
                        Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name})");
#else
                        Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name}), leaving...");
                        await gaArgs.Guild.LeaveAsync().ConfigureAwait(false);
#endif
                        return;
                    }

                    Config.Log.Info($"Server {gaArgs.Guild.Name} is available now");
                    Config.Log.Info($"Checking moderation backlogs in {gaArgs.Guild.Name}...");
                    try
                    {
                        await Task.WhenAll(
                            Starbucks.CheckBacklogAsync(c, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Starbucks backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default),
                            DiscordInviteFilter.CheckBacklogAsync(c, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Discord invites backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default)
                            ).ConfigureAwait(false);
                    }
                    catch (Exception e)
                    {
                        Config.Log.Warn(e, "Error running backlog tasks");
                    }
                    Config.Log.Info($"All moderation backlogs checked in {gaArgs.Guild.Name}.");
                };
                client.GuildAvailable   += (c, _) => UsernameValidationMonitor.MonitorAsync(c, true);
                client.GuildUnavailable += (_, guArgs) =>
                {
                    Config.Log.Warn($"{guArgs.Guild.Name} is unavailable");
                    return(Task.CompletedTask);
                };
#if !DEBUG
/*
 *              client.GuildDownloadCompleted += async gdcArgs =>
 *                                               {
 *                                                   foreach (var guild in gdcArgs.Guilds)
 *                                                       await ModProvider.SyncRolesAsync(guild.Value).ConfigureAwait(false);
 *                                               };
 */
#endif
                client.MessageReactionAdded += Starbucks.Handler;
                client.MessageReactionAdded += ContentFilterMonitor.OnReaction;

                client.MessageCreated += (_, __) => { Watchdog.TimeSinceLastIncomingMessage.Restart(); return(Task.CompletedTask); };
                client.MessageCreated += ContentFilterMonitor.OnMessageCreated; // should be first
                client.MessageCreated += GlobalMessageCache.OnMessageCreated;
                var mediaScreenshotMonitor = new MediaScreenshotMonitor(client);
                if (!string.IsNullOrEmpty(Config.AzureComputerVisionKey))
                {
                    client.MessageCreated += mediaScreenshotMonitor.OnMessageCreated;
                }
                client.MessageCreated += ProductCodeLookup.OnMessageCreated;
                client.MessageCreated += LogParsingHandler.OnMessageCreated;
                client.MessageCreated += LogAsTextMonitor.OnMessageCreated;
                client.MessageCreated += DiscordInviteFilter.OnMessageCreated;
                client.MessageCreated += PostLogHelpHandler.OnMessageCreated;
                client.MessageCreated += BotReactionsHandler.OnMessageCreated;
                client.MessageCreated += GithubLinksHandler.OnMessageCreated;
                client.MessageCreated += NewBuildsMonitor.OnMessageCreated;
                client.MessageCreated += TableFlipMonitor.OnMessageCreated;
                client.MessageCreated += IsTheGamePlayableHandler.OnMessageCreated;
                client.MessageCreated += EmpathySimulationHandler.OnMessageCreated;

                client.MessageUpdated += GlobalMessageCache.OnMessageUpdated;
                client.MessageUpdated += ContentFilterMonitor.OnMessageUpdated;
                client.MessageUpdated += DiscordInviteFilter.OnMessageUpdated;
                client.MessageUpdated += EmpathySimulationHandler.OnMessageUpdated;

                client.MessageDeleted += GlobalMessageCache.OnMessageDeleted;
                if (Config.DeletedMessagesLogChannelId > 0)
                {
                    client.MessageDeleted += DeletedMessagesMonitor.OnMessageDeleted;
                }
                client.MessageDeleted += ThumbnailCacheMonitor.OnMessageDeleted;
                client.MessageDeleted += EmpathySimulationHandler.OnMessageDeleted;

                client.MessagesBulkDeleted += GlobalMessageCache.OnMessagesBulkDeleted;

                client.UserUpdated += UsernameSpoofMonitor.OnUserUpdated;
                client.UserUpdated += UsernameZalgoMonitor.OnUserUpdated;

                client.GuildMemberAdded += Greeter.OnMemberAdded;
                client.GuildMemberAdded += UsernameSpoofMonitor.OnMemberAdded;
                client.GuildMemberAdded += UsernameZalgoMonitor.OnMemberAdded;
                client.GuildMemberAdded += UsernameValidationMonitor.OnMemberAdded;

                client.GuildMemberUpdated += UsernameSpoofMonitor.OnMemberUpdated;
                client.GuildMemberUpdated += UsernameZalgoMonitor.OnMemberUpdated;
                client.GuildMemberUpdated += UsernameValidationMonitor.OnMemberUpdated;

                Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);

                try
                {
                    await client.ConnectAsync().ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Config.Log.Error(e, "Failed to connect to Discord: " + e.Message);
                    throw;
                }

                ulong? channelId  = null;
                string restartMsg = null;
                using (var db = new BotDb())
                {
                    var chState = db.BotState.FirstOrDefault(k => k.Key == "bot-restart-channel");
                    if (chState != null)
                    {
                        if (ulong.TryParse(chState.Value, out var ch))
                        {
                            channelId = ch;
                        }
                        db.BotState.Remove(chState);
                    }
                    var msgState = db.BotState.FirstOrDefault(i => i.Key == "bot-restart-msg");
                    if (msgState != null)
                    {
                        restartMsg = msgState.Value;
                        db.BotState.Remove(msgState);
                    }
                    db.SaveChanges();
                }
                if (string.IsNullOrEmpty(restartMsg))
                {
                    restartMsg = null;
                }

                if (channelId.HasValue)
                {
                    Config.Log.Info($"Found channelId {channelId}");
                    DiscordChannel channel;
                    if (channelId == InvalidChannelId)
                    {
                        channel = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);

                        await channel.SendMessageAsync(restartMsg ?? "Bot has suffered some catastrophic failure and was restarted").ConfigureAwait(false);
                    }
                    else
                    {
                        channel = await client.GetChannelAsync(channelId.Value).ConfigureAwait(false);

                        await channel.SendMessageAsync("Bot is up and running").ConfigureAwait(false);
                    }
                }
                else
                {
                    Config.Log.Debug($"Args count: {args.Length}");
                    var pArgs = args.Select(a => a == Config.Token ? "<Token>" : $"[{a}]");
                    Config.Log.Debug("Args: " + string.Join(" ", pArgs));
                }

                Config.Log.Debug("Running RPCS3 update check thread");
                backgroundTasks = Task.WhenAll(
                    backgroundTasks,
                    NewBuildsMonitor.MonitorAsync(client),
                    Watchdog.Watch(client),
                    InviteWhitelistProvider.CleanupAsync(client),
                    UsernameValidationMonitor.MonitorAsync(client),
                    Psn.Check.MonitorFwUpdates(client, Config.Cts.Token),
                    Watchdog.SendMetrics(client),
                    Watchdog.CheckGCStats(),
                    mediaScreenshotMonitor.ProcessWorkQueue()
                    );

                while (!Config.Cts.IsCancellationRequested)
                {
                    if (client.Ping > 1000)
                    {
                        Config.Log.Warn($"High ping detected: {client.Ping}");
                    }
                    await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => { /* in case it was cancelled */ }, TaskScheduler.Default).ConfigureAwait(false);
                }
                await backgroundTasks.ConfigureAwait(false);
            }
            catch (Exception e)
            {
                if (!Config.inMemorySettings.ContainsKey("shutdown"))
                {
                    Config.Log.Fatal(e, "Experienced catastrophic failure, attempting to restart...");
                }
            }
            finally
            {
                Config.TelemetryClient?.Flush();
                ShutdownCheck.Release();
                if (singleInstanceCheckThread.IsAlive)
                {
                    singleInstanceCheckThread.Join(100);
                }
            }
            if (!Config.inMemorySettings.ContainsKey("shutdown"))
            {
                Sudo.Bot.Restart(InvalidChannelId, null);
            }
        }
        public static async Task <bool> CheckMessageForInvitesAsync(DiscordClient client, DiscordMessage message)
        {
            if (message.Channel.IsPrivate)
            {
                return(true);
            }

            if (message.Author.IsBotSafeCheck())
            {
                return(true);
            }

#if !DEBUG
            if (message.Author.IsWhitelisted(client, message.Channel.Guild))
            {
                return(true);
            }
#endif

            if (message.Reactions.Any(r => r.Emoji == Config.Reactions.Moderated && r.IsMe))
            {
                return(true);
            }

            var(hasInvalidResults, attemptedWorkaround, invites) = await client.GetInvitesAsync(message.Content, message.Author).ConfigureAwait(false);

            if (!hasInvalidResults && invites.Count == 0)
            {
                return(true);
            }

            if (hasInvalidResults)
            {
                try
                {
                    DeletedMessagesMonitor.RemovedByBotCache.Set(message.Id, true, DeletedMessagesMonitor.CacheRetainTime);
                    await message.DeleteAsync("Not a white-listed discord invite link").ConfigureAwait(false);

                    await client.ReportAsync("🛃 An unapproved discord invite", message, "In invalid or expired invite", null, ReportSeverity.Low).ConfigureAwait(false);

                    await message.Channel.SendMessageAsync($"{message.Author.Mention} please refrain from posting invites that were not approved by a moderator, especially expired or invalid.").ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e);
                    await client.ReportAsync("🛃 An unapproved discord invite", message, "In invalid or expired invite", null, ReportSeverity.Medium).ConfigureAwait(false);

                    await message.ReactWithAsync(Config.Reactions.Moderated,
                                                 $"{message.Author.Mention} please remove this expired or invalid invite, and refrain from posting it again until you have received an approval from a moderator.",
                                                 true
                                                 ).ConfigureAwait(false);
                }
                return(false);
            }

            foreach (var invite in invites)
            {
                if (!await InviteWhitelistProvider.IsWhitelistedAsync(invite).ConfigureAwait(false))
                {
                    if (!InviteCodeCache.TryGetValue(message.Author.Id, out HashSet <string> recentInvites))
                    {
                        recentInvites = new HashSet <string>();
                    }
                    var circumventionAttempt = !recentInvites.Add(invite.Code) && attemptedWorkaround; //do not flip, must add to cache always
                    InviteCodeCache.Set(message.Author.Id, recentInvites, CacheDuration);
                    var removed = false;
                    try
                    {
                        DeletedMessagesMonitor.RemovedByBotCache.Set(message.Id, true, DeletedMessagesMonitor.CacheRetainTime);
                        await message.DeleteAsync("Not a white-listed discord invite").ConfigureAwait(false);

                        removed = true;
                    }
                    catch (Exception e)
                    {
                        Config.Log.Warn(e);
                    }

                    var    codeResolveMsg = $"Invite {invite.Code} was resolved to the {invite.Guild?.Name} server";
                    var    reportMsg      = codeResolveMsg;
                    string userMsg;
                    if (circumventionAttempt)
                    {
                        reportMsg += "\nAlso tried to workaround filter despite being asked not to do so.";
                        userMsg    = $"{message.Author.Mention} you have been asked nicely to not post invites to this unapproved discord server before.";
                    }
                    else
                    {
                        userMsg = $"{message.Author.Mention} invites to other servers must be whitelisted first.\n";
                        if (removed)
                        {
                            userMsg += "Please refrain from posting it again until you have received an approval from a moderator.";
                        }
                        else
                        {
                            userMsg += "Please remove it and refrain from posting it again until you have received an approval from a moderator.";
                        }
                    }
                    await client.ReportAsync("🛃 An unapproved discord invite", message, reportMsg, null, ReportSeverity.Low).ConfigureAwait(false);

                    await message.Channel.SendMessageAsync(userMsg).ConfigureAwait(false);

                    if (circumventionAttempt)
                    {
                        await Warnings.AddAsync(client, message, message.Author.Id, message.Author.Username, client.CurrentUser, "Attempted to circumvent discord invite filter", codeResolveMsg);
                    }
                    return(false);
                }
            }
            return(true);
        }
Example #6
0
        internal static async Task Main(string[] args)
        {
            var singleInstanceCheckThread = new Thread(() =>
            {
                using (var instanceLock = new Mutex(false, @"Global\RPCS3 Compatibility Bot"))
                {
                    if (instanceLock.WaitOne(1000))
                    {
                        try
                        {
                            InstanceCheck.Release();
                            ShutdownCheck.Wait();
                        }
                        finally
                        {
                            instanceLock.ReleaseMutex();
                        }
                    }
                }
            });

            try
            {
                singleInstanceCheckThread.Start();
                if (!await InstanceCheck.WaitAsync(1000).ConfigureAwait(false))
                {
                    Config.Log.Fatal("Another instance is already running.");
                    return;
                }

                if (string.IsNullOrEmpty(Config.Token))
                {
                    Config.Log.Fatal("No token was specified.");
                    return;
                }

                using (var db = new BotDb())
                    if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
                    {
                        return;
                    }

                using (var db = new ThumbnailDb())
                    if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
                    {
                        return;
                    }

                await StatsStorage.RestoreAsync().ConfigureAwait(false);

                Config.Log.Debug("Restored stats from persistent storage");

                var backgroundTasks = Task.WhenAll(
                    AmdDriverVersionProvider.RefreshAsync(),
                    new PsnScraper().RunAsync(Config.Cts.Token),
                    GameTdbScraper.RunAsync(Config.Cts.Token),
                    new AppveyorClient.Client().GetBuildAsync(Guid.NewGuid().ToString(), Config.Cts.Token),
                    StatsStorage.BackgroundSaveAsync()
                    );

                try
                {
                    if (!Directory.Exists(Config.IrdCachePath))
                    {
                        Directory.CreateDirectory(Config.IrdCachePath);
                    }
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e, $"Failed to create new folder {Config.IrdCachePath}: {e.Message}");
                }

                var config = new DiscordConfiguration
                {
                    Token     = Config.Token,
                    TokenType = TokenType.Bot,
                };
                using (var client = new DiscordClient(config))
                {
                    var commands = client.UseCommandsNext(new CommandsNextConfiguration
                    {
                        StringPrefixes = new[] { Config.CommandPrefix, Config.AutoRemoveCommandPrefix },
                        Services       = new ServiceCollection().BuildServiceProvider(),
                    });
                    commands.RegisterConverter(new TextOnlyDiscordChannelConverter());
                    commands.RegisterCommands <Misc>();
                    commands.RegisterCommands <CompatList>();
                    commands.RegisterCommands <Sudo>();
                    commands.RegisterCommands <CommandsManagement>();
                    commands.RegisterCommands <ContentFilters>();
                    commands.RegisterCommands <Warnings>();
                    commands.RegisterCommands <Explain>();
                    commands.RegisterCommands <Psn>();
                    commands.RegisterCommands <Invites>();
                    commands.RegisterCommands <Moderation>();
                    commands.RegisterCommands <Ird>();
                    commands.RegisterCommands <BotMath>();
                    commands.RegisterCommands <Pr>();
                    commands.RegisterCommands <Events>();
                    commands.RegisterCommands <E3>();
                    commands.RegisterCommands <Cyberpunk2077>();
                    commands.RegisterCommands <Rpcs3Ama>();
                    commands.RegisterCommands <BotStats>();
                    commands.RegisterCommands <Syscall>();

                    commands.CommandErrored += UnknownCommandHandler.OnError;

                    var interactivityConfig = new InteractivityConfiguration {
                    };
                    client.UseInteractivity(interactivityConfig);

                    client.Ready += async r =>
                    {
                        Config.Log.Info("Bot is ready to serve!");
                        Config.Log.Info("");
                        Config.Log.Info($"Bot user id : {r.Client.CurrentUser.Id} ({r.Client.CurrentUser.Username})");
                        Config.Log.Info($"Bot admin id : {Config.BotAdminId} ({(await r.Client.GetUserAsync(Config.BotAdminId)).Username})");
                        Config.Log.Info("");
                    };
                    client.GuildAvailable += async gaArgs =>
                    {
                        await BotStatusMonitor.RefreshAsync(gaArgs.Client).ConfigureAwait(false);

                        Watchdog.DisconnectTimestamps.Clear();
                        if (gaArgs.Guild.Id != Config.BotGuildId)
                        {
#if DEBUG
                            Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name})");
#else
                            Config.Log.Warn($"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name}), leaving...");
                            await gaArgs.Guild.LeaveAsync().ConfigureAwait(false);
#endif
                            return;
                        }

                        Config.Log.Info($"Server {gaArgs.Guild.Name} is available now");
                        Config.Log.Info($"Checking moderation backlogs in {gaArgs.Guild.Name}...");
                        try
                        {
                            await Task.WhenAll(
                                Starbucks.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Starbucks backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default),
                                DiscordInviteFilter.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Discord invites backlog checked in {gaArgs.Guild.Name}."), TaskScheduler.Default)
                                ).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            Config.Log.Warn(e, "Error running backlog tasks");
                        }
                        Config.Log.Info($"All moderation backlogs checked in {gaArgs.Guild.Name}.");
                    };
                    client.GuildUnavailable += guArgs =>
                    {
                        Config.Log.Warn($"{guArgs.Guild.Name} is unavailable");
                        return(Task.CompletedTask);
                    };

                    client.MessageReactionAdded += Starbucks.Handler;
                    client.MessageReactionAdded += AntipiracyMonitor.OnReaction;

                    client.MessageCreated += AntipiracyMonitor.OnMessageCreated; // should be first
                    client.MessageCreated += ProductCodeLookup.OnMessageCreated;
                    client.MessageCreated += LogParsingHandler.OnMessageCreated;
                    client.MessageCreated += LogAsTextMonitor.OnMessageCreated;
                    client.MessageCreated += DiscordInviteFilter.OnMessageCreated;
                    client.MessageCreated += PostLogHelpHandler.OnMessageCreated;
                    client.MessageCreated += BotReactionsHandler.OnMessageCreated;
                    client.MessageCreated += AppveyorLinksHandler.OnMessageCreated;
                    client.MessageCreated += GithubLinksHandler.OnMessageCreated;
                    client.MessageCreated += NewBuildsMonitor.OnMessageCreated;
                    client.MessageCreated += TableFlipMonitor.OnMessageCreated;
                    client.MessageCreated += IsTheGamePlayableHandler.OnMessageCreated;
                    client.MessageCreated += EmpathySimulationHandler.OnMessageCreated;

                    client.MessageUpdated += AntipiracyMonitor.OnMessageUpdated;
                    client.MessageUpdated += DiscordInviteFilter.OnMessageUpdated;
                    client.MessageUpdated += EmpathySimulationHandler.OnMessageUpdated;

                    client.MessageDeleted += ThumbnailCacheMonitor.OnMessageDeleted;
                    client.MessageDeleted += EmpathySimulationHandler.OnMessageDeleted;

                    client.UserUpdated += UsernameSpoofMonitor.OnUserUpdated;
                    client.UserUpdated += UsernameZalgoMonitor.OnUserUpdated;

                    client.GuildMemberAdded += Greeter.OnMemberAdded;
                    client.GuildMemberAdded += UsernameSpoofMonitor.OnMemberAdded;
                    client.GuildMemberAdded += UsernameZalgoMonitor.OnMemberAdded;

                    client.GuildMemberUpdated += UsernameSpoofMonitor.OnMemberUpdated;
                    client.GuildMemberUpdated += UsernameZalgoMonitor.OnMemberUpdated;

                    client.DebugLogger.LogMessageReceived += (sender, eventArgs) =>
                    {
                        Action <Exception, string> logLevel = Config.Log.Info;
                        if (eventArgs.Level == LogLevel.Debug)
                        {
                            logLevel = Config.Log.Debug;
                        }
                        else if (eventArgs.Level == LogLevel.Info)
                        {
                            //logLevel = Config.Log.Info;
                            if (eventArgs.Message?.Contains("Session resumed") ?? false)
                            {
                                Watchdog.DisconnectTimestamps.Clear();
                            }
                        }
                        else if (eventArgs.Level == LogLevel.Warning)
                        {
                            logLevel = Config.Log.Warn;
                            if (eventArgs.Message?.Contains("Dispatch:PRESENCES_REPLACE") ?? false)
                            {
                                BotStatusMonitor.RefreshAsync(client).ConfigureAwait(false).GetAwaiter().GetResult();
                            }
                        }
                        else if (eventArgs.Level == LogLevel.Error)
                        {
                            logLevel = Config.Log.Error;
                        }
                        else if (eventArgs.Level == LogLevel.Critical)
                        {
                            logLevel = Config.Log.Fatal;
                            if ((eventArgs.Message?.Contains("Socket connection terminated") ?? false) ||
                                (eventArgs.Message?.Contains("heartbeats were skipped. Issuing reconnect.") ?? false))
                            {
                                Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);
                            }
                        }
                        logLevel(eventArgs.Exception, eventArgs.Message);
                    };
                    Watchdog.DisconnectTimestamps.Enqueue(DateTime.UtcNow);

                    await client.ConnectAsync().ConfigureAwait(false);

                    if (args.Length > 1 && ulong.TryParse(args[1], out var channelId))
                    {
                        Config.Log.Info($"Found channelId: {args[1]}");
                        DiscordChannel channel;
                        if (channelId == InvalidChannelId)
                        {
                            channel = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);

                            await channel.SendMessageAsync("Bot has suffered some catastrophic failure and was restarted").ConfigureAwait(false);
                        }
                        else
                        {
                            channel = await client.GetChannelAsync(channelId).ConfigureAwait(false);

                            await channel.SendMessageAsync("Bot is up and running").ConfigureAwait(false);
                        }
                    }

                    Config.Log.Debug("Running RPCS3 update check thread");
                    backgroundTasks = Task.WhenAll(
                        backgroundTasks,
                        NewBuildsMonitor.MonitorAsync(client),
                        Watchdog.Watch(client),
                        InviteWhitelistProvider.CleanupAsync(client)
                        );

                    while (!Config.Cts.IsCancellationRequested)
                    {
                        if (client.Ping > 1000)
                        {
                            Config.Log.Warn($"High ping detected: {client.Ping}");
                        }
                        await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => { /* in case it was cancelled */ }, TaskScheduler.Default).ConfigureAwait(false);
                    }
                }
                await backgroundTasks.ConfigureAwait(false);
            }
            catch (Exception e)
            {
                if (!Config.inMemorySettings.ContainsKey("shutdown"))
                {
                    Config.Log.Fatal(e, "Experienced catastrophic failure, attempting to restart...");
                }
            }
            finally
            {
                ShutdownCheck.Release();
                if (singleInstanceCheckThread.IsAlive)
                {
                    singleInstanceCheckThread.Join(100);
                }
            }
            if (!Config.inMemorySettings.ContainsKey("shutdown"))
            {
                Sudo.Bot.Restart(InvalidChannelId);
            }
        }
        private static async Task CheckMessageForInvitesAsync(DiscordClient client, DiscordMessage message)
        {
            if (DefaultHandlerFilter.IsFluff(message))
            {
                return;
            }

            if (message.Author.IsWhitelisted(client, message.Channel.Guild))
            {
                return;
            }

            if (message.Reactions.Any(r => r.Emoji == Config.Reactions.Moderated && r.IsMe))
            {
                return;
            }

            var(hasInvalidResults, invites) = await client.GetInvitesAsync(message.Content).ConfigureAwait(false);

            if (!hasInvalidResults && invites.Count == 0)
            {
                return;
            }

            if (hasInvalidResults)
            {
                try
                {
                    await message.DeleteAsync("Not a white-listed discord invite link").ConfigureAwait(false);

                    await client.ReportAsync("An unapproved discord invite", message, "In invalid or expired invite", null, ReportSeverity.Low).ConfigureAwait(false);

                    await message.Channel.SendMessageAsync($"{message.Author.Mention} please refrain from posting invites that were not approved by a moderator, especially expired or invalid.").ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e);
                    await client.ReportAsync("An unapproved discord invite", message, "In invalid or expired invite", null, ReportSeverity.Medium).ConfigureAwait(false);

                    await message.ReactWithAsync(
                        client,
                        Config.Reactions.Moderated,
                        $"{message.Author.Mention} please remove this expired or invalid invite, and refrain from posting it again until you have recieved an approval from a moderator.",
                        true
                        ).ConfigureAwait(false);
                }
                return;
            }

            foreach (var invite in invites)
            {
                if (!await InviteWhitelistProvider.IsWhitelistedAsync(invite).ConfigureAwait(false))
                {
                    try
                    {
                        await message.DeleteAsync("Not a white-listed discord invite link").ConfigureAwait(false);

                        await client.ReportAsync("An unapproved discord invite", message, $"Invite {invite.Code} was resolved to the {invite.Guild.Name} server", null, ReportSeverity.Low).ConfigureAwait(false);

                        await message.Channel.SendMessageAsync($"{message.Author.Mention} invites to the {invite.Guild.Name.Sanitize()} server are not whitelisted.\n" +
                                                               $"Please refrain from posting it again until you have recieved an approval from a moderator.").ConfigureAwait(false);
                    }
                    catch (Exception e)
                    {
                        Config.Log.Warn(e);
                        await client.ReportAsync("An unapproved discord invite", message, $"Invite {invite.Code} was resolved to the {invite.Guild.Name} server", null, ReportSeverity.Medium).ConfigureAwait(false);

                        await message.ReactWithAsync(
                            client,
                            Config.Reactions.Moderated,
                            $"{message.Author.Mention} invites to the {invite.Guild.Name.Sanitize()} server are not whitelisted.\n" +
                            $"Please remove it and refrain from posting it again until you have recieved an approval from a moderator.",
                            true
                            ).ConfigureAwait(false);
                    }
                    return;
                }
            }
        }