diff --git a/bot/embeds/templates/neverwinter/config.ts b/bot/embeds/templates/neverwinter/config.ts index 856bef8..d804abb 100644 --- a/bot/embeds/templates/neverwinter/config.ts +++ b/bot/embeds/templates/neverwinter/config.ts @@ -11,7 +11,7 @@ export const tenPersonSeperation: ISectionSeperation = { [Category.HEALER_TITLE]: { start: 10, end: 11 }, [Category.HEALER]: { start: 11, end: 13 }, [Category.WAITLIST_TITLE]: { start: 13, end: 14 }, - [Category.WAITLIST]: { start: 14, end: 19 }, + [Category.WAITLIST]: { start: 14, end: 20 }, }; export const tenPersonSoloTankSeperation: ISectionSeperation = { @@ -22,7 +22,7 @@ export const tenPersonSoloTankSeperation: ISectionSeperation = { [Category.HEALER_TITLE]: { start: 10, end: 11 }, [Category.HEALER]: { start: 11, end: 13 }, [Category.WAITLIST_TITLE]: { start: 13, end: 14 }, - [Category.WAITLIST]: { start: 14, end: 19 }, + [Category.WAITLIST]: { start: 14, end: 20 }, }; export const tenPersonSoloHealSeperation: ISectionSeperation = { @@ -33,7 +33,7 @@ export const tenPersonSoloHealSeperation: ISectionSeperation = { [Category.HEALER_TITLE]: { start: 11, end: 12 }, [Category.HEALER]: { start: 12, end: 13 }, [Category.WAITLIST_TITLE]: { start: 13, end: 14 }, - [Category.WAITLIST]: { start: 14, end: 19 }, + [Category.WAITLIST]: { start: 14, end: 20 }, }; export const tenPersonSoloHealTankSeperation: ISectionSeperation = { @@ -44,7 +44,7 @@ export const tenPersonSoloHealTankSeperation: ISectionSeperation = { [Category.HEALER_TITLE]: { start: 11, end: 12 }, [Category.HEALER]: { start: 12, end: 13 }, [Category.WAITLIST_TITLE]: { start: 13, end: 14 }, - [Category.WAITLIST]: { start: 14, end: 19 }, + [Category.WAITLIST]: { start: 14, end: 20 }, }; export const fivePersonSeperation: ISectionSeperation = { @@ -64,4 +64,9 @@ export const raidConfigs = { "1U7U1U2U1U1U1U3": tenPersonSoloHealSeperation, "1U8U1U1U1U1U1U3": tenPersonSoloHealTankSeperation, "1U3U1U1U1U1U1U3": fivePersonSeperation, + // compatible with previous configs + "1U6U1U2U1U2U1U6": tenPersonSeperation, + "1U7U1U1U1U2U1U6": tenPersonSoloTankSeperation, + "1U7U1U2U1U1U1U6": tenPersonSoloHealSeperation, + "1U8U1U1U1U1U1U6": tenPersonSoloHealTankSeperation, }; diff --git a/bot/embeds/templates/neverwinter/profile.ts b/bot/embeds/templates/neverwinter/profile.ts new file mode 100644 index 0000000..be6287a --- /dev/null +++ b/bot/embeds/templates/neverwinter/profile.ts @@ -0,0 +1,30 @@ +export const infoCardBuilder = ({}) => { + return { + components: [], + embeds: [userProfile], + }; +}; +const userProfile = ({ userName, timestamp= new Date() }) => { + + return { + type: "rich", + title: `Profile - ${userName}`, + description: `Paragon Path - Hunter\nTotal Item Level - 81,404\n☑️ Masterworks\n☑️ Raptors(Power)`, + color: 0xffa200, + timestamp, + thumbnail: { + url: `https://www.dasithsblog.com/images/Ranger-Hunter-Build.png`, + height: 0, + width: 0, + }, + author: { + name: `${userName}`, + }, + fields: [ + + ], + footer: { + text: ``, + }, + }; +}; diff --git a/bot/embeds/templates/neverwinter/raid.ts b/bot/embeds/templates/neverwinter/raid.ts index 44786e6..8d51d2c 100644 --- a/bot/embeds/templates/neverwinter/raid.ts +++ b/bot/embeds/templates/neverwinter/raid.ts @@ -38,6 +38,13 @@ export const generateAvailableFields = ({ }); export const sectionTitleNames = { + [Category.DPS_TITLE]: `───────── <:dps:911695908482207774> DPS <:dps:911695908482207774> ─────────`, + [Category.TANK_TITLE]: `──────── <:tank:911695908134060112> TANKS <:tank:911695908134060112> ────────`, + [Category.HEALER_TITLE]: `──────── <:healer:911695908117315654> HEALS <:healer:911695908117315654> ────────`, + [Category.WAITLIST_TITLE]: `────── ⌛ WAITING LIST ⌛ ──────`, +}; + +export const previousSectionTitleNames = { [Category.DPS_TITLE]: `__𒆜𒆜⚔️ DPS ⚔️𒆜𒆜__`, [Category.TANK_TITLE]: `__𒆜𒆜🛡️ TANKS 🛡️𒆜𒆜__`, [Category.HEALER_TITLE]: `__𒆜𒆜⚕️ HEALS ⚕️𒆜𒆜__`, @@ -150,22 +157,22 @@ export const raidBuilder = ({ // }, fields: [ { - name: `__𒆜𒆜⚔️ DPS ⚔️𒆜𒆜__`, + name: sectionTitleNames[Category.DPS_TITLE], value: `\u200B`, }, ...generateAvailableFields(template).DPS, { - name: `__𒆜𒆜🛡️ TANKS 🛡️𒆜𒆜__`, + name: sectionTitleNames[Category.TANK_TITLE], value: `\u200B`, }, ...generateAvailableFields(template).TANKS, { - name: `__𒆜𒆜⚕️ HEALS ⚕️𒆜𒆜__`, + name: sectionTitleNames[Category.HEALER_TITLE], value: `\u200B`, }, ...generateAvailableFields(template).HEALS, { - name: `__𒆜𒆜⌛ WAITING LIST ⌛𒆜𒆜__`, + name: sectionTitleNames[Category.WAITLIST_TITLE], value: `\u200B`, }, ...generateAvailableFields(template).WAITLIST, diff --git a/bot/interactions/commands/create/index.ts b/bot/interactions/commands/create/index.ts index c0141de..38ad379 100644 --- a/bot/interactions/commands/create/index.ts +++ b/bot/interactions/commands/create/index.ts @@ -6,6 +6,7 @@ import { } from "discord.js"; import { Logger } from "winston"; import { createRaidCommand } from "./raid"; +import { profileCommand } from "./profile"; import { unrecognizedCommand } from ".."; interface factoryInitializations { logger: Logger; @@ -21,6 +22,7 @@ interface factoryInitializations { export const commandName_create = "create"; export const availableSubCommands = { raid: createRaidCommand, + user_profile: profileCommand, }; export const recognizedSubCommands = Object.keys(availableSubCommands); export const createCommand = async ( diff --git a/bot/interactions/commands/create/profile.ts b/bot/interactions/commands/create/profile.ts new file mode 100644 index 0000000..048932d --- /dev/null +++ b/bot/interactions/commands/create/profile.ts @@ -0,0 +1,33 @@ +import { + APIChatInputApplicationCommandInteractionData, + REST, + Routes, + APIInteractionGuildMember, + APIApplicationCommandInteractionDataSubcommandOption, + } from "discord.js"; + + import { Logger } from "winston"; + import { userProfile } from "../../../modals/profile"; + + interface factoryInitializations { + logger: Logger; + rest: REST; + interactionConfig: { + guild_id: string; + application_id: string; + token: string; + member: APIInteractionGuildMember; + }; + } + + export const profileCommand = async ( + data: APIChatInputApplicationCommandInteractionData, + factoryInits: factoryInitializations + ) => { + const { rest, logger, interactionConfig } = factoryInits; + const [{ type: subCommandType, options: subCommandOptions = [] }] = + data.options as APIApplicationCommandInteractionDataSubcommandOption[]; + + return false; + }; + \ No newline at end of file diff --git a/bot/interactions/commands/create/raid.ts b/bot/interactions/commands/create/raid.ts index 0182202..77387ff 100644 --- a/bot/interactions/commands/create/raid.ts +++ b/bot/interactions/commands/create/raid.ts @@ -3,6 +3,7 @@ import { REST, Routes, APIInteractionGuildMember, + RESTPostAPIChannelMessageResult, } from "discord.js"; import ShortUniqueId from 'short-unique-id'; import { Logger } from "winston"; @@ -59,6 +60,7 @@ export const createRaidCommand = async ( "https://cdn.player.one/sites/player.one/files/styles/full_large/public/2022/01/12/neverwinter-update.jpg", [trialNamesList.TM]: "https://db4sgowjqfwig.cloudfront.net/campaigns/68638/assets/330407/Tiamat_Mobile.jpg?1400812964", + [trialNamesList.VOS]: "https://pwimages-a.akamaihd.net/arc/14/57/1457e07177cd42d03f8ef695335a88441613762258.jpg", [trialNamesList.TOSM]: "https://static.wikia.nocookie.net/forgottenrealms/images/f/fd/Spider_Temple_Concept.png/revision/latest/scale-to-width-down/350?cb=20210725190230", }; @@ -68,9 +70,9 @@ export const createRaidCommand = async ( const requestedRelativeDate = convertToDiscordDate(dateTime,{relative:true}) logger.log("info", "create attributes", {isFivePerson, requestedDate, dateTime}); const partyOptionsToMap = { - Standard: { DPS: 6, HEALS: 2, TANKS: 2, WAITLIST: 3 }, - Solo_tank: { DPS: 7, HEALS: 2, TANKS: 1, WAITLIST: 3 }, - Solo_heal: { DPS: 7, HEALS: 1, TANKS: 2, WAITLIST: 3 }, + Standard: { DPS: 6, HEALS: 2, TANKS: 2, WAITLIST: 6 }, + Solo_tank: { DPS: 7, HEALS: 2, TANKS: 1, WAITLIST: 6 }, + Solo_heal: { DPS: 7, HEALS: 1, TANKS: 2, WAITLIST: 6 }, Solo_tank_heal: { DPS: 8, HEALS: 1, TANKS: 1, WAITLIST: 3 }, }; const uniqueRaidId = new ShortUniqueId({ length: 10 })(); @@ -91,6 +93,25 @@ export const createRaidCommand = async ( template: { DPS: 3, HEALS: 1, TANKS: 1, WAITLIST: 3 }, }), }); + logger.log("info", "creating raid", { + raidId: uniqueRaidId, + raidEmbed + }); + const raidCreateResponse = await rest.patch( + (Routes as any).webhookMessage( + interactionConfig.application_id, + interactionConfig.token, + ), + { + body: { + content: `Event/Raid will start on ${requestedDate}`, + ...raidEmbed, + allowed_mentions: { + parse: [], + }, + }, + } + ) as RESTPostAPIChannelMessageResult; const updateValues = setUpdateValues({ title, creatorId: interactionConfig.member?.user?.id, @@ -104,12 +125,10 @@ export const createRaidCommand = async ( type, raidEmbed: JSON.stringify(raidEmbed), serverId: interactionConfig?.guild_id, + messageId: raidCreateResponse?.id, + channelId: raidCreateResponse?.channel_id, updatedAt: Date.now(), }); - logger.log("info", "creating raid", { - updateValues, - raidId: uniqueRaidId, - }); const createdRaid = await documentClient .update({ TableName: raidsTable.name.get(), @@ -123,19 +142,13 @@ export const createRaidCommand = async ( ExpressionAttributeValues: updateValues.updateExpressionAttributeValues, }) .promise(); + logger.log("info", "created raid", { createdRaid, updateValues, + raidCreateResponse }); - return { - body: { - content: `Event/Raid will start on ${requestedDate}`, - ...raidEmbed, - allowed_mentions: { - parse: [], - }, - }, - }; + return false; }; //https://discordjs.guide/popular-topics/embeds.html#resending-a-received-embed diff --git a/bot/interactions/commands/index.ts b/bot/interactions/commands/index.ts index 7546d53..77c1867 100644 --- a/bot/interactions/commands/index.ts +++ b/bot/interactions/commands/index.ts @@ -1,12 +1,14 @@ import { infoCommand, commandName_info } from "./info/info"; import { requestCommand, commandName_request } from "./request"; import { createCommand, commandName_create } from "./create"; +import { commandName_remove, removeCommand } from "./remove"; import { notRecognized } from "./unrecognized"; export const commandActions = { [commandName_info]: infoCommand, [commandName_request]: requestCommand, [commandName_create]: createCommand, + [commandName_remove]: removeCommand, }; export const unrecognizedCommand = notRecognized; diff --git a/bot/interactions/commands/remove/index.ts b/bot/interactions/commands/remove/index.ts new file mode 100644 index 0000000..4b630ba --- /dev/null +++ b/bot/interactions/commands/remove/index.ts @@ -0,0 +1,47 @@ +import { + APIInteractionGuildMember, + ApplicationCommandType, + REST, + APIChatInputApplicationCommandInteractionData, + } from "discord.js"; + import { Logger } from "winston"; + import { removeRaidUserCommand } from "./raidUser"; + import { unrecognizedCommand } from ".."; + interface factoryInitializations { + logger: Logger; + rest: REST; + interactionConfig: { + application_id: string; + token: string; + member: APIInteractionGuildMember; + channel_id: string, + }; + } + + export const commandName_remove = "remove"; + export const availableSubCommands = { + raid_user: removeRaidUserCommand, + }; + export const recognizedSubCommands = Object.keys(availableSubCommands); + export const removeCommand = async ( + data: APIChatInputApplicationCommandInteractionData & { type: number }, + factoryInits: factoryInitializations + ) => { + const { logger, rest, interactionConfig } = factoryInits; + logger.log("info", `command - ${commandName_remove}`, { data }); + const { options, type } = data; + const [{ name = "" } = {}] = options || []; + if (type !== ApplicationCommandType.ChatInput) { + return { + body: { + content: "Unsupported input used", + }, + }; + } + const subCommandResult = recognizedSubCommands.includes(name) + ? await availableSubCommands[name](data, factoryInits) + : unrecognizedCommand(); + + return subCommandResult; + }; + \ No newline at end of file diff --git a/bot/interactions/commands/remove/raidUser.ts b/bot/interactions/commands/remove/raidUser.ts new file mode 100644 index 0000000..bafb657 --- /dev/null +++ b/bot/interactions/commands/remove/raidUser.ts @@ -0,0 +1,155 @@ +import dayjs from "dayjs-parser/dayjs"; +import { + APIChatInputApplicationCommandInteractionData, + APIApplicationCommandInteractionDataRoleOption, + ApplicationCommandOptionType, + REST, + Routes, + APIInteractionGuildMember, + RESTPostAPIChannelInviteJSONBody, + RESTPostAPIChannelInviteResult, + APIApplicationCommandInteractionDataSubcommandOption, + RESTGetAPIChannelMessageResult, + RESTPostAPIChannelMessageResult, +} from "discord.js"; +import { Logger } from "winston"; +import { raidsTable } from "../../../../pulumi/persistantStore/tables/raids"; +import { raidConfigs } from "../../../embeds/templates/neverwinter/config"; +import { + availableSlotValue, + Category, + determineActions, + getEmbedFieldsSeperatedSections, + getExistingMemberRecordDetails, +} from "../../messageComponents/utils/categorizeEmbedFields/categorizeEmbedFields"; +import { defaultJoinStatus } from "../../messageComponents/utils/helper/embedFieldAttribute"; +import { + createRaidContent, + determineRaidTemplateType, +} from "../../messageComponents/utils/helper/raid"; +import { getRaid } from "../../messageComponents/utils/storeOps/fetchData"; +import { IfactoryInitializations } from "../../typeDefinitions/event"; + +export const removeRaidUserCommand = async ( + data: APIChatInputApplicationCommandInteractionData, + factoryInits: IfactoryInitializations +) => { + const { rest, logger, documentClient, interactionConfig } = factoryInits; + const { guild_id, channel_id } = interactionConfig; + const [{ type, options, name }] = + data.options as APIApplicationCommandInteractionDataSubcommandOption[]; + const [{ type: subCommandType, options: subCommandOptions = [] }] = + data.options as APIApplicationCommandInteractionDataSubcommandOption[]; + const userId = subCommandOptions.find(({ name }) => name === "user")?.value; + const raidId = subCommandOptions.find( + ({ name }) => name === "raid_id" + )?.value; + const reason = subCommandOptions.find(({ name }) => name === "reason")?.value; + const creatorId = interactionConfig.member?.user?.id; + const raidRecord = await getRaid({ raidId, creatorId }, { documentClient }); + + const raidChannelId = raidRecord?.channelId; + const raidMessageId = raidRecord?.messageId; + if (!raidChannelId || !raidMessageId) { + return { + body: { + content: `No record for this raid exists or you are not the creator of this raid. + Unable to remove user <@${userId}>`, + allowed_mentions: { + parse: [], + }, + }, + }; + } + const findRaidMessage = (raidRecord && + (await rest.get( + (Routes as any).channelMessage(raidChannelId, raidMessageId) + ))) as RESTGetAPIChannelMessageResult; + if (!findRaidMessage) { + return { + body: { + content: `Could not find the raid message accociated with this raid Id. + Unable to remove user <@${userId}>`, + allowed_mentions: { + parse: [], + }, + }, + }; + } + const { content, embeds } = findRaidMessage; + const [currentEmbed] = embeds; + const { templateId } = determineRaidTemplateType({ + embedFields: currentEmbed?.fields || [], + }); + const sectionSeperation = raidConfigs[templateId]; + const seperatedSections = getEmbedFieldsSeperatedSections( + currentEmbed.fields || [], + sectionSeperation + ); + logger.log("info", "remove raid user", { findRaidMessage, raidRecord }); + const [ + { + userArtifacts = "", + userExists = false, + userStatus = defaultJoinStatus, + userRecord = {}, + sectionName = Category.WAITLIST, + } = {}, + ] = getExistingMemberRecordDetails(seperatedSections, userId as string); + if (!userExists) { + return { + body: { + content: `Could not find this user <@${userId}> on the embed`, + allowed_mentions: { + parse: [], + }, + }, + }; + } + + const { updatedFieldsList, updatedSections } = determineActions( + seperatedSections, + { + memberId: userId as string, + requestedUserSection: sectionName, + userField: { + inline: true, + name: sectionName, + value: availableSlotValue, + }, + factoryInits, + defaultSeperation: sectionSeperation, + userRemove: true, + } + ); + + const raidEditResponse = (await rest.patch( + (Routes as any).channelMessage(raidChannelId, raidMessageId), + { + body: { + embeds: [{ ...currentEmbed, fields: updatedFieldsList }], + }, + } + )) as RESTPostAPIChannelMessageResult; + + logger.log("info", "removed user", { + raidId, + userId, + data, + findRaidMessage, + raidEditResponse, + }); + const reasonText = reason ? `since he/she ${reason}` : `for no specified reason`; + return { + body: { + content: `<@${ + interactionConfig.member?.user?.id + }> removed user <@${userId}> ${reasonText} + *Please do note that there is a known limitation/bug on discord API which will cause the emojis to not show up once a user is removed. + You could however do any interaction(like press confirm) on the embed after removing a user to make it show the emojis again*`, + allowed_mentions: { + parse: [], + }, + }, + }; +}; diff --git a/bot/interactions/commands/request/builds.ts b/bot/interactions/commands/request/builds.ts index caec6c2..58c9f8f 100644 --- a/bot/interactions/commands/request/builds.ts +++ b/bot/interactions/commands/request/builds.ts @@ -29,8 +29,6 @@ export const buildCommand = async ( factoryInits: factoryInitializations ) => { const { rest, logger, interactionConfig } = factoryInits; - const { guild_id } = interactionConfig; - const resolvedRoles = data.resolved?.roles || {}; const [{ type: subCommandType, options: subCommandOptions = [] }] = data.options as APIApplicationCommandInteractionDataSubcommandOption[]; const userId = subCommandOptions.find(({ name }) => name === "user")?.value; diff --git a/bot/interactions/messageComponents/buttons/raidButton/waitlist.ts b/bot/interactions/messageComponents/buttons/raidButton/waitlist.ts index deaa01d..8567338 100644 --- a/bot/interactions/messageComponents/buttons/raidButton/waitlist.ts +++ b/bot/interactions/messageComponents/buttons/raidButton/waitlist.ts @@ -7,7 +7,7 @@ import { defaultClassName, } from "../../../../embeds/templates/neverwinter/classesList"; import { raidConfigs } from "../../../../embeds/templates/neverwinter/config"; -import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { IfactoryInitializations } from "../../../typeDefinitions/event"; import { Category, determineActions, diff --git a/bot/interactions/messageComponents/buttons/raidButton/wontJoin.ts b/bot/interactions/messageComponents/buttons/raidButton/wontJoin.ts index e96daea..1487309 100644 --- a/bot/interactions/messageComponents/buttons/raidButton/wontJoin.ts +++ b/bot/interactions/messageComponents/buttons/raidButton/wontJoin.ts @@ -1,7 +1,7 @@ import { APIMessageSelectMenuInteractionData } from "discord-api-types/payloads/v10/interactions"; import { EmbedField, Routes } from "discord.js"; import { NeverwinterClassesMap } from "../../../../embeds/templates/neverwinter/classesList"; -import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { IfactoryInitializations } from "../../../typeDefinitions/event"; import { availableSlotValue, Category, diff --git a/bot/interactions/messageComponents/selectMenu/raidSelectMenu/artifactSelect.ts b/bot/interactions/messageComponents/selectMenu/raidSelectMenu/artifactSelect.ts index c569a31..9200848 100644 --- a/bot/interactions/messageComponents/selectMenu/raidSelectMenu/artifactSelect.ts +++ b/bot/interactions/messageComponents/selectMenu/raidSelectMenu/artifactSelect.ts @@ -1,7 +1,7 @@ import { APIMessageSelectMenuInteractionData } from "discord-api-types/payloads/v10/interactions"; import { EmbedField, Routes } from "discord.js"; import { raidConfigs } from "../../../../embeds/templates/neverwinter/config"; -import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { IfactoryInitializations } from "../../../typeDefinitions/event"; import { membersTable } from "../../../../../pulumi/persistantStore/tables/members"; import { setUpdateValues } from "../../../../store/utils"; import { @@ -53,7 +53,7 @@ export const raidArtifactSelect = async ( ); const PersistedClassInfo = await getLastUsersClass(member, { documentClient }); const defaultClassType = - (new Map(NeverwinterClassesMap).get(PersistedClassInfo.className || defaultClass?.value) + (new Map(NeverwinterClassesMap).get(PersistedClassInfo?.className || defaultClass?.value) ?.type as Category) || Category.WAITLIST; const [ { @@ -72,7 +72,7 @@ export const raidArtifactSelect = async ( { fieldName: (userRecord as EmbedField)?.name || - PersistedClassInfo.className || + PersistedClassInfo?.className || (defaultClass?.value as string), optionalClasses }, diff --git a/bot/interactions/messageComponents/selectMenu/raidSelectMenu/classSelect.ts b/bot/interactions/messageComponents/selectMenu/raidSelectMenu/classSelect.ts index 6e3f316..ec42b20 100644 --- a/bot/interactions/messageComponents/selectMenu/raidSelectMenu/classSelect.ts +++ b/bot/interactions/messageComponents/selectMenu/raidSelectMenu/classSelect.ts @@ -6,7 +6,7 @@ import { } from "../../../../embeds/templates/neverwinter/classesList"; import { raidConfigs } from "../../../../embeds/templates/neverwinter/config"; import { membersTable } from "../../../../../pulumi/persistantStore/tables/members"; -import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { IfactoryInitializations } from "../../../typeDefinitions/event"; import { Category, diff --git a/bot/interactions/messageComponents/utils/categorizeEmbedFields/categorizeEmbedFields.ts b/bot/interactions/messageComponents/utils/categorizeEmbedFields/categorizeEmbedFields.ts index f021206..bb321a4 100644 --- a/bot/interactions/messageComponents/utils/categorizeEmbedFields/categorizeEmbedFields.ts +++ b/bot/interactions/messageComponents/utils/categorizeEmbedFields/categorizeEmbedFields.ts @@ -1,5 +1,5 @@ import { APIEmbedField } from "discord-api-types/payloads/v10/channel"; -import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { IfactoryInitializations } from "../../../typeDefinitions/event"; import { extractFieldName } from "../helper/embedFieldAttribute"; import { ActionConditions, diff --git a/bot/interactions/messageComponents/utils/helper/embedFieldAttribute.ts b/bot/interactions/messageComponents/utils/helper/embedFieldAttribute.ts index a8e2f65..4852ffe 100644 --- a/bot/interactions/messageComponents/utils/helper/embedFieldAttribute.ts +++ b/bot/interactions/messageComponents/utils/helper/embedFieldAttribute.ts @@ -31,7 +31,7 @@ export const createFieldName = ( return `<:${name}:${id}>`; }); return foundClassName - ? `${[`<:${name}:${id}>`, ...optionalClassesEmoji].join("|")} ${fieldName}` + ? [`<:${name}:${id}>`, ...optionalClassesEmoji].join("|") : `❔ ${fieldName}`; }; @@ -44,8 +44,8 @@ export const extractFieldName = ( ) => { const splitFieldName = fieldNameText.split(seperator); const [emojis, fieldName] = splitFieldName; - const isEmoji = splitFieldName.length > 1; - const optionalClasses = isEmoji + const isEmoji = /<:.+:(.+)>/gi.test(fieldNameText); + const classList = isEmoji ? emojis .split("|") .map((optionalClassEmoji) => { @@ -55,22 +55,15 @@ export const extractFieldName = ( const classDetails = classNamesList.find(({ emoji: { id, name } }) => { const isValid = id === captureEmojiId; - console.log({ - id, - optionalClassEmoji, - capturedText, - captureEmojiId, - isValid, - }); return isValid; }) || {}; const { value } = classDetails; return value; }) - .slice(1) .filter((valid) => valid) - : []; - return { fieldName: isEmoji ? fieldName : fieldNameText, optionalClasses }; + : [fieldName]; + const [primaryClass, ...optionalClasses] = classList; + return { fieldName: primaryClass, optionalClasses }; }; export const createFieldValue = ({ diff --git a/bot/interactions/messageComponents/utils/helper/raid.ts b/bot/interactions/messageComponents/utils/helper/raid.ts index 7ad5616..a360209 100644 --- a/bot/interactions/messageComponents/utils/helper/raid.ts +++ b/bot/interactions/messageComponents/utils/helper/raid.ts @@ -1,5 +1,5 @@ import { APIEmbedField } from "discord-api-types/payloads/v10/channel"; -import { sectionTitleNames } from "../../../../embeds/templates/neverwinter/raid"; +import { previousSectionTitleNames, sectionTitleNames } from "../../../../embeds/templates/neverwinter/raid"; import { Category } from "../categorizeEmbedFields/categorizeEmbedFields"; import { convertToDiscordDate } from "../date/dateToDiscordTimeStamp"; import { TitleToCategorySectionMapper } from "./userActions"; @@ -50,6 +50,10 @@ export const determineRaidTemplateType = ({ ([sectionName, sectionTitle]) => { return sectionTitle === name; } + ) || Object.entries(previousSectionTitleNames).find( + ([sectionName, sectionTitle]) => { + return sectionTitle === name; + } ) || []; const isNewSection = newSectionTitle && currentSectionCategoryTitle !== newSectionTitle; diff --git a/bot/interactions/messageComponents/utils/helper/userActions.ts b/bot/interactions/messageComponents/utils/helper/userActions.ts index d1361d1..eab34c6 100644 --- a/bot/interactions/messageComponents/utils/helper/userActions.ts +++ b/bot/interactions/messageComponents/utils/helper/userActions.ts @@ -53,7 +53,7 @@ export interface IEmbedFieldActionsArgs { export const isFivePersonDungeon = (title = "") => { const [name] = title.split("-"); - return [trialNamesList.TOSM as string].includes(name); + return [trialNamesList.TOSM as string, trialNamesList.STANDARD_DUNGEON, trialNamesList.VOS].includes(name); }; export const executeEmbedFieldsActions = ({ @@ -219,7 +219,7 @@ export const conditionsToActionsMapper = ( ...seperatedSections[Category.WAITLIST_TITLE][0], value: `\`Capacity: ${ waitListSectioninfo.sectionUserOccupyCount + 1 - } / ${requestedSectionInfo.sectionCapacity}\``, + } / ${waitListSectioninfo.sectionCapacity}\``, }, index: 0, }, diff --git a/bot/interactions/messageComponents/utils/storeOps/fetchData.ts b/bot/interactions/messageComponents/utils/storeOps/fetchData.ts index 21a75c2..0fb6cc0 100644 --- a/bot/interactions/messageComponents/utils/storeOps/fetchData.ts +++ b/bot/interactions/messageComponents/utils/storeOps/fetchData.ts @@ -1,8 +1,12 @@ import { APIInteractionGuildMember } from "discord-api-types/v10"; import { membersTable } from "../../../../../pulumi/persistantStore/tables/members"; +import { raidsTable } from "../../../../../pulumi/persistantStore/tables/raids"; import { fieldSorter } from "../helper/artifactsSorter"; -export const getLastUsersClass = async(member:APIInteractionGuildMember,{documentClient}) => { +export const getLastUsersClass = async ( + member: APIInteractionGuildMember, + { documentClient } +) => { const dbResult = await documentClient .query({ TableName: membersTable.name.get(), @@ -20,3 +24,25 @@ export const getLastUsersClass = async(member:APIInteractionGuildMember,{documen const [Item] = Items.sort(fieldSorter(["-updatedAt", "-createdAt"])); return Item; }; + +export const getRaid = async ({ raidId, creatorId }, { documentClient }) => { + const dbResult = await documentClient + .query({ + TableName: raidsTable.name.get(), + KeyConditionExpression: "#DYNOBASE_raidId = :pkey", + ExpressionAttributeValues: { + ":pkey": raidId, + ":creatorId": creatorId, + }, + ExpressionAttributeNames: { + "#DYNOBASE_raidId": "raidId", + "#DYNOBASE_creatorId": "creatorId", + }, + ScanIndexForward: true, + FilterExpression: "#DYNOBASE_creatorId = :creatorId", + }) + .promise(); + const { Items = [] } = dbResult; + const [Item] = Items; + return Item; +}; diff --git a/bot/interactions/modalSubmit/index.ts b/bot/interactions/modalSubmit/index.ts new file mode 100644 index 0000000..be2a0a4 --- /dev/null +++ b/bot/interactions/modalSubmit/index.ts @@ -0,0 +1,45 @@ +import { + APIInteractionGuildMember, + ApplicationCommandType, + REST, + APIChatInputApplicationCommandInteractionData, +} from "discord.js"; +import { Logger } from "winston"; +import { unrecognizedCommand } from "../commands"; +import { userProfile } from "./userProfile"; +interface factoryInitializations { + logger: Logger; + rest: REST; + documentClient: any, + interactionConfig: { + application_id: string; + token: string; + guild_id: string; + member: APIInteractionGuildMember; + channel_id: string; + message: string; + }; +} + +export const availableModalSubmits = { + user_profile: userProfile, +}; +export const recognizedSubCommands = Object.keys(availableModalSubmits); +export const modalSubmit = async ( + data: APIChatInputApplicationCommandInteractionData & { type: number }, + factoryInits: factoryInitializations +) => { + const { logger, rest, interactionConfig } = factoryInits; + logger.log("info", `submission`, { data }); + const { options, type } = data; + const [{ name = "" } = {}] = options || []; + const subCommandResult = recognizedSubCommands.includes(name) + ? await availableModalSubmits[name](data, factoryInits) + : { + body: { + content: "Unknown submission", + }, + }; + + return subCommandResult; +}; diff --git a/bot/interactions/modalSubmit/userProfile.ts b/bot/interactions/modalSubmit/userProfile.ts new file mode 100644 index 0000000..b1a641c --- /dev/null +++ b/bot/interactions/modalSubmit/userProfile.ts @@ -0,0 +1,3 @@ +export const userProfile = ()=>{ + +} \ No newline at end of file diff --git a/bot/interactions/messageComponents/typeDefinitions/event.ts b/bot/interactions/typeDefinitions/event.ts similarity index 100% rename from bot/interactions/messageComponents/typeDefinitions/event.ts rename to bot/interactions/typeDefinitions/event.ts diff --git a/bot/interactions/verify.ts b/bot/interactions/verify.ts index 8717a21..988b609 100644 --- a/bot/interactions/verify.ts +++ b/bot/interactions/verify.ts @@ -3,6 +3,7 @@ import { APIGatewayProxyEvent } from "aws-lambda"; import winston from "winston"; import { verifyKey } from "discord-interactions"; import { InteractionType } from "discord.js"; +import { userProfile } from "../modals/profile" export const verifyRequest = ( event: APIGatewayProxyEvent, factory: { logger: winston.Logger; strBody: string } @@ -40,16 +41,37 @@ export const verifyRequest = ( body: JSON.stringify({ type: 1 }), }; } + const requiresModalCommand = ["create profile"]; + const subCommandName = body?.data?.options?.[0]?.name; + const commandName = [body?.data?.name, subCommandName].join(" "); + const isModalInteraction = requiresModalCommand.includes(commandName); const isMessageComponent = InteractionType.MessageComponent === body.type; + const responseType = isModalInteraction ? "modalInteraction" : isMessageComponent ? "messageComponent" : "default"; + const responseTypes = { + messageComponent: 6, + modalInteraction: 9, + default: 4 + } + logger.log("info", "command info", { + responseType, + isModalInteraction, + isMessageComponent, + commandName + }); return { statusCode: 200, body: JSON.stringify({ - type: isMessageComponent ? 6 : 4, - ...(!isMessageComponent && { + type: responseTypes[responseType], + ...(!isModalInteraction && !isMessageComponent && { data: { content: `Please wait while I make some 🥞 pancakes...`, }, }), + ...(isModalInteraction) && { + data: { + ...userProfile, + } + }, }), }; }; diff --git a/bot/modals/profile.ts b/bot/modals/profile.ts new file mode 100644 index 0000000..6075f3f --- /dev/null +++ b/bot/modals/profile.ts @@ -0,0 +1,21 @@ +export const userProfile = { + title: "My Profile", + custom_id: "user_profile", + components: [ + { + type: 1, + components: [ + { + type: 4, + custom_id: "handle_name", + label: "In Game @Handle", + style: 1, + min_length: 4, + max_length: 40, + placeholder: "@dasi123", + required: false, + }, + ], + }, + ], +}; diff --git a/bot/registerCommands/commands.ts b/bot/registerCommands/commands.ts index 066f990..9e5704e 100644 --- a/bot/registerCommands/commands.ts +++ b/bot/registerCommands/commands.ts @@ -56,12 +56,77 @@ export const request_role = { ], }; +export const remove_raidUser = { + name: "remove", + description: "remove a user", + options: [ + { + type: 1, + name: "raid_user", + description: "remove a raid user", + options: [ + { + type: 6, + name: "user", + description: "The user you want to remove", + required: true, + }, + { + type: 3, + name: "raid_id", + description: "The id of the raid you want the user removed from", + required: true, + }, + { + type: 3, + name: "reason", + description: "Reason for removing", + required: false, + choices: [ + { + name: "did not show up", + value: "did not show up", + }, + { + name: "informed Unable to join", + value: "informed Unable to join", + }, + { + name: "did not meet requirements", + value: "did not meet requirements", + }, + { + name: "was wiping floor all day", + value: "was wiping floor all day", + }, + { + name: "is too ugly", + value: "is too ugly", + }, + { + name: "coulden't hold aggro", + value: "coulden't hold aggro", + }, + { + name: "ignores mechanics", + value: "ignores mechanics", + }, + ], + }, + ], + }, + ] + }; + export enum trialNamesList { TOMM = "Tower of the mad mage", ZCM = "Zariel's Challenge(Master)", COKM = "Crown of Keldegonn(Master)", TM = "Tiamat(Master)", TOSM = "Temple of Spider(Master)", + VOS = "Vault of Stars", + STANDARD_DUNGEON = "Standard Dungeon", + STANDARD_TRIAL = "Standard Trial" } export const createRaidNameChoicesList = [ @@ -89,6 +154,18 @@ export const createRaidNameChoicesList = [ name: trialNamesList.TOSM, value: trialNamesList.TOSM, }, + { + name: trialNamesList.VOS, + value: trialNamesList.VOS, + }, + { + name: trialNamesList.STANDARD_DUNGEON, + value: trialNamesList.STANDARD_DUNGEON, + }, + { + name: trialNamesList.STANDARD_TRIAL, + value: trialNamesList.STANDARD_TRIAL, + }, ]; export const create_raid = { @@ -181,5 +258,10 @@ export const create_raid = { }, ], }, + { + type: 1, + name: "profile", + description: "Setup your profile" + }, ], }; diff --git a/bot/registerCommands/sync.ts b/bot/registerCommands/sync.ts index 0bdb8fe..ec7b331 100644 --- a/bot/registerCommands/sync.ts +++ b/bot/registerCommands/sync.ts @@ -1,14 +1,19 @@ import { Logger } from "winston"; import { REST } from "@discordjs/rest"; import { Routes } from "discord.js"; -import { info, request_role, create_raid } from "./commands"; +import { info, request_role, create_raid, remove_raidUser } from "./commands"; export const syncDiscordCommands = async ( { discordBotToken, discordApplicationID, discordServerId }, { logger }: { logger: Logger } ) => { const rest = new REST({ version: "10" }).setToken(discordBotToken); - const registerableCommandsList = [info, request_role, create_raid]; + const registerableCommandsList = [ + remove_raidUser, + info, + request_role, + create_raid, + ]; try { const response = await rest.put( Routes.applicationCommands(discordApplicationID), diff --git a/modules/discordEventsProcessor/applicationCommand.ts b/modules/discordEventsProcessor/applicationCommand.ts index 54ea057..db0e977 100644 --- a/modules/discordEventsProcessor/applicationCommand.ts +++ b/modules/discordEventsProcessor/applicationCommand.ts @@ -28,9 +28,11 @@ export const applicationCommands = async ( commandName: data.name, commandResponse, }); - const responseResult = await rest.patch( - (Routes as any).webhookMessage(application_id, token), - commandResponse - ); + const responseResult = commandResponse + ? await rest.patch( + (Routes as any).webhookMessage(application_id, token), + commandResponse + ) + : false; return responseResult; }; diff --git a/modules/discordEventsProcessor/index.ts b/modules/discordEventsProcessor/index.ts index 21f1282..c7f2151 100644 --- a/modules/discordEventsProcessor/index.ts +++ b/modules/discordEventsProcessor/index.ts @@ -6,9 +6,11 @@ import { getEnvironmentVariables } from "../../bot/configs"; import { applicationCommands } from "./applicationCommand"; import { messageComponent } from "./messageComponent"; import warmer from "lambda-warmer"; +import { modal } from "./modal"; export const discordInteractionEventHandler = { [InteractionType.ApplicationCommand]: applicationCommands, [InteractionType.MessageComponent]: messageComponent, + [InteractionType.ModalSubmit]: modal }; export const supportedInteractionTypes = Object.keys( diff --git a/modules/discordEventsProcessor/modal.ts b/modules/discordEventsProcessor/modal.ts new file mode 100644 index 0000000..0323047 --- /dev/null +++ b/modules/discordEventsProcessor/modal.ts @@ -0,0 +1,34 @@ +import { Routes, ComponentType } from "discord.js"; +import { modalSubmit } from "../../bot/interactions/modalSubmit/index" + +export const modal = async ( + { data, application_id, token, member, channel_id, guild_id, message }, + { logger, rest, documentClient } +) => { + logger.log("info", "modal submit", { + data, + }); + + const handlerResponse = await modalSubmit(data, { + logger, + rest, + documentClient, + interactionConfig: { + application_id, + token, + member, + guild_id, + channel_id, + message, + }, + }); + const responseResult = await rest.patch( + (Routes as any).webhookMessage(application_id, token), + handlerResponse || { + body: { + content: "Unknown Interaction", + }, + } + ); + return responseResult; +}; diff --git a/modules/httpEventsProcessor/index.ts b/modules/httpEventsProcessor/index.ts index 561b842..b7b79b5 100644 --- a/modules/httpEventsProcessor/index.ts +++ b/modules/httpEventsProcessor/index.ts @@ -75,7 +75,6 @@ export const httpEventsHandler = async ( * return a wrapped main function with the initialized * variables passed on * */ - export const httpEventsFactoryHandler = (config: any) => { const { getLogger } = startBot(); const logger = getLogger(); diff --git a/package.json b/package.json index 8876d4e..da067c5 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "distDir": "./build/utils", "isLibrary": true, "optimize": false, - "outputFormat": "esmodule", + "outputFormat": "commonjs", "engines": { "node": ">= 16" } @@ -56,7 +56,6 @@ }, "author": "dasith kuruppu", "license": "MIT", - "type": "module", "dependencies": { "@aws-sdk/client-sqs": "^3.170.0", "@discordjs/rest": "^1.1.0", diff --git a/utils/updateDiscordCommands.ts b/utils/updateDiscordCommands.ts index e7bcd9b..6fc707a 100644 --- a/utils/updateDiscordCommands.ts +++ b/utils/updateDiscordCommands.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { initializeAll } from "../bot/initializations"; import { syncDiscordCommands } from "../bot/registerCommands/sync"; import { getEnvironmentVariables } from "../bot/configs";