Add button systems, add game selection, add initial details modal
This commit is contained in:
parent
5303a20234
commit
d712b116b6
2
deps.ts
2
deps.ts
|
@ -26,6 +26,7 @@ export {
|
||||||
sendInteractionResponse,
|
sendInteractionResponse,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
startBot,
|
startBot,
|
||||||
|
TextStyles,
|
||||||
} from 'https://deno.land/x/discordeno@17.0.1/mod.ts';
|
} from 'https://deno.land/x/discordeno@17.0.1/mod.ts';
|
||||||
export type {
|
export type {
|
||||||
ActionRow,
|
ActionRow,
|
||||||
|
@ -43,6 +44,7 @@ export type {
|
||||||
MakeRequired,
|
MakeRequired,
|
||||||
Message,
|
Message,
|
||||||
PermissionStrings,
|
PermissionStrings,
|
||||||
|
SelectOption,
|
||||||
} from 'https://deno.land/x/discordeno@17.0.1/mod.ts';
|
} from 'https://deno.land/x/discordeno@17.0.1/mod.ts';
|
||||||
|
|
||||||
export { Client } from 'https://deno.land/x/mysql@v2.11.0/mod.ts';
|
export { Client } from 'https://deno.land/x/mysql@v2.11.0/mod.ts';
|
||||||
|
|
|
@ -5,6 +5,7 @@ export type Activity = {
|
||||||
options?: Array<Activity>;
|
options?: Array<Activity>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Max depth is limited to 4, 5th component row must be reserved for the custom button
|
||||||
export const Activities: Array<Activity> = [
|
export const Activities: Array<Activity> = [
|
||||||
{
|
{
|
||||||
name: 'Destiny 2',
|
name: 'Destiny 2',
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import { ApplicationCommandFlags, ApplicationCommandTypes, Bot, Interaction, InteractionResponseTypes, MessageComponentTypes, ActionRow, ButtonStyles, TextStyles } from '../../../deps.ts';
|
||||||
|
import { infoColor1, somethingWentWrong } from '../../commandUtils.ts';
|
||||||
|
import { CommandDetails } from '../../types/commandTypes.ts';
|
||||||
|
import { Activities } from './activities.ts';
|
||||||
|
import { generateActionRow, getNestedActivity, generateMapId, pathIdxSeparator, pathIdxEnder } from './utils.ts';
|
||||||
|
import utils from '../../utils.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.',
|
||||||
|
type: ApplicationCommandTypes.ChatInput,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tokenMap: Map<string, {
|
||||||
|
token: string,
|
||||||
|
timeoutId: number,
|
||||||
|
}> = new Map();
|
||||||
|
|
||||||
|
const customEventRow: ActionRow = {
|
||||||
|
type: MessageComponentTypes.ActionRow,
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.Button,
|
||||||
|
label: 'Create Custom Event',
|
||||||
|
customId,
|
||||||
|
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)) {
|
||||||
|
// User selected activity, give them the details modal and delete the selectMenus
|
||||||
|
bot.helpers.deleteOriginalInteractionResponse(tokenMap.get(generateMapId(interaction.guildId, interaction.channelId, interaction.member.id))?.token || '').catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:cleanup', interaction, e));
|
||||||
|
tokenMap.delete(generateMapId(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
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.ActionRow,
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.InputText,
|
||||||
|
customId: 'eventTime',
|
||||||
|
label: 'Start Time:',
|
||||||
|
style: TextStyles.Short,
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
type: MessageComponentTypes.ActionRow,
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.InputText,
|
||||||
|
customId: 'eventTimeZone',
|
||||||
|
label: 'Time Zone:',
|
||||||
|
style: TextStyles.Short,
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
type: MessageComponentTypes.ActionRow,
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.InputText,
|
||||||
|
customId: 'eventDate',
|
||||||
|
label: 'Start Date:',
|
||||||
|
style: TextStyles.Short,
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
type: MessageComponentTypes.ActionRow,
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.InputText,
|
||||||
|
customId: 'eventDescription',
|
||||||
|
label: 'Description:',
|
||||||
|
style: TextStyles.Paragraph,
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectMenus: Array<ActionRow> = [];
|
||||||
|
let selectMenuCustomId = `${customId}$`;
|
||||||
|
let currentBaseValue = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < idxPath.length; i++) {
|
||||||
|
const idx = idxPath[i];
|
||||||
|
const idxPathCopy = [...idxPath].slice(0, i);
|
||||||
|
selectMenus.push(generateActionRow(currentBaseValue, getNestedActivity(idxPathCopy, Activities), selectMenuCustomId, idx));
|
||||||
|
|
||||||
|
selectMenuCustomId = `${selectMenuCustomId}$`;
|
||||||
|
currentBaseValue = `${currentBaseValue}${idx}${pathIdxSeparator}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectMenus.push(customEventRow);
|
||||||
|
|
||||||
|
if (interaction.data.customId && interaction.data.customId.includes('$')) {
|
||||||
|
// Let discord know we didn't ignore the user
|
||||||
|
await bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||||
|
type: InteractionResponseTypes.DeferredUpdateMessage,
|
||||||
|
}).catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:ping', interaction, e));
|
||||||
|
|
||||||
|
// Update the original game selector
|
||||||
|
await bot.helpers.editOriginalInteractionResponse(tokenMap.get(generateMapId(interaction.guildId, interaction.channelId, interaction.member.id))?.token || '', {
|
||||||
|
components: selectMenus,
|
||||||
|
}).catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:edit', interaction, e));
|
||||||
|
} else {
|
||||||
|
// Delete old token entry if it exists
|
||||||
|
if (tokenMap.has(generateMapId(interaction.guildId, interaction.channelId, interaction.member.id))) {
|
||||||
|
bot.helpers.deleteOriginalInteractionResponse(tokenMap.get(generateMapId(interaction.guildId, interaction.channelId, interaction.member.id))?.token || '').catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:cleanup', interaction, e));
|
||||||
|
tokenMap.delete(generateMapId(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, memberId) => {
|
||||||
|
bot.helpers.deleteOriginalInteractionResponse(tokenMap.get(generateMapId(guildId, channelId, memberId))?.token || '').catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:delete', interaction, e));
|
||||||
|
tokenMap.delete(generateMapId(guildId, channelId, memberId));
|
||||||
|
}, tokenTimeoutMS, interaction.guildId, interaction.channelId, interaction.member.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate destruction time
|
||||||
|
const destructTime = Math.floor((new Date().getTime() + tokenTimeoutMS) / 1000);
|
||||||
|
|
||||||
|
// Send initial interaction
|
||||||
|
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||||
|
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||||
|
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.`,
|
||||||
|
color: infoColor1,
|
||||||
|
}],
|
||||||
|
flags: ApplicationCommandFlags.Ephemeral,
|
||||||
|
components: selectMenus,
|
||||||
|
},
|
||||||
|
}).catch((e: Error) => utils.commonLoggers.interactionSendError('step1-gameSelection.ts:init', interaction, e));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
somethingWentWrong;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEventCommand = {
|
||||||
|
details,
|
||||||
|
execute,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEventButton = {
|
||||||
|
customId,
|
||||||
|
execute,
|
||||||
|
};
|
|
@ -1,25 +0,0 @@
|
||||||
import config from '../../../config.ts';
|
|
||||||
import { ApplicationCommandFlags, ApplicationCommandTypes, Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
|
|
||||||
import { CommandDetails } from "../../types/commandTypes.ts";
|
|
||||||
|
|
||||||
|
|
||||||
export const customId = 'gameSel';
|
|
||||||
const details: CommandDetails = {
|
|
||||||
name: 'create-event',
|
|
||||||
description: 'Creates a new event in this channel.',
|
|
||||||
type: ApplicationCommandTypes.ChatInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
const execute = (bot: Bot, interaction: Interaction) => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createEventCommand = {
|
|
||||||
details,
|
|
||||||
execute,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createEventButton = {
|
|
||||||
customId,
|
|
||||||
execute,
|
|
||||||
};
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Activity } from './activities.ts';
|
||||||
|
import { SelectOption, ActionRow, MessageComponentTypes } from '../../../deps.ts';
|
||||||
|
|
||||||
|
export const pathIdxSeparator = '|';
|
||||||
|
export const pathIdxEnder = '&';
|
||||||
|
|
||||||
|
export const getNestedActivity = (idxPath: Array<number>, activities: Array<Activity>): Array<Activity> => {
|
||||||
|
const nextIdx = idxPath[0];
|
||||||
|
if (idxPath.length && activities[nextIdx] && activities[nextIdx].options) {
|
||||||
|
idxPath.shift();
|
||||||
|
return getNestedActivity(idxPath, activities[nextIdx].options || []);
|
||||||
|
} else {
|
||||||
|
return activities;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectOptions = (baseValue: string, activities: Array<Activity>, defaultIdx?: number): Array<SelectOption> => activities.map((act, idx) => ({
|
||||||
|
label: act.name,
|
||||||
|
value: `${baseValue}${idx}${act.maxMembers ? pathIdxEnder : pathIdxSeparator}`,
|
||||||
|
default: idx === defaultIdx,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const generateActionRow = (baseValue: string, activities: Array<Activity>, customId: string, defaultIdx?: number): ActionRow => ({
|
||||||
|
type: MessageComponentTypes.ActionRow,
|
||||||
|
components: [{
|
||||||
|
type: MessageComponentTypes.SelectMenu,
|
||||||
|
customId,
|
||||||
|
options: getSelectOptions(baseValue, activities, defaultIdx),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const generateMapId = (guildId: bigint, channelId: bigint, userId: bigint) => `${guildId}-${channelId}-${userId}`;
|
|
@ -6,8 +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';
|
||||||
|
|
||||||
export const commands: Array<Command> = [deleteCmd, info, report, setup];
|
export const commands: Array<Command> = [deleteCmd, info, report, setup, createEventCommand];
|
||||||
|
|
||||||
export const createSlashCommands = async (bot: Bot) => {
|
export const createSlashCommands = async (bot: Bot) => {
|
||||||
const globalCommands: MakeRequired<CreateApplicationCommand, 'name'>[] = [];
|
const globalCommands: MakeRequired<CreateApplicationCommand, 'name'>[] = [];
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { failColor, infoColor2, somethingWentWrong, successColor } from '../comm
|
||||||
import { dbClient, lfgChannelSettings, queries } from '../db.ts';
|
import { dbClient, lfgChannelSettings, queries } from '../db.ts';
|
||||||
import { CommandDetails } from '../types/commandTypes.ts';
|
import { CommandDetails } from '../types/commandTypes.ts';
|
||||||
import utils from '../utils.ts';
|
import utils from '../utils.ts';
|
||||||
|
import { customId as gameSelId } from '../buttons/event-creation/step1-gameSelection.ts';
|
||||||
|
|
||||||
const withoutMgrRole = 'without-manager-role';
|
const withoutMgrRole = 'without-manager-role';
|
||||||
const withMgrRole = 'with-manager-role';
|
const withMgrRole = 'with-manager-role';
|
||||||
|
@ -266,7 +267,7 @@ The Discord Slash Command system will ensure you provide all the required detail
|
||||||
components: [{
|
components: [{
|
||||||
type: MessageComponentTypes.Button,
|
type: MessageComponentTypes.Button,
|
||||||
label: createNewEventBtn,
|
label: createNewEventBtn,
|
||||||
customId: 'temp', // TODO: set this
|
customId: gameSelId,
|
||||||
style: ButtonStyles.Success,
|
style: ButtonStyles.Success,
|
||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
import { Bot, BotWithCache, Interaction } from '../../deps.ts';
|
import { Bot, BotWithCache, Interaction } from '../../deps.ts';
|
||||||
import { commands } from '../commands/_index.ts';
|
import { commands } from '../commands/_index.ts';
|
||||||
|
|
||||||
|
import { Button } from '../types/commandTypes.ts';
|
||||||
|
import { createEventButton } from '../buttons/event-creation/step1-gameSelection.ts';
|
||||||
|
|
||||||
|
const buttons: Array<Button> = [createEventButton];
|
||||||
|
|
||||||
const commandNames: Array<string> = commands.map((command) => command.details.name);
|
const commandNames: Array<string> = commands.map((command) => command.details.name);
|
||||||
|
const buttonNames: Array<string> = buttons.map((button) => button.customId);
|
||||||
|
|
||||||
export const interactionCreate = (rawBot: Bot, interaction: Interaction) => {
|
export const interactionCreate = (rawBot: Bot, interaction: Interaction) => {
|
||||||
const bot = rawBot as BotWithCache;
|
const bot = rawBot as BotWithCache;
|
||||||
if (interaction.data && interaction.id) {
|
if (interaction.data && interaction.id) {
|
||||||
if (interaction.data.name) {
|
if (interaction.data.name && commandNames.includes(interaction.data.name)) {
|
||||||
if (commandNames.includes(interaction.data.name)) {
|
|
||||||
const cmdIdx = commandNames.indexOf(interaction.data.name);
|
const cmdIdx = commandNames.indexOf(interaction.data.name);
|
||||||
commands[cmdIdx].execute(bot, interaction);
|
commands[cmdIdx].execute(bot, interaction);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customId = interaction.data.customId ? interaction.data.customId.replace(/\$/g, '') : ''
|
||||||
|
if (customId && buttonNames.includes(customId)) {
|
||||||
|
const btnIdx = buttonNames.indexOf(customId);
|
||||||
|
buttons[btnIdx].execute(bot, interaction);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('interaction NOT HANDLED')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,11 @@ export type Command = {
|
||||||
execute: Function;
|
execute: Function;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Button = {
|
||||||
|
customId: string;
|
||||||
|
execute: Function;
|
||||||
|
};
|
||||||
|
|
||||||
export type LfgChannelSetting = {
|
export type LfgChannelSetting = {
|
||||||
managed: boolean;
|
managed: boolean;
|
||||||
managerRoleId: bigint;
|
managerRoleId: bigint;
|
||||||
|
|
Loading…
Reference in New Issue