public async ValueTask HandleLoggerBotCleanup(DiscordMessage msg) { if (msg.Channel.Type != ChannelType.Text) { return; } if (!msg.Channel.BotHasAllPermissions(Permissions.ManageMessages)) { return; } // If this message is from a *webhook*, check if the name matches one of the bots we know // TODO: do we need to do a deeper webhook origin check, or would that be too hard on the rate limit? // If it's from a *bot*, check the bot ID to see if we know it. LoggerBot bot = null; if (msg.WebhookMessage) { _botsByWebhookName.TryGetValue(msg.Author.Username, out bot); } else if (msg.Author.IsBot) { _bots.TryGetValue(msg.Author.Id, out bot); } // If we didn't find anything before, or what we found is an unsupported bot, bail if (bot == null) { return; } try { // We try two ways of extracting the actual message, depending on the bots if (bot.FuzzyExtractFunc != null) { // Some bots (Carl, Circle, etc) only give us a user ID and a rough timestamp, so we try our best to // "cross-reference" those with the message DB. We know the deletion event happens *after* the message // was sent, so we're checking for any messages sent in the same guild within 3 seconds before the // delete event timestamp, which is... good enough, I think? Potential for false positives and negatives // either way but shouldn't be too much, given it's constrained by user ID and guild. var fuzzy = bot.FuzzyExtractFunc(msg); if (fuzzy == null) { return; } using var conn = await _db.Obtain(); var mid = await conn.QuerySingleOrDefaultAsync <ulong?>( "select mid from messages where sender = @User and mid > @ApproxID and guild = @Guild limit 1", new { fuzzy.Value.User, Guild = msg.Channel.GuildId, ApproxId = DiscordUtils.InstantToSnowflake( fuzzy.Value.ApproxTimestamp - TimeSpan.FromSeconds(3)) }); if (mid == null) { return; // If we didn't find a corresponding message, bail } // Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message. await msg.DeleteAsync(); } else if (bot.ExtractFunc != null) { // Other bots give us the message ID itself, and we can just extract that from the database directly. var extractedId = bot.ExtractFunc(msg); if (extractedId == null) { return; // If we didn't find anything, bail. } using var conn = await _db.Obtain(); // We do this through an inline query instead of through DataStore since we don't need all the joins it does var mid = await conn.QuerySingleOrDefaultAsync <ulong?>( "select mid from messages where original_mid = @Mid", new { Mid = extractedId.Value }); if (mid == null) { return; } // If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it! await msg.DeleteAsync(); } // else should not happen, but idk, it might } catch (NotFoundException) { // Sort of a temporary measure: getting an error in Sentry about a NotFoundException from D#+ here // The only thing I can think of that'd cause this are the DeleteAsync() calls which 404 when // the message doesn't exist anyway - so should be safe to just ignore it, right? } }
public async ValueTask HandleLoggerBotCleanup(SocketMessage msg, GuildConfig cachedGuild) { // Bail if not enabled, or if we don't have permission here if (!cachedGuild.LogCleanupEnabled) { return; } if (!(msg.Channel is SocketTextChannel channel)) { return; } if (!channel.Guild.GetUser(_client.CurrentUser.Id).GetPermissions(channel).ManageMessages) { return; } // If this message is from a *webhook*, check if the name matches one of the bots we know // TODO: do we need to do a deeper webhook origin check, or would that be too hard on the rate limit? // If it's from a *bot*, check the bot ID to see if we know it. LoggerBot bot = null; if (msg.Author.IsWebhook) { _botsByWebhookName.TryGetValue(msg.Author.Username, out bot); } else if (msg.Author.IsBot) { _bots.TryGetValue(msg.Author.Id, out bot); } // If we didn't find anything before, or what we found is an unsupported bot, bail if (bot == null) { return; } // We try two ways of extracting the actual message, depending on the bots if (bot.FuzzyExtractFunc != null) { // Some bots (Carl, Circle, etc) only give us a user ID and a rough timestamp, so we try our best to // "cross-reference" those with the message DB. We know the deletion event happens *after* the message // was sent, so we're checking for any messages sent in the same guild within 3 seconds before the // delete event timestamp, which is... good enough, I think? Potential for false positives and negatives // either way but shouldn't be too much, given it's constrained by user ID and guild. var fuzzy = bot.FuzzyExtractFunc(msg); if (fuzzy == null) { return; } using var conn = await _db.Obtain(); var mid = await conn.QuerySingleOrDefaultAsync <ulong?>( "select mid from messages where sender = @User and mid > @ApproxID and guild = @Guild", new { fuzzy.Value.User, Guild = (msg.Channel as ITextChannel)?.GuildId ?? 0, ApproxId = SnowflakeUtils.ToSnowflake(fuzzy.Value.ApproxTimestamp - TimeSpan.FromSeconds(3)) }); if (mid == null) { return; // If we didn't find a corresponding message, bail } // Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message. await msg.DeleteAsync(); } else if (bot.ExtractFunc != null) { // Other bots give us the message ID itself, and we can just extract that from the database directly. var extractedId = bot.ExtractFunc(msg); if (extractedId == null) { return; // If we didn't find anything, bail. } using var conn = await _db.Obtain(); // We do this through an inline query instead of through DataStore since we don't need all the joins it does var mid = await conn.QuerySingleOrDefaultAsync <ulong?>("select mid from messages where original_mid = @Mid", new { Mid = extractedId.Value }); if (mid == null) { return; } // If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it! await msg.DeleteAsync(); } // else should not happen, but idk, it might }