diff --git a/bot/embeds/templates/neverwinter/availabilityBuilder.ts b/bot/embeds/templates/neverwinter/availabilityBuilder.ts new file mode 100644 index 0000000..a29416b --- /dev/null +++ b/bot/embeds/templates/neverwinter/availabilityBuilder.ts @@ -0,0 +1,81 @@ +import { convertToDiscordDate } from "../../../interactions/messageComponents/utils/date/dateToDiscordTimeStamp"; + +const availabilityInfo = ({ + userId, + availabilityList, + prefferedRaids, + timestamp = new Date(), + requestedTime = Date.now(), +}) => { + return { + type: "rich", + title: `Raid summary for the upcoming week`, + description: `<@${userId}}> availablility for raids\n + Last updated - ${convertToDiscordDate("now", { + relative: true, + })} + `, + color: 0xffa200, + timestamp, + // thumbnail: { + // url: `https://www.dasithsblog.com/images/Ranger-Hunter-Build.png`, + // height: 0, + // width: 0, + // }, + // author: { + // name: `${userName}`, + // }, + fields: availabilityList.map( + ({ + availableDay, + availableStartTime, + availableDuration, + }) => { + return { + name: `Availablility`, + value: `${availableDay} ${availableStartTime} ${availableDuration}`, + inline: false, + }; + } + ), + // footer: { + // text: `Footer text`, + // }, + }; +}; + +export const availabilityBuilder = ({ + userName, + userId, + availabilityList, + prefferedRaids = [], +}) => { + const availabilityInfoEmbed = availabilityInfo({ + userId, + availabilityList, + prefferedRaids, + }); + console.log({ prefferedRaids }); + return { + components: [ + { + type: 1, + components: [ + { + style: 3, + label: `Refresh`, + emoji: { + id: `1074205923154858014`, + name: `refresh`, + animated: false, + }, + custom_id: `button_raidavailability_refresh`, + disabled: false, + type: 2, + }, + ], + }, + ], + embeds: [availabilityInfoEmbed], + }; +}; diff --git a/bot/embeds/templates/neverwinter/profile.ts b/bot/embeds/templates/neverwinter/profile.ts index 8a84bf9..a1e72f5 100644 --- a/bot/embeds/templates/neverwinter/profile.ts +++ b/bot/embeds/templates/neverwinter/profile.ts @@ -1,11 +1,16 @@ import { displayMountsAsEmoji } from "../../../interactions/messageComponents/utils/helper/artifactsRenderer"; +import { createRaidNameChoicesList } from "../../../registerCommands/commands"; import { MountsList } from "../mountsList"; const userProfile = ({ userId, userName, userStatus, + timeZone, activityList, + prefferedRaids, + availabilityList, + preferredRunTypes, mountsList = [], rankList, classesPlayed, @@ -37,7 +42,7 @@ const userProfile = ({ inline: false, }, { - name: `Account wide mounts`, + name: `Mounts`, value: mountsList.length ? displayMountsAsEmoji(mountsList).join("|") : "-", @@ -53,6 +58,38 @@ const userProfile = ({ value: hasActivityList ? processedActivityList.join("\n") : "-", inline: false, }, + { + name: `Time Zone`, + value: timeZone ? `> ${timeZone?.label}` : "-", + inline: false, + }, + { + name: "Availablity", + value: availabilityList.length ? availabilityList + .map(({ startTime, endTime }) => { + return `> to `; + }) + .join("\n") : `> /request update_user_availability command can be used to update your availability here`, + inline: false, + }, + { + name: "Preferred Raids", + value: prefferedRaids.length ? prefferedRaids + .map((name) => { + return `> ${name}`; + }) + .join("\n"): "-", + inline: false, + }, + { + name: "Preferred Raid Types", + value: preferredRunTypes.length ? preferredRunTypes + .map((name) => { + return `> ${name}`; + }) + .join("\n"): "-", + inline: false, + }, ], // footer: { // text: `Footer text`, @@ -115,10 +152,64 @@ export const getProfileStatusesList = () => [ shortName: UserStatusValues.RANKI, }, ]; +export const getTimeZones = () => [ + { + label: "Central Standard Time (NA) [GMT-6]", + value: "GMT-06:00", + }, + { + label: "Eastern Standard Time (NA) [GMT-5]", + value: "GMT-05:00", + }, + { + label: "Pacific Standard Time (NA) [GMT-8]", + value: "GMT-08:00", + }, + { + label: "Moscow Standard Time (Russia) [GMT+3]", + value: "GMT+03:00", + }, + { + label: "Greenwich Mean Time (UTC) [GMT+0]", + value: "GMT+0:00", + }, + { + label: "Eastern European/Central Africa/Isreal Time [GMT+2]", + value: "GMT+02:00", + }, + { + label: "Central European/British/West Africa Time [GMT+1]", + value: "GMT+01:00", + }, + { + label: "Australian Central Standard Time [GMT+9:30]", + value: "GMT+9:30", + }, + { + label: "Brazil Time(Sao Paulo) [GMT-03:00]", + value: "GMT-03:00", + }, + { + label: "Australian Eastern Standard/Hawaii Time [GMT+10:00]", + value: "GMT+10:00", + }, + { + label: "Singapore Time [GMT+8:00]", + value: "GMT+8:00", + }, + { + label: "Indian Standard Time [GMT+5:30]", + value: "GMT+5:30", + }, +]; export const profileBuilder = ({ userId, userName, + prefferedRaids, + preferredRunTypes, + timeZone, activityList, + availabilityList, mountsList, rankList, userStatus, @@ -127,10 +218,14 @@ export const profileBuilder = ({ }) => { const userProfileEmbed = userProfile({ userId, + timeZone, userName, + prefferedRaids, + preferredRunTypes, activityList, mountsList, userStatus, + availabilityList, rankList, classesPlayed, trialsParticipatedOn, @@ -159,20 +254,67 @@ export const profileBuilder = ({ type: 1, components: [ { - custom_id: `select_profile_mounts`, - placeholder: `Select mounts`, - options: MountsList.map(({ label, shortName, emoji }) => ({ + custom_id: `select_profile_timezone`, + placeholder: `Select Timezone`, + options: getTimeZones().map(({ label, value }) => ({ label, - value: shortName, - emoji, + value, default: false, })), - min_values: 1, + min_values: 0, + type: 3, + }, + ], + }, + { + type: 1, + components: [ + { + custom_id: `select_preferred_raids`, + placeholder: `Select Preferred Raids`, + options: createRaidNameChoicesList.map(({ name, value }) => ({ + label: name, + value, + default: false, + })), + min_values: 0, max_values: 5, type: 3, }, ], }, + { + type: 1, + components: [ + { + custom_id: `select_preferred_raid_types`, + placeholder: `Select Preferred Raid Types`, + options: [ + { + label: "Training", + value: "Training", + default: false, + // description: "Set type of raid to a training run", + }, + { + label: "Farm", + value: "Farm", + default: false, + // description: "Set type of raid to a farm run", + }, + { + label: "Wipe", + value: "Wipe", + default: false, + // description: "Set type of raid to a wipe with a mix of experienced and inexperienced players", + }, + ], + min_values: 0, + max_values: 3, + type: 3, + }, + ], + }, ], embeds: [userProfileEmbed], }; diff --git a/bot/embeds/templates/serverProfile/getServerProfile.ts b/bot/embeds/templates/serverProfile/getServerProfile.ts index c38b9a0..72254e1 100644 --- a/bot/embeds/templates/serverProfile/getServerProfile.ts +++ b/bot/embeds/templates/serverProfile/getServerProfile.ts @@ -115,7 +115,7 @@ export const serverProfileBuilder = ({ serverRoles, activityList, }); - const filteredRoles = guildRoles.filter(({ name }) => name !== "@everyone"); + const filteredRoles = guildRoles.filter(({ name }) => name !== "@everyone").slice(0,24); const maxRoles = filteredRoles?.length > 10 ? 10 : filteredRoles?.length; return { components: [ diff --git a/bot/interactions/commands/create/raid.ts b/bot/interactions/commands/create/raid.ts index a1df48a..484d259 100644 --- a/bot/interactions/commands/create/raid.ts +++ b/bot/interactions/commands/create/raid.ts @@ -65,7 +65,8 @@ export const createRaidCommand = async ( const nameToCoverUrl = { [trialNamesList.TOMM]: "https://pwimages-a.akamaihd.net/arc/8d/5d/8d5d88772e1edccad4f98cb882677a5e1564178653.jpg", - [trialNamesList.GAZEMNIDS_RELIQUARY_M]: "https://i.ibb.co/qsVXXJJ/img-atd-Npq30-Pp-Rdfo-Vw-MN6yzl-RR.png", + [trialNamesList.GAZEMNIDS_RELIQUARY_M]: + "https://i.ibb.co/qsVXXJJ/img-atd-Npq30-Pp-Rdfo-Vw-MN6yzl-RR.png", [trialNamesList.ZCM]: "https://static.wikia.nocookie.net/dungeonsdragons/images/4/43/Zariel.jpg/revision/latest?cb=20200408175529", [trialNamesList.COKM]: @@ -88,7 +89,9 @@ export const createRaidCommand = async ( { discordServerId: interactionConfig.guild_id }, { documentClient } ); - const processedTime = normalizeTime(dateTime, { offSet: serverProfile?.timezoneOffset }); + const processedTime = normalizeTime(dateTime, { + offSet: serverProfile?.timezoneOffset, + }); const isFivePerson = isFivePersonDungeon(title); const requestedDate = convertToDiscordDate(processedTime); const requestedRelativeDate = convertToDiscordDate(processedTime, { @@ -106,8 +109,8 @@ export const createRaidCommand = async ( 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: 6 }, - Three_heal: {DPS: 5, HEALS: 3, TANKS: 2, WAITLIST: 6 }, - Three_heal_one_tank: {DPS: 6, HEALS: 3, TANKS: 1, WAITLIST: 6 }, + Three_heal: { DPS: 5, HEALS: 3, TANKS: 2, WAITLIST: 6 }, + Three_heal_one_tank: { DPS: 6, HEALS: 3, TANKS: 1, WAITLIST: 6 }, }; const uniqueRaidId = new ShortUniqueId({ length: 10 })(); const raidEmbed = raidBuilder({ diff --git a/bot/interactions/commands/request/index.ts b/bot/interactions/commands/request/index.ts index 0cc41e9..a9a3f59 100644 --- a/bot/interactions/commands/request/index.ts +++ b/bot/interactions/commands/request/index.ts @@ -6,11 +6,12 @@ import { } from "discord.js"; import { Logger } from "winston"; import { requestRoleCommand } from "./role"; -import { inviteLinkCommand} from "./inviteLink" +import { inviteLinkCommand } from "./inviteLink"; import { buildCommand } from "./builds"; import { profileCommand } from "./profile"; -import { serverProfileCommand } from "./serverProfile" -import { raidSummaryCommand } from "./raidSummary" +import { serverProfileCommand } from "./serverProfile"; +import { raidSummaryCommand } from "./raidSummary"; +import { updateUserAvailabilityCommand } from "./updateUserAvailablity"; import { unrecognizedCommand } from ".."; interface factoryInitializations { logger: Logger; @@ -29,8 +30,10 @@ export const availableSubCommands = { build: buildCommand, profile: profileCommand, server_profile: serverProfileCommand, - raid_summary: raidSummaryCommand + raid_summary: raidSummaryCommand, + update_user_availability: updateUserAvailabilityCommand, }; + export const recognizedSubCommands = Object.keys(availableSubCommands); export const requestCommand = async ( data: APIChatInputApplicationCommandInteractionData & { type: number }, diff --git a/bot/interactions/commands/request/profile.ts b/bot/interactions/commands/request/profile.ts index 8fc7023..f908572 100644 --- a/bot/interactions/commands/request/profile.ts +++ b/bot/interactions/commands/request/profile.ts @@ -12,8 +12,12 @@ import { getNewClassOptionsList, getOptionsList, } from "../../../embeds/templates/neverwinter/classesList"; -import { profileBuilder, UserStatusValues } from "../../../embeds/templates/neverwinter/profile"; -import { convertToDiscordDate } from "../../messageComponents/utils/date/dateToDiscordTimeStamp"; +import { + getTimeZones, + profileBuilder, + UserStatusValues, +} from "../../../embeds/templates/neverwinter/profile"; +import { convertToDiscordDate, normalizeTime } from "../../messageComponents/utils/date/dateToDiscordTimeStamp"; import { displayArtifactAsEmoji } from "../../messageComponents/utils/helper/artifactsRenderer"; import { fieldSorter } from "../../messageComponents/utils/helper/artifactsSorter"; import { @@ -26,8 +30,13 @@ import { ACTIVITY_STATUS, getMemberActions, } from "../../messageComponents/utils/storeOps/memberActions"; -import { listUserStatusChanges, userStatusCodes } from "../../messageComponents/utils/storeOps/userStatus"; +import { + listUserStatusChanges, + userStatusCodes, +} from "../../messageComponents/utils/storeOps/userStatus"; import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { getUserProfile } from "../../messageComponents/utils/storeOps/userProfile"; +import { getUserAvailability } from "../../messageComponents/utils/storeOps/userAvailability"; export const profileCommand = async ( data: APIChatInputApplicationCommandInteractionData, @@ -85,7 +94,7 @@ export const profileCommand = async ( documentClient, } ); - if(!lastUserClassActivity){ + if (!lastUserClassActivity) { return { body: { content: `Requested profile of <@${userId}> \n > No data records was found for this user`, @@ -106,7 +115,6 @@ export const profileCommand = async ( documentClient, }); - const RankIList = userStatusChanges.filter( ({ statusCode }) => statusCode === userStatusCodes.RANK_I ); @@ -155,11 +163,40 @@ export const profileCommand = async ( const [firstRank] = rankList; - const firstRankStatusCode = firstRank?.userStatusCode || userStatusCodes.RANK_I; + const firstRankStatusCode = + firstRank?.userStatusCode || userStatusCodes.RANK_I; + + const [userProfile, userAvailability] = await Promise.all([ + getUserProfile({ discordMemberId: userId }, { documentClient }), + getUserAvailability(userId, { documentClient }), + ]); + + const processedUserAvailability = userAvailability.map( + ({ availableDayUTC, availableStartTimeUTC, availableDuration }) => { + + const normalizedDate = normalizeTime( + `${availableDayUTC} ${availableStartTimeUTC}`, + { offSet: "GMT+0:00" } + ); + const epochTime = Math.floor(normalizedDate.getTime() / 1000); + return { + startTime: epochTime, + endTime: epochTime + Number(availableDuration) * 60 * 60, + } + } + ); + const timeZone = getTimeZones().find(({ value }) => { + return value === userProfile?.timezoneOffset; + }); + const buildData = profileBuilder({ userId, userName, + availabilityList: processedUserAvailability, + prefferedRaids: userProfile?.prefferedTrials || [], + preferredRunTypes: userProfile?.preferredRunTypes || [], activityList: processedMemberActivity, + timeZone, rankList: rankList.map( ({ userStatusCode, upvotesCount, userStatusText }) => { return `> ${statusSymbols[userStatusCode]} - ${upvotesCount} votes`; @@ -168,7 +205,9 @@ export const profileCommand = async ( mountsList: lastUserClassActivity.mountsList, userStatus: statusSymbols[firstRankStatusCode], trialsParticipatedOn: [], - classesPlayed: [`Class (Main and Optional): ${defaultClassesSelected} \n\u200b\n Artifacts: ${artifactsEmoji}`], + classesPlayed: [ + `Class (Main and Optional): ${defaultClassesSelected} \n\u200b\n Artifacts: ${artifactsEmoji}`, + ], }); return { diff --git a/bot/interactions/commands/request/updateUserAvailablity.ts b/bot/interactions/commands/request/updateUserAvailablity.ts new file mode 100644 index 0000000..90773ff --- /dev/null +++ b/bot/interactions/commands/request/updateUserAvailablity.ts @@ -0,0 +1,110 @@ +import { + APIChatInputApplicationCommandInteractionData, + ApplicationCommandOptionType, + REST, + Routes, + APIInteractionGuildMember, + APIApplicationCommandInteractionDataSubcommandOption, +} from "discord.js"; +import { + getUserAvailability, + removeUserAvailability, + updateUserAvailability, +} from "../../messageComponents/utils/storeOps/userAvailability"; +import { Logger } from "winston"; +import { availabilityBuilder } from "../../../embeds/templates/neverwinter/availabilityBuilder"; +import { IfactoryInitializations } from "../../typeDefinitions/event"; +import { + dayOfWeekAsString, + normalizeTime, +} from "../../messageComponents/utils/date/dateToDiscordTimeStamp"; +import { getUserProfile } from "../../messageComponents/utils/storeOps/userProfile"; + +export const updateUserAvailabilityCommand = async ( + data: APIChatInputApplicationCommandInteractionData, + factoryInits: IfactoryInitializations +) => { + const { rest, logger, interactionConfig, documentClient } = factoryInits; + const [{ type: subCommandType, options: subCommandOptions = [] }] = + data.options as APIApplicationCommandInteractionDataSubcommandOption[]; + const userId = + (subCommandOptions.find(({ name }) => name === "user")?.value as string) || + (interactionConfig?.member.user.id as string); + const resolvedMembers = data.resolved?.users || {}; + const member = resolvedMembers?.[userId] + ? { user: resolvedMembers?.[userId] } + : interactionConfig?.member; + const userName = member?.user.username; + const availableDay = subCommandOptions.find(({ name }) => name === "day") + ?.value as string; + const availableStartTime = subCommandOptions.find( + ({ name }) => name === "start_time" + )?.value as string; + const availableDuration = subCommandOptions.find( + ({ name }) => name === "number_of_hours" + )?.value as string; + + const [userProfile, userAvailability] = await Promise.all([ + getUserProfile({ discordMemberId: userId }, { documentClient }), + getUserAvailability(userId, { documentClient }), + ]); + const offSet = userProfile?.timezoneOffset || "GMT+0:00"; + const normalizedDate = normalizeTime( + `${availableDay} ${availableStartTime}`, + { offSet } + ); + const normalizedDay = normalizedDate.getUTCDay(); + const normalizedHours = normalizedDate.getUTCHours(); + const normalizedMins = + normalizedDate.getUTCMinutes() == 0 ? "00" : normalizedDate.getUTCMinutes(); + const normalizeDayAsString = dayOfWeekAsString(normalizedDay); + const foundCurrentRecord = userAvailability.find(({ availableDayUTC }) => { + return availableDayUTC === normalizeDayAsString; + }); + logger.log("info", "UserAvailability", { + foundCurrentRecord, + userAvailability, + }); + const deleteRecord = foundCurrentRecord && Number(availableDuration) == 0; + if (deleteRecord) { + const removedRecord = await removeUserAvailability( + { discordMemberId: userId, day: normalizeDayAsString }, + { documentClient } + ); + logger.log("info", { + removedRecord, + }); + } else { + const updatedUserAvailability = await updateUserAvailability( + { + discordMemberId: userId, + updates: { + availableDayUTC: normalizeDayAsString, + availableStartTimeUTC: `${normalizedHours}:${normalizedMins}`, + availableDuration: availableDuration, + }, + }, + { documentClient } + ); + logger.log("info", { + subCommandOptions, + member, + updatedUserAvailability, + }); + } + const epochTime = Math.floor(normalizedDate.getTime() / 1000); + + const startTime = epochTime; + const endTime = epochTime + Number(availableDuration) * 60 * 60; + + return { + body: { + content: deleteRecord + ? `Deleted availability of <@${userId}> from ` + : `Updated availability of <@${userId}> from > to `, + allowed_mentions: { + parse: [], + }, + }, + }; +}; diff --git a/bot/interactions/messageComponents/selectMenu/index.ts b/bot/interactions/messageComponents/selectMenu/index.ts index 93f30d0..e1125fc 100644 --- a/bot/interactions/messageComponents/selectMenu/index.ts +++ b/bot/interactions/messageComponents/selectMenu/index.ts @@ -6,7 +6,9 @@ import { raidMountSelectId, raidMounttSelect } from "./raidSelectMenu/mountSelec import { profileStatusVoteId, profileStatusVote } from "./profileSelectMenu/statusVoteSelect"; import { serverProfileTZSelect, timezoneSelect } from "./serverProfileSelectMenu/serverProfileSelectTimezone" import { serverProfileRoleSelect, serverProfileRoles} from "./serverProfileSelectMenu/selectUserRoles"; -import { profileMountSelect, profileMountsSelectId } from "./profileSelectMenu/selectMounts"; +import { profileTimeZoneSelect, profileTimezoneSelectId } from "./profileSelectMenu/selectTimeZone"; +import { profilePrefferredRaidsId, profilePrefferredRaidsSelect} from "./profileSelectMenu/selectPreferredRaids"; +import { profilePrefferredRaidTypesId, profilePrefferredRaidTypesSelect } from "./profileSelectMenu/selectPrefferedRaidTypes"; export const selectMenusInteractions = { [raidArtifactSelectId]: raidArtifactSelect, @@ -14,8 +16,10 @@ export const selectMenusInteractions = { [raidMountSelectId]: raidMounttSelect, [profileStatusVoteId]: profileStatusVote, [serverProfileTZSelect]: timezoneSelect, - [profileMountsSelectId]: profileMountSelect, - [serverProfileRoles]: serverProfileRoleSelect + [profileTimezoneSelectId]: profileTimeZoneSelect, + [serverProfileRoles]: serverProfileRoleSelect, + [profilePrefferredRaidsId]: profilePrefferredRaidsSelect, + [profilePrefferredRaidTypesId]: profilePrefferredRaidTypesSelect }; export const recognizedMenuInteractionComponentIds = Object.keys( diff --git a/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectMounts.ts b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectPreferredRaids.ts similarity index 76% rename from bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectMounts.ts rename to bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectPreferredRaids.ts index 2b96f9d..c7b8fb7 100644 --- a/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectMounts.ts +++ b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectPreferredRaids.ts @@ -9,7 +9,7 @@ import { updateuserStatus, userStatusCodes, } from "../../utils/storeOps/userStatus"; -import { updateMemberDetails } from "../../../../interactions/messageComponents/utils/storeOps/members"; +import { updateMemberDetails } from "../../utils/storeOps/members"; import { createFieldName, createFieldValue, @@ -24,6 +24,7 @@ import { } from "../../../../embeds/templates/neverwinter/classesList"; import { getLastUsersClass } from "../../utils/storeOps/fetchData"; import { + getTimeZones, profileBuilder, UserStatusValues, } from "../../../../embeds/templates/neverwinter/profile"; @@ -33,9 +34,15 @@ import { } from "../../utils/storeOps/memberActions"; import { displayArtifactAsEmoji } from "../../utils/helper/artifactsRenderer"; import { fieldSorter } from "../../utils/helper/artifactsSorter"; -export const profileMountsSelectId = "select_profile_mounts"; +import { + getUserProfile, + updateUserProfile, +} from "../../utils/storeOps/userProfile"; +import { getUserAvailability } from "../../utils/storeOps/userAvailability"; +import { normalizeTime } from "../../utils/date/dateToDiscordTimeStamp"; +export const profilePrefferredRaidsId = "select_preferred_raids"; -export const profileMountSelect = async ( +export const profilePrefferredRaidsSelect = async ( data: APIMessageSelectMenuInteractionData, factoryInits: IfactoryInitializations ) => { @@ -60,7 +67,7 @@ export const profileMountSelect = async ( .trim() .split(" "); const userId = userDiscordId.substring(2, userDiscordId.length - 1); - const selectedMountsValues = data.values; + const selectedPreferredRaids = data.values; const statusUpvotes = await listUserStatusChanges(userId, { documentClient, }); @@ -73,12 +80,19 @@ export const profileMountSelect = async ( ); const isUserAllowed = userId === member.user.id; - - isUserAllowed && await updateMemberDetails( - userId, - { mountsList: selectedMountsValues }, - { documentClient } - ); + + isUserAllowed && + (await updateUserProfile( + { + discordMemberId: userId, + updates: { + userName: member?.user?.username, + updatedAt: new Date().toISOString(), + prefferedTrials: selectedPreferredRaids || [], + }, + }, + { documentClient } + )); const lastUserClassActivity = await getLastUsersClass( { user: { id: userId } } as APIInteractionGuildMember, { @@ -170,8 +184,34 @@ export const profileMountSelect = async ( const artifactsEmoji = displayArtifactAsEmoji(artifactsList).join("|"); const firstRankStatusCode = firstRank?.userStatusCode || userStatusCodes.RANK_I; + + const [userProfile, userAvailability] = await Promise.all([ + getUserProfile({ discordMemberId: userId }, { documentClient }), + getUserAvailability(userId, { documentClient }), + ]); + + const processedUserAvailability = userAvailability.map( + ({ availableDayUTC, availableStartTimeUTC, availableDuration }) => { + const normalizedDate = normalizeTime( + `${availableDayUTC} ${availableStartTimeUTC}`, + { offSet: "GMT+0:00" } + ); + const epochTime = Math.floor(normalizedDate.getTime() / 1000); + return { + startTime: epochTime, + endTime: epochTime + (Number(availableDuration) * 60 * 60), + }; + } + ); + const timeZone = getTimeZones().find(({ value }) => { + return value === userProfile?.timezoneOffset; + }); const buildData = profileBuilder({ userId, + timeZone, + prefferedRaids: userProfile?.prefferedTrials || [], + preferredRunTypes: userProfile?.preferredRunTypes || [], + availabilityList: processedUserAvailability, userName: processedProfileUserName, activityList: processedMemberActivity, trialsParticipatedOn: [], diff --git a/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectPrefferedRaidTypes.ts b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectPrefferedRaidTypes.ts new file mode 100644 index 0000000..759f7ac --- /dev/null +++ b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectPrefferedRaidTypes.ts @@ -0,0 +1,236 @@ +import { + APIInteractionGuildMember, + APIMessageSelectMenuInteractionData, + } from "discord-api-types/payloads/v10/interactions"; + import { EmbedField, Routes } from "discord.js"; + import { IfactoryInitializations } from "../../../typeDefinitions/event"; + import { + listUserStatusChanges, + updateuserStatus, + userStatusCodes, + } from "../../utils/storeOps/userStatus"; + import { updateMemberDetails } from "../../utils/storeOps/members"; + import { + createFieldName, + createFieldValue, + defaultJoinStatus, + statusSymbols, + } from "../../utils/helper/embedFieldAttribute"; + + import { + defaultClassName, + getOptionsList, + NeverwinterClassesMap, + } from "../../../../embeds/templates/neverwinter/classesList"; + import { getLastUsersClass } from "../../utils/storeOps/fetchData"; + import { + getTimeZones, + profileBuilder, + UserStatusValues, + } from "../../../../embeds/templates/neverwinter/profile"; + import { + ACTIVITY_STATUS, + getMemberActions, + } from "../../utils/storeOps/memberActions"; + import { displayArtifactAsEmoji } from "../../utils/helper/artifactsRenderer"; + import { fieldSorter } from "../../utils/helper/artifactsSorter"; + import { + getUserProfile, + updateUserProfile, + } from "../../utils/storeOps/userProfile"; + import { getUserAvailability } from "../../utils/storeOps/userAvailability"; + import { normalizeTime } from "../../utils/date/dateToDiscordTimeStamp"; + export const profilePrefferredRaidTypesId = "select_preferred_raid_types"; + + export const profilePrefferredRaidTypesSelect = async ( + data: APIMessageSelectMenuInteractionData, + factoryInits: IfactoryInitializations + ) => { + const { + logger, + rest, + documentClient, + interactionConfig: { + application_id, + token, + channel_id, + guild_id, + member, + message, + }, + } = factoryInits; + const embed = message?.embeds[0]; + const content = message?.content; + const [firstPart, UnprocessedprofileUserName] = embed.title?.split("-") || []; + const processedProfileUserName = UnprocessedprofileUserName.trim(); + const [requestedText, profileText, ofText, userDiscordId] = content + .trim() + .split(" "); + const userId = userDiscordId.substring(2, userDiscordId.length - 1); + const selectedPreferredRaidTypes = data.values; + const statusUpvotes = await listUserStatusChanges(userId, { + documentClient, + }); + + const memberActivity = await getMemberActions( + { user: { id: userId } } as APIInteractionGuildMember, + { + documentClient, + } + ); + + const isUserAllowed = userId === member.user.id; + + isUserAllowed && + (await updateUserProfile( + { + discordMemberId: userId, + updates: { + userName: member?.user?.username, + updatedAt: new Date().toISOString(), + preferredRunTypes: selectedPreferredRaidTypes || [], + }, + }, + { documentClient } + )); + const lastUserClassActivity = await getLastUsersClass( + { user: { id: userId } } as APIInteractionGuildMember, + { + documentClient, + } + ); + const classOptionsList = getOptionsList(); + const processedMemberActivity = memberActivity.map( + ({ + metaData, + createdAt, + status, + raidId, + raidTitle, + raidType, + requestedSectionName, + primaryClassName, + optionalClassesNames, + }) => { + const classesEmojis = createFieldName( + { fieldName: primaryClassName, optionalClasses: optionalClassesNames }, + { classNamesList: classOptionsList } + ); + const activityTime = Math.round(Number(createdAt) / 1000); + const timestamp = ``; + const activityText = { + [ACTIVITY_STATUS.REMOVED]: `> ${timestamp}: <@${metaData?.removedBy}> (Admin) removed your ${classesEmojis} from ${raidTitle}[${raidType}] (Raid ID:${raidId}) ${metaData?.removedReason}`, + }; + const customMessage = activityText[status]; + const defaultMessage = `> ${timestamp}: ${status} ${raidTitle}[${raidType}] (Raid ID: **${raidId}**) with ${classesEmojis}`; + return customMessage ? customMessage : defaultMessage; + } + ); + + const RankIList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_I + ); + const RankIIList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_II + ); + const RankIIIList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_III + ); + const RankIVList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_IV + ); + const RankVList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_V + ); + + const rankList = [ + { + userStatusCode: userStatusCodes.RANK_I, + upvotesCount: RankIList.length, + userStatusText: UserStatusValues.RANKI, + }, + { + userStatusCode: userStatusCodes.RANK_II, + upvotesCount: RankIIList.length, + userStatusText: UserStatusValues.RANKII, + }, + { + userStatusCode: userStatusCodes.RANK_III, + upvotesCount: RankIIIList.length, + userStatusText: UserStatusValues.RANKIII, + }, + { + userStatusCode: userStatusCodes.RANK_IV, + upvotesCount: RankIVList.length, + userStatusText: UserStatusValues.RANKIV, + }, + { + userStatusCode: userStatusCodes.RANK_V, + upvotesCount: RankVList.length, + userStatusText: UserStatusValues.RANKV, + }, + ] + .filter(({ upvotesCount = 0 }) => upvotesCount > 0) + .sort(fieldSorter(["-upvotesCount"])); + + const [firstRank] = rankList; + + const { className, optionalClasses, updatedAt, artifactsList, mountsList } = + lastUserClassActivity; + const defaultClassesSelected = createFieldName( + { fieldName: className, optionalClasses: optionalClasses }, + { classNamesList: classOptionsList } + ); + const artifactsEmoji = displayArtifactAsEmoji(artifactsList).join("|"); + const firstRankStatusCode = + firstRank?.userStatusCode || userStatusCodes.RANK_I; + + const [userProfile, userAvailability] = await Promise.all([ + getUserProfile({ discordMemberId: userId }, { documentClient }), + getUserAvailability(userId, { documentClient }), + ]); + + const processedUserAvailability = userAvailability.map( + ({ availableDayUTC, availableStartTimeUTC, availableDuration }) => { + const normalizedDate = normalizeTime( + `${availableDayUTC} ${availableStartTimeUTC}`, + { offSet: "GMT+0:00" } + ); + const epochTime = Math.floor(normalizedDate.getTime() / 1000); + return { + startTime: epochTime, + endTime: epochTime + (Number(availableDuration) * 60 * 60), + }; + } + ); + const timeZone = getTimeZones().find(({ value }) => { + return value === userProfile?.timezoneOffset; + }); + const buildData = profileBuilder({ + userId, + timeZone, + prefferedRaids: userProfile?.prefferedTrials || [], + preferredRunTypes: userProfile?.preferredRunTypes || [], + availabilityList: processedUserAvailability, + userName: processedProfileUserName, + activityList: processedMemberActivity, + trialsParticipatedOn: [], + userStatus: statusSymbols[firstRankStatusCode], + rankList: rankList.map( + ({ userStatusCode, upvotesCount, userStatusText }) => { + return `> ${statusSymbols[userStatusCode]} - ${upvotesCount} votes`; + } + ), + mountsList: mountsList, + classesPlayed: [ + `Class (Main and Optional): ${defaultClassesSelected} \n\u200b\n Artifacts: ${artifactsEmoji}`, + ], + }); + + return { + body: { + ...buildData, + }, + }; + }; + \ No newline at end of file diff --git a/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectTimeZone.ts b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectTimeZone.ts new file mode 100644 index 0000000..c8827c4 --- /dev/null +++ b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/selectTimeZone.ts @@ -0,0 +1,233 @@ +import { + APIInteractionGuildMember, + APIMessageSelectMenuInteractionData, +} from "discord-api-types/payloads/v10/interactions"; +import { EmbedField, Routes } from "discord.js"; +import { IfactoryInitializations } from "../../../typeDefinitions/event"; +import { + listUserStatusChanges, + updateuserStatus, + userStatusCodes, +} from "../../utils/storeOps/userStatus"; +import { updateMemberDetails } from "../../utils/storeOps/members"; +import { + createFieldName, + createFieldValue, + defaultJoinStatus, + statusSymbols, +} from "../../utils/helper/embedFieldAttribute"; + +import { + defaultClassName, + getOptionsList, + NeverwinterClassesMap, +} from "../../../../embeds/templates/neverwinter/classesList"; +import { getLastUsersClass } from "../../utils/storeOps/fetchData"; +import { + getTimeZones, + profileBuilder, + UserStatusValues, +} from "../../../../embeds/templates/neverwinter/profile"; +import { + ACTIVITY_STATUS, + getMemberActions, +} from "../../utils/storeOps/memberActions"; +import { displayArtifactAsEmoji } from "../../utils/helper/artifactsRenderer"; +import { fieldSorter } from "../../utils/helper/artifactsSorter"; +import { getUserProfile, updateUserProfile } from "../../utils/storeOps/userProfile"; +import { getUserAvailability } from "../../utils/storeOps/userAvailability"; +import { normalizeTime } from "../../utils/date/dateToDiscordTimeStamp"; +export const profileTimezoneSelectId = "select_profile_timezone"; + +export const profileTimeZoneSelect = async ( + data: APIMessageSelectMenuInteractionData, + factoryInits: IfactoryInitializations +) => { + const { + logger, + rest, + documentClient, + interactionConfig: { + application_id, + token, + channel_id, + guild_id, + member, + message, + }, + } = factoryInits; + const embed = message?.embeds[0]; + const content = message?.content; + const [firstPart, UnprocessedprofileUserName] = embed.title?.split("-") || []; + const processedProfileUserName = UnprocessedprofileUserName.trim(); + const [requestedText, profileText, ofText, userDiscordId] = content + .trim() + .split(" "); + const userId = userDiscordId.substring(2, userDiscordId.length - 1); + const [selectedTimeZone] = data.values; + const statusUpvotes = await listUserStatusChanges(userId, { + documentClient, + }); + + const memberActivity = await getMemberActions( + { user: { id: userId } } as APIInteractionGuildMember, + { + documentClient, + } + ); + + const isUserAllowed = userId === member.user.id; + + isUserAllowed && + (await updateUserProfile( + { + discordMemberId: userId, + updates: { + userName: member?.user?.username, + updatedAt: new Date().toISOString(), + timezoneOffset: selectedTimeZone, + }, + }, + { documentClient } + )); + const lastUserClassActivity = await getLastUsersClass( + { user: { id: userId } } as APIInteractionGuildMember, + { + documentClient, + } + ); + const classOptionsList = getOptionsList(); + const processedMemberActivity = memberActivity.map( + ({ + metaData, + createdAt, + status, + raidId, + raidTitle, + raidType, + requestedSectionName, + primaryClassName, + optionalClassesNames, + }) => { + const classesEmojis = createFieldName( + { fieldName: primaryClassName, optionalClasses: optionalClassesNames }, + { classNamesList: classOptionsList } + ); + const activityTime = Math.round(Number(createdAt) / 1000); + const timestamp = ``; + const activityText = { + [ACTIVITY_STATUS.REMOVED]: `> ${timestamp}: <@${metaData?.removedBy}> (Admin) removed your ${classesEmojis} from ${raidTitle}[${raidType}] (Raid ID:${raidId}) ${metaData?.removedReason}`, + }; + const customMessage = activityText[status]; + const defaultMessage = `> ${timestamp}: ${status} ${raidTitle}[${raidType}] (Raid ID: **${raidId}**) with ${classesEmojis}`; + return customMessage ? customMessage : defaultMessage; + } + ); + + const RankIList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_I + ); + const RankIIList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_II + ); + const RankIIIList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_III + ); + const RankIVList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_IV + ); + const RankVList = statusUpvotes.filter( + ({ statusCode }) => statusCode === userStatusCodes.RANK_V + ); + + const rankList = [ + { + userStatusCode: userStatusCodes.RANK_I, + upvotesCount: RankIList.length, + userStatusText: UserStatusValues.RANKI, + }, + { + userStatusCode: userStatusCodes.RANK_II, + upvotesCount: RankIIList.length, + userStatusText: UserStatusValues.RANKII, + }, + { + userStatusCode: userStatusCodes.RANK_III, + upvotesCount: RankIIIList.length, + userStatusText: UserStatusValues.RANKIII, + }, + { + userStatusCode: userStatusCodes.RANK_IV, + upvotesCount: RankIVList.length, + userStatusText: UserStatusValues.RANKIV, + }, + { + userStatusCode: userStatusCodes.RANK_V, + upvotesCount: RankVList.length, + userStatusText: UserStatusValues.RANKV, + }, + ] + .filter(({ upvotesCount = 0 }) => upvotesCount > 0) + .sort(fieldSorter(["-upvotesCount"])); + + const [firstRank] = rankList; + + const { className, optionalClasses, updatedAt, artifactsList, mountsList } = + lastUserClassActivity; + const defaultClassesSelected = createFieldName( + { fieldName: className, optionalClasses: optionalClasses }, + { classNamesList: classOptionsList } + ); + const artifactsEmoji = displayArtifactAsEmoji(artifactsList).join("|"); + const firstRankStatusCode = + firstRank?.userStatusCode || userStatusCodes.RANK_I; + + const [userProfile, userAvailability] = await Promise.all([ + getUserProfile({ discordMemberId: userId }, { documentClient }), + getUserAvailability(userId, { documentClient }), + ]); + + const processedUserAvailability = userAvailability.map( + ({ availableDayUTC, availableStartTimeUTC, availableDuration }) => { + + const normalizedDate = normalizeTime( + `${availableDayUTC} ${availableStartTimeUTC}`, + { offSet: "GMT+0:00" } + ); + const epochTime = Math.floor(normalizedDate.getTime() / 1000); + return { + startTime: epochTime, + endTime: epochTime + epochTime + (Number(availableDuration) * 60 * 60), + } + } + ); + const timeZone = getTimeZones().find(({value})=>{ + return value === userProfile?.timezoneOffset; + }); + const buildData = profileBuilder({ + userId, + timeZone, + availabilityList: processedUserAvailability, + prefferedRaids: userProfile?.prefferedTrials || [], + preferredRunTypes: userProfile?.preferredRunTypes || [], + userName: processedProfileUserName, + activityList: processedMemberActivity, + trialsParticipatedOn: [], + userStatus: statusSymbols[firstRankStatusCode], + rankList: rankList.map( + ({ userStatusCode, upvotesCount, userStatusText }) => { + return `> ${statusSymbols[userStatusCode]} - ${upvotesCount} votes`; + } + ), + mountsList: mountsList, + classesPlayed: [ + `Class (Main and Optional): ${defaultClassesSelected} \n\u200b\n Artifacts: ${artifactsEmoji}`, + ], + }); + + return { + body: { + ...buildData, + }, + }; +}; diff --git a/bot/interactions/messageComponents/selectMenu/profileSelectMenu/statusVoteSelect.ts b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/statusVoteSelect.ts index c9ae73f..c9a8649 100644 --- a/bot/interactions/messageComponents/selectMenu/profileSelectMenu/statusVoteSelect.ts +++ b/bot/interactions/messageComponents/selectMenu/profileSelectMenu/statusVoteSelect.ts @@ -24,6 +24,7 @@ import { } from "../../../../embeds/templates/neverwinter/classesList"; import { getLastUsersClass } from "../../utils/storeOps/fetchData"; import { + getTimeZones, profileBuilder, UserStatusValues, } from "../../../../embeds/templates/neverwinter/profile"; @@ -33,6 +34,9 @@ import { } from "../../utils/storeOps/memberActions"; import { displayArtifactAsEmoji } from "../../utils/helper/artifactsRenderer"; import { fieldSorter } from "../../utils/helper/artifactsSorter"; +import { getUserProfile } from "../../utils/storeOps/userProfile"; +import { getUserAvailability } from "../../utils/storeOps/userAvailability"; +import { normalizeTime } from "../../utils/date/dateToDiscordTimeStamp"; export const profileStatusVoteId = "select_profile_status"; export const profileStatusVote = async ( @@ -87,7 +91,7 @@ export const profileStatusVote = async ( documentClient, }); - logger.log("info", {selectedStatusCode, statusUpvotes, updatedUserStatus}); + logger.log("info", { selectedStatusCode, statusUpvotes, updatedUserStatus }); const RankIList = statusUpvotes.filter( ({ statusCode }) => statusCode === userStatusCodes.RANK_I ); @@ -148,7 +152,12 @@ export const profileStatusVote = async ( documentClient, } ); - logger.log("info", { rankList, firstRank, memberActivity, lastUserClassActivity }); + logger.log("info", { + rankList, + firstRank, + memberActivity, + lastUserClassActivity, + }); const updatedMember = await updateMemberDetails( userId, { userStatus: firstRank?.userStatusCode }, @@ -193,7 +202,6 @@ export const profileStatusVote = async ( } ); - const { className, optionalClasses, updatedAt, artifactsList } = lastUserClassActivity; const defaultClassesSelected = createFieldName( @@ -201,12 +209,39 @@ export const profileStatusVote = async ( { classNamesList: classOptionsList } ); const artifactsEmoji = displayArtifactAsEmoji(artifactsList).join("|"); - const firstRankStatusCode = firstRank?.userStatusCode || userStatusCodes.RANK_I; + const firstRankStatusCode = + firstRank?.userStatusCode || userStatusCodes.RANK_I; + + const [userProfile, userAvailability] = await Promise.all([ + getUserProfile({ discordMemberId: userId }, { documentClient }), + getUserAvailability(userId, { documentClient }), + ]); + + const processedUserAvailability = userAvailability.map( + ({ availableDayUTC, availableStartTimeUTC, availableDuration }) => { + const normalizedDate = normalizeTime( + `${availableDayUTC} ${availableStartTimeUTC}`, + { offSet: "GMT+0:00" } + ); + const epochTime = Math.floor(normalizedDate.getTime() / 1000); + return { + startTime: epochTime, + endTime: epochTime + (Number(availableDuration) * 60 * 60), + }; + } + ); + const timeZone = getTimeZones().find(({ value }) => { + return value === userProfile?.timezoneOffset; + }); const buildData = profileBuilder({ userId, userName: processedProfileUserName, + availabilityList: processedUserAvailability, + prefferedRaids: userProfile?.prefferedTrials || [], + preferredRunTypes: userProfile?.preferredRunTypes || [], activityList: processedMemberActivity, mountsList: lastUserClassActivity?.mountsList, + timeZone, trialsParticipatedOn: [], userStatus: statusSymbols[firstRankStatusCode], rankList: rankList.map( diff --git a/bot/interactions/messageComponents/utils/date/dateToDiscordTimeStamp.ts b/bot/interactions/messageComponents/utils/date/dateToDiscordTimeStamp.ts index 6f899d4..84507d0 100644 --- a/bot/interactions/messageComponents/utils/date/dateToDiscordTimeStamp.ts +++ b/bot/interactions/messageComponents/utils/date/dateToDiscordTimeStamp.ts @@ -27,6 +27,16 @@ export const normalizeTime = (inputDate, { offSet = "GMT-05:00" } = {}) => { return containsTimeZone ? inputDate : chrono.parseDate(`${inputDate} ${offSet}`); }; +/** +* Converts a day number to a string. +* +* @param {Number} dayIndex +* @return {String} Returns day as string +*/ +export const dayOfWeekAsString = (dayIndex)=> { + return ["Sunday", "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][dayIndex] || ''; +} + export const convertToDiscordDate = ( date: string, { long = true, relative = false, offset = 0 } = {} diff --git a/bot/interactions/messageComponents/utils/storeOps/userAvailability.ts b/bot/interactions/messageComponents/utils/storeOps/userAvailability.ts new file mode 100644 index 0000000..f1358cd --- /dev/null +++ b/bot/interactions/messageComponents/utils/storeOps/userAvailability.ts @@ -0,0 +1,80 @@ +import { userAvailabilityTable } from "../../../../../pulumi/persistantStore/tables/userAvailability"; +import { setUpdateValues } from "../../../../store/utils"; + + +export const getUserAvailability = async ( + userId: string, + { documentClient } +) => { + const dbResult = await documentClient + .query({ + TableName: userAvailabilityTable.name.get(), + KeyConditionExpression: "#DYNOBASE_discordMemberId = :pkey", + ExpressionAttributeValues: { + ":pkey": userId, + }, + ExpressionAttributeNames: { + "#DYNOBASE_discordMemberId": "discordMemberId", + }, + ScanIndexForward: true, + }) + .promise(); + const { Items = [] } = dbResult; + return Items; +}; + +interface IuserAvailabilityUpdates { + availableDayUTC: string, + availableStartTimeUTC: string, + availableDuration: string, +} + +export const updateUserAvailability = async ( + { + discordMemberId, + updates, + }: { + discordMemberId: string; + updates?: IuserAvailabilityUpdates; + }, + { documentClient } +) => { + const date_time_composite = `${updates?.availableDayUTC}#${updates?.availableStartTimeUTC}`; + const updateValues = setUpdateValues({...updates, date_time_composite}); + const updatedUserAvailability = await documentClient + .update({ + TableName: userAvailabilityTable.name.get(), + Key: { + discordMemberId, + availabilitySpecified: `${updates?.availableDayUTC}`, + }, + ReturnValues: "UPDATED_NEW", + UpdateExpression: updateValues.updateExpression, + ExpressionAttributeNames: updateValues.updateExpressionAttributeNames, + ExpressionAttributeValues: updateValues.updateExpressionAttributeValues, + }) + .promise(); + return updatedUserAvailability; +}; + +export const removeUserAvailability = async ( + { + discordMemberId, + day, + }: { + discordMemberId: string; + day: string + }, + { documentClient } +) => { + const updatedUserAvailability = await documentClient + .delete({ + TableName: userAvailabilityTable.name.get(), + Key: { + discordMemberId, + availabilitySpecified: day + }, + }) + .promise(); + return updatedUserAvailability; +}; diff --git a/bot/interactions/messageComponents/utils/storeOps/userProfile.ts b/bot/interactions/messageComponents/utils/storeOps/userProfile.ts new file mode 100644 index 0000000..6f4cfae --- /dev/null +++ b/bot/interactions/messageComponents/utils/storeOps/userProfile.ts @@ -0,0 +1,66 @@ +import { APIInteractionGuildMember } from "discord.js"; +import { userProfileTable } from "../../../../../pulumi/persistantStore/tables/userProfile"; +import { setUpdateValues } from "../../../../store/utils"; + +interface IUserProfileUpdates { + timezoneOffset?: string; + updatedAt: string; + prefferedTrials?: string[]; + preferredRunTypes?: string[]; + userName: string; +} + +export const updateUserProfile = async ( + { + discordMemberId, + updates, + }: { + discordMemberId: string; + updates?: IUserProfileUpdates; + }, + { documentClient } +) => { + const { userName, ...otherUpdates } = updates || {}; + const updateValues = setUpdateValues(otherUpdates); + const updatedMemberProfile = await documentClient + .update({ + TableName: userProfileTable.name.get(), + Key: { + discordMemberId, + userName, + }, + ReturnValues: "UPDATED_NEW", + UpdateExpression: updateValues.updateExpression, + ExpressionAttributeNames: updateValues.updateExpressionAttributeNames, + ExpressionAttributeValues: updateValues.updateExpressionAttributeValues, + }) + .promise(); + return updatedMemberProfile; +}; + +export const getUserProfile = async ( + { discordMemberId, userName }: { discordMemberId: string; userName?: string }, + { documentClient } +) => { + const dbResult = await documentClient + .query({ + TableName: userProfileTable.name.get(), + KeyConditionExpression: "#DYNOBASE_discordMemberId = :pkey", + ExpressionAttributeValues: { + ":pkey": discordMemberId, + ...(userName && { ":userName": userName }), + }, + ExpressionAttributeNames: { + "#DYNOBASE_discordMemberId": "discordMemberId", + ...(userName && { "#DYNOBASE_userName": "userName" }), + }, + ScanIndexForward: true, + ...(userName && { + FilterExpression: "#DYNOBASE_userName = :userName", + }), + }) + .promise(); + const { Items = [] } = dbResult; + const [Item] = Items; + return Item; +}; diff --git a/bot/registerCommands/commands.ts b/bot/registerCommands/commands.ts index 3311153..29782a9 100644 --- a/bot/registerCommands/commands.ts +++ b/bot/registerCommands/commands.ts @@ -150,6 +150,218 @@ export const request_role = { }, ], }, + { + type: 1, + name: "update_user_availability", + description: "User's availability for raids", + options: [ + { + type: 3, + name: "day", + description: "The day of the week you would be available", + required: true, + choices: [ + { + name: "Monday", + value: "Monday", + }, + { + name: "Tuesday", + value: "Tuesday", + }, + { + name: "Wednesday", + value: "Wednesday", + }, + { + name: "Thursday", + value: "Thursday", + }, + { + name: "Friday", + value: "Friday", + }, + { + name: "Saturday", + value: "Saturday", + }, + { + name: "Sunday", + value: "Sunday", + }, + ], + }, + { + type: 3, + name: "start_time", + description: "Start time of the day you will be available from", + required: true, + choices: [ + { + name: "00:00", + value: "00:00", + }, + { + name: "01:00", + value: "01:00", + }, + { + name: "02:00", + value: "02:00", + }, + { + name: "03:00", + value: "03:00", + }, + { + name: "04:00", + value: "04:00", + }, + { + name: "05:00", + value: "05:00", + }, + { + name: "06:00", + value: "06:00", + }, + { + name: "07:00", + value: "07:00", + }, + { + name: "08:00", + value: "08:00", + }, + { + name: "09:00", + value: "09:00", + }, + { + name: "10:00", + value: "10:00", + }, + { + name: "11:00", + value: "11:00", + }, + { + name: "12:00", + value: "12:00", + }, + { + name: "13:00", + value: "13:00", + }, + { + name: "14:00", + value: "14:00", + }, + { + name: "15:00", + value: "15:00", + }, + + { + name: "16:00", + value: "16:00", + }, + { + name: "17:00", + value: "17:00", + }, + { + name: "18:00", + value: "18:00", + }, + { + name: "19:00", + value: "19:00", + }, + { + name: "20:00", + value: "20:00", + }, + { + name: "21:00", + value: "21:00", + }, + { + name: "22:00", + value: "22:00", + }, + { + name: "23:00", + value: "23:00", + }, + ], + }, + { + type: 3, + name: "number_of_hours", + description: "The number of hours you intend to play from the start time", + required: true, + choices: [ + { + name: "Not Available", + value: "0", + }, + { + name: "1 Hour", + value: "1", + }, + { + name: "2 Hours", + value: "2", + }, + { + name: "3 Hours", + value: "3", + }, + { + name: "4 Hours", + value: "4", + }, + { + name: "5 Hours", + value: "5", + }, + { + name: "6 Hours", + value: "6", + }, + { + name: "7 Hours", + value: "7", + }, + { + name: "8 Hours", + value: "8", + }, + { + name: "9 Hours", + value: "9", + }, + { + name: "10 Hours", + value: "10", + }, + { + name: "11 Hours", + value: "11", + }, + { + name: "12 Hours", + value: "12", + }, + { + name: "I have no life", + value: "24", + }, + ], + }, + ], + }, { type: 1, name: "server_profile", @@ -365,3 +577,4 @@ export const create_raid = { // }, ], }; + diff --git a/docs/usage/README.md b/docs/usage/README.md index 53a1a78..3249a72 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -1,6 +1,7 @@ # GETTING STARTED Currently the bot supports the below mentioned commands. + # Commands ## /info @@ -70,10 +71,321 @@ Create a raid or event ### Options -- `raid`: Set-up a raid for a trial or dungeon - - `name`: Name of the trial. (required) - - Choice list: `createRaidNameChoicesList` - - `date`: Date and time of the raid. ex: `2016-03-05 23:59:59 CST , 2022-09-25T21:00:00 GMT+05:30` (required) - - `type`: Set type of run. (optional) - - `Training`: Training (value: "Training") - - `Farm`: Farm (value: "Farm") +- `name`: Name of the trial. (required) + - Choice list: `createRaidNameChoicesList` +- `date`: Date and time of the raid. ex: `2016-03-05 23:59:59 CST , 2022-09-25T21:00:00 GMT+05:30` (required) +- `type`: Set type of run. (optional) + - `Training` + - `Farm` + - `Wipe` +- `party`: Team composition additional configurations. (optional) + - `Standard` + - `Solo Tank` + - `Solo Heal` + - `Solo Tank & Solo Heal` + - `3 Heals and 2 Tanks` + - `3 Heals and 1 Tank` +- `requirements`: Raid Requirements. (optional) + - `Masterworks & Power Raptors` + - `Masterworks` + - `Wizards` + - `Masterworks, Power Raptors , Wizards & UHDPS 4K` +- `description`: Set a description. (optional) +- `duration`: Duration for the Discord event. (Default: 1 Hour) (optional) +- `voice`: The voice channel this raid takes place in (optional) + + + +## /request role + +### Description + +Request a role + +### Options +- name \ No newline at end of file diff --git a/pulumi/index.ts b/pulumi/index.ts index acf2824..2386fb4 100644 --- a/pulumi/index.ts +++ b/pulumi/index.ts @@ -4,9 +4,11 @@ import { warmHTTPEventsSchedule } from "./lambdas/httpEventsProcessor"; import { discordEventsQueue } from "./sqs/discordEvents"; import { membersTable } from "./persistantStore/tables/members"; import { raidsTable } from "./persistantStore/tables/raids"; -import { memberActionsTable } from "./persistantStore/tables/memberActions" +import { memberActionsTable } from "./persistantStore/tables/memberActions"; import { userStatusTable } from "./persistantStore/tables/userStatus"; import { userNotifcations } from "./persistantStore/tables/userNotifications"; +import { userProfileTable } from "./persistantStore/tables/userProfile"; +import { userAvailabilityTable } from "./persistantStore/tables/userAvailability"; export default { apiGatewayEndpointUrl: apiEndpoint.url, cloudwatchSchedule: { @@ -16,12 +18,13 @@ export default { sqs: { discordEventsUrl: discordEventsQueue.url, }, - persistantStore:{ + persistantStore: { membersTable: membersTable.name, raidsTable: raidsTable.name, memberActionsTable: memberActionsTable.name, userStatusTable: userStatusTable.name, - userNotifications: userNotifcations.name - //memberActionsTable: memberActionsTable.name.get() - } + userNotifications: userNotifcations.name, + userAvailabilityTable: userAvailabilityTable.name, + userProfileTable: userProfileTable.name, + }, }; diff --git a/pulumi/persistantStore/tables/userAvailability.ts b/pulumi/persistantStore/tables/userAvailability.ts new file mode 100644 index 0000000..7cef6d0 --- /dev/null +++ b/pulumi/persistantStore/tables/userAvailability.ts @@ -0,0 +1,27 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as aws from "@pulumi/aws"; +import { getEnvironmentFromStack } from "../../utils/stackEnvMap"; +const stack = pulumi.getStack(); + +export const userAvailabilityTable = new aws.dynamodb.Table( + `${stack}_userAvailabilitySchedule`, + { + attributes: [ + { + name: "discordMemberId", + type: "S", + }, + { + name: "availabilitySpecified", + type: "S", + }, + ], + billingMode: "PAY_PER_REQUEST", + hashKey: "discordMemberId", + rangeKey: "availabilitySpecified", + tags: { + Environment: `${getEnvironmentFromStack(stack)}`, + Name: `${stack}userAvailability`, + }, + } +); diff --git a/pulumi/persistantStore/tables/userProfile.ts b/pulumi/persistantStore/tables/userProfile.ts new file mode 100644 index 0000000..32649d5 --- /dev/null +++ b/pulumi/persistantStore/tables/userProfile.ts @@ -0,0 +1,24 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as aws from "@pulumi/aws"; +import { getEnvironmentFromStack } from "../../utils/stackEnvMap"; +const stack = pulumi.getStack(); + +export const userProfileTable = new aws.dynamodb.Table(`${stack}_userProfile`, { + attributes: [ + { + name: "discordMemberId", + type: "S", + }, + { + name: "userName", + type: "S", + }, + ], + billingMode: "PAY_PER_REQUEST", + hashKey: "discordMemberId", + rangeKey: "userName", + tags: { + Environment: `${getEnvironmentFromStack(stack)}`, + Name: `${stack}userProfile`, + }, +});