From bc835922a5d72badb41d4784adc480b18283bf32 Mon Sep 17 00:00:00 2001 From: Ashley Rosch Date: Fri, 20 Aug 2021 03:19:44 -0500 Subject: [PATCH] added slash command support --- rolemanager/index.ts | 265 +++++++++++++++++++++++++++++++------------ 1 file changed, 192 insertions(+), 73 deletions(-) diff --git a/rolemanager/index.ts b/rolemanager/index.ts index 7610e76..01069f7 100644 --- a/rolemanager/index.ts +++ b/rolemanager/index.ts @@ -1,21 +1,43 @@ -import { ButtonInteraction, GuildMember, Interaction, Message, MessageActionRow, MessageButton, MessageSelectMenu, MessageSelectOptionData, SelectMenuInteraction } from "discord.js"; +import { ButtonInteraction, Client, CommandInteraction, GuildMember, Interaction, Message, MessageActionRow, MessageButton, MessageSelectMenu, MessageSelectOptionData, PartialDMChannel, SelectMenuInteraction, TextBasedChannels } from "discord.js"; import { TextChannel } from "discord.js"; import * as Opt from "../types/Opt"; +import refreshCommands from "./slash"; -import InteractionRoles from "./data"; +import { InteractionRoles, GuildId } from "./data"; + +type ReplyableInteraction = ButtonInteraction | CommandInteraction | SelectMenuInteraction; + +const isReplyableInteraction = (res: any): boolean => res instanceof ButtonInteraction || res instanceof CommandInteraction || res instanceof SelectMenuInteraction; + +const sendMessage = (res: ReplyableInteraction | TextBasedChannels, msg: any, ephemeral?: boolean) => { + + let arg: any = {}; + if (typeof msg === 'object') + arg = msg; + else + arg.content = msg; + + if (isReplyableInteraction(res) && ephemeral) + arg.ephemeral = true; + + if (isReplyableInteraction(res)) + (res as ReplyableInteraction).reply(arg); + else + (res as TextBasedChannels).send(arg); +} //initialize discord const Discord = require('discord.js'), - client = new Discord.Client({ intents: [ + client: Client = new Discord.Client({ intents: [ 'GUILD_MEMBERS' ] }); /** * send message containing menu - * @param c text channel to send message + * @param res text channel to send message or interaction to reply to * @param menuOpts menu data object */ -function sendMenu(c: TextChannel, menuOpts: Opt.Menu) { +function sendMenu(res: ReplyableInteraction|TextBasedChannels, menuOpts: Opt.Menu, ephemeral?: boolean) { let row = new MessageActionRow() @@ -52,10 +74,13 @@ function sendMenu(c: TextChannel, menuOpts: Opt.Menu) { select.addOptions(options); row.addComponents(select) - c.send({ + let arg = { content: menuOpts.message, components: [row] - }); + }; + + sendMessage(res, arg, ephemeral); + } /** @@ -63,7 +88,7 @@ function sendMenu(c: TextChannel, menuOpts: Opt.Menu) { * @param c text channel to send message * @param rowOpts row data object */ -function sendRow(c: TextChannel, rowOpts: Opt.Row) { +function sendRow(res: ReplyableInteraction|TextBasedChannels, rowOpts: Opt.Row, ephemeral?: boolean) { let row = new MessageActionRow(); @@ -84,42 +109,87 @@ function sendRow(c: TextChannel, rowOpts: Opt.Row) { row.addComponents(options); - c.send({ + let arg = { content: rowOpts.message, components: [row] - }); + }; + + sendMessage(res, arg, ephemeral); + } -//uncomment if you want to create a message, will change to slash command -client.on('message', (m: Message) => { +//on interaction event +client.on('interactionCreate', (interaction: Interaction) => { - if (m.author.id === '167336999844315137' && m.content === '.') { + if (interaction.inGuild()) { - for (let i = 0; i < InteractionRoles.length; i++) { - switch (InteractionRoles[i].type) { - case 'menu': - sendMenu(m.channel as TextChannel, InteractionRoles[i] as Opt.Menu); - break; - case 'row': - sendRow(m.channel as TextChannel, InteractionRoles[i] as Opt.Row); - break; - } - } + if (interaction.isSelectMenu()) + handleSelect(interaction as SelectMenuInteraction); + + else if (interaction.isButton()) + handleButton(interaction as ButtonInteraction); + + else if (interaction.isCommand()) + handleCommand(interaction as CommandInteraction); } }); -//on interaction event -client.on('interactionCreate', (interaction: Interaction) => { +async function handleButton(interaction: ButtonInteraction) { + + //start typing + await interaction.deferReply({ ephemeral: true }); + + //get custom id + let customId: string; + if (interaction.component instanceof MessageButton) + customId = interaction.component.customId; + else if ('custom_id' in interaction.component) + customId = interaction.component.custom_id; - if (interaction.isSelectMenu()) - handleSelect(interaction as SelectMenuInteraction); + //list of button rows from data + let rows = InteractionRoles.filter(opt => opt.type === 'row'); - else if (interaction.isButton()) - handleButton(interaction as ButtonInteraction); + //loop through each the buttons rows + for (let i = 0; i < rows.length; i++) { -}); + let row = rows[i] as Opt.Row; + + //filter to just buttons with matching id + let matchingButtons = row.buttons.filter(opt => opt.id === customId); + + if (matchingButtons.length) { + + //if there's no role id, then nothing can be done + if (matchingButtons[0].roleid && interaction.member instanceof GuildMember) { + + //since it is a GuildMember, let's force typescript to rememeber it + let member = interaction.member as GuildMember; + + //get corresponding role + let role = await interaction.guild.roles.fetch(matchingButtons[0].roleid); + + //if clicker (member) does have the role + if (member.roles.cache.some(r => r.id === role.id)) { + + //remove role + await member.roles.remove(role.id); + interaction.editReply(`Removed \`${role.name}\``); + + } else { + + //add role + await member.roles.add(role.id); + interaction.editReply(`Added \`${role.name}\``); + + } + break; + } + } + } + +}; //on menu click async function handleSelect(interaction: SelectMenuInteraction) { @@ -208,60 +278,109 @@ async function handleSelect(interaction: SelectMenuInteraction) { }; -async function handleButton(interaction: ButtonInteraction) { - - //start typing - await interaction.deferReply({ ephemeral: true }); - - //get custom id - let customId: string; - if (interaction.component instanceof MessageButton) - customId = interaction.component.customId; - else if ('custom_id' in interaction.component) - customId = interaction.component.custom_id; +//on slash command +async function handleCommand(interaction: CommandInteraction) { - //list of button rows from data - let rows = InteractionRoles.filter(opt => opt.type === 'row'); + console.log(interaction.commandName) - //loop through each the buttons rows - for (let i = 0; i < rows.length; i++) { + let category: number; - let row = rows[i] as Opt.Row; + switch (interaction.commandName) { + + case 'roles': - //filter to just buttons with matching id - let matchingButtons = row.buttons.filter(opt => opt.id === customId); + category = interaction.options.getInteger('category', true); + sendRoles(interaction, InteractionRoles[category].id, true); + break; + + case 'rolesall': + if (interaction.user.id === '167336999844315137') { - if (matchingButtons.length) { + let givenChannel = interaction.options.getChannel('channel'), + channel: TextChannel = await client.channels.fetch(interaction.channelId) as TextChannel; - //if there's no role id, then nothing can be done - if (matchingButtons[0].roleid && interaction.member instanceof GuildMember) { + if (givenChannel && givenChannel.type === 'GUILD_TEXT') + channel = givenChannel as TextChannel; - //since it is a GuildMember, let's force typescript to rememeber it - let member = interaction.member as GuildMember; + if (channel) { + category = interaction.options.getInteger('category'); + sendRoles(channel, category ? InteractionRoles[category].id : undefined); + interaction.reply({ + content: `sending message in <#${channel.id}>`, + ephemeral: true + }) + + } else + interaction.reply({ + content: 'error finding channel to send message in', + ephemeral: true + }) - //get corresponding role - let role = await interaction.guild.roles.fetch(matchingButtons[0].roleid); + } else + interaction.reply({ + content: 'missing permissions', + ephemeral: true + }) + break; + + case 'updateroles': + //TODO reload data.ts + //TODO reload slash.ts + interaction.reply({ + content: 'command incomplete', + ephemeral: true + }) + break; - //if clicker (member) does have the role - if (member.roles.cache.some(r => r.id === role.id)) { - - //remove role - await member.roles.remove(role.id); - interaction.editReply(`Removed \`${role.name}\``); - - } else { - - //add role - await member.roles.add(role.id); - interaction.editReply(`Added \`${role.name}\``); - - } - break; - } - } } -}; +} + +async function sendRoles(res: ReplyableInteraction|TextBasedChannels, id?: string, ephemeral?: boolean) { + + if (id) { + + let rowarr = InteractionRoles.filter(r => r.id === id); + if (rowarr.length) { + + let row = rowarr[0]; + + if (row.type === 'menu') + sendMenu(res, row as Opt.Menu, ephemeral); + else if (row.type === 'row') + sendRow(res, row as Opt.Row, ephemeral); + + } else + sendMessage(res, `invalid id ${id}`, ephemeral); + + } else if (isReplyableInteraction(res)) + sendMessage(res, `missing id`, ephemeral); + + else { + + for (let i = 0; i < InteractionRoles.length; i++) { + + switch (InteractionRoles[i].type) { + case 'menu': + sendMenu(res, InteractionRoles[i] as Opt.Menu, ephemeral); + break; + case 'row': + sendRow(res, InteractionRoles[i] as Opt.Row, ephemeral); + break; + } + + } + + } + +} + +//register slash commands +client.on('ready', () => { + console.log('Ready') + setInterval(() => refreshCommands(client.user.id), 1000*60*60*24); + refreshCommands(client.user.id) +}); //login with token client.login(require('fs').readFileSync('../token').toString()); \ No newline at end of file