Ejemplo n.º 1
0
        public async Task ShowExplanation(CommandContext ctx, [RemainingText, Description("Term to explain")] string term)
        {
            var sourceTerm = term;

            if (string.IsNullOrEmpty(term))
            {
                var lastBotMessages = await ctx.Channel.GetMessagesBeforeCachedAsync(ctx.Message.Id, 10).ConfigureAwait(false);

                var showList = true;
                foreach (var pastMsg in lastBotMessages)
                {
                    if (pastMsg.Embeds.FirstOrDefault() is DiscordEmbed pastEmbed &&
                        pastEmbed.Title == TermListTitle ||
                        BotReactionsHandler.NeedToSilence(pastMsg).needToChill)
                    {
                        showList = false;
                        break;
                    }
                }
                if (showList)
                {
                    await List(ctx).ConfigureAwait(false);
                }
                var botMsg = await ctx.RespondAsync("Please tell what term to explain:").ConfigureAwait(false);

                var interact   = ctx.Client.GetInteractivity();
                var newMessage = await interact.WaitForMessageAsync(m => m.Author == ctx.User && m.Channel == ctx.Channel && !string.IsNullOrEmpty(m.Content)).ConfigureAwait(false);

                await botMsg.DeleteAsync().ConfigureAwait(false);

                if (string.IsNullOrEmpty(newMessage.Result?.Content) || newMessage.Result.Content.StartsWith(Config.CommandPrefix))
                {
                    await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);

                    return;
                }

                sourceTerm = term = newMessage.Result.Content;
            }

            if (!await DiscordInviteFilter.CheckMessageForInvitesAsync(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            if (!await ContentFilter.IsClean(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            term = term.ToLowerInvariant();
            var result = await LookupTerm(term).ConfigureAwait(false);

            if (result.explanation == null || !string.IsNullOrEmpty(result.fuzzyMatch))
            {
                term = term.StripQuotes();
                var idx = term.LastIndexOf(" to ");
                if (idx > 0)
                {
                    var  potentialUserId = term[(idx + 4)..].Trim();
Ejemplo n.º 2
0
        public async Task Compat(CommandContext ctx, [RemainingText, Description("Game title to look up")] string title)
        {
            title = title?.TrimEager().Truncate(40);
            if (string.IsNullOrEmpty(title))
            {
                await ctx.ReactWithAsync(Config.Reactions.Failure, "You should specify what you're looking for").ConfigureAwait(false);

                return;
            }

            if (!await DiscordInviteFilter.CheckMessageForInvitesAsync(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            if (!await AntipiracyMonitor.IsClean(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            try
            {
                var requestBuilder = RequestBuilder.Start().SetSearch(title);
                await DoRequestAndRespond(ctx, requestBuilder).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                Config.Log.Error(e, "Failed to get compat list info");
            }
        }
Ejemplo n.º 3
0
        public async Task Compat(CommandContext ctx, [RemainingText, Description("Game title to look up")] string title)
        {
            title = title?.TrimEager().Truncate(40);
            if (string.IsNullOrEmpty(title))
            {
                var prompt = await ctx.RespondAsync($"{ctx.Message.Author.Mention} what game do you want to check?").ConfigureAwait(false);

                var interact = ctx.Client.GetInteractivity();
                var response = await interact.WaitForMessageAsync(m => m.Author == ctx.Message.Author && m.Channel == ctx.Channel).ConfigureAwait(false);

                if (string.IsNullOrEmpty(response.Result?.Content) || response.Result.Content.StartsWith(Config.CommandPrefix))
                {
                    await prompt.ModifyAsync("You should specify what you're looking for").ConfigureAwait(false);

                    return;
                }

                DeletedMessagesMonitor.RemovedByBotCache.Set(prompt.Id, true, DeletedMessagesMonitor.CacheRetainTime);
                await prompt.DeleteAsync().ConfigureAwait(false);

                title = response.Result.Content.TrimEager().Truncate(40);
            }

            if (!await DiscordInviteFilter.CheckMessageForInvitesAsync(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            if (!await ContentFilter.IsClean(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            var productCodes = ProductCodeLookup.GetProductIds(ctx.Message.Content);

            if (productCodes.Any())
            {
                await ProductCodeLookup.LookupAndPostProductCodeEmbedAsync(ctx.Client, ctx.Message, productCodes).ConfigureAwait(false);

                return;
            }

            try
            {
                var requestBuilder = RequestBuilder.Start().SetSearch(title);
                await DoRequestAndRespond(ctx, requestBuilder).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                Config.Log.Error(e, "Failed to get compat list info");
            }
        }
Ejemplo n.º 4
0
        public async Task Dump(CommandContext ctx, [RemainingText, Description("Term to dump **or** a link to a message containing the explanation")] string termOrLink = null)
        {
            if (string.IsNullOrEmpty(termOrLink))
            {
                var term = ctx.Message.Content.Split(' ', 2).Last();
                await ShowExplanation(ctx, term).ConfigureAwait(false);

                return;
            }

            if (!await DiscordInviteFilter.CheckMessageForInvitesAsync(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            termOrLink = termOrLink.ToLowerInvariant().StripQuotes();
            var isLink = CommandContextExtensions.MessageLinkRegex.IsMatch(termOrLink);

            if (isLink)
            {
                await DumpLink(ctx, termOrLink).ConfigureAwait(false);

                return;
            }

            using (var db = new BotDb())
            {
                var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == termOrLink).ConfigureAwait(false);

                if (item == null)
                {
                    var term = ctx.Message.Content.Split(' ', 2).Last();
                    await ShowExplanation(ctx, term).ConfigureAwait(false);
                }
                else
                {
                    if (!string.IsNullOrEmpty(item.Text))
                    {
                        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(item.Text)))
                            await ctx.Channel.SendFileAsync($"{termOrLink}.txt", stream).ConfigureAwait(false);
                    }
                    if (!string.IsNullOrEmpty(item.AttachmentFilename))
                    {
                        using (var stream = new MemoryStream(item.Attachment))
                            await ctx.Channel.SendFileAsync(item.AttachmentFilename, stream).ConfigureAwait(false);
                    }
                }
            }
        }
Ejemplo n.º 5
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);
            }
        }
Ejemplo n.º 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);
            }
        }
Ejemplo n.º 7
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();
                        }
                    }
                }
            });
            var rpcs3UpdateCheckThread = new Thread(client =>
            {
                try
                {
                    while (!Config.Cts.IsCancellationRequested)
                    {
                        try
                        {
                            CompatList.UpdatesCheck.CheckForRpcs3Updates((DiscordClient)client, null).ConfigureAwait(false).GetAwaiter().GetResult();
                        }
                        catch { }
                        Task.Delay(TimeSpan.FromHours(1), Config.Cts.Token).ConfigureAwait(false).GetAwaiter().GetResult();
                    }
                }
                catch (TaskCanceledException) { }
            })
            {
                IsBackground = true
            };

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

                if (string.IsNullOrEmpty(Config.Token))
                {
                    Config.Log.Fatal("No token was specified.");
                    return;
                }
                var amdDriverRefreshTask = AmdDriverVersionProvider.RefreshAsync();

                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;
                    }

                var psnScrappingTask    = new PsnScraper().RunAsync(Config.Cts.Token);
                var gameTdbScrapingTask = GameTdbScraper.RunAsync(Config.Cts.Token);
                await amdDriverRefreshTask.ConfigureAwait(false);

                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,
                    //UseInternalLogHandler = true,
                    //LogLevel = LogLevel.Debug,
                };
                using (var client = new DiscordClient(config))
                {
                    var commands = client.UseCommandsNext(new CommandsNextConfiguration
                    {
                        StringPrefixes = new[] { Config.CommandPrefix },
                        Services       = new ServiceCollection().BuildServiceProvider(),
                    });
                    commands.RegisterConverter(new CustomDiscordChannelConverter());
                    commands.RegisterCommands <Misc>();
                    commands.RegisterCommands <CompatList>();
                    commands.RegisterCommands <Sudo>();
                    commands.RegisterCommands <Antipiracy>();
                    commands.RegisterCommands <Warnings>();
                    commands.RegisterCommands <Explain>();
                    commands.RegisterCommands <Psn>();
                    commands.RegisterCommands <Invites>();
                    commands.RegisterCommands <Moderation>();
                    commands.RegisterCommands <Ird>();

                    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 =>
                    {
                        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}.")),
                                DiscordInviteFilter.CheckBacklogAsync(gaArgs.Client, gaArgs.Guild).ContinueWith(_ => Config.Log.Info($"Discord invites backlog checked in {gaArgs.Guild.Name}."))
                                ).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.MessageCreated += AntipiracyMonitor.OnMessageCreated; // should be first
                    client.MessageCreated += ProductCodeLookup.OnMessageCreated;
                    client.MessageCreated += LogParsingHandler.OnMessageCreated;
                    client.MessageCreated += LogAsTextMonitor.OnMessageCreated;
                    client.MessageCreated += DiscordInviteFilter.OnMessageCreated;
                    client.MessageCreated += BotShutupHandler.OnMessageCreated;
                    client.MessageCreated += NewBuildsMonitor.OnMessageCreated;
                    client.MessageCreated += TableFlipMonitor.OnMessageCreated;

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

                    client.MessageDeleted += ThumbnailCacheMonitor.OnMessageDeleted;

                    client.UserUpdated        += UsernameSpoofMonitor.OnUserUpdated;
                    client.GuildMemberAdded   += UsernameSpoofMonitor.OnMemberAdded;
                    client.GuildMemberUpdated += UsernameSpoofMonitor.OnMemberUpdated;

                    client.DebugLogger.LogMessageReceived += (sender, eventArgs) =>
                    {
                        Action <string> logLevel = Config.Log.Info;
                        if (eventArgs.Level == LogLevel.Debug)
                        {
                            logLevel = Config.Log.Debug;
                        }
                        //else if (eventArgs.Level == LogLevel.Info)
                        //    logLevel = botLog.Info;
                        else if (eventArgs.Level == LogLevel.Warning)
                        {
                            logLevel = Config.Log.Warn;
                        }
                        else if (eventArgs.Level == LogLevel.Error)
                        {
                            logLevel = Config.Log.Error;
                        }
                        else if (eventArgs.Level == LogLevel.Critical)
                        {
                            logLevel = Config.Log.Fatal;
                        }
                        logLevel(eventArgs.Message);
                    };

                    try
                    {
                        await client.ConnectAsync();
                    }
                    catch (Exception e)
                    {
                        Config.Log.Fatal(e, "Terminating");
                        return;
                    }

                    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 RPC3 update check thread");
                    rpcs3UpdateCheckThread.Start(client);

                    while (!Config.Cts.IsCancellationRequested)
                    {
                        if (client.Ping > 1000)
                        {
                            await client.ReconnectAsync();
                        }
                        await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => { /* in case it was cancelled */ }).ConfigureAwait(false);
                    }
                }
                await Task.WhenAll(
                    psnScrappingTask,
                    gameTdbScrapingTask
                    ).ConfigureAwait(false);
            }
            catch (TaskCanceledException)
            {
            }
            catch (Exception e)
            {
                Config.Log.Fatal(e, $"Experienced catastrofic failure, attempting to restart...");
                Sudo.Bot.Restart(InvalidChannelId);
            }
            finally
            {
                ShutdownCheck.Release();
                if (singleInstanceCheckThread.IsAlive)
                {
                    singleInstanceCheckThread.Join(100);
                }
                if (rpcs3UpdateCheckThread.IsAlive)
                {
                    rpcs3UpdateCheckThread.Join(100);
                }
                Config.Log.Info("Exiting");
            }
        }
Ejemplo n.º 8
0
        public async Task ShowExplanation(CommandContext ctx, [RemainingText, Description("Term to explain")] string term)
        {
            var sourceTerm = term;

            if (string.IsNullOrEmpty(term))
            {
                var lastBotMessages = await ctx.Channel.GetMessagesBeforeAsync(ctx.Message.Id, 10).ConfigureAwait(false);

                var showList = true;
                foreach (var pastMsg in lastBotMessages)
                {
                    if (pastMsg.Embeds.FirstOrDefault() is DiscordEmbed pastEmbed &&
                        pastEmbed.Title == TermListTitle ||
                        BotReactionsHandler.NeedToSilence(pastMsg).needToChill)
                    {
                        showList = false;
                        break;
                    }
                }
                if (showList)
                {
                    await List(ctx).ConfigureAwait(false);
                }
                var botMsg = await ctx.RespondAsync("Please tell what term to explain:").ConfigureAwait(false);

                var interact   = ctx.Client.GetInteractivity();
                var newMessage = await interact.WaitForMessageAsync(m => m.Author == ctx.User && m.Channel == ctx.Channel && !string.IsNullOrEmpty(m.Content)).ConfigureAwait(false);

                await botMsg.DeleteAsync().ConfigureAwait(false);

                if (string.IsNullOrEmpty(newMessage.Result?.Content) || newMessage.Result.Content.StartsWith(Config.CommandPrefix))
                {
                    await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);

                    return;
                }

                sourceTerm = term = newMessage.Result.Content;
            }

            if (!await DiscordInviteFilter.CheckMessageForInvitesAsync(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            if (!await ContentFilter.IsClean(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            term = term.ToLowerInvariant();
            var result = await LookupTerm(term).ConfigureAwait(false);

            if (result.explanation == null || !string.IsNullOrEmpty(result.fuzzyMatch))
            {
                term = term.StripQuotes();
                var idx = term.LastIndexOf(" to ");
                if (idx > 0)
                {
                    var  potentialUserId = term.Substring(idx + 4).Trim();
                    bool hasMention      = false;
                    try
                    {
                        var lookup = await((IArgumentConverter <DiscordUser>) new DiscordUserConverter()).ConvertAsync(potentialUserId, ctx).ConfigureAwait(false);
                        hasMention = lookup.HasValue;
                    }
                    catch {}

                    if (hasMention)
                    {
                        term = term.Substring(0, idx).TrimEnd();
                        var mentionResult = await LookupTerm(term).ConfigureAwait(false);

                        if (mentionResult.score > result.score)
                        {
                            result = mentionResult;
                        }
                    }
                }
            }

            try
            {
                if (result.explanation != null && result.score > 0.5)
                {
                    if (!string.IsNullOrEmpty(result.fuzzyMatch))
                    {
                        var fuzzyNotice = $"Showing explanation for `{result.fuzzyMatch}`:";
#if DEBUG
                        fuzzyNotice = $"Showing explanation for `{result.fuzzyMatch}` ({result.score:0.######}):";
#endif
                        await ctx.RespondAsync(fuzzyNotice).ConfigureAwait(false);
                    }

                    var explain = result.explanation;
                    StatsStorage.ExplainStatCache.TryGetValue(explain.Keyword, out int stat);
                    StatsStorage.ExplainStatCache.Set(explain.Keyword, ++stat, StatsStorage.CacheTime);
                    await ctx.Channel.SendMessageAsync(explain.Text, explain.Attachment, explain.AttachmentFilename).ConfigureAwait(false);

                    return;
                }
            }
            catch (Exception e)
            {
                Config.Log.Error(e, "Failed to explain " + sourceTerm);
                return;
            }

            string inSpecificLocation = null;
            if (!LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
            {
                var spamChannel = await ctx.Client.GetChannelAsync(Config.BotSpamId).ConfigureAwait(false);

                inSpecificLocation = $" in {spamChannel.Mention} or bot DMs";
            }
            var msg = $"Unknown term `{term.Sanitize(replaceBackTicks: true)}`. Use `{ctx.Prefix}explain list` to look at defined terms{inSpecificLocation}";
            await ctx.RespondAsync(msg).ConfigureAwait(false);
        }
Ejemplo n.º 9
0
        public async Task ShowExplanation(CommandContext ctx, [RemainingText, Description("Term to explain")] string term)
        {
            string inSpecificLocation = null;

            if (!LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
            {
                var spamChannel = await ctx.Client.GetChannelAsync(Config.BotSpamId).ConfigureAwait(false);

                inSpecificLocation = $" in {spamChannel.Mention} or bot DMs";
            }


            if (!await DiscordInviteFilter.CheckMessageForInvitesAsync(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            if (!await AntipiracyMonitor.IsClean(ctx.Client, ctx.Message).ConfigureAwait(false))
            {
                return;
            }

            if (string.IsNullOrEmpty(term))
            {
                await ctx.RespondAsync($"You may want to look at available terms by using `{Config.CommandPrefix}explain list`{inSpecificLocation}").ConfigureAwait(false);

                return;
            }

            term = term.ToLowerInvariant();
            using (var db = new BotDb())
            {
                var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);

                if (explanation != null)
                {
                    await ctx.RespondAsync(explanation.Text).ConfigureAwait(false);

                    return;
                }
            }

            term = term.StripQuotes();
            var idx = term.LastIndexOf(" to ");

            if (idx > 0)
            {
                var  potentialUserId = term.Substring(idx + 4).Trim();
                bool hasMention      = false;
                try
                {
                    var lookup = await new DiscordUserConverter().ConvertAsync(potentialUserId, ctx).ConfigureAwait(false);
                    hasMention = lookup.HasValue;
                }
                catch { }
                if (hasMention)
                {
                    term = term.Substring(0, idx).TrimEnd();
                    using (var db = new BotDb())
                    {
                        var explanation = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);

                        if (explanation != null)
                        {
                            await ctx.RespondAsync(explanation.Text).ConfigureAwait(false);

                            return;
                        }
                    }
                }
            }

            var msg = $"Unknown term `{term.Sanitize()}`. Use `{Config.CommandPrefix}explain list` to look at defined terms{inSpecificLocation}";
            await ctx.RespondAsync(msg).ConfigureAwait(false);
        }