Add select menu interaction for editing list of channels

This commit is contained in:
Charlie Laabs
2021-12-28 19:10:14 -06:00
parent 2f719ba1ee
commit fbee6ef249
2 changed files with 147 additions and 50 deletions

View File

@@ -66,6 +66,11 @@ export const listenChannelCommand = new SlashCommandBuilder()
sub sub
.setName('list') .setName('list')
.setDescription(`List the channels the bot is currently actively listening to.`) .setDescription(`List the channels the bot is currently actively listening to.`)
)
.addSubcommand((sub) =>
sub
.setName('modify')
.setDescription(`Add or remove channels via select menu UI (first 25 text channels only)`)
); );
export const trainCommand = new SlashCommandBuilder() export const trainCommand = new SlashCommandBuilder()

View File

@@ -11,8 +11,11 @@ import Markov, {
import { createConnection } from 'typeorm'; import { createConnection } from 'typeorm';
import { MarkovInputData } from 'markov-strings-db/dist/src/entity/MarkovInputData'; import { MarkovInputData } from 'markov-strings-db/dist/src/entity/MarkovInputData';
import { APIInteractionGuildMember } from 'discord-api-types';
import type { PackageJsonPerson } from 'types-package-json'; import type { PackageJsonPerson } from 'types-package-json';
import {
APISelectMenuComponent,
APIInteractionGuildMember,
} from 'discord.js/node_modules/discord-api-types';
import L from './logger'; import L from './logger';
import { Channel } from './entity/Channel'; import { Channel } from './entity/Channel';
import { Guild } from './entity/Guild'; import { Guild } from './entity/Guild';
@@ -32,6 +35,12 @@ interface MarkovDataCustom {
attachments: string[]; attachments: string[];
} }
interface SelectMenuChannel {
id: string;
listen?: boolean;
name?: string;
}
const INVALID_PERMISSIONS_MESSAGE = 'You do not have the permissions for this action.'; const INVALID_PERMISSIONS_MESSAGE = 'You do not have the permissions for this action.';
const INVALID_GUILD_MESSAGE = 'This action must be performed within a server.'; const INVALID_GUILD_MESSAGE = 'This action must be performed within a server.';
@@ -77,6 +86,25 @@ async function getValidChannels(guild: Discord.Guild): Promise<Discord.TextChann
return channels; return channels;
} }
async function getTextChannels(guild: Discord.Guild): Promise<SelectMenuChannel[]> {
const MAX_SELECT_OPTIONS = 25;
const textChannels = guild.channels.cache.filter(
(c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel
);
const foundDbChannels = await Channel.findByIds(Array.from(textChannels.keys()));
const foundDbChannelsWithName: SelectMenuChannel[] = foundDbChannels.map((c) => ({
...c,
name: textChannels.find((t) => t.id === c.id)?.name,
}));
const notFoundDbChannels: SelectMenuChannel[] = textChannels
.filter((c) => !foundDbChannels.find((d) => d.id === c.id))
.map((c) => ({ id: c.id, listen: false, name: textChannels.find((t) => t.id === c.id)?.name }));
const limitedDbChannels = foundDbChannelsWithName
.concat(notFoundDbChannels)
.slice(0, MAX_SELECT_OPTIONS);
return limitedDbChannels;
}
async function addValidChannels(channels: Discord.TextChannel[], guildId: string): Promise<void> { async function addValidChannels(channels: Discord.TextChannel[], guildId: string): Promise<void> {
const dbChannels = channels.map((c) => { const dbChannels = channels.map((c) => {
return Channel.create({ id: c.id, guild: Guild.create({ id: guildId }), listen: true }); return Channel.create({ id: c.id, guild: Guild.create({ id: guildId }), listen: true });
@@ -165,7 +193,7 @@ function messageToData(message: Discord.Message): AddDataProps {
async function saveGuildMessageHistory( async function saveGuildMessageHistory(
interaction: Discord.Message | Discord.CommandInteraction interaction: Discord.Message | Discord.CommandInteraction
): Promise<string> { ): Promise<string> {
if (!isModerator(interaction.member as any)) return INVALID_PERMISSIONS_MESSAGE; if (!isModerator(interaction.member)) return INVALID_PERMISSIONS_MESSAGE;
if (!interaction.guildId || !interaction.guild) return INVALID_GUILD_MESSAGE; if (!interaction.guildId || !interaction.guild) return INVALID_GUILD_MESSAGE;
const markov = await getMarkovByGuildId(interaction.guildId); const markov = await getMarkovByGuildId(interaction.guildId);
const channels = await getValidChannels(interaction.guild); const channels = await getValidChannels(interaction.guild);
@@ -456,8 +484,7 @@ client.on('messageUpdate', async (oldMessage, newMessage) => {
}); });
client.on('interactionCreate', async (interaction) => { client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return; if (interaction.isCommand()) {
L.info({ command: interaction.commandName }, 'Recieved slash command'); L.info({ command: interaction.commandName }, 'Recieved slash command');
if (interaction.commandName === helpCommand.name) { if (interaction.commandName === helpCommand.name) {
@@ -482,7 +509,7 @@ client.on('interactionCreate', async (interaction) => {
const reply = await listValidChannels(interaction); const reply = await listValidChannels(interaction);
await interaction.editReply(reply); await interaction.editReply(reply);
} else if (subCommand === 'add') { } else if (subCommand === 'add') {
if (!isModerator(interaction.member as any)) { if (!isModerator(interaction.member)) {
await interaction.deleteReply(); await interaction.deleteReply();
await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true }); await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true });
return; return;
@@ -493,7 +520,7 @@ client.on('interactionCreate', async (interaction) => {
`Added ${channels.length} text channels to the list. Use \`/train\` to update the past known messages.` `Added ${channels.length} text channels to the list. Use \`/train\` to update the past known messages.`
); );
} else if (subCommand === 'remove') { } else if (subCommand === 'remove') {
if (!isModerator(interaction.member as any)) { if (!isModerator(interaction.member)) {
await interaction.deleteReply(); await interaction.deleteReply();
await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true }); await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true });
return; return;
@@ -503,12 +530,77 @@ client.on('interactionCreate', async (interaction) => {
await interaction.editReply( await interaction.editReply(
`Removed ${channels.length} text channels from the list. Use \`/train\` to remove these channels from the past known messages.` `Removed ${channels.length} text channels from the list. Use \`/train\` to remove these channels from the past known messages.`
); );
} else if (subCommand === 'modify') {
await interaction.deleteReply();
if (!interaction.guild) {
await interaction.followUp({ content: INVALID_GUILD_MESSAGE, ephemeral: true });
return;
}
if (!isModerator(interaction.member)) {
await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true });
return;
}
const dbTextChannels = await getTextChannels(interaction.guild);
const row = new Discord.MessageActionRow().addComponents(
new Discord.MessageSelectMenu()
.setCustomId('listen-modify-select')
.setPlaceholder('Nothing selected')
.setMinValues(0)
.setMaxValues(dbTextChannels.length)
.addOptions(
dbTextChannels.map((c) => ({
label: `#${c.name}` || c.id,
value: c.id,
default: c.listen || false,
}))
)
);
await interaction.followUp({
content: 'Select which channels you would like to the bot to actively listen to',
components: [row],
ephemeral: true,
});
} }
} else if (interaction.commandName === trainCommand.name) { } else if (interaction.commandName === trainCommand.name) {
await interaction.deferReply(); await interaction.deferReply();
const responseMessage = await saveGuildMessageHistory(interaction); const responseMessage = await saveGuildMessageHistory(interaction);
await interaction.editReply(responseMessage); await interaction.editReply(responseMessage);
} }
} else if (interaction.isSelectMenu()) {
await interaction.deferUpdate();
const { guild } = interaction;
if (!isModerator(interaction.member)) {
await interaction.followUp({ content: INVALID_PERMISSIONS_MESSAGE, ephemeral: true });
return;
}
if (!guild) {
await interaction.deleteReply();
await interaction.followUp({ content: INVALID_GUILD_MESSAGE, ephemeral: true });
return;
}
const allChannels =
(interaction.component as APISelectMenuComponent).options?.map((o) => o.value) || [];
const selectedChannelIds = interaction.values;
const textChannels = (
await Promise.all(
allChannels.map(async (c) => {
return guild.channels.fetch(c);
})
)
).filter((c): c is Discord.TextChannel => c !== null && c instanceof Discord.TextChannel);
const unselectedChannels = textChannels.filter((t) => !selectedChannelIds.includes(t.id));
const selectedChannels = textChannels.filter((t) => selectedChannelIds.includes(t.id));
await addValidChannels(selectedChannels, guild.id);
await removeValidChannels(unselectedChannels, guild.id);
await interaction.followUp({
content: 'Updated actively listened to channels list.',
ephemeral: true,
});
}
}); });
/** /**