Add edit system for editing date/time and description

This commit is contained in:
Ean Milligan (Bastion) 2023-04-26 06:51:50 -04:00
parent 1f1460f553
commit edeffc8669
9 changed files with 307 additions and 2 deletions

View File

@ -12,6 +12,11 @@ import { alternateRequestButton } from './live-event/alternateRequest.ts';
import { deleteEventButton } from './live-event/deleteEvent.ts'; import { deleteEventButton } from './live-event/deleteEvent.ts';
import { deleteConfirmedButton } from './live-event/deleteConfirmed.ts'; import { deleteConfirmedButton } from './live-event/deleteConfirmed.ts';
import { editEventButton } from './live-event/editEvent.ts'; import { editEventButton } from './live-event/editEvent.ts';
import { editDescriptionButton } from './live-event/editDescription.ts';
import { editDateTimeButton } from './live-event/editDateTime.ts';
import { applyDescriptionButton } from './live-event/applyDescription.ts';
import { applyDateTimeButton } from './live-event/applyDateTime.ts';
import { updateEventButton } from './live-event/updateEvent.ts';
export const buttons: Array<Button> = [ export const buttons: Array<Button> = [
gameSelectionButton, gameSelectionButton,
@ -27,4 +32,9 @@ export const buttons: Array<Button> = [
deleteEventButton, deleteEventButton,
deleteConfirmedButton, deleteConfirmedButton,
editEventButton, editEventButton,
editDescriptionButton,
editDateTimeButton,
applyDescriptionButton,
applyDateTimeButton,
updateEventButton,
]; ];

View File

@ -0,0 +1,69 @@
import { ApplicationCommandFlags, Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
import { somethingWentWrong } from '../../commandUtils.ts';
import { eventDateId, eventTimeId, eventTimeZoneId, idSeparator, LfgEmbedIndexes, pathIdxEnder, pathIdxSeparator } from '../eventUtils.ts';
import { addTokenToMap } from '../tokenCleanup.ts';
import utils from '../../utils.ts';
import { applyEditButtons, applyEditMessage } from './utils.ts';
import { getDateFromRawInput } from '../event-creation/dateTimeUtils.ts';
import { generateTimeFieldStr } from '../event-creation/utils.ts';
export const customId = 'applyDateTime';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.customId && interaction.data?.components?.length && interaction.member && interaction.channelId && interaction.guildId) {
const [evtChannelId, evtMessageId] = (interaction.data.customId.replaceAll(pathIdxEnder, '').split(idSeparator)[1] || '').split(pathIdxSeparator).map((id) => BigInt(id || '0'));
const eventMessage = await bot.helpers.getMessage(evtChannelId, evtMessageId).catch((e: Error) => utils.commonLoggers.messageGetError('applyDateTime.ts', 'get eventMessage', e));
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 || '');
}
}
const newTime = tempDataMap.get(eventTimeId);
const newTimeZone = tempDataMap.get(eventTimeZoneId);
const newDate = tempDataMap.get(eventDateId);
if (!newTime || !newTimeZone || !newDate) {
// Error out if user somehow failed to provide one of the fields (eventDescription is allowed to be null/empty)
somethingWentWrong(bot, interaction, `missingFieldFromEventDescription@${newTime}_${newTimeZone}_${newDate}`);
return;
}
// Get Date Object from user input
const [eventDateTime, eventDateTimeStr] = getDateFromRawInput(newTime, newTimeZone, newDate);
if (eventMessage && eventMessage.embeds[0].fields) {
// eventMessage.embeds[0].fields[LfgEmbedIndexes.Description].value = newDescription || noDescProvided;
eventMessage.embeds[0].fields[LfgEmbedIndexes.StartTime].value = generateTimeFieldStr(eventDateTimeStr, eventDateTime);
const tIdx = eventMessage.embeds[0].fields[LfgEmbedIndexes.ICSLink].value.indexOf('?t=') + 3;
const nIdx = eventMessage.embeds[0].fields[LfgEmbedIndexes.ICSLink].value.indexOf('&n=');
eventMessage.embeds[0].fields[LfgEmbedIndexes.ICSLink].value = `${eventMessage.embeds[0].fields[LfgEmbedIndexes.ICSLink].value.slice(0, tIdx)}${eventDateTime.getTime()}${
eventMessage.embeds[0].fields[LfgEmbedIndexes.ICSLink].value.slice(nIdx)
}`;
eventMessage.embeds[0].timestamp = eventDateTime.getTime();
// Send edit confirmation
addTokenToMap(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: applyEditMessage(new Date().getTime()),
embeds: [eventMessage.embeds[0]],
components: applyEditButtons(interaction.data.customId.split(idSeparator)[1] || ''),
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('applyDateTime.ts', interaction, e));
} else {
somethingWentWrong(bot, interaction, 'failedToGetEventMsgInApplyDateTime');
}
} else {
somethingWentWrong(bot, interaction, 'noDataFromApplyDateTime');
}
};
export const applyDateTimeButton = {
customId,
execute,
};

View File

@ -0,0 +1,49 @@
import { ApplicationCommandFlags, Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
import { somethingWentWrong } from '../../commandUtils.ts';
import { eventDescriptionId, idSeparator, LfgEmbedIndexes, noDescProvided, pathIdxEnder, pathIdxSeparator } from '../eventUtils.ts';
import { addTokenToMap } from '../tokenCleanup.ts';
import utils from '../../utils.ts';
import { applyEditButtons, applyEditMessage } from './utils.ts';
export const customId = 'applyDescription';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.customId && interaction.data?.components?.length && interaction.member && interaction.channelId && interaction.guildId) {
const [evtChannelId, evtMessageId] = (interaction.data.customId.replaceAll(pathIdxEnder, '').split(idSeparator)[1] || '').split(pathIdxSeparator).map((id) => BigInt(id || '0'));
const eventMessage = await bot.helpers.getMessage(evtChannelId, evtMessageId).catch((e: Error) => utils.commonLoggers.messageGetError('applyDescription.ts', 'get eventMessage', e));
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 || '');
}
}
const newDescription = tempDataMap.get(eventDescriptionId);
if (eventMessage && eventMessage.embeds[0].fields) {
eventMessage.embeds[0].fields[LfgEmbedIndexes.Description].value = newDescription || noDescProvided;
// Send edit confirmation
addTokenToMap(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: applyEditMessage(new Date().getTime()),
embeds: [eventMessage.embeds[0]],
components: applyEditButtons(interaction.data.customId.split(idSeparator)[1] || ''),
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('applyDescription.ts', interaction, e));
} else {
somethingWentWrong(bot, interaction, 'failedToGetEventMsgInApplyDescription');
}
} else {
somethingWentWrong(bot, interaction, 'noDataFromApplyDescription');
}
};
export const applyDescriptionButton = {
customId,
execute,
};

View File

@ -0,0 +1,43 @@
import { Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
import { dbClient, queries } from '../../db.ts';
import { somethingWentWrong } from '../../commandUtils.ts';
import { dateTimeFields, idSeparator, LfgEmbedIndexes, pathIdxEnder, pathIdxSeparator } from '../eventUtils.ts';
import { deleteTokenEarly } from '../tokenCleanup.ts';
import { monthsShort } from '../event-creation/dateTimeUtils.ts';
import utils from '../../utils.ts';
import { customId as applyDateTimeCustomId } from './applyDateTime.ts';
export const customId = 'editDateTime';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.customId && interaction.member && interaction.channelId && interaction.guildId) {
// Light Telemetry
dbClient.execute(queries.callIncCnt('btn-eeChangeTime')).catch((e) => utils.commonLoggers.dbError('editDateTime.ts', 'call sproc INC_CNT on', e));
deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
const [evtChannelId, evtMessageId] = (interaction.data.customId.replaceAll(pathIdxEnder, '').split(idSeparator)[1] || '').split(pathIdxSeparator).map((id) => BigInt(id || '0'));
const eventMessage = await bot.helpers.getMessage(evtChannelId, evtMessageId).catch((e: Error) => utils.commonLoggers.messageGetError('editDateTime.ts', 'get eventMessage', e));
let rawEventDateTime = eventMessage?.embeds[0].fields ? eventMessage.embeds[0].fields[LfgEmbedIndexes.StartTime].value.trim().split('\n')[0].split(' ') : [];
const monthIdx = rawEventDateTime.findIndex((item) => monthsShort.includes(item.toUpperCase()));
const prefillTime = rawEventDateTime.slice(0, monthIdx - 1).join(' ').trim();
const prefillTimeZone = rawEventDateTime[monthIdx - 1].trim();
const prefillDate = rawEventDateTime.slice(monthIdx).join(' ').trim();
// Open Edit Date/Time Modal
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.Modal,
data: {
title: 'Edit Event Date/Time',
customId: `${applyDateTimeCustomId}${idSeparator}${interaction.data.customId.split(idSeparator)[1] || ''}`,
components: dateTimeFields(prefillTime, prefillTimeZone, prefillDate),
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('editDateTime.ts', interaction, e));
} else {
somethingWentWrong(bot, interaction, 'noDataFromEditDateTimeButton');
}
};
export const editDateTimeButton = {
customId,
execute,
};

View File

@ -0,0 +1,38 @@
import { Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
import { dbClient, queries } from '../../db.ts';
import { somethingWentWrong } from '../../commandUtils.ts';
import { descriptionTextField, idSeparator, LfgEmbedIndexes, pathIdxEnder, pathIdxSeparator } from '../eventUtils.ts';
import { deleteTokenEarly } from '../tokenCleanup.ts';
import utils from '../../utils.ts';
import { customId as applyDescriptionCustomId } from './applyDescription.ts';
export const customId = 'editDescription';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.customId && interaction.member && interaction.channelId && interaction.guildId) {
// Light Telemetry
dbClient.execute(queries.callIncCnt('btn-eeChangeDesc')).catch((e) => utils.commonLoggers.dbError('editDescription.ts', 'call sproc INC_CNT on', e));
deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
const [evtChannelId, evtMessageId] = (interaction.data.customId.replaceAll(pathIdxEnder, '').split(idSeparator)[1] || '').split(pathIdxSeparator).map((id) => BigInt(id || '0'));
const eventMessage = await bot.helpers.getMessage(evtChannelId, evtMessageId).catch((e: Error) => utils.commonLoggers.messageGetError('editDescription.ts', 'get eventMessage', e));
const prefillDescription = eventMessage?.embeds[0].fields ? eventMessage.embeds[0].fields[LfgEmbedIndexes.Description].value.trim() : '';
// Open Edit Description Modal
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.Modal,
data: {
title: 'Edit Event Description',
customId: `${applyDescriptionCustomId}${idSeparator}${interaction.data.customId.split(idSeparator)[1] || ''}`,
components: [descriptionTextField(prefillDescription)],
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('editDescription.ts', interaction, e));
} else {
somethingWentWrong(bot, interaction, 'noDataFromEditDescriptionButton');
}
};
export const editDescriptionButton = {
customId,
execute,
};

View File

@ -4,6 +4,8 @@ import { infoColor1, somethingWentWrong, stopThat } from '../../commandUtils.ts'
import { idSeparator, pathIdxEnder, pathIdxSeparator } from '../eventUtils.ts'; import { idSeparator, pathIdxEnder, pathIdxSeparator } from '../eventUtils.ts';
import { addTokenToMap, selfDestructMessage } from '../tokenCleanup.ts'; import { addTokenToMap, selfDestructMessage } from '../tokenCleanup.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
import { customId as editDescriptionCustomId } from './editDescription.ts';
import { customId as editDateTimeCustomId } from './editDateTime.ts';
export const customId = 'editEvent'; export const customId = 'editEvent';
@ -59,12 +61,12 @@ ${selfDestructMessage(new Date().getTime())}`,
type: MessageComponentTypes.Button, type: MessageComponentTypes.Button,
label: 'Change Date/Time', label: 'Change Date/Time',
style: ButtonStyles.Primary, style: ButtonStyles.Primary,
customId: `b${editIdxPath}`, // TODO: add customId customId: `${editDateTimeCustomId}${editIdxPath}`,
}, { }, {
type: MessageComponentTypes.Button, type: MessageComponentTypes.Button,
label: 'Edit Description', label: 'Edit Description',
style: ButtonStyles.Primary, style: ButtonStyles.Primary,
customId: `c${editIdxPath}`, // TODO: add customId customId: `${editDescriptionCustomId}${editIdxPath}`,
}], }],
}], }],
}, },

View File

@ -0,0 +1,76 @@
import { ApplicationCommandFlags, Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
import { failColor, safelyDismissMsg, somethingWentWrong, successColor, infoColor2, infoColor1 } from '../../commandUtils.ts';
import { idSeparator, pathIdxEnder, pathIdxSeparator, LfgEmbedIndexes } from '../eventUtils.ts';
import { deleteTokenEarly } from '../tokenCleanup.ts';
import utils from '../../utils.ts';
import config from '../../../config.ts';
import { dbClient, queries, generateGuildSettingKey, lfgChannelSettings } from '../../db.ts';
export const customId = 'updateEvent';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.customId && interaction.member?.user && interaction.channelId && interaction.guildId && interaction.message?.embeds[0].fields) {
deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
const lfgChannelSetting = lfgChannelSettings.get(generateGuildSettingKey(interaction.guildId, interaction.channelId)) || {
managed: false,
managerRoleId: 0n,
logChannelId: 0n,
};
const actionByManager = interaction.data.customId.endsWith(pathIdxEnder);
const [evtChannelId, evtMessageId] = (interaction.data.customId.replaceAll(pathIdxEnder, '').split(idSeparator)[1] || '').split(pathIdxSeparator).map((id) => BigInt(id || '0'));
const eventTime: Date = new Date(parseInt(interaction.message.embeds[0].fields[LfgEmbedIndexes.ICSLink].value.split('?t=')[1].split('&n=')[0] || '0'));
const oldEventEmbed = (await bot.helpers.getMessage(evtChannelId, evtMessageId).catch((e: Error) => utils.commonLoggers.messageGetError('updateEvent.ts', 'get eventMessage', e)))?.embeds[0];
const newEventEmbed = interaction.message.embeds[0];
const userId = interaction.member.id;
const userName = interaction.member.user.username;
bot.helpers.editMessage(evtChannelId, evtMessageId, { embeds: [interaction.message.embeds[0]] }).then(() => {
dbClient.execute(queries.updateEvent, [eventTime, evtChannelId, evtMessageId]).then(() => {
// Acknowledge user so discord doesn't get annoyed
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
embeds: [{
color: successColor,
title: 'Update successfully applied.',
description: safelyDismissMsg,
}],
},
}).catch((e: Error) => utils.commonLoggers.interactionSendError('updateEvent.ts', interaction, e));
if (actionByManager) {
const missingOldEmbed = { title: 'Failed to get old event', color: failColor };
if (oldEventEmbed) {
oldEventEmbed.color = infoColor1;
}
bot.helpers.sendMessage(lfgChannelSetting.logChannelId, {
embeds: [{
color: infoColor2,
title: `Event edited by a ${config.name} Manager`,
description: `The following event was edited by ${userName} - <@${userId}>. The old event is listed first and marked with a blue bar.`,
timestamp: new Date().getTime(),
}, oldEventEmbed || missingOldEmbed, newEventEmbed],
}).catch((e: Error) => utils.commonLoggers.messageSendError('updateEvent.ts', 'send log message', e));
}
}).catch((e) => {
utils.commonLoggers.dbError('updateEvent.ts', 'update event in', e);
if (oldEventEmbed) {
bot.helpers.editMessage(evtChannelId, evtMessageId, { embeds: [oldEventEmbed] }).catch((e) => utils.commonLoggers.messageEditError('updateEvent.ts', 'resetEventFailed', e))
}
somethingWentWrong(bot, interaction, 'updateDBInUpdateEventButton');
});
}).catch((e) => {
utils.commonLoggers.messageEditError('updateEvent.ts', 'updateEventFailed', e);
somethingWentWrong(bot, interaction, 'updateEventMessageInUpdateEventButton');
});
} else {
somethingWentWrong(bot, interaction, 'noDataFromUpdateEvent');
}
};
export const updateEventButton = {
customId,
execute,
};

View File

@ -2,7 +2,9 @@ import { ActionRow, ApplicationCommandFlags, Bot, ButtonStyles, Embed, Interacti
import { LFGMember, UrlIds } from '../../types/commandTypes.ts'; import { LFGMember, UrlIds } from '../../types/commandTypes.ts';
import { infoColor1, safelyDismissMsg, sendDirectMessage, somethingWentWrong, successColor } from '../../commandUtils.ts'; import { infoColor1, safelyDismissMsg, sendDirectMessage, somethingWentWrong, successColor } from '../../commandUtils.ts';
import { generateAlternateList, generateMemberList, generateMemberTitle, idSeparator, leaveEventBtnStr, LfgEmbedIndexes, noMembersStr } from '../eventUtils.ts'; import { generateAlternateList, generateMemberList, generateMemberTitle, idSeparator, leaveEventBtnStr, LfgEmbedIndexes, noMembersStr } from '../eventUtils.ts';
import { selfDestructMessage } from '../tokenCleanup.ts';
import { approveStr, customId as joinRequestCustomId, denyStr } from './joinRequest.ts'; import { approveStr, customId as joinRequestCustomId, denyStr } from './joinRequest.ts';
import { customId as updateEventCustomId } from './updateEvent.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
// Join status map to prevent spamming the system // Join status map to prevent spamming the system
@ -343,3 +345,18 @@ export const joinRequestResponseButtons = (disabled: boolean): ActionRow[] => [{
disabled, disabled,
}], }],
}]; }];
export const applyEditButtonName = 'Apply Edit';
export const applyEditMessage = (currentTime: number) =>
`Please verify the updated event below, then click on the \`${applyEditButtonName}\` button. If this does not look right, please dismiss this message and start over.\n\n${
selfDestructMessage(currentTime)
}`;
export const applyEditButtons = (idxPath: string): ActionRow[] => [{
type: MessageComponentTypes.ActionRow,
components: [{
type: MessageComponentTypes.Button,
label: applyEditButtonName,
style: ButtonStyles.Success,
customId: `${updateEventCustomId}${idSeparator}${idxPath}`,
}],
}];

View File

@ -14,6 +14,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_events(messageId,channelId,guildId,ownerId,eventTime) values(?,?,?,?,?)', insertEvent: 'INSERT INTO active_events(messageId,channelId,guildId,ownerId,eventTime) values(?,?,?,?,?)',
updateEvent: 'UPDATE active_events SET eventTime = ? WHERE channelId = ? AND messageId = ?',
deleteEvent: 'DELETE FROM active_events WHERE channelId = ? AND messageId = ?', deleteEvent: 'DELETE FROM active_events WHERE channelId = ? AND messageId = ?',
}; };