All around datetime parsing update, finalize step allows editing, lfg creator mostly there

This commit is contained in:
Ean Milligan (Bastion) 2023-03-27 22:41:59 -04:00
parent 4ddfd70655
commit 89bfce32f0
7 changed files with 201 additions and 16 deletions

View File

@ -15,6 +15,7 @@ export const config = {
'link': { // Links to various sites 'link': { // Links to various sites
'sourceCode': 'https://github.com/Burn-E99/GroupUp', // Link to the repository 'sourceCode': 'https://github.com/Burn-E99/GroupUp', // Link to the repository
'supportServer': '', // Invite link to the Discord support server 'supportServer': '', // Invite link to the Discord support server
'addToCalendar': '', // Link to where the icsGenerator is hosted
}, },
'logChannel': 'the_log_channel', // Discord channel ID where the bot should put startup messages and other error messages needed '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 'reportChannel': 'the_report_channel', // Discord channel ID where reports will be sent when using the built-in report command

View File

@ -41,6 +41,7 @@ export type {
EventHandlers, EventHandlers,
Guild, Guild,
Interaction, Interaction,
InteractionResponse,
MakeRequired, MakeRequired,
Message, Message,
PermissionStrings, PermissionStrings,

View File

@ -1,5 +1,5 @@
const monthsLong: Array<string> = ['JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER']; const monthsLong: Array<string> = ['JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER'];
const monthsShort: Array<string> = monthsLong.map((month) => month.slice(0, 3)); export const monthsShort: Array<string> = monthsLong.map((month) => month.slice(0, 3));
const tzMap: Map<string, string> = new Map([ const tzMap: Map<string, string> = new Map([
['CDT', '-05:00'], ['CDT', '-05:00'],
['CST', '-06:00'], ['CST', '-06:00'],
@ -77,11 +77,15 @@ const parseEventTime = (preParsedEventTime: string): [string, string, string] =>
parsedEventTimePeriod = ''; parsedEventTimePeriod = '';
} }
if (!parsedEventTimePeriod && parsedEventTimeHours.length < 2) {
parsedEventTimeHours = `0${parsedEventTimeHours}`;
}
return [parsedEventTimeHours, parsedEventTimeMinutes, parsedEventTimePeriod]; return [parsedEventTimeHours, parsedEventTimeMinutes, parsedEventTimePeriod];
}; };
// Takes user input Time Zone and makes it actually usable // Takes user input Time Zone and makes it actually usable
const parseEventTimeZone = (preParsedEventTimeZone: string): string => { const parseEventTimeZone = (preParsedEventTimeZone: string): [string, string] => {
if (shorthandUSTZ.includes(preParsedEventTimeZone)) { if (shorthandUSTZ.includes(preParsedEventTimeZone)) {
// Handle shorthand US timezones, adding S for standard time and D for Daylight Savings // Handle shorthand US timezones, adding S for standard time and D for Daylight Savings
const today = new Date(); const today = new Date();
@ -95,20 +99,20 @@ const parseEventTimeZone = (preParsedEventTimeZone: string): string => {
} }
if (tzMap.has(preParsedEventTimeZone)) { if (tzMap.has(preParsedEventTimeZone)) {
// TZ is proper abbreviation, use our map to convert // TZ is proper abbreviation, use our map to convert
return tzMap.get(preParsedEventTimeZone) || 'how did we get here?'; return [`UTC${tzMap.get(preParsedEventTimeZone)}`, preParsedEventTimeZone];
} else { } else {
// Determine if user put in UTC4, which needs to be UTC+4 // Determine if user put in UTC4, which needs to be UTC+4
let addPlusSign = false; let addPlusSign = false;
if (!preParsedEventTimeZone.includes('+') || !preParsedEventTimeZone.includes('-')) { if (!preParsedEventTimeZone.includes('+') && !preParsedEventTimeZone.includes('-')) {
addPlusSign = true; addPlusSign = true;
} }
// Determine if we need to prepend UTC/GMT, handle adding the + into the string // Determine if we need to prepend UTC/GMT, handle adding the + into the string
if (!preParsedEventTimeZone.startsWith('UTC') || preParsedEventTimeZone.startsWith('GMT')) { if (!preParsedEventTimeZone.startsWith('UTC') && preParsedEventTimeZone.startsWith('GMT')) {
preParsedEventTimeZone = `UTC${addPlusSign && '+'}${preParsedEventTimeZone}`; preParsedEventTimeZone = `UTC${addPlusSign && '+'}${preParsedEventTimeZone}`;
} else if (addPlusSign) { } else if (addPlusSign) {
preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, 3)}+${preParsedEventTimeZone.slice(3)}`; preParsedEventTimeZone = `${preParsedEventTimeZone.slice(0, 3)}+${preParsedEventTimeZone.slice(3)}`;
} }
return preParsedEventTimeZone; return [preParsedEventTimeZone, preParsedEventTimeZone];
} }
}; };
@ -135,8 +139,8 @@ const parseEventDate = (preParsedEventDate: string): [string, string, string] =>
// Month and Day exist, so determine year and parse month/day // Month and Day exist, so determine year and parse month/day
parsedEventYear = (isNaN(parseInt(parsedEventYear)) ? today.getFullYear() : parseInt(parsedEventYear)).toString(); parsedEventYear = (isNaN(parseInt(parsedEventYear)) ? today.getFullYear() : parseInt(parsedEventYear)).toString();
parsedEventDay = parseInt(parsedEventDay).toString(); parsedEventDay = parseInt(parsedEventDay).toString();
if (!monthsLong.includes(parsedEventMonth) || !monthsShort.includes(parsedEventMonth)) { if (!monthsLong.includes(parsedEventMonth) && !monthsShort.includes(parsedEventMonth)) {
parsedEventMonth = parseInt(parsedEventMonth).toString(); parsedEventMonth = monthsShort[parseInt(parsedEventMonth) - 1];
} }
} }
@ -144,15 +148,20 @@ const parseEventDate = (preParsedEventDate: string): [string, string, string] =>
}; };
// Take full raw Date/Time input and convert it to a proper Date // Take full raw Date/Time input and convert it to a proper Date
export const getDateFromRawInput = (rawEventTime: string, rawEventTimeZone: string, rawEventDate: string): Date => { export const getDateFromRawInput = (rawEventTime: string, rawEventTimeZone: string, rawEventDate: string): [Date, string] => {
// Verify/Set Time // Verify/Set Time
const [parsedEventTimeHours, parsedEventTimeMinutes, parsedEventTimePeriod] = parseEventTime(rawEventTime.replaceAll(':', '').toUpperCase()); const [parsedEventTimeHours, parsedEventTimeMinutes, parsedEventTimePeriod] = parseEventTime(rawEventTime.replaceAll(':', '').toUpperCase());
// Verify/Set Time Zone // Verify/Set Time Zone
const parsedEventTimeZone = parseEventTimeZone(rawEventTimeZone.replaceAll(' ', '').trim().toUpperCase()); const [parsedEventTimeZone, userInputTimeZone] = parseEventTimeZone(rawEventTimeZone.replaceAll(' ', '').trim().toUpperCase());
// Verify/Set Date // Verify/Set Date
const [parsedEventYear, parsedEventMonth, parsedEventDay] = parseEventDate(rawEventDate.trim().toUpperCase()); const [parsedEventYear, parsedEventMonth, parsedEventDay] = parseEventDate(rawEventDate.trim().toUpperCase());
return new Date(`${parsedEventMonth} ${parsedEventDay}, ${parsedEventYear} ${parsedEventTimeHours}:${parsedEventTimeMinutes} ${parsedEventTimePeriod} ${parsedEventTimeZone}`); return [
new Date(`${parsedEventMonth} ${parsedEventDay}, ${parsedEventYear} ${parsedEventTimeHours}:${parsedEventTimeMinutes} ${parsedEventTimePeriod} ${parsedEventTimeZone}`),
`${parsedEventTimeHours}${parsedEventTimePeriod ? ':' : ''}${parsedEventTimeMinutes} ${parsedEventTimePeriod} ${userInputTimeZone} ${parsedEventMonth.slice(0, 1)}${
parsedEventMonth.slice(1, 3).toLowerCase()
} ${parsedEventDay}, ${parsedEventYear}`,
];
}; };

View File

@ -2,10 +2,23 @@ import { ActionRow, ApplicationCommandFlags, ApplicationCommandTypes, Bot, Butto
import { infoColor1, somethingWentWrong } from '../../commandUtils.ts'; import { infoColor1, somethingWentWrong } from '../../commandUtils.ts';
import { CommandDetails } from '../../types/commandTypes.ts'; import { CommandDetails } from '../../types/commandTypes.ts';
import { Activities } from './activities.ts'; import { Activities } from './activities.ts';
import { addTokenToMap, deleteTokenEarly, generateActionRow, generateMapId, getNestedActivity, idSeparator, pathIdxEnder, pathIdxSeparator, selfDestructMessage, tokenMap } from './utils.ts'; import {
addTokenToMap,
deleteTokenEarly,
generateActionRow,
generateMapId,
getNestedActivity,
idSeparator,
LfgEmbedIndexes,
pathIdxEnder,
pathIdxSeparator,
selfDestructMessage,
tokenMap,
} 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';
import { customId as finalizeEventBtnId } from './step2-finalize.ts'; import { customId as finalizeEventBtnId } from './step2-finalize.ts';
import { monthsShort } from './dateTimeUtils.ts';
export const customId = 'gameSel'; export const customId = 'gameSel';
export const eventTimeId = 'eventTime'; export const eventTimeId = 'eventTime';
@ -39,6 +52,20 @@ const execute = async (bot: Bot, interaction: Interaction) => {
if ((interaction.data.customId?.includes(idSeparator) && interaction.data.customId.endsWith(pathIdxEnder)) || interaction.data?.values?.[0].endsWith(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 // User selected activity, give them the details modal and delete the selectMenus
await deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id); await deleteTokenEarly(bot, interaction, interaction.guildId, interaction.channelId, interaction.member.id);
let prefillTime = '';
let prefillTimeZone = '';
let prefillDate = '';
let prefillDescription = '';
if (interaction.message && interaction.message.embeds[0].fields) {
let rawEventDateTime = interaction.message.embeds[0].fields[LfgEmbedIndexes.StartTime].value.split('\n')[0].split(' ');
const monthIdx = rawEventDateTime.findIndex((item) => monthsShort.includes(item.toUpperCase()));
prefillTime = rawEventDateTime.slice(0, monthIdx - 2).join(' ').trim();
prefillTimeZone = rawEventDateTime[monthIdx - 1].trim();
prefillDate = rawEventDateTime.slice(monthIdx).join(' ').trim();
prefillDescription = interaction.message.embeds[0].fields[LfgEmbedIndexes.Description].value.trim();
}
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, { bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.Modal, type: InteractionResponseTypes.Modal,
data: { data: {
@ -54,6 +81,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
style: TextStyles.Short, style: TextStyles.Short,
minLength: 1, minLength: 1,
maxLength: 8, maxLength: 8,
value: prefillTime || undefined,
}], }],
}, { }, {
type: MessageComponentTypes.ActionRow, type: MessageComponentTypes.ActionRow,
@ -65,6 +93,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
style: TextStyles.Short, style: TextStyles.Short,
minLength: 2, minLength: 2,
maxLength: 8, maxLength: 8,
value: prefillTimeZone || undefined,
}], }],
}, { }, {
type: MessageComponentTypes.ActionRow, type: MessageComponentTypes.ActionRow,
@ -76,6 +105,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
style: TextStyles.Short, style: TextStyles.Short,
minLength: 1, minLength: 1,
maxLength: 20, maxLength: 20,
value: prefillDate || undefined,
}], }],
}, { }, {
type: MessageComponentTypes.ActionRow, type: MessageComponentTypes.ActionRow,
@ -88,6 +118,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
required: false, required: false,
minLength: 0, minLength: 0,
maxLength: 1000, maxLength: 1000,
value: prefillDescription || undefined,
}], }],
}], }],
}, },

View File

@ -1,7 +1,7 @@
import { Bot, Interaction, InteractionResponseTypes, MessageComponentTypes, TextStyles } from '../../../deps.ts'; import { Bot, Interaction, InteractionResponseTypes, MessageComponentTypes, TextStyles } 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 { getFinalActivity, idSeparator, pathIdxSeparator } from './utils.ts'; import { createLFGPost, getFinalActivity, idSeparator, pathIdxSeparator } 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';
@ -51,9 +51,15 @@ const execute = async (bot: Bot, interaction: Interaction) => {
} }
// Get Date Object from user input // Get Date Object from user input
const eventDateTime = getDateFromRawInput(rawEventTime, rawEventTimeZone, rawEventDate); const [eventDateTime, eventDateTimeStr] = getDateFromRawInput(rawEventTime, rawEventTimeZone, rawEventDate);
somethingWentWrong(bot, interaction, `TESTING@${rawEventTime}_${rawEventTimeZone}_${rawEventDate}`); bot.helpers.sendInteractionResponse(
interaction.id,
interaction.token,
createLFGPost(category, activity, eventDateTime, eventDateTimeStr, eventDescription, interaction.member.nick || 'test', [], [], customIdIdxPath, true),
);
// somethingWentWrong(bot, interaction, `TESTING@${rawEventTime}_${rawEventTimeZone}_${rawEventDate}`);
} else { } else {
somethingWentWrong(bot, interaction, 'noDataFromEventDescriptionModal'); somethingWentWrong(bot, interaction, 'noDataFromEventDescriptionModal');
} }

View File

@ -1,6 +1,21 @@
import config from '../../../config.ts';
import { Activity } from './activities.ts'; import { Activity } from './activities.ts';
import { ActionRow, Bot, Interaction, MessageComponentTypes, SelectOption } from '../../../deps.ts'; import {
ActionRow,
ApplicationCommandFlags,
Bot,
ButtonComponent,
ButtonStyles,
Interaction,
InteractionResponse,
InteractionResponseTypes,
MessageComponentTypes,
SelectOption,
} from '../../../deps.ts';
import utils from '../../utils.ts'; import utils from '../../utils.ts';
import { successColor } from '../../commandUtils.ts';
import { LFGMember } from '../../types/commandTypes.ts';
import { customId as gameSelCustomId } from './step1-gameSelection.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;
@ -70,3 +85,119 @@ export const deleteTokenEarly = async (bot: Bot, interaction: Interaction, guild
tokenMap.delete(generateMapId(guildId, channelId, userId)); tokenMap.delete(generateMapId(guildId, channelId, userId));
} }
}; };
const finalizeButtons = (idxPath: string): [ButtonComponent, ButtonComponent, ButtonComponent] => [{
type: MessageComponentTypes.Button,
label: 'Create Event',
style: ButtonStyles.Success,
customId: 'createEvent', // TODO: replace with proper id
}, {
type: MessageComponentTypes.Button,
label: 'Create Whitelisted Event',
style: ButtonStyles.Primary,
customId: `createEvent${idSeparator}`, // TODO: replace with proper id
}, {
type: MessageComponentTypes.Button,
label: 'Edit Event Details',
style: ButtonStyles.Secondary,
customId: `${gameSelCustomId}${idSeparator}${idxPath}${pathIdxEnder}`,
}];
export const generateLFGButtons = (whitelist: boolean): [ButtonComponent, ButtonComponent, ButtonComponent, ButtonComponent, ButtonComponent] => [{
type: MessageComponentTypes.Button,
label: `${whitelist ? 'Request to ' : ''}Join`,
style: ButtonStyles.Success,
customId: `joinEvent${whitelist ? idSeparator : ''}`, // TODO: replace with proper id
}, {
type: MessageComponentTypes.Button,
label: `Join as Alternate`,
style: ButtonStyles.Primary,
customId: 'alternateEvent', // TODO: replace with proper id
}, {
type: MessageComponentTypes.Button,
label: 'Leave',
style: ButtonStyles.Danger,
customId: 'leaveEvent', // TODO: replace with proper id
}, {
type: MessageComponentTypes.Button,
label: '',
style: ButtonStyles.Secondary,
customId: 'editEvent', // TODO: replace with proper id
emoji: {
name: '✏️',
},
}, {
type: MessageComponentTypes.Button,
label: '',
style: ButtonStyles.Secondary,
customId: 'deleteEvent', // TODO: replace with proper id
emoji: {
name: '🗑️',
},
}];
export enum LfgEmbedIndexes {
Activity,
StartTime,
ICSLink,
Description,
JoinedMembers,
AlternateMembers,
}
export const createLFGPost = (
category: string,
activity: Activity,
eventDateTime: Date,
eventDateTimeStr: String,
eventDescription: string,
author: string,
memberList: Array<LFGMember>,
alternateList: Array<LFGMember>,
idxPath: string,
editing: boolean,
whitelist = false,
): InteractionResponse => {
const icsDetails = `${category}: ${activity.name}`;
return {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: ApplicationCommandFlags.Ephemeral,
content: editing ? 'Please verify the information below, then click on the $name button below' : 'test',
embeds: [{
color: successColor,
fields: [{
name: `${category}:`,
value: activity.name,
inline: true,
}, {
name: 'Start Time:',
value: `${eventDateTimeStr}\n<t:${Math.floor(eventDateTime.getTime() / 1000)}:R>`,
inline: true,
}, {
name: 'Add to Calendar:',
value: `[Download ICS File](${config.links.addToCalendar}?t=${eventDateTime.getTime()}&n=${icsDetails.replaceAll(' ', '+')})`,
inline: true,
}, {
name: 'Description:',
value: eventDescription,
}, {
name: `Members Joined: ${memberList.length}/${activity.maxMembers}`,
value: memberList.length ? memberList.map((member) => `${member.name} - <@${member.id}>`).join('\n') : 'None',
inline: true,
}, {
name: 'Alternates:',
value: alternateList.length ? alternateList.map((member) => `${member.name} - <@${member.id}>${member.joined ? ' *' : ''}`).join('\n') : 'None',
inline: true,
}],
footer: {
text: `Created by: ${author}`,
},
timestamp: eventDateTime.getTime(),
}],
components: [{
type: MessageComponentTypes.ActionRow,
components: editing ? finalizeButtons(idxPath) : generateLFGButtons(whitelist),
}],
},
};
};

View File

@ -31,3 +31,9 @@ export type DBGuildSettings = {
managerRoleId: bigint; managerRoleId: bigint;
logChannelId: bigint; logChannelId: bigint;
}; };
export type LFGMember = {
id: bigint;
name: string;
joined?: boolean;
};