From 1612b1dd58e21bcc2b14728092a6a9797c42657e Mon Sep 17 00:00:00 2001 From: Evo <85353424+EvolutionX-10@users.noreply.github.com> Date: Fri, 19 Aug 2022 08:31:04 +0530 Subject: [PATCH] feat: menu (#11) --- src/Resolver.ts | 89 +++++++++++++++++++++++++++++++ src/commands/handlers/roleMenu.ts | 5 +- src/commands/{role.ts => menu.ts} | 35 ++++++++---- src/index.ts | 2 +- src/plugins/ownerOnly.ts | 6 ++- 5 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 src/Resolver.ts rename src/commands/{role.ts => menu.ts} (57%) diff --git a/src/Resolver.ts b/src/Resolver.ts new file mode 100644 index 0000000..654e082 --- /dev/null +++ b/src/Resolver.ts @@ -0,0 +1,89 @@ +import { + Collection, + CommandInteraction, + GuildBasedChannel, + GuildMember, + Role, + User, +} from 'discord.js'; +import type { Snowflake } from 'discord-api-types/v10'; + +/** + * It resolves mentions from the content of a command + * @example + * ```ts + * const resolved = new Resolver(content, interaction); + * console.log(resolved.users); // Collection [Map] of users + * ``` + */ +export class Resolver { + public constructor( + private readonly content: string, + private readonly interaction: CommandInteraction + ) {} + + readonly #regex = { + Channel: /<#(?\d{17,20})>/g, + Role: /<@&(?\d{17,20})>/g, + User: /<@!?(?\d{17,20})>/g, + }; + + private getIds(mentionType: 'Channel' | 'Role' | 'User'): string[] { + const matches = this.content.matchAll(this.#regex[mentionType]); + return Array.from(matches) + .map((match) => match.groups?.id) + .filter(Boolean) as string[]; + } + + /** + * Resolves a user from the content. + * @returns The collection of resolved {@link User users}. + */ + public get users(): Readonly> { + const users = this.getIds('User') + .map((id) => this.interaction.client.users.cache.get(id)) + .filter(Boolean) + .map((u) => [u!.id, u]) as [Snowflake, User][]; + + return new Collection(users); + } + + /** + * Resolves a member from the content. + * @returns The collection of resolved {@link GuildMember members}. + */ + public get members(): Readonly> { + const members = this.getIds('User') + .map((id) => this.interaction.guild?.members.cache.get(id)) + .filter(Boolean) + .map((m) => [m!.id, m]) as [Snowflake, GuildMember][]; + + return new Collection(members); + } + + /** + * Resolves a channel from the content. + * @returns The collection of resolved {@link GuildBasedChannel channels}. + */ + public get channels(): Readonly> { + const channels = this.getIds('Channel') + .map((id) => this.interaction.guild?.channels.cache.get(id)) + .filter(Boolean) + .map((c) => [c!.id, c]) as [Snowflake, GuildBasedChannel][]; + + return new Collection(channels); + } + + /** + * Resolves a role from the content. + * @returns The collection of resolved {@link Role roles}. + */ + public get roles(): Readonly> { + const roles = this.getIds('Role') + .map((id) => this.interaction.guild?.roles.cache.get(id)) + .filter(Boolean) + .map((r) => [r!.id, r]) as [Snowflake, Role][]; + + return new Collection(roles); + } +} \ No newline at end of file diff --git a/src/commands/handlers/roleMenu.ts b/src/commands/handlers/roleMenu.ts index 9da1514..404c3a7 100644 --- a/src/commands/handlers/roleMenu.ts +++ b/src/commands/handlers/roleMenu.ts @@ -1,5 +1,5 @@ import { commandModule, CommandType } from "@sern/handler"; -import type { GuildMember } from "discord.js"; +import type { APISelectMenuComponent, GuildMember } from "discord.js"; export default commandModule({ type: CommandType.MenuSelect, @@ -10,7 +10,8 @@ export default commandModule({ const roles = interaction.values; const menuRoles: string[] = ( - interaction.message.components[0].components[0].data as any + interaction.message.components[0].components[0] + .data as Readonly ).options.map((o: { label: string; value: string }) => o.value); const member = interaction.member as GuildMember; diff --git a/src/commands/role.ts b/src/commands/menu.ts similarity index 57% rename from src/commands/role.ts rename to src/commands/menu.ts index e1a914c..3086606 100644 --- a/src/commands/role.ts +++ b/src/commands/menu.ts @@ -1,14 +1,31 @@ import { commandModule, CommandType } from "@sern/handler"; -import { ActionRowBuilder, Collection, Role, TextChannel, SelectMenuBuilder } from "discord.js"; +import { ActionRowBuilder, Collection, Role, TextChannel, SelectMenuBuilder, ApplicationCommandOptionType, ChannelType } from "discord.js"; import { ownerOnly } from "../plugins/ownerOnly"; +import { publish } from "../plugins/publish"; +import { Resolver } from "../Resolver"; export default commandModule({ - plugins: [ownerOnly()], - type: CommandType.Text, + plugins: [ownerOnly(), publish()], + type: CommandType.Slash, description: "Select Menu Role", - async execute(ctx) { - const channel = ctx.message.mentions.channels.first() as TextChannel; - const role = ctx.message.mentions.roles; + options: [ + { + name: 'channel', + type: ApplicationCommandOptionType.Channel, + description: 'The channel to send the message to', + channelTypes: [ChannelType.GuildText], + required: true + }, + { + name: 'role', + type: ApplicationCommandOptionType.String, + description: 'The roles to attach (upto 25)', + required: true, + } + ], + async execute(ctx, [, options]) { + const channel = options.getChannel('channel', true) as TextChannel; + const role = new Resolver(options.getString('role', true), ctx.interaction).roles; if (!channel || !role) return ctx.reply("Missing channel or role"); if (role.size > 25) return ctx.reply("Too many roles"); @@ -23,24 +40,24 @@ export default commandModule({ `Some roles are managed by integration or higher than my highest role.\nPlease try again` ); } + await ctx.interaction.deferReply(); const row = createMenu(channel, role); await channel.send({ content: "Role Menu", components: [row], }); - await ctx.message.react("✅"); + await ctx.interaction.editReply("✅ Done!"); }, }); function createMenu(channel: TextChannel, role: Collection) { - // sern role #channel @role if (!channel || !role) throw new Error("Missing channel or role"); const menu = new SelectMenuBuilder() .setCustomId("role-menu") .setMaxValues(role.size) .setMinValues(0) - .setPlaceholder("Pick some roles") + .setPlaceholder("Pick some roles here!") .setOptions( role.map((r) => { return { diff --git a/src/index.ts b/src/index.ts index dec3679..05358de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ const client = new Client({ Sern.init({ client, sernEmitter: new SernEmitter(), - defaultPrefix: "sern", + defaultPrefix: "!sern", commands: "dist/src/commands", events: "dist/src/events", }); diff --git a/src/plugins/ownerOnly.ts b/src/plugins/ownerOnly.ts index e81f3db..b735f86 100644 --- a/src/plugins/ownerOnly.ts +++ b/src/plugins/ownerOnly.ts @@ -1,5 +1,9 @@ import { CommandType, EventPlugin, PluginType } from "@sern/handler"; -const ownerIDs = ["697795666373640213", "182326315813306368"]; +const ownerIDs = [ + "697795666373640213", + "182326315813306368", + "756393473430519849", +]; export function ownerOnly(): EventPlugin { return { type: PluginType.Event,