More work on perms and blacklisting

This commit is contained in:
MSWS
2023-04-11 19:41:05 -07:00
parent 85bcc4d340
commit 28649ed845
16 changed files with 316 additions and 118 deletions

View File

@@ -38,7 +38,9 @@
"no-var": "error",
"indent": [
"error",
4
4, {
"SwitchCase": 1
}
],
"camelcase": "warn",
"block-scoped-var": "error",
@@ -127,11 +129,6 @@
],
"padding-line-between-statements": [
"error",
{
"blankLine": "always",
"prev": "*",
"next": "return"
},
{
"blankLine": "always",
"prev": "directive",

View File

@@ -1,2 +1,3 @@
{
"TOKEN": ""
}

View File

@@ -35,7 +35,13 @@
"ds-voice": {
"enabled": true,
"joinChannel": "1056645195510321283",
"maxSizes": [2, 10, 15, 50, 100],
"maxSizes": [
2,
10,
15,
50,
100
],
"tierRequirements": {
"create": "SILVER",
"join": "NONE",
@@ -43,5 +49,12 @@
"lock": "PLATINUM",
"whitelist": "GOLD"
}
},
"permissions": {
"leRoles": [
"1056498302360768550",
"1056498302360768551",
"1056498302394314834"
]
}
}

View File

@@ -1 +1 @@
{"voice-channels":{"482668550331301928":{"user":"482668550331301928","channelName":"asdf","size":2,"locked":false,"whitelist":[],"blacklist":[],"id":"1095214227498737844"},"219601562048135168":{"user":"219601562048135168","channelName":"It works!","size":20,"locked":false,"whitelist":["265749200468180992","161928064614400001","228251456162103306"],"blacklist":[]}}}
{"voice-channels":{"219601562048135168":{"user":"219601562048135168","channelName":"asdfasdfasdf","size":0,"locked":false,"whitelist":["161928064614400001"],"blacklist":["161928064614400001"],"mode":"blacklisted","id":"1095538052673314826"}}}

View File

@@ -12,6 +12,7 @@ import { ModuleManager } from "./modules/Manager";
import { ReactRoles } from "./modules/ReactRoles";
import { PingCommand } from "./modules/commands/PingCommand";
import { VoiceCommand } from "./modules/commands/VoiceCommand";
import { PermissionsManager } from "./modules/PermissionsManager";
export class Bot implements DSBot {
maul: MaulAPI | undefined;
@@ -50,6 +51,7 @@ export class Bot implements DSBot {
const cmds = this.modules.addModule(new CommandManager(this)) as CommandManager;
this.modules.addModule(new ReactRoles(this));
this.modules.addModule(new DSVoice(this));
this.modules.addModule(new PermissionsManager(this));
this.modules.loadModules();

View File

@@ -1,7 +1,8 @@
import { BaseGuildVoiceChannel, ChannelType, VoiceChannel, VoiceState } from "discord.js";
import { BaseGuildVoiceChannel, ChannelType, PermissionResolvable, PermissionsBitField, VoiceChannel, VoiceState } from "discord.js";
import { DSBot } from "../api/DSBot";
import { Module } from "../api/Module";
import { GuildedConfigEntry } from "../api/data/Config";
import { PermissionBuilder, PermissionsManager } from "./PermissionsManager";
export class DSVoice extends Module {
name = "DSVoice";
@@ -77,7 +78,7 @@ export class DSVoice extends Module {
newState.setChannel(newChannel);
data.id = newChannel.id;
this.updateUserChannel(data, newState.guild.id);
return newChannel;
}
@@ -144,25 +145,11 @@ export class DSVoice extends Module {
await guildChannel.setName(channel.channelName);
await guildChannel.setUserLimit(channel.size);
const kickers: Promise<unknown>[] = [];
if (channel.whitelist) {
const members = guildChannel.members;
for (const member of members.values()) {
if (!channel.whitelist.includes(member.id))
kickers.push(member.voice.setChannel(null));
}
} else if (channel.blacklist) {
const members = guildChannel.members;
for (const member of members.values()) {
if (channel.blacklist.includes(member.id))
kickers.push(member.voice.setChannel(null));
}
}
await Promise.all(kickers);
this.enforceLists(channel, guild);
}
async enforceLists(channel: VoiceChannelEntry, guild: string) {
console.log(`Enforcing lists for ${channel.id} in ${guild}...`);
if (!channel.id)
return;
const guildChannel =
@@ -172,10 +159,39 @@ export class DSVoice extends Module {
if (!guildChannel)
return;
if (channel.mode === VoiceChannelMode.PUBLIC) {
guildChannel.lockPermissions();
// guildChannel.permissionOverwrites.edit()
const permManager = this.main.modules.getModule<PermissionsManager>(PermissionsManager);
const config = permManager?.configs[guild];
const leRoles = config ? config.value.leRoles : [];
if (!permManager)
throw new Error("No perm manager");
console.log(`Enforcing mode ${channel.mode} for ${channel.id} in ${guild}...`);
const perms = new PermissionBuilder(guildChannel.guild);
const flags: PermissionResolvable[] = ["ViewChannel", "Connect"];
const all = PermissionsBitField.All;
switch (channel.mode) {
case VoiceChannelMode.PUBLIC:
perms.grantAccess(flags, "@everyone");
break;
case VoiceChannelMode.WHITELIST:
perms.blockAccess(all, "@everyone");
perms.grantAccess(flags, channel.user, ...channel.whitelist, ...leRoles);
break;
case VoiceChannelMode.BLACKLIST:
perms.grantAccess(flags, "@everyone");
perms.blockAccess(all, ...channel.blacklist);
break;
case VoiceChannelMode.LOCKED:
perms.blockAccess(all, "@everyone");
break;
default:
throw new Error("Invalid mode: " + channel.mode);
}
perms.grantAccess(flags, channel.user, ...leRoles);
permManager.applyPermissions(perms.permissions, guildChannel);
}
}
@@ -185,6 +201,8 @@ export class DSVoiceConfig implements GuildedConfigEntry<DSVoiceConfigEntry> {
value: DSVoiceConfigEntry;
constructor(guildId: string) {
this.guildId = guildId;
this.value = {
enabled: false,
joinChannel: "",
@@ -198,8 +216,6 @@ export class DSVoiceConfig implements GuildedConfigEntry<DSVoiceConfigEntry> {
},
maxSizes: []
};
this.guildId = guildId;
}
}
@@ -252,17 +268,6 @@ export class VoiceChannelEntry {
this.channelName = `${userId}'s Channel`;
this.mode = VoiceChannelMode.PUBLIC;
}
toJSON() {
return {
user: this.user,
channelName: this.channelName,
size: this.size,
locked: this.locked,
whitelist: this.whitelist,
blacklist: this.blacklist
};
}
}
export enum VoiceChannelMode {

View File

@@ -1,7 +1,7 @@
import { BitFieldResolvable, GuildChannel, PermissionResolvable, PermissionsBitField } from "discord.js";
import { Guild, GuildChannel, OverwriteData, OverwriteResolvable, OverwriteType, PermissionResolvable, PermissionsBitField } from "discord.js";
import { DSBot } from "../api/DSBot";
import { Module } from "../api/Module";
import { ConfigEntry, GuildedConfigEntry } from "../api/data/Config";
import { GuildedConfigEntry } from "../api/data/Config";
export class PermissionsManager extends Module {
name = "PermissionsManager";
@@ -11,53 +11,93 @@ export class PermissionsManager extends Module {
super(main);
}
onEnable(): void {
async onEnable() {
const loaders = [];
for (const guild of this.main.client.guilds.cache.values()) {
loaders.push(async () => {
const config = await this.main.config.getGuildConfig(guild.id);
const entry = config.registerEntry(new PermissionsConfig(guild.id));
config.load();
await config.load();
this.configs[guild.id] = entry as PermissionsConfig;
});
}
await Promise.all(loaders.map((f) => f()));
}
lockdownChannel(channel: GuildChannel, view: boolean) {
const overwrites = channel.permissionOverwrites;
// const flags: PermissionResolvable = ["Connect"];
const flags = new PermissionsBitField("Connect");
if (view)
flags.add("ViewChannel");
if (!view) {
overwrites.cache.get("@everyone")?.allow.remove(flags);
overwrites.cache.get("@everyone")?.deny.add(flags);
}
overwrites.cache.get("@everyone")?.allow.remove(flags);
overwrites.cache.get("@everyone")?.deny.add(flags);
const config = this.configs[channel.guild.id].value;
if (!config)
return;
for (const leRole of config.leRoles) {
overwrites.cache.get(leRole)?.deny.remove(flags);
overwrites.cache.get(leRole)?.allow.add(flags);
}
async applyPermissions(perms: OverwriteResolvable[], channel: GuildChannel) {
channel.permissionOverwrites.set(perms);
}
grantAccess(channel: GuildChannel, member: string) {
const overwrites = channel.permissionOverwrites;
overwrites.cache.get(member)?.allow.remove("ViewChannel", "Connect");
overwrites.cache.get(member)?.deny.add("ViewChannel");
}
// async grantAccess(channel: GuildChannel | string, flags = new PermissionsBitField("Connect"), ...members: string[]) {
// if (typeof channel === "string") {
// channel = this.main.client.channels.cache.get(channel) as GuildChannel;
// if (!channel)
// throw new Error("Channel not found");
// }
// flags.add("ViewChannel");
// this.processRoleList(channel, members);
// console.log(`Granting ${flags.bitfield} to ${members.join(", ")}`);
// const permWrites: OverwriteData[] = [];
// for (const member of members) {
// if (channel.guild.roles.cache.has(member)) {
// permWrites.push({
// id: member,
// allow: flags,
// type: OverwriteType.Role
// });
// } else {
// permWrites.push({
// id: member,
// allow: flags,
// type: OverwriteType.Member
// });
// }
// }
// await channel.permissionOverwrites.set(permWrites);
// }
// async blockAccess(channel: GuildChannel | string, flags = new PermissionsBitField("Connect"), ...members: string[]) {
// if (typeof channel === "string") {
// channel = this.main.client.channels.cache.get(channel) as GuildChannel;
// if (!channel)
// throw new Error("Channel not found");
// }
// processRoleList(channel.guild, members);
// console.log(`Revoking ${flags.bitfield} to ${members.join(", ")}`);
// const permWrites: OverwriteResolvable[] = [];
// for (const member of members) {
// if (channel.guild.roles.cache.has(member)) {
// permWrites.push({
// id: member,
// deny: flags,
// type: OverwriteType.Role
// });
// } else {
// permWrites.push({
// id: member,
// deny: flags,
// type: OverwriteType.Member
// });
// }
// }
// await channel.permissionOverwrites.set(permWrites);
// }
}
export class PermissionsConfig implements GuildedConfigEntry<PermissionsEntry> {
name = "permissions";
guildId: string;
value: PermissionsEntry;
constructor(guildId: string) {
this.guildId = guildId;
this.value = {
leRoles: [],
dsRoles: [],
@@ -65,8 +105,6 @@ export class PermissionsConfig implements GuildedConfigEntry<PermissionsEntry> {
nitroRole: ""
};
}
name = "permissions";
}
export interface PermissionsEntry {
@@ -74,4 +112,65 @@ export interface PermissionsEntry {
dsRoles: string[];
bannedRole: string;
nitroRole: string;
}
}
export class PermissionBuilder {
permissions: OverwriteResolvable[] = [];
guild: Guild;
constructor(guild: Guild) {
this.guild = guild;
}
grantAccess(flags: PermissionResolvable[], ...members: string[]): PermissionBuilder {
processRoleList(this.guild, members);
for (const member of members) {
if (this.guild.roles.cache.has(member)) {
this.permissions.push({
id: member,
allow: flags,
type: OverwriteType.Role
});
} else {
this.permissions.push({
id: member,
allow: flags,
type: OverwriteType.Member
});
}
}
return this;
}
blockAccess(flags: PermissionResolvable, ...members: string[]): PermissionBuilder {
processRoleList(this.guild, members);
for (const member of members) {
if (this.guild.roles.cache.has(member)) {
this.permissions.push({
id: member,
deny: flags,
type: OverwriteType.Role
});
} else {
this.permissions.push({
id: member,
deny: flags,
type: OverwriteType.Member
});
}
}
return this;
}
}
/**
* @param guild The guild to process the members for
* @param members The members to process
*/
function processRoleList(guild: Guild, members: string[]) {
const index = members.indexOf("@everyone");
if (index !== -1) {
members.splice(index, 1);
members.push(guild.roles.everyone.id);
}
}

View File

@@ -4,6 +4,8 @@ import { Command, SubCommand, SubGroupCommand } from "../../api/data/Command";
import { RenameCommand } from "./voice/RenameCommand";
import { ResizeCommand } from "./voice/ResizeCommand";
import { WhitelistCommand } from "./voice/WhitelistCommand";
import { SetModeCommand } from "./voice/SetModeCommand";
import { BlacklistCommand } from "./voice/BlacklistCommand";
export class VoiceCommand extends Command {
name = "voice";
@@ -19,6 +21,8 @@ export class VoiceCommand extends Command {
this.subCommands.set("rename", new RenameCommand(main));
this.subCommands.set("resize", new ResizeCommand(main));
this.subCommands.set("whitelist", new WhitelistCommand(main));
this.subCommands.set("blacklist", new BlacklistCommand(main));
this.subCommands.set("setmode", new SetModeCommand(main));
for (const subCommand of this.subCommands.values()) {
if (subCommand.data instanceof SlashCommandSubcommandBuilder)

View File

@@ -0,0 +1,43 @@
import { ChatInputCommandInteraction, CommandInteraction, SlashCommandSubcommandGroupBuilder } from "discord.js";
import { DSBot } from "../../../api/DSBot";
import { SubGroupCommand } from "../../../api/data/Command";
import { AddCommand } from "./list/AddCommand";
import { ListCommand } from "./list/ListCommand";
import { RemoveCommand } from "./list/RemoveCommand";
import { VoiceChannelMode } from "../../DSVoice";
export class BlacklistCommand extends SubGroupCommand {
name = "blacklist";
data = new SlashCommandSubcommandGroupBuilder()
.setName(this.name)
.setDescription("Blacklist management");
constructor(main: DSBot) {
super();
this.subs.add = new AddCommand(main, VoiceChannelMode.BLACKLIST);
this.subs.remove = new RemoveCommand(main, VoiceChannelMode.BLACKLIST);
this.subs.list = new ListCommand(main, VoiceChannelMode.BLACKLIST);
for (const sub of Object.values(this.subs))
this.data.addSubcommand(sub.data);
}
async handleCommand(interaction: CommandInteraction): Promise<boolean> {
if (!(interaction instanceof ChatInputCommandInteraction)) {
interaction.reply({ content: "This command can only be used in a server!", ephemeral: true });
return false;
}
const command = this.subs[interaction.options.getSubcommand(true)];
if (!command) {
interaction.reply({ content: "Unknown sub-command.", ephemeral: true });
return false;
}
command.handleCommand(interaction);
return true;
}
}

View File

@@ -43,15 +43,18 @@ export class RenameCommand extends SubCommand {
}
dsVoice.getVoiceData(interaction.user.id, interaction.guildId).then((userData) => {
if (!userData.id) {
interaction.reply({ content: "You are not in a voice channel!", ephemeral: true });
if (!interaction.guildId) {
interaction.reply({ content: "This command can only be used in a server!", ephemeral: true });
return false;
}
if (!userData) {
interaction.reply({ content: "You do not have a voice channel!", ephemeral: true });
return false;
}
userData.channelName = name;
interaction.reply({ content: `Renamed channel to ${name}`, ephemeral: true });
interaction.reply({ content: `Renamed channel to \`${name}\`.` });
if (interaction.guildId)
dsVoice.updateUserChannel(userData, interaction.guildId);

View File

@@ -13,7 +13,7 @@ export class ResizeCommand extends SubCommand {
.setName("size")
.setDescription("The new size of the voice channel")
.setMaxValue(100)
.setMinValue(1)
.setMinValue(0)
.setRequired(true));
main: DSBot;
@@ -50,8 +50,8 @@ export class ResizeCommand extends SubCommand {
}
const userData = await dsVoice.getVoiceData(interaction.user.id, interaction.guildId);
if (!userData.id) {
interaction.reply({ content: "You are not in a voice channel!", ephemeral: true });
if (!userData) {
interaction.reply({ content: "You do not have a voice channel!", ephemeral: true });
return false;
}
@@ -65,7 +65,7 @@ export class ResizeCommand extends SubCommand {
// userData.channelName = name;
userData.size = size;
interaction.reply({ content: `Resized channel to ${size}`, ephemeral: true });
interaction.reply({ content: `Resized channel to ${size} slots.`, ephemeral: true });
dsVoice.updateUserChannel(userData, interaction.guildId);
return true;

View File

@@ -1,5 +1,7 @@
import { SlashCommandSubcommandBuilder, CommandInteraction, CacheType, SlashCommandStringOption } from "discord.js";
import { CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
import { DSBot } from "../../../api/DSBot";
import { SubCommand } from "../../../api/data/Command";
import { DSVoice, VoiceChannelMode } from "../../DSVoice";
export class SetModeCommand extends SubCommand {
name = "setmode";
@@ -11,13 +13,42 @@ export class SetModeCommand extends SubCommand {
.setDescription("The mode to set the voice channel to")
.setRequired(true)
.setChoices(
{ name: "Whitelist", value: "whitelist" },
{ name: "Blacklist", value: "blacklist" },
{ name: "Public", value: "public" },
{ name: "Private", value: "private" }
{ name: "Whitelist - Only Listed Allowed", value: VoiceChannelMode.WHITELIST },
{ name: "Blacklist - Everyone Allowed but Listed", value: VoiceChannelMode.BLACKLIST },
{ name: "Public - Everyone Allowed", value: VoiceChannelMode.PUBLIC },
{ name: "Locked - No More Allowed", value: VoiceChannelMode.LOCKED }
)
);
handleCommand(interaction: CommandInteraction<CacheType>): Promise<boolean> {
throw new Error("Method not implemented.");
main: DSBot;
constructor(main: DSBot) {
super();
this.main = main;
}
}
async handleCommand(interaction: CommandInteraction): Promise<boolean> {
if (!interaction.guildId) {
interaction.reply({ content: "This command can only be used in a server!", ephemeral: true });
return false;
}
const dsVoice = this.main.modules.getModule<DSVoice>(DSVoice);
if (!dsVoice) {
interaction.reply({ content: "DSVoice is not enabled in this server!", ephemeral: true });
return false;
}
const data = await dsVoice.getVoiceData(interaction.user.id, interaction.guildId);
if (!data) {
interaction.reply({ content: "You do not have a voice channel.", ephemeral: true });
return false;
}
const mode = interaction.options.get("mode", true).value as string;
data.mode = mode as VoiceChannelMode;
interaction.reply({ content: `Set mode to ${mode}.`, ephemeral: true });
dsVoice.updateUserChannel(data, interaction.guildId);
return true;
}
}

View File

@@ -4,6 +4,7 @@ import { SubGroupCommand } from "../../../api/data/Command";
import { AddCommand } from "./list/AddCommand";
import { ListCommand } from "./list/ListCommand";
import { RemoveCommand } from "./list/RemoveCommand";
import { VoiceChannelMode } from "../../DSVoice";
export class WhitelistCommand extends SubGroupCommand {
name = "whitelist";
@@ -14,9 +15,9 @@ export class WhitelistCommand extends SubGroupCommand {
constructor(main: DSBot) {
super();
this.subs.add = new AddCommand(main, ListType.WHITE);
this.subs.remove = new RemoveCommand(main, ListType.WHITE);
this.subs.list = new ListCommand(main, ListType.WHITE);
this.subs.add = new AddCommand(main, VoiceChannelMode.WHITELIST);
this.subs.remove = new RemoveCommand(main, VoiceChannelMode.WHITELIST);
this.subs.list = new ListCommand(main, VoiceChannelMode.WHITELIST);
for (const sub of Object.values(this.subs))
this.data.addSubcommand(sub.data);
@@ -40,7 +41,3 @@ export class WhitelistCommand extends SubGroupCommand {
return true;
}
}
export enum ListType {
WHITE = "whitelist", BLACK = "blacklist"
}

View File

@@ -1,8 +1,7 @@
import { CacheType, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
import { DSBot } from "../../../../api/DSBot";
import { SubCommand } from "../../../../api/data/Command";
import { DSVoice } from "../../../DSVoice";
import { ListType } from "../WhitelistCommand";
import { DSVoice, VoiceChannelMode } from "../../../DSVoice";
export class AddCommand extends SubCommand {
name = "add";
@@ -15,9 +14,9 @@ export class AddCommand extends SubCommand {
.setRequired(true));
main: DSBot;
type: ListType;
type: VoiceChannelMode;
constructor(main: DSBot, type: ListType) {
constructor(main: DSBot, type: VoiceChannelMode) {
super();
this.main = main;
this.type = type;
@@ -50,10 +49,10 @@ export class AddCommand extends SubCommand {
return false;
}
const list = this.type === ListType.WHITE ? config.whitelist : config.blacklist;
const list = this.type === VoiceChannelMode.WHITELIST ? config.whitelist : config.blacklist;
if (list.indexOf(user.id) !== -1) {
interaction.reply({ content: `${user.toString()} is already in the ${this.type}.`, ephemeral: true });
interaction.reply({ content: `${user.toString()} has already been ${this.type}.`, ephemeral: true });
return false;
}
@@ -61,7 +60,7 @@ export class AddCommand extends SubCommand {
list.push(user.id);
voiceModule.updateUserChannel(config, interaction.guildId);
interaction.reply({ content: `Added ${user.toString()} to the ${this.type}`, ephemeral: true });
interaction.reply({ content: `${user.toString()} has been ${this.type}`, ephemeral: true });
return true;
}

View File

@@ -1,8 +1,7 @@
import { CacheType, CommandInteraction, SlashCommandSubcommandBuilder, User } from "discord.js";
import { CacheType, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
import { DSBot } from "../../../../api/DSBot";
import { SubCommand } from "../../../../api/data/Command";
import { DSVoice } from "../../../DSVoice";
import { ListType } from "../WhitelistCommand";
import { DSVoice, VoiceChannelMode } from "../../../DSVoice";
export class ListCommand extends SubCommand {
name = "list";
@@ -11,9 +10,9 @@ export class ListCommand extends SubCommand {
.setDescription("Lists all users on the list");
main: DSBot;
type: ListType;
type: VoiceChannelMode;
constructor(main: DSBot, type: ListType) {
constructor(main: DSBot, type: VoiceChannelMode) {
super();
this.main = main;
this.type = type;
@@ -41,10 +40,16 @@ export class ListCommand extends SubCommand {
return false;
}
const list = this.type === ListType.WHITE ? config.whitelist : config.blacklist;
const list = this.type === VoiceChannelMode.WHITELIST ? config.whitelist : config.blacklist;
if (list.length === 0) {
interaction.reply({ content: `No users have been ${this.type}.`, ephemeral: true });
return false;
}
const names = list.flatMap(user => `<@${user}>`);
interaction.reply(`Users on the ${this.type}: ${names.join(", ")}`);
interaction.reply({ content: `Users that are ${this.type}: ${names.join(", ")}`, ephemeral: true });
return true;
}

View File

@@ -1,8 +1,7 @@
import { CacheType, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
import { DSBot } from "../../../../api/DSBot";
import { SubCommand } from "../../../../api/data/Command";
import { DSVoice } from "../../../DSVoice";
import { ListType } from "../WhitelistCommand";
import { DSVoice, VoiceChannelMode } from "../../../DSVoice";
export class RemoveCommand extends SubCommand {
name = "remove";
@@ -15,9 +14,9 @@ export class RemoveCommand extends SubCommand {
.setRequired(true));
main: DSBot;
type: ListType;
type: VoiceChannelMode;
constructor(main: DSBot, type: ListType) {
constructor(main: DSBot, type: VoiceChannelMode) {
super();
this.main = main;
this.type = type;
@@ -50,10 +49,10 @@ export class RemoveCommand extends SubCommand {
return false;
}
const list = this.type === ListType.WHITE ? config.whitelist : config.blacklist;
const list = this.type === VoiceChannelMode.WHITELIST ? config.whitelist : config.blacklist;
if (list.indexOf(user.id) === -1) {
interaction.reply({ content: `${user.toString()} is not in the ${this.type}`, ephemeral: true });
interaction.reply({ content: `${user.toString()} is not ${this.type}.`, ephemeral: true });
return false;
}
@@ -61,7 +60,7 @@ export class RemoveCommand extends SubCommand {
list.splice(list.indexOf(user.id), 1);
voiceModule.updateUserChannel(config, interaction.guildId);
interaction.reply({ content: `Removed ${user.toString()} from the ${this.type}`, ephemeral: true });
interaction.reply({ content: `${user.toString()} has been ${this.type}.`, ephemeral: true });
return true;
}