aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/commands/moderation/massBan.ts34
-rw-r--r--src/lib/common/util/Moderation.ts101
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts17
-rw-r--r--src/lib/extensions/discord.js/BushClientEvents.ts7
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts85
-rw-r--r--src/listeners/member-custom/massBan.ts38
6 files changed, 234 insertions, 48 deletions
diff --git a/src/commands/moderation/massBan.ts b/src/commands/moderation/massBan.ts
index e4aeb55..499bcd4 100644
--- a/src/commands/moderation/massBan.ts
+++ b/src/commands/moderation/massBan.ts
@@ -1,6 +1,14 @@
-import { BushCommand, type ArgType, type BushMessage, type BushSlashMessage, type OptionalArgType } from '#lib';
+import {
+ BanResponse,
+ banResponse,
+ BushCommand,
+ type ArgType,
+ type BushMessage,
+ type BushSlashMessage,
+ type OptionalArgType
+} from '#lib';
import assert from 'assert';
-import { ApplicationCommandOptionType, Embed, PermissionFlagsBits } from 'discord.js';
+import { ApplicationCommandOptionType, Collection, Embed, PermissionFlagsBits } from 'discord.js';
export default class MassBanCommand extends BushCommand {
public constructor() {
@@ -8,8 +16,8 @@ export default class MassBanCommand extends BushCommand {
aliases: ['mass-ban', 'mass-dban'],
category: 'moderation',
description: 'Ban multiple users at once.',
- usage: ['template <...users> [--reason "<reason>"] [--days <days>]'],
- examples: ['template 1 2'],
+ usage: ['mass-ban <...users> [--reason "<reason>"] [--days <days>]'],
+ examples: ['mass-ban 311294982898057217 792202575851814942 792199864510447666 792201010118131713 --reason "too many alts"'],
args: [
{
id: 'users',
@@ -71,21 +79,29 @@ export default class MassBanCommand extends BushCommand {
}
const promises = ids.map((id) =>
- message.guild.bushBan({
+ message.guild.massBanOne({
user: id,
- reason: `[MassBan] ${args.reason ? args.reason.trim() : 'No reason provided.'}`,
moderator: message.author.id,
+ reason: `[MassBan] ${args.reason ? args.reason.trim() : 'No reason provided.'}`,
deleteDays: args.days ?? 0
})
);
- const res = await Promise.allSettled(promises);
+ const res = await Promise.all(promises);
+
+ const map = new Collection(res.map((r, i) => [ids[i], r]));
+ client.emit('massBan', message.member!, message.guild!, args.reason ? args.reason.trim() : 'No reason provided.', map);
+
+ const success = (res: BanResponse): boolean => [banResponse.SUCCESS, banResponse.DM_ERROR].includes(res as any);
const embed = new Embed()
.setTitle(`Mass Ban Results`)
.setDescription(
- res.map((r, i) => `${r.status === 'rejected' ? util.emojis.error : util.emojis.success} ${ids[i]}`).join('')
- );
+ res
+ .map((r, i) => `${success(r) ? util.emojis.success : util.emojis.error} ${ids[i]}${success(r) ? '' : ` - ${r}`}`)
+ .join('\n')
+ )
+ .setColor(util.colors.DarkRed);
return message.util.send({ embeds: [embed] });
}
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts
index 04ccb31..365dbd5 100644
--- a/src/lib/common/util/Moderation.ts
+++ b/src/lib/common/util/Moderation.ts
@@ -123,25 +123,41 @@ export class Moderation {
const user = (await util.resolveNonCachedUser(options.user))!.id;
const moderator = (await util.resolveNonCachedUser(options.moderator))!.id;
const guild = client.guilds.resolveId(options.guild)!;
- const duration = options.duration ? options.duration : undefined;
+ return this.createModLogEntrySimple(
+ {
+ ...options,
+ user: user,
+ moderator: moderator,
+ guild: guild
+ },
+ getCaseNumber
+ );
+ }
+
+ /**
+ * Creates a modlog entry with already resolved ids.
+ * @param options Options for creating a modlog entry.
+ * @param getCaseNumber Whether or not to get the case number of the entry.
+ * @returns An object with the modlog and the case number.
+ */
+ public static async createModLogEntrySimple(
+ options: SimpleCreateModLogEntryOptions,
+ getCaseNumber = false
+ ): Promise<{ log: ModLog | null; caseNum: number | null }> {
// If guild does not exist create it so the modlog can reference a guild.
await Guild.findOrCreate({
- where: {
- id: guild
- },
- defaults: {
- id: guild
- }
+ where: { id: options.guild },
+ defaults: { id: options.guild }
});
const modLogEntry = ModLog.build({
type: options.type,
- user,
- moderator,
+ user: options.user,
+ moderator: options.moderator,
reason: options.reason,
- duration: duration,
- guild,
+ duration: options.duration ? options.duration : undefined,
+ guild: options.guild,
pseudo: options.pseudo ?? false,
evidence: options.evidence,
hidden: options.hidden ?? false
@@ -153,7 +169,9 @@ export class Moderation {
if (!getCaseNumber) return { log: saveResult, caseNum: null };
- const caseNum = (await ModLog.findAll({ where: { type: options.type, user: user, guild: guild, hidden: 'false' } }))?.length;
+ const caseNum = (
+ await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild, hidden: false } })
+ )?.length;
return { log: saveResult, caseNum };
}
@@ -269,7 +287,6 @@ export class Moderation {
if (appealsEnabled && options.modlog)
components = [
new ActionRow({
- type: ComponentType.ActionRow,
components: [
new ButtonComponent({
custom_id: `appeal;${this.punishmentToPresentTense(options.punishment)};${
@@ -294,54 +311,76 @@ export class Moderation {
}
}
-/**
- * Options for creating a modlog entry.
- */
-export interface CreateModLogEntryOptions {
+interface BaseCreateModLogEntryOptions {
/**
* The type of modlog entry.
*/
type: ModLogType;
/**
- * The user that a modlog entry is created for.
+ * The reason for the punishment.
*/
- user: BushGuildMemberResolvable;
+ reason: string | undefined | null;
/**
- * The moderator that created the modlog entry.
+ * The duration of the punishment.
*/
- moderator: BushGuildMemberResolvable;
+ duration?: number;
/**
- * The reason for the punishment.
+ * Whether the punishment is a pseudo punishment.
*/
- reason: string | undefined | null;
+ pseudo?: boolean;
/**
- * The duration of the punishment.
+ * The evidence for the punishment.
*/
- duration?: number;
+ evidence?: string;
+
+ /**
+ * Makes the modlog entry hidden.
+ */
+ hidden?: boolean;
+}
+
+/**
+ * Options for creating a modlog entry.
+ */
+export interface CreateModLogEntryOptions extends BaseCreateModLogEntryOptions {
+ /**
+ * The user that a modlog entry is created for.
+ */
+ user: BushGuildMemberResolvable;
+
+ /**
+ * The moderator that created the modlog entry.
+ */
+ moderator: BushGuildMemberResolvable;
/**
* The guild that the punishment is created for.
*/
guild: BushGuildResolvable;
+}
+/**
+ * Simple options for creating a modlog entry.
+ */
+export interface SimpleCreateModLogEntryOptions extends BaseCreateModLogEntryOptions {
/**
- * Whether the punishment is a pseudo punishment.
+ * The user that a modlog entry is created for.
*/
- pseudo?: boolean;
+ user: Snowflake;
/**
- * The evidence for the punishment.
+ * The moderator that created the modlog entry.
*/
- evidence?: string;
+ moderator: Snowflake;
/**
- * Makes the modlog entry hidden.
+ * The guild that the punishment is created for.
*/
- hidden?: boolean;
+ guild: Snowflake;
}
/**
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index ecfa360..9903140 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -678,16 +678,17 @@ export class BushClientUtil extends ClientUtil {
*/
public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<BushUser | undefined> {
if (user == null) return undefined;
- const id =
- user instanceof User || user instanceof GuildMember || user instanceof ThreadMember
- ? user.id
+ const resolvedUser =
+ user instanceof User
+ ? <BushUser>user
+ : user instanceof GuildMember
+ ? <BushUser>user.user
+ : user instanceof ThreadMember
+ ? <BushUser>user.user
: user instanceof Message
- ? user.author.id
- : typeof user === 'string'
- ? user
+ ? <BushUser>user.author
: undefined;
- if (!id) return undefined;
- else return await client.users.fetch(id).catch(() => undefined);
+ return resolvedUser ?? (await client.users.fetch(user as Snowflake).catch(() => undefined));
}
/**
diff --git a/src/lib/extensions/discord.js/BushClientEvents.ts b/src/lib/extensions/discord.js/BushClientEvents.ts
index 50b198d..e6cf93f 100644
--- a/src/lib/extensions/discord.js/BushClientEvents.ts
+++ b/src/lib/extensions/discord.js/BushClientEvents.ts
@@ -1,4 +1,5 @@
import type {
+ BanResponse,
BushApplicationCommand,
BushClient,
BushDMChannel,
@@ -264,6 +265,12 @@ export interface BushClientEvents extends AkairoClientEvents {
channelsSuccessMap: Collection<Snowflake, boolean>,
all?: boolean
];
+ massBan: [
+ moderator: BushGuildMember,
+ guild: BushGuild,
+ reason: string | undefined,
+ results: Collection<Snowflake, BanResponse>
+ ];
}
type Setting = GuildSettings | 'enabledFeatures' | 'blacklistedChannels' | 'blacklistedUsers' | 'disabledCommands';
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 80799fd..155f32c 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -236,6 +236,69 @@ export class BushGuild extends Guild {
}
/**
+ * {@link bushBan} with less resolving and checks
+ * @param options Options for banning the user.
+ * @returns A string status message of the ban.
+ * **Preconditions:**
+ * - {@link me} has the `BanMembers` permission
+ * **Warning:**
+ * - Doesn't emit bushBan Event
+ */
+ public async massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse> {
+ if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED;
+
+ const ret = await (async () => {
+ // add modlog entry
+ const { log: modlog } = await Moderation.createModLogEntrySimple({
+ type: ModLogType.PERM_BAN,
+ user: options.user,
+ moderator: options.moderator,
+ reason: options.reason,
+ duration: 0,
+ guild: this.id
+ });
+ if (!modlog) return banResponse.MODLOG_ERROR;
+
+ let dmSuccessEvent: boolean | undefined = undefined;
+ // dm user
+ if (this.members.cache.has(options.user)) {
+ dmSuccessEvent = await Moderation.punishDM({
+ modlog: modlog.id,
+ guild: this,
+ user: options.user,
+ punishment: 'banned',
+ duration: 0,
+ reason: options.reason ?? undefined,
+ sendFooter: true
+ });
+ }
+
+ // ban
+ const banSuccess = await this.bans
+ .create(options.user, {
+ reason: `${options.moderator} | ${options.reason}`,
+ deleteMessageDays: options.deleteDays
+ })
+ .catch(() => false);
+ if (!banSuccess) return banResponse.ACTION_ERROR;
+
+ // add punishment entry so they can be unbanned later
+ const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ type: 'ban',
+ user: options.user,
+ guild: this,
+ duration: 0,
+ modlog: modlog.id
+ });
+ if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
+
+ if (!dmSuccessEvent) return banResponse.DM_ERROR;
+ return banResponse.SUCCESS;
+ })();
+ return ret;
+ }
+
+ /**
* Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry.
* @param options Options for unbanning the user.
* @returns A status message of the unban.
@@ -414,6 +477,28 @@ export interface GuildBushUnbanOptions {
evidence?: string;
}
+export interface GuildMassBanOneOptions {
+ /**
+ * The user to ban
+ */
+ user: Snowflake;
+
+ /**
+ * The reason to ban the user
+ */
+ reason: string;
+
+ /**
+ * The moderator who banned the user
+ */
+ moderator: Snowflake;
+
+ /**
+ * The number of days to delete the user's messages for
+ */
+ deleteDays?: number;
+}
+
/**
* Options for banning a user
*/
diff --git a/src/listeners/member-custom/massBan.ts b/src/listeners/member-custom/massBan.ts
new file mode 100644
index 0000000..93089a3
--- /dev/null
+++ b/src/listeners/member-custom/massBan.ts
@@ -0,0 +1,38 @@
+import { BanResponse, banResponse, BushListener, type BushClientEvents } from '#lib';
+import { Embed } from 'discord.js';
+
+export default class BushBanListener extends BushListener {
+ public constructor() {
+ super('massBan', {
+ emitter: 'client',
+ event: 'massBan',
+ category: 'member-custom'
+ });
+ }
+
+ public override async exec(...[moderator, guild, reason, results]: BushClientEvents['massBan']) {
+ const logChannel = await guild.getLogChannel('moderation');
+ if (!logChannel) return;
+
+ const success = (res: BanResponse): boolean => [banResponse.SUCCESS, banResponse.DM_ERROR].includes(res as any);
+
+ const logEmbed = new Embed()
+ .setColor(util.colors.DarkRed)
+ .setTimestamp()
+ .setTitle('Mass Ban')
+ .addFields(
+ { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` },
+ { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }
+ )
+ .setDescription(
+ results
+ .map(
+ (reason, user) =>
+ `${success(reason) ? util.emojis.success : util.emojis.error} ${user}${success(reason) ? '' : ` - ${reason}`}`
+ )
+ .join('\n')
+ );
+
+ return await logChannel.send({ embeds: [logEmbed] });
+ }
+}