public static async Task <bool> IsClean(DiscordClient client, DiscordMessage message)
        {
            if (message.Channel.IsPrivate)
            {
                return(true);
            }

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

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

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

            string trigger  = null;
            var    severity = ReportSeverity.Low;

            try
            {
                trigger = await PiracyStringProvider.FindTriggerAsync(message.Content);

                if (trigger == null)
                {
                    return(true);
                }

                await message.Channel.DeleteMessageAsync(message, $"Mention of piracy trigger '{trigger}'").ConfigureAwait(false);
            }
            catch (Exception e)
            {
                Config.Log.Warn(e, $"Couldn't delete message in {message.Channel.Name}");
                severity = ReportSeverity.High;
            }
            try
            {
                var rules = await client.GetChannelAsync(Config.BotRulesChannelId).ConfigureAwait(false);

                await Task.WhenAll(
                    message.Channel.SendMessageAsync($"{message.Author.Mention} Please follow the {rules.Mention} and do not discuss piracy on this server. Repeated offence may result in a ban."),
                    client.ReportAsync("Mention of piracy", message, trigger, message.Content, severity),
                    Warnings.AddAsync(client, message, message.Author.Id, message.Author.Username, client.CurrentUser, "Mention of piracy", message.Content.Sanitize())
                    ).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                Config.Log.Warn(e, $"Couldn't finish piracy trigger actions for a message in {message.Channel.Name}");
            }
            return(false);
        }
        public static async Task <bool> CheckMessageForInvitesAsync(DiscordClient client, DiscordMessage message)
        {
            if (message.Channel.IsPrivate)
            {
                return(true);
            }

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

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

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

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

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

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

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

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

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

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

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

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

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

                    if (circumventionAttempt)
                    {
                        await Warnings.AddAsync(client, message, message.Author.Id, message.Author.Username, client.CurrentUser, "Attempted to circumvent discord invite filter", codeResolveMsg);
                    }
                    return(false);
                }
            }
            return(true);
        }
        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);
                        }
                    }
        public static async Task PerformFilterActions(DiscordClient client, DiscordMessage message, Piracystring trigger, FilterAction ignoreFlags = 0, string triggerContext = null, string infraction = null, string warningReason = null)
        {
            if (trigger == null)
            {
                return;
            }

            var severity         = ReportSeverity.Low;
            var completedActions = new List <FilterAction>();

            if (trigger.Actions.HasFlag(FilterAction.RemoveContent) && !ignoreFlags.HasFlag(FilterAction.RemoveContent))
            {
                try
                {
                    DeletedMessagesMonitor.RemovedByBotCache.Set(message.Id, true, DeletedMessagesMonitor.CacheRetainTime);
                    await message.Channel.DeleteMessageAsync(message, $"Removed according to filter '{trigger}'").ConfigureAwait(false);

                    completedActions.Add(FilterAction.RemoveContent);
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e);
                    severity = ReportSeverity.High;
                }
                try
                {
                    var author = client.GetMember(message.Author);
                    Config.Log.Debug($"Removed message from {author.GetMentionWithNickname()} in #{message.Channel.Name}: {message.Content}");
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e);
                }
            }

            if (trigger.Actions.HasFlag(FilterAction.IssueWarning) && !ignoreFlags.HasFlag(FilterAction.IssueWarning))
            {
                try
                {
                    await Warnings.AddAsync(client, message, message.Author.Id, message.Author.Username, client.CurrentUser, warningReason ?? "Mention of piracy", message.Content.Sanitize()).ConfigureAwait(false);

                    completedActions.Add(FilterAction.IssueWarning);
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e, $"Couldn't issue warning in #{message.Channel.Name}");
                }
            }

            if (trigger.Actions.HasFlag(FilterAction.SendMessage) && !ignoreFlags.HasFlag(FilterAction.SendMessage))
            {
                try
                {
                    var msgContent = trigger.CustomMessage;
                    if (string.IsNullOrEmpty(msgContent))
                    {
                        var rules = await client.GetChannelAsync(Config.BotRulesChannelId).ConfigureAwait(false);

                        msgContent = $"Please follow the {rules.Mention} and do not discuss piracy on this server. Repeated offence may result in a ban.";
                    }
                    await message.Channel.SendMessageAsync($"{message.Author.Mention} {msgContent}").ConfigureAwait(false);

                    completedActions.Add(FilterAction.SendMessage);
                }
                catch (Exception e)
                {
                    Config.Log.Warn(e, $"Failed to send message in #{message.Channel.Name}");
                }
            }

            if (trigger.Actions.HasFlag(FilterAction.ShowExplain) && !ignoreFlags.HasFlag(FilterAction.ShowExplain))
            {
                var result = await Explain.LookupTerm(trigger.ExplainTerm).ConfigureAwait(false);

                await Explain.SendExplanation(result, trigger.ExplainTerm, message).ConfigureAwait(false);
            }

            var actionList = "";

            foreach (FilterAction fa in Enum.GetValues(typeof(FilterAction)))
            {
                if (trigger.Actions.HasFlag(fa) && !ignoreFlags.HasFlag(fa))
                {
                    actionList += (completedActions.Contains(fa) ? "✅" : "❌") + " " + fa + ' ';
                }
            }

            try
            {
                if (!trigger.Actions.HasFlag(FilterAction.MuteModQueue) && !ignoreFlags.HasFlag(FilterAction.MuteModQueue))
                {
                    await client.ReportAsync(infraction ?? "🤬 Content filter hit", message, trigger.String, triggerContext ?? message.Content, severity, actionList).ConfigureAwait(false);
                }
            }
            catch (Exception e)
            {
                Config.Log.Error(e, "Failed to report content filter hit");
            }
        }
        public static async void BackgroundProcessor(MessageCreateEventArgs args)
        {
            try
            {
                var message = args.Message;
                if (!QueueLimiter.Wait(0))
                {
                    await args.Channel.SendMessageAsync("Log processing is rate limited, try again a bit later").ConfigureAwait(false);

                    return;
                }

                bool parsedLog = false;
                var  startTime = Stopwatch.StartNew();
                try
                {
                    foreach (var attachment in message.Attachments.Where(a => a.FileSize < Config.AttachmentSizeLimit && !a.FileName.EndsWith("tty.log", StringComparison.InvariantCultureIgnoreCase)))
                    {
                        foreach (var handler in handlers)
                        {
                            if (await handler.CanHandleAsync(attachment).ConfigureAwait(false))
                            {
                                await args.Channel.TriggerTypingAsync().ConfigureAwait(false);

                                Config.Log.Debug($">>>>>>> {message.Id % 100} Parsing log from attachment {attachment.FileName} ({attachment.FileSize})...");
                                parsedLog = true;
                                LogParseState result = null;
                                try
                                {
                                    var pipe         = new Pipe();
                                    var fillPipeTask = handler.FillPipeAsync(attachment, pipe.Writer);
                                    result = await LogParser.ReadPipeAsync(pipe.Reader).ConfigureAwait(false);

                                    await fillPipeTask.ConfigureAwait(false);
                                }
                                catch (Exception e)
                                {
                                    Config.Log.Error(e, "Log parsing failed");
                                }
                                if (result == null)
                                {
                                    await args.Channel.SendMessageAsync("Log analysis failed, most likely cause is a truncated/invalid log. Please run the game again and reupload the new copy.").ConfigureAwait(false);
                                }
                                else
                                {
                                    try
                                    {
                                        if (result.Error == LogParseState.ErrorCode.PiracyDetected)
                                        {
                                            if (args.Author.IsWhitelisted(args.Client, args.Guild))
                                            {
                                                await Task.WhenAll(
                                                    args.Channel.SendMessageAsync("I see wha' ye did thar ☠"),
                                                    args.Client.ReportAsync("Pirated Release (whitelisted by role)", args.Message, result.PiracyTrigger, result.PiracyContext, 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 {args.Channel.Name}");
                                                }
                                                await args.Channel.SendMessageAsync(embed : await result.AsEmbedAsync(args.Client, args.Message).ConfigureAwait(false)).ConfigureAwait(false);

                                                await Task.WhenAll(
                                                    args.Client.ReportAsync("Pirated Release", args.Message, result.PiracyTrigger, result.PiracyContext, severity),
                                                    Warnings.AddAsync(args.Client, args.Message, args.Message.Author.Id, args.Message.Author.Username, args.Client.CurrentUser,
                                                                      "Pirated Release", $"{message.Content.Sanitize()} - {result.PiracyTrigger}")
                                                    );
                                            }
                                        }
                                        else
                                        {
                                            await args.Channel.SendMessageAsync(embed : await result.AsEmbedAsync(args.Client, args.Message).ConfigureAwait(false)).ConfigureAwait(false);
                                        }
                                    }
                                    catch (Exception e)
                                    {
                                        Config.Log.Error(e, "Sending log results failed");
                                    }
                                }
                                return;
                            }
                        }
                    }

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

                    var potentialLogExtension = message.Attachments.Select(a => Path.GetExtension(a.FileName).ToUpperInvariant().TrimStart('.')).FirstOrDefault();
                    switch (potentialLogExtension)
                    {
                    case "TXT":
                    {
                        await args.Channel.SendMessageAsync($"{message.Author.Mention} please do not copy/paste logs from UI, they do not contain all the required information; ask if you do not know how to upload full log file").ConfigureAwait(false);

                        return;
                    }
                    }

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

                    var linkStart = message.Content.IndexOf("http");
                    if (linkStart > -1)
                    {
                        var link = message.Content.Substring(linkStart).Split(linkSeparator, 2)[0];
                        if (link.Contains(".log", StringComparison.InvariantCultureIgnoreCase) || link.Contains("rpcs3.zip", StringComparison.CurrentCultureIgnoreCase))
                        {
                            await args.Channel.SendMessageAsync("If you intended to upload a log file please re-upload it directly to discord").ConfigureAwait(false);
                        }
                    }
                }
                finally
                {
                    QueueLimiter.Release();
                    if (parsedLog)
                    {
                        Config.Log.Debug($"<<<<<<< {message.Id % 100} Finished parsing in {startTime.Elapsed}");
                    }
                }
            }
            catch (Exception e)
            {
                Config.Log.Error(e, "Error parsing log");
            }
        }