aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/common/ConfirmationPrompt.ts90
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts111
-rw-r--r--src/lib/index.ts7
3 files changed, 203 insertions, 5 deletions
diff --git a/src/lib/common/ConfirmationPrompt.ts b/src/lib/common/ConfirmationPrompt.ts
new file mode 100644
index 0000000..9e790c7
--- /dev/null
+++ b/src/lib/common/ConfirmationPrompt.ts
@@ -0,0 +1,90 @@
+import { type BushMessage, type BushSlashMessage } from '#lib';
+import { MessageActionRow, MessageButton, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
+import { MessageButtonStyles } from 'discord.js/typings/enums';
+
+/**
+ * Sends a message with buttons for the user to confirm or cancel the action.
+ */
+export class ConfirmationPrompt {
+ /**
+ * Options for sending the message
+ */
+ protected messageOptions: MessageOptions;
+
+ /**
+ * The message that triggered the command
+ */
+ protected message: BushMessage | BushSlashMessage;
+
+ /**
+ * @param message The message to respond to
+ * @param options The send message options
+ */
+ protected constructor(message: BushMessage | BushSlashMessage, messageOptions: MessageOptions) {
+ this.message = message;
+ this.messageOptions = messageOptions;
+ }
+
+ /**
+ * Sends a message with buttons for the user to confirm or cancel the action.
+ */
+ protected async send(): Promise<boolean> {
+ this.messageOptions.components = [
+ new MessageActionRow().addComponents(
+ new MessageButton({
+ style: MessageButtonStyles.SUCCESS,
+ customId: 'confirmationPrompt__confirm',
+ emoji: util.emojis.successFull,
+ label: 'Yes'
+ }),
+ new MessageButton({
+ style: MessageButtonStyles.DANGER,
+ customId: 'confirmationPrompt__deny',
+ emoji: util.emojis.errorFull,
+ label: 'No'
+ })
+ )
+ ];
+
+ const msg = (await this.message.util.reply(this.messageOptions)) as BushMessage;
+
+ return await new Promise<boolean>((resolve) => {
+ let responded = false;
+ const collector = msg.createMessageComponentCollector({
+ filter: (interaction) =>
+ ['confirmationPrompt__confirm', 'confirmationPrompt__deny'].includes(interaction.customId) &&
+ interaction.message?.id == msg.id,
+ time: 300000
+ });
+
+ collector.on('collect', async (interaction: MessageComponentInteraction) => {
+ await interaction.deferUpdate().catch(() => undefined);
+ if (interaction.user.id == this.message.author.id || client.config.owners.includes(interaction.user.id)) {
+ if (interaction.id === 'confirmationPrompt__confirm') {
+ resolve(true);
+ responded = true;
+ collector.stop();
+ } else if (interaction.id === 'confirmationPrompt__deny') {
+ resolve(false);
+ responded = true;
+ collector.stop();
+ }
+ }
+ });
+
+ collector.on('end', async () => {
+ await msg.delete().catch(() => undefined);
+ if (!responded) resolve(false);
+ });
+ });
+ }
+
+ /**
+ * Sends a message with buttons for the user to confirm or cancel the action.
+ * @param message The message to respond to
+ * @param options The send message options
+ */
+ public static async send(message: BushMessage | BushSlashMessage, sendOptions: MessageOptions): Promise<boolean> {
+ return new ConfirmationPrompt(message, sendOptions).send();
+ }
+}
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index e12053e..299fdbc 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -2,29 +2,41 @@ import type {
BushClient,
BushGuildMember,
BushGuildMemberManager,
+ BushGuildMemberResolvable,
+ BushNewsChannel,
BushTextChannel,
+ BushThreadChannel,
BushUser,
BushUserResolvable,
GuildFeatures,
GuildLogType,
GuildModel
} from '#lib';
-import { Guild, MessagePayload, type MessageOptions, type UserResolvable } from 'discord.js';
+import {
+ Collection,
+ Guild,
+ GuildChannelManager,
+ Snowflake,
+ type MessageOptions,
+ type MessagePayload,
+ type UserResolvable
+} from 'discord.js';
import type { RawGuildData } from 'discord.js/typings/rawDataTypes';
import _ from 'lodash';
-import { Moderation } from '../../common/Moderation.js';
+import { Moderation } from '../../common/util/Moderation.js';
import { Guild as GuildDB } from '../../models/Guild.js';
import { ModLogType } from '../../models/ModLog.js';
/**
* Represents a guild (or a server) on Discord.
* <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can
- * check this with {@link Guild.available}.</info>
+ * check this with {@link BushGuild.available}.</info>
*/
export class BushGuild extends Guild {
public declare readonly client: BushClient;
public declare readonly me: BushGuildMember | null;
public declare members: BushGuildMemberManager;
+ public declare channels: GuildChannelManager;
public constructor(client: BushClient, data: RawGuildData) {
super(client, data);
@@ -261,6 +273,58 @@ export class BushGuild extends Guild {
void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>'));
void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] });
}
+
+ /**
+ * Denies send permissions in specified channels
+ * @param options The options for locking down the guild
+ */
+ public async lockdown(options: LockdownOptions): Promise<LockdownResponse> {
+ if (!options.all && !options.channel) return 'all not chosen and no channel specified';
+ const channelIds = options.all ? await this.getSetting('lockdownChannels') : [options.channel!.id];
+
+ if (!channelIds.length) return 'no channels configured';
+ const mappedChannels = channelIds.map((id) => this.channels.cache.get(id));
+
+ const invalidChannels = mappedChannels.filter((c) => c === undefined);
+ if (invalidChannels.length) return `invalid channel configured: ${invalidChannels.join(', ')}`;
+
+ const moderator = this.members.resolve(options.moderator);
+ if (!moderator) return 'moderator not found';
+ const errors = new Collection<Snowflake, Error>();
+ let successCount = 0;
+
+ for (const _channel of mappedChannels) {
+ const channel = _channel!;
+ if (!channel.permissionsFor(this.me!.id)?.has(['MANAGE_CHANNELS'])) {
+ errors.set(channel.id, new Error('client no permission'));
+ continue;
+ } else if (!channel.permissionsFor(options.moderator)?.has(['MANAGE_CHANNELS'])) {
+ errors.set(channel.id, new Error('moderator no permission'));
+ continue;
+ }
+
+ const reason = `${options.unlock ? 'Unlocking' : 'Locking Down'} Channel | ${moderator.user.tag} | ${
+ options.reason ?? 'No reason provided'
+ }`;
+
+ if (channel.isThread()) {
+ const lockdownSuccess = await channel.parent?.permissionOverwrites
+ .edit(this.id, { SEND_MESSAGES_IN_THREADS: options.unlock ? null : false }, { reason })
+ .catch((e) => e);
+ if (lockdownSuccess instanceof Error) errors.set(channel.id, lockdownSuccess);
+ else successCount++;
+ } else {
+ const lockdownSuccess = await channel.permissionOverwrites
+ .edit(this.id, { SEND_MESSAGES: options.unlock ? null : false }, { reason })
+ .catch((e) => e);
+ if (lockdownSuccess instanceof Error) errors.set(channel.id, lockdownSuccess);
+ else successCount++;
+ }
+ }
+
+ if (errors.size) return errors;
+ else return `success: ${successCount}`;
+ }
}
/**
@@ -329,3 +393,44 @@ type BanResponse = PunishmentResponse | 'error banning' | 'error creating ban en
* Response returned when unbanning a user
*/
type UnbanResponse = PunishmentResponse | 'user not banned' | 'error unbanning' | 'error removing ban entry';
+
+/**
+ * Options for locking down channel(s)
+ */
+interface LockdownOptions {
+ /**
+ * The moderator responsible for the lockdown
+ */
+ moderator: BushGuildMemberResolvable;
+
+ /**
+ * Whether to lock down all (specified) channels
+ */
+ all: boolean;
+
+ /**
+ * Reason for the lockdown
+ */
+ reason?: string;
+
+ /**
+ * A specific channel to lockdown
+ */
+ channel?: BushThreadChannel | BushNewsChannel | BushTextChannel;
+
+ /**
+ * Whether or not to unlock the channel(s) instead of locking them
+ */
+ unlock?: boolean;
+}
+
+/**
+ * Response returned when locking down a channel
+ */
+type LockdownResponse =
+ | `success: ${number}`
+ | 'all not chosen and no channel specified'
+ | 'no channels configured'
+ | `invalid channel configured: ${string}`
+ | 'moderator not found'
+ | Collection<string, Error>;
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 94a7dd9..8809e27 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -1,11 +1,12 @@
export * from './common/AutoMod.js';
export * from './common/ButtonPaginator.js';
+export * from './common/ConfirmationPrompt.js';
export * from './common/DeleteButton.js';
-export * from './common/Format.js';
-export * from './common/Moderation.js';
export type { BushInspectOptions } from './common/typings/BushInspectOptions.js';
export type { CodeBlockLang } from './common/typings/CodeBlockLang.js';
export * from './common/util/Arg.js';
+export * from './common/util/Format.js';
+export * from './common/util/Moderation.js';
export * from './extensions/discord-akairo/BushArgumentTypeCaster.js';
export * from './extensions/discord-akairo/BushClient.js';
export * from './extensions/discord-akairo/BushClientUtil.js';
@@ -38,6 +39,7 @@ export * from './extensions/discord.js/BushGuild.js';
export type { BushGuildApplicationCommandManager } from './extensions/discord.js/BushGuildApplicationCommandManager.js';
export type { BushGuildBan } from './extensions/discord.js/BushGuildBan.js';
export * from './extensions/discord.js/BushGuildChannel.js';
+export type { BushGuildChannelManager } from './extensions/discord.js/BushGuildChannelManager.js';
export * from './extensions/discord.js/BushGuildEmoji.js';
export type { BushGuildEmojiRoleManager } from './extensions/discord.js/BushGuildEmojiRoleManager.js';
export type { BushGuildManager } from './extensions/discord.js/BushGuildManager.js';
@@ -63,6 +65,7 @@ export * from './extensions/discord.js/BushUser.js';
export type { BushUserManager } from './extensions/discord.js/BushUserManager.js';
export * from './extensions/discord.js/BushVoiceChannel.js';
export * from './extensions/discord.js/BushVoiceState.js';
+export * from './extensions/discord.js/other.js';
export * from './models/ActivePunishment.js';
export * from './models/BaseModel.js';
export * from './models/Global.js';