Example #1
0
        protected async Task Update(CommandContext ctx, int id, string eventName = null)
        {
            using var db = new BotDb();
            var evt = eventName == null
                ? db.EventSchedule.FirstOrDefault(e => e.Id == id)
                : db.EventSchedule.FirstOrDefault(e => e.Id == id && e.EventName == eventName);

            if (evt == null)
            {
                await ctx.ReactWithAsync(Config.Reactions.Failure, $"No event with id {id}").ConfigureAwait(false);

                return;
            }

            var(success, msg) = await EditEventPropertiesAsync(ctx, evt, eventName).ConfigureAwait(false);

            if (success)
            {
                await db.SaveChangesAsync().ConfigureAwait(false);

                if (LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
                {
                    await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed : FormatEvent(evt).WithTitle("Updated event schedule entry #" + evt.Id)).ConfigureAwait(false);
                }
                else
                {
                    await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Updated the schedule entry").ConfigureAwait(false);
                }
            }
            else
            {
                await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Event update aborted, changes weren't saved").ConfigureAwait(false);
            }
        }
Example #2
0
        protected async Task Add(CommandContext ctx, string eventName = null)
        {
            var evt = new EventSchedule();

            var(success, msg) = await EditEventPropertiesAsync(ctx, evt, eventName).ConfigureAwait(false);

            if (success)
            {
                using (var db = new BotDb())
                {
                    await db.EventSchedule.AddAsync(evt).ConfigureAwait(false);

                    await db.SaveChangesAsync().ConfigureAwait(false);
                }
                await ctx.ReactWithAsync(Config.Reactions.Success).ConfigureAwait(false);

                if (LimitedToSpamChannel.IsSpamChannel(ctx.Channel))
                {
                    await msg.UpdateOrCreateMessageAsync(ctx.Channel, embed : FormatEvent(evt).WithTitle("Created new event schedule entry #" + evt.Id)).ConfigureAwait(false);
                }
                else
                {
                    await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Added a new schedule entry").ConfigureAwait(false);
                }
            }
            else
            {
                await msg.UpdateOrCreateMessageAsync(ctx.Channel, "Event creation aborted").ConfigureAwait(false);
            }
        }
        public static Task OnMessageCreated(MessageCreateEventArgs args)
        {
            var message = args.Message;

            if (message.Author.IsBotSafeCheck())
            {
                return(Task.CompletedTask);
            }

            if (!string.IsNullOrEmpty(message.Content) &&
                (message.Content.StartsWith(Config.CommandPrefix) ||
                 message.Content.StartsWith(Config.AutoRemoveCommandPrefix)))
            {
                return(Task.CompletedTask);
            }

            var checkExternalLinks = "help".Equals(args.Channel.Name, StringComparison.InvariantCultureIgnoreCase) ||
                                     LimitedToSpamChannel.IsSpamChannel(args.Channel);

            OnNewLog(args.Client, args.Channel, args.Message, checkExternalLinks: checkExternalLinks);
            return(Task.CompletedTask);
        }
Example #4
0
        public static async Task OnMessageCreated(MessageCreateEventArgs args)
        {
            if (DefaultHandlerFilter.IsFluff(args.Message))
            {
                return;
            }

            if (args.Message.Channel.IsPrivate)
            {
                return;
            }

#if DEBUG
            if (args.Message.Content == "emoji test")
            {
                var badEmojis = new List <DiscordEmoji>(SadReactions.Concat(ThankYouReactions));
                var posted    = 0;
                var line      = 1;
                var msg       = await args.Channel.SendMessageAsync("Line " + line).ConfigureAwait(false);

                for (var i = 0; i < 5; i++)
                {
                    var tmp = new List <DiscordEmoji>();
                    foreach (var emoji in badEmojis)
                    {
                        try
                        {
                            await msg.CreateReactionAsync(emoji).ConfigureAwait(false);

                            if (++posted == 15)
                            {
                                line++;
                                posted = 0;
                                msg    = await args.Channel.SendMessageAsync("Line " + line).ConfigureAwait(false);
                            }
                        }
                        catch (Exception e)
                        {
                            Config.Log.Debug(e);
                            tmp.Add(emoji);
                        }
                    }
                    badEmojis = tmp;
                    if (badEmojis.Any())
                    {
                        await Task.Delay(1000).ConfigureAwait(false);
                    }
                }
                if (badEmojis.Any())
                {
                    await args.Channel.SendMessageAsync("Bad emojis: " + string.Concat(badEmojis)).ConfigureAwait(false);
                }
                else
                {
                    await args.Channel.SendMessageAsync("Everything looks fine").ConfigureAwait(false);
                }
                return;
            }
#endif

            var(needToSilence, needToThank) = NeedToSilence(args.Message);
            if (!(needToSilence || needToThank))
            {
                return;
            }

            if (needToThank)
            {
                DiscordEmoji emoji;
                string       thankYouMessage;
                lock (theDoor)
                {
                    emoji           = ThankYouReactions[rng.Next(ThankYouReactions.Length)];
                    thankYouMessage = LimitedToSpamChannel.IsSpamChannel(args.Channel) ? ThankYouMessages[rng.Next(ThankYouMessages.Length)] : null;
                }
                await args.Message.ReactWithAsync(emoji, thankYouMessage).ConfigureAwait(false);
            }
            if (needToSilence)
            {
                DiscordEmoji emoji;
                string       sadMessage;
                lock (theDoor)
                {
                    emoji      = SadReactions[rng.Next(SadReactions.Length)];
                    sadMessage = SadMessages[rng.Next(SadMessages.Length)];
                }
                await args.Message.ReactWithAsync(emoji, sadMessage).ConfigureAwait(false);

                if (args.Author.IsSmartlisted(args.Client, args.Message.Channel.Guild))
                {
                    var botMember = args.Guild?.CurrentMember ?? args.Client.GetMember(args.Client.CurrentUser);
                    if (args.Channel.PermissionsFor(botMember).HasPermission(Permissions.ReadMessageHistory))
                    {
                        var lastBotMessages = await args.Channel.GetMessagesBeforeAsync(args.Message.Id, 20, DateTime.UtcNow.Add(-Config.ShutupTimeLimit)).ConfigureAwait(false);

                        if (lastBotMessages.OrderByDescending(m => m.CreationTimestamp).FirstOrDefault(m => m.Author.IsCurrent) is DiscordMessage msg)
                        {
                            await msg.DeleteAsync("asked to shut up").ConfigureAwait(false);
                        }
                    }
                    else
                    {
                        await args.Message.ReactWithAsync(DiscordEmoji.FromUnicode("🙅"), @"No can do, boss ¯\\_(ツ)\_/¯").ConfigureAwait(false);
                    }
                }
            }
        }
        public static async void EnqueueLogProcessing(DiscordClient client, DiscordChannel channel, DiscordMessage message, DiscordMember requester = null, bool checkExternalLinks = false)
        {
            try
            {
                if (!QueueLimiter.Wait(0))
                {
                    await channel.SendMessageAsync("Log processing is rate limited, try again a bit later").ConfigureAwait(false);

                    return;
                }

                bool           parsedLog = false;
                var            startTime = Stopwatch.StartNew();
                DiscordMessage botMsg    = null;
                try
                {
                    var possibleHandlers = sourceHandlers.Select(h => h.FindHandlerAsync(message, archiveHandlers).ConfigureAwait(false).GetAwaiter().GetResult()).ToList();
                    var source           = possibleHandlers.FirstOrDefault(h => h.source != null).source;
                    var fail             = possibleHandlers.FirstOrDefault(h => !string.IsNullOrEmpty(h.failReason)).failReason;
                    if (source != null)
                    {
                        Config.Log.Debug($">>>>>>> {message.Id % 100} Parsing log '{source.FileName}' from {message.Author.Username}#{message.Author.Discriminator} ({message.Author.Id}) using {source.GetType().Name} ({source.SourceFileSize} bytes)...");
                        var analyzingProgressEmbed = GetAnalyzingMsgEmbed(client);
                        botMsg = await channel.SendMessageAsync(embed : analyzingProgressEmbed.AddAuthor(client, message, source)).ConfigureAwait(false);

                        parsedLog = true;

                        LogParseState result = null;
                        using (var timeout = new CancellationTokenSource(Config.LogParsingTimeout))
                        {
                            using var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, Config.Cts.Token);
                            var tries = 0;
                            do
                            {
                                result = await ParseLogAsync(
                                    source,
                                    async() => botMsg = await botMsg.UpdateOrCreateMessageAsync(channel, embed : analyzingProgressEmbed.AddAuthor(client, message, source)).ConfigureAwait(false),
                                    combinedTokenSource.Token
                                    ).ConfigureAwait(false);

                                tries++;
                            } while (result == null && !combinedTokenSource.IsCancellationRequested && tries < 3);
                        }
                        if (result == null)
                        {
                            botMsg = await botMsg.UpdateOrCreateMessageAsync(channel, embed : new DiscordEmbedBuilder
                            {
                                Description = "Log analysis failed, most likely cause is a truncated/invalid log.\n" +
                                              "Please run the game again and re-upload a new copy.",
                                Color = Config.Colors.LogResultFailed,
                            }
                                                                             .AddAuthor(client, message, source)
                                                                             .Build()
                                                                             ).ConfigureAwait(false);
                        }
                        else
                        {
                            result.ParsingTime = startTime.Elapsed;
                            try
                            {
                                if (result.Error == LogParseState.ErrorCode.PiracyDetected)
                                {
                                    var yarr = client.GetEmoji(":piratethink:", "☠");
                                    result.ReadBytes = 0;
                                    if (message.Author.IsWhitelisted(client, channel.Guild))
                                    {
                                        var piracyWarning = await result.AsEmbedAsync(client, message, source).ConfigureAwait(false);

                                        piracyWarning = piracyWarning.WithDescription("Please remove the log and issue warning to the original author of the log");
                                        botMsg        = await botMsg.UpdateOrCreateMessageAsync(channel, embed : piracyWarning).ConfigureAwait(false);

                                        await client.ReportAsync(yarr + " Pirated Release (whitelisted by role)", message, result.SelectedFilter?.String, result.SelectedFilterContext, ReportSeverity.Low).ConfigureAwait(false);
                                    }
                                    else
                                    {
                                        var severity = ReportSeverity.Low;
                                        try
                                        {
                                            await message.DeleteAsync("Piracy detected in log").ConfigureAwait(false);
                                        }
                                        catch (Exception e)
                                        {
                                            severity = ReportSeverity.High;
                                            Config.Log.Warn(e, $"Unable to delete message in {channel.Name}");
                                        }
                                        try
                                        {
                                            /*
                                             * botMsg = await botMsg.UpdateOrCreateMessageAsync(channel,
                                             *  $"{message.Author.Mention}, please read carefully:",
                                             *  embed: await result.AsEmbedAsync(client, message, source).ConfigureAwait(false)
                                             * ).ConfigureAwait(false);
                                             */
                                            botMsg = await botMsg.UpdateOrCreateMessageAsync(channel,
                                                                                             $"{message.Author.Mention}, please read carefully:\n" +
                                                                                             "🏴‍☠️ **Pirated content detected** 🏴‍☠️\n" +
                                                                                             "__You are being denied further support until you legally dump the game__.\n" +
                                                                                             "Please note that the RPCS3 community and its developers do not support piracy.\n" +
                                                                                             "Most of the issues with pirated dumps occur due to them being modified in some way " +
                                                                                             "that prevent them from working on RPCS3.\n" +
                                                                                             "If you need help obtaining valid working dump of the game you own, please read the quickstart guide at <https://rpcs3.net/quickstart>"
                                                                                             ).ConfigureAwait(false);
                                        }
                                        catch (Exception e)
                                        {
                                            Config.Log.Error(e, "Failed to send piracy warning");
                                        }
                                        try
                                        {
                                            await client.ReportAsync(yarr + " Pirated Release", message, result.SelectedFilter?.String, result.SelectedFilterContext, severity).ConfigureAwait(false);
                                        }
                                        catch (Exception e)
                                        {
                                            Config.Log.Error(e, "Failed to send piracy report");
                                        }
                                        if (!(message.Channel.IsPrivate || (message.Channel.Name?.Contains("spam") ?? true)))
                                        {
                                            await Warnings.AddAsync(client, message, message.Author.Id, message.Author.Username, client.CurrentUser, "Pirated Release", $"{result.SelectedFilter?.String} - {result.SelectedFilterContext?.Sanitize()}");
                                        }
                                    }
                                }
                                else
                                {
                                    if (result.SelectedFilter != null)
                                    {
                                        await ContentFilter.PerformFilterActions(client, message, result.SelectedFilter, FilterAction.IssueWarning | FilterAction.SendMessage, result.SelectedFilterContext).ConfigureAwait(false);
                                    }
                                    botMsg = await botMsg.UpdateOrCreateMessageAsync(channel,
                                                                                     requester == null?null : $"Analyzed log from {client.GetMember(channel.Guild, message.Author)?.GetUsernameWithNickname()} by request from {requester.Mention}:",
                                                                                     embed : await result.AsEmbedAsync(client, message, source).ConfigureAwait(false)
                                                                                     ).ConfigureAwait(false);
                                }
                            }
                            catch (Exception e)
                            {
                                Config.Log.Error(e, "Sending log results failed");
                            }
                        }
                        return;
                    }
                    else if (!string.IsNullOrEmpty(fail) &&
                             ("help".Equals(channel.Name, StringComparison.InvariantCultureIgnoreCase) || LimitedToSpamChannel.IsSpamChannel(channel)))
                    {
                        await channel.SendMessageAsync($"{message.Author.Mention} {fail}").ConfigureAwait(false);

                        return;
                    }

                    if (!"help".Equals(channel.Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        return;
                    }

                    var potentialLogExtension = message.Attachments.Select(a => Path.GetExtension(a.FileName).ToUpperInvariant().TrimStart('.')).FirstOrDefault();
                    switch (potentialLogExtension)
                    {
                    case "TXT":
                    {
                        await channel.SendMessageAsync($"{message.Author.Mention} Please upload the full RPCS3.log.gz (or RPCS3.log with a zip/rar icon) file after closing the emulator instead of copying the logs from RPCS3's interface, as it doesn't contain all the required information.").ConfigureAwait(false);

                        return;
                    }
                    }

                    if (string.IsNullOrEmpty(message.Content))
                    {
                        return;
                    }

                    var linkStart = message.Content.IndexOf("http");
                    if (linkStart > -1)
                    {
                        var link = message.Content[linkStart..].Split(linkSeparator, 2)[0];
                        if (link.Contains(".log", StringComparison.InvariantCultureIgnoreCase) || link.Contains("rpcs3.zip", StringComparison.CurrentCultureIgnoreCase))
                        {
                            await channel.SendMessageAsync("If you intended to upload a log file please re-upload it directly to discord").ConfigureAwait(false);
                        }
                    }
Example #6
0
 public static Task <DiscordChannel> GetChannelForSpamAsync(this CommandContext ctx)
 {
     return(LimitedToSpamChannel.IsSpamChannel(ctx.Channel) ? Task.FromResult(ctx.Channel) : ctx.CreateDmAsync());
 }
        public static async Task OnMessageCreated(DiscordClient c, MessageCreateEventArgs args)
        {
            if (DefaultHandlerFilter.IsFluff(args.Message))
            {
                return;
            }

            if (args.Message.Channel.IsPrivate)
            {
                return;
            }

#if DEBUG
            if (args.Message.Content == "emoji test")
            {
                var badEmojis = new List <DiscordEmoji>(SadReactions.Concat(ThankYouReactions));
                var posted    = 0;
                var line      = 1;
                var msg       = await args.Channel.SendMessageAsync("Line " + line).ConfigureAwait(false);

                for (var i = 0; i < 5; i++)
                {
                    var tmp = new List <DiscordEmoji>();
                    foreach (var emoji in badEmojis)
                    {
                        try
                        {
                            await msg.CreateReactionAsync(emoji).ConfigureAwait(false);

                            if (++posted == 15)
                            {
                                line++;
                                posted = 0;
                                msg    = await args.Channel.SendMessageAsync("Line " + line).ConfigureAwait(false);
                            }
                        }
                        catch (Exception e)
                        {
                            Config.Log.Debug(e);
                            tmp.Add(emoji);
                        }
                    }
                    badEmojis = tmp;
                    if (badEmojis.Any())
                    {
                        await Task.Delay(1000).ConfigureAwait(false);
                    }
                }
                if (badEmojis.Any())
                {
                    await args.Channel.SendMessageAsync("Bad emojis: " + string.Concat(badEmojis)).ConfigureAwait(false);
                }
                else
                {
                    await args.Channel.SendMessageAsync("Everything looks fine").ConfigureAwait(false);
                }
                return;
            }
#endif

            if (!string.IsNullOrEmpty(args.Message.Content) && Paws.Matches(args.Message.Content) is MatchCollection mc)
            {
                using var db = new BotDb();
                var matchedGroups = (from m in mc
                                     from Group g in m.Groups
                                     where g.Success && !string.IsNullOrEmpty(g.Value)
                                     select g.Name
                                     ).Distinct()
                                    .ToArray();
                if (matchedGroups.Contains("kot"))
                {
                    if (!db.Kot.Any(k => k.UserId == args.Author.Id))
                    {
                        db.Kot.Add(new Kot {
                            UserId = args.Author.Id
                        });
                        await db.SaveChangesAsync().ConfigureAwait(false);
                    }
                }
                if (matchedGroups.Contains("doggo"))
                {
                    if (!db.Doggo.Any(d => d.UserId == args.Author.Id))
                    {
                        db.Doggo.Add(new Doggo {
                            UserId = args.Author.Id
                        });
                        await db.SaveChangesAsync().ConfigureAwait(false);
                    }
                }
            }

            var(needToSilence, needToThank) = NeedToSilence(args.Message);
            if (!(needToSilence || needToThank))
            {
                return;
            }

            if (needToThank)
            {
                DiscordEmoji emoji;
                string       thankYouMessage;
                lock (theDoor)
                {
                    emoji           = ThankYouReactions[rng.Next(ThankYouReactions.Length)];
                    thankYouMessage = LimitedToSpamChannel.IsSpamChannel(args.Channel) ||
                                      LimitedToOfftopicChannel.IsOfftopicChannel(args.Channel)
                        ? ThankYouMessages[rng.Next(ThankYouMessages.Length)]
                        : null;
                }
                await args.Message.ReactWithAsync(emoji, thankYouMessage).ConfigureAwait(false);
            }
            if (needToSilence)
            {
                DiscordEmoji emoji;
                string       sadMessage;
                lock (theDoor)
                {
                    emoji      = SadReactions[rng.Next(SadReactions.Length)];
                    sadMessage = SadMessages[rng.Next(SadMessages.Length)];
                }
                await args.Message.ReactWithAsync(emoji, sadMessage).ConfigureAwait(false);

                if (args.Author.IsSmartlisted(c, args.Message.Channel.Guild))
                {
                    var botMember = args.Guild?.CurrentMember ?? c.GetMember(c.CurrentUser);
                    if (args.Channel.PermissionsFor(botMember).HasPermission(Permissions.ReadMessageHistory))
                    {
                        var lastBotMessages = await args.Channel.GetMessagesBeforeAsync(args.Message.Id, 20, DateTime.UtcNow.Add(-Config.ShutupTimeLimitInMin)).ConfigureAwait(false);

                        if (lastBotMessages.OrderByDescending(m => m.CreationTimestamp).FirstOrDefault(m => m.Author.IsCurrent) is DiscordMessage msg)
                        {
                            await msg.DeleteAsync("asked to shut up").ConfigureAwait(false);
                        }
                    }
                    else
                    {
                        await args.Message.ReactWithAsync(DiscordEmoji.FromUnicode("🙅"), @"No can do, boss ¯\\_(ツ)\_/¯").ConfigureAwait(false);
                    }
                }
            }
        }
Example #8
0
        public async Task ShowExplanation(CommandContext ctx, [RemainingText, Description("Term to explain")] string term)
        {
            await ctx.TriggerTypingAsync().ConfigureAwait(false);

            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 (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);
        }
Example #9
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);
        }