import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder, APIEmbed, JSONEncodable, } from 'discord.js'; import { getAllMembers } from '@/db/db.js'; import { Command } from '@/types/CommandTypes.js'; import { createPaginationButtons } from '@/util/helpers.js'; const command: Command = { data: new SlashCommandBuilder() .setName('members') .setDescription('Lists all non-bot members of the server'), execute: async (interaction) => { if (!interaction.isChatInputCommand() || !interaction.guild) return; await interaction.deferReply(); let members = await getAllMembers(); members = members.sort((a, b) => (a.discordUsername ?? '').localeCompare(b.discordUsername ?? ''), ); const ITEMS_PER_PAGE = 15; const pages: (APIEmbed | JSONEncodable)[] = []; for (let i = 0; i < members.length; i += ITEMS_PER_PAGE) { const pageMembers = members.slice(i, i + ITEMS_PER_PAGE); const memberList = pageMembers .map((m) => `**${m.discordUsername}** (${m.discordId})`) .join('\n'); const embed = new EmbedBuilder() .setTitle('Members') .setDescription(memberList || 'No members to display.') .setColor(0x0099ff) .addFields({ name: 'Total Members', value: members.length.toString() }) .setFooter({ text: `Page ${Math.floor(i / ITEMS_PER_PAGE) + 1} of ${Math.ceil(members.length / ITEMS_PER_PAGE)}`, }); pages.push(embed); } let currentPage = 0; const getButtonActionRow = () => createPaginationButtons(pages.length, currentPage); const getSelectMenuRow = () => { const options = pages.map((_, index) => ({ label: `Page ${index + 1}`, value: index.toString(), default: index === currentPage, })); const select = new StringSelectMenuBuilder() .setCustomId('select_page') .setPlaceholder('Jump to a page') .addOptions(options); return new ActionRowBuilder().addComponents( select, ); }; const components = pages.length > 1 ? [getButtonActionRow(), getSelectMenuRow()] : []; await interaction.editReply({ embeds: [pages[currentPage]], components, }); const message = await interaction.fetchReply(); if (pages.length <= 1) return; const collector = message.createMessageComponentCollector({ time: 300000, }); collector.on('collect', async (i) => { if (i.user.id !== interaction.user.id) { await i.reply({ content: 'These controls are not for you!', flags: ['Ephemeral'], }); return; } if (i.isButton()) { switch (i.customId) { case 'first': currentPage = 0; break; case 'prev': if (currentPage > 0) currentPage--; break; case 'next': if (currentPage < pages.length - 1) currentPage++; break; case 'last': currentPage = pages.length - 1; break; } } if (i.isStringSelectMenu()) { const selected = parseInt(i.values[0]); if (!isNaN(selected) && selected >= 0 && selected < pages.length) { currentPage = selected; } } await i.update({ embeds: [pages[currentPage]], components: [getButtonActionRow(), getSelectMenuRow()], }); }); collector.on('end', async () => { if (message.editable) { await message.edit({ components: [] }); } }); }, }; export default command;