mirror of
https://github.com/sern-handler/sern-community
synced 2026-06-27 18:22:18 +00:00
feat: menu (#11)
This commit is contained in:
89
src/Resolver.ts
Normal file
89
src/Resolver.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user