aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/config/muteRole.ts2
-rw-r--r--src/commands/config/prefix.ts2
-rw-r--r--src/commands/config/welcomeChannel.ts2
-rw-r--r--src/commands/dev/reload.ts2
-rw-r--r--src/commands/dev/setLevel.ts4
-rw-r--r--src/commands/dev/testDuration.ts2
-rw-r--r--src/commands/info/botInfo.ts38
-rw-r--r--src/commands/info/help.ts2
-rw-r--r--src/commands/info/ping.ts8
-rw-r--r--src/commands/info/pronouns.ts2
-rw-r--r--src/commands/moderation/_unmute.ts0
-rw-r--r--src/commands/moderation/ban.ts204
-rw-r--r--src/commands/moderation/kick.ts29
-rw-r--r--src/commands/moderation/mute.ts6
-rw-r--r--src/commands/moderation/role.ts12
-rw-r--r--src/commands/moderation/unban.ts85
-rw-r--r--src/commands/moderation/unmute.ts109
-rw-r--r--src/commands/moderation/warn.ts6
-rw-r--r--src/commands/moulberry-bush/level.ts2
-rw-r--r--src/commands/utilities/whoHasRole.ts (renamed from src/commands/moderation/_unban.ts)0
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts63
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts64
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts149
23 files changed, 587 insertions, 206 deletions
diff --git a/src/commands/config/muteRole.ts b/src/commands/config/muteRole.ts
index 6fda0b8..9a172be 100644
--- a/src/commands/config/muteRole.ts
+++ b/src/commands/config/muteRole.ts
@@ -27,9 +27,9 @@ export default class MuteRoleCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'ROLE',
name: 'role',
description: "What would you like to set the server's mute role to?",
+ type: 'ROLE',
required: true
}
]
diff --git a/src/commands/config/prefix.ts b/src/commands/config/prefix.ts
index c7a1a9f..dc51671 100644
--- a/src/commands/config/prefix.ts
+++ b/src/commands/config/prefix.ts
@@ -26,9 +26,9 @@ export default class PrefixCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'STRING',
name: 'prefix',
description: 'What would you like the new prefix to be?',
+ type: 'STRING',
required: false
}
]
diff --git a/src/commands/config/welcomeChannel.ts b/src/commands/config/welcomeChannel.ts
index f15e07d..71d59f8 100644
--- a/src/commands/config/welcomeChannel.ts
+++ b/src/commands/config/welcomeChannel.ts
@@ -27,9 +27,9 @@ export default class WelcomeChannelCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'CHANNEL',
name: 'channel',
description: 'What channel would you like me to send welcome messages in?',
+ type: 'CHANNEL',
required: false
}
]
diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts
index c6a2140..928632c 100644
--- a/src/commands/dev/reload.ts
+++ b/src/commands/dev/reload.ts
@@ -21,9 +21,9 @@ export default class ReloadCommand extends BushCommand {
typing: true,
slashOptions: [
{
- type: 'BOOLEAN',
name: 'fast',
description: 'Whether to use esbuild for fast compiling or not',
+ type: 'BOOLEAN',
required: false
}
],
diff --git a/src/commands/dev/setLevel.ts b/src/commands/dev/setLevel.ts
index 9c7daeb..db0cfab 100644
--- a/src/commands/dev/setLevel.ts
+++ b/src/commands/dev/setLevel.ts
@@ -32,15 +32,15 @@ export default class SetLevelCommand extends BushCommand {
ownerOnly: true,
slashOptions: [
{
- type: 'USER',
name: 'user',
description: 'The user to change the level of',
+ type: 'USER',
required: true
},
{
- type: 'INTEGER',
name: 'level',
description: 'The level to set the user to',
+ type: 'INTEGER',
required: true
}
],
diff --git a/src/commands/dev/testDuration.ts b/src/commands/dev/testDuration.ts
index 719292e..2d636d2 100644
--- a/src/commands/dev/testDuration.ts
+++ b/src/commands/dev/testDuration.ts
@@ -27,9 +27,9 @@ export default class TestDurationCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'STRING',
name: 'reason',
description: 'Enter text and a duration here.',
+ type: 'STRING',
required: false
}
],
diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts
index d9961af..d5941b2 100644
--- a/src/commands/info/botInfo.ts
+++ b/src/commands/info/botInfo.ts
@@ -1,5 +1,5 @@
import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
-import { MessageEmbed } from 'discord.js';
+import { MessageEmbed, version as discordJSVersion } from 'discord.js';
export default class BotInfoCommand extends BushCommand {
public constructor() {
@@ -18,32 +18,24 @@ export default class BotInfoCommand extends BushCommand {
}
public async exec(message: BushMessage | BushSlashMessage): Promise<void> {
- const owners = (await this.client.util.mapIDs(this.client.ownerID)).map((u) => u.tag).join('\n');
+ const developers = (await this.client.util.mapIDs(this.client.config.owners)).map((u) => u?.tag).join('\n');
const currentCommit = (await this.client.util.shell('git rev-parse HEAD')).stdout.replace('\n', '');
const repoUrl = (await this.client.util.shell('git remote get-url origin')).stdout.replace('\n', '');
const embed = new MessageEmbed()
.setTitle('Bot Info:')
- .addFields([
- {
- name: 'Owners',
- value: owners,
- inline: true
- },
- {
- name: 'Uptime',
- value: this.client.util.capitalize(this.client.util.humanizeDuration(this.client.uptime))
- },
- {
- name: 'User count',
- value: this.client.users.cache.size.toLocaleString(),
- inline: true
- },
- {
- name: 'Current commit',
- value: `[${currentCommit.substring(0, 7)}](${repoUrl}/commit/${currentCommit})`
- }
- ])
- .setTimestamp();
+ .addField('**Uptime**', this.client.util.humanizeDuration(this.client.uptime), true)
+ .addField('**Servers**', this.client.guilds.cache.size.toLocaleString(), true)
+ .addField('**Users**', this.client.users.cache.size.toLocaleString(), true)
+ .addField('**Discord.js Version**', discordJSVersion, true)
+ .addField('**Node.js Version**', process.version.slice(1), true)
+ .addField('**Commands**', this.client.commandHandler.modules.size.toLocaleString(), true)
+ .addField('**Listeners**', this.client.listenerHandler.modules.size.toLocaleString(), true)
+ .addField('**Inhibitors**', this.client.inhibitorHandler.modules.size.toLocaleString(), true)
+ .addField('**Tasks**', this.client.taskHandler.modules.size.toLocaleString(), true)
+ .addField('**Current Commit**', `[${currentCommit.substring(0, 7)}](${repoUrl}/commit/${currentCommit})`, true)
+ .addField('**Developers**', developers, true)
+ .setTimestamp()
+ .setColor(this.client.util.colors.default);
await message.util.reply({ embeds: [embed] });
}
}
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index 439f0ef..e061453 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -33,9 +33,9 @@ export default class HelpCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'STRING',
name: 'command',
description: 'The command you would like to find information about.',
+ type: 'STRING',
required: false
}
]
diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts
index 4638aa9..59f475f 100644
--- a/src/commands/info/ping.ts
+++ b/src/commands/info/ping.ts
@@ -20,8 +20,8 @@ export default class PingCommand extends BushCommand {
public async exec(message: BushMessage): Promise<void> {
const sentMessage = (await message.util.send('Pong!')) as Message;
const timestamp: number = message.editedTimestamp ? message.editedTimestamp : message.createdTimestamp;
- const botLatency = `\`\`\`\n ${Math.floor(sentMessage.createdTimestamp - timestamp)}ms \`\`\``;
- const apiLatency = `\`\`\`\n ${Math.round(message.client.ws.ping)}ms \`\`\``;
+ const botLatency = `${'```'}\n ${Math.round(sentMessage.createdTimestamp - timestamp)}ms ${'```'}`;
+ const apiLatency = `${'```'}\n ${Math.round(message.client.ws.ping)}ms ${'```'}`;
const embed = new MessageEmbed()
.setTitle('Pong! 🏓')
.addField('Bot Latency', botLatency, true)
@@ -39,8 +39,8 @@ export default class PingCommand extends BushCommand {
const timestamp1 = message.interaction.createdTimestamp;
await message.interaction.reply('Pong!');
const timestamp2 = await message.interaction.fetchReply().then((m) => (m as Message).createdTimestamp);
- const botLatency = `\`\`\`\n ${Math.floor(timestamp2 - timestamp1)}ms \`\`\``;
- const apiLatency = `\`\`\`\n ${Math.round(this.client.ws.ping)}ms \`\`\``;
+ const botLatency = `${'```'}\n ${Math.round(timestamp2 - timestamp1)}ms ${'```'}`;
+ const apiLatency = `${'```'}\n ${Math.round(this.client.ws.ping)}ms ${'```'}`;
const embed = new MessageEmbed()
.setTitle('Pong! 🏓')
.addField('Bot Latency', botLatency, true)
diff --git a/src/commands/info/pronouns.ts b/src/commands/info/pronouns.ts
index 60701d1..0a5c0bc 100644
--- a/src/commands/info/pronouns.ts
+++ b/src/commands/info/pronouns.ts
@@ -51,9 +51,9 @@ export default class PronounsCommand extends BushCommand {
clientPermissions: ['SEND_MESSAGES'],
slashOptions: [
{
- type: 'USER',
name: 'user',
description: 'The user to get pronouns for',
+ type: 'USER',
required: false
}
],
diff --git a/src/commands/moderation/_unmute.ts b/src/commands/moderation/_unmute.ts
deleted file mode 100644
index e69de29..0000000
--- a/src/commands/moderation/_unmute.ts
+++ /dev/null
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index be7a51f..d713a15 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -1,4 +1,5 @@
-import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage } from '@lib';
+import { Argument } from 'discord-akairo';
import { User } from 'discord.js';
export default class BanCommand extends BushCommand {
@@ -6,6 +7,11 @@ export default class BanCommand extends BushCommand {
super('ban', {
aliases: ['ban'],
category: 'moderation',
+ description: {
+ content: 'Ban a member from the server.',
+ usage: 'ban <member> <reason> [--delete ]',
+ examples: ['ban 322862723090219008 1 day commands in #general --delete 7']
+ },
args: [
{
id: 'user',
@@ -20,137 +26,109 @@ export default class BanCommand extends BushCommand {
type: 'contentWithDuration',
match: 'restContent',
prompt: {
- start: 'Why would you like to ban this user?',
- retry: '{error} Choose a ban reason.',
+ start: 'Why should this user be banned and for how long?',
+ retry: '{error} Choose a valid ban reason and duration.',
optional: true
}
+ },
+ {
+ id: 'days',
+ flag: '--days',
+ match: 'option',
+ type: Argument.range('integer', 0, 7, true),
+ default: 0
}
],
- clientPermissions: ['BAN_MEMBERS'],
- userPermissions: ['BAN_MEMBERS'],
- description: {
- content: 'Ban a member from the server.',
- usage: 'ban <member> <reason> [--time]',
- examples: ['ban @user bad --time 69d']
- },
+ slash: true,
slashOptions: [
{
- type: 'USER',
name: 'user',
- description: 'Who would you like to ban?',
+ description: 'What user would you like to ban?',
+ type: 'USER',
required: true
},
{
- type: 'STRING',
name: 'reason',
- description: 'Why are they getting banned?',
+ description: 'Why should this user be banned and for how long?',
+ type: 'STRING',
required: false
+ },
+ {
+ name: 'days',
+ description: "How many days of the user's messages would you like to delete?",
+ type: 'INTEGER',
+ required: false,
+ choices: [
+ { name: '0', value: 0 },
+ { name: '1', value: 1 },
+ { name: '2', value: 2 },
+ { name: '3', value: 3 },
+ { name: '4', value: 4 },
+ { name: '5', value: 5 },
+ { name: '6', value: 6 },
+ { name: '7', value: 7 }
+ ]
}
],
- slash: true
+ channel: 'guild',
+ clientPermissions: ['BAN_MEMBERS'],
+ userPermissions: ['BAN_MEMBERS']
});
}
- // async *genResponses(
- // message: Message | CommandInteraction,
- // user: User,
- // reason?: string,
- // time?: number
- // ): AsyncIterable<string> {
- // const duration = moment.duration();
- // let modLogEntry: ModLog;
- // let banEntry: Ban;
- // // const translatedTime: string[] = [];
- // // Create guild entry so postgres doesn't get mad when I try and add a modlog entry
- // await Guild.findOrCreate({
- // where: {
- // id: message.guild.id
- // },
- // defaults: {
- // id: message.guild.id
- // }
- // });
- // try {
- // if (time) {
- // duration.add(time);
- // /* const parsed = [...time.matchAll(durationRegex)];
- // if (parsed.length < 1) {
- // yield `${this.client.util.emojis.error} Invalid time.`;
- // return;
- // }
- // for (const part of parsed) {
- // const translated = Object.keys(durationAliases).find((k) => durationAliases[k].includes(part[2]));
- // translatedTime.push(part[1] + ' ' + translated);
- // duration.add(Number(part[1]), translated as 'weeks' | 'days' | 'hours' | 'months' | 'minutes');
- // } */
- // modLogEntry = ModLog.build({
- // user: user.id,
- // guild: message.guild.id,
- // reason,
- // type: ModLogType.TEMP_BAN,
- // duration: duration.asMilliseconds(),
- // moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
- // });
- // banEntry = Ban.build({
- // user: user.id,
- // guild: message.guild.id,
- // reason,
- // expires: new Date(new Date().getTime() + duration.asMilliseconds()),
- // modlog: modLogEntry.id
- // });
- // } else {
- // modLogEntry = ModLog.build({
- // user: user.id,
- // guild: message.guild.id,
- // reason,
- // type: ModLogType.BAN,
- // moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
- // });
- // banEntry = Ban.build({
- // user: user.id,
- // guild: message.guild.id,
- // reason,
- // modlog: modLogEntry.id
- // });
- // }
- // await modLogEntry.save();
- // await banEntry.save();
-
- // try {
- // await user.send(
- // `You were banned in ${message.guild.name} ${duration ? duration.humanize() : 'permanently'} with reason \`${
- // reason || 'No reason given'
- // }\``
- // );
- // } catch {
- // yield `${this.client.util.emojis.warn} Unable to dm user`;
- // }
- // await message.guild.members.ban(user, {
- // reason: `Banned by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${
- // reason ? `reason ${reason}` : 'no reason'
- // }`
- // });
- // yield `${this.client.util.emojis.success} Banned <@!${user.id}> ${
- // duration ? duration.humanize() : 'permanently'
- // } with reason \`${reason || 'No reason given'}\``;
- // } catch {
- // yield `${this.client.util.emojis.error} Error banning :/`;
- // await banEntry.destroy();
- // await modLogEntry.destroy();
- // return;
- // }
- // }
async exec(
message: BushMessage | BushSlashMessage,
- { user, reason, time }: { user: User; reason?: string; time?: number | string }
+ { user, reason, days }: { user: User; reason?: { duration: number; contentWithoutTime: string }; days?: number }
): Promise<unknown> {
- return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
+ const member = message.guild.members.cache.get(user.id) as BushGuildMember;
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'ban');
+
+ if (canModerateResponse !== true) {
+ return message.util.reply(canModerateResponse);
+ }
+
+ if (!Number.isInteger(days) || days < 0 || days > 7) {
+ return message.util.reply(`${this.client.util.emojis.error} The delete days must be an integer between 0 and 7.`);
+ }
+
+ let time: number;
+ if (reason) {
+ time =
+ typeof reason === 'string'
+ ? await Argument.cast('duration', this.client.commandHandler.resolver, message as BushMessage, reason)
+ : reason.duration;
+ }
+ const parsedReason = reason.contentWithoutTime;
+
+ const response = await member.bushBan({
+ reason: parsedReason,
+ moderator: message.author,
+ duration: time,
+ deleteDays: days ?? 0
+ });
- // if (typeof time === 'string') {
- // time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
- // //// time = this.client.commandHandler.resolver.type('duration')
- // }
- // for await (const response of this.genResponses(message, user, reason, time)) {
- // await message.util.send(response);
- // }
+ switch (response) {
+ case 'missing permissions':
+ return message.util.reply(
+ `${this.client.util.emojis.error} Could not ban **${member.user.tag}** because I do not have permissions`
+ );
+ case 'error banning':
+ return message.util.reply(
+ `${this.client.util.emojis.error} An error occurred while trying to ban **${member.user.tag}**.`
+ );
+ case 'error creating ban entry':
+ return message.util.reply(
+ `${this.client.util.emojis.error} While banning **${member.user.tag}**, there was an error creating a ban entry, please report this to my developers.`
+ );
+ case 'error creating modlog entry':
+ return message.util.reply(
+ `${this.client.util.emojis.error} While banning **${member.user.tag}**, there was an error creating a modlog entry, please report this to my developers.`
+ );
+ case 'failed to dm':
+ return message.util.reply(
+ `${this.client.util.emojis.warn} Banned **${member.user.tag}** however I could not send them a dm.`
+ );
+ case 'success':
+ return message.util.reply(`${this.client.util.emojis.success} Successfully banned **${member.user.tag}**.`);
+ }
}
}
diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts
index f960488..d70bbfb 100644
--- a/src/commands/moderation/kick.ts
+++ b/src/commands/moderation/kick.ts
@@ -22,7 +22,7 @@ export default class KickCommand extends BushCommand {
{
id: 'reason',
type: 'string',
- match: 'restContent',
+ match: 'rest',
prompt: {
start: 'Why should this user be kicked?',
retry: '{error} Choose a valid kick reason.',
@@ -33,15 +33,15 @@ export default class KickCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'USER',
name: 'user',
description: 'What user would you like to kick?',
+ type: 'USER',
required: true
},
{
- type: 'STRING',
name: 'reason',
description: 'Why should this user be kicked?',
+ type: 'STRING',
required: false
}
],
@@ -55,7 +55,7 @@ export default class KickCommand extends BushCommand {
const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'kick');
// const victimBoldTag = `**${member.user.tag}**`;
- if (typeof canModerateResponse !== 'boolean') {
+ if (canModerateResponse !== true) {
return message.util.reply(canModerateResponse);
}
@@ -63,5 +63,26 @@ export default class KickCommand extends BushCommand {
reason,
moderator: message.author
});
+
+ switch (response) {
+ case 'missing permissions':
+ return message.util.reply(
+ `${this.client.util.emojis.error} Could not kick **${member.user.tag}** because I am missing the \`Kick Members\` permission.`
+ );
+ case 'error kicking':
+ return message.util.reply(
+ `${this.client.util.emojis.error} An error occurred while trying to kick **${member.user.tag}**.`
+ );
+ case 'error creating modlog entry':
+ return message.util.reply(
+ `${this.client.util.emojis.error} While muting **${member.user.tag}**, there was an error creating a modlog entry, please report this to my developers.`
+ );
+ case 'failed to dm':
+ return message.util.reply(
+ `${this.client.util.emojis.warn} Kicked **${member.user.tag}** however I could not send them a dm.`
+ );
+ case 'success':
+ return message.util.reply(`${this.client.util.emojis.success} Successfully kicked **${member.user.tag}**.`);
+ }
}
}
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
index 26ccb21..0eae547 100644
--- a/src/commands/moderation/mute.ts
+++ b/src/commands/moderation/mute.ts
@@ -34,15 +34,15 @@ export default class MuteCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'USER',
name: 'user',
description: 'What user would you like to mute?',
+ type: 'USER',
required: true
},
{
- type: 'STRING',
name: 'reason',
description: 'Why should this user be muted and for how long?',
+ type: 'STRING',
required: false
}
],
@@ -60,7 +60,7 @@ export default class MuteCommand extends BushCommand {
const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'mute');
const victimBoldTag = `**${member.user.tag}**`;
- if (typeof canModerateResponse !== 'boolean') {
+ if (canModerateResponse !== true) {
return message.util.reply(canModerateResponse);
}
diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts
index 33f474e..29913d5 100644
--- a/src/commands/moderation/role.ts
+++ b/src/commands/moderation/role.ts
@@ -4,7 +4,7 @@ import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushRole, B
export default class RoleCommand extends BushCommand {
public constructor() {
super('role', {
- aliases: ['role', 'addrole', 'removerole'],
+ aliases: ['role'],
category: 'moderation',
description: {
content: "Manages users' roles.",
@@ -69,7 +69,6 @@ export default class RoleCommand extends BushCommand {
start: `What user do you want to ${action} the role ${action2}?`,
retry: `{error} Choose a valid user to ${action} the role ${action2}.`
}
- //unordered: true
};
const role = yield {
id: 'role',
@@ -87,7 +86,7 @@ export default class RoleCommand extends BushCommand {
message: BushMessage | BushSlashMessage,
{ action, user, role }: { action: 'add' | 'remove'; user: BushGuildMember; role: BushRole }
): Promise<unknown> {
- if (!message.member.permissions.has('MANAGE_ROLES') && !this.client.ownerID.includes(message.author.id)) {
+ if (!message.member.permissions.has('MANAGE_ROLES') && !message.author.isOwner()) {
const mappings = this.client.consts.mappings;
let mappedRole: { name: string; id: string };
for (let i = 0; i < mappings.roleMap.length; i++) {
@@ -112,8 +111,7 @@ export default class RoleCommand extends BushCommand {
allowedMentions: AllowedMentions.none()
});
}
- }
- if (!this.client.ownerID.includes(message.author.id)) {
+ } else if (!message.author.isOwner()) {
if (role.comparePositionTo(message.member.roles.highest) >= 0) {
return await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is higher or equal to your highest role.`,
@@ -127,7 +125,7 @@ export default class RoleCommand extends BushCommand {
});
}
if (role.managed) {
- await await message.util.reply({
+ return await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is managed by an integration and cannot be managed.`,
allowedMentions: AllowedMentions.none()
});
@@ -138,7 +136,7 @@ export default class RoleCommand extends BushCommand {
const success = await user.roles.remove(role.id).catch(() => {});
if (success) {
return await message.util.reply({
- content: `${this.client.util.emojis.success}Successfully removed <@&${role.id}> from <@${user.id}>!`,
+ content: `${this.client.util.emojis.success} Successfully removed <@&${role.id}> from <@${user.id}>!`,
allowedMentions: AllowedMentions.none()
});
} else {
diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts
new file mode 100644
index 0000000..4f52666
--- /dev/null
+++ b/src/commands/moderation/unban.ts
@@ -0,0 +1,85 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { User } from 'discord.js';
+
+export default class UnbanCommand extends BushCommand {
+ public constructor() {
+ super('unban', {
+ aliases: ['unban'],
+ category: 'moderation',
+ description: {
+ content: 'Unban a member from the server.',
+ usage: 'unban <member> <reason> [--delete ]',
+ examples: ['unban 322862723090219008 I changed my mind, commands are allowed in #general']
+ },
+ args: [
+ {
+ id: 'user',
+ type: 'user',
+ prompt: {
+ start: 'What user would you like to unban?',
+ retry: '{error} Choose a valid user to unban.'
+ }
+ },
+ {
+ id: 'reason',
+ type: 'string',
+ match: 'restContent',
+ prompt: {
+ start: 'Why should this user be unbanned?',
+ retry: '{error} Choose a valid unban reason.',
+ optional: true
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'user',
+ description: 'What user would you like to unban?',
+ type: 'USER',
+ required: true
+ },
+ {
+ name: 'reason',
+ description: 'Why should this user be unbanned?',
+ type: 'STRING',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['BAN_MEMBERS'],
+ userPermissions: ['BAN_MEMBERS']
+ });
+ }
+ async exec(message: BushMessage | BushSlashMessage, { user, reason }: { user: User; reason?: string }): Promise<unknown> {
+ if (!(user instanceof User)) {
+ user = this.client.util.resolveUser(user, this.client.users.cache);
+ }
+ const response = await message.guild.unban({
+ user,
+ moderator: message.author,
+ reason
+ });
+
+ switch (response) {
+ case 'missing permissions':
+ return message.util.reply(
+ `${this.client.util.emojis.error} Could not unban **${user.tag}** because I do not have permissions`
+ );
+ case 'error unbanning':
+ return message.util.reply(`${this.client.util.emojis.error} An error occurred while trying to unban **${user.tag}**.`);
+ case 'error removing ban entry':
+ return message.util.reply(
+ `${this.client.util.emojis.error} While unbanning **${user.tag}**, there was an error removing their ban entry, please report this to my developers.`
+ );
+ case 'error creating modlog entry':
+ return message.util.reply(
+ `${this.client.util.emojis.error} While unbanning **${user.tag}**, there was an error creating a modlog entry, please report this to my developers.`
+ );
+ case 'user not banned':
+ return message.util.reply(`${this.client.util.emojis.warn} **${user.tag}** but I tried to unban them anyways.`);
+ case 'success':
+ return message.util.reply(`${this.client.util.emojis.success} Successfully unbanned **${user.tag}**.`);
+ }
+ }
+}
diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts
new file mode 100644
index 0000000..4030fb7
--- /dev/null
+++ b/src/commands/moderation/unmute.ts
@@ -0,0 +1,109 @@
+import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib';
+
+export default class UnmuteCommand extends BushCommand {
+ public constructor() {
+ super('unmute', {
+ aliases: ['unmute'],
+ category: 'moderation',
+ description: {
+ content: 'unmute a user.',
+ usage: 'unmute <member> [reason] [duration]',
+ examples: ['unmute 322862723090219008 1 day commands in #general']
+ },
+ args: [
+ {
+ id: 'user',
+ type: 'user',
+ prompt: {
+ start: 'What user would you like to unmute?',
+ retry: '{error} Choose a valid user to unmute.'
+ }
+ },
+ {
+ id: 'reason',
+ type: 'string',
+ match: 'rest',
+ prompt: {
+ start: 'Why should this user be unmuted?',
+ retry: '{error} Choose a valid unmute reason.',
+ optional: true
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'user',
+ description: 'What user would you like to unmute?',
+ type: 'USER',
+ required: true
+ },
+ {
+ name: 'reason',
+ description: 'Why should this user be unmuted?',
+ type: 'STRING',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES', 'MANAGE_ROLES'],
+ userPermissions: ['MANAGE_MESSAGES']
+ });
+ }
+ async exec(message: BushMessage | BushSlashMessage, { user, reason }: { user: BushUser; reason?: string }): Promise<unknown> {
+ const error = this.client.util.emojis.error;
+ const member = message.guild.members.cache.get(user.id) as BushGuildMember;
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'unmute');
+ const victimBoldTag = `**${member.user.tag}**`;
+
+ if (canModerateResponse !== true) {
+ return message.util.reply(canModerateResponse);
+ }
+
+ const response = await member.unmute({
+ reason,
+ moderator: message.author
+ });
+
+ switch (response) {
+ case 'missing permissions':
+ return message.util.reply(
+ `${error} Could not unmute ${victimBoldTag} because I am missing the \`Manage Roles\` permission.`
+ );
+ case 'no mute role':
+ return message.util.reply(
+ `${error} Could not unmute ${victimBoldTag}, you must set a mute role with \`${message.guild.getSetting(
+ 'prefix'
+ )}muterole\`.`
+ );
+ case 'invalid mute role':
+ return message.util.reply(
+ `${error} Could not unmute ${victimBoldTag} because the current mute role no longer exists. Please set a new mute role with \`${message.guild.getSetting(
+ 'prefix'
+ )}muterole\`.`
+ );
+ case 'mute role not manageable':
+ return message.util.reply(
+ `${error} Could not unmute ${victimBoldTag} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${message.guild.getSetting(
+ 'prefix'
+ )}muterole\`.`
+ );
+ case 'error removing mute role':
+ return message.util.reply(`${error} Could not unmute ${victimBoldTag}, there was an error removing their mute role.`);
+ case 'error creating modlog entry':
+ return message.util.reply(
+ `${error} While muting ${victimBoldTag}, there was an error creating a modlog entry, please report this to my developers.`
+ );
+ case 'error removing mute entry':
+ return message.util.reply(
+ `${error} While muting ${victimBoldTag}, there was an error removing their mute entry, please report this to my developers.`
+ );
+ case 'failed to dm':
+ return message.util.reply(
+ `${this.client.util.emojis.warn} unmuted **${member.user.tag}** however I could not send them a dm.`
+ );
+ case 'success':
+ return message.util.reply(`${this.client.util.emojis.success} Successfully unmuted **${member.user.tag}**.`);
+ }
+ }
+}
diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts
index 5d679ab..3d353ca 100644
--- a/src/commands/moderation/warn.ts
+++ b/src/commands/moderation/warn.ts
@@ -33,15 +33,15 @@ export default class WarnCommand extends BushCommand {
slash: true,
slashOptions: [
{
- type: 'USER',
name: 'user',
description: 'What user would you like to warn?',
+ type: 'USER',
required: true
},
{
- type: 'STRING',
name: 'reason',
description: 'Why should this user be warned?',
+ type: 'STRING',
required: false
}
],
@@ -58,7 +58,7 @@ export default class WarnCommand extends BushCommand {
const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'warn');
const victimBoldTag = `**${member.user.tag}**`;
- if (typeof canModerateResponse !== 'boolean') {
+ if (canModerateResponse !== true) {
return message.util.reply(canModerateResponse);
}
diff --git a/src/commands/moulberry-bush/level.ts b/src/commands/moulberry-bush/level.ts
index 7f3509f..fc1e93e 100644
--- a/src/commands/moulberry-bush/level.ts
+++ b/src/commands/moulberry-bush/level.ts
@@ -30,9 +30,9 @@ export default class LevelCommand extends BushCommand {
],
slashOptions: [
{
- type: 'USER',
name: 'user',
description: 'The user to get the level of',
+ type: 'USER',
required: false
}
],
diff --git a/src/commands/moderation/_unban.ts b/src/commands/utilities/whoHasRole.ts
index e69de29..e69de29 100644
--- a/src/commands/moderation/_unban.ts
+++ b/src/commands/utilities/whoHasRole.ts
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 6c6d49a..a981d30 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -643,21 +643,7 @@ export class BushClientUtil extends ClientUtil {
guild: BushGuildResolvable;
modlog: string;
}): Promise<Mute | Ban | PunishmentRole> {
- let dbModel: typeof Mute | typeof Ban | typeof PunishmentRole;
- switch (options.type) {
- case 'mute':
- dbModel = Mute;
- break;
- case 'ban':
- dbModel = Ban;
- break;
- case 'role':
- dbModel = PunishmentRole;
- break;
- default:
- throw 'choose a valid punishment entry type';
- }
-
+ const dbModel = this.findPunishmentModel(options.type);
const expires = options.duration ? new Date(new Date().getTime() + options.duration) : null;
const user = this.client.users.resolveId(options.user);
const guild = this.client.guilds.resolveId(options.guild);
@@ -669,6 +655,53 @@ export class BushClientUtil extends ClientUtil {
});
}
+ public async removePunishmentEntry(options: {
+ type: 'mute' | 'ban' | 'role';
+ user: BushGuildMemberResolvable;
+ guild: BushGuildResolvable;
+ }): Promise<boolean> {
+ const dbModel = this.findPunishmentModel(options.type);
+ const user = this.client.users.resolveId(options.user);
+ const guild = this.client.guilds.resolveId(options.guild);
+
+ let success = true;
+
+ const entries = await dbModel
+ .findAll({
+ // finding all cases of a certain type incase there were duplicates or something
+ where: {
+ user,
+ guild
+ }
+ })
+ .catch((e) => {
+ this.client.console.error('removePunishmentEntry', e?.stack || e);
+ success = false;
+ });
+ if (entries) {
+ entries.forEach(async (entry) => {
+ await entry.destroy().catch((e) => {
+ this.client.console.error('removePunishmentEntry', e?.stack || e);
+ });
+ success = false;
+ });
+ }
+ return success;
+ }
+
+ private findPunishmentModel(type: 'mute' | 'ban' | 'role'): typeof Mute | typeof Ban | typeof PunishmentRole {
+ switch (type) {
+ case 'mute':
+ return Mute;
+ case 'ban':
+ return Ban;
+ case 'role':
+ return PunishmentRole;
+ default:
+ throw 'choose a valid punishment entry type';
+ }
+ }
+
public humanizeDuration(duration: number): string {
return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2 });
}
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index ea34aec..6eca44d 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -1,6 +1,7 @@
-import { Guild } from 'discord.js';
+import { Guild, User } from 'discord.js';
+import { ModLogType } from '../..';
import { Guild as GuildDB, GuildModel } from '../../models/Guild';
-import { BushClient } from '../discord-akairo/BushClient';
+import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient';
export class BushGuild extends Guild {
public declare readonly client: BushClient;
@@ -19,4 +20,63 @@ export class BushGuild extends Guild {
row[setting] = value;
return await row.save();
}
+
+ public async unban(options: {
+ user: BushUserResolvable | User;
+ reason?: string;
+ moderator?: BushUserResolvable;
+ }): Promise<
+ | 'success'
+ | 'missing permissions'
+ | 'user not banned'
+ | 'error unbanning'
+ | 'error creating modlog entry'
+ | 'error removing ban entry'
+ > {
+ const user = this.client.users.resolveId(options.user);
+ const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator));
+
+ const bans = await this.bans.fetch();
+
+ let notBanned = false;
+ if (!bans.has(user)) notBanned = true;
+
+ const unbanSuccess = this.bans.remove(user, `${moderator.tag} | ${options.reason || 'No reason provided.'}`).catch((e) => {
+ if (e?.code === 'UNKNOWN_BAN') {
+ notBanned = true;
+ return true;
+ } else return false;
+ });
+
+ if (!unbanSuccess) return 'error unbanning';
+
+ // add modlog entry
+ const modlog = await this.client.util
+ .createModLogEntry({
+ type: ModLogType.UNBAN,
+ user,
+ moderator: moderator.id,
+ reason: options.reason,
+ guild: this
+ })
+ .catch(() => null);
+ if (!modlog) return 'error creating modlog entry';
+
+ // remove punishment entry
+ const removePunishmentEntrySuccess = await this.client.util
+ .removePunishmentEntry({
+ type: 'ban',
+ user,
+ guild: this
+ })
+ .catch(() => null);
+ if (!removePunishmentEntrySuccess) return 'error removing ban entry';
+
+ const userObject = this.client.users.cache.get(user);
+
+ userObject?.send(`You have been unbanned from **${this}** for **${options.reason || 'No reason provided'}**.`);
+
+ if (notBanned) return 'user not banned';
+ return 'success';
+ }
}
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 9e9266e..adcae69 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -7,7 +7,7 @@ import { BushUser } from './BushUser';
interface BushPunishmentOptions {
reason?: string;
- moderator: BushUserResolvable;
+ moderator?: BushUserResolvable;
}
interface BushTimedPunishmentOptions extends BushPunishmentOptions {
@@ -24,6 +24,8 @@ type WarnResponse = PunishmentResponse;
type PunishmentRoleResponse = PunishmentResponse;
+type RemovePunishmentRoleResponse = PunishmentResponse;
+
type MuteResponse =
| PunishmentResponse
| 'missing permissions'
@@ -33,16 +35,22 @@ type MuteResponse =
| 'error giving mute role'
| 'error creating mute entry';
-type UnmuteResponse = PunishmentResponse;
+type UnmuteResponse =
+ | PunishmentResponse
+ | 'missing permissions'
+ | 'no mute role'
+ | 'invalid mute role'
+ | 'mute role not manageable'
+ | 'error removing mute role'
+ | 'error removing mute entry';
type KickResponse = PunishmentResponse | 'missing permissions' | 'error kicking';
interface BushBanOptions extends BushTimedPunishmentOptions {
deleteDays?: number;
- duration?: number;
}
-type BanResponse = PunishmentResponse;
+type BanResponse = PunishmentResponse | 'missing permissions' | 'error creating ban entry' | 'error banning';
export class BushGuildMember extends GuildMember {
public declare readonly client: BushClient;
@@ -53,7 +61,7 @@ export class BushGuildMember extends GuildMember {
}
public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number }> {
- //add modlog entry
+ // add modlog entry
const { log, caseNum } = await this.client.util
.createModLogEntry(
{
@@ -68,7 +76,7 @@ export class BushGuildMember extends GuildMember {
.catch(() => null);
if (!log) return { result: 'error creating modlog entry', caseNum: null };
- //dm user
+ // dm user
const ending = this.guild.getSetting('punishmentEnding');
const dmSuccess = await this.send({
content: `You have been warned in **${this.guild}** for **${options.reason || 'No reason provided'}**.${
@@ -85,8 +93,12 @@ export class BushGuildMember extends GuildMember {
throw 'not implemented';
}
+ public removePunishRole(options: BushPunishmentRoleOptions): Promise<RemovePunishmentRoleResponse> {
+ throw 'not implemented';
+ }
+
public async mute(options: BushTimedPunishmentOptions): Promise<MuteResponse> {
- //checks
+ // checks
if (!this.guild.me.permissions.has('MANAGE_ROLES')) return 'missing permissions';
const muteRoleID = await this.guild.getSetting('muteRole');
if (!muteRoleID) return 'no mute role';
@@ -94,20 +106,20 @@ export class BushGuildMember extends GuildMember {
if (!muteRole) return 'invalid mute role';
if (muteRole.position >= this.guild.me.roles.highest.position || muteRole.managed) return 'mute role not manageable';
- const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator));
+ const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
- //add role
+ // add role
const muteSuccess = await this.roles
.add(muteRole, `[Mute] ${moderator.tag} | ${options.reason || 'No reason provided.'}`)
.catch(() => null);
if (!muteSuccess) return 'error giving mute role';
- //add modlog entry
+ // add modlog entry
const modlog = await this.client.util
.createModLogEntry({
type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE,
user: this,
- moderator: options.moderator,
+ moderator: moderator.id,
reason: options.reason,
duration: options.duration,
guild: this.guild
@@ -116,7 +128,7 @@ export class BushGuildMember extends GuildMember {
if (!modlog) return 'error creating modlog entry';
// add punishment entry so they can be unmuted later
- const mute = await this.client.util
+ const punishmentEntrySuccess = await this.client.util
.createPunishmentEntry({
type: 'mute',
user: this,
@@ -125,9 +137,9 @@ export class BushGuildMember extends GuildMember {
modlog: modlog.id
})
.catch(() => null);
- if (!mute) return 'error creating mute entry';
+ if (!punishmentEntrySuccess) return 'error creating mute entry';
- //dm user
+ // dm user
const ending = this.guild.getSetting('punishmentEnding');
const dmSuccess = await this.send({
content: `You have been muted ${
@@ -141,14 +153,61 @@ export class BushGuildMember extends GuildMember {
}
public async unmute(options: BushPunishmentOptions): Promise<UnmuteResponse> {
- throw 'not implemented';
+ //checks
+ if (!this.guild.me.permissions.has('MANAGE_ROLES')) return 'missing permissions';
+ const muteRoleID = await this.guild.getSetting('muteRole');
+ if (!muteRoleID) return 'no mute role';
+ const muteRole = this.guild.roles.cache.get(muteRoleID);
+ if (!muteRole) return 'invalid mute role';
+ if (muteRole.position >= this.guild.me.roles.highest.position || muteRole.managed) return 'mute role not manageable';
+
+ const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
+
+ //remove role
+ const muteSuccess = await this.roles
+ .remove(muteRole, `[Unmute] ${moderator.tag} | ${options.reason || 'No reason provided.'}`)
+ .catch(() => null);
+ if (!muteSuccess) return 'error removing mute role';
+
+ //remove modlog entry
+ const modlog = await this.client.util
+ .createModLogEntry({
+ type: ModLogType.UNMUTE,
+ user: this,
+ moderator: moderator.id,
+ reason: options.reason,
+ guild: this.guild
+ })
+ .catch(() => null);
+ if (!modlog) return 'error creating modlog entry';
+
+ // remove mute entry
+ const removePunishmentEntrySuccess = await this.client.util
+ .removePunishmentEntry({
+ type: 'mute',
+ user: this,
+ guild: this.guild
+ })
+ .catch(() => null);
+ if (!removePunishmentEntrySuccess) return 'error removing mute entry';
+
+ //dm user
+ const dmSuccess = await this.send({
+ content: `You have been unmuted in **${this.guild}** because **${options.reason || 'No reason provided'}**.`
+ }).catch(() => null);
+
+ if (!dmSuccess) return 'failed to dm';
+
+ return 'success';
}
public async bushKick(options: BushPunishmentOptions): Promise<KickResponse> {
- //checks
+ // checks
if (!this.guild.me.permissions.has('KICK_MEMBERS') || !this.kickable) return 'missing permissions';
- //dm user
+ const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
+
+ // dm user
const ending = this.guild.getSetting('punishmentEnding');
const dmSuccess = await this.send({
content: `You have been kicked from **${this.guild}** for **${options.reason || 'No reason provided'}**.${
@@ -156,16 +215,16 @@ export class BushGuildMember extends GuildMember {
}`
}).catch(() => null);
- //Kick
- const kickSuccess = await this.kick().catch(() => null);
+ // kick
+ const kickSuccess = await this.kick(`${moderator.tag} | ${options.reason || 'No reason provided.'}`).catch(() => null);
if (!kickSuccess) return 'error kicking';
- //add modlog entry
+ // add modlog entry
const modlog = await this.client.util
.createModLogEntry({
type: ModLogType.KICK,
user: this,
- moderator: options.moderator,
+ moderator: moderator.id,
reason: options.reason,
guild: this.guild
})
@@ -176,7 +235,53 @@ export class BushGuildMember extends GuildMember {
}
public async bushBan(options?: BushBanOptions): Promise<BanResponse> {
- throw 'not implemented';
+ // checks
+ if (!this.guild.me.permissions.has('BAN_MEMBERS') || !this.bannable) return 'missing permissions';
+
+ const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
+
+ // dm user
+ const ending = this.guild.getSetting('punishmentEnding');
+ const dmSuccess = await this.send({
+ content: `You have been banned ${
+ options.duration ? 'for ' + this.client.util.humanizeDuration(options.duration) : 'permanently'
+ } from **${this.guild}** for **${options.reason || 'No reason provided'}**.${ending ? `\n\n${ending}` : ''}`
+ }).catch(() => null);
+
+ // ban
+ const banSuccess = await this.ban({
+ reason: `${moderator.tag} | ${options.reason || 'No reason provided.'}`,
+ days: options.deleteDays
+ });
+ if (!banSuccess) return 'error banning';
+
+ // add modlog entry
+ const modlog = await this.client.util
+ .createModLogEntry({
+ type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
+ user: this,
+ moderator: moderator.id,
+ reason: options.reason,
+ duration: options.duration,
+ guild: this.guild
+ })
+ .catch(() => null);
+ if (!modlog) return 'error creating modlog entry';
+
+ // add punishment entry so they can be unbanned later
+ const punishmentEntrySuccess = await this.client.util
+ .createPunishmentEntry({
+ type: 'ban',
+ user: this,
+ guild: this.guild,
+ duration: options.duration,
+ modlog: modlog.id
+ })
+ .catch(() => null);
+ if (!punishmentEntrySuccess) return 'error creating ban entry';
+
+ if (!dmSuccess) return 'failed to dm';
+ return 'success';
}
public isOwner(): boolean {