aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/config/autoPublishChannel.ts53
-rw-r--r--src/commands/config/muteRole.ts7
-rw-r--r--src/commands/config/prefix.ts7
-rw-r--r--src/commands/config/punishmentFooter.ts7
-rw-r--r--src/commands/config/welcomeChannel.ts7
-rw-r--r--src/commands/dev/_testDuration.ts53
-rw-r--r--src/commands/dev/say.ts58
-rw-r--r--src/commands/dev/setLevel.ts6
-rw-r--r--src/commands/dev/testDuration.ts53
-rw-r--r--src/commands/fun/coinflip.ts26
-rw-r--r--src/commands/fun/dice.ts23
-rw-r--r--src/commands/fun/eightBall.ts64
-rw-r--r--src/commands/fun/minesweeper.ts123
-rw-r--r--src/commands/info/avatar.ts51
-rw-r--r--src/commands/info/botInfo.ts2
-rw-r--r--src/commands/info/color.ts42
-rw-r--r--src/commands/info/guildInfo.ts171
-rw-r--r--src/commands/info/help.ts6
-rw-r--r--src/commands/info/icon.ts34
-rw-r--r--src/commands/info/invite.ts33
-rw-r--r--src/commands/info/ping.ts4
-rw-r--r--src/commands/info/snowflakeInfo.ts151
-rw-r--r--src/commands/info/userInfo.ts160
-rw-r--r--src/commands/moderation/modlog.ts12
-rw-r--r--src/commands/moderation/removeReactionEmoji.ts64
-rw-r--r--src/commands/moderation/slowmode.ts87
-rw-r--r--src/commands/skyblockReborn/chooseColorCommand.ts179
-rw-r--r--src/commands/utilities/_whoHasRole.ts (renamed from src/commands/utilities/whoHasRole.ts)0
-rw-r--r--src/commands/utilities/decode.ts131
-rw-r--r--src/commands/utilities/serverStatus.ts58
-rw-r--r--src/commands/utilities/uuid.ts43
-rw-r--r--src/commands/utilities/viewraw.ts75
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts2
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts43
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts24
-rw-r--r--src/lib/models/Guild.ts8
-rw-r--r--src/lib/utils/BushConstants.ts3
-rw-r--r--src/listeners/commands/commandMissingPermissions.ts13
38 files changed, 1772 insertions, 111 deletions
diff --git a/src/commands/config/autoPublishChannel.ts b/src/commands/config/autoPublishChannel.ts
new file mode 100644
index 0000000..421e030
--- /dev/null
+++ b/src/commands/config/autoPublishChannel.ts
@@ -0,0 +1,53 @@
+import { BushCommand, BushMessage } from '@lib';
+import { Channel } from 'discord.js';
+
+export default class AutoPublishChannelCommand extends BushCommand {
+ public constructor() {
+ super('autopublishchannel', {
+ aliases: ['autopublishchannel', 'apc', 'publishchannel', 'autopublishchannels', 'publishchannels', 'autopublish'],
+ category: 'config',
+ description: {
+ content: 'A command to add/remove channels from being automatically published.',
+ usage: 'autopublishchannel <channel>',
+ examples: ['autopublishchannel #github']
+ },
+ args: [
+ {
+ id: 'channel',
+ type: 'channel',
+ match: 'content',
+ prompt: {
+ start: 'What channel would you like to toggle auto publishing in?',
+ retry: '{error} Choose a valid channel.',
+ optional: false
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'channel',
+ description: 'What channel would you like me to send welcome messages in?',
+ type: 'CHANNEL',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['MANAGE_GUILD', 'SEND_MESSAGES']
+ });
+ }
+
+ 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}>.`
+ );
+ }
+}
diff --git a/src/commands/config/muteRole.ts b/src/commands/config/muteRole.ts
index 1bec4a9..9074a1d 100644
--- a/src/commands/config/muteRole.ts
+++ b/src/commands/config/muteRole.ts
@@ -11,8 +11,6 @@ export default class MuteRoleCommand extends BushCommand {
usage: 'muterole <role>',
examples: ['muterole 748912426581229690']
},
- clientPermissions: ['SEND_MESSAGES'],
- userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
args: [
{
id: 'role',
@@ -32,7 +30,10 @@ export default class MuteRoleCommand extends BushCommand {
type: 'ROLE',
required: true
}
- ]
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD']
});
}
diff --git a/src/commands/config/prefix.ts b/src/commands/config/prefix.ts
index 4624e99..79956be 100644
--- a/src/commands/config/prefix.ts
+++ b/src/commands/config/prefix.ts
@@ -10,8 +10,6 @@ export default class PrefixCommand extends BushCommand {
usage: 'prefix [prefix]',
examples: ['prefix', 'prefix -']
},
- clientPermissions: ['SEND_MESSAGES'],
- userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
args: [
{
id: 'prefix',
@@ -31,7 +29,10 @@ export default class PrefixCommand extends BushCommand {
type: 'STRING',
required: false
}
- ]
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD']
});
}
diff --git a/src/commands/config/punishmentFooter.ts b/src/commands/config/punishmentFooter.ts
index 3a246df..6b2759a 100644
--- a/src/commands/config/punishmentFooter.ts
+++ b/src/commands/config/punishmentFooter.ts
@@ -11,8 +11,6 @@ export default class PunishmentFooterCommand extends BushCommand {
usage: 'punishmentfooter [message]',
examples: ['punishmentfooter', 'prefix you can appeal at https://example.com.']
},
- clientPermissions: ['SEND_MESSAGES'],
- userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
args: [
{
id: 'ending',
@@ -33,7 +31,10 @@ export default class PunishmentFooterCommand extends BushCommand {
type: 'STRING',
required: false
}
- ]
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD']
});
}
diff --git a/src/commands/config/welcomeChannel.ts b/src/commands/config/welcomeChannel.ts
index 71d59f8..488a0a9 100644
--- a/src/commands/config/welcomeChannel.ts
+++ b/src/commands/config/welcomeChannel.ts
@@ -11,8 +11,6 @@ export default class WelcomeChannelCommand extends BushCommand {
usage: 'welcomechannel [channel]',
examples: ['welcomechannel #welcome']
},
- clientPermissions: ['SEND_MESSAGES'],
- userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
args: [
{
id: 'channel',
@@ -32,7 +30,10 @@ export default class WelcomeChannelCommand extends BushCommand {
type: 'CHANNEL',
required: false
}
- ]
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD']
});
}
public async exec(message: BushMessage | BushSlashMessage, args: { channel: Channel }): Promise<unknown> {
diff --git a/src/commands/dev/_testDuration.ts b/src/commands/dev/_testDuration.ts
new file mode 100644
index 0000000..3ad9aff
--- /dev/null
+++ b/src/commands/dev/_testDuration.ts
@@ -0,0 +1,53 @@
+// import { BushCommand, BushSlashMessage } from '@lib';
+// import { stripIndents } from 'common-tags';
+// import { Message } from 'discord.js';
+
+// export default class TestDurationCommand extends BushCommand {
+// public constructor() {
+// super('testduration', {
+// aliases: ['testduration'],
+// category: 'dev',
+// description: {
+// content: 'Tests duration parsing.',
+// usage: 'testduration [reason]',
+// examples: ['testduration']
+// },
+// args: [
+// {
+// id: 'reason',
+// type: 'contentWithDuration',
+// match: 'rest',
+// prompt: {
+// start: 'Enter text and a duration here.',
+// retry: '{error} Error parsing duration and text.',
+// optional: true
+// }
+// }
+// ],
+// slash: true,
+// slashOptions: [
+// {
+// name: 'reason',
+// description: 'Enter text and a duration here.',
+// type: 'STRING',
+// required: false
+// }
+// ],
+// hidden: true,
+// ownerOnly: true
+// });
+// }
+
+// async exec(
+// message: Message | BushSlashMessage,
+// { reason }: { reason?: { duration: number; contentWithoutTime: string } }
+// ): Promise<unknown> {
+// const rawDuration = reason.duration;
+// const text = reason.contentWithoutTime;
+// const humanizedDuration = this.client.util.humanizeDuration(rawDuration);
+// return await message.util.reply(stripIndents`
+// **rawDuration:** ${rawDuration}
+// **text:** ${text}
+// **humanizedDuration:** ${humanizedDuration}`);
+// }
+// }
diff --git a/src/commands/dev/say.ts b/src/commands/dev/say.ts
new file mode 100644
index 0000000..9dc2632
--- /dev/null
+++ b/src/commands/dev/say.ts
@@ -0,0 +1,58 @@
+import { AllowedMentions, BushCommand, BushMessage } from '@lib';
+import { AkairoMessage } from 'discord-akairo';
+
+export default class SayCommand extends BushCommand {
+ public constructor() {
+ super('say', {
+ aliases: ['say'],
+ category: 'dev',
+ description: {
+ content: 'A command make the bot say something.',
+ usage: 'say <message>',
+ examples: ['say hello']
+ },
+ args: [
+ {
+ id: 'say',
+ type: 'string',
+ match: 'rest',
+ prompt: {
+ start: 'What would you like the bot to say?',
+ retry: '{error} Choose something valid to say.'
+ }
+ }
+ ],
+ slashOptions: [
+ {
+ name: 'content',
+ description: 'What would you like the bot to say?',
+ type: 'STRING'
+ }
+ ],
+ ownerOnly: true,
+ clientPermissions: ['SEND_MESSAGES'],
+ slash: true
+ });
+ }
+
+ public async exec(message: BushMessage, { say }: { say: string }): Promise<unknown> {
+ if (!message.author.isOwner())
+ return await message.util.reply(`${this.client.util.emojis.error} Only my developers can run this command.`);
+
+ if (message.deletable) {
+ await message.delete();
+ }
+ await message.util.send({ content: say, allowedMentions: AllowedMentions.none() });
+ }
+
+ public async execSlash(message: AkairoMessage, { content }: { content: string }): Promise<unknown> {
+ if (!this.client.config.owners.includes(message.author.id)) {
+ return await message.interaction.reply({
+ content: `${this.client.util.emojis.error} Only my developers can run this command.`,
+ ephemeral: true
+ });
+ }
+ await message.interaction.reply({ content: 'Attempting to send message.', ephemeral: true });
+ return message.channel.send({ content, allowedMentions: AllowedMentions.none() });
+ }
+}
diff --git a/src/commands/dev/setLevel.ts b/src/commands/dev/setLevel.ts
index db0cfab..f2ae6c7 100644
--- a/src/commands/dev/setLevel.ts
+++ b/src/commands/dev/setLevel.ts
@@ -29,21 +29,21 @@ export default class SetLevelCommand extends BushCommand {
}
}
],
- ownerOnly: true,
slashOptions: [
{
name: 'user',
- description: 'The user to change the level of',
+ description: 'What user would you like to change the level of?',
type: 'USER',
required: true
},
{
name: 'level',
- description: 'The level to set the user to',
+ description: 'What level would you like to set the user to?',
type: 'INTEGER',
required: true
}
],
+ ownerOnly: true,
slash: true
});
}
diff --git a/src/commands/dev/testDuration.ts b/src/commands/dev/testDuration.ts
deleted file mode 100644
index 2d636d2..0000000
--- a/src/commands/dev/testDuration.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { BushCommand, BushSlashMessage } from '@lib';
-import { stripIndents } from 'common-tags';
-import { Message } from 'discord.js';
-
-export default class TestDurationCommand extends BushCommand {
- public constructor() {
- super('testduration', {
- aliases: ['testduration'],
- category: 'dev',
- description: {
- content: 'Tests duration parsing.',
- usage: 'testduration [reason]',
- examples: ['testduration']
- },
- args: [
- {
- id: 'reason',
- type: 'contentWithDuration',
- match: 'rest',
- prompt: {
- start: 'Enter text and a duration here.',
- retry: '{error} Error parsing duration and text.',
- optional: true
- }
- }
- ],
- slash: true,
- slashOptions: [
- {
- name: 'reason',
- description: 'Enter text and a duration here.',
- type: 'STRING',
- required: false
- }
- ],
- hidden: true,
- ownerOnly: true
- });
- }
-
- async exec(
- message: Message | BushSlashMessage,
- { reason }: { reason?: { duration: number; contentWithoutTime: string } }
- ): Promise<unknown> {
- const rawDuration = reason.duration;
- const text = reason.contentWithoutTime;
- const humanizedDuration = this.client.util.humanizeDuration(rawDuration);
- return await message.util.reply(stripIndents`
- **rawDuration:** ${rawDuration}
- **text:** ${text}
- **humanizedDuration:** ${humanizedDuration}`);
- }
-}
diff --git a/src/commands/fun/coinflip.ts b/src/commands/fun/coinflip.ts
new file mode 100644
index 0000000..68484bb
--- /dev/null
+++ b/src/commands/fun/coinflip.ts
@@ -0,0 +1,26 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+
+export default class CoinFlipCommand extends BushCommand {
+ public constructor() {
+ super('coinflip', {
+ aliases: ['coinflip', 'cf'],
+ category: 'fun',
+ description: {
+ content: 'Flip a virtual coin.',
+ usage: 'coinflip',
+ examples: ['coinflip']
+ },
+ clientPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage): Promise<void> {
+ const random = Math.random();
+ let result: string;
+ const fall = message.author.id === '322862723090219008' ? 0.1 : 0.001;
+ if (random < fall) result = 'The coin fell off the table :(';
+ else if (random <= 0.5 + fall / 2) result = 'Heads';
+ else result = 'Tails';
+ await message.util.reply(result);
+ }
+}
diff --git a/src/commands/fun/dice.ts b/src/commands/fun/dice.ts
new file mode 100644
index 0000000..46b159b
--- /dev/null
+++ b/src/commands/fun/dice.ts
@@ -0,0 +1,23 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+
+export default class EightBallCommand extends BushCommand {
+ public constructor() {
+ super('dice', {
+ aliases: ['dice', 'die'],
+ category: 'fun',
+ description: {
+ content: 'Roll virtual dice.',
+ usage: 'dice',
+ examples: ['dice']
+ },
+ clientPermissions: ['SEND_MESSAGES'],
+ slash: true
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
+ const responses = ['1', '2', '3', '4', '5', '6'];
+ const answer = responses[Math.floor(Math.random() * responses.length)];
+ return await message.util.reply(`You rolled a **${answer}**.`);
+ }
+}
diff --git a/src/commands/fun/eightBall.ts b/src/commands/fun/eightBall.ts
new file mode 100644
index 0000000..7b7d39c
--- /dev/null
+++ b/src/commands/fun/eightBall.ts
@@ -0,0 +1,64 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+export default class EightBallCommand extends BushCommand {
+ public constructor() {
+ super('eightBall', {
+ aliases: ['8ball', 'eightball'],
+ category: 'fun',
+ description: {
+ content: 'Ask questions for a randomly generated response.',
+ usage: '8Ball <question>',
+ examples: ['8Ball does anyone love me?']
+ },
+ args: [
+ {
+ id: 'question',
+ type: 'string',
+ match: 'rest',
+ prompt: {
+ start: 'What question would you like answered?',
+ retry: '{error} Invalid question.'
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'question',
+ description: 'What question would you like answered?',
+ type: 'STRING',
+ required: true
+ }
+ ],
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage): Promise<void> {
+ const responses = [
+ 'It is certain',
+ 'Without a doubt',
+ 'You may rely on it',
+ 'Yes definitely',
+ 'It is decidedly so',
+ 'As I see it, yes',
+ 'Most likely',
+ 'Yes',
+ 'Outlook good',
+ 'Signs point to yes',
+ 'Reply hazy try again',
+ 'Better not tell you now',
+ 'Ask again later',
+ 'Cannot predict now',
+ 'Concentrate and ask again',
+ "Don't count on it",
+ 'Outlook not so good',
+ 'My sources say no',
+ 'Very doubtful',
+ 'My reply is no'
+ ];
+ const answer = responses[Math.floor(Math.random() * responses.length)];
+ await message.util.reply(answer);
+ }
+}
diff --git a/src/commands/fun/minesweeper.ts b/src/commands/fun/minesweeper.ts
new file mode 100644
index 0000000..2b46e34
--- /dev/null
+++ b/src/commands/fun/minesweeper.ts
@@ -0,0 +1,123 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import Minesweeper from 'discord.js-minesweeper';
+
+export default class MinesweeperCommand extends BushCommand {
+ public constructor() {
+ super('minesweeper', {
+ aliases: ['minesweeper'],
+ category: 'fun',
+ description: {
+ content: 'minesweeper command.',
+ usage: 'minesweeper <rows> <columns> <mines> [--spaces] [--revealFirstCell]',
+ examples: ['minesweeper 10 10 2']
+ },
+ args: [
+ {
+ id: 'rows',
+ type: 'integer',
+ prompt: {
+ start: 'How many rows would you like?',
+ retry: '{error} Choose a valid number of rows',
+ optional: true
+ },
+ default: 9
+ },
+ {
+ id: 'columns',
+ type: 'integer',
+ prompt: {
+ start: 'How many columns would you like?',
+ retry: '{error} Choose a valid number of columns',
+ optional: true
+ },
+ default: 9
+ },
+ {
+ id: 'mines',
+ type: 'integer',
+ prompt: {
+ start: 'How many mines would you like?',
+ retry: '{error} Choose a valid number of mines',
+ optional: true
+ },
+ default: 10
+ },
+ {
+ id: 'spaces',
+ match: 'flag',
+ flag: '--spaces'
+ },
+ {
+ id: 'reveal_first_cell',
+ match: 'flag',
+ flag: '--revealFirstCell'
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'rows',
+ description: 'How many rows would you like?',
+ type: 'INTEGER',
+ required: false
+ },
+ {
+ name: 'columns',
+ description: 'How many rows would you like?',
+ type: 'INTEGER',
+ required: false
+ },
+ {
+ name: 'mines',
+ description: 'How many rows would you like?',
+ type: 'INTEGER',
+ required: false
+ },
+ {
+ name: 'spaces',
+ description: 'Would you like there to be spaces?',
+ type: 'BOOLEAN',
+ required: false
+ },
+ {
+ name: 'reveal_first_cell',
+ description: 'Would you like to automatically reveal the first cell?',
+ type: 'BOOLEAN',
+ required: false
+ }
+ ],
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ {
+ rows,
+ columns,
+ mines,
+ spaces,
+ reveal_first_cell
+ }: {
+ rows: number;
+ columns: number;
+ mines: number;
+ spaces: boolean;
+ reveal_first_cell: boolean;
+ }
+ ): Promise<unknown> {
+ const minesweeper = new Minesweeper({
+ rows: rows ?? 9,
+ columns: columns ?? 9,
+ mines: mines ?? 10,
+ emote: 'boom',
+ revealFirstCell: reveal_first_cell ?? false,
+ zeroFirstCell: true,
+ spaces: spaces ?? false,
+ returnType: 'emoji'
+ });
+ const matrix = minesweeper.start();
+ return await message.util.reply(matrix.toString());
+ }
+}
diff --git a/src/commands/info/avatar.ts b/src/commands/info/avatar.ts
new file mode 100644
index 0000000..3d1776f
--- /dev/null
+++ b/src/commands/info/avatar.ts
@@ -0,0 +1,51 @@
+import { Constants } from 'discord-akairo';
+import { MessageEmbed, User } from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+export default class AvatarCommand extends BushCommand {
+ constructor() {
+ super('avatar', {
+ aliases: ['avatar', 'av'],
+ category: 'info',
+ description: {
+ content: "A command to get a user's avatar",
+ usage: 'avatar [user]',
+ examples: 'avatar'
+ },
+ args: [
+ {
+ id: 'user',
+ type: Constants.ArgumentTypes.USER,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'Who would you like to see the avatar of?',
+ retry: '{error} Choose a valid user.',
+ optional: true
+ }
+ }
+ ],
+ clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'user',
+ description: 'The user you would like to find the avatar of.',
+ type: 'USER',
+ required: false
+ }
+ ]
+ });
+ }
+
+ async exec(message: BushMessage | BushSlashMessage, { user }: { user: User }): Promise<void> {
+ user = user ?? message.author;
+
+ const embed = new MessageEmbed()
+ .setTimestamp()
+ .setColor(this.client.util.colors.default)
+ .setTitle(user.tag)
+ .setImage(user.avatarURL({ size: 2048, format: 'png', dynamic: true }));
+
+ await message.util.reply({ embeds: [embed] });
+ }
+}
diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts
index d5941b2..986bd01 100644
--- a/src/commands/info/botInfo.ts
+++ b/src/commands/info/botInfo.ts
@@ -4,7 +4,7 @@ import { MessageEmbed, version as discordJSVersion } from 'discord.js';
export default class BotInfoCommand extends BushCommand {
public constructor() {
super('botinfo', {
- aliases: ['botinfo'],
+ aliases: ['botinfo', 'stats'],
category: 'info',
description: {
content: 'Shows information about the bot',
diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts
new file mode 100644
index 0000000..4db20e7
--- /dev/null
+++ b/src/commands/info/color.ts
@@ -0,0 +1,42 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { Constants } from 'discord-akairo';
+import { ColorResolvable, MessageEmbed } from 'discord.js';
+
+export default class ColorCommand extends BushCommand {
+ public constructor() {
+ super('color', {
+ aliases: ['color'],
+ category: 'info',
+ description: {
+ content: 'See what color a hex code is.',
+ usage: 'color <color>',
+ examples: ['color #0000FF']
+ },
+ args: [
+ {
+ id: 'color',
+ type: /^#?(?<code>[0-9A-F]{6})$/i,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What color value would you like to see the color of',
+ retry: '{error} Choose a valid hex color code.'
+ }
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES']
+ });
+ }
+
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ { color: { match } }: { color: { match: RegExpMatchArray; matches: RegExpMatchArray[] } }
+ ): Promise<unknown> {
+ 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);
+
+ return await message.util.reply({ embeds: [embed] });
+ }
+}
diff --git a/src/commands/info/guildInfo.ts b/src/commands/info/guildInfo.ts
new file mode 100644
index 0000000..a3e7405
--- /dev/null
+++ b/src/commands/info/guildInfo.ts
@@ -0,0 +1,171 @@
+import { Argument, Constants } from 'discord-akairo';
+import { Guild, GuildPreview, MessageEmbed, Snowflake, Vanity } from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+// TODO: Implement regions and security
+export default class GuildInfoCommand extends BushCommand {
+ public constructor() {
+ super('guildInfo', {
+ aliases: ['guildinfo', 'serverinfo', 'guild', 'server', 'g'],
+ category: 'info',
+ description: {
+ content: 'Get info about a server.',
+ usage: 'guildinfo [guild]',
+ examples: ['guildinfo 516977525906341928']
+ },
+ args: [
+ {
+ id: 'guild',
+ type: Argument.union(Constants.ArgumentTypes.GUILD, Constants.ArgumentTypes.BIGINT),
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What server would you like to find information about?',
+ retry: '{error} Choose a valid server to find information about.',
+ optional: true
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'guild',
+ description: 'The id of the guild you would like to find information about.',
+ type: 'STRING',
+ required: false
+ }
+ ],
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage, args: { guild: Guild | bigint | GuildPreview }): Promise<unknown> {
+ if (!args?.guild && !message.guild) {
+ return await message.util.reply(
+ `${this.client.util.emojis.error} You must either provide an server to provide info about or run this command in a server.`
+ );
+ }
+ let isPreview = false;
+ if (['bigint', 'number', 'string'].includes(typeof args?.guild)) {
+ const preview = await this.client.fetchGuildPreview(`${args.guild}` as Snowflake).catch(() => {});
+ if (preview) {
+ args.guild = preview;
+ isPreview = true;
+ } else {
+ return await message.util.reply(`${this.client.util.emojis.error} That guild is not discoverable or does not exist.`);
+ }
+ }
+ const guild: Guild | GuildPreview = (args?.guild as Guild | GuildPreview) || (message.guild as Guild);
+ const emojis: string[] = [];
+ const guildAbout: string[] = [];
+ // const guildSecurity = [];
+ if (['516977525906341928', '784597260465995796', '717176538717749358', '767448775450820639'].includes(guild.id))
+ emojis.push(this.client.consts.mappings.otherEmojis.BUSH_VERIFIED);
+
+ if (!isPreview && guild instanceof Guild) {
+ if (guild.premiumTier) emojis.push(this.client.consts.mappings.otherEmojis['BOOST_' + guild.premiumTier]);
+ await guild.fetch();
+ const channelTypes = [
+ `${this.client.consts.mappings.otherEmojis.TEXT} ${guild.channels.cache
+ .filter((channel) => channel.type === 'GUILD_TEXT')
+ .size.toLocaleString()}`,
+ `${this.client.consts.mappings.otherEmojis.NEWS} ${guild.channels.cache
+ .filter((channel) => channel.type === 'GUILD_NEWS')
+ .size.toLocaleString()}`,
+ `${this.client.consts.mappings.otherEmojis.VOICE} ${guild.channels.cache
+ .filter((channel) => channel.type === 'GUILD_VOICE')
+ .size.toLocaleString()}`,
+ `${this.client.consts.mappings.otherEmojis.STAGE} ${guild.channels.cache
+ .filter((channel) => channel.type === 'GUILD_STAGE_VOICE')
+ .size.toLocaleString()}`,
+ `${this.client.consts.mappings.otherEmojis.STORE} ${guild.channels.cache
+ .filter((channel) => channel.type === 'GUILD_STORE')
+ .size.toLocaleString()}`,
+ `${this.client.consts.mappings.otherEmojis.CATEGORY} ${guild.channels.cache
+ .filter((channel) => channel.type === 'GUILD_CATEGORY')
+ .size.toLocaleString()}`,
+ `${this.client.consts.mappings.otherEmojis.THREAD} ${guild.channels.cache
+ .filter((channel) =>
+ ['GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD', 'GUILD_STAGE_VOICE'].includes(channel.type)
+ )
+ .size.toLocaleString()}`
+ ];
+
+ // TODO add guild regions
+ // const guildRegions = [];
+
+ guildAbout.push(
+ `**Owner:** ${guild.members.cache.get(guild.ownerId)?.user.tag}`,
+ `**Created** ${guild.createdAt.toLocaleString()}`,
+ `**Members:** ${guild.memberCount.toLocaleString()}`,
+ `**Online:** ${guild.approximatePresenceCount?.toLocaleString()}`,
+ `**Channels:** ${guild.channels.cache.size} (${channelTypes.join(', ')})`,
+ `**Emojis:** ${guild.emojis.cache.size.toLocaleString()}`
+ // `**Region:** ${guildRegions.join()}`
+ );
+ if (guild.premiumSubscriptionCount)
+ guildAbout.push(
+ `**Boosts:** Level ${guild.premiumTier.slice(0, 4)} with ${guild.premiumSubscriptionCount ?? 0} boosts`
+ );
+ if (guild.me?.permissions.has('MANAGE_GUILD') && guild.vanityURLCode) {
+ const vanityInfo: Vanity = await guild.fetchVanityData();
+ guildAbout.push(
+ `**Vanity URL:** discord.gg/${vanityInfo.code}`,
+ `**Vanity Uses:** ${vanityInfo.uses?.toLocaleString()}`
+ );
+ }
+
+ // guildSecurity.push;
+ } else {
+ guildAbout.push(
+ `**Members:** ${guild.approximateMemberCount?.toLocaleString()}`,
+ `**Online:** ${guild.approximatePresenceCount?.toLocaleString()}`,
+ `**Emojis:** ${(guild as GuildPreview).emojis.size}`
+ );
+ }
+
+ const guildFeatures = guild.features.sort((a, b) => {
+ const aWeight = this.client.consts.mappings.features[a]?.weight;
+ const bWeight = this.client.consts.mappings.features[b]?.weight;
+
+ if (aWeight != undefined && bWeight != undefined) {
+ return aWeight - bWeight;
+ } else if (aWeight == undefined) {
+ return 1;
+ } else if (bWeight == undefined) {
+ return -1;
+ }
+ });
+ if (guildFeatures.length) {
+ guildFeatures.forEach((feature) => {
+ if (this.client.consts.mappings.features[feature]?.emoji) {
+ emojis.push(`${this.client.consts.mappings.features[feature].emoji}`);
+ } else if (this.client.consts.mappings.features[feature]?.name) {
+ emojis.push(`\`${this.client.consts.mappings.features[feature].name}\``);
+ } else {
+ emojis.push(`\`${feature}\``);
+ }
+ });
+ }
+
+ if (guild.description) {
+ emojis.push(`\n\n${guild.description}`);
+ }
+
+ const guildInfoEmbed = new MessageEmbed()
+ .setTitle(guild.name)
+ .setColor(this.client.util.colors.default)
+ .addField('» About', guildAbout.join('\n'));
+ const guildIcon = guild.iconURL({ size: 2048, format: 'png', dynamic: true });
+ if (guildIcon) {
+ guildInfoEmbed.setThumbnail(guildIcon);
+ }
+ // if (!isPreview) {
+ // guildInfoEmbed.addField('» Security', guildSecurity.join('\n'));
+ // }
+ if (emojis) {
+ guildInfoEmbed.setDescription(emojis.join(' '));
+ }
+ return await message.util.reply({ embeds: [guildInfoEmbed] });
+ }
+}
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index e061453..0977c36 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -11,8 +11,6 @@ export default class HelpCommand extends BushCommand {
usage: 'help [command]',
examples: ['help prefix']
},
- clientPermissions: ['EMBED_LINKS'],
-
args: [
{
id: 'command',
@@ -38,7 +36,9 @@ export default class HelpCommand extends BushCommand {
type: 'STRING',
required: false
}
- ]
+ ],
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
});
}
diff --git a/src/commands/info/icon.ts b/src/commands/info/icon.ts
new file mode 100644
index 0000000..bd1cdf4
--- /dev/null
+++ b/src/commands/info/icon.ts
@@ -0,0 +1,34 @@
+import { MessageEmbed } from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+export default class IconCommand extends BushCommand {
+ constructor() {
+ super('icon', {
+ aliases: ['icon', 'guildavatar', 'severicon', 'guildicon'],
+ category: 'info',
+ description: {
+ content: "A command to get the server's icon",
+ usage: 'icon',
+ examples: 'icon'
+ },
+ clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'],
+ channel: 'guild',
+ slash: true
+ });
+ }
+
+ async exec(message: BushMessage | BushSlashMessage): Promise<void> {
+ const embed = new MessageEmbed()
+ .setTimestamp()
+ .setColor(this.client.util.colors.default)
+ .setImage(
+ message.guild?.iconURL({
+ size: 2048,
+ dynamic: true,
+ format: 'png'
+ })
+ )
+ .setTitle(message.guild.name);
+ await message.util.reply({ embeds: [embed] });
+ }
+}
diff --git a/src/commands/info/invite.ts b/src/commands/info/invite.ts
new file mode 100644
index 0000000..f53e930
--- /dev/null
+++ b/src/commands/info/invite.ts
@@ -0,0 +1,33 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { MessageActionRow, MessageButton } from 'discord.js';
+
+export default class InviteCommand extends BushCommand {
+ public constructor() {
+ super('invite', {
+ aliases: ['invite'],
+ category: 'info',
+ description: {
+ content: 'Sends the bot invite link.',
+ usage: 'invite',
+ examples: ['invite']
+ },
+ ratelimit: 4,
+ cooldown: 4000,
+ clientPermissions: ['SEND_MESSAGES'],
+ slash: true
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
+ if (this.client.config.dev)
+ return await message.util.reply(`${this.client.util.emojis.error} The dev bot cannot be invited.`);
+ const 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`
+ })
+ );
+ return await message.util.reply({ components: [ButtonRow] });
+ }
+}
diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts
index 59f475f..d1f406d 100644
--- a/src/commands/info/ping.ts
+++ b/src/commands/info/ping.ts
@@ -11,9 +11,9 @@ export default class PingCommand extends BushCommand {
usage: 'ping',
examples: ['ping']
},
+ slash: true,
clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'],
- userPermissions: ['SEND_MESSAGES'],
- slash: true
+ userPermissions: ['SEND_MESSAGES']
});
}
diff --git a/src/commands/info/snowflakeInfo.ts b/src/commands/info/snowflakeInfo.ts
new file mode 100644
index 0000000..e234cc2
--- /dev/null
+++ b/src/commands/info/snowflakeInfo.ts
@@ -0,0 +1,151 @@
+import {
+ CategoryChannel,
+ Channel,
+ DeconstructedSnowflake,
+ DMChannel,
+ Emoji,
+ Guild,
+ MessageEmbed,
+ NewsChannel,
+ Role,
+ Snowflake,
+ SnowflakeUtil,
+ StageChannel,
+ TextChannel,
+ User,
+ VoiceChannel
+} from 'discord.js';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+export default class SnowflakeInfoCommand extends BushCommand {
+ public constructor() {
+ super('snowflake', {
+ aliases: ['snowflake', 'info', 'sf'],
+ category: 'info',
+ ratelimit: 4,
+ cooldown: 4000,
+ description: {
+ content: 'Provides information about the specified Snowflake.',
+ usage: 'snowflake <snowflake>',
+ examples: ['snowflake 322862723090219008']
+ },
+ args: [
+ {
+ id: 'snowflake',
+ type: 'bigint',
+ prompt: {
+ start: 'Enter the snowflake you would like to get information about.',
+ retry: '{error} Choose a valid snowflake.',
+ optional: false
+ }
+ }
+ ],
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'snowflake',
+ description: 'The snowflake you would like to get information about.',
+ type: 'STRING',
+ required: true
+ }
+ ]
+ });
+ }
+ public async exec(message: BushMessage | BushSlashMessage, args: { snowflake: bigint }): Promise<unknown> {
+ const snowflake = `${args.snowflake}` as Snowflake;
+ const snowflakeEmbed = new MessageEmbed().setTitle('Unknown :snowflake:').setColor(this.client.util.colors.default);
+
+ // Channel
+ if (this.client.channels.cache.has(snowflake)) {
+ const channel: Channel = this.client.channels.cache.get(snowflake);
+ const channelInfo = [`**Type:** ${channel.type}`];
+ if (['dm', 'group'].includes(channel.type)) {
+ const _channel = channel as DMChannel;
+ channelInfo.push(`**Recipient:** ${_channel.recipient.tag} (${_channel.recipient.id})`);
+ snowflakeEmbed.setTitle(`:snowflake: DM with ${(channel as DMChannel).recipient.tag} \`[Channel]\``);
+ } else if (
+ [
+ 'GUILD_CATEGORY',
+ 'GUILD_NEWS',
+ 'GUILD_TEXT',
+ 'GUILD_VOICE',
+ 'GUILD_STORE',
+ 'GUILD_STAGE_VOICE',
+ 'GUILD_NEWS_THREAD',
+ 'GUILD_PUBLIC_THREAD',
+ 'GUILD_PRIVATE_THREAD'
+ ].includes(channel.type)
+ ) {
+ const _channel = channel as TextChannel | VoiceChannel | NewsChannel | StageChannel | CategoryChannel | StageChannel;
+ channelInfo.push(
+ `**Channel Name:** <#${_channel.id}> (${_channel.name})`,
+ `**Channel's Server:** ${_channel.guild.name} (${_channel.guild.id})`
+ );
+ snowflakeEmbed.setTitle(`:snowflake: ${_channel.name} \`[Channel]\``);
+ }
+ snowflakeEmbed.addField('» Channel Info', channelInfo.join('\n'));
+ }
+
+ // Guild
+ else if (this.client.guilds.cache.has(snowflake)) {
+ const guild: Guild = this.client.guilds.cache.get(snowflake);
+ const guildInfo = [
+ `**Name:** ${guild.name}`,
+ `**Owner:** ${this.client.users.cache.get(guild.ownerId)?.tag || '¯\\_(ツ)_/¯'} (${guild.ownerId})`,
+ `**Members:** ${guild.memberCount?.toLocaleString()}`
+ ];
+ snowflakeEmbed.setThumbnail(guild.iconURL({ size: 2048, dynamic: true }));
+ snowflakeEmbed.addField('» Server Info', guildInfo.join('\n'));
+ snowflakeEmbed.setTitle(`:snowflake: ${guild.name} \`[Server]\``);
+ }
+
+ // User
+ else if (this.client.users.cache.has(snowflake)) {
+ const user: User = this.client.users.cache.get(snowflake);
+ const userInfo = [`**Name:** <@${user.id}> (${user.tag})`];
+ snowflakeEmbed.setThumbnail(user.avatarURL({ size: 2048, dynamic: true }));
+ snowflakeEmbed.addField('» User Info', userInfo.join('\n'));
+ snowflakeEmbed.setTitle(`:snowflake: ${user.tag} \`[User]\``);
+ }
+
+ // Emoji
+ else if (this.client.emojis.cache.has(snowflake)) {
+ const emoji: Emoji = this.client.emojis.cache.get(snowflake);
+ const emojiInfo = [`**Name:** ${emoji.name}`, `**Animated:** ${emoji.animated}`];
+ snowflakeEmbed.setThumbnail(emoji.url);
+ snowflakeEmbed.addField('» Emoji Info', emojiInfo.join('\n'));
+ snowflakeEmbed.setTitle(`:snowflake: ${emoji.name} \`[Emoji]\``);
+ }
+
+ // Role
+ else if (message.guild.roles.cache.has(snowflake)) {
+ const role: Role = message.guild.roles.cache.get(snowflake);
+ const roleInfo = [
+ `**Name:** <@&${role.id}> (${role.name})`,
+ `**Members:** ${role.members.size}`,
+ `**Hoisted:** ${role.hoist}`,
+ `**Managed:** ${role.managed}`,
+ `**Position:** ${role.position}`,
+ `**Hex Color:** ${role.hexColor}`
+ ];
+ if (role.color) snowflakeEmbed.setColor(role.color);
+ snowflakeEmbed.addField('» Role Info', roleInfo.join('\n'));
+ snowflakeEmbed.setTitle(`:snowflake: ${role.name} \`[Role]\``);
+ }
+
+ // SnowflakeInfo
+ const deconstructedSnowflake: DeconstructedSnowflake = SnowflakeUtil.deconstruct(snowflake);
+ const snowflakeInfo = [
+ `**Timestamp:** ${deconstructedSnowflake.timestamp}`,
+ `**Created:** ${deconstructedSnowflake.date.toLocaleString()}`,
+ `**Worker ID:** ${deconstructedSnowflake.workerId}`,
+ `**Process ID:** ${deconstructedSnowflake.processId}`,
+ `**Increment:** ${deconstructedSnowflake.increment}`
+ // `**Binary:** ${deconstructedSnowflake.binary}`
+ ];
+ snowflakeEmbed.addField('» Snowflake Info', snowflakeInfo.join('\n'));
+
+ return await message.util.reply({ embeds: [snowflakeEmbed] });
+ }
+}
diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts
new file mode 100644
index 0000000..50756a6
--- /dev/null
+++ b/src/commands/info/userInfo.ts
@@ -0,0 +1,160 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { GuildMember, MessageEmbed } from 'discord.js';
+import moment from 'moment';
+
+// TODO: Allow looking up a user not in the guild and not cached
+// TODO: Re-Implement Status Emojis
+// TODO: Add bot information
+export default class UserInfoCommand extends BushCommand {
+ public constructor() {
+ super('userinfo', {
+ aliases: ['userinfo', 'user', 'u'],
+ category: 'info',
+ description: {
+ usage: 'userinfo [user]',
+ examples: ['userinfo 322862723090219008'],
+ content: 'Gives information about a specified user.'
+ },
+ args: [
+ {
+ id: 'user',
+ type: 'member',
+ prompt: {
+ start: 'What user would you like to find information about?',
+ retry: '{error} Choose a valid user to find information about.',
+ optional: true
+ },
+ default: null
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'user',
+ description: 'The user you would like to find information about.',
+ type: 'USER',
+ required: false
+ }
+ ],
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage, args: { user: GuildMember }): Promise<unknown> {
+ const user = args?.user || message.member;
+ const emojis = [];
+ const superUsers = this.client.cache.global.superUsers;
+
+ const userEmbed: MessageEmbed = new MessageEmbed()
+ .setTitle(user.user.tag)
+ .setThumbnail(user.user.avatarURL({ size: 2048, format: 'png', dynamic: true }))
+ .setTimestamp();
+
+ // Flags
+ if (this.client.config.owners.includes(user.id)) emojis.push(this.client.consts.mappings.otherEmojis.DEVELOPER);
+ if (superUsers.includes(user.id)) emojis.push(this.client.consts.mappings.otherEmojis.SUPERUSER);
+ const flags = user.user.flags?.toArray();
+ if (flags) {
+ flags.forEach((f) => {
+ if (this.client.consts.mappings.userFlags[f]) {
+ emojis.push(this.client.consts.mappings.userFlags[f]);
+ } else emojis.push(f);
+ });
+ }
+
+ // Since discord bald I just guess if someone has nitro
+ if (
+ Number(user.user.discriminator) < 10 ||
+ this.client.consts.mappings.maybeNitroDiscrims.includes(user.user.discriminator) ||
+ user.user.displayAvatarURL({ dynamic: true })?.endsWith('.gif') ||
+ user.user.flags?.toArray().includes('PARTNERED_SERVER_OWNER')
+ ) {
+ emojis.push(this.client.consts.mappings.otherEmojis.NITRO);
+ }
+
+ if (message.guild.ownerId == user.id) emojis.push(this.client.consts.mappings.otherEmojis.OWNER);
+ else if (user.permissions.has('ADMINISTRATOR')) emojis.push(this.client.consts.mappings.otherEmojis.ADMIN);
+ 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(),
+ joinedAt = user.joinedAt?.toLocaleString(),
+ joinedAtDelta = moment(user.joinedAt)?.diff(moment()).toLocaleString(),
+ premiumSince = user.premiumSince?.toLocaleString(),
+ premiumSinceDelta = moment(user.premiumSince)?.diff(moment()).toLocaleString();
+
+ // General Info
+ const generalInfo = [
+ `**Mention:** <@${user.id}>`,
+ `**ID:** ${user.id}`,
+ `**Created: **${createdAt} (${createdAtDelta} ago)`
+ ];
+ userEmbed.addField('» General Info', generalInfo.join('\n'));
+
+ // Server User Info
+ const serverUserInfo = [];
+ if (joinedAt)
+ serverUserInfo.push(
+ `**${message.guild.ownerId == user.id ? 'Created Server' : 'Joined'}: ** ${joinedAt} (${joinedAtDelta} ago)`
+ );
+ if (premiumSince) serverUserInfo.push(`**Boosting Since:** ${premiumSince} (${premiumSinceDelta} ago)`);
+ if (user.displayHexColor) serverUserInfo.push(`**Display Color:** ${user.displayHexColor}`);
+ if (user.id == '322862723090219008' && message.guild.id == this.client.consts.mappings.guilds.bush)
+ serverUserInfo.push(`**General Deletions:** 2`);
+ if (
+ ['384620942577369088', '496409778822709251'].includes(user.id) &&
+ message.guild.id == this.client.consts.mappings.guilds.bush
+ )
+ serverUserInfo.push(`**General Deletions:** 1`);
+ if (user.nickname) serverUserInfo.push(`**Nickname** ${user.nickname}`);
+ if (serverUserInfo.length)
+ userEmbed
+ .addField('» Server Info', serverUserInfo.join('\n'))
+ .setColor(user.displayColor || this.client.util.colors.default);
+
+ // User Presence Info
+ if (user.presence?.status || user.presence?.clientStatus || user.presence?.activities) {
+ let customStatus = '';
+ const activitiesNames = [];
+ if (user.presence.activities) {
+ user.presence.activities.forEach((a) => {
+ if (a.type == 'CUSTOM' && a.state) {
+ const emoji = `${a.emoji ? `${a.emoji.toString()} ` : ''}`;
+ customStatus = `${emoji}${a.state}`;
+ }
+ activitiesNames.push(`\`${a.name}\``);
+ });
+ }
+ let devices;
+ if (user.presence.clientStatus) devices = Object.keys(user.presence.clientStatus);
+ const presenceInfo = [];
+ if (user.presence.status) presenceInfo.push(`**Status:** ${user.presence.status}`);
+ if (devices)
+ presenceInfo.push(`**${devices.length - 1 ? 'Devices' : 'Device'}:** ${this.client.util.oxford(devices, 'and', '')}`);
+ if (activitiesNames.length)
+ presenceInfo.push(
+ `**Activit${activitiesNames.length - 1 ? 'ies' : 'y'}:** ${this.client.util.oxford(activitiesNames, 'and', '')}`
+ );
+ if (customStatus) presenceInfo.push(`**Custom Status:** ${customStatus}`);
+ userEmbed.addField('» Presence', presenceInfo.join('\n'));
+ }
+
+ // Important Perms
+ const perms = [];
+ if (user.permissions.has('ADMINISTRATOR') || message.guild.ownerId == user.id) {
+ perms.push('`Administrator`');
+ } else {
+ user.permissions.toArray(true).forEach((permission) => {
+ if (this.client.consts.mappings.permissions[permission]?.important) {
+ perms.push(`\`${this.client.consts.mappings.permissions[permission].name}\``);
+ }
+ });
+ }
+
+ if (perms.length) userEmbed.addField('» Important Perms', perms.join(' '));
+ if (emojis) userEmbed.setDescription(emojis.join(' '));
+
+ return await message.util.reply({ embeds: [userEmbed] });
+ }
+}
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index af5f563..4b57a4f 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -1,6 +1,6 @@
-import { BushCommand, BushMessage, BushSlashMessage, ModLog } from '@lib';
+import { BushCommand, BushMessage, BushSlashMessage, BushUser, ModLog } from '@lib';
import { Argument } from 'discord-akairo';
-import { MessageEmbed } from 'discord.js';
+import { MessageEmbed, User } from 'discord.js';
export default class ModlogCommand extends BushCommand {
public constructor() {
@@ -47,8 +47,8 @@ export default class ModlogCommand extends BushCommand {
return modLog.join(`\n`);
}
- async exec(message: BushMessage | BushSlashMessage, { search }: { search: string }): Promise<unknown> {
- const foundUser = await this.client.util.resolveUserAsync(search);
+ async exec(message: BushMessage | BushSlashMessage, { search }: { search: BushUser | string }): Promise<unknown> {
+ const foundUser = search instanceof User ? search : await this.client.util.resolveUserAsync(search);
if (foundUser) {
const logs = await ModLog.findAll({
where: {
@@ -57,6 +57,8 @@ export default class ModlogCommand extends BushCommand {
},
order: [['createdAt', 'ASC']]
});
+ if (!logs.length)
+ return message.util.reply(`${this.client.util.emojis.error} **${foundUser.tag}** does not have any modlogs.`);
const niceLogs: string[] = [];
for (const log of logs) {
niceLogs.push(this.generateModlogInfo(log));
@@ -72,7 +74,7 @@ export default class ModlogCommand extends BushCommand {
);
this.client.util.buttonPaginate(message, embedPages, '', true);
} else if (search) {
- const entry = await ModLog.findByPk(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.`);
const embed = new MessageEmbed({
title: `Case ${entry.id}`,
diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts
new file mode 100644
index 0000000..3d274e1
--- /dev/null
+++ b/src/commands/moderation/removeReactionEmoji.ts
@@ -0,0 +1,64 @@
+import { BushCommand, BushMessage } from '@lib';
+import { Argument } from 'discord-akairo';
+import { Emoji } from 'discord.js';
+
+export default class RemoveReactionEmojiCommand extends BushCommand {
+ public constructor() {
+ super('removereactionemoji', {
+ aliases: ['removereactionemoji', 'rre'],
+ category: 'moderation',
+ description: {
+ content: 'Deleted all the reactions of a certain emoji from a message.',
+ usage: 'removereactionemoji <message> <emoji>',
+ examples: ['removereactionemoji 791413052347252786 <:omegaclown:782630946435366942>']
+ },
+ clientPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES', 'EMBED_LINKS'],
+ userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS'], // Can't undo the removal of 1000s of reactions
+ args: [
+ {
+ id: 'messageToRemoveFrom',
+ type: 'guildMessage',
+ prompt: {
+ start: 'What message would you like to remove a reaction from?',
+ retry: '{error} Please pick a valid message.'
+ }
+ },
+ {
+ id: 'emoji',
+ type: Argument.union('emoji', 'bigint'),
+ match: 'restContent',
+ prompt: {
+ start: 'What emoji would you like to remove?',
+ retry: '{error} Please pick a valid emoji.'
+ }
+ }
+ ],
+ channel: 'guild'
+ });
+ }
+
+ public async exec(
+ message: BushMessage,
+ { messageToRemoveFrom, emoji }: { messageToRemoveFrom: BushMessage; emoji: Emoji | BigInt }
+ ): Promise<unknown> {
+ const id = !['bigint', 'string'].includes(typeof emoji);
+ const emojiID = !id ? `${emoji}` : (emoji as Emoji).id;
+ const success = await messageToRemoveFrom.reactions.cache
+ .get(emojiID)
+ .remove()
+ .catch(() => {});
+ if (success) {
+ return await message.util.reply(
+ `${this.client.util.emojis.success} Removed all reactions of \`${
+ id ? emojiID : emoji
+ }\` from the message with the id of \`${messageToRemoveFrom.id}\`.`
+ );
+ } else {
+ return await message.util.reply(
+ `${this.client.util.emojis.error} There was an error removing all reactions of \`${
+ id ? emojiID : emoji
+ }\` from the message with the id of \`${messageToRemoveFrom.id}\`.`
+ );
+ }
+ }
+}
diff --git a/src/commands/moderation/slowmode.ts b/src/commands/moderation/slowmode.ts
new file mode 100644
index 0000000..8384562
--- /dev/null
+++ b/src/commands/moderation/slowmode.ts
@@ -0,0 +1,87 @@
+import { BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { Argument, Constants } from 'discord-akairo';
+import { TextChannel, ThreadChannel } from 'discord.js';
+
+export default class SlowModeCommand extends BushCommand {
+ public constructor() {
+ super('slowmode', {
+ aliases: ['slowmode', 'slow'],
+ category: 'moderation',
+ description: {
+ content: 'A command to set the slowmode of a channel.',
+ usage: 'slowmode <length>',
+ examples: ['slowmode 3']
+ },
+ args: [
+ {
+ id: 'length',
+ type: Argument.union('duration', 'off', 'none', 'disable'),
+ default: 0,
+ prompt: {
+ start: 'What would you like to set the slowmode to?',
+ retry: '{error} Please set the slowmode to a valid length.',
+ optional: true
+ }
+ },
+ {
+ id: 'channel',
+ type: Constants.ArgumentTypes.CHANNEL,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What channel would you like to change?',
+ retry: '{error} Choose a valid channel.',
+ optional: true
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'channel',
+ description: 'What channel would you like to change the slowmode of?',
+ type: 'CHANNEL',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES', 'EMBED_LINKS'],
+ userPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES']
+ });
+ }
+
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ { length, channel }: { length: number | 'off' | 'none' | 'disable'; channel: TextChannel | ThreadChannel }
+ ): Promise<unknown> {
+ if (message.channel.type === 'DM')
+ return await message.util.reply(`${this.client.util.emojis.error} This command cannot be run in dms.`);
+ if (!channel) channel = message.channel as ThreadChannel | TextChannel;
+ if (!(channel instanceof TextChannel) || !(channel instanceof ThreadChannel))
+ return await message.util.reply(`${this.client.util.emojis.error} <#${channel.id}> is not a text or thread channel.`);
+ if (length) {
+ length =
+ typeof length === 'string' && !['off', 'none', 'disable'].includes(length)
+ ? await Argument.cast('duration', this.client.commandHandler.resolver, message as BushMessage, length)
+ : length;
+ }
+
+ // @ts-expect-error: stop being dumb smh
+ const length2: number = ['off', 'none', 'disable'].includes(length) ? 0 : length;
+
+ const setSlowmode = await (channel as ThreadChannel | TextChannel)
+ .setRateLimitPerUser(length2 / 1000, `Changed by ${message.author.tag} (${message.author.id}).`)
+ .catch(() => {});
+ if (!setSlowmode)
+ return await message.util.reply(
+ `${this.client.util.emojis.error} There was an error changing the slowmode of <#${
+ (channel as ThreadChannel | TextChannel).id
+ }>.`
+ );
+ else
+ return await message.util.reply(
+ `${this.client.util.emojis.success} Successfully changed the slowmode of ${channel} ${
+ length2 ? `to \`${this.client.util.humanizeDuration(length2)}` : '`off'
+ }\`.`
+ );
+ }
+}
diff --git a/src/commands/skyblockReborn/chooseColorCommand.ts b/src/commands/skyblockReborn/chooseColorCommand.ts
new file mode 100644
index 0000000..ce70419
--- /dev/null
+++ b/src/commands/skyblockReborn/chooseColorCommand.ts
@@ -0,0 +1,179 @@
+import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage } from '@lib';
+import { Constants } from 'discord-akairo';
+import { CommandInteraction, Role, RoleResolvable, Snowflake } from 'discord.js';
+
+const roleColorMap = [
+ {
+ name: 'Brown',
+ value: '840952499883737108'
+ },
+ {
+ name: 'Dark Red',
+ value: '840952434574229546'
+ },
+ {
+ name: 'Red',
+ value: '840952208552230913'
+ },
+ {
+ name: 'Pink',
+ value: '840952722681364531'
+ },
+ {
+ name: 'Hot Pink',
+ value: '840952638309007412'
+ },
+ {
+ name: 'Yellow',
+ value: '840952282598473778'
+ },
+ {
+ name: 'Gold',
+ value: '840952256764313610'
+ },
+ {
+ name: 'Light Green',
+ value: '846394838744170517'
+ },
+ {
+ name: 'Green',
+ value: '840952308702642206'
+ },
+ {
+ name: 'Sea Green',
+ value: '840952901853511690'
+ },
+ {
+ name: 'Forest Green',
+ value: '840952382510858260'
+ },
+ {
+ name: 'Dark Green',
+ value: '840952336339042315'
+ },
+ {
+ name: 'Blue',
+ value: '840952833200750682'
+ },
+ {
+ name: 'Dark Blue',
+ value: '840952875734532137'
+ },
+ {
+ name: 'Blurple',
+ value: '853037502188617778'
+ },
+ {
+ name: 'Wizard Purple',
+ value: '840952750816755723'
+ },
+ {
+ name: 'White',
+ value: '840953158276743208'
+ },
+ {
+ name: 'Dark Mode',
+ value: '840953434785710110'
+ },
+ {
+ name: 'Black',
+ value: '840953275326660629'
+ }
+];
+export default class ChooseColorCommand extends BushCommand {
+ public constructor() {
+ super('chooseColor', {
+ aliases: ['choosecolor'],
+ category: 'Skyblock: Reborn',
+ description: {
+ content: 'Choose a color.',
+ usage: 'color <color>',
+ examples: ['report IRONM00N']
+ },
+ args: [
+ {
+ id: 'color',
+ type: Constants.ArgumentTypes.ROLE,
+ match: Constants.ArgumentMatches.REST,
+ prompt: {
+ start: 'Please choose a valid color.',
+ retry: `{error} Provide what did they do wrong.`,
+ optional: true
+ }
+ }
+ ],
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ channel: 'guild',
+ restrictedGuilds: ['839287012409999391'],
+ slash: true,
+ slashGuilds: ['839287012409999391'],
+ slashOptions: [
+ {
+ name: 'color',
+ description: 'The color you would like to have.',
+ type: 'STRING',
+ choices: roleColorMap,
+ required: true
+ }
+ ]
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage, args: { color: Role | RoleResolvable }): Promise<unknown> {
+ if (message.guild.id != this.client.consts.mappings.guilds.sbr) {
+ return await message.util.reply(`${this.client.util.emojis.error} This command can only be run in Skyblock: Reborn.`);
+ }
+ const allowedRoles: Snowflake[] = [
+ '839317526395879449', //Server Booster
+ '840949387534008360' //Mega Donator
+ ];
+
+ if (
+ !(
+ allowedRoles.some((role) => (message.member as BushGuildMember).roles.cache.has(role)) ||
+ (message.member as BushGuildMember).permissions.has('ADMINISTRATOR') ||
+ message.guild.ownerId === message.author.id
+ )
+ ) {
+ const allowed = this.client.util.oxford(
+ allowedRoles.map((id) => `<@&${id}>s`),
+ 'and',
+ ''
+ );
+ return await message.util.reply({
+ content: `${this.client.util.emojis.error} Only ${allowed} can use this command.`,
+ allowedMentions: AllowedMentions.none(),
+ ephemeral: true
+ });
+ }
+ if (message.util.isSlash) await (message.interaction as CommandInteraction).defer();
+ // new Array(
+ // roleColorMap.map(obj => obj.name.toLowerCase()),
+ // roleColorMap.map(obj => obj.value)
+ // );
+ const colorID = message.util.isSlash ? (args.color as string) : (args.color as Role).id;
+ if (!roleColorMap.map((obj) => obj.value).includes(colorID)) {
+ return await message.util.reply({
+ content: `${this.client.util.emojis.error} ${args.color} is not a whitelisted color role.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ }
+ const memberColorRoles = (message.member as BushGuildMember).roles.cache.filter((role) =>
+ roleColorMap.map((obj) => obj.value).includes(role.id)
+ );
+
+ await (message.member as BushGuildMember).roles.add(args.color, 'Choose Color Command.');
+
+ if (memberColorRoles.size) {
+ memberColorRoles.forEach(
+ (role) => (message.member as BushGuildMember).roles.remove(role),
+ 'Removing Duplicate Color Roles.'
+ );
+ }
+
+ message.util.reply({
+ content: `${this.client.util.emojis.success} Assigned you the <@&${colorID}> role.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ }
+}
diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/_whoHasRole.ts
index e69de29..e69de29 100644
--- a/src/commands/utilities/whoHasRole.ts
+++ b/src/commands/utilities/_whoHasRole.ts
diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts
new file mode 100644
index 0000000..97ff444
--- /dev/null
+++ b/src/commands/utilities/decode.ts
@@ -0,0 +1,131 @@
+import { AllowedMentions, BushCommand, BushMessage } from '@lib';
+import { AkairoMessage } from 'discord-akairo';
+import { MessageEmbed } from 'discord.js';
+
+const encodingTypesArray = ['ascii', 'utf8', 'utf-8', 'utf16le', 'ucs2', 'ucs-2', 'base64', 'latin1', 'binary', 'hex'];
+const encodingTypesString = '`ascii`, `utf8`, `utf-8`, `utf16le`, `ucs2`, `ucs-2`, `base64`, `latin1`, `binary`, `hex`';
+
+export default class DecodeCommand extends BushCommand {
+ public constructor() {
+ super('decode', {
+ aliases: ['decode', 'encode'],
+ category: 'utilities',
+ description: {
+ content: 'Decode / encode.',
+ usage: 'decode <from> <to> <data>',
+ examples: ['decode base64 ascii TmVyZApJbWFnaW5lIGRlY29kaW5nIHRoaXMgbG1hbw==']
+ },
+ args: [
+ {
+ id: 'from',
+ type: encodingTypesArray,
+ prompt: {
+ start: 'What is the encoding of the original data?',
+ retry: `{error} Choose one of the following ${encodingTypesString} for the encoding of the original data.`
+ }
+ },
+ {
+ id: 'to',
+ type: encodingTypesArray,
+ prompt: {
+ start: 'What would you like the encoding of the resulting data to be?',
+ retry: `{error} Choose one of the following ${encodingTypesString} for the encoding of the resulting data.`
+ }
+ },
+ {
+ id: 'data',
+ type: 'string',
+ match: 'restContent',
+ prompt: {
+ start: 'What would you to decode.',
+ retry: '{error} Choose a valid string to decode.'
+ }
+ }
+ ],
+ clientPermissions: ['SEND_MESSAGES'],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'from',
+ description: 'The type of data you are inputting.',
+ type: 'STRING',
+ choices: [
+ { name: 'ascii', value: 'ascii' },
+ { name: 'utf8', value: 'utf8' },
+ { name: 'utf-8', value: 'utf-8' },
+ { name: 'utf16le', value: 'utf16le' },
+ { name: 'ucs2', value: 'ucs2' },
+ { name: 'ucs-2', value: 'ucs-2' },
+ { name: 'base64', value: 'base64' },
+ { name: 'latin1', value: 'latin1' },
+ { name: 'binary', value: 'binary' },
+ { name: 'hex', value: 'hex' }
+ ]
+ },
+ {
+ name: 'to',
+ description: 'The type of data you want the output to be.',
+ type: 'STRING',
+ choices: [
+ { name: 'ascii', value: 'ascii' },
+ { name: 'utf8', value: 'utf8' },
+ { name: 'utf-8', value: 'utf-8' },
+ { name: 'utf16le', value: 'utf16le' },
+ { name: 'ucs2', value: 'ucs2' },
+ { name: 'ucs-2', value: 'ucs-2' },
+ { name: 'base64', value: 'base64' },
+ { name: 'latin1', value: 'latin1' },
+ { name: 'binary', value: 'binary' },
+ { name: 'hex', value: 'hex' }
+ ]
+ },
+ {
+ name: 'data',
+ description: 'What you would like to decode.',
+ type: 'STRING'
+ }
+ ]
+ });
+ }
+
+ public async exec(
+ message: BushMessage,
+ { from, to, data }: { from: BufferEncoding; to: BufferEncoding; data: string }
+ ): Promise<unknown> {
+ const encodeOrDecode = message?.util?.parsed?.alias?.charAt(0).toUpperCase() + message?.util?.parsed?.alias?.slice(1);
+ const decodedEmbed = new MessageEmbed()
+ .setTitle(`${encodeOrDecode} Information`)
+ .addField('📥 Input', await this.client.util.codeblock(data, 1024));
+ try {
+ const decoded = Buffer.from(data, from).toString(to);
+ decodedEmbed
+ .setColor(this.client.util.colors.success)
+ .addField('📤 Output', await this.client.util.codeblock(decoded, 1024));
+ } catch (error) {
+ decodedEmbed
+ .setColor(this.client.util.colors.error)
+ .addField(`📤 Error ${encodeOrDecode.slice(1)}ing`, await this.client.util.codeblock(error.stack, 1024));
+ }
+ return await message.util.send({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() });
+ }
+
+ public async execSlash(
+ message: AkairoMessage,
+ { from, to, data }: { from: BufferEncoding; to: BufferEncoding; data: string }
+ ): Promise<unknown> {
+ const decodedEmbed = new MessageEmbed()
+ .setTitle(`Decoded Information`)
+ .addField('📥 Input', await this.client.util.codeblock(data, 1024));
+ try {
+ const decoded = Buffer.from(data, from).toString(to);
+ decodedEmbed
+ .setColor(this.client.util.colors.success)
+ .addField('📤 Output', await this.client.util.codeblock(decoded, 1024));
+ } catch (error) {
+ decodedEmbed
+ .setColor(this.client.util.colors.error)
+ .addField(`📤 Error decoding`, await this.client.util.codeblock(error.stack, 1024));
+ }
+ return await message.interaction.reply({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() });
+ }
+}
diff --git a/src/commands/utilities/serverStatus.ts b/src/commands/utilities/serverStatus.ts
new file mode 100644
index 0000000..a20f0b2
--- /dev/null
+++ b/src/commands/utilities/serverStatus.ts
@@ -0,0 +1,58 @@
+import { MessageEmbed } from 'discord.js';
+import got from 'got';
+import { BushCommand, BushMessage } from '../../lib';
+
+export default class ServerStatusCommand extends BushCommand {
+ public constructor() {
+ super('serverstatus', {
+ aliases: ['serverstatus', 'ss'],
+ category: 'utilities',
+ description: {
+ usage: 'serverstatus',
+ examples: ['serverstatus', 'ss'],
+ content: "Gives the status of moulberry's server"
+ },
+ ratelimit: 4,
+ cooldown: 4000,
+ clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
+ slash: true
+ });
+ }
+
+ public async exec(message: BushMessage): Promise<void> {
+ const msgEmbed: MessageEmbed = new MessageEmbed()
+ .setTitle('Server status')
+ .setDescription(`Checking server:\n${this.client.util.emojis.loading}`)
+ .setColor(this.client.util.colors.default)
+ .setFooter('Checking https://moulberry.codes/lowestbin.json');
+ await message.util.reply({ embeds: [msgEmbed] });
+ let main;
+ try {
+ await got.get('https://moulberry.codes/lowestbin.json').json();
+ main = this.client.util.emojis.success;
+ } catch (e) {
+ main = this.client.util.emojis.error;
+ }
+ await message.util.edit({ embeds: [msgEmbed.setDescription(`Checking server:\n${main}`)] });
+ if (main == this.client.util.emojis.success) {
+ await message.util.edit({
+ embeds: [
+ msgEmbed
+ .addField('Status', 'The server is online, all features related to prices will likely work.')
+ .setColor(this.client.util.colors.success)
+ ]
+ });
+ } else {
+ await message.util.edit({
+ embeds: [
+ msgEmbed
+ .addField(
+ 'Status',
+ "It appears Moulberry's server is offline, this means that everything related to prices will likely not work."
+ )
+ .setColor(this.client.util.colors.error)
+ ]
+ });
+ }
+ }
+}
diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts
new file mode 100644
index 0000000..0b96ce1
--- /dev/null
+++ b/src/commands/utilities/uuid.ts
@@ -0,0 +1,43 @@
+import { Constants } from 'discord-akairo';
+import { BushCommand, BushMessage } from '../../lib';
+
+export default class UuidCommand extends BushCommand {
+ public constructor() {
+ super('uuid', {
+ aliases: ['uuid'],
+ category: 'utilities',
+ description: {
+ content: "Find someone's minecraft uuid",
+ usage: 'uuid <ign>',
+ examples: ['uuid ironm00n']
+ },
+ args: [
+ {
+ id: 'ign',
+ type: /\w{1,16}/im,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What ign would you like to find the uuid of?',
+ retry: '{error} Choose a valid ign.',
+ optional: false
+ }
+ }
+ ],
+ cooldown: 4000,
+ ratelimit: 1,
+ clientPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public async exec(message: BushMessage, { ign }: { ign: { match: any[]; matches: any[] } }): Promise<unknown> {
+ if (!ign) return await message.util.reply(`${this.client.util.emojis.error} Please enter a valid ign.`);
+ const readableIGN = ign.match[0];
+ try {
+ const uuid = await this.client.util.findUUID(readableIGN);
+ return await message.util.reply(`The uuid for \`${readableIGN}\` is \`${uuid}\``);
+ } catch (e) {
+ return await message.util.reply(`${this.client.util.emojis.error} Could not find an uuid for \`${readableIGN}\`.`);
+ }
+ }
+}
diff --git a/src/commands/utilities/viewraw.ts b/src/commands/utilities/viewraw.ts
new file mode 100644
index 0000000..7642b2a
--- /dev/null
+++ b/src/commands/utilities/viewraw.ts
@@ -0,0 +1,75 @@
+import { Argument, Constants } from 'discord-akairo';
+import { DMChannel, Message, MessageEmbed, NewsChannel, Snowflake, TextChannel } from 'discord.js';
+import { inspect } from 'util';
+import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
+
+export default class ViewRawCommand extends BushCommand {
+ public constructor() {
+ super('viewraw', {
+ aliases: ['viewraw'],
+ category: 'info',
+ clientPermissions: ['EMBED_LINKS'],
+ description: {
+ usage: 'viewraw <message id> <channel>',
+ examples: ['viewraw 322862723090219008'],
+ content: 'Gives information about a specified user.'
+ },
+ args: [
+ {
+ id: 'message',
+ type: Argument.union(Constants.ArgumentTypes.MESSAGE, Constants.ArgumentTypes.BIGINT),
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What message would you like to view?',
+ retry: '{error} Choose a valid message.',
+ optional: false
+ }
+ },
+ {
+ id: 'channel',
+ type: Constants.ArgumentTypes.CHANNEL,
+ match: Constants.ArgumentMatches.PHRASE,
+ prompt: {
+ start: 'What channel is the message in?',
+ retry: '{error} Choose a valid channel.',
+ optional: true
+ },
+ default: (m) => m.channel
+ },
+ {
+ id: 'json',
+ match: Constants.ArgumentMatches.FLAG,
+ flag: '--json'
+ }
+ ]
+ });
+ }
+
+ public async exec(
+ message: BushMessage | BushSlashMessage,
+ args: { message: Message | BigInt; channel: TextChannel | NewsChannel | DMChannel; json?: boolean }
+ ): Promise<unknown> {
+ let newMessage: Message | 0;
+ if (!(typeof args.message === 'object')) {
+ newMessage = await args.channel.messages.fetch(`${args.message}` as Snowflake).catch(() => {
+ return 0;
+ });
+ if (!newMessage) {
+ return await message.util.reply(
+ `${this.client.util.emojis.error} There was an error fetching that message, try supplying a channel.`
+ );
+ }
+ } else {
+ newMessage = args.message as Message;
+ }
+ const content = args.json ? inspect(newMessage.toJSON()) || '[No Content]' : newMessage.content || '[No Content]';
+ const messageEmbed = new MessageEmbed()
+ .setFooter(newMessage.author.tag, newMessage.author.avatarURL({ dynamic: true }))
+ .setTimestamp(newMessage.createdTimestamp)
+ .setColor(newMessage.member?.roles?.color?.color || this.client.util.colors.default)
+ .setTitle('Raw Message Information')
+ .setDescription(await this.client.util.codeblock(content, 2048, 'js'));
+
+ return await message.util.reply({ embeds: [messageEmbed] });
+ }
+}
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index cb6a1de..3b14200 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -25,6 +25,7 @@ import { Guild as GuildModel } from '../../models/Guild';
import { Level } from '../../models/Level';
import { ModLog } from '../../models/ModLog';
import { Mute } from '../../models/Mute';
+import { PunishmentRole } from '../../models/PunishmentRole';
import { StickyRole } from '../../models/StickyRole';
import { AllowedMentions } from '../../utils/AllowedMentions';
import { BushCache } from '../../utils/BushCache';
@@ -260,6 +261,7 @@ export class BushClient extends AkairoClient {
Mute.initModel(this.db);
Level.initModel(this.db);
StickyRole.initModel(this.db);
+ PunishmentRole.initModel(this.db);
await this.db.sync({ alter: true }); // Sync all tables to fix everything if updated
await this.console.success('Startup', `Successfully connected to <<database>>.`, false);
} catch (e) {
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index b3c9953..6538ebf 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -43,7 +43,7 @@ import {
} from 'discord.js';
import got from 'got';
import humanizeDuration from 'humanize-duration';
-import Op from 'sequelize/types/lib/operators';
+import { Op } from 'sequelize';
import { promisify } from 'util';
interface hastebinRes {
@@ -99,6 +99,11 @@ interface punishmentModels {
role: PunishmentRole;
}
+interface MojangProfile {
+ username: string;
+ uuid: string;
+}
+
export class BushClientUtil extends ClientUtil {
/** The client of this ClientUtil */
public declare readonly client: BushClient;
@@ -302,6 +307,7 @@ export class BushClientUtil extends ClientUtil {
text: string | null = null,
deleteOnExit?: boolean
): Promise<void> {
+ const paginateEmojis = this.paginateEmojis;
if (deleteOnExit === undefined) deleteOnExit = true;
if (embeds.length === 1) {
@@ -374,7 +380,6 @@ export class BushClientUtil extends ClientUtil {
?.update({ content: text, embeds: [embeds[curPage]], components: [getPaginationRow()] })
.catch(() => undefined);
}
- const paginateEmojis = this.paginateEmojis;
function getPaginationRow(disableAll = false): MessageActionRow {
return new MessageActionRow().addComponents(
new MessageButton({
@@ -408,6 +413,7 @@ export class BushClientUtil extends ClientUtil {
/** Sends a message with a button for the user to delete it. */
public async sendWithDeleteButton(message: BushMessage | BushSlashMessage, options: MessageOptions): Promise<void> {
+ const paginateEmojis = this.paginateEmojis;
updateOptions();
const msg = await message.util.reply(options as MessageOptions & { split?: false });
const filter = (interaction: ButtonInteraction) => interaction.customId == 'paginate__stop' && interaction.message == msg;
@@ -429,7 +435,6 @@ export class BushClientUtil extends ClientUtil {
await msg.edit(options as MessageEditOptions).catch(() => undefined);
});
- const paginateEmojis = this.paginateEmojis;
function updateOptions(edit?: boolean, disable?: boolean) {
if (edit == undefined) edit = false;
if (disable == undefined) disable = false;
@@ -644,7 +649,7 @@ export class BushClientUtil extends ClientUtil {
if (!getCaseNumber) return { log: saveResult, caseNum: null };
- const caseNum = (await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild } }))?.length;
+ const caseNum = (await ModLog.findAll({ where: { type: options.type, user: user, guild: guild } }))?.length;
return { log: saveResult, caseNum };
}
@@ -703,6 +708,7 @@ export class BushClientUtil extends ClientUtil {
public async findExpiredEntries<K extends keyof punishmentModels>(type: K): Promise<punishmentModels[K][]> {
const dbModel = this.findPunishmentModel(type);
+ console.log(dbModel);
//@ts-ignore: stfu idc
return await dbModel.findAll({
where: {
@@ -733,4 +739,33 @@ export class BushClientUtil extends ClientUtil {
public humanizeDuration(duration: number): string {
return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2 });
}
+
+ public async findUUID(player: string): Promise<string> {
+ try {
+ const raw = await got.get(`https://api.ashcon.app/mojang/v2/user/${player}`);
+ let profile: MojangProfile;
+ if (raw.statusCode == 200) {
+ profile = JSON.parse(raw.body);
+ } else {
+ throw 'invalid player';
+ }
+
+ if (raw.statusCode == 200 && profile && profile.uuid) {
+ return profile.uuid.replace(/-/g, '');
+ } else {
+ throw `Could not fetch the uuid for ${player}.`;
+ }
+ } catch (e) {
+ throw 'An error has occurred.';
+ }
+ }
+
+ public hexToRgb(hex: string): string {
+ const arrBuff = new ArrayBuffer(4);
+ const vw = new DataView(arrBuff);
+ vw.setUint32(0, parseInt(hex, 16), false);
+ const arrByte = new Uint8Array(arrBuff);
+
+ return arrByte[1] + ', ' + arrByte[2] + ', ' + arrByte[3];
+ }
}
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 4bb1c2c..fb85d7f 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -80,32 +80,36 @@ export class BushGuildMember extends GuildMember {
}
public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number }> {
+ const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
// add modlog entry
- const { log, caseNum } = await this.client.util
+ const result = await this.client.util
.createModLogEntry(
{
type: ModLogType.WARN,
user: this,
- moderator: options.moderator || this.client.user.id,
+ moderator: moderator.id,
reason: options.reason,
guild: this.guild
},
true
)
- .catch(() => null);
- if (!log) return { result: 'error creating modlog entry', caseNum: null };
+ .catch((e) => {
+ this.client.console.error('warn', e, true, 1);
+ return { log: null, caseNum: null };
+ });
+ if (!result || !result.log) return { result: 'error creating modlog entry', caseNum: null };
// dm user
- const ending = this.guild.getSetting('punishmentEnding');
+ const ending = await 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 };
+ if (!dmSuccess) return { result: 'failed to dm', caseNum: result.caseNum };
- return { result: 'success', caseNum };
+ return { result: 'success', caseNum: result.caseNum };
}
public async addRole(options: AddRoleOptions): Promise<AddRoleResponse> {
@@ -232,7 +236,7 @@ export class BushGuildMember extends GuildMember {
if (!punishmentEntrySuccess) return 'error creating mute entry';
// dm user
- const ending = this.guild.getSetting('punishmentEnding');
+ const ending = await this.guild.getSetting('punishmentEnding');
const dmSuccess = await this.send({
content: `You have been muted ${
options.duration ? 'for ' + this.client.util.humanizeDuration(options.duration) : 'permanently'
@@ -300,7 +304,7 @@ export class BushGuildMember extends GuildMember {
const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
// dm user
- const ending = this.guild.getSetting('punishmentEnding');
+ const ending = await 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}` : ''
@@ -333,7 +337,7 @@ export class BushGuildMember extends GuildMember {
const moderator = this.client.users.cache.get(this.client.users.resolveId(options.moderator || this.client.user));
// dm user
- const ending = this.guild.getSetting('punishmentEnding');
+ const ending = await this.guild.getSetting('punishmentEnding');
const dmSuccess = await this.send({
content: `You have been banned ${
options.duration ? 'for ' + this.client.util.humanizeDuration(options.duration) : 'permanently'
diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index f6aa1a4..43fcd64 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -6,17 +6,13 @@ import { BaseModel } from './BaseModel';
export interface GuildModel {
id: string;
prefix: string;
- autoPublishChannels: string[];
+ autoPublishChannels: Snowflake[];
blacklistedChannels: Snowflake[];
welcomeChannel: Snowflake;
muteRole: Snowflake;
punishmentEnding: string;
}
-// export type GuildModelCreationAttributes = Optional<
-// GuildModel,
-// 'prefix' | 'autoPublishChannels' | 'blacklistedChannels' | 'welcomeChannel' | 'muteRole' | 'punishmentEnding'
-// >;
export interface GuildModelCreationAttributes {
id: string;
prefix?: string;
@@ -30,7 +26,7 @@ export interface GuildModelCreationAttributes {
export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel {
id!: string;
prefix!: string;
- autoPublishChannels: string[];
+ autoPublishChannels: Snowflake[];
blacklistedChannels: Snowflake[];
welcomeChannel: Snowflake;
muteRole: Snowflake;
diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts
index 7e6013d..747c6d6 100644
--- a/src/lib/utils/BushConstants.ts
+++ b/src/lib/utils/BushConstants.ts
@@ -152,7 +152,8 @@ export class BushConstants {
VOICE: '<:voice:853375566735212584>',
STAGE: '<:stage:853375583521210468>',
STORE: '<:store:853375601175691266>',
- CATEGORY: '<:category:853375615260819476>'
+ CATEGORY: '<:category:853375615260819476>',
+ THREAD: '<:thread:865033845753249813>'
},
userFlags: {
diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts
index ffcc408..f3d2218 100644
--- a/src/listeners/commands/commandMissingPermissions.ts
+++ b/src/listeners/commands/commandMissingPermissions.ts
@@ -16,15 +16,6 @@ export default class CommandMissingPermissionsListener extends BushListener {
type: 'client' | 'user',
missing: Array<PermissionString>
): Promise<void> {
- this.client.console.debug(message.guild.me.permissions.toArray());
- missing.forEach((permission) => {
- this.client.console.debug(message.guild.me.permissions.has(permission));
- });
- message.guild.me.permissions;
- this.client.console.debug(type);
- this.client.console.debug(command.clientPermissions);
- this.client.console.debug(command.userPermissions);
- this.client.console.debug(missing);
const niceMissing = [];
missing.forEach((missing) => {
if (this.client.consts.mappings.permissions[missing]) {
@@ -34,7 +25,7 @@ export default class CommandMissingPermissionsListener extends BushListener {
}
});
- const discordFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '`'), 'and', '');
+ const discordFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '**'), 'and', '');
const consoleFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '<<', '>>'), 'and', '');
this.client.console.info(
'CommandMissingPermissions',
@@ -55,7 +46,7 @@ export default class CommandMissingPermissionsListener extends BushListener {
.reply(
`${this.client.util.emojis.error} You are missing the ${discordFormat} permission${
missing.length ? 's' : ''
- } required for the \`${command?.id}\` command.`
+ } required for the **${command?.id}** command.`
)
.catch(() => {});
}