aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/dev/superUser.ts4
-rw-r--r--src/commands/info/botInfo.ts6
-rw-r--r--src/commands/info/ping.ts4
-rw-r--r--src/commands/moderation/_block.ts (renamed from src/commands/moderation/block.ts)0
-rw-r--r--src/commands/moderation/_unban.ts (renamed from src/commands/moderation/unban.ts)0
-rw-r--r--src/commands/moderation/_unblock.ts (renamed from src/commands/moderation/unblock.ts)0
-rw-r--r--src/commands/moderation/_unmute.ts (renamed from src/commands/moderation/unmute.ts)0
-rw-r--r--src/commands/moderation/ban.ts6
-rw-r--r--src/commands/moderation/kick.ts93
-rw-r--r--src/commands/moderation/modlog.ts4
-rw-r--r--src/commands/moderation/mute.ts35
-rw-r--r--src/commands/moderation/role.ts177
-rw-r--r--src/commands/moderation/warn.ts110
-rw-r--r--src/commands/moulberry-bush/capePerms.ts4
-rw-r--r--src/commands/moulberry-bush/level.ts7
-rw-r--r--src/commands/moulberry-bush/rule.ts25
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts55
-rw-r--r--src/lib/extensions/discord-akairo/BushSlashMessage.ts3
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts74
19 files changed, 343 insertions, 264 deletions
diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts
index 981c0da..4d5ce2d 100644
--- a/src/commands/dev/superUser.ts
+++ b/src/commands/dev/superUser.ts
@@ -1,6 +1,6 @@
import { Constants } from 'discord-akairo';
import { User } from 'discord.js';
-import { BushCommand, BushMessage, Global } from '../../lib';
+import { BushCommand, BushMessage, BushSlashMessage, Global } from '../../lib';
export default class SuperUserCommand extends BushCommand {
public constructor() {
@@ -39,7 +39,7 @@ export default class SuperUserCommand extends BushCommand {
};
return { action, user };
}
- public async exec(message: BushMessage, args: { action: 'add' | 'remove'; user: User }): Promise<unknown> {
+ public async exec(message: BushMessage | BushSlashMessage, args: { action: 'add' | 'remove'; user: User }): Promise<unknown> {
if (!message.author.isOwner())
return await message.util.reply(`${this.client.util.emojis.error} Only my developers can run this command.`);
diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts
index 4a94318..80ca29d 100644
--- a/src/commands/info/botInfo.ts
+++ b/src/commands/info/botInfo.ts
@@ -1,5 +1,5 @@
-import { Message, MessageEmbed } from 'discord.js';
-import { BushCommand } from '../../lib';
+import { MessageEmbed } from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
export default class BotInfoCommand extends BushCommand {
public constructor() {
@@ -17,7 +17,7 @@ export default class BotInfoCommand extends BushCommand {
});
}
- public async exec(message: Message): Promise<void> {
+ 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 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', '');
diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts
index c1be3fb..804ede2 100644
--- a/src/commands/info/ping.ts
+++ b/src/commands/info/ping.ts
@@ -1,5 +1,5 @@
import { Message, MessageEmbed } from 'discord.js';
-import { BushCommand, BushSlashMessage } from '../../lib';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
export default class PingCommand extends BushCommand {
public constructor() {
@@ -17,7 +17,7 @@ export default class PingCommand extends BushCommand {
});
}
- public async exec(message: Message): Promise<void> {
+ 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 \`\`\``;
diff --git a/src/commands/moderation/block.ts b/src/commands/moderation/_block.ts
index e69de29..e69de29 100644
--- a/src/commands/moderation/block.ts
+++ b/src/commands/moderation/_block.ts
diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/_unban.ts
index e69de29..e69de29 100644
--- a/src/commands/moderation/unban.ts
+++ b/src/commands/moderation/_unban.ts
diff --git a/src/commands/moderation/unblock.ts b/src/commands/moderation/_unblock.ts
index e69de29..e69de29 100644
--- a/src/commands/moderation/unblock.ts
+++ b/src/commands/moderation/_unblock.ts
diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/_unmute.ts
index e69de29..e69de29 100644
--- a/src/commands/moderation/unmute.ts
+++ b/src/commands/moderation/_unmute.ts
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index 0c68497..244014b 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -1,5 +1,5 @@
-import { Message, User } from 'discord.js';
-import { BushCommand } from '../../lib';
+import { User } from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
export default class BanCommand extends BushCommand {
public constructor() {
@@ -140,7 +140,7 @@ export default class BanCommand extends BushCommand {
// }
// }
async exec(
- message: Message,
+ message: BushMessage | BushSlashMessage,
{ user, reason, time }: { user: User; reason?: string; time?: number | string }
): Promise<unknown> {
return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts
index 8375198..919c14b 100644
--- a/src/commands/moderation/kick.ts
+++ b/src/commands/moderation/kick.ts
@@ -1,15 +1,19 @@
-import { GuildMember, Message } from 'discord.js';
-import { BushCommand } from '../../lib';
+import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '../../lib';
export default class KickCommand extends BushCommand {
public constructor() {
super('kick', {
aliases: ['kick'],
category: 'moderation',
+ description: {
+ content: 'Kick a user.',
+ usage: 'kick <member> <reason>',
+ examples: ['kick @user bad']
+ },
args: [
{
id: 'user',
- type: 'member',
+ type: 'user',
prompt: {
start: 'What user would you like to kick?',
retry: '{error} Choose a valid user to kick.'
@@ -20,19 +24,13 @@ export default class KickCommand extends BushCommand {
type: 'string',
match: 'restContent',
prompt: {
- start: 'Why would you like to kick this user?',
- retry: '{error} Choose a valid user to kick.',
+ start: 'Why should this user be kicked?',
+ retry: '{error} Choose a valid kick reason.',
optional: true
}
}
],
- clientPermissions: ['KICK_MEMBERS'],
- userPermissions: ['KICK_MEMBERS'],
- description: {
- content: 'Kick a member from the server.',
- usage: 'kick <member> <reason>',
- examples: ['kick @user bad']
- },
+ slash: true,
slashOptions: [
{
type: 'USER',
@@ -43,67 +41,30 @@ export default class KickCommand extends BushCommand {
{
type: 'STRING',
name: 'reason',
- description: 'Why would you like to kick this user?',
+ description: 'Why should this user be kicked?',
required: false
}
],
- slash: true
+ clientPermissions: ['SEND_MESSAGES', 'KICK_MEMBERS'],
+ userPermissions: ['KICK_MEMBERS']
});
}
- // private async *genResponses(
- // message: Message | CommandInteraction,
- // user: GuildMember,
- // reason?: string
- // ): AsyncIterable<string> {
- // let modlogEnry: ModLog;
- // // 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 {
- // modlogEnry = ModLog.build({
- // user: user.id,
- // guild: message.guild.id,
- // moderator: message instanceof Message ? message.author.id : message.user.id,
- // type: ModLogType.KICK,
- // reason
- // });
- // await modlogEnry.save();
- // } catch (e) {
- // this.client.console.error(`KickCommand`, `Error saving to database. ${e?.stack || e}`);
- // yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
- // return;
- // }
- // try {
- // await user.send(`You were kicked in ${message.guild.name} with reason \`${reason || 'No reason given'}\``);
- // } catch {
- // yield `${this.client.util.emojis.warn} Unable to dm user`;
- // }
- // try {
- // await user.kick(
- // `Kicked by ${message instanceof Message ? message.author.tag : message.user.tag} with ${
- // reason ? `reason ${reason}` : 'no reason'
- // }`
- // );
- // } catch {
- // yield `${this.client.util.emojis.error} Error kicking :/`;
- // await modlogEnry.destroy();
- // return;
- // }
- // yield `${this.client.util.emojis.success} Kicked <@!${user.id}> with reason \`${reason || 'No reason given'}\``;
- // }
+ async exec(message: BushMessage | BushSlashMessage, { user, reason }: { user: BushUser; reason?: string }): Promise<unknown> {
+ const member = message.guild.members.cache.get(user.id) as BushGuildMember;
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'kick');
+ // const victimBoldTag = `**${member.user.tag}**`;
+
+ if (typeof canModerateResponse !== 'boolean') {
+ return message.util.reply(canModerateResponse);
+ }
- async exec(message: Message, { user, reason }: { user: GuildMember; reason?: string }): Promise<unknown> {
- return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
- // for await (const response of this.genResponses(message, user, reason)) {
- // await message.util.send(response);
- // }
+ const response = await member.bushKick({
+ reason,
+ moderator: message.author
+ });
+
+
}
}
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index 5be50a4..36f72fc 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -1,7 +1,7 @@
import { Argument } from 'discord-akairo';
import { MessageEmbed } from 'discord.js';
import moment from 'moment';
-import { BushCommand, BushMessage, ModLog } from '../../lib';
+import { BushCommand, BushMessage, BushSlashMessage, ModLog } from '../../lib';
export default class ModlogCommand extends BushCommand {
public constructor() {
@@ -48,7 +48,7 @@ export default class ModlogCommand extends BushCommand {
return modLog.join(`\n`);
}
- async exec(message: BushMessage, { search }: { search: string }): Promise<unknown> {
+ async exec(message: BushMessage | BushSlashMessage, { search }: { search: string }): Promise<unknown> {
const foundUser = await this.client.util.resolveUserAsync(search);
if (foundUser) {
const logs = await ModLog.findAll({
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
index 33c0e32..bc3abf2 100644
--- a/src/commands/moderation/mute.ts
+++ b/src/commands/moderation/mute.ts
@@ -1,5 +1,5 @@
import { Argument } from 'discord-akairo';
-import { BushCommand, BushGuildMember, BushMessage, BushUser } from '../../lib';
+import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '../../lib';
export default class MuteCommand extends BushCommand {
public constructor() {
@@ -8,8 +8,8 @@ export default class MuteCommand extends BushCommand {
category: 'moderation',
description: {
content: 'Mute a user.',
- usage: 'mute <member> <reason> [--time]',
- examples: ['mute @user bad boi --time 1h']
+ usage: 'mute <member> [reason] [duration]',
+ examples: ['mute 322862723090219008 1 day commands in #general']
},
args: [
{
@@ -31,8 +31,7 @@ export default class MuteCommand extends BushCommand {
}
}
],
- clientPermissions: ['MANAGE_ROLES'],
- userPermissions: ['MANAGE_MESSAGES'],
+ slash: true,
slashOptions: [
{
type: 'USER',
@@ -47,37 +46,29 @@ export default class MuteCommand extends BushCommand {
required: false
}
],
- slash: true
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES', 'MANAGE_ROLES'],
+ userPermissions: ['MANAGE_MESSAGES']
});
}
async exec(
- message: BushMessage,
+ message: BushMessage | BushSlashMessage,
{ user, reason }: { user: BushUser; reason?: { duration: number; contentWithoutTime: 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);
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'mute');
const victimBoldTag = `**${member.user.tag}**`;
- switch (canModerateResponse) {
- case 'moderator':
- return message.util.reply(`${error} You cannot mute ${victimBoldTag} because they are a moderator.`);
- case 'user hierarchy':
- return message.util.reply(
- `${error} You cannot mute ${victimBoldTag} because they have higher or equal role hierarchy as you do.`
- );
- case 'client hierarchy':
- return message.util.reply(
- `${error} You cannot mute ${victimBoldTag} because they have higher or equal role hierarchy as I do.`
- );
- case 'self':
- return message.util.reply(`${error} You cannot mute yourself.`);
+
+ if (typeof canModerateResponse !== 'boolean') {
+ return message.util.reply(canModerateResponse);
}
let time: number;
if (reason) {
time =
typeof reason === 'string'
- ? await Argument.cast('duration', this.client.commandHandler.resolver, message, reason)
+ ? await Argument.cast('duration', this.client.commandHandler.resolver, message as BushMessage, reason)
: reason.duration;
}
const parsedReason = reason.contentWithoutTime;
diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts
index 83e85e0..6bac9e8 100644
--- a/src/commands/moderation/role.ts
+++ b/src/commands/moderation/role.ts
@@ -1,28 +1,7 @@
/* eslint-disable @typescript-eslint/no-empty-function */
-import { GuildMember, Message, Role } from 'discord.js';
-import { AllowedMentions, BushCommand } from '../../lib';
+import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushRole, BushSlashMessage } from '../../lib';
export default class RoleCommand extends BushCommand {
- private roleWhitelist: Record<string, string[]> = {
- 'Partner': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Suggester': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper', 'Trial Helper', 'Contributor'],
- 'Level Locked': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Files': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Reactions': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Links': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Bots': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No VC': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Giveaways': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper'],
- 'No Support': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway Donor': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (200m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (100m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (50m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (25m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (10m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (5m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (1m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator']
- };
public constructor() {
super('role', {
aliases: ['role', 'addrole', 'removerole'],
@@ -32,59 +11,103 @@ export default class RoleCommand extends BushCommand {
usage: 'role <add|remove> <user> <role>',
examples: ['role add tyman adminperms']
},
- clientPermissions: ['MANAGE_ROLES', 'EMBED_LINKS', 'SEND_MESSAGES'],
- channel: 'guild',
- typing: true,
- args: [
+ slash: true,
+ slashOptions: [
{
- id: 'user',
- type: 'member',
- prompt: {
- start: `What user do you want to add/remove the role on?`,
- retry: `{error} Choose a valid user to add/remove the role on.`
- }
+ name: 'action',
+ description: 'Would you like to add or remove a role?',
+ type: 'STRING',
+ choices: [
+ {
+ name: 'add',
+ value: 'add'
+ },
+ {
+ name: 'remove',
+ value: 'remove'
+ }
+ ],
+ required: true
},
{
- id: 'role',
- type: 'role',
- match: 'restContent',
- prompt: {
- start: `What role do you want to add/remove?`,
- retry: `{error} Choose a valid role to add/remove.`
- }
- }
- ],
- slashOptions: [
- {
- type: 'USER',
name: 'user',
- description: 'The user to add/remove the role on',
+ description: 'The user you would like to add/remove the role from.',
+ type: 'USER',
required: true
},
{
- type: 'ROLE',
name: 'role',
- description: 'The role to add/remove',
+ description: 'The role you would like to add/remove from the user.',
+ type: 'ROLE',
required: true
}
- ]
+ ],
+ channel: 'guild',
+ typing: true,
+ clientPermissions: ['MANAGE_ROLES', 'EMBED_LINKS', 'SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
});
}
- public async exec(message: Message, { user, role }: { user: GuildMember; role: Role }): Promise<unknown> {
+ *args(): unknown {
+ const action: 'add' | 'remove' = yield {
+ id: 'action',
+ type: [['add'], ['remove']],
+ prompt: {
+ start: 'Would you like to `add` or `remove` a role?',
+ retry: '{error} Choose whether you would you like to `add` or `remove` a role.'
+ }
+ };
+ let action2: 'to' | 'from';
+ if (action === 'add') action2 = 'to';
+ else if (action === 'remove') action2 = 'from';
+ else return;
+ const user = yield {
+ id: 'user',
+ type: 'member',
+ prompt: {
+ 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',
+ type: 'role',
+ match: 'restContent',
+ prompt: {
+ start: `What role do you want to ${action}?`,
+ retry: `{error} Choose a valid role to ${action}.`
+ }
+ };
+ return { action, user, role };
+ }
+
+ public async exec(
+ 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)) {
- const mappedRole = this.client.consts.moulberryBushRoleMap.find((m) => m.id === role.id);
- if (!mappedRole || !this.roleWhitelist[mappedRole.name]) {
- return message.util.reply({
+ const mappings = this.client.consts.mappings;
+ let mappedRole: { name: string; id: string };
+ for (let i = 0; i < mappings.roleMap.length; i++) {
+ const a = mappings.roleMap[i];
+ if (a.id == role.id) mappedRole = a;
+ }
+ if (!mappedRole || !mappings.roleWhitelist[mappedRole.name]) {
+ return await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is not whitelisted, and you do not have manage roles permission.`,
allowedMentions: AllowedMentions.none()
});
}
- const allowedRoles = this.roleWhitelist[mappedRole.name].map((r) => {
- return this.client.consts.moulberryBushRoleMap.find((m) => m.name === r).id;
+ const allowedRoles = mappings.roleWhitelist[mappedRole.name].map((r) => {
+ for (let i = 0; i < mappings.roleMap.length; i++) {
+ if (mappings.roleMap[i].name == r) return mappings.roleMap[i].id;
+ }
+ return;
});
if (!message.member.roles.cache.some((role) => allowedRoles.includes(role.id))) {
- return message.util.reply({
+ return await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is whitelisted, but you do not have any of the roles required to manage it.`,
allowedMentions: AllowedMentions.none()
});
@@ -92,51 +115,51 @@ export default class RoleCommand extends BushCommand {
}
if (!this.client.ownerID.includes(message.author.id)) {
if (role.comparePositionTo(message.member.roles.highest) >= 0) {
- return message.util.reply({
+ return await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is higher or equal to your highest role.`,
allowedMentions: AllowedMentions.none()
});
}
if (role.comparePositionTo(message.guild.me.roles.highest) >= 0) {
- return message.util.reply({
+ return await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is higher or equal to my highest role.`,
allowedMentions: AllowedMentions.none()
});
}
if (role.managed) {
- await message.util.reply({
+ await await message.util.reply({
content: `${this.client.util.emojis.error} <@&${role.id}> is managed by an integration and cannot be managed.`,
allowedMentions: AllowedMentions.none()
});
}
}
- // No checks if the user has MANAGE_ROLES
- if (user.roles.cache.has(role.id)) {
- try {
- await user.roles.remove(role.id);
- } catch {
- return message.util.reply({
+ // no checks if the user has MANAGE_ROLES
+ if (action == 'remove') {
+ 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}>!`,
+ allowedMentions: AllowedMentions.none()
+ });
+ } else {
+ return await message.util.reply({
content: `${this.client.util.emojis.error} Could not remove <@&${role.id}> from <@${user.id}>.`,
allowedMentions: AllowedMentions.none()
});
}
- return message.util.reply({
- content: `${this.client.util.emojis.success} Successfully removed <@&${role.id}> from <@${user.id}>!`,
- allowedMentions: AllowedMentions.none()
- });
- } else {
- try {
- await user.roles.add(role.id);
- } catch {
- return message.util.reply({
+ } else if (action == 'add') {
+ const success = await user.roles.add(role.id).catch(() => {});
+ if (success) {
+ return await message.util.reply({
+ content: `${this.client.util.emojis.success} Successfully added <@&${role.id}> to <@${user.id}>!`,
+ allowedMentions: AllowedMentions.none()
+ });
+ } else {
+ return await message.util.reply({
content: `${this.client.util.emojis.error} Could not add <@&${role.id}> to <@${user.id}>.`,
allowedMentions: AllowedMentions.none()
});
}
- return message.util.reply({
- content: `${this.client.util.emojis.success} Successfully added <@&${role.id}> to <@${user.id}>!`,
- allowedMentions: AllowedMentions.none()
- });
}
}
}
diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts
index f2b9142..d1c17d4 100644
--- a/src/commands/moderation/warn.ts
+++ b/src/commands/moderation/warn.ts
@@ -1,61 +1,89 @@
-import { GuildMember, Message } from 'discord.js';
-import { BushCommand, Guild, ModLog, ModLogType } from '../../lib';
+import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '../../lib';
export default class WarnCommand extends BushCommand {
public constructor() {
super('warn', {
aliases: ['warn'],
category: 'moderation',
- userPermissions: ['MANAGE_MESSAGES'],
+ description: {
+ content: 'Warn a user.',
+ usage: 'warn <member> [reason]',
+ examples: ['warn @Tyman being cool']
+ },
args: [
{
- id: 'member',
- type: 'member'
+ id: 'user',
+ type: 'user',
+ prompt: {
+ start: 'What user would you like to warn?',
+ retry: '{error} Choose a valid user to warn.'
+ }
},
{
id: 'reason',
- type: 'contentWithDuration',
- match: 'rest'
+ type: 'content',
+ match: 'rest',
+ prompt: {
+ start: 'Why should this user be warned?',
+ retry: '{error} Choose a valid warn reason.',
+ optional: true
+ }
}
],
- description: {
- content: 'Warn a member and log it in modlogs',
- usage: 'warn <member> <reason>',
- examples: ['warn @Tyman being cool']
- }
+ slash: true,
+ slashOptions: [
+ {
+ type: 'USER',
+ name: 'user',
+ description: 'What user would you like to warn?',
+ required: true
+ },
+ {
+ type: 'STRING',
+ name: 'reason',
+ description: 'Why should this user be warned?',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['MANAGE_MESSAGES']
});
}
- public async exec(message: Message, { member, reason }: { member: GuildMember; reason: string }): Promise<unknown> {
- return message.util.reply(`${this.client.util.emojis.error} This command is not finished.`);
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ { user, reason }: { user: BushUser; reason: string }
+ ): Promise<unknown> {
+ const member = message.guild.members.cache.get(user.id) as BushGuildMember;
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'warn');
+ const victimBoldTag = `**${member.user.tag}**`;
- // 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 {
- const entry = ModLog.build({
- user: member.id,
- guild: message.guild.id,
- moderator: message.author.id,
- type: ModLogType.WARN,
- reason
- });
- await entry.save();
- } catch {
- await message.util.send('Error saving to database, please contact the developers');
- return;
+ if (typeof canModerateResponse !== 'boolean') {
+ return message.util.reply(canModerateResponse);
}
- try {
- await member.send(`You were warned in ${message.guild.name} for reason "${reason}".`);
- } catch {
- await message.util.send('Error messaging user, warning still saved.');
- return;
+
+ const { result: response, caseNum } = await member.warn({
+ reason,
+ moderator: message.author
+ });
+
+ switch (response) {
+ case 'error creating modlog entry':
+ return message.util.reply(
+ `${this.client.util.emojis.error} While warning ${victimBoldTag}, 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} **${member.user.tag}** has been warned for the ${this.client.util.ordinal(
+ caseNum
+ )} time, however I could not send them a dm.`
+ );
+ case 'success':
+ return message.util.reply(
+ `${this.client.util.emojis.success} Successfully warned **${member.user.tag}** for the ${this.client.util.ordinal(
+ caseNum
+ )} time.`
+ );
}
- await message.util.send(`${member.user.tag} was warned for reason "${reason}".`);
}
}
diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts
index 539ddf6..a564fc3 100644
--- a/src/commands/moulberry-bush/capePerms.ts
+++ b/src/commands/moulberry-bush/capePerms.ts
@@ -1,7 +1,7 @@
import { Constants } from 'discord-akairo';
import { MessageEmbed } from 'discord.js';
import got from 'got';
-import { BushCommand, BushMessage } from '../../lib';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
export default class CapePermissionsCommand extends BushCommand {
private nameMap = {
@@ -67,7 +67,7 @@ export default class CapePermissionsCommand extends BushCommand {
});
}
- public async exec(message: BushMessage, args: { ign: string }): Promise<unknown> {
+ public async exec(message: BushMessage | BushSlashMessage, args: { ign: string }): Promise<unknown> {
interface Capeperms {
success: boolean;
perms: User[];
diff --git a/src/commands/moulberry-bush/level.ts b/src/commands/moulberry-bush/level.ts
index 90a4b7f..5dcafe5 100644
--- a/src/commands/moulberry-bush/level.ts
+++ b/src/commands/moulberry-bush/level.ts
@@ -1,5 +1,4 @@
-import { Message, User } from 'discord.js';
-import { BushCommand, BushSlashMessage, Level } from '../../lib';
+import { BushCommand, BushMessage, BushSlashMessage, BushUser, Level } from '../../lib';
/*
import canvas from 'canvas';
import { MessageAttachment } from 'discord.js';
@@ -128,7 +127,7 @@ export default class LevelCommand extends BushCommand {
return image.toBuffer();
} */
- private async getResponse(user: User): Promise<string> {
+ private async getResponse(user: BushUser): Promise<string> {
const userLevelRow = await Level.findByPk(user.id);
if (userLevelRow) {
return `${user ? `${user.tag}'s` : 'Your'} level is ${userLevelRow.level} (${userLevelRow.xp} XP)`;
@@ -137,7 +136,7 @@ export default class LevelCommand extends BushCommand {
}
}
- async exec(message: Message | BushSlashMessage, { user }: { user?: User }): Promise<void> {
+ async exec(message: BushMessage | BushSlashMessage, { user }: { user?: BushUser }): Promise<void> {
// await message.reply(
// new MessageAttachment(
// await this.getImage(user || message.author),
diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts
index 516aadf..f8d312f 100644
--- a/src/commands/moulberry-bush/rule.ts
+++ b/src/commands/moulberry-bush/rule.ts
@@ -1,6 +1,6 @@
import { Argument, Constants } from 'discord-akairo';
import { MessageEmbed, User } from 'discord.js';
-import { AllowedMentions, BushCommand, BushMessage } from '../../lib';
+import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '../../lib';
const rules = [
{
@@ -106,7 +106,10 @@ export default class RuleCommand extends BushCommand {
});
}
- public async exec(message: BushMessage, { rule, user }: { rule: undefined | number; user: User }): Promise<unknown> {
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ { rule, user }: { rule: undefined | number; user: User }
+ ): Promise<unknown> {
const rulesEmbed = new MessageEmbed()
.setColor('#ef3929')
.setFooter(`Triggered by ${message.author.tag}`, message.author.avatarURL({ dynamic: true }))
@@ -130,21 +133,19 @@ export default class RuleCommand extends BushCommand {
return;
async function respond(): Promise<unknown> {
if (!user) {
- return (
- // If the original message was a reply -> imitate it
- message.reference?.messageID && !message.util.isSlash
- ? await message.channel.messages.fetch(message.reference.messageID).then(async (message) => {
- await message.util.reply({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() });
- })
- : await message.util.send({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() })
- );
+ // If the original message was a reply -> imitate it
+ (message as BushMessage).reference?.messageID && !message.util.isSlash
+ ? await message.channel.messages.fetch((message as BushMessage).reference.messageID).then(async (message) => {
+ await message.util.reply({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() });
+ })
+ : await message.util.send({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() });
} else {
- return message.reference?.messageID && !message.util.isSlash
+ return (message as BushMessage).reference?.messageID && !message.util.isSlash
? await message.util.send({
content: `<@!${user.id}>`,
embeds: [rulesEmbed],
allowedMentions: AllowedMentions.users(),
- reply: { messageReference: message.reference.messageID }
+ reply: { messageReference: (message as BushMessage).reference.messageID }
})
: await message.util.send({
content: `<@!${user.id}>`,
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 20ce365..4a38b3e 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -35,6 +35,7 @@ import {
BushGuildMemberResolvable,
BushGuildResolvable,
BushMessage,
+ BushSlashMessage,
Global,
Guild,
ModLog,
@@ -287,7 +288,7 @@ export class BushClientUtil extends ClientUtil {
/** Paginates an array of embeds using buttons. */
public async buttonPaginate(
- message: BushMessage,
+ message: BushMessage | BushSlashMessage,
embeds: MessageEmbed[],
text: string | null = null,
deleteOnExit?: boolean
@@ -397,7 +398,7 @@ export class BushClientUtil extends ClientUtil {
}
/** Sends a message with a button for the user to delete it. */
- public async sendWithDeleteButton(message: BushMessage, options: MessageOptions): Promise<void> {
+ public async sendWithDeleteButton(message: BushMessage | BushSlashMessage, options: MessageOptions): Promise<void> {
updateOptions();
const msg = await message.util.reply(options as MessageOptions & { split?: false });
const filter = (interaction: ButtonInteraction) => interaction.customID == 'paginate__stop' && interaction.message == msg;
@@ -565,30 +566,45 @@ export class BushClientUtil extends ClientUtil {
* Checks if a moderator can perform a moderation action on another user.
* @param moderator - The person trying to perform the action.
* @param victim - The person getting punished.
+ * @param type - The type of punishment - used to format the response.
* @param checkModerator - Whether or not to check if the victim is a moderator.
*/
public moderationPermissionCheck(
moderator: BushGuildMember,
victim: BushGuildMember,
+ type: 'mute' | 'unmute' | 'warn' | 'kick' | 'ban' | 'unban' | 'add a punishment role to' | 'remove a punishment role from',
checkModerator = true
- ): true | 'user hierarchy' | 'client hierarchy' | 'moderator' | 'self' {
- if (moderator.guild.id !== victim.guild.id) throw 'wtf';
+ ): true | string {
+ if (moderator.guild.id !== victim.guild.id) {
+ throw 'moderator and victim not in same guild';
+ }
const isOwner = moderator.guild.ownerID === moderator.id;
- if (moderator.id === victim.id) return 'self';
- if (moderator.roles.highest.position <= victim.roles.highest.position && !isOwner) return 'user hierarchy';
- if (victim.roles.highest.position >= victim.guild.me.roles.highest.position) return 'client hierarchy';
- if (checkModerator && victim.permissions.has('MANAGE_MESSAGES')) return 'moderator';
+ if (moderator.id === victim.id) {
+ return `${this.client.util.emojis.error} You cannot ${type} yourself.`;
+ }
+ if (moderator.roles.highest.position <= victim.roles.highest.position && !isOwner) {
+ return `${this.client.util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`;
+ }
+ if (victim.roles.highest.position >= victim.guild.me.roles.highest.position) {
+ return `${this.client.util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`;
+ }
+ if (checkModerator && victim.permissions.has('MANAGE_MESSAGES')) {
+ return `${this.client.util.emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`;
+ }
return true;
}
- public async createModLogEntry(options: {
- type: ModLogType;
- user: BushGuildMemberResolvable;
- moderator: BushGuildMemberResolvable;
- reason: string;
- duration: number;
- guild: BushGuildResolvable;
- }): Promise<ModLog> {
+ public async createModLogEntry(
+ options: {
+ type: ModLogType;
+ user: BushGuildMemberResolvable;
+ moderator: BushGuildMemberResolvable;
+ reason: string;
+ duration?: number;
+ guild: BushGuildResolvable;
+ },
+ getCaseNumber = false
+ ): Promise<{ log: ModLog; caseNum: number }> {
const user = this.client.users.resolveID(options.user);
const moderator = this.client.users.resolveID(options.moderator);
const guild = this.client.guilds.resolveID(options.guild);
@@ -612,10 +628,15 @@ export class BushClientUtil extends ClientUtil {
duration: duration,
guild
});
- return modLogEntry.save().catch((e) => {
+ const saveResult: ModLog = await modLogEntry.save().catch((e) => {
this.client.console.error('createModLogEntry', e?.stack || e);
return null;
});
+
+ if (!getCaseNumber) return { log: saveResult, caseNum: null };
+
+ const caseNum = (await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild } }))?.length;
+ return { log: saveResult, caseNum };
}
public async createPunishmentEntry(options: {
diff --git a/src/lib/extensions/discord-akairo/BushSlashMessage.ts b/src/lib/extensions/discord-akairo/BushSlashMessage.ts
index cf2f391..63358b0 100644
--- a/src/lib/extensions/discord-akairo/BushSlashMessage.ts
+++ b/src/lib/extensions/discord-akairo/BushSlashMessage.ts
@@ -1,12 +1,13 @@
import { AkairoMessage } from 'discord-akairo';
import { CommandInteraction } from 'discord.js';
-import { BushClient, BushCommandUtil, BushGuild, BushUser } from '..';
+import { BushClient, BushCommandUtil, BushGuild, BushGuildMember, BushUser } from '..';
export class BushSlashMessage extends AkairoMessage {
public declare client: BushClient;
public declare util: BushCommandUtil;
public declare guild: BushGuild;
public declare author: BushUser;
+ public declare member: BushGuildMember;
public constructor(
client: BushClient,
interaction: CommandInteraction,
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 54b26f0..e7f1ddf 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -15,7 +15,7 @@ interface BushPunishmentRoleOptions extends BushTimedPunishmentOptions {
role: RoleResolvable;
}
-type PunishmentResponse = 'success';
+type PunishmentResponse = 'success' | 'error creating modlog entry' | 'failed to dm';
type WarnResponse = PunishmentResponse;
@@ -28,13 +28,11 @@ type MuteResponse =
| 'invalid mute role'
| 'mute role not manageable'
| 'error giving mute role'
- | 'error creating modlog entry'
- | 'error creating mute entry'
- | 'failed to dm';
+ | 'error creating mute entry';
type UnmuteResponse = PunishmentResponse;
-type KickResponse = PunishmentResponse;
+type KickResponse = PunishmentResponse | 'missing permissions' | 'error kicking';
interface BushBanOptions extends BushTimedPunishmentOptions {
deleteDays?: number;
@@ -51,8 +49,33 @@ export class BushGuildMember extends GuildMember {
super(client, data, guild);
}
- public async warn(options: BushPunishmentOptions): Promise<WarnResponse> {
- throw 'not implemented';
+ public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number }> {
+ //add modlog entry
+ const { log, caseNum } = await this.client.util
+ .createModLogEntry(
+ {
+ type: ModLogType.WARN,
+ user: this,
+ moderator: options.moderator,
+ reason: options.reason,
+ guild: this.guild
+ },
+ true
+ )
+ .catch(() => null);
+ if (!log) return { result: 'error creating modlog entry', caseNum: null };
+
+ //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'}**.${
+ ending ? `\n\n${ending}` : ''
+ }`
+ }).catch(() => null);
+
+ if (!dmSuccess) return { result: 'failed to dm', caseNum };
+
+ return { result: 'success', caseNum };
}
public punishRole(options: BushPunishmentRoleOptions): Promise<PunishmentRoleResponse> {
@@ -68,9 +91,13 @@ 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));
+
//add role
- const success = await this.roles.add(muteRole).catch(() => null);
- if (!success) return 'error giving mute 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
const modlog = await this.client.util
@@ -115,7 +142,34 @@ export class BushGuildMember extends GuildMember {
}
public async bushKick(options: BushPunishmentOptions): Promise<KickResponse> {
- throw 'not implemented';
+ //checks
+ if (!this.guild.me.permissions.has('KICK_MEMBERS') || !this.kickable) return 'missing permissions';
+
+ //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'}**.${
+ ending ? `\n\n${ending}` : ''
+ }`
+ }).catch(() => null);
+
+ //Kick
+ const kickSuccess = await this.kick().catch(() => null);
+ if (!kickSuccess) return 'error kicking';
+
+ //add modlog entry
+ const modlog = await this.client.util
+ .createModLogEntry({
+ type: ModLogType.KICK,
+ user: this,
+ moderator: options.moderator,
+ reason: options.reason,
+ guild: this.guild
+ })
+ .catch(() => null);
+ if (!modlog) return 'error creating modlog entry';
+ if (!dmSuccess) return 'failed to dm';
+ return 'success';
}
public async bushBan(options?: BushBanOptions): Promise<BanResponse> {