From bc5f7a0473272acd21722447f3d55f20608c15d8 Mon Sep 17 00:00:00 2001 From: Ean Milligan Date: Mon, 20 May 2024 02:54:45 -0400 Subject: [PATCH] Add Guild and CustomActivity auditing --- src/commands/audit.ts | 148 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 17 deletions(-) diff --git a/src/commands/audit.ts b/src/commands/audit.ts index 2ebea8d..0ae6684 100644 --- a/src/commands/audit.ts +++ b/src/commands/audit.ts @@ -1,5 +1,5 @@ import config from '../../config.ts'; -import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Bot, DiscordEmbedField, Interaction, InteractionResponseTypes } from '../../deps.ts'; +import { ApplicationCommandOptionTypes, ApplicationCommandTypes, BotWithCache, DiscordEmbedField, Interaction, InteractionResponseTypes } from '../../deps.ts'; import { infoColor2, isLFGChannel, somethingWentWrong } from '../commandUtils.ts'; import { dbClient } from '../db/client.ts'; import { queries } from '../db/common.ts'; @@ -7,8 +7,20 @@ import { CommandDetails } from '../types/commandTypes.ts'; import utils from '../utils.ts'; import { auditSlashName } from './slashCommandNames.ts'; +type DupeAct = { + upperActTitle?: string; + upperActSubtitle?: string; + dupeCount: number; +}; + +type DBSizeTable = { + table: string; + size: number; + rows: number; +}; + const auditDbName = 'database'; -const auditCustomActivities = 'custom-activities'; +const auditCustomActivitiesName = 'custom-activities'; const auditGuildName = 'guilds'; const details: CommandDetails = { @@ -23,7 +35,7 @@ const details: CommandDetails = { description: `Developer Command: Checks ${config.name}'s DB size.`, }, { - name: auditCustomActivities, + name: auditCustomActivitiesName, type: ApplicationCommandOptionTypes.SubCommand, description: 'Developer Command: Checks for duplicate custom activities.', }, @@ -35,18 +47,18 @@ const details: CommandDetails = { ], }; -const execute = async (bot: Bot, interaction: Interaction) => { +const execute = async (bot: BotWithCache, interaction: Interaction) => { if (interaction.member && interaction.guildId && interaction.data?.options?.[0].options) { dbClient.execute(queries.callIncCnt('cmd-audit')).catch((e) => utils.commonLoggers.dbError('audit.ts@inc', 'call sproc INC_CNT on', e)); const auditName = interaction.data.options[0].name; switch (auditName) { case auditDbName: { // Get DB statistics - const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('audit.ts@dbSize', 'query', e)); + const auditQuery: Array = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('audit.ts@dbSize', 'query', e)); // Turn all tables into embed fields, currently only properly will handle 25 tables, but we'll fix that when group up gets 26 tables const embedFields: Array = []; - auditQuery.forEach((row: any) => { + auditQuery.forEach((row) => { embedFields.push({ name: `${row.table}`, value: `**Size:** ${row.size} MB @@ -54,27 +66,129 @@ const execute = async (bot: Bot, interaction: Interaction) => { inline: true, }); }); - bot.helpers.sendInteractionResponse( - interaction.id, - interaction.token, - { + bot.helpers + .sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n), + embeds: [ + { + color: infoColor2, + title: 'Database Audit', + description: 'Lists all tables with their current size and row count.', + timestamp: new Date().getTime(), + fields: embedFields.slice(0, 25), + }, + ], + }, + }) + .catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@dbSize', interaction, e)); + break; + } + case auditCustomActivitiesName: { + const dupActTitles: Array = await dbClient.query( + `SELECT UPPER(activityTitle) as upperActTitle, COUNT(*) as dupeCount FROM custom_activities GROUP BY upperActTitle HAVING dupeCount > 1;`, + ).catch((e) => utils.commonLoggers.dbError('audit.ts@customActTitle', 'query', e)); + const dupActSubTitles: Array = await dbClient.query( + `SELECT UPPER(activitySubtitle) as upperActSubtitle, COUNT(*) as dupeCount FROM custom_activities GROUP BY upperActSubtitle HAVING dupeCount > 1;`, + ).catch((e) => utils.commonLoggers.dbError('audit.ts@customActSubTitle', 'query', e)); + const dupActs: Array = await dbClient + .query( + `SELECT UPPER(activityTitle) as upperActTitle, UPPER(activitySubtitle) as upperActSubtitle, COUNT(*) as dupeCount FROM custom_activities GROUP BY upperActTitle, upperActSubtitle HAVING dupeCount > 1;`, + ) + .catch((e) => utils.commonLoggers.dbError('audit.ts@customAct', 'query', e)); + + bot.helpers + .sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n), + content: 'Duplicate Custom Activity Titles, Subtitles, and Activities:', + embeds: [ + { + color: infoColor2, + title: 'Duplicate Activity Titles:', + description: dupActTitles.map((dupAct) => `${dupAct.upperActTitle}: ${dupAct.dupeCount}`).join('\n'), + timestamp: new Date().getTime(), + }, + { + color: infoColor2, + title: 'Duplicate Activity Subtitles:', + description: dupActSubTitles.map((dupAct) => `${dupAct.upperActSubtitle}: ${dupAct.dupeCount}`).join('\n'), + timestamp: new Date().getTime(), + }, + { + color: infoColor2, + title: 'Duplicate Activities (Title/Subtitle):', + description: dupActs.map((dupAct) => `${dupAct.upperActTitle}/${dupAct.upperActSubtitle}: ${dupAct.dupeCount}`).join('\n'), + timestamp: new Date().getTime(), + }, + ], + }, + }) + .catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@dbSize', interaction, e)); + break; + } + case auditGuildName: { + let totalCount = 0; + let auditText = ''; + + bot.guilds.forEach((guild) => { + totalCount += guild.memberCount; + auditText += `Guild: ${guild.name} (${guild.id}) +Owner: ${guild.ownerId} +Tot mem: ${guild.memberCount} + +`; + }); + + const b = await new Blob([auditText as BlobPart], { 'type': 'text' }); + const tooBig = await new Blob(['tooBig' as BlobPart], { 'type': 'text' }); + + bot.helpers + .sendInteractionResponse(interaction.id, interaction.token, { type: InteractionResponseTypes.ChannelMessageWithSource, data: { flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n), embeds: [{ color: infoColor2, - title: 'Database Audit', - description: 'Lists all tables with their current size and row count.', + title: 'Guilds Audit', + description: `Shows details of the guilds that ${config.name} serves. + +Please see attached file for audit details on cached guilds and members.`, + fields: [ + { + name: 'Total Guilds:', + value: `${bot.guilds.size}`, + inline: true, + }, + { + name: 'Uncached Guilds:', + value: `${bot.dispatchedGuildIds.size}`, + inline: true, + }, + { + name: 'Total Members\n(may be artificially higher if 1 user is in multiple guilds the bot is in):', + value: `${totalCount}`, + inline: true, + }, + { + name: 'Average members per guild:', + value: `${(totalCount / bot.guilds.size).toFixed(2)}`, + inline: true, + }, + ], timestamp: new Date().getTime(), - fields: embedFields.slice(0, 25), }], + file: { + 'blob': b.size > 8388290 ? tooBig : b, + 'name': 'auditDetails.txt', + }, }, - }, - ).catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@dbSize', interaction, e)); + }) + .catch((e: Error) => utils.commonLoggers.interactionSendError('audit.ts@guilds', interaction, e)); break; } - case auditCustomActivities: - case auditGuildName: default: somethingWentWrong(bot, interaction, `auditNameNotHandled@${auditName}`); break;