From f34fb907a83e543cf571122140b554f9c6c63288 Mon Sep 17 00:00:00 2001 From: coinmoles Date: Sat, 2 Mar 2024 15:41:54 +0900 Subject: [PATCH] Added peek (pls work) --- .../missionGive/postMission/operations.ts | 190 +++++++---- src/commands/peek/builders.ts | 46 +++ src/commands/peek/index.ts | 21 +- src/commands/peek/operations.ts | 306 ++++++++++++++++++ src/commands/peek/reply.ts | 13 + src/commands/utils/errorEmbeds.ts | 2 +- 6 files changed, 499 insertions(+), 79 deletions(-) create mode 100644 src/commands/peek/builders.ts create mode 100644 src/commands/peek/operations.ts create mode 100644 src/commands/peek/reply.ts diff --git a/src/commands/missionGive/postMission/operations.ts b/src/commands/missionGive/postMission/operations.ts index 00f24c6..3e409cb 100644 --- a/src/commands/missionGive/postMission/operations.ts +++ b/src/commands/missionGive/postMission/operations.ts @@ -1,86 +1,138 @@ -import { ButtonInteraction, ChatInputCommandInteraction, EmbedBuilder, ModalSubmitInteraction, User } from "discord.js" -import { postMission } from "../../../db/actions/missionActions.js" -import { Mission } from "../../../interfaces/models/Mission.js" -import { checkAssociate } from "../../utils/checks/checkAssociate.js" -import { addConfirmCollector, createConfirmActionRow } from "../../utils/components/confirmActionRow.js" -import { createMissionModal, createMissionModalId, getMissionModalMission } from "../../utils/components/missionModal.js" -import { createMissionPreviewString, createMissionPreviewTitle } from "../../utils/createString/createMissionPreviewString.js" -import { errorEmbed } from "../../utils/errorEmbeds.js" -import { getQuarterDataFooter } from "../../utils/quarterData/getQuarterData.js" -import builders from "./builders.js" +import { + ButtonInteraction, + ChatInputCommandInteraction, + EmbedBuilder, + ModalSubmitInteraction, + User, +} from "discord.js"; +import { postMission } from "../../../db/actions/missionActions.js"; +import { Mission } from "../../../interfaces/models/Mission.js"; +import { checkAssociate } from "../../utils/checks/checkAssociate.js"; +import { + addConfirmCollector, + createConfirmActionRow, +} from "../../utils/components/confirmActionRow.js"; +import { + createMissionModal, + createMissionModalId, + getMissionModalMission, +} from "../../utils/components/missionModal.js"; +import { + createMissionPreviewString, + createMissionPreviewTitle, +} from "../../utils/createString/createMissionPreviewString.js"; +import { errorEmbed } from "../../utils/errorEmbeds.js"; +import { getQuarterDataFooter } from "../../utils/quarterData/getQuarterData.js"; +import builders from "./builders.js"; const { - commandId, - invalidScoreEmbed, - replyEmbedPrototype, - successEmbedPrototype, - cancelEmbedPrototype, -} = builders + commandId, + invalidScoreEmbed, + replyEmbedPrototype, + successEmbedPrototype, + cancelEmbedPrototype, +} = builders; export const readOptions = (interaction: ChatInputCommandInteraction) => ({ - target: interaction.options.getUser("대상", true) -}) + target: interaction.options.getUser("대상", true), +}); -const onConfirm = (modalInteraction: ModalSubmitInteraction, target: User, index: number, mission: Mission) => - async (buttonInteraction: ButtonInteraction) => { - await buttonInteraction.deferReply({ ephemeral: true }) +const onConfirm = + ( + modalInteraction: ModalSubmitInteraction, + target: User, + index: number, + mission: Mission + ) => + async (buttonInteraction: ButtonInteraction) => { + await buttonInteraction.deferReply({ ephemeral: true }); - const success = await postMission(index, mission) + const success = await postMission(index, mission); - if (success) { - const successEmbed = new EmbedBuilder(successEmbedPrototype.toJSON()) - .addFields({ name: createMissionPreviewTitle(mission, target), value: createMissionPreviewString(mission, target) }) + if (success) { + const successEmbed = new EmbedBuilder( + successEmbedPrototype.toJSON() + ).addFields({ + name: createMissionPreviewTitle(mission, target), + value: createMissionPreviewString(mission, target), + }); - await buttonInteraction.deleteReply() - await modalInteraction.editReply({ embeds: [successEmbed.setFooter(await getQuarterDataFooter())], components: [] }) - } - else - await buttonInteraction.editReply({ embeds: [errorEmbed] }) - } + await buttonInteraction.deleteReply(); + await modalInteraction.editReply({ + embeds: [successEmbed.setFooter(await getQuarterDataFooter())], + components: [], + }); + } else await buttonInteraction.editReply({ embeds: [errorEmbed] }); + }; -const onCancel = (modalInteraction: ModalSubmitInteraction, target: User, mission: Mission) => - async (buttonInteraction: ButtonInteraction) => { - await buttonInteraction.deferUpdate() +const onCancel = + (modalInteraction: ModalSubmitInteraction, target: User, mission: Mission) => + async (buttonInteraction: ButtonInteraction) => { + await buttonInteraction.deferUpdate(); - const cancelEmbed = new EmbedBuilder(cancelEmbedPrototype.toJSON()) - .addFields({ name: createMissionPreviewTitle(mission, target), value: createMissionPreviewString(mission, target) }) - - await modalInteraction.editReply({ embeds: [cancelEmbed.setFooter(await getQuarterDataFooter())], components: [] }) - } + const cancelEmbed = new EmbedBuilder( + cancelEmbedPrototype.toJSON() + ).addFields({ + name: createMissionPreviewTitle(mission, target), + value: createMissionPreviewString(mission, target), + }); -export const doReply = async (interaction: ChatInputCommandInteraction, target: User, isEditing: boolean = false) => { - if (!await checkAssociate(interaction, target.id, true)) - return + await modalInteraction.editReply({ + embeds: [cancelEmbed.setFooter(await getQuarterDataFooter())], + components: [], + }); + }; - await interaction.showModal(createMissionModal(commandId, interaction.user, target)) +export const doReply = async ( + interaction: ChatInputCommandInteraction, + target: User, + isEditing: boolean = false +) => { + if (!(await checkAssociate(interaction, target.id, true))) return; - const modalInteraction = await interaction.awaitModalSubmit({ - filter: mI => mI.customId === createMissionModalId(commandId, interaction.user), - time: 60_000 - }).catch(_ => null) - if (modalInteraction === null) - return + await interaction.showModal( + createMissionModal(commandId, interaction.user, target) + ); - const modalValue = getMissionModalMission(modalInteraction, target) - if (modalValue === undefined) { - await modalInteraction.reply({ embeds: [invalidScoreEmbed], ephemeral: true }) - return - } + const modalInteraction = await interaction + .awaitModalSubmit({ + filter: (mI) => + mI.customId === createMissionModalId(commandId, interaction.user), + time: 60_000, + }) + .catch((_) => null); + if (modalInteraction === null) return; - const { index, mission } = modalValue - const reply = await modalInteraction.reply({ - embeds: [EmbedBuilder.from(replyEmbedPrototype) - .addFields({ name: createMissionPreviewTitle(mission, target), value: createMissionPreviewString(mission, target) }) - .setFooter(await getQuarterDataFooter())], - components: [createConfirmActionRow(commandId, modalInteraction.user)], - ephemeral: true, - fetchReply: true + const modalValue = getMissionModalMission(modalInteraction, target); + if (modalValue === undefined) { + await modalInteraction.reply({ + embeds: [invalidScoreEmbed], + ephemeral: true, }); + return; + } + + const { index, mission } = modalValue; + const reply = await modalInteraction.reply({ + embeds: [ + EmbedBuilder.from(replyEmbedPrototype) + .addFields({ + name: createMissionPreviewTitle(mission, target), + value: createMissionPreviewString(mission, target), + }) + .setFooter(await getQuarterDataFooter()), + ], + components: [createConfirmActionRow(commandId, modalInteraction.user)], + ephemeral: true, + fetchReply: true, + }); - if (!isEditing) - await addConfirmCollector( - commandId, modalInteraction, reply, - onConfirm(modalInteraction, target, index, mission), - onCancel(modalInteraction, target, mission) - ) -} \ No newline at end of file + if (!isEditing) + await addConfirmCollector( + commandId, + modalInteraction, + reply, + onConfirm(modalInteraction, target, index, mission), + onCancel(modalInteraction, target, mission) + ); +}; diff --git a/src/commands/peek/builders.ts b/src/commands/peek/builders.ts new file mode 100644 index 0000000..11d3752 --- /dev/null +++ b/src/commands/peek/builders.ts @@ -0,0 +1,46 @@ +import { + ActionRowBuilder, + EmbedBuilder, + MessageActionRowComponentBuilder, + UserSelectMenuBuilder, +} from "discord.js"; +import { normalColor } from "../utils/colors.js"; + +const noUserEmbed = new EmbedBuilder() + .setTitle("정회원 또는 준회원을 입력하세요") + .setColor(normalColor); +const viewListEmbedPrototype = new EmbedBuilder().setColor(normalColor); + +const noAssociateEmbed = new EmbedBuilder() + .setTitle("아직 승격신청을 한 준회원이 없습니다.") + .setColor(normalColor); + +const REGULAR_MENU_ID = "regular-menu"; +const regularMenu = new UserSelectMenuBuilder() + .setCustomId(REGULAR_MENU_ID) + .setPlaceholder("승격조건 목록을 확인하고 싶은 정회원"); +const ASSOCITE_MENU_ID = "associate-menu"; +const associateMenu = new UserSelectMenuBuilder() + .setCustomId(ASSOCITE_MENU_ID) + .setPlaceholder("승격조건 목록을 확인하고 싶은 준회원"); + +const actionRow1 = + new ActionRowBuilder().addComponents( + regularMenu + ); +const actionRow2 = + new ActionRowBuilder().addComponents( + associateMenu + ); + +export default { + noUserEmbed, + noAssociateEmbed, + viewListEmbedPrototype, + REGULAR_MENU_ID, + regularMenu, + ASSOCITE_MENU_ID, + associateMenu, + actionRow1, + actionRow2 +}; diff --git a/src/commands/peek/index.ts b/src/commands/peek/index.ts index 629166a..f990b76 100644 --- a/src/commands/peek/index.ts +++ b/src/commands/peek/index.ts @@ -1,21 +1,24 @@ -import { - SlashCommandContainer, - SlashCommandSubcommandsOnlyContainer, -} from "../../interfaces/commands/CommandContainer.js"; +import { SlashCommandContainer } from "../../interfaces/commands/CommandContainer.js"; import { CommandType } from "../../interfaces/commands/CommandTypes.js"; import { MySlashCommandBuilder } from "../MySlashCommandBuilder.js"; +import reply from "./reply.js"; const name = "엿보기"; -const description = "자신 외의 정회원 또는 준회원이 받은 승격조건을 확인합니다"; - -const subcommands = []; +const description = + "자신 외의 정회원 또는 준회원이 받은 승격조건을 확인합니다."; const peek: SlashCommandContainer = { commandType: CommandType.Public, builder: new MySlashCommandBuilder() .setName(name) - .setDescription(description), - callback: async () => {}, + .setDescription(description) + .addUserOption((option) => + option.setName("정회원").setDescription("승격조건 목록을 확인할 정회원") + ) + .addUserOption((option) => + option.setName("준회원").setDescription("승격조건 목록을 확인할 준회원") + ), + callback: reply, }; export default peek; diff --git a/src/commands/peek/operations.ts b/src/commands/peek/operations.ts new file mode 100644 index 0000000..76cace6 --- /dev/null +++ b/src/commands/peek/operations.ts @@ -0,0 +1,306 @@ +import { + ChatInputCommandInteraction, + ComponentType, + EmbedBuilder, + InteractionResponse, + Message, + User, + UserSelectMenuInteraction, +} from "discord.js"; +import { + getMissionAll, + getMissionCount, +} from "../../db/actions/missionActions.js"; +import { + getMissionProgress, + getMissionProgressAll, +} from "../../db/actions/missionProgressActions.js"; +import { checkRegular } from "../utils/checks/checkRegular.js"; +import { createMissionMapString } from "../utils/createString/createMissionString.js"; +import { createProgressString } from "../utils/createString/createProgresString.js"; +import { errorEmbed } from "../utils/errorEmbeds.js"; +import { getQuarterDataFooter } from "../utils/quarterData/getQuarterData.js"; +import builders from "./builders.js"; +import { checkAssociate } from "../utils/checks/checkAssociate.js"; +import { getAllAssociateIds } from "../../db/actions/memberActions.js"; +import assert from "assert"; + +const { + noUserEmbed, + noAssociateEmbed, + viewListEmbedPrototype, + REGULAR_MENU_ID, + ASSOCITE_MENU_ID, + actionRow1, + actionRow2, +} = builders; + +export const readOptions = (interaction: ChatInputCommandInteraction) => ({ + giver: interaction.options.getUser("정회원"), + target: interaction.options.getUser("준회원"), +}); + +const doMenu = async ( + interaction: ChatInputCommandInteraction, + menuInteraction: UserSelectMenuInteraction, + oldGiver: User | null, + oldTarget: User | null +) => { + if (menuInteraction.customId === REGULAR_MENU_ID) { + await menuInteraction.deferUpdate(); + + const newGiver = interaction.client.users.cache.get( + menuInteraction.values[0] + )!; + + await doReply(interaction, newGiver, oldTarget, true); + } else if (menuInteraction.customId === ASSOCITE_MENU_ID) { + await menuInteraction.deferUpdate(); + + const newTarget = interaction.client.users.cache.get( + menuInteraction.values[0] + )!; + + await doReply(interaction, oldGiver, newTarget, true); + } +}; + +const addCollector = ( + interaction: ChatInputCommandInteraction, + reply: Message | InteractionResponse, + oldGiver: User | null, + oldTarget: User | null +) => { + const collector = reply.createMessageComponentCollector({ + max: 10, + filter: (i) => i.user === interaction.user, + componentType: ComponentType.UserSelect, + }); + collector.on("collect", (buttonInteraction) => + doMenu(interaction, buttonInteraction, oldGiver, oldTarget) + ); +}; + +export const doReply = async ( + interaction: ChatInputCommandInteraction, + giver: User | null, + target: User | null, + isEditing: boolean = false +) => { + if (!isEditing) await interaction.deferReply({ ephemeral: true }); + + if (!giver && !target) { + const reply = await interaction.editReply({ + embeds: [noUserEmbed.setFooter(await getQuarterDataFooter())], + components: [actionRow1, actionRow2], + }); + if (!isEditing) addCollector(interaction, reply, giver, target); + return; + } else if (!giver && target) { + if ( + !(await checkAssociate(interaction, target.id, false, [ + actionRow1, + actionRow2, + ])) + ) { + if (!isEditing) + addCollector( + interaction, + await interaction.fetchReply(), + giver, + target + ); + return; + } + const progresses = await getMissionProgressAll( + interaction.client, + target.id + ); + if (progresses === undefined) { + await interaction.editReply({ embeds: [errorEmbed] }); + return; + } + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle(`${target.displayName}의 승격조건 : 현황`) + .addFields( + progresses.map((progress) => ({ + name: + (progress.currentScore >= progress.goalScore + ? ":white_check_mark:" + : ":white_square_button:") + + " " + + progress.giverName, + value: `달성 현황: ${createProgressString( + progress.currentScore, + progress.goalScore + )}`, + inline: false, + })) + ) + .setFooter(await getQuarterDataFooter()), + ], + }); + } else if (giver && !target) { + if ( + !(await checkRegular(interaction, giver.id, [actionRow1, actionRow2])) + ) { + if (!isEditing) + addCollector( + interaction, + await interaction.fetchReply(), + giver, + target + ); + return; + } + + const associateIds = await getAllAssociateIds(); + if (associateIds === undefined) { + await interaction.editReply({ embeds: [noAssociateEmbed] }); + return; + } + + const universalCount = await getMissionCount(giver.id, giver.id); + + const datas = await Promise.all( + associateIds.map(async (associateId) => { + const progress = await getMissionProgress(giver.id, associateId); + assert(progress !== undefined); + return { + targetName: await interaction.client.users + .fetch(associateId) + .then((user) => user.displayName), + status: { + universalCount, + specificCount: await getMissionCount(giver.id, associateId), + }, + progress, + }; + }) + ); + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle(`${giver.displayName}이 제시한 승격조건 : 현황`) + .addFields( + datas.map((data) => ({ + name: + (data.progress.currentScore >= data.progress.goalScore + ? ":white_check_mark:" + : ":white_square_button:") + + " " + + data.targetName, + value: + `제시 현황: 공통조건 ${data.status.universalCount}개 / 개인조건 ${data.status.specificCount}개\n` + + `달성 현황: ${createProgressString( + data.progress.currentScore, + data.progress.goalScore + )}`, + inline: false, + })) + ) + .setFooter(await getQuarterDataFooter()), + ], + }); + } else if (giver && target) { + if ( + !(await checkRegular(interaction, giver.id, [actionRow1, actionRow2])) + ) { + if (!isEditing) + addCollector( + interaction, + await interaction.fetchReply(), + giver, + target + ); + return; + } + + let replyEmbed: EmbedBuilder; + if (target.id === interaction.user.id) { + const missionUniversal = await getMissionAll( + interaction.user.id, + interaction.user.id + ); + if (missionUniversal === undefined) { + await interaction.editReply({ embeds: [errorEmbed] }); + return; + } + + replyEmbed = new EmbedBuilder(viewListEmbedPrototype.toJSON()) + .setTitle(`공통 승격 조건: ${interaction.user.displayName}`) + .addFields({ + name: "공통 조건", + value: createMissionMapString(missionUniversal, interaction.user.id), + inline: false, + }); + } else { + if ( + !(await checkAssociate(interaction, target.id, true, [ + actionRow1, + actionRow2, + ])) + ) { + if (!isEditing) + addCollector( + interaction, + await interaction.fetchReply(), + giver, + target + ); + return; + } + + const progress = await getMissionProgress(interaction.user.id, target.id); + const missionUniversal = await getMissionAll( + interaction.user.id, + interaction.user.id + ); + const missionSpecific = await getMissionAll( + interaction.user.id, + target.id + ); + if ( + missionUniversal === undefined || + missionSpecific === undefined || + progress === undefined + ) { + await interaction.editReply({ embeds: [errorEmbed] }); + return; + } + + replyEmbed = new EmbedBuilder(viewListEmbedPrototype.toJSON()) + .setTitle( + `${target.displayName}의 승격 조건 : ${interaction.user.displayName}` + ) + .addFields({ + name: "달성 현황", + value: createProgressString( + progress.currentScore, + progress.goalScore + ), + inline: false, + }) + .addFields({ + name: "공통 조건", + value: createMissionMapString(missionUniversal, target.id), + inline: false, + }) + .addFields({ + name: "개인 조건", + value: createMissionMapString(missionSpecific, target.id), + inline: false, + }); + } + + const reply = await interaction.editReply({ + embeds: [replyEmbed.setFooter(await getQuarterDataFooter())], + components: [actionRow1, actionRow2], + }); + if (!isEditing) addCollector(interaction, reply, giver, target); + } +}; diff --git a/src/commands/peek/reply.ts b/src/commands/peek/reply.ts new file mode 100644 index 0000000..30154b2 --- /dev/null +++ b/src/commands/peek/reply.ts @@ -0,0 +1,13 @@ +import assert from "assert"; +import { InteractionOperation } from "../../interfaces/commands/InteractionOperation.js"; +import { doReply, readOptions } from "./operations.js"; + +const reply: InteractionOperation = async interaction => { + assert(interaction.isChatInputCommand()); + + const { giver, target } = readOptions(interaction); + + await doReply(interaction, giver, target); +} + +export default reply \ No newline at end of file diff --git a/src/commands/utils/errorEmbeds.ts b/src/commands/utils/errorEmbeds.ts index 8e6ee73..b92c814 100644 --- a/src/commands/utils/errorEmbeds.ts +++ b/src/commands/utils/errorEmbeds.ts @@ -19,7 +19,7 @@ export const notAssociateEmbed = new EmbedBuilder().setTitle("준회원이 아 export const unknownAssociateEmbed = new EmbedBuilder().setTitle("먼저 승격신청을 해 주세요 ").setColor(forbiddenColor) export const selectNotAssociateEmbed = new EmbedBuilder().setTitle("준회원을 선택해 주세요").setColor(errorColor) -export const selectNotAssociateOrSelfEmbed = new EmbedBuilder().setTitle("준회원 또는 자기 자신을 선택해 주세요").setColor(errorColor) +export const selectNotAssociateOrSelfEmbed = new EmbedBuilder().setTitle("준회원 또는 정회원 본인을 선택해 주세요").setColor(errorColor) export const selectUnknownAssociateEmbed = new EmbedBuilder().setTitle("해당 준회원은 아직 승격신청을 하지 않았습니다").setColor(errorColor) export const selectNotRegularEmbed = new EmbedBuilder().setTitle("정회원을 선택해 주세요").setColor(errorColor)