diff --git a/config.example.ts b/config.example.ts index c655cfa..4972098 100644 --- a/config.example.ts +++ b/config.example.ts @@ -3,12 +3,12 @@ export const config = { 'version': '1.0.0', // Version of the bot 'token': 'the_bot_token', // Discord API Token for this bot 'localToken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" - 'prefix': 'gu!', // Prefix for all commands + 'prefix': '/', // Prefix for all commands 'db': { // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot 'host': '', // IP address for the db, usually localhost 'localhost': '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment 'port': 3306, // Port for the db - 'username': '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privalages + 'username': '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privileges 'password': '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box 'name': '', // Name of the database Schema to use for the bot }, @@ -18,7 +18,7 @@ export const config = { }, 'logChannel': 'the_log_channel', // Discord channel ID where the bot should put startup messages and other error messages needed 'reportChannel': 'the_report_channel', // Discord channel ID where reports will be sent when using the built-in report command - 'devServer': 'the_dev_server', // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts + 'devServer': 'the_dev_server', // Discord guild ID where testing of indev features/commands will be handled, used in conjunction with the DEVMODE bool in mod.ts 'owner': 'the_bot_owner', // Discord user ID of the bot admin 'botLists': [ // Array of objects containing all bot lists that stats should be posted to { // Bot List object, duplicate for each bot list diff --git a/db/initialize.ts b/db/initialize.ts index d850fd8..279219b 100644 --- a/db/initialize.ts +++ b/db/initialize.ts @@ -1,21 +1,8 @@ // This file will create all tables for the artificer schema // DATA WILL BE LOST IF DB ALREADY EXISTS, RUN AT OWN RISK -import { - // MySQL deps - Client, -} from '../deps.ts'; - -import { LOCALMODE } from '../flags.ts'; import config from '../config.ts'; - -// Log into the MySQL DB -const dbClient = await new Client().connect({ - hostname: LOCALMODE ? config.db.localhost : config.db.host, - port: config.db.port, - username: config.db.username, - password: config.db.password, -}); +import { dbClient } from '../src/db.ts'; console.log('Attempting to create DB'); await dbClient.execute(`CREATE SCHEMA IF NOT EXISTS ${config.db.name};`); @@ -25,9 +12,7 @@ console.log('DB created'); console.log('Attempt to drop all tables'); await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_CNT;`); await dbClient.execute(`DROP TABLE IF EXISTS command_cnt;`); -await dbClient.execute(`DROP TABLE IF EXISTS guild_prefix;`); -await dbClient.execute(`DROP TABLE IF EXISTS guild_mod_role;`); -await dbClient.execute(`DROP TABLE IF EXISTS guild_clean_channel;`); +await dbClient.execute(`DROP TABLE IF EXISTS guild_settings;`); console.log('Tables dropped'); console.log('Attempting to create table command_cnt'); @@ -47,41 +32,21 @@ await dbClient.execute(` IN cmd CHAR(20) ) BEGIN - declare oldcnt bigint unsigned; - set oldcnt = (SELECT count FROM command_cnt WHERE command = cmd); - UPDATE command_cnt SET count = oldcnt + 1 WHERE command = cmd; + declare oldCnt bigint unsigned; + set oldCnt = (SELECT count FROM command_cnt WHERE command = cmd); + UPDATE command_cnt SET count = oldCnt + 1 WHERE command = cmd; END `); console.log('Stored Procedure created'); -console.log('Attempting to create table guild_prefix'); +console.log('Attempting to create table guild_settings'); await dbClient.execute(` - CREATE TABLE guild_prefix ( + CREATE TABLE guild_settings ( guildId bigint unsigned NOT NULL, - prefix char(10) NOT NULL, - PRIMARY KEY (guildid), - UNIQUE KEY guild_prefix_guildid_UNIQUE (guildid) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -`); -console.log('Table created'); - -console.log('Attempting to create table guild_mod_role'); -await dbClient.execute(` - CREATE TABLE guild_mod_role ( - guildId bigint unsigned NOT NULL, - roleId bigint unsigned NOT NULL, - PRIMARY KEY (guildid), - UNIQUE KEY guild_mod_role_guildid_UNIQUE (guildid) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -`); -console.log('Table created'); - -console.log('Attempting to create table guild_clean_channel'); -await dbClient.execute(` - CREATE TABLE guild_clean_channel ( - guildId bigint unsigned NOT NULL, - channelId bigint unsigned NOT NULL, - PRIMARY KEY (guildid, channelId) + lfgChannelId bigint unsigned NOT NULL, + managerRoleId bigint unsigned NOT NULL, + logChannelId bigint unsigned NOT NULL, + PRIMARY KEY (guildId, lfgChannelId) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; `); console.log('Table created'); diff --git a/db/populateDefaults.ts b/db/populateDefaults.ts index 4eb9fae..31f6dea 100644 --- a/db/populateDefaults.ts +++ b/db/populateDefaults.ts @@ -1,26 +1,15 @@ // This file will populate the tables with default values -import { - // MySQL deps - Client, -} from '../deps.ts'; +import { dbClient } from '../src/db.ts'; -import { LOCALMODE } from '../flags.ts'; -import config from '../config.ts'; - -// Log into the MySQL DB -const dbClient = await new Client().connect({ - hostname: LOCALMODE ? config.db.localhost : config.db.host, - port: config.db.port, - db: config.db.name, - username: config.db.username, - password: config.db.password, -}); - -console.log('Attempting to insert default commands into command_cnt'); -const commands = ['ping', 'help', 'info', 'version', 'report', 'privacy', 'lfg', 'prefix']; -for (const command of commands) { - await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => { +console.log('Attempting to insert default actions into command_cnt'); +const actions = [ + 'cmd-setup', + 'cmd-info', + 'cmd-report', +]; +for (const action of actions) { + await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [action]).catch((e) => { console.log(`Failed to insert into database`, e); }); } diff --git a/deps.ts b/deps.ts index ce53a83..d86169f 100644 --- a/deps.ts +++ b/deps.ts @@ -12,6 +12,7 @@ export { ApplicationCommandFlags, ApplicationCommandOptionTypes, ApplicationCommandTypes, + ChannelTypes, createBot, editBotMember, editBotStatus, @@ -21,6 +22,10 @@ export { sendInteractionResponse, sendMessage, startBot, + OverwriteTypes, + BitwisePermissionFlags, + MessageComponentTypes, + ButtonStyles, } from 'https://deno.land/x/discordeno@17.0.1/mod.ts'; export type { ActionRow, @@ -37,6 +42,7 @@ export type { MakeRequired, Message, PermissionStrings, + DiscordEmbedField, } from 'https://deno.land/x/discordeno@17.0.1/mod.ts'; export { Client } from 'https://deno.land/x/mysql@v2.11.0/mod.ts'; diff --git a/mod.ts b/mod.ts index bd99686..6aca384 100644 --- a/mod.ts +++ b/mod.ts @@ -18,4 +18,5 @@ enableCacheSweepers(bot); // Start the bot await startBot(bot); +// Announce the slash commands so users can use them await createSlashCommands(bot); diff --git a/src/commandUtils.ts b/src/commandUtils.ts index 9b1b61e..1cf70da 100644 --- a/src/commandUtils.ts +++ b/src/commandUtils.ts @@ -1,6 +1,7 @@ -import { ApplicationCommandFlags } from '../deps.ts'; +import { ApplicationCommandFlags, Bot, Interaction, InteractionResponseTypes } from '../deps.ts'; import config from '../config.ts'; -import { lfgChannels } from './db.ts'; +import { lfgChannelSettings } from './db.ts'; +import utils from './utils.ts'; export const failColor = 0xe71212; export const warnColor = 0xe38f28; @@ -17,8 +18,8 @@ export const getRandomStatus = (guildCount: number): string => { return statuses[Math.floor((Math.random() * statuses.length) + 1)]; }; -export const isLFGChannel = (channelId: bigint) => { - return (lfgChannels.includes(channelId) || channelId === 0n) ? ApplicationCommandFlags.Ephemeral : undefined; +export const isLFGChannel = (guildId: bigint, channelId: bigint) => { + return (lfgChannelSettings.has(`${guildId}-${channelId}`) || channelId === 0n || guildId === 0n) ? ApplicationCommandFlags.Ephemeral : undefined; }; export const generateReport = (msg: string) => ({ @@ -28,3 +29,20 @@ export const generateReport = (msg: string) => ({ description: msg, }], }); + +export const somethingWentWrong = (bot: Bot, interaction: Interaction, errorCode: string) => + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Something went wrong...', + description: 'You should not be able to get here. Please try again and if the issue continues, `/report` this issue to the developers with the error code below.', + fields: [{ + name: 'Error Code:', + value: errorCode, + }], + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('commandUtils.ts', interaction, e)); diff --git a/src/commands/_index.ts b/src/commands/_index.ts index 100b155..679332b 100644 --- a/src/commands/_index.ts +++ b/src/commands/_index.ts @@ -1,11 +1,12 @@ import { Bot, CreateApplicationCommand, log, LT, MakeRequired } from '../../deps.ts'; -import { Commands } from '../types/commandTypes.ts'; +import { Command } from '../types/commandTypes.ts'; import utils from '../utils.ts'; import info from './info.ts'; import report from './report.ts'; +import setup from './setup.ts'; -export const commands: Array = [info, report]; +export const commands: Array = [info, report, setup]; export const createSlashCommands = async (bot: Bot) => { const globalCommands: MakeRequired[] = []; diff --git a/src/commands/info.ts b/src/commands/info.ts index 6551a26..12fe25d 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -12,14 +12,14 @@ const details: CommandDetails = { }; const execute = (bot: Bot, interaction: Interaction) => { - dbClient.execute(queries.callIncCnt('info')).catch((e) => utils.commonLoggers.dbError('info.ts', 'call sproc INC_CNT on', e)); + dbClient.execute(queries.callIncCnt('cmd-info')).catch((e) => utils.commonLoggers.dbError('info.ts', 'call sproc INC_CNT on', e)); bot.helpers.sendInteractionResponse( interaction.id, interaction.token, { type: InteractionResponseTypes.ChannelMessageWithSource, data: { - flags: isLFGChannel(interaction.channelId || 0n), + flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n), embeds: [{ color: infoColor2, title: `${config.name}, the LFG bot`, diff --git a/src/commands/report.ts b/src/commands/report.ts index 03cbc9c..3155f58 100644 --- a/src/commands/report.ts +++ b/src/commands/report.ts @@ -1,6 +1,6 @@ import config from '../../config.ts'; import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Bot, Interaction, InteractionResponseTypes, sendMessage } from '../../deps.ts'; -import { generateReport, isLFGChannel, successColor } from '../commandUtils.ts'; +import { generateReport, isLFGChannel, somethingWentWrong, successColor } from '../commandUtils.ts'; import { dbClient, queries } from '../db.ts'; import { CommandDetails } from '../types/commandTypes.ts'; import utils from '../utils.ts'; @@ -22,26 +22,27 @@ const details: CommandDetails = { }; const execute = (bot: Bot, interaction: Interaction) => { - console.log(interaction); - dbClient.execute(queries.callIncCnt('report')).catch((e) => utils.commonLoggers.dbError('report.ts', 'call sproc INC_CNT on', e)); - sendMessage(bot, config.reportChannel, generateReport(interaction.data?.options?.[0].value as string || 'Missing Options')).catch((e: Error) => - utils.commonLoggers.interactionSendError('report.ts:28', interaction, e) - ); - bot.helpers.sendInteractionResponse( - interaction.id, - interaction.token, - { - type: InteractionResponseTypes.ChannelMessageWithSource, - data: { - flags: isLFGChannel(interaction.channelId || 0n), - embeds: [{ - color: successColor, - title: 'Failed command has been reported to my developer.', - description: `For more in depth support, and information about planned maintenance, please join the support server [here](${config.links.supportServer}).`, - }], + dbClient.execute(queries.callIncCnt('cmd-report')).catch((e) => utils.commonLoggers.dbError('report.ts', 'call sproc INC_CNT on', e)); + if (interaction.data?.options?.[0].value) { + sendMessage(bot, config.reportChannel, generateReport(interaction.data.options[0].value as string)).catch((e: Error) => utils.commonLoggers.interactionSendError('report.ts:28', interaction, e)); + bot.helpers.sendInteractionResponse( + interaction.id, + interaction.token, + { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: isLFGChannel(interaction.guildId || 0n, interaction.channelId || 0n), + embeds: [{ + color: successColor, + title: 'Failed command has been reported to my developer.', + description: `For more in depth support, and information about planned maintenance, please join the support server [here](${config.links.supportServer}).`, + }], + }, }, - }, - ).catch((e: Error) => utils.commonLoggers.interactionSendError('report.ts:44', interaction, e)); + ).catch((e: Error) => utils.commonLoggers.interactionSendError('report.ts:44', interaction, e)); + } else { + somethingWentWrong(bot, interaction, 'reportMissingAllOptions'); + } }; export default { diff --git a/src/commands/setup.ts b/src/commands/setup.ts new file mode 100644 index 0000000..5e065ca --- /dev/null +++ b/src/commands/setup.ts @@ -0,0 +1,321 @@ +import config from '../../config.ts'; +import { ApplicationCommandFlags, ApplicationCommandOptionTypes, ApplicationCommandTypes, ButtonStyles, Bot, ChannelTypes, Interaction, InteractionResponseTypes, sendMessage, OverwriteTypes, botId, MessageComponentTypes, DiscordEmbedField } from '../../deps.ts'; +import { failColor, infoColor2, somethingWentWrong, successColor } from '../commandUtils.ts'; +import { dbClient, queries, lfgChannelSettings } from '../db.ts'; +import { CommandDetails } from '../types/commandTypes.ts'; +import utils from '../utils.ts'; + +const withoutMgrRole = 'without-manager-role'; +const withMgrRole = 'with-manager-role'; +const managerRoleStr = 'manager-role'; +const logChannelStr = 'log-channel'; + +const details: CommandDetails = { + name: 'setup', + description: `Configures this channel to be a dedicated event channel to be managed by ${config.name}.`, + type: ApplicationCommandTypes.ChatInput, + defaultMemberPermissions: ['ADMINISTRATOR'], + options: [ + { + name: withoutMgrRole, + type: ApplicationCommandOptionTypes.SubCommand, + description: `This will configure ${config.name} without a manager role.`, + }, + { + name: withMgrRole, + type: ApplicationCommandOptionTypes.SubCommand, + description: `This will configure ${config.name} with a manager role.`, + options: [ + { + name: managerRoleStr, + type: ApplicationCommandOptionTypes.Role, + description: 'This role will be allowed to manage all events in this guild.', + required: true, + }, + { + name: logChannelStr, + type: ApplicationCommandOptionTypes.Channel, + description: `This channel is where ${config.name} will send Audit Messages whenever a manager updates an event.`, + required: true, + channelTypes: [ChannelTypes.GuildText], + }, + ], + }, + ], +}; + +const execute = async (bot: Bot, interaction: Interaction) => { + dbClient.execute(queries.callIncCnt('cmd-setup')).catch((e) => utils.commonLoggers.dbError('setup.ts', 'call sproc INC_CNT on', e)); + + const setupOpts = interaction.data?.options?.[0]; + + if (setupOpts && setupOpts.name && interaction.channelId && interaction.guildId) { + if (lfgChannelSettings.has(`${interaction.guildId}-${interaction.channelId}`)) { + // Cannot setup a lfg channel that is already set up + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Unable to setup LFG channel.', + description: 'This channel is already set as an LFG channel. If you need to edit the channel, please run `/delete lfg-channel` in this channel and then run `/setup` again.\n\nThis will not harm any active events in this channel and simply resets the settings for this channel.', + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + return; + } + + const messages = await bot.helpers.getMessages(interaction.channelId, { limit: 100 }); + if (messages.size < 100) { + let logChannelId = 0n; + let managerRoleId = 0n; + let logChannelErrorOut = false; + let mgrRoleErrorOut = false; + const introFields: Array = [{ + name: 'Editing/Deleting your event:', + value: 'To edit or delete your event, simply click on the ✏️ or 🗑️ buttons respectively.', + }]; + const permissionFields: Array = [ + { + name: `Please make sure ${config.name} has the following permissions:`, + value: '`MANAGE_GUILD`\n`MANAGE_CHANNELS`\n`MANAGE_ROLES`\n`MANAGE_MESSAGES`\n\nThe only permission that is required after setup completes is `MANAGE_MESSAGES`.', + }, + ]; + if (setupOpts.name === withMgrRole) { + introFields.push({ + name: `${config.name} Manager Details:`, + value: `${config.name} Managers with the <@&${managerRoleId}> role may edit or delete events in this guild, along with using the following commands to update the activity members: + +\`/join\` +\`/leave\` +\`/alternate\` + +The Discord Slash Command system will ensure you provide all the required details.`, + }) + if (setupOpts.options?.length) { + setupOpts.options.forEach(opt => { + if (opt.name === managerRoleStr) { + managerRoleId = BigInt(opt.value as string || '0'); + } else if (opt.name === logChannelStr) { + logChannelId = BigInt(opt.value as string || '0'); + } + }); + + if (logChannelId === 0n || managerRoleId === 0n) { + // One or both Ids did not get parsed + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Unable to setup log channel or manager role.', + description: `${config.name} attempted to set the log channel or manager role, but one or both were undefined. Please try again and if the issue continues, \`/report\` this issue to the developers with the error code below.`, + fields: [{ + name: 'Error Code:', + value: `setupLog${logChannelId}Mgr${managerRoleId}`, + }], + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + return; + } + } else { + // Discord broke? + somethingWentWrong(bot, interaction, 'setupMissingRoleMgrOptions'); + return; + } + + // Test sending a message to the logChannel + await sendMessage(bot, logChannelId, { + embeds: [{ + title: `This is the channel ${config.name} will be logging events to.`, + description: `${config.name} will only send messages here as frequently as your event managers update events.`, + color: infoColor2, + }] + }).catch((e: Error) => { + utils.commonLoggers.messageSendError('setup.ts', 'log-test', e); + logChannelErrorOut = true; + }); + if (logChannelErrorOut) { + // Cannot send message into log channel, error out + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Unable to setup log channel.', + description: `${config.name} attempted to send a message to the specified log channel.`, + fields: [ + { + name: `Please allow ${config.name} to send messages in the requested channel.`, + value: `${config.name}`, + }, + ], + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + return; + } + + // Set permissions for managerId + await bot.helpers.editChannelPermissionOverrides(interaction.channelId, { + id: managerRoleId, + type: OverwriteTypes.Role, + allow: ['SEND_MESSAGES'], + }).catch((e: Error) => { + utils.commonLoggers.channelUpdateError('setup.ts', 'manager-allow', e); + mgrRoleErrorOut = true; + }); + } + + // Set permissions for everyone, skip if we already failed to set roles + !mgrRoleErrorOut && await bot.helpers.editChannelPermissionOverrides(interaction.channelId, { + id: interaction.guildId, + type: OverwriteTypes.Role, + deny: ['SEND_MESSAGES'], + }).catch((e: Error) => { + utils.commonLoggers.channelUpdateError('setup.ts', 'everyone-deny', e); + mgrRoleErrorOut = true; + }); + + if (mgrRoleErrorOut) { + // Cannot update role overrides on channel, error out + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Unable to set lfg channel permissions.', + description: `${config.name} attempted to update the permissions for the current channel, but could not.`, + fields: permissionFields, + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + return; + } + + // Delete all messages that are not LFG posts + const msgsToDel: Array = []; + const oldLfgMsgs: Array = [] + messages.forEach(msg => { + if (msg.authorId === botId && msg.embeds.length && msg.embeds[0].footer && msg.embeds[0].footer.text.includes('Created by:')) { + oldLfgMsgs.push(msg.id); + } else { + msgsToDel.push(msg.id); + } + }); + if (msgsToDel.length) { + await bot.helpers.deleteMessages(interaction.channelId, msgsToDel, 'Cleaning LFG Channel').catch((e: Error) => utils.commonLoggers.messageDeleteError('setup.ts', 'bulk-msg-cleanup', e)); + } + + // Retrofit all old LFG posts that we found + if (oldLfgMsgs.length) { + // TODO: Retrofit old LFG posts, should delete ones that have already passed, should begin watching these events + } + + // Store the ids to the db + let dbErrorOut = false; + await dbClient.execute('INSERT INTO guild_settings(guildId,lfgChannelId,managerRoleId,logChannelId) values(?,?,?,?)', [interaction.guildId, interaction.channelId, managerRoleId, logChannelId]).catch((e) => { + utils.commonLoggers.dbError('setup.ts', 'insert into guild_settings', e); + dbErrorOut = true; + }); + if (dbErrorOut) { + // DB died? + somethingWentWrong(bot, interaction, 'setupDBInsertFailed'); + return; + } + // Store the ids to the active map + lfgChannelSettings.set(`${interaction.guildId}-${interaction.channelId}`, { + managed: setupOpts.name === withMgrRole, + managerRoleId, + logChannelId, + }); + + // Send the initial introduction message + const createNewEventBtn = 'Create New Event'; + const introMsg = await sendMessage(bot, interaction.channelId, { + content: `Welcome to <#${interaction.channelId}>, managed by <@${botId}>!`, + embeds: [{ + title: `To get started, click on the '${createNewEventBtn}' button below!`, + color: successColor, + fields: introFields, + }], + components: [{ + type: MessageComponentTypes.ActionRow, + components: [{ + type: MessageComponentTypes.Button, + label: createNewEventBtn, + customId: 'temp', // TODO: set this + style: ButtonStyles.Success, + }], + }], + }).catch((e: Error) => utils.commonLoggers.messageSendError('setup.ts', 'init-msg', e)); + + if (introMsg) { + bot.helpers.pinMessage(interaction.channelId, introMsg.id).catch((e: Error) => utils.commonLoggers.messageSendError('setup.ts', 'pin-init-msg', e)); + // Complete the interaction + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: successColor, + title: 'LFG Channel setup complete!', + description: `${config.name} has finished setting up this channel. You may safely dismiss this message.`, + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + } else { + // Could not send initial message + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Failed to send the initial message!', + fields: permissionFields, + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + } + } else { + // Too many messages to delete, give up + bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { + type: InteractionResponseTypes.ChannelMessageWithSource, + data: { + flags: ApplicationCommandFlags.Ephemeral, + embeds: [{ + color: failColor, + title: 'Unable to setup LFG channel.', + description: `${config.name} attempted to clean this channel, but encountered too many messages (100 or more). There are two ways to move forward:`, + fields: [ + { + name: 'Is this channel a dedicated LFG Channel?', + value: 'You either need to manually clean this channel or create a brand new channel for events.', + inline: true, + }, + { + name: 'Is this a chat channel that you want events mixed into?', + value: 'You do not need to run the `/setup` command, and instead should use the `/lfg create` command.', + inline: true, + }, + ], + }], + }, + }).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e)); + } + } else { + // Discord fucked up? + somethingWentWrong(bot, interaction, 'setupMissingAllOptions'); + } +}; + +export default { + details, + execute, +}; diff --git a/src/db.ts b/src/db.ts index 94e0077..abfa900 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,6 +1,7 @@ import config from '../config.ts'; import { Client } from '../deps.ts'; import { LOCALMODE } from '../flags.ts'; +import { LfgChannelSetting, DBGuildSettings } from './types/commandTypes.ts'; export const dbClient = await new Client().connect({ hostname: LOCALMODE ? config.db.localhost : config.db.host, @@ -14,4 +15,12 @@ export const queries = { callIncCnt: (cmdName: string) => `CALL INC_CNT("${cmdName}");`, }; -export const lfgChannels: Array = [1055568692697649232n]; +export const lfgChannelSettings: Map = new Map(); +const getGuildSettings = await dbClient.query('SELECT * FROM guild_settings'); +getGuildSettings.forEach((g: DBGuildSettings) => { + lfgChannelSettings.set(`${g.guildId}-${g.lfgChannelId}`, { + managed: g.managerRoleId === 0n && g.logChannelId === 0n, + managerRoleId: g.managerRoleId, + logChannelId: g.logChannelId, + }); +}); diff --git a/src/types/commandTypes.ts b/src/types/commandTypes.ts index 65024d4..e35a593 100644 --- a/src/types/commandTypes.ts +++ b/src/types/commandTypes.ts @@ -9,7 +9,20 @@ export type CommandDetails = { defaultMemberPermissions?: PermissionStrings[]; }; -export type Commands = { +export type Command = { details: CommandDetails; execute: Function; }; + +export type LfgChannelSetting = { + managed: boolean; + managerRoleId: bigint; + logChannelId: bigint; +}; + +export type DBGuildSettings = { + guildId: bigint; + lfgChannelId: bigint; + managerRoleId: bigint; + logChannelId: bigint; +}; diff --git a/src/utils.ts b/src/utils.ts index ac26a42..620cc00 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,10 +19,14 @@ const reactionAddError = (location: string, message: Message | string, err: Erro genericLogger(LT.ERROR, `${location} | Failed to add emoji (${emoji}) to message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); const reactionDeleteError = (location: string, message: Message | string, err: Error, emoji: string) => genericLogger(LT.ERROR, `${location} | Failed to delete emoji (${emoji}) from message: ${jsonStringifyBig(message)} | Error: ${err.name} - ${err.message}`); +const channelUpdateError = (location: string, message: string, err: Error) => + genericLogger(LT.ERROR, `${location} | Failed to update channel | ${message} | Error: ${err.name} - ${err.message}`); + const dbError = (location: string, type: string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to ${type} database | Error: ${err.name} - ${err.message}`); export default { commonLoggers: { + channelUpdateError, dbError, interactionSendError, messageGetError,