Finish most of the create event

This commit is contained in:
Ean Milligan (Bastion) 2023-02-02 21:33:27 -05:00
parent d5f0b08e08
commit 8c6a3749df
9 changed files with 167 additions and 41 deletions

View File

@ -1,5 +1,6 @@
import { Button } from '../types/commandTypes.ts';
import { createEventButton } from './event-creation/step1-gameSelection.ts';
import { createCustomEventButton } from './event-creation/step1a-openCustomModal.ts';
import { verifyCustomEventButton } from './event-creation/step1b-verifyCustomActivity.ts';
export const buttons: Array<Button> = [createEventButton, createCustomEventButton];
export const buttons: Array<Button> = [createEventButton, createCustomEventButton, verifyCustomEventButton];

View File

@ -2,15 +2,12 @@ import { ActionRow, ApplicationCommandFlags, ApplicationCommandTypes, Bot, Butto
import { infoColor1, somethingWentWrong } from '../../commandUtils.ts';
import { CommandDetails } from '../../types/commandTypes.ts';
import { Activities } from './activities.ts';
import { deleteTokenEarly, generateActionRow, generateMapId, getNestedActivity, pathIdxEnder, pathIdxSeparator, tokenMap } from './utils.ts';
import { deleteTokenEarly, generateActionRow, generateMapId, getNestedActivity, pathIdxEnder, idSeparator, pathIdxSeparator, tokenMap, addTokenToMap, tokenTimeoutMS, selfDestructMessage } from './utils.ts';
import utils from '../../utils.ts';
import { customId as createCustomActivityBtnId } from './step1a-openCustomModal.ts';
export const customId = 'gameSel';
const slashCommandName = 'create-event';
// Discord Interaction Tokens last 15 minutes, we will self kill after 14.5 minutes
const tokenTimeoutS = (15 * 60) - 30;
const tokenTimeoutMS = tokenTimeoutS * 1000;
const details: CommandDetails = {
name: slashCommandName,
description: 'Creates a new event in this channel.',
@ -21,26 +18,27 @@ const customEventRow: ActionRow = {
type: MessageComponentTypes.ActionRow,
components: [{
type: MessageComponentTypes.Button,
style: ButtonStyles.Primary,
label: 'Create Custom Event',
customId: createCustomActivityBtnId,
style: ButtonStyles.Primary,
}],
};
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data && (interaction.data.name === slashCommandName || interaction.data.customId) && interaction.member && interaction.guildId && interaction.channelId) {
// Parse indexPath from the select value
const rawIdxPath: Array<string> = interaction.data.values ? interaction.data.values[0].split(pathIdxSeparator) : [''];
const idxPath: Array<number> = rawIdxPath.map((rawIdx) => rawIdx ? parseInt(rawIdx) : -1);
if (interaction.data.values && interaction.data.values[0] && interaction.data.values[0].endsWith(pathIdxEnder)) {
// Check if we are done
const customIdIdxPath = ((interaction.data.customId || '').substring((interaction.data.customId || '').indexOf(idSeparator) + 1) || '');
const valuesIdxPath = (interaction.data?.values?.[0] || '');
const strippedIdxPath = interaction.data.customId?.includes(idSeparator) ? customIdIdxPath : valuesIdxPath;
const finalizedIdxPath = strippedIdxPath.substring(0, strippedIdxPath.lastIndexOf(pathIdxEnder));
if ((interaction.data.customId?.includes(idSeparator) && interaction.data.customId.endsWith(pathIdxEnder)) || interaction.data?.values?.[0].endsWith(pathIdxEnder)) {
// User selected activity, give them the details modal and delete the selectMenus
await deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.Modal,
data: {
title: 'Enter Event Details',
customId: 'temp', //TODO: fix
customId: `temp${idSeparator}${finalizedIdxPath}`, //TODO: finish
components: [{
type: MessageComponentTypes.ActionRow,
components: [{
@ -48,6 +46,8 @@ const execute = async (bot: Bot, interaction: Interaction) => {
customId: 'eventTime',
label: 'Start Time:',
style: TextStyles.Short,
minLength: 1,
maxLength: 8,
}],
}, {
type: MessageComponentTypes.ActionRow,
@ -56,6 +56,8 @@ const execute = async (bot: Bot, interaction: Interaction) => {
customId: 'eventTimeZone',
label: 'Time Zone:',
style: TextStyles.Short,
minLength: 2,
maxLength: 8,
}],
}, {
type: MessageComponentTypes.ActionRow,
@ -64,6 +66,8 @@ const execute = async (bot: Bot, interaction: Interaction) => {
customId: 'eventDate',
label: 'Start Date:',
style: TextStyles.Short,
minLength: 1,
maxLength: 20,
}],
}, {
type: MessageComponentTypes.ActionRow,
@ -73,6 +77,9 @@ const execute = async (bot: Bot, interaction: Interaction) => {
label: 'Description:',
style: TextStyles.Paragraph,
required: false,
placeholder: finalizedIdxPath,
minLength: 0,
maxLength: 1000,
}],
}],
},
@ -80,6 +87,9 @@ const execute = async (bot: Bot, interaction: Interaction) => {
return;
}
// Parse indexPath from the select value
const rawIdxPath: Array<string> = interaction.data.values ? interaction.data.values[0].split(pathIdxSeparator) : [''];
const idxPath: Array<number> = rawIdxPath.map((rawIdx) => rawIdx ? parseInt(rawIdx) : -1);
const selectMenus: Array<ActionRow> = [];
let selectMenuCustomId = `${customId}$`;
let currentBaseValue = '';
@ -110,21 +120,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
await deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
// Store token for later use
tokenMap.set(generateMapId(interaction.guildId, interaction.channelId, interaction.member.id), {
token: interaction.token,
timeoutId: setTimeout(
(guildId, channelId, userId) => {
deleteTokenEarly(bot, interaction, guildId, channelId, userId);
},
tokenTimeoutMS,
interaction.guildId,
interaction.channelId,
interaction.member.id,
),
});
// Calculate destruction time
const destructTime = Math.floor((new Date().getTime() + tokenTimeoutMS) / 1000);
addTokenToMap(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
// Send initial interaction
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
@ -132,7 +128,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
data: {
embeds: [{
title: 'Please select a Game and Activity, or create a Custom Event.',
description: `Please note: This message will self destruct <t:${destructTime}:R> due to limits imposed by the Discord API.`,
description: selfDestructMessage(new Date().getTime()),
color: infoColor1,
}],
flags: ApplicationCommandFlags.Ephemeral,
@ -141,7 +137,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
}).catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:init', interaction, e));
}
} else {
somethingWentWrong;
somethingWentWrong(bot, interaction, 'missingCoreValuesOnGameSel');
}
};

View File

@ -1,28 +1,35 @@
import { Bot, Interaction, InteractionResponseTypes, MessageComponentTypes, TextStyles } from '../../../deps.ts';
import { deleteTokenEarly } from './utils.ts';
import { deleteTokenEarly, idSeparator, pathIdxSeparator } from './utils.ts';
import { customId as verifyCustomActivityId } from './step1b-verifyCustomActivity.ts';
import utils from '../../utils.ts';
export const customId = 'createCustomActivity';
export const customId = 'customAct';
export const activityTitleId = 'activityTitle';
export const activitySubtitleId = 'activitySubtitle';
export const activityMaxPlayersId = 'activityMaxPlayers';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.member && interaction.guildId && interaction.channelId) {
if (interaction.data?.customId && interaction.member && interaction.guildId && interaction.channelId) {
const [actTitle, actSubtitle, activityMaxPlayers] = (interaction.data.customId.split(idSeparator)[1] || '').split(pathIdxSeparator);
await deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.Modal,
data: {
title: 'Create Custom Activity',
customId: 'temp', //TODO: fix
customId: verifyCustomActivityId,
components: [{
type: MessageComponentTypes.ActionRow,
components: [{
type: MessageComponentTypes.InputText,
customId: activityTitleId,
label: 'Activity Title:',
placeholder: 'The name of the game or event.',
style: TextStyles.Short,
minLength: 1,
maxLength: 35,
value: actTitle || undefined,
}],
}, {
type: MessageComponentTypes.ActionRow,
@ -30,15 +37,23 @@ const execute = async (bot: Bot, interaction: Interaction) => {
type: MessageComponentTypes.InputText,
customId: activitySubtitleId,
label: 'Activity Subtitle:',
placeholder: 'The specific activity within the game or event.',
style: TextStyles.Short,
minLength: 1,
maxLength: 50,
value: actSubtitle || undefined,
}],
}, {
type: MessageComponentTypes.ActionRow,
components: [{
type: MessageComponentTypes.InputText,
customId: activityMaxPlayersId,
label: 'Max Players:',
label: 'Maximum Players:',
placeholder: 'Please enter a number between 1 and 99.',
style: TextStyles.Short,
minLength: 1,
maxLength: 2,
value: activityMaxPlayers || undefined,
}],
}],
},

View File

@ -0,0 +1,92 @@
import config from '../../../config.ts';
import { Bot, Interaction, InteractionResponseTypes, MessageComponentTypes, ButtonStyles, ApplicationCommandFlags } from '../../../deps.ts';
import { infoColor1, somethingWentWrong, failColor, safelyDismissMsg } from '../../commandUtils.ts';
import { addTokenToMap, idSeparator, pathIdxSeparator, pathIdxEnder, selfDestructMessage } from './utils.ts';
import { activityTitleId, activitySubtitleId, activityMaxPlayersId } from './step1a-openCustomModal.ts';
import { customId as gameSelectionId } from './step1-gameSelection.ts';
import { customId as openCustomModalId } from './step1a-openCustomModal.ts';
import utils from '../../utils.ts';
export const customId = 'verifyCustomActivity';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction?.data?.components?.length && interaction.guildId && interaction.channelId && interaction.member) {
// Parse out our data
const tempDataMap: Map<string, string> = new Map();
for (const row of interaction.data.components) {
if (row.components?.[0]) {
const textField = row.components[0];
tempDataMap.set(textField.customId || 'missingCustomId', textField.value || 'missingValue');
}
}
// Remove any pipe characters to avoid issues down the process
const activityTitle = (tempDataMap.get(activityTitleId) || '').replace(/\|/g, '');
const activitySubtitle = (tempDataMap.get(activitySubtitleId) || '').replace(/\|/g, '');
const activityMaxPlayers = parseInt(tempDataMap.get(activityMaxPlayersId) || '0');
if (!activityMaxPlayers || !activitySubtitle || !activityTitle) {
// Verify fields exist
somethingWentWrong(bot, interaction, `missingFieldFromCustomActivity@${activityTitle}|${activitySubtitle}|${activityMaxPlayers}$`);
return;
}
if (activityMaxPlayers < 1 || activityMaxPlayers > 99) {
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
embeds: [{
color: failColor,
title: 'Invalid Max Member count!',
description: `${config.name} parsed the max members as ${activityMaxPlayers}, which is outside of the allowed range. Please recreate this activity, but make sure the maximum player count is between 1 and 99.\n\n${safelyDismissMsg}`
}],
}
}).catch((e: Error) => utils.commonLoggers.interactionSendError('step1b-verifyCustomActivity.ts:invalidPlayer', interaction, e));
return;
}
addTokenToMap(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
const idxPath = `${idSeparator}${activityTitle}${pathIdxSeparator}${activitySubtitle}${pathIdxSeparator}${activityMaxPlayers}`;
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
embeds: [{
color: infoColor1,
title: 'Please verify the following Custom Event details:',
description: `Please note, pipe characters (\`|\`) are not allowed and will be automatically removed.\n\n${selfDestructMessage(new Date().getTime())}`,
fields: [{
name: 'Activity Title:',
value: activityTitle,
}, {
name: 'Activity Subtitle:',
value: activitySubtitle,
}, {
name: 'Maximum Players:',
value: `${activityMaxPlayers}`,
}],
}],
components: [{
type: MessageComponentTypes.ActionRow,
components: [{
type: MessageComponentTypes.Button,
style: ButtonStyles.Success,
label: 'Yup, looks great!',
customId: `${gameSelectionId}${idxPath}${pathIdxEnder}`,
},{
type: MessageComponentTypes.Button,
style: ButtonStyles.Danger,
label: 'Nope, let me change something.',
customId: `${openCustomModalId}${idxPath}`,
}]
}]
}
}).catch((e: Error) => utils.commonLoggers.interactionSendError('step1b-verifyCustomActivity.ts:message', interaction, e));
} else {
somethingWentWrong(bot, interaction, 'noDataFromCustomActivityModal');
}
};
export const verifyCustomEventButton = {
customId,
execute,
};

View File

@ -2,8 +2,13 @@ import { Activity } from './activities.ts';
import { ActionRow, Bot, Interaction, MessageComponentTypes, SelectOption } from '../../../deps.ts';
import utils from '../../utils.ts';
// Discord Interaction Tokens last 15 minutes, we will self kill after 14.5 minutes
const tokenTimeoutS = (15 * 60) - 30;
export const tokenTimeoutMS = tokenTimeoutS * 1000;
export const idSeparator = '@';
export const pathIdxSeparator = '|';
export const pathIdxEnder = '&';
export const selfDestructMessage = (currentTime: number) => `**Please note:** This message will self destruct <t:${Math.floor((currentTime + tokenTimeoutMS) / 1000)}:R> due to limits imposed by the Discord API.`
export const tokenMap: Map<string, {
token: string;
@ -38,6 +43,19 @@ export const generateActionRow = (baseValue: string, activities: Array<Activity>
export const generateMapId = (guildId: bigint, channelId: bigint, userId: bigint) => `${guildId}-${channelId}-${userId}`;
export const addTokenToMap = (bot: Bot, interaction: Interaction, guildId: bigint, channelId: bigint, userId: bigint) => tokenMap.set(generateMapId(guildId, channelId, userId), {
token: interaction.token,
timeoutId: setTimeout(
(guildId, channelId, userId) => {
deleteTokenEarly(bot, interaction, guildId, channelId, userId);
},
tokenTimeoutMS,
guildId,
channelId,
userId,
),
});
export const deleteTokenEarly = async (bot: Bot, interaction: Interaction, guildId: bigint, channelId: bigint, userId: bigint) => {
const tokenMapEntry = tokenMap.get(generateMapId(guildId, channelId, userId));
if (tokenMapEntry && tokenMapEntry.token) {

View File

@ -9,6 +9,8 @@ export const successColor = 0x0f8108;
export const infoColor1 = 0x313bf9;
export const infoColor2 = 0x6805e9;
export const safelyDismissMsg = 'You may safely dismiss this message.'
export const getRandomStatus = (guildCount: number): string => {
const statuses = [
`Running V${config.version}`,

View File

@ -1,6 +1,6 @@
import config from '../../config.ts';
import { ApplicationCommandFlags, ApplicationCommandTypes, Bot, Interaction, InteractionResponseTypes } from '../../deps.ts';
import { failColor, somethingWentWrong, successColor } from '../commandUtils.ts';
import { failColor, somethingWentWrong, successColor, safelyDismissMsg } from '../commandUtils.ts';
import { dbClient, lfgChannelSettings, queries } from '../db.ts';
import { CommandDetails } from '../types/commandTypes.ts';
import utils from '../utils.ts';
@ -53,7 +53,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
embeds: [{
color: successColor,
title: 'LFG Channel settings removed!',
description: `${config.name} has finished removing the settings for this channel. You may safely dismiss this message.`,
description: `${config.name} has finished removing the settings for this channel. ${safelyDismissMsg}`,
}],
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('delete.ts', interaction, e));

View File

@ -14,7 +14,7 @@ import {
OverwriteTypes,
sendMessage,
} from '../../deps.ts';
import { failColor, infoColor2, somethingWentWrong, successColor } from '../commandUtils.ts';
import { failColor, infoColor2, somethingWentWrong, successColor, safelyDismissMsg } from '../commandUtils.ts';
import { dbClient, lfgChannelSettings, queries } from '../db.ts';
import { CommandDetails } from '../types/commandTypes.ts';
import utils from '../utils.ts';
@ -64,7 +64,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
const setupOpts = interaction.data?.options?.[0];
if (setupOpts && setupOpts.name && interaction.channelId && interaction.guildId) {
if (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, {
@ -283,7 +283,7 @@ The Discord Slash Command system will ensure you provide all the required detail
embeds: [{
color: successColor,
title: 'LFG Channel setup complete!',
description: `${config.name} has finished setting up this channel. You may safely dismiss this message.`,
description: `${config.name} has finished setting up this channel. ${safelyDismissMsg}`,
}],
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('setup.ts', interaction, e));

View File

@ -1,6 +1,7 @@
import { Bot, BotWithCache, Interaction } from '../../deps.ts';
import { buttons } from '../buttons/_index.ts';
import { commands } from '../commands/_index.ts';
import { idSeparator } from '../buttons/event-creation/utils.ts'
const commandNames: Array<string> = commands.map((command) => command.details.name);
const buttonNames: Array<string> = buttons.map((button) => button.customId);
@ -14,13 +15,14 @@ export const interactionCreate = (rawBot: Bot, interaction: Interaction) => {
return;
}
const customId = interaction.data.customId ? interaction.data.customId.replace(/\$/g, '') : '';
const tempCustomId = interaction.data.customId ? interaction.data.customId.replace(/\$/g, '') : '';
const customId = tempCustomId.split(idSeparator)[0] || '';
if (customId && buttonNames.includes(customId)) {
const btnIdx = buttonNames.indexOf(customId);
buttons[btnIdx].execute(bot, interaction);
return;
}
console.log('interaction NOT HANDLED');
console.log('interaction NOT HANDLED', interaction);
}
};