Finalize event creation

Add active_event db table
Rename gameSelection button to gameSelection for consistency
Fix prefill for DateTime modal to correctly parse time and to not misparse other embeds
Add autoCleanup to finalize step
Add whole createEvent step
Code usability updates to event-creation utils
This commit is contained in:
Ean Milligan (Bastion) 2023-04-06 23:23:52 -04:00
parent be4caa1fb1
commit e609654761
8 changed files with 116 additions and 22 deletions

View File

@ -13,6 +13,7 @@ console.log('Attempt to drop all tables');
await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_CNT;`); 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 command_cnt;`);
await dbClient.execute(`DROP TABLE IF EXISTS guild_settings;`); await dbClient.execute(`DROP TABLE IF EXISTS guild_settings;`);
await dbClient.execute(`DROP TABLE IF EXISTS active_event;`);
console.log('Tables dropped'); console.log('Tables dropped');
console.log('Attempting to create table command_cnt'); console.log('Attempting to create table command_cnt');
@ -51,5 +52,20 @@ await dbClient.execute(`
`); `);
console.log('Table created'); console.log('Table created');
console.log('Attempting to create table active_event');
await dbClient.execute(`
CREATE TABLE active_event (
messageId bigint unsigned NOT NULL,
channelId bigint unsigned NOT NULL,
guildId bigint unsigned NOT NULL,
ownerId bigint unsigned NOT NULL,
eventTime timestamp NOT NULL,
notifiedFlag tinyint(1) NOT NULL DEFAULT 0,
lockedFlag tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (messageId, channelId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`);
console.log('Table created');
await dbClient.close(); await dbClient.close();
console.log('Done!'); console.log('Done!');

View File

@ -1,7 +1,8 @@
import { Button } from '../types/commandTypes.ts'; import { Button } from '../types/commandTypes.ts';
import { createEventButton } from './event-creation/step1-gameSelection.ts'; import { gameSelectionButton } from './event-creation/step1-gameSelection.ts';
import { createCustomEventButton } from './event-creation/step1a-openCustomModal.ts'; import { createCustomEventButton } from './event-creation/step1a-openCustomModal.ts';
import { verifyCustomEventButton } from './event-creation/step1b-verifyCustomActivity.ts'; import { verifyCustomEventButton } from './event-creation/step1b-verifyCustomActivity.ts';
import { finalizeEventButton } from './event-creation/step2-finalize.ts'; import { finalizeEventButton } from './event-creation/step2-finalize.ts';
import { createEventButton } from './event-creation/step3-createEvent.ts';
export const buttons: Array<Button> = [createEventButton, createCustomEventButton, verifyCustomEventButton, finalizeEventButton]; export const buttons: Array<Button> = [gameSelectionButton, createCustomEventButton, verifyCustomEventButton, finalizeEventButton, createEventButton];

View File

@ -14,6 +14,7 @@ import {
pathIdxSeparator, pathIdxSeparator,
selfDestructMessage, selfDestructMessage,
tokenMap, tokenMap,
lfgStartTimeName,
} from './utils.ts'; } from './utils.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
import { customId as createCustomActivityBtnId } from './step1a-openCustomModal.ts'; import { customId as createCustomActivityBtnId } from './step1a-openCustomModal.ts';
@ -57,10 +58,10 @@ const execute = async (bot: Bot, interaction: Interaction) => {
let prefillTimeZone = ''; let prefillTimeZone = '';
let prefillDate = ''; let prefillDate = '';
let prefillDescription = ''; let prefillDescription = '';
if (interaction.message && interaction.message.embeds[0].fields) { if (interaction.message && interaction.message.embeds[0].fields && interaction.message.embeds[0].fields[LfgEmbedIndexes.StartTime].name === lfgStartTimeName) {
let rawEventDateTime = interaction.message.embeds[0].fields[LfgEmbedIndexes.StartTime].value.split('\n')[0].split(' '); let rawEventDateTime = interaction.message.embeds[0].fields[LfgEmbedIndexes.StartTime].value.split('\n')[0].split(' ');
const monthIdx = rawEventDateTime.findIndex((item) => monthsShort.includes(item.toUpperCase())); const monthIdx = rawEventDateTime.findIndex((item) => monthsShort.includes(item.toUpperCase()));
prefillTime = rawEventDateTime.slice(0, monthIdx - 2).join(' ').trim(); prefillTime = rawEventDateTime.slice(0, monthIdx - 1).join(' ').trim();
prefillTimeZone = rawEventDateTime[monthIdx - 1].trim(); prefillTimeZone = rawEventDateTime[monthIdx - 1].trim();
prefillDate = rawEventDateTime.slice(monthIdx).join(' ').trim(); prefillDate = rawEventDateTime.slice(monthIdx).join(' ').trim();
prefillDescription = interaction.message.embeds[0].fields[LfgEmbedIndexes.Description].value.trim(); prefillDescription = interaction.message.embeds[0].fields[LfgEmbedIndexes.Description].value.trim();
@ -180,12 +181,12 @@ const execute = async (bot: Bot, interaction: Interaction) => {
} }
}; };
export const createEventCommand = { export const gameSelectionCommand = {
details, details,
execute, execute,
}; };
export const createEventButton = { export const gameSelectionButton = {
customId, customId,
execute, execute,
}; };

View File

@ -1,7 +1,7 @@
import { Bot, Interaction, InteractionResponseTypes, MessageComponentTypes, TextStyles } from '../../../deps.ts'; import { Bot, Interaction } from '../../../deps.ts';
import { somethingWentWrong } from '../../commandUtils.ts'; import { somethingWentWrong } from '../../commandUtils.ts';
import { eventDateId, eventDescriptionId, eventTimeId, eventTimeZoneId } from './step1-gameSelection.ts'; import { eventDateId, eventDescriptionId, eventTimeId, eventTimeZoneId } from './step1-gameSelection.ts';
import { createLFGPost, getFinalActivity, idSeparator, pathIdxSeparator } from './utils.ts'; import { createLFGPost, getFinalActivity, idSeparator, pathIdxSeparator, addTokenToMap } from './utils.ts';
import { Activities, Activity } from './activities.ts'; import { Activities, Activity } from './activities.ts';
import { getDateFromRawInput } from './dateTimeUtils.ts'; import { getDateFromRawInput } from './dateTimeUtils.ts';
@ -53,13 +53,27 @@ const execute = async (bot: Bot, interaction: Interaction) => {
// Get Date Object from user input // Get Date Object from user input
const [eventDateTime, eventDateTimeStr] = getDateFromRawInput(rawEventTime, rawEventTimeZone, rawEventDate); const [eventDateTime, eventDateTimeStr] = getDateFromRawInput(rawEventTime, rawEventTimeZone, rawEventDate);
addTokenToMap(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
bot.helpers.sendInteractionResponse( bot.helpers.sendInteractionResponse(
interaction.id, interaction.id,
interaction.token, interaction.token,
createLFGPost(category, activity, eventDateTime, eventDateTimeStr, eventDescription, interaction.member.id, interaction.member.user.username, [], [], customIdIdxPath, true), createLFGPost(
category,
activity,
eventDateTime,
eventDateTimeStr,
eventDescription,
interaction.member.id,
interaction.member.user.username,
[{
id: interaction.member.id,
name: interaction.member.user.username,
}],
[],
customIdIdxPath,
true,
),
); );
// somethingWentWrong(bot, interaction, `TESTING@${rawEventTime}_${rawEventTimeZone}_${rawEventDate}`);
} else { } else {
somethingWentWrong(bot, interaction, 'noDataFromEventDescriptionModal'); somethingWentWrong(bot, interaction, 'noDataFromEventDescriptionModal');
} }

View File

@ -0,0 +1,55 @@
import { Bot, Interaction, InteractionResponseTypes, MessageComponentTypes } from '../../../deps.ts';
import { deleteTokenEarly, LfgEmbedIndexes, generateLFGButtons, idSeparator } from './utils.ts';
import { somethingWentWrong } from '../../commandUtils.ts';
import { dbClient, queries } from '../../db.ts';
import utils from '../../utils.ts';
export const customId = 'createEvent';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.customId && interaction.member && interaction.guildId && interaction.channelId && interaction.message && interaction.message.embeds[0] && interaction.message.embeds[0].fields) {
deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
// Get OwnerId and EventTime from embed for DB
const ownerId: bigint = BigInt(interaction.message.embeds[0].footer?.iconUrl?.split('#')[1] || '0');
const eventTime: Date = new Date(parseInt(interaction.message.embeds[0].fields[LfgEmbedIndexes.ICSLink].value.split('?t=')[1].split('&n=')[0] || '0'));
// Send Event Message
const eventMessage = await bot.helpers.sendMessage(interaction.channelId, {
embeds: [interaction.message.embeds[0]],
components: [{
type: MessageComponentTypes.ActionRow,
components: generateLFGButtons(interaction.data.customId.includes(idSeparator)),
}]
}).catch((e: Error) => utils.commonLoggers.messageSendError('step3-createEvent.ts', 'createEvent', e));
if (!eventMessage) {
somethingWentWrong(bot, interaction, 'creatingEventSendMessageFinalizeEventStep');
return;
}
// Store in DB
let dbErrorOut = false;
await dbClient.execute(queries.insertEvent, [eventMessage.id, eventMessage.channelId, interaction.guildId, ownerId, eventTime]).catch((e) => {
utils.commonLoggers.dbError('step3-createEvent.ts', 'INSERT event to DB', e);
dbErrorOut = true;
});
if (dbErrorOut) {
bot.helpers.deleteMessage(eventMessage.channelId, eventMessage.id, 'Failed to log event to DB').catch((e: Error) => utils.commonLoggers.messageDeleteError('step3-createEvent.ts', 'deleteEventFailedDB', e));
somethingWentWrong(bot, interaction, 'creatingEventDBStoreFinalizeEventStep');
return;
}
// Let discord know we didn't ignore the user
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
});
} else {
somethingWentWrong(bot, interaction, 'noDataFromFinalizeEventStep');
}
};
export const createEventButton = {
customId,
execute,
};

View File

@ -16,6 +16,7 @@ import utils from '../../utils.ts';
import { successColor } from '../../commandUtils.ts'; import { successColor } from '../../commandUtils.ts';
import { LFGMember } from '../../types/commandTypes.ts'; import { LFGMember } from '../../types/commandTypes.ts';
import { customId as gameSelCustomId } from './step1-gameSelection.ts'; import { customId as gameSelCustomId } from './step1-gameSelection.ts';
import { customId as createEventCustomId } from './step3-createEvent.ts';
// Discord Interaction Tokens last 15 minutes, we will self kill after 14.5 minutes // Discord Interaction Tokens last 15 minutes, we will self kill after 14.5 minutes
const tokenTimeoutS = (15 * 60) - 30; const tokenTimeoutS = (15 * 60) - 30;
@ -90,12 +91,12 @@ const finalizeButtons = (idxPath: string): [ButtonComponent, ButtonComponent, Bu
type: MessageComponentTypes.Button, type: MessageComponentTypes.Button,
label: 'Create Event', label: 'Create Event',
style: ButtonStyles.Success, style: ButtonStyles.Success,
customId: 'createEvent', // TODO: replace with proper id customId: createEventCustomId,
}, { }, {
type: MessageComponentTypes.Button, type: MessageComponentTypes.Button,
label: 'Create Whitelisted Event', label: 'Create Whitelisted Event',
style: ButtonStyles.Primary, style: ButtonStyles.Primary,
customId: `createEvent${idSeparator}`, // TODO: replace with proper id customId: `${createEventCustomId}${idSeparator}`,
}, { }, {
type: MessageComponentTypes.Button, type: MessageComponentTypes.Button,
label: 'Edit Event Details', label: 'Edit Event Details',
@ -136,6 +137,10 @@ export const generateLFGButtons = (whitelist: boolean): [ButtonComponent, Button
}, },
}]; }];
const generateMemberTitle = (memberList: Array<LFGMember>, maxMembers: number): string => `Members Joined: ${memberList.length}/${maxMembers}`;
const generateMemberList = (memberList: Array<LFGMember>): string => memberList.length ? memberList.map((member) => `${member.name} - <@${member.id}>`).join('\n') : 'None';
const generateAlternateList = (alternateList: Array<LFGMember>): string => alternateList.length ? alternateList.map((member) => `${member.name} - <@${member.id}>${member.joined ? ' *' : ''}`).join('\n') : 'None';
export enum LfgEmbedIndexes { export enum LfgEmbedIndexes {
Activity, Activity,
StartTime, StartTime,
@ -143,7 +148,8 @@ export enum LfgEmbedIndexes {
Description, Description,
JoinedMembers, JoinedMembers,
AlternateMembers, AlternateMembers,
} };
export const lfgStartTimeName = 'Start Time:';
export const createLFGPost = ( export const createLFGPost = (
category: string, category: string,
activity: Activity, activity: Activity,
@ -162,8 +168,8 @@ export const createLFGPost = (
return { return {
type: InteractionResponseTypes.ChannelMessageWithSource, type: InteractionResponseTypes.ChannelMessageWithSource,
data: { data: {
flags: ApplicationCommandFlags.Ephemeral, flags: editing ? ApplicationCommandFlags.Ephemeral : undefined,
content: editing ? 'Please verify the information below, then click on the $name button below' : 'test', content: editing ? `Please verify the information below, then click on the $name button below.\n\n${selfDestructMessage(new Date().getTime())}` : '',
embeds: [{ embeds: [{
color: successColor, color: successColor,
fields: [{ fields: [{
@ -171,7 +177,7 @@ export const createLFGPost = (
value: activity.name, value: activity.name,
inline: true, inline: true,
}, { }, {
name: 'Start Time:', name: lfgStartTimeName,
value: `${eventDateTimeStr}\n<t:${Math.floor(eventDateTime.getTime() / 1000)}:R>`, value: `${eventDateTimeStr}\n<t:${Math.floor(eventDateTime.getTime() / 1000)}:R>`,
inline: true, inline: true,
}, { }, {
@ -182,12 +188,12 @@ export const createLFGPost = (
name: 'Description:', name: 'Description:',
value: eventDescription, value: eventDescription,
}, { }, {
name: `Members Joined: ${memberList.length}/${activity.maxMembers}`, name: generateMemberTitle(memberList, activity.maxMembers || 0),
value: memberList.length ? memberList.map((member) => `${member.name} - <@${member.id}>`).join('\n') : 'None', value: generateMemberList(memberList),
inline: true, inline: true,
}, { }, {
name: 'Alternates:', name: 'Alternates:',
value: alternateList.length ? alternateList.map((member) => `${member.name} - <@${member.id}>${member.joined ? ' *' : ''}`).join('\n') : 'None', value: generateAlternateList(alternateList),
inline: true, inline: true,
}], }],
footer: { footer: {

View File

@ -6,9 +6,9 @@ import info from './info.ts';
import report from './report.ts'; import report from './report.ts';
import setup from './setup.ts'; import setup from './setup.ts';
import deleteCmd from './delete.ts'; import deleteCmd from './delete.ts';
import { createEventCommand } from '../buttons/event-creation/step1-gameSelection.ts'; import { gameSelectionCommand } from '../buttons/event-creation/step1-gameSelection.ts';
export const commands: Array<Command> = [deleteCmd, info, report, setup, createEventCommand]; export const commands: Array<Command> = [deleteCmd, info, report, setup, gameSelectionCommand];
export const createSlashCommands = async (bot: Bot) => { export const createSlashCommands = async (bot: Bot) => {
const globalCommands: MakeRequired<CreateApplicationCommand, 'name'>[] = []; const globalCommands: MakeRequired<CreateApplicationCommand, 'name'>[] = [];

View File

@ -13,6 +13,7 @@ export const dbClient = await new Client().connect({
export const queries = { export const queries = {
callIncCnt: (cmdName: string) => `CALL INC_CNT("${cmdName}");`, callIncCnt: (cmdName: string) => `CALL INC_CNT("${cmdName}");`,
insertEvent: 'INSERT INTO active_event(messageId,channelId,guildId,ownerId,eventTime) values(?,?,?,?,?)',
}; };
export const lfgChannelSettings: Map<string, LfgChannelSetting> = new Map(); export const lfgChannelSettings: Map<string, LfgChannelSetting> = new Map();