feat: menu (#11)

This commit is contained in:
Evo
2022-08-19 08:31:04 +05:30
committed by GitHub
parent e570e4716a
commit 1612b1dd58
5 changed files with 124 additions and 13 deletions

89
src/Resolver.ts Normal file
View File

@@ -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: /<#(?<id>\d{17,20})>/g,
Role: /<@&(?<id>\d{17,20})>/g,
User: /<@!?(?<id>\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<Collection<Snowflake, User>> {
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<Snowflake, User>(users);
}
/**
* Resolves a member from the content.
* @returns The collection of resolved {@link GuildMember members}.
*/
public get members(): Readonly<Collection<Snowflake, GuildMember>> {
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<Snowflake, GuildMember>(members);
}
/**
* Resolves a channel from the content.
* @returns The collection of resolved {@link GuildBasedChannel channels}.
*/
public get channels(): Readonly<Collection<Snowflake, GuildBasedChannel>> {
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<Snowflake, GuildBasedChannel>(channels);
}
/**
* Resolves a role from the content.
* @returns The collection of resolved {@link Role roles}.
*/
public get roles(): Readonly<Collection<Snowflake, Role>> {
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<Snowflake, Role>(roles);
}
}

View File

@@ -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<APISelectMenuComponent>
).options.map((o: { label: string; value: string }) => o.value);
const member = interaction.member as GuildMember;

View File

@@ -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<string, Role>) {
// 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 {

View File

@@ -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",
});

View File

@@ -1,5 +1,9 @@
import { CommandType, EventPlugin, PluginType } from "@sern/handler";
const ownerIDs = ["697795666373640213", "182326315813306368"];
const ownerIDs = [
"697795666373640213",
"182326315813306368",
"756393473430519849",
];
export function ownerOnly(): EventPlugin<CommandType.Both> {
return {
type: PluginType.Event,