aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore208
-rw-r--r--.vscode/typescript.code-snippets73
-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
21 files changed, 417 insertions, 471 deletions
diff --git a/.gitignore b/.gitignore
index 7db86d9..4f33c09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,140 +1,19 @@
-
-# Created by https://www.toptal.com/developers/gitignore/api/node,yarn,vscode,webstorm
-# Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn,vscode,webstorm
-
-### Node ###
# Logs
-logs
*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
# Dependency directories
node_modules/
-jspm_packages/
-
-# TypeScript v1 declaration files
-typings/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variables file
-.env
-.env.test
-.env*.local
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-
-# Nuxt.js build / generate output
-.nuxt
dist
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
### vscode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+!.vscode/*.code-snippets
*.code-workspace
-### WebStorm ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
# Generated files
.idea/**/contentModel.xml
@@ -147,90 +26,13 @@ dist
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-### WebStorm Patch ###
-# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
-
-# *.iml
-# modules.xml
-# .idea/misc.xml
-# *.ipr
-
-# Sonarlint plugin
-# https://plugins.jetbrains.com/plugin/7973-sonarlint
-.idea/**/sonarlint/
-
-# SonarQube Plugin
-# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
-.idea/**/sonarIssues.xml
-
-# Markdown Navigator plugin
-# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
-.idea/**/markdown-navigator.xml
-.idea/**/markdown-navigator-enh.xml
-.idea/**/markdown-navigator/
-
# Cache file creation bug
-# See https://youtrack.jetbrains.com/issue/JBR-2257
-.idea/$CACHE_FILE$
.yarn/cache/*
.yarn/sdks/eslint/*
.yarn/sdks/prettier/*
.yarn/install-state.gz
.yarn/build-state.url
.yarn/releases.gz
-# CodeStream plugin
-# https://plugins.jetbrains.com/plugin/12206-codestream
-.idea/codestream.xml
### yarn ###
# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored
@@ -240,16 +42,8 @@ fabric.properties
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
-
-# if you are NOT using Zero-installs, then:
-# comment the following lines
!.yarn/cache
-# and uncomment the following lines
-# .pnp.*
-
-# End of https://www.toptal.com/developers/gitignore/api/node,yarn,vscode,webstorm
-
# Options and credentials for the bot
src/config/options.ts
diff --git a/.vscode/typescript.code-snippets b/.vscode/typescript.code-snippets
new file mode 100644
index 0000000..0984c3c
--- /dev/null
+++ b/.vscode/typescript.code-snippets
@@ -0,0 +1,73 @@
+/* prettier-ignore */
+{
+ /**
+ * Place your snippets for typescript here. Each snippet is defined under a snippet name and has a prefix, body and
+ * description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
+ * $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
+ * same ids are connected.
+ */
+
+ "Setup Slash Command": {
+ "prefix": "slash",
+ "body": [
+ "slash: true,",
+ "slashOptions: [",
+ "\t$0",
+ "]"
+ ]
+ },
+
+ "Slash Command User Argument": {
+ "prefix": "user",
+ "body": [
+ "{",
+ "\tname: 'user',",
+ "\tdescription: 'The user you would like to$1',",
+ "\ttype: 'USER',",
+ "\trequired: $2",
+ "},$0"
+ ]
+ },
+
+ "Slash Command String Argument": {
+ "prefix": "string",
+ "body": [
+ "{",
+ "\tname: '$1',",
+ "\tdescription: '$2',",
+ "\ttype: 'STRING',",
+ "\trequired: $3",
+ "},$0"
+ ]
+ },
+
+ "Slash Command Choice Argument": {
+ "prefix": "choice",
+ "body": [
+ "{",
+ "\tname: '$1',",
+ "\tdescription: '$2',",
+ "\ttype: 'STRING',",
+ "\tchoices: [",
+ "\t\t{",
+ "\t\t\tname: '$3',",
+ "\t\t\tvalue: '$4'",
+ "\t\t},",
+ "\t],",
+ "\trequired: $5",
+ "},$0"
+ ]
+ },
+
+ "Slash Boolean Argument": {
+ "prefix": "boolean",
+ "body": [
+ "{",
+ "\tname: '$1',",
+ "\tdescription: '$2',",
+ "\ttype: 'BOOLEAN',",
+ "\trequired: $3",
+ "},$0"
+ ]
+ }
+}
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> {