aboutsummaryrefslogtreecommitdiff
path: root/src/commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/commands')
-rw-r--r--src/commands/_fake-command/test.ts18
-rw-r--r--src/commands/admin/channelPermissions.ts97
-rw-r--r--src/commands/config/autoPublishChannel.ts21
-rw-r--r--src/commands/config/blacklist.ts132
-rw-r--r--src/commands/config/disable.ts128
-rw-r--r--src/commands/config/prefix.ts20
-rw-r--r--src/commands/dev/__template.ts61
-rw-r--r--src/commands/dev/eval.ts10
-rw-r--r--src/commands/dev/servers.ts48
-rw-r--r--src/commands/dev/setLevel.ts6
-rw-r--r--src/commands/dev/sh.ts84
-rw-r--r--src/commands/info/color.ts47
-rw-r--r--src/commands/info/help.ts42
-rw-r--r--src/commands/info/userInfo.ts2
-rw-r--r--src/commands/moderation/ban.ts15
-rw-r--r--src/commands/moderation/kick.ts13
-rw-r--r--src/commands/moderation/lockdown.ts45
-rw-r--r--src/commands/moderation/modlog.ts2
-rw-r--r--src/commands/moderation/mute.ts10
-rw-r--r--src/commands/moderation/warn.ts10
-rw-r--r--src/commands/moulberry-bush/capePerms.ts33
-rw-r--r--src/commands/moulberry-bush/level.ts8
-rw-r--r--src/commands/moulberry-bush/report.ts121
-rw-r--r--src/commands/moulberry-bush/rule.ts25
-rw-r--r--src/commands/skyblock-reborn/chooseColorCommand.ts (renamed from src/commands/skyblockReborn/chooseColorCommand.ts)0
-rw-r--r--src/commands/utilities/_whoHasRole.ts0
-rw-r--r--src/commands/utilities/hash.ts42
-rw-r--r--src/commands/utilities/price.ts220
-rw-r--r--src/commands/utilities/viewraw.ts2
-rw-r--r--src/commands/utilities/whoHasRole.ts53
30 files changed, 1200 insertions, 115 deletions
diff --git a/src/commands/_fake-command/test.ts b/src/commands/_fake-command/test.ts
new file mode 100644
index 0000000..8eeca9e
--- /dev/null
+++ b/src/commands/_fake-command/test.ts
@@ -0,0 +1,18 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+
+export default class TestCommand extends BushCommand {
+ public constructor() {
+ super('test', {
+ category: 'fake-commands',
+ description: { content: '', examples: '', usage: '' },
+ condition: (message: BushMessage) => {
+ if (message.content.toLowerCase().includes('ironmoon')) return true;
+ else return false;
+ },
+ completelyHide: true
+ });
+ }
+ public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
+ return await message.util.reply('Your message included the word ironmoon.');
+ }
+}
diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts
new file mode 100644
index 0000000..249789d
--- /dev/null
+++ b/src/commands/admin/channelPermissions.ts
@@ -0,0 +1,97 @@
+import { Argument, Constants } from 'discord-akairo';
+import { GuildChannel, GuildMember, MessageEmbed, Role } from 'discord.js';
+import { BushCommand, BushMessage } from '../../lib';
+
+export default class ChannelPermissionsCommand extends BushCommand {
+ public constructor() {
+ super('channelpermissions', {
+ aliases: ['channelperms', 'cperms', 'cperm', 'chanperms', 'chanperm', 'channelpermissions'],
+ category: 'admin',
+ typing: true,
+ description: {
+ content: 'Use to mass change the channel ',
+ usage: 'ChannelPerms <role_id> <perm> <state>',
+ examples: ['ChannelPerms 783794633129197589 read_messages deny']
+ },
+ args: [
+ {
+ id: 'target',
+ type: Argument.union(Constants.ArgumentTypes.ROLE, Constants.ArgumentTypes.MEMBER),
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What user/role would you like to change?',
+ retry: 'Invalid response. What user/role would you like to change?'
+ }
+ },
+ {
+ id: 'permission',
+ type: 'permission',
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What permission would you like to change?',
+ retry: '{error} Choose a valid permission.'
+ }
+ },
+ {
+ id: 'state',
+ type: [
+ ['true', '1', 'yes', 'enable', 'allow'],
+ ['false', '0', 'no', 'disable', 'disallow', 'deny'],
+ ['neutral', 'remove', 'none']
+ ],
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What should that permission be set to?',
+ retry: '{error} Set the state to either `enable`, `disable`, or `remove`.'
+ }
+ }
+ ],
+ ratelimit: 4,
+ cooldown: 4000,
+ clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES'],
+ userPermissions: ['ADMINISTRATOR'],
+ channel: 'guild'
+ });
+ }
+
+ public async exec(
+ message: BushMessage,
+ {
+ target,
+ permission,
+ state
+ }: {
+ target: Role | GuildMember;
+ permission: string;
+ state: 'true' | 'false' | 'neutral';
+ }
+ ): Promise<void> {
+ const failedChannels = [];
+ for (const channel of message.guild.channels.cache.array()) {
+ try {
+ if (channel.isThread()) return;
+ if (channel.permissionsLocked) return;
+ const permissionState = state === 'true' ? true : state === 'false' ? false : null;
+ await channel.permissionOverwrites.create(
+ target.id,
+ { [permission]: permissionState },
+ { reason: 'Changing overwrites for mass channel channel perms command' }
+ );
+ } catch (e) {
+ this.client.console.debug(e.stack);
+ failedChannels.push(channel);
+ }
+ }
+ const failure = failedChannels.map((e: GuildChannel) => `<#${e.id}>`).join(' ');
+ if (failure.length > 2000) {
+ const paginate: MessageEmbed[] = [];
+ for (let i = 0; i < failure.length; i += 2000) {
+ paginate.push(new MessageEmbed().setDescription(failure.substring(i, Math.min(failure.length, i + 2000))));
+ }
+ const normalMessage = `Finished changing perms! Failed channels:`;
+ this.client.util.buttonPaginate(message, paginate, normalMessage);
+ } else {
+ await message.util.reply({ content: `Finished changing perms! Failed channels:`, embeds: [{ description: failure }] });
+ }
+ }
+}
diff --git a/src/commands/config/autoPublishChannel.ts b/src/commands/config/autoPublishChannel.ts
index 421e030..a2692e2 100644
--- a/src/commands/config/autoPublishChannel.ts
+++ b/src/commands/config/autoPublishChannel.ts
@@ -1,4 +1,4 @@
-import { BushCommand, BushMessage } from '@lib';
+import { AllowedMentions, BushCommand, BushMessage } from '@lib';
import { Channel } from 'discord.js';
export default class AutoPublishChannelCommand extends BushCommand {
@@ -40,14 +40,17 @@ export default class AutoPublishChannelCommand extends BushCommand {
public async exec(message: BushMessage, { channel }: { channel: Channel }): Promise<unknown> {
const autoPublishChannels = await message.guild.getSetting('autoPublishChannels');
- autoPublishChannels.includes(channel.id)
- ? autoPublishChannels.splice(autoPublishChannels.indexOf(channel.id), 1)
- : autoPublishChannels.push(channel.id);
- await message.guild.setSetting('autoPublishChannels', autoPublishChannels);
- return await message.util.reply(
- `${this.client.util.emojis.success} Successfully ${
- autoPublishChannels.includes(channel.id) ? 'disabled' : 'enabled'
- } auto publishing in <#${channel.id}>.`
+ const newValue = this.client.util.addOrRemoveFromArray(
+ autoPublishChannels.includes(channel.id) ? 'remove' : 'add',
+ autoPublishChannels,
+ channel.id
);
+ await message.guild.setSetting('autoPublishChannels', newValue);
+ return await message.util.reply({
+ content: `${this.client.util.emojis.success} Successfully ${
+ autoPublishChannels.includes(channel.id) ? 'disabled' : 'enabled'
+ } auto publishing in <#${channel.id}>.`,
+ allowedMentions: AllowedMentions.none()
+ });
}
}
diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts
new file mode 100644
index 0000000..4706041
--- /dev/null
+++ b/src/commands/config/blacklist.ts
@@ -0,0 +1,132 @@
+import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, Global } from '@lib';
+import { Argument } from 'discord-akairo';
+import { Channel, User } from 'discord.js';
+
+export default class BlacklistCommand extends BushCommand {
+ public constructor() {
+ super('blacklist', {
+ aliases: ['blacklist', 'unblacklist'],
+ category: 'config',
+ description: {
+ content: 'A command to blacklist users and channels.',
+ usage: 'blacklist|unblacklist <user|channel>',
+ examples: ['blacklist @user', 'unblacklist #channel']
+ },
+ args: [
+ {
+ id: 'target',
+ type: Argument.union('channel', 'user'),
+ match: 'phrase',
+ prompt: {
+ start: 'What channel or user that you would like to blacklist/unblacklist?',
+ retry: '{error} Pick a valid command.',
+ optional: false
+ }
+ },
+ {
+ id: 'global',
+ match: 'flag',
+ flag: '--global'
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'action',
+ description: 'Would you like to add or remove someone or something from/to the blacklist?',
+ type: 'STRING',
+ choices: [
+ {
+ name: 'blacklist',
+ value: 'blacklist'
+ },
+ {
+ name: 'unblacklist',
+ value: 'unblacklist'
+ }
+ ],
+ required: true
+ },
+ {
+ name: 'target',
+ description: 'What channel or user that you would like to blacklist/unblacklist?',
+ type: 'STRING',
+ required: true
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD']
+ });
+ }
+
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ args: { action: 'blacklist' | 'unblacklist'; target: Channel | User | string; global: boolean }
+ ): Promise<unknown> {
+ let action: 'blacklist' | 'unblacklist' | 'toggle' =
+ args.action ?? (message?.util?.parsed?.alias as 'blacklist' | 'unblacklist') ?? 'toggle';
+ const global = args.global && message.author.isOwner();
+ const target =
+ typeof args.target === 'string'
+ ? (await Argument.cast('channel', this.client.commandHandler.resolver, message as BushMessage, args.target)) ??
+ (await Argument.cast('user', this.client.commandHandler.resolver, message as BushMessage, args.target))
+ : args.target;
+ if (!target) return await message.util.reply(`${this.client.util.emojis.error} Choose a valid channel or user.`);
+ const targetID = target.id;
+
+ if (global) {
+ if (action === 'toggle') {
+ const blacklistedUsers = (await Global.findByPk(this.client.config.dev ? 'development' : 'production'))
+ .blacklistedUsers;
+ const blacklistedChannels = (await Global.findByPk(this.client.config.dev ? 'development' : 'production'))
+ .blacklistedChannels;
+ action = blacklistedUsers.includes(targetID) || blacklistedChannels.includes(targetID) ? 'unblacklist' : 'blacklist';
+ }
+ const success = await this.client.util
+ .insertOrRemoveFromGlobal(
+ action === 'blacklist' ? 'add' : 'remove',
+ target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels',
+ targetID
+ )
+ .catch(() => false);
+ if (!success)
+ return await message.util.reply({
+ content: `${this.client.util.emojis.error} There was an error globally **${action}ing** ${
+ target?.tag ?? target.name
+ }.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ else
+ return await message.util.reply({
+ content: `${this.client.util.emojis.success} Successfully **${action}ed** ${target?.tag ?? target.name} globally.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ // guild disable
+ } else {
+ const blacklistedChannels = await message.guild.getSetting('blacklistedChannels');
+ const blacklistedUsers = await message.guild.getSetting('blacklistedUsers');
+ if (action === 'toggle') {
+ action = blacklistedChannels.includes(targetID) ?? blacklistedUsers.includes(targetID) ? 'unblacklist' : 'blacklist';
+ }
+ const newValue = this.client.util.addOrRemoveFromArray(
+ action === 'blacklist' ? 'add' : 'remove',
+ target instanceof User ? blacklistedUsers : blacklistedChannels,
+ targetID
+ );
+ const success = await message.guild
+ .setSetting(target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels', newValue)
+ .catch(() => false);
+ if (!success)
+ return await message.util.reply({
+ content: `${this.client.util.emojis.error} There was an error **${action}ing** ${target?.tag ?? target.name}.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ else
+ return await message.util.reply({
+ content: `${this.client.util.emojis.success} Successfully **${action}ed** ${target?.tag ?? target.name}.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ }
+ }
+}
diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts
new file mode 100644
index 0000000..007cdb1
--- /dev/null
+++ b/src/commands/config/disable.ts
@@ -0,0 +1,128 @@
+import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, Global } from '@lib';
+
+export default class DisableCommand extends BushCommand {
+ public constructor() {
+ super('disable', {
+ aliases: ['disable', 'enable'],
+ category: 'config',
+ description: {
+ content: 'A command to disable and enable commands.',
+ usage: 'disable|enable <command>',
+ examples: ['enable ban', 'disable kick']
+ },
+ args: [
+ {
+ id: 'command',
+ type: 'commandAlias',
+ match: 'phrase',
+ prompt: {
+ start: 'What command would you like to enable/disable?',
+ retry: '{error} Pick a valid command.',
+ optional: false
+ }
+ },
+ {
+ id: 'global',
+ match: 'flag',
+ flag: '--global'
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'action',
+ description: 'Would you like to disable or enable a command?',
+ type: 'STRING',
+ choices: [
+ {
+ name: 'enable',
+ value: 'enable'
+ },
+ {
+ name: 'disable',
+ value: 'disable'
+ }
+ ],
+ required: true
+ },
+ {
+ name: 'command',
+ description: 'What command would you like to enable/disable?',
+ type: 'STRING',
+ required: true
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD']
+ });
+ }
+
+ blacklistedCommands = ['eval', 'disable'];
+
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ args: { action: 'enable' | 'disable'; command: BushCommand | string; global: boolean }
+ ): Promise<unknown> {
+ let action: 'disable' | 'enable' | 'toggle' =
+ args.action ?? (message?.util?.parsed?.alias as 'disable' | 'enable') ?? 'toggle';
+ const global = args.global && message.author.isOwner();
+ const commandID = (args.command as BushCommand).id;
+
+ if (global) {
+ if (action === 'toggle') {
+ const disabledCommands = (await Global.findByPk(this.client.config.dev ? 'development' : 'production'))
+ .disabledCommands;
+ action = disabledCommands.includes(commandID) ? 'disable' : 'enable';
+ }
+ const success = await this.client.util
+ .insertOrRemoveFromGlobal(action === 'disable' ? 'remove' : 'add', 'disabledCommands', commandID)
+ .catch(() => false);
+ if (!success)
+ return await message.util.reply({
+ content: `${this.client.util.emojis.error} There was an error globally **${action.substr(
+ 0,
+ action.length - 2
+ )}ing** the **${commandID}** command.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ else
+ return await message.util.reply({
+ content: `${this.client.util.emojis.success} Successfully **${action.substr(
+ 0,
+ action.length - 2
+ )}ed** the **${commandID}** command globally.`,
+ allowedMentions: AllowedMentions.none()
+ });
+
+ // guild disable
+ } else {
+ const disabledCommands = await message.guild.getSetting('disabledCommands');
+ if (action === 'toggle') {
+ action = disabledCommands.includes(commandID) ? 'disable' : 'enable';
+ }
+ const newValue = this.client.util.addOrRemoveFromArray(
+ action === 'disable' ? 'remove' : 'add',
+ disabledCommands,
+ commandID
+ );
+ const success = await message.guild.setSetting('disabledCommands', newValue).catch(() => false);
+ if (!success)
+ return await message.util.reply({
+ content: `${this.client.util.emojis.error} There was an error **${action.substr(
+ 0,
+ action.length - 2
+ )}ing** the **${commandID}** command.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ else
+ return await message.util.reply({
+ content: `${this.client.util.emojis.success} Successfully **${action.substr(
+ 0,
+ action.length - 2
+ )}ed** the **${commandID}** command.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ }
+ }
+}
diff --git a/src/commands/config/prefix.ts b/src/commands/config/prefix.ts
index 79956be..380442b 100644
--- a/src/commands/config/prefix.ts
+++ b/src/commands/config/prefix.ts
@@ -1,4 +1,4 @@
-import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib';
export default class PrefixCommand extends BushCommand {
public constructor() {
@@ -40,15 +40,17 @@ export default class PrefixCommand extends BushCommand {
const oldPrefix = await message.guild.getSetting('prefix');
await message.guild.setSetting('prefix', args.prefix ?? this.client.config.prefix);
if (args.prefix) {
- return await message.util.send(
- `${this.client.util.emojis.success} changed the server's prefix ${oldPrefix ? `from \`${oldPrefix}\`` : ''} to \`${
- args.prefix
- }\`.`
- );
+ return await message.util.send({
+ content: `${this.client.util.emojis.success} changed the server's prefix ${
+ oldPrefix ? `from \`${oldPrefix}\`` : ''
+ } to \`${args.prefix}\`.`,
+ allowedMentions: AllowedMentions.none()
+ });
} else {
- return await message.util.send(
- `${this.client.util.emojis.success} reset the server's prefix to \`${this.client.config.prefix}\`.`
- );
+ return await message.util.send({
+ content: `${this.client.util.emojis.success} reset the server's prefix to \`${this.client.config.prefix}\`.`,
+ allowedMentions: AllowedMentions.none()
+ });
}
}
}
diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts
new file mode 100644
index 0000000..ffc67ae
--- /dev/null
+++ b/src/commands/dev/__template.ts
@@ -0,0 +1,61 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+
+export default class TemplateCommand extends BushCommand {
+ public constructor() {
+ super('template', {
+ aliases: ['template'],
+ category: 'template',
+ description: {
+ content: 'Command description.',
+ usage: 'template <requiredArg> [optionalArg]',
+ examples: ['template 1 2']
+ },
+ args: [
+ {
+ id: 'required_argument',
+ type: 'string',
+ match: 'phrase',
+ prompt: {
+ start: 'What would you like to set your first argument to be?',
+ retry: '{error} Pick a valid argument.',
+ optional: false
+ }
+ },
+ {
+ id: 'optional_argument',
+ type: 'string',
+ match: 'phrase',
+ prompt: {
+ start: 'What would you like to set your second argument to be?',
+ retry: '{error} Pick a valid argument.',
+ optional: true
+ }
+ }
+ ],
+ slash: false, //set this to true
+ slashOptions: [
+ {
+ name: 'required_argument',
+ description: 'What would you like to set your first argument to be?',
+ type: 'STRING',
+ required: true
+ },
+ {
+ name: 'optional_argument',
+ description: 'What would you like to set your second argument to be?',
+ type: 'STRING',
+ required: false
+ }
+ ],
+ superUserOnly: true,
+ ownerOnly: true,
+ channel: 'guild',
+ hidden: true,
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+ public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
+ return await message.util.reply(`${this.client.util.emojis.error} Do not use the template command.`);
+ }
+}
diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts
index f3a30ab..76a78ba 100644
--- a/src/commands/dev/eval.ts
+++ b/src/commands/dev/eval.ts
@@ -20,7 +20,7 @@ export default class EvalCommand extends BushCommand {
aliases: ['eval', 'ev'],
category: 'dev',
description: {
- content: 'Use the command to eval stuff in the bot.',
+ content: 'Evaluate code.',
usage: 'eval [--depth #] <code> [--sudo] [--silent] [--delete] [--proto] [--hidden] [--ts]',
examples: ['eval message.guild.name', 'eval this.client.ownerID']
},
@@ -72,7 +72,6 @@ export default class EvalCommand extends BushCommand {
}
}
],
- ownerOnly: true,
slash: true,
slashOptions: [
{
@@ -117,7 +116,8 @@ export default class EvalCommand extends BushCommand {
type: 'BOOLEAN',
required: false
}
- ]
+ ],
+ ownerOnly: true
});
}
@@ -141,8 +141,8 @@ export default class EvalCommand extends BushCommand {
}
const code: { js?: string | null; ts?: string | null; lang?: 'js' | 'ts' } = {};
args.code = args.code.replace(/[“”]/g, '"');
- args.code = args.code.replace(/```/g, '');
- if (args.typescript) {
+ args.code = args.code.replace(/```*(?:js|ts)?/g, '');
+ if (args.typescript || message) {
code.ts = args.code;
code.js = transpile(args.code);
code.lang = 'ts';
diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts
new file mode 100644
index 0000000..08f74e8
--- /dev/null
+++ b/src/commands/dev/servers.ts
@@ -0,0 +1,48 @@
+import { Guild, MessageEmbed } from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+export default class ServersCommand extends BushCommand {
+ public constructor() {
+ super('servers', {
+ aliases: ['servers'],
+ category: 'dev',
+ description: {
+ content: 'Displays all the severs the bot is in',
+ usage: 'servers',
+ examples: ['servers']
+ },
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES'],
+ superUserOnly: true
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
+ const maxLength = 10;
+ const guilds = this.client.guilds.cache.sort((a, b) => (a.memberCount < b.memberCount ? 1 : -1)).array();
+ const chunkedGuilds: Guild[][] = [];
+ const embeds: MessageEmbed[] = [];
+
+ for (let i = 0, j = guilds.length; i < j; i += maxLength) {
+ chunkedGuilds.push(guilds.slice(i, i + maxLength));
+ }
+
+ chunkedGuilds.forEach((c: Guild[]) => {
+ const embed = new MessageEmbed();
+ c.forEach((g: Guild) => {
+ const owner = this.client.users.cache.get(g.ownerId)?.tag;
+ embed
+ .addField(
+ `**${g.name}**`,
+ `**ID:** ${g.id}\n**Owner:** ${owner ? owner : g.ownerId}\n**Members:** ${g.memberCount.toLocaleString()}`,
+ false
+ )
+ .setTitle('Server List')
+ .setColor(this.client.util.colors.default);
+ });
+ embeds.push(embed);
+ });
+
+ return await this.client.util.buttonPaginate(message, embeds);
+ }
+}
diff --git a/src/commands/dev/setLevel.ts b/src/commands/dev/setLevel.ts
index f2ae6c7..4ec4c08 100644
--- a/src/commands/dev/setLevel.ts
+++ b/src/commands/dev/setLevel.ts
@@ -54,10 +54,12 @@ export default class SetLevelCommand extends BushCommand {
const [levelEntry] = await Level.findOrBuild({
where: {
- id: user.id
+ user: user.id,
+ guild: message.guild.id
},
defaults: {
- id: user.id
+ user: user.id,
+ guild: message.guild.id
}
});
await levelEntry.update({ xp: Level.convertLevelToXp(level) });
diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts
new file mode 100644
index 0000000..d53e500
--- /dev/null
+++ b/src/commands/dev/sh.ts
@@ -0,0 +1,84 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import chalk from 'chalk';
+import { exec } from 'child_process';
+import { Constants } from 'discord-akairo';
+import { MessageEmbed, Util } from 'discord.js';
+import { promisify } from 'util';
+
+const sh = promisify(exec);
+const clean = (text) => {
+ chalk.toString;
+ if (typeof text === 'string') {
+ return (text = Util.cleanCodeBlockContent(text));
+ } else return text;
+};
+export default class ShCommand extends BushCommand {
+ public constructor() {
+ super('sh', {
+ aliases: ['sh', 'shell', 'cmd'],
+ category: 'dev',
+ description: {
+ content: 'Run shell commands.',
+ usage: 'sh <command>',
+ examples: ['sh git pull']
+ },
+ args: [
+ {
+ id: 'command',
+ type: Constants.ArgumentTypes.STRING,
+ match: Constants.ArgumentMatches.REST,
+ prompt: {
+ start: 'What would you like run',
+ retry: '{error} Invalid command to run.'
+ }
+ }
+ ],
+ ownerOnly: true
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage, { command }: { command: string }): Promise<unknown> {
+ if (!this.client.config.owners.includes(message.author.id))
+ return await message.util.reply(`${this.client.util.emojis.error} Only my developers can run this command.`);
+ const input = clean(command);
+
+ const embed = new MessageEmbed()
+ .setColor(this.client.util.colors.gray)
+ .setFooter(message.author.tag, message.author.avatarURL({ dynamic: true }))
+ .setTimestamp()
+ .setTitle('Shell Command')
+ .addField('📥 Input', await this.client.util.codeblock(input, 1024, 'sh'))
+ .addField('Running', this.client.util.emojis.loading);
+
+ await message.util.reply({ embeds: [embed] });
+
+ const pattern = [
+ '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
+ '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
+ ].join('|');
+ function strip(abc: string): string {
+ return abc.replace(new RegExp(pattern, 'g'), '');
+ }
+ try {
+ const output = await sh(command);
+ const stdout = strip(clean(output.stdout));
+ const stderr = strip(clean(output.stderr));
+
+ embed
+ .setTitle(`${this.client.util.emojis.successFull} Executed command successfully.`)
+ .setColor(this.client.util.colors.success)
+ .spliceFields(1, 1);
+
+ if (stdout) embed.addField('📤 stdout', await this.client.util.codeblock(stdout, 1024, 'json'));
+ if (stderr) embed.addField('📤 stderr', await this.client.util.codeblock(stderr, 1024, 'json'));
+ } catch (e) {
+ embed
+ .setTitle(`${this.client.util.emojis.errorFull} An error occurred while executing.`)
+ .setColor(this.client.util.colors.error)
+ .spliceFields(1, 1);
+
+ embed.addField('📤 Output', await this.client.util.codeblock(e?.stack, 1024, 'js'));
+ }
+ await message.util.edit({ embeds: [embed] });
+ }
+}
diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts
index 4db20e7..45c2545 100644
--- a/src/commands/info/color.ts
+++ b/src/commands/info/color.ts
@@ -1,6 +1,15 @@
-import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
-import { Constants } from 'discord-akairo';
-import { ColorResolvable, MessageEmbed } from 'discord.js';
+import { BushCommand, BushGuildMember, BushMessage, BushRole, BushSlashMessage } from '@lib';
+import { Argument } from 'discord-akairo';
+import { ColorResolvable, MessageEmbed, Role } from 'discord.js';
+import { Constructor } from 'tinycolor2';
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const tinycolor: Constructor = require('tinycolor2'); // this is the only way I got it to work consistently
+const isValidTinyColor = (_message: BushMessage, phase: string) => {
+ // if the phase is a number it converts it to hex incase it could be representing a color in decimal
+ const newPhase = Number.isNaN(phase) ? phase : `#${Number(phase).toString(16)}`;
+ return tinycolor(newPhase).isValid() ? newPhase : null;
+};
export default class ColorCommand extends BushCommand {
public constructor() {
@@ -8,18 +17,17 @@ export default class ColorCommand extends BushCommand {
aliases: ['color'],
category: 'info',
description: {
- content: 'See what color a hex code is.',
- usage: 'color <color>',
+ content: 'Find the color of a hex code, user, or role.',
+ usage: 'color <color|role|user>',
examples: ['color #0000FF']
},
args: [
{
id: 'color',
- type: /^#?(?<code>[0-9A-F]{6})$/i,
- match: Constants.ArgumentMatches.PHRASE,
+ type: Argument.union(isValidTinyColor, 'role', 'member'),
prompt: {
- start: 'What color value would you like to see the color of',
- retry: '{error} Choose a valid hex color code.'
+ start: 'What color code, role, or user would you like to find the color of?',
+ retry: '{error} Choose a valid color, role, or member.'
}
}
],
@@ -28,14 +36,27 @@ export default class ColorCommand extends BushCommand {
});
}
+ public removePrefixAndParenthesis(color: string): string{
+ return color.substr(4, color.length-5)
+ }
+
public async exec(
message: BushMessage | BushSlashMessage,
- { color: { match } }: { color: { match: RegExpMatchArray; matches: RegExpMatchArray[] } }
+ args: { color: string | BushRole | BushGuildMember }
): Promise<unknown> {
+ const color =
+ typeof args.color === 'string'
+ ? tinycolor(args.color)
+ : args.color instanceof Role
+ ? tinycolor(args.color.hexColor)
+ : tinycolor(args.color.displayHexColor);
+
const embed = new MessageEmbed()
- .addField('Hex', match.groups.code, false)
- .addField('RGB', this.client.util.hexToRgb(match.groups.code), false)
- .setColor(match.groups.code as ColorResolvable);
+ .addField('» Hexadecimal', color.toHexString())
+ .addField('» Decimal', `${parseInt(color.toHex(), 16)}`)
+ .addField('» HSL', this.removePrefixAndParenthesis(color.toHslString()))
+ .addField('» RGB', this.removePrefixAndParenthesis(color.toRgbString()))
+ .setColor(color.toHex() as ColorResolvable);
return await message.util.reply({ embeds: [embed] });
}
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index 0977c36..a0b03a0 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -47,16 +47,19 @@ export default class HelpCommand extends BushCommand {
args: { command: BushCommand | string; showHidden?: boolean }
): Promise<unknown> {
const prefix = this.client.config.dev ? 'dev ' : message.util.parsed.prefix;
- let ButtonRow: MessageActionRow;
- if (!this.client.config.dev) {
- ButtonRow = new MessageActionRow().addComponents(
- new MessageButton({
- style: 'LINK',
- label: 'Invite Me',
- url: `https://discord.com/api/oauth2/authorize?client_id=${this.client.user.id}&permissions=2147483647&scope=bot%20applications.commands`
- })
- );
- }
+ const components =
+ !this.client.config.dev || !this.client.guilds.cache.some((guild) => guild.ownerId === message.author.id)
+ ? [
+ new MessageActionRow().addComponents(
+ new MessageButton({
+ style: 'LINK',
+ label: 'Invite Me',
+ url: `https://discord.com/api/oauth2/authorize?client_id=${this.client.user.id}&permissions=2147483647&scope=bot%20applications.commands`
+ })
+ )
+ ]
+ : undefined;
+
const isOwner = this.client.isOwner(message.author);
const isSuperUser = this.client.isSuperUser(message.author);
const command = args.command
@@ -65,20 +68,21 @@ export default class HelpCommand extends BushCommand {
: args.command
: null;
if (!isOwner) args.showHidden = false;
- if (!command) {
+ if (!command || command.completelyHide) {
const embed = new MessageEmbed().setColor(this.client.util.colors.default).setTimestamp();
if (message.guild) {
- embed.setFooter(`For more information about a command use '${prefix}help <command>'`);
+ embed.setFooter(`For more information about a command use ${prefix}help <command>`);
}
for (const [, category] of this.handler.categories) {
const categoryFilter = category.filter((command) => {
+ if (command.completelyHide) return false;
if (command.hidden && !args.showHidden) return false;
if (command.channel == 'guild' && !message.guild && !args.showHidden) return false;
if (command.ownerOnly && !isOwner) return false;
if (command.superUserOnly && !isSuperUser) {
return false;
}
- return !(command.restrictedGuilds?.includes(message.guild.id) == false && !args.showHidden);
+ return !(command.restrictedGuilds?.includes(message.guild.id) === false && !args.showHidden);
});
const categoryNice = category.id
.replace(/(\b\w)/gi, (lc): string => lc.toUpperCase())
@@ -90,24 +94,24 @@ export default class HelpCommand extends BushCommand {
embed.addField(`${categoryNice}`, `${categoryCommands.join(' ')}`);
}
}
- return await message.util.reply({ embeds: [embed], components: ButtonRow ? [ButtonRow] : undefined });
+ return await message.util.reply({ embeds: [embed], components });
}
const embed = new MessageEmbed()
.setColor(this.client.util.colors.default)
- .setTitle(`\`${command.description?.usage ? command.description.usage : 'This command does not have usages.'}\``)
+ .setTitle(`\`${command.description?.usage || `${this.client.util.emojis.error} This command does not have usages.`}\``)
.addField(
'Description',
- `${command.description?.content ? command.description.content : '*This command does not have a description.*'} ${
- command.ownerOnly ? '\n__Dev Only__' : ''
+ `${command.description?.content || `${this.client.util.emojis.error} This command does not have a description.`} ${
+ command.ownerOnly ? '\n__Developer Only__' : ''
} ${command.superUserOnly ? '\n__Super User Only__' : ''}`
);
if (command.aliases?.length > 1) embed.addField('Aliases', `\`${command.aliases.join('` `')}\``, true);
- if (command.description?.examples && command.description.examples.length) {
+ if (command.description?.examples?.length) {
embed.addField('Examples', `\`${command.description.examples.join('`\n`')}\``, true);
}
- return await message.util.reply({ embeds: [embed], components: ButtonRow ? [ButtonRow] : undefined });
+ return await message.util.reply({ embeds: [embed], components });
}
}
diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts
index 50756a6..5e70323 100644
--- a/src/commands/info/userInfo.ts
+++ b/src/commands/info/userInfo.ts
@@ -78,7 +78,7 @@ export default class UserInfoCommand extends BushCommand {
if (user.premiumSinceTimestamp) emojis.push(this.client.consts.mappings.otherEmojis.BOOSTER);
const createdAt = user.user.createdAt.toLocaleString(),
- createdAtDelta = moment(user.user.createdAt).diff(moment()).toLocaleString(),
+ createdAtDelta = moment(moment(user.user.createdAt).diff(moment())).toLocaleString(),
joinedAt = user.joinedAt?.toLocaleString(),
joinedAtDelta = moment(user.joinedAt)?.diff(moment()).toLocaleString(),
premiumSince = user.premiumSince?.toLocaleString(),
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index c5833fc..874d5ed 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -37,6 +37,11 @@ export default class BanCommand extends BushCommand {
match: 'option',
type: Argument.range('integer', 0, 7, true),
default: 0
+ },
+ {
+ id: 'force',
+ flag: '--force',
+ match: 'flag'
}
],
slash: true,
@@ -77,10 +82,16 @@ export default class BanCommand extends BushCommand {
}
async exec(
message: BushMessage | BushSlashMessage,
- { user, reason, days }: { user: User; reason?: { duration: number; contentWithoutTime: string }; days?: number }
+ {
+ user,
+ reason,
+ days,
+ force
+ }: { user: User; reason?: { duration: number; contentWithoutTime: string }; days?: number; force: boolean }
): Promise<unknown> {
const member = message.guild.members.cache.get(user.id) as BushGuildMember;
- const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'ban');
+ const useForce = force && message.author.isOwner();
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'ban', true, useForce);
if (canModerateResponse !== true) {
return message.util.reply(canModerateResponse);
diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts
index ccf35a9..74ace94 100644
--- a/src/commands/moderation/kick.ts
+++ b/src/commands/moderation/kick.ts
@@ -28,6 +28,11 @@ export default class KickCommand extends BushCommand {
retry: '{error} Choose a valid kick reason.',
optional: true
}
+ },
+ {
+ id: 'force',
+ flag: '--force',
+ match: 'flag'
}
],
slash: true,
@@ -50,9 +55,13 @@ export default class KickCommand extends BushCommand {
});
}
- async exec(message: BushMessage | BushSlashMessage, { user, reason }: { user: BushUser; reason?: string }): Promise<unknown> {
+ async exec(
+ message: BushMessage | BushSlashMessage,
+ { user, reason, force }: { user: BushUser; reason?: string; force: boolean }
+ ): Promise<unknown> {
const member = message.guild.members.cache.get(user.id) as BushGuildMember;
- const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'kick');
+ const useForce = force && message.author.isOwner();
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'kick', true, useForce);
if (canModerateResponse !== true) {
return message.util.reply(canModerateResponse);
diff --git a/src/commands/moderation/lockdown.ts b/src/commands/moderation/lockdown.ts
new file mode 100644
index 0000000..e67d110
--- /dev/null
+++ b/src/commands/moderation/lockdown.ts
@@ -0,0 +1,45 @@
+import { BushCommand, BushMessage, BushNewsChannel, BushSlashMessage, BushTextChannel } from '@lib';
+
+export default class LockdownCommand extends BushCommand {
+ public constructor() {
+ super('lockdown', {
+ aliases: ['lockdown', 'unlockdown'],
+ category: 'moderation',
+ description: {
+ content: 'Allows you to lockdown a channel or all configured channels..',
+ usage: 'lockdown [--all]',
+ examples: ['lockdown', 'lockdown --all']
+ },
+ args: [
+ {
+ id: 'all',
+ type: 'flag',
+ flag: '--all'
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'all',
+ description: 'Would you like to lockdown all channels?',
+ type: 'BOOLEAN',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+ public async exec(message: BushMessage | BushSlashMessage, { all }: { all: boolean }): Promise<unknown> {
+ return await message.util.reply('no');
+ if (!all) {
+ if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(message.channel.type))
+ return message.util.reply(`${this.client.util.emojis.error} You can only lock down text and announcement channels.`);
+ const lockdownSuccess = await this.client.util.lockdownChannel({
+ channel: message.channel as BushTextChannel | BushNewsChannel,
+ moderator: message.author
+ });
+ }
+ }
+}
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index 4b57a4f..e3da45f 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -72,7 +72,7 @@ export default class ModlogCommand extends BushCommand {
color: this.client.util.colors.default
})
);
- this.client.util.buttonPaginate(message, embedPages, '', true);
+ return await this.client.util.buttonPaginate(message, embedPages, '', true);
} else if (search) {
const entry = await ModLog.findByPk(search as string);
if (!entry) return message.util.send(`${this.client.util.emojis.error} That modlog does not exist.`);
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
index 7443c55..2004eb8 100644
--- a/src/commands/moderation/mute.ts
+++ b/src/commands/moderation/mute.ts
@@ -29,6 +29,11 @@ export default class MuteCommand extends BushCommand {
retry: '{error} Choose a valid mute reason and duration.',
optional: true
}
+ },
+ {
+ id: 'force',
+ flag: '--force',
+ match: 'flag'
}
],
slash: true,
@@ -53,11 +58,12 @@ export default class MuteCommand extends BushCommand {
}
async exec(
message: BushMessage | BushSlashMessage,
- { user, reason }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string } }
+ { user, reason, force }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string }; force: boolean }
): 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, 'mute');
+ const useForce = force && message.author.isOwner();
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'mute', true, useForce);
const victimBoldTag = `**${member.user.tag}**`;
if (canModerateResponse !== true) {
diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts
index 3d353ca..d5bf009 100644
--- a/src/commands/moderation/warn.ts
+++ b/src/commands/moderation/warn.ts
@@ -28,6 +28,11 @@ export default class WarnCommand extends BushCommand {
retry: '{error} Choose a valid warn reason.',
optional: true
}
+ },
+ {
+ id: 'force',
+ flag: '--force',
+ match: 'flag'
}
],
slash: true,
@@ -52,10 +57,11 @@ export default class WarnCommand extends BushCommand {
}
public async exec(
message: BushMessage | BushSlashMessage,
- { user, reason }: { user: BushUser; reason: string }
+ { user, reason, force }: { user: BushUser; reason: string; force: boolean }
): Promise<unknown> {
const member = message.guild.members.cache.get(user.id) as BushGuildMember;
- const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'warn');
+ const useForce = force && message.author.isOwner();
+ const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'warn', true, useForce);
const victimBoldTag = `**${member.user.tag}**`;
if (canModerateResponse !== true) {
diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts
index b19d3bc..08b42ff 100644
--- a/src/commands/moulberry-bush/capePerms.ts
+++ b/src/commands/moulberry-bush/capePerms.ts
@@ -4,34 +4,6 @@ import { MessageEmbed } from 'discord.js';
import got from 'got';
export default class CapePermissionsCommand extends BushCommand {
- private nameMap = {
- patreon1: 'Patreon Tier 1',
- patreon2: 'Patreon Tier 2',
- fade: 'Fade',
- contrib: 'Contributor',
- nullzee: 'Patreon Tier 1',
- gravy: 'Patreon Tier 1',
- space: 'Patreon Tier 1',
- mcworld: 'Patreon Tier 1',
- lava: 'Patreon Tier 1',
- packshq: 'Patreon Tier 1',
- mbstaff: 'Patreon Tier 1',
- thebakery: 'Patreon Tier 1',
- negative: 'Patreon Tier 1',
- void: 'Patreon Tier 1',
- ironmoon: 'Patreon Tier 1',
- krusty: 'Patreon Tier 1',
- furf: 'Patreon Tier 1',
- soldier: 'Patreon Tier 1',
- dsm: 'Patreon Tier 1',
- zera: 'Patreon Tier 1',
- tunnel: 'Patreon Tier 1',
- alexxoffi: 'Patreon Tier 1',
- parallax: 'Patreon Tier 1',
- jakethybro: 'Patreon Tier 1',
- planets: 'Patreon Tier 1'
- };
-
public constructor() {
super('capepermissions', {
aliases: ['capeperms', 'capeperm', 'capepermissions'],
@@ -80,7 +52,7 @@ export default class CapePermissionsCommand extends BushCommand {
let capeperms: Capeperms, uuid: string;
try {
- uuid = await this.client.util.mcUUID(args.ign);
+ uuid = await this.client.util.findUUID(args.ign);
} catch (e) {
return await message.util.reply(
`${this.client.util.emojis.error} \`${args.ign}\` doesn't appear to be a valid username.`
@@ -88,7 +60,7 @@ export default class CapePermissionsCommand extends BushCommand {
}
try {
- capeperms = await got.get('https://moulberry.codes/permscapes.json').json();
+ capeperms = await got.get('http://moulberry.codes/permscapes.json').json();
} catch (error) {
capeperms = null;
}
@@ -105,6 +77,7 @@ export default class CapePermissionsCommand extends BushCommand {
index = i;
break;
}
+ continue;
}
if (index == null)
return await message.util.reply(
diff --git a/src/commands/moulberry-bush/level.ts b/src/commands/moulberry-bush/level.ts
index fc1e93e..86ab985 100644
--- a/src/commands/moulberry-bush/level.ts
+++ b/src/commands/moulberry-bush/level.ts
@@ -1,4 +1,4 @@
-import { BushCommand, BushMessage, BushSlashMessage, BushUser, Level } from '@lib';
+import { BushCommand, BushGuild, BushMessage, BushSlashMessage, BushUser, Level } from '@lib';
/*
import canvas from 'canvas';
import { MessageAttachment } from 'discord.js';
@@ -127,8 +127,8 @@ export default class LevelCommand extends BushCommand {
return image.toBuffer();
} */
- private async getResponse(user: BushUser): Promise<string> {
- const userLevelRow = await Level.findByPk(user.id);
+ private async getResponse(user: BushUser, guild: BushGuild): Promise<string> {
+ const userLevelRow = await Level.findOne({ where: { user: user.id, guild: guild.id } });
if (userLevelRow) {
return `${user ? `${user.tag}'s` : 'Your'} level is ${userLevelRow.level} (${userLevelRow.xp} XP)`;
} else {
@@ -143,6 +143,6 @@ export default class LevelCommand extends BushCommand {
// 'lel.png'
// )
// );
- await message.reply(await this.getResponse(user || message.author));
+ await message.reply(await this.getResponse(user || message.author, message.guild));
}
}
diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts
new file mode 100644
index 0000000..ebc8f1d
--- /dev/null
+++ b/src/commands/moulberry-bush/report.ts
@@ -0,0 +1,121 @@
+import { Constants } from 'discord-akairo';
+import { GuildMember, MessageEmbed, TextChannel } from 'discord.js';
+import moment from 'moment';
+import { AllowedMentions, BushCommand, BushMessage } from '../../lib';
+
+export default class ReportCommand extends BushCommand {
+ public constructor() {
+ super('report', {
+ aliases: ['report'],
+ category: "Moulberry's Bush",
+ description: {
+ content: 'A command to report a user..',
+ usage: 'report <user> <reason/evidence>',
+ examples: ['report IRONM00N']
+ },
+ args: [
+ {
+ id: 'member',
+ type: Constants.ArgumentTypes.MEMBER,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'Who would you like to report?',
+ retry: `{error} Choose a valid user to report.`,
+ optional: false
+ }
+ },
+ {
+ id: 'evidence',
+ type: Constants.ArgumentTypes.STRING,
+ match: Constants.ArgumentMatches.REST,
+ prompt: {
+ start: 'What evidence do you have?',
+ retry: `{error} Provide what did they do wrong.`,
+ optional: true
+ }
+ }
+ ],
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ channel: 'guild',
+ restrictedGuilds: ['516977525906341928'],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'user',
+ description: 'The user you would like to report.',
+ type: 'USER',
+ required: true
+ },
+ {
+ name: 'evidence',
+ description: 'What did the user do wrong?',
+ type: 'STRING',
+ required: false
+ }
+ ],
+ slashGuilds: ['516977525906341928']
+ });
+ }
+
+ public async exec(message: BushMessage, { member, evidence }: { member: GuildMember; evidence: string }): Promise<unknown> {
+ if (message.guild.id != this.client.consts.mappings.guilds.bush)
+ return await message.util.reply(`${this.client.util.emojis.error} This command can only be run in Moulberry's bush.`);
+ if (!member) return await message.util.reply(`${this.client.util.emojis.error} Choose someone to report`);
+ if (member.user.id === '322862723090219008')
+ return await message.util.reply({
+ content: `Thank you for your report! We take these allegations very seriously and have reported <@${member.user.id}> to the FBI!`,
+ allowedMentions: AllowedMentions.none()
+ });
+ if (member.user.bot)
+ return await message.util.reply(
+ `${this.client.util.emojis.error} You cannot report a bot <:WeirdChamp:756283321301860382>.`
+ );
+
+ //// if (!evidence) evidence = 'No Evidence.';
+ //todo: Add channel id to db instead of hard coding it & allow in any guild
+ //The formatting of the report is mostly copied from carl since it is pretty good when it actually works
+ const reportEmbed = new MessageEmbed()
+ .setFooter(`Reporter ID: ${message.author.id} Reported ID: ${member.user.id}`)
+ .setTimestamp()
+ .setAuthor(`Report From: ${message.author.tag}`, message.author.avatarURL({ dynamic: true }))
+ .setTitle('New Report')
+ .setColor(this.client.util.colors.red)
+ .setDescription(evidence)
+ .addField(
+ 'Reporter',
+ `**Name:**${message.author.tag} <@${message.author.id}>\n**Joined:** ${moment(
+ message.member.joinedTimestamp
+ ).fromNow()}\n**Created:** ${moment(message.author.createdTimestamp).fromNow()}\n**Sent From**: <#${
+ message.channel.id
+ }> [Jump to context](${message.url})`,
+ true
+ )
+ .addField(
+ 'Reported User',
+ `**Name:**${member.user.tag} <@${member.user.id}>\n**Joined:** ${moment(
+ member.joinedTimestamp
+ ).fromNow()}\n**Created:** ${moment(member.user.createdTimestamp).fromNow()}`,
+ true
+ );
+
+ //reusing code pog
+ if (message.attachments.size > 0) {
+ const fileName = message.attachments.first().name.toLowerCase();
+ if (fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.gif') || fileName.endsWith('.webp')) {
+ reportEmbed.setImage(message.attachments.first().url);
+ } else {
+ reportEmbed.addField('Attachment', message.attachments.first().url);
+ }
+ }
+ const reportChannel = <TextChannel>this.client.channels.cache.get('782972723654688848');
+ await reportChannel.send({ embeds: [reportEmbed] }).then(async (ReportMessage) => {
+ try {
+ await ReportMessage.react(this.client.util.emojis.success);
+ await ReportMessage.react(this.client.util.emojis.error);
+ } catch {
+ this.client.console.warn('ReportCommand', 'Could not react to report message.');
+ }
+ });
+ return await message.util.reply('Successfully made a report.');
+ }
+}
diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts
index a2b8c78..1681a1b 100644
--- a/src/commands/moulberry-bush/rule.ts
+++ b/src/commands/moulberry-bush/rule.ts
@@ -1,6 +1,6 @@
-import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib';
import { Argument, Constants } from 'discord-akairo';
import { MessageEmbed, User } from 'discord.js';
+import { AllowedMentions, BushCommand, BushMessage } from '../../lib';
const rules = [
{
@@ -106,10 +106,7 @@ export default class RuleCommand extends BushCommand {
});
}
- public async exec(
- message: BushMessage | BushSlashMessage,
- { rule, user }: { rule: undefined | number; user: User }
- ): Promise<unknown> {
+ public async exec(message: BushMessage, { 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 }))
@@ -133,19 +130,21 @@ export default class RuleCommand extends BushCommand {
return;
async function respond(): Promise<unknown> {
if (!user) {
- // 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() });
+ 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() })
+ );
} else {
- return (message as BushMessage).reference?.messageId && !message.util.isSlash
+ return message.reference?.messageId && !message.util.isSlash
? await message.util.send({
content: `<@!${user.id}>`,
embeds: [rulesEmbed],
allowedMentions: AllowedMentions.users(),
- reply: { messageReference: (message as BushMessage).reference.messageId }
+ reply: { messageReference: message.reference.messageId }
})
: await message.util.send({
content: `<@!${user.id}>`,
diff --git a/src/commands/skyblockReborn/chooseColorCommand.ts b/src/commands/skyblock-reborn/chooseColorCommand.ts
index ce70419..ce70419 100644
--- a/src/commands/skyblockReborn/chooseColorCommand.ts
+++ b/src/commands/skyblock-reborn/chooseColorCommand.ts
diff --git a/src/commands/utilities/_whoHasRole.ts b/src/commands/utilities/_whoHasRole.ts
deleted file mode 100644
index e69de29..0000000
--- a/src/commands/utilities/_whoHasRole.ts
+++ /dev/null
diff --git a/src/commands/utilities/hash.ts b/src/commands/utilities/hash.ts
new file mode 100644
index 0000000..4b5b01c
--- /dev/null
+++ b/src/commands/utilities/hash.ts
@@ -0,0 +1,42 @@
+import crypto from 'crypto';
+import { Constants } from 'discord-akairo';
+import got from 'got';
+import { BushCommand, BushMessage } from '../../lib';
+
+export default class HashCommand extends BushCommand {
+ constructor() {
+ super('hash', {
+ aliases: ['hash'],
+ category: 'utilities',
+ description: {
+ content: 'Gets the file hash of the given discord link',
+ usage: 'hash <file url>',
+ examples: ['hash https://cdn.discordapp.com/emojis/782630946435366942.png?v=1'] //nice
+ },
+ args: [
+ {
+ id: 'url',
+ type: Constants.ArgumentTypes.URL,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What url would you like to find the hash of?',
+ retry: '{error} Enter a valid url.'
+ }
+ }
+ ],
+ clientPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public async exec(message: BushMessage, { url }: { url: string }): Promise<void> {
+ try {
+ const req = await got.get(url);
+ const rawHash = crypto.createHash('md5');
+ rawHash.update(req.rawBody.toString('binary'));
+ const hash = rawHash.digest('hex');
+ await message.util.reply(`\`${hash}\``);
+ } catch {
+ await message.util.reply('Unable to calculate hash.');
+ }
+ }
+}
diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts
new file mode 100644
index 0000000..231930c
--- /dev/null
+++ b/src/commands/utilities/price.ts
@@ -0,0 +1,220 @@
+import { Constants } from 'discord-akairo';
+import { ColorResolvable, MessageEmbed } from 'discord.js';
+import Fuse from 'fuse.js';
+import got from 'got';
+import { BushCommand, BushMessage } from '../../lib';
+
+interface Summary {
+ amount: number;
+ pricePerUnit: number;
+ orders: number;
+}
+
+interface Bazaar {
+ success: boolean;
+ lastUpdated: number;
+ products: {
+ [key: string]: {
+ product_id: string;
+ sell_summary: Summary[];
+ buy_summary: Summary[];
+ quick_status: {
+ productId: string;
+ sellPrice: number;
+ sellVolume: number;
+ sellMovingWeek: number;
+ sellOrders: number;
+ buyPrice: number;
+ buyVolume: number;
+ buyMovingWeek: number;
+ buyOrders: number;
+ };
+ };
+ };
+}
+
+interface LowestBIN {
+ [key: string]: number;
+}
+
+interface AuctionAverages {
+ [key: string]: {
+ price?: number;
+ count?: number;
+ sales?: number;
+ clean_price?: number;
+ clean_sales?: number;
+ };
+}
+
+export default class PriceCommand extends BushCommand {
+ public constructor() {
+ super('price', {
+ aliases: ['price'],
+ category: 'utilities',
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ description: {
+ usage: 'price <item id>',
+ examples: ['price ASPECT_OF_THE_END'],
+ content: 'Finds the price information of an item.'
+ },
+ ratelimit: 4,
+ cooldown: 4000,
+ typing: true,
+ args: [
+ {
+ id: 'item',
+ match: Constants.ArgumentMatches.CONTENT,
+ type: Constants.ArgumentTypes.STRING,
+ prompt: {
+ start: 'What item would you like to find the price of?',
+ retry: '{error} Choose a valid item.'
+ }
+ },
+ {
+ id: 'strict',
+ match: Constants.ArgumentMatches.FLAG,
+ flag: '--strict',
+ default: false
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'item',
+ description: 'The item that you would you like to find the price of.',
+ type: 'STRING',
+ required: true
+ },
+ {
+ name: 'strict',
+ description: 'Whether or not to bypass the fuzzy search.',
+ type: 'BOOLEAN',
+ required: false
+ }
+ ]
+ });
+ }
+
+ public async exec(message: BushMessage, { item, strict }: { item: string; strict: boolean }): Promise<unknown> {
+ const errors = new Array<string>();
+ const bazaar: Bazaar = await get('https://api.hypixel.net/skyblock/bazaar').catch(() => errors.push('bazaar'));
+ const currentLowestBIN: LowestBIN = await get('https://moulberry.codes/lowestbin.json').catch(() =>
+ errors.push('current lowest BIN')
+ );
+ const averageLowestBIN: LowestBIN = await get('https://moulberry.codes/auction_averages_lbin/3day.json').catch(() =>
+ errors.push('average Lowest BIN')
+ );
+ const auctionAverages: AuctionAverages = await get('https://moulberry.codes/auction_averages/3day.json').catch(() =>
+ errors.push('auction average')
+ );
+ // adds _ to item name
+ let parsedItem = item.toString().toUpperCase().replace(/ /g, '_').replace(/'S/g, '');
+ const priceEmbed = new MessageEmbed();
+
+ if (errors?.length) {
+ priceEmbed.setFooter;
+ }
+
+ //combines all the item names from each
+ const itemNames = Array.from(
+ new Set(
+ (averageLowestBIN ? Object.keys(averageLowestBIN) : []).concat(
+ currentLowestBIN ? Object.keys(currentLowestBIN) : [],
+ auctionAverages ? Object.keys(auctionAverages) : [],
+ bazaar?.products ? Object.keys(bazaar.products) : []
+ )
+ )
+ );
+
+ // fuzzy search
+ if (!strict) {
+ parsedItem = new Fuse(itemNames)?.search(parsedItem)[0]?.item;
+ }
+
+ // If bazaar item then it there should not be any ah data
+ if (bazaar['products'][parsedItem]) {
+ const bazaarPriceEmbed = new MessageEmbed()
+ .setColor(
+ errors?.length
+ ? (this.client.util.emojis.warn as ColorResolvable)
+ : (this.client.util.colors.success as ColorResolvable)
+ )
+ .setTitle(`Bazaar Information for \`${parsedItem}\``)
+ .addField('Sell Price', Bazaar('sellPrice', 2, true))
+ .addField('Buy Price', Bazaar('buyPrice', 2, true))
+ .addField('Margin', (Number(Bazaar('buyPrice', 2, false)) - Number(Bazaar('sellPrice', 2, false))).toLocaleString())
+ .addField('Current Sell Orders', Bazaar('sellOrders', 0, true))
+ .addField('Current Buy Orders', Bazaar('buyOrders', 0, true));
+ return await message.util.reply({ embeds: [bazaarPriceEmbed] });
+ }
+
+ // Checks if the item exists in any of the action information otherwise it is not a valid item
+ if (currentLowestBIN?.[parsedItem] || averageLowestBIN?.[parsedItem] || auctionAverages?.[parsedItem]) {
+ priceEmbed
+ .setColor(this.client.util.colors.success)
+ .setTitle(`Price Information for \`${parsedItem}\``)
+ .setFooter('All information is based on the last 3 days.');
+ } else {
+ const errorEmbed = new MessageEmbed();
+ errorEmbed
+ .setColor(this.client.util.colors.error)
+ .setDescription(
+ `${this.client.util.emojis.error} \`${parsedItem}\` is not a valid item id, or it has no auction data.`
+ );
+ return await message.util.reply({ embeds: [errorEmbed] });
+ }
+
+ if (currentLowestBIN?.[parsedItem]) {
+ const currentLowestBINPrice = currentLowestBIN[parsedItem].toLocaleString();
+ priceEmbed.addField('Current Lowest BIN', currentLowestBINPrice);
+ }
+ if (averageLowestBIN?.[parsedItem]) {
+ const averageLowestBINPrice = averageLowestBIN[parsedItem].toLocaleString();
+ priceEmbed.addField('Average Lowest BIN', averageLowestBINPrice);
+ }
+ if (auctionAverages?.[parsedItem]?.price) {
+ const auctionAveragesPrice = auctionAverages[parsedItem].price.toLocaleString();
+ priceEmbed.addField('Average Auction Price', auctionAveragesPrice);
+ }
+ if (auctionAverages?.[parsedItem]?.count) {
+ const auctionAveragesCountPrice = auctionAverages[parsedItem].count.toLocaleString();
+ priceEmbed.addField('Average Auction Count', auctionAveragesCountPrice);
+ }
+ if (auctionAverages?.[parsedItem]?.sales) {
+ const auctionAveragesSalesPrice = auctionAverages[parsedItem].sales.toLocaleString();
+ priceEmbed.addField('Average Auction Sales', auctionAveragesSalesPrice);
+ }
+ if (auctionAverages?.[parsedItem]?.clean_price) {
+ const auctionAveragesCleanPrice = auctionAverages[parsedItem].clean_price.toLocaleString();
+ priceEmbed.addField('Average Auction Clean Price', auctionAveragesCleanPrice);
+ }
+ if (auctionAverages?.[parsedItem]?.clean_sales) {
+ const auctionAveragesCleanSales = auctionAverages[parsedItem].clean_sales.toLocaleString();
+ priceEmbed.addField('Average Auction Clean Sales', auctionAveragesCleanSales);
+ }
+ return await message.util.reply({ embeds: [priceEmbed] });
+
+ //Helper functions
+ function Bazaar(Information: string, digits: number, commas: boolean): string {
+ const price = bazaar?.products[parsedItem]?.quick_status?.[Information];
+ const a = Number(Number(price).toFixed(digits));
+ return commas ? a?.toLocaleString() : a?.toString();
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ async function get(url: string): Promise<any> {
+ const data = await got.get(url).catch((error) => {
+ this.client.console.warn('PriceCommand', `There was an problem fetching data from <<${url}>> with error:\n${error}`);
+ throw 'Error Fetching price data';
+ });
+ try {
+ const json = JSON.parse(data.body);
+ return json;
+ } catch (error) {
+ this.client.console.warn('PriceCommand', `There was an problem parsing data from <<${url}>> with error:\n${error}`);
+ throw 'json error';
+ }
+ }
+ }
+}
diff --git a/src/commands/utilities/viewraw.ts b/src/commands/utilities/viewraw.ts
index 7642b2a..3658bde 100644
--- a/src/commands/utilities/viewraw.ts
+++ b/src/commands/utilities/viewraw.ts
@@ -7,7 +7,7 @@ export default class ViewRawCommand extends BushCommand {
public constructor() {
super('viewraw', {
aliases: ['viewraw'],
- category: 'info',
+ category: 'utilities',
clientPermissions: ['EMBED_LINKS'],
description: {
usage: 'viewraw <message id> <channel>',
diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts
new file mode 100644
index 0000000..1828c95
--- /dev/null
+++ b/src/commands/utilities/whoHasRole.ts
@@ -0,0 +1,53 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { MessageEmbed, Role, Util } from 'discord.js';
+
+export default class WhoHasRoleCommand extends BushCommand {
+ public constructor() {
+ super('whohasrole', {
+ aliases: ['whohasrole'],
+ category: 'utilities',
+ description: {
+ content: 'Allows you to view what users have a certain role.',
+ usage: 'template <requiredArg> [optionalArg]',
+ examples: ['template 1 2']
+ },
+ args: [
+ {
+ id: 'role',
+ type: 'role',
+ prompt: {
+ start: 'What role would you like to find the users of?',
+ retry: '{error} Pick a valid role.',
+ optional: false
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'role',
+ description: 'What role would you like to find the users of?',
+ type: 'ROLE',
+ required: true
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+ public async exec(message: BushMessage | BushSlashMessage, args: { role: Role }): Promise<unknown> {
+ const roleMembers = args.role.members.map((member) => `${member.user} (${Util.escapeMarkdown(member.user.tag)})`);
+
+ const chunkedRoleMembers = this.client.util.chunk(roleMembers, 30);
+ const embedPages = chunkedRoleMembers.map(
+ (chunk) =>
+ new MessageEmbed({
+ title: `${args.role.name}'s Members`,
+ description: chunk.join('\n'),
+ color: this.client.util.colors.default
+ })
+ );
+ return await this.client.util.buttonPaginate(message, embedPages, null, true);
+ }
+}