GroupUp/src/commands/setup.ts

407 lines
17 KiB
TypeScript

import config from '../../config.ts';
import {
ApplicationCommandFlags,
ApplicationCommandOptionTypes,
ApplicationCommandTypes,
Bot,
botId,
ButtonStyles,
ChannelTypes,
DiscordEmbedField,
Interaction,
InteractionResponseTypes,
LT, log,
MessageComponentTypes,
OverwriteTypes,
} from '../../deps.ts';
import { failColor, infoColor2, safelyDismissMsg, somethingWentWrong, successColor } from '../commandUtils.ts';
import { dbClient, generateGuildSettingKey, lfgChannelSettings, queries } from '../db.ts';
import { CommandDetails } from '../types/commandTypes.ts';
import utils from '../utils.ts';
import { customId as gameSelId } from '../buttons/event-creation/step1-gameSelection.ts';
import { alternateEventBtnStr, joinEventBtnStr, leaveEventBtnStr, LfgEmbedIndexes, requestToJoinEventBtnStr } from '../buttons/eventUtils.ts';
import { alternateName, eventLinkName, joinName, leaveName, userName } from './managerJLA.ts';
import { createEventSlashName, deleteSlashName, managerJLASlashName, reportSlashName, setupSlashName } from './slashCommandNames.ts';
import { generateLFGButtons, generateTimeFieldStr } from '../buttons/event-creation/utils.ts';
import { getLfgMembers } from '../buttons/live-event/utils.ts';
const withoutMgrRole = 'without-manager-role';
const withMgrRole = 'with-manager-role';
const managerRoleStr = 'manager-role';
const logChannelStr = 'log-channel';
const details: CommandDetails = {
name: setupSlashName,
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?.name && interaction.channelId && interaction.guildId) {
const lfgChannelSettingKey = generateGuildSettingKey(interaction.guildId, interaction.channelId);
if (lfgChannelSettings.has(lfgChannelSettingKey)) {
// 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 \`/${deleteSlashName}\` in this channel and then run \`/${setupSlashName}\` 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<DiscordEmbedField> = [{
name: 'Joining Events:',
value:
`To join an event, simply click on the \`${joinEventBtnStr}\` or \`${requestToJoinEventBtnStr}\` button. If you try to join a full event, you will be placed in the Alternates column with an \`*\` next to your name. Members with an \`*\` next to their name will automatically get promoted to the Joined list if someone leaves the event.`,
}, {
name: 'Leaving Events:',
value: `To leave an event, simply click on the \`${leaveEventBtnStr}\` button.`,
inline: true,
}, {
name: 'Joining Events as an Alternate:',
value: `To join as a backup or indicate you might be available, simply click on the \`${alternateEventBtnStr}\` button.`,
inline: true,
}, {
name: 'Editing/Deleting your event:',
value: 'To edit or delete your event, simply click on the ✏️ or 🗑️ buttons respectively.',
}];
const permissionFields: Array<DiscordEmbedField> = [
{
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) {
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, \`/${reportSlashName}\` 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;
}
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:
\`/${managerJLASlashName} [${joinName} | ${leaveName} | ${alternateName}] [${eventLinkName}] [${userName}]\`
The Discord Slash Command system will ensure you provide all the required details.`,
});
// Set permissions for self, skip if we already failed to set roles
!logChannelErrorOut && await bot.helpers.editChannelPermissionOverrides(logChannelId, {
id: botId,
type: OverwriteTypes.Member,
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL', 'EMBED_LINKS'],
}).catch((e: Error) => {
utils.commonLoggers.channelUpdateError('setup.ts', 'self-allow', e);
mgrRoleErrorOut = true;
});
// Test sending a message to the logChannel
!logChannelErrorOut && await bot.helpers.sendMessage(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: `<#${logChannelId}>`,
},
],
}],
},
}).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: ['VIEW_CHANNEL', '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,
allow: ['VIEW_CHANNEL'],
deny: ['SEND_MESSAGES'],
}).catch((e: Error) => {
utils.commonLoggers.channelUpdateError('setup.ts', 'everyone-deny', e);
mgrRoleErrorOut = true;
});
const x = await bot.helpers.getRoles(interaction.guildId);
x.forEach(role => log(LT.INFO, `${utils.jsonStringifyBig(role)}`))
// Set permissions for self, skip if we already failed to set roles
!mgrRoleErrorOut && await bot.helpers.editChannelPermissionOverrides(interaction.channelId, {
id: botId,
type: OverwriteTypes.Member,
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL', 'EMBED_LINKS'],
}).catch((e: Error) => {
utils.commonLoggers.channelUpdateError('setup.ts', 'self-allow', 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<bigint> = [];
const oldLfgMsgs: Array<bigint> = [];
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) {
for (const msgToDel of msgsToDel) {
await bot.helpers.deleteMessage(interaction.channelId, msgToDel, 'Initial LFG Channel Cleanup').catch((e: Error) =>
utils.commonLoggers.messageDeleteError('setup.ts', 'bulk-msg-cleanup', e)
);
}
}
// Retrofit all old LFG posts that we found
oldLfgMsgs.forEach((oldEventId) => {
const oldEvent = messages.get(oldEventId);
if (oldEvent && oldEvent.embeds[0].fields && oldEvent.embeds[0].footer) {
const eventMembers = [...getLfgMembers(oldEvent.embeds[0].fields[LfgEmbedIndexes.JoinedMembers].value), ...getLfgMembers(oldEvent.embeds[0].fields[LfgEmbedIndexes.AlternateMembers].value)];
const eventDateTime = new Date(parseInt((oldEvent.embeds[0].fields[LfgEmbedIndexes.StartTime].value.split('tz#')[1] || ' ').slice(0, -1)));
if (!isNaN(eventDateTime.getTime())) {
const eventDateTimeStr = (oldEvent.embeds[0].fields[LfgEmbedIndexes.StartTime].value.split('](')[0] || ' ').slice(1);
oldEvent.embeds[0].fields[LfgEmbedIndexes.StartTime].value = generateTimeFieldStr(eventDateTimeStr, eventDateTime);
oldEvent.embeds[0].footer.text = oldEvent.embeds[0].footer.text.split(' | ')[0];
const ownerName = oldEvent.embeds[0].footer.text.split(': ')[1];
const ownerId = eventMembers.find((member) => ownerName === member.name)?.id || 0n;
oldEvent.embeds[0].footer.iconUrl = `${config.links.creatorIcon}#${ownerId}`;
bot.helpers.editMessage(oldEvent.channelId, oldEvent.id, {
content: '',
embeds: [oldEvent.embeds[0]],
components: [{
type: MessageComponentTypes.ActionRow,
components: generateLFGButtons(false),
}],
}).catch((e: Error) => utils.commonLoggers.messageEditError('setup.ts', 'retrofit event', e));
dbClient.execute(queries.insertEvent, [oldEvent.id, oldEvent.channelId, interaction.guildId, ownerId, eventDateTime]).catch((e) =>
utils.commonLoggers.dbError('setup.ts@retrofit', 'INSERT event to DB', e)
);
}
}
});
// 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(lfgChannelSettingKey, {
managed: setupOpts.name === withMgrRole,
managerRoleId,
logChannelId,
});
// Send the initial introduction message
const createNewEventBtn = 'Create New Event';
const introMsg = await bot.helpers.sendMessage(interaction.channelId, {
content: `Welcome to <#${interaction.channelId}>, managed by <@${botId}>!`,
embeds: [{
title: `To get started, click on the '${createNewEventBtn}' button below!`,
color: infoColor2,
fields: introFields,
}],
components: [{
type: MessageComponentTypes.ActionRow,
components: [{
type: MessageComponentTypes.Button,
label: createNewEventBtn,
customId: gameSelId,
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. ${safelyDismissMsg}`,
}],
},
}).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 \`/${setupSlashName}\` command, and instead should use the \`/${createEventSlashName}\` 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,
};