From 6e22687bf64b6e9a63ae1756519ac2c208fb6677 Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Mon, 21 Jun 2021 01:36:35 +0200 Subject: [PATCH] Switch pagination to buttons --- .../src/plugins/Mutes/commands/MutesCmd.ts | 88 ++++++++------ .../events/ButtonInteractionEvt.ts | 2 + backend/src/plugins/Utility/search.ts | 113 ++++++++++++------ backend/src/utils/waitForInteraction.ts | 10 -- 4 files changed, 131 insertions(+), 82 deletions(-) diff --git a/backend/src/plugins/Mutes/commands/MutesCmd.ts b/backend/src/plugins/Mutes/commands/MutesCmd.ts index 10c2d2e3..47e4a4fd 100644 --- a/backend/src/plugins/Mutes/commands/MutesCmd.ts +++ b/backend/src/plugins/Mutes/commands/MutesCmd.ts @@ -1,4 +1,4 @@ -import { GuildMember } from "discord.js"; +import { GuildMember, MessageActionRow, MessageButton, MessageComponentInteraction } from "discord.js"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { humanizeDurationShort } from "../../../humanizeDurationShort"; @@ -27,10 +27,9 @@ export const MutesCmd = mutesCmd({ let totalMutes = 0; let hasFilters = false; - let hasReactions = false; - let clearReactionsFn; - let clearReactionsTimeout; - const clearReactionsDebounce = 5 * MINUTES; + let stopCollectionFn; + let stopCollectionTimeout; + const stopCollectionDebounce = 5 * MINUTES; let lines: string[] = []; @@ -167,13 +166,12 @@ export const MutesCmd = mutesCmd({ message += "\n\n" + pageLines.join("\n"); listMessage.edit(message); - bumpClearReactionsTimeout(); + bumpCollectionTimeout(); }; - const bumpClearReactionsTimeout = () => { - if (!hasReactions) return; - clearTimeout(clearReactionsTimeout); - clearReactionsTimeout = setTimeout(clearReactionsFn, clearReactionsDebounce); + const bumpCollectionTimeout = () => { + clearTimeout(stopCollectionTimeout); + stopCollectionTimeout = setTimeout(stopCollectionFn, stopCollectionDebounce); }; if (totalMutes === 0) { @@ -194,35 +192,55 @@ export const MutesCmd = mutesCmd({ drawListPage(1); if (totalPages > 1) { - hasReactions = true; - listMessage.react("⬅"); - listMessage.react("➡"); + const idMod = `${listMessage.id}:muteList`; + const buttons: MessageButton[] = []; - const paginationReactionListener = pluginData.events.on( - "messageReactionAdd", - async ({ args: { reaction, user } }) => { - const rMsg = reaction.message; - const member = await pluginData.guild.members.fetch(user.id); - if (!isFullMessage(rMsg)) return; - if (rMsg.id !== listMessage.id) return; - if (member.id !== msg.author.id) return; - if (!["⬅", "➡"].includes(reaction.emoji.name!)) return; - - if (reaction.emoji.name === "⬅" && currentPage > 1) { - drawListPage(currentPage - 1); - } else if (reaction.emoji.name === "➡" && currentPage < totalPages) { - drawListPage(currentPage + 1); - } - - reaction.remove().catch(noop); - }, + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("⬅") + .setType("BUTTON") + .setCustomID(`previousButton:${idMod}`), ); - clearReactionsFn = () => { - listMessage.reactions.removeAll().catch(noop); - pluginData.events.off("messageReactionAdd", paginationReactionListener); + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("➡") + .setType("BUTTON") + .setCustomID(`nextButton:${idMod}`), + ); + + const row = new MessageActionRow().addComponents(buttons); + await listMessage.edit({ components: [row] }); + + const filter = (iac: MessageComponentInteraction) => iac.message.id === listMessage.id; + const collector = listMessage.createMessageComponentInteractionCollector(filter, { + time: stopCollectionDebounce, + }); + + collector.on("collect", async (interaction: MessageComponentInteraction) => { + if (msg.author.id !== interaction.user.id) { + interaction.reply(`You are not permitted to use these buttons.`, { ephemeral: true }); + } else { + collector.resetTimer(); + if (interaction.customID === `previousButton:${idMod}` && currentPage > 1) { + await interaction.deferUpdate(); + await drawListPage(currentPage - 1); + } else if (interaction.customID === `nextButton:${idMod}` && currentPage < totalPages) { + await interaction.deferUpdate(); + await drawListPage(currentPage + 1); + } else { + await interaction.deferUpdate(); + } + } + }); + + stopCollectionFn = async () => { + collector.stop(); + await listMessage.edit({ content: listMessage.content, components: [] }); }; - bumpClearReactionsTimeout(); + bumpCollectionTimeout(); } } }, diff --git a/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts b/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts index 6dd3f793..a5feb154 100644 --- a/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts +++ b/backend/src/plugins/ReactionRoles/events/ButtonInteractionEvt.ts @@ -21,6 +21,8 @@ export const ButtonInteractionEvt = reactionRolesEvt({ ? (meta.args.interaction as MessageComponentInteraction) : null; if (!int) return; + const allOnMessage = await meta.pluginData.state.buttonRoles.getAllForMessageId(int.message.id); + if (allOnMessage.length === 0) return; const cfg = meta.pluginData.config.get(); const split = int.customID.split(BUTTON_CONTEXT_SEPARATOR); const context = (await resolveStatefulCustomId(meta.pluginData, int.customID)) ?? { diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts index 0a36ebd9..04289dac 100644 --- a/backend/src/plugins/Utility/search.ts +++ b/backend/src/plugins/Utility/search.ts @@ -1,11 +1,20 @@ -import { GuildMember, Message, Permissions, TextChannel, User } from "discord.js"; +import { + GuildMember, + Message, + MessageActionRow, + MessageButton, + MessageComponentInteraction, + Permissions, + TextChannel, + User, +} from "discord.js"; import escapeStringRegexp from "escape-string-regexp"; import { GuildPluginData } from "knub"; import { ArgsFromSignatureOrArray } from "knub/dist/commands/commandUtils"; import moment from "moment-timezone"; import { getBaseUrl, sendErrorMessage } from "../../pluginUtils"; import { allowTimeout, RegExpRunner } from "../../RegExpRunner"; -import { isFullMessage, MINUTES, multiSorter, noop, sorter, trimLines } from "../../utils"; +import { MINUTES, multiSorter, sorter, trimLines } from "../../utils"; import { asyncFilter } from "../../utils/async"; import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions"; import { inputPatternToRegExp, InvalidRegexError } from "../../validatorUtils"; @@ -75,9 +84,8 @@ export async function displaySearch( let originalSearchMsg: Message; let searching = false; let currentPage = args.page || 1; - let hasReactions = false; - let clearReactionsFn: () => void; - let clearReactionsTimeout: Timeout; + let stopCollectionFn: () => void; + let stopCollectionTimeout: Timeout; const perPage = args.ids ? SEARCH_ID_RESULTS_PER_PAGE : SEARCH_RESULTS_PER_PAGE; @@ -155,47 +163,78 @@ export async function displaySearch( } } - searchMsg.edit(result); + currentPage = searchResult.page; // Set up pagination reactions if needed. The reactions are cleared after a timeout. if (searchResult.totalResults > perPage) { - if (!hasReactions) { - hasReactions = true; - searchMsg.react("⬅"); - searchMsg.react("➡"); - searchMsg.react("🔄"); + const idMod = `${searchMsg.id}:${moment.utc().valueOf()}`; + const buttons: MessageButton[] = []; - const listenerFn = pluginData.events.on("messageReactionAdd", async ({ args: { reaction, user } }) => { - const rMsg = reaction.message; - const member = await pluginData.guild.members.fetch(user.id); - if (rMsg.id !== searchMsg.id) return; - if (member.user.id !== msg.author.id) return; - if (!["⬅", "➡", "🔄"].includes(reaction.emoji.name!)) return; + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("⬅") + .setType("BUTTON") + .setCustomID(`previousButton:${idMod}`) + .setDisabled(currentPage === 1), + ); - if (reaction.emoji.name === "⬅" && currentPage > 1) { - loadSearchPage(currentPage - 1); - } else if (reaction.emoji.name === "➡" && currentPage < searchResult.lastPage) { - loadSearchPage(currentPage + 1); - } else if (reaction.emoji.name === "🔄") { - loadSearchPage(currentPage); + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("➡") + .setType("BUTTON") + .setCustomID(`nextButton:${idMod}`) + .setDisabled(currentPage === searchResult.lastPage), + ); + + buttons.push( + new MessageButton() + .setStyle("SECONDARY") + .setEmoji("🔄") + .setType("BUTTON") + .setCustomID(`reloadButton:${idMod}`), + ); + + const row = new MessageActionRow().addComponents(buttons); + await searchMsg.edit({ content: result, components: [row] }); + + const filter = (iac: MessageComponentInteraction) => iac.message.id === searchMsg.id; + const collector = searchMsg.createMessageComponentInteractionCollector(filter, { time: 2 * MINUTES }); + + collector.on("collect", async (interaction: MessageComponentInteraction) => { + if (msg.author.id !== interaction.user.id) { + interaction.reply(`You are not permitted to use these buttons.`, { ephemeral: true }); + } else { + if (interaction.customID === `previousButton:${idMod}` && currentPage > 1) { + collector.stop(); + await interaction.deferUpdate(); + await loadSearchPage(currentPage - 1); + } else if (interaction.customID === `nextButton:${idMod}` && currentPage < searchResult.lastPage) { + collector.stop(); + await interaction.deferUpdate(); + await loadSearchPage(currentPage + 1); + } else if (interaction.customID === `reloadButton:${idMod}`) { + collector.stop(); + await interaction.deferUpdate(); + await loadSearchPage(currentPage); + } else { + await interaction.deferUpdate(); } + } + }); - if (isFullMessage(rMsg)) { - reaction.remove(); - } - }); + stopCollectionFn = async () => { + collector.stop(); + await searchMsg.edit({ content: searchMsg.content, components: [] }); + }; - clearReactionsFn = async () => { - searchMsg.reactions.removeAll().catch(noop); - pluginData.events.off("messageReactionAdd", listenerFn); - }; - } - - clearTimeout(clearReactionsTimeout); - clearReactionsTimeout = setTimeout(clearReactionsFn, 5 * MINUTES); + clearTimeout(stopCollectionTimeout); + stopCollectionTimeout = setTimeout(stopCollectionFn, 2 * MINUTES); + } else { + searchMsg.edit(result); } - currentPage = searchResult.page; searching = false; }; @@ -370,7 +409,7 @@ async function performMemberSearch( } else { matchingMembers.sort( multiSorter([ - [m => m.username.toLowerCase(), realSortDir], + [m => m.user.username.toLowerCase(), realSortDir], [m => m.discriminator, realSortDir], ]), ); diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index 497c882d..dd36f3fd 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -1,17 +1,7 @@ import { MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions, TextChannel } from "discord.js"; -import { PluginError } from "knub"; import { noop } from "knub/dist/utils"; import moment from "moment"; -export async function waitForComponent( - channel: TextChannel, - toPost: MessageOptions, - components: MessageActionRow[], - options?: WaitForOptions, -) { - throw new PluginError("Unimplemented method waitForComponent called."); -} - export async function waitForButtonConfirm( channel: TextChannel, toPost: MessageOptions,