diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-07-14 21:22:09 -0400 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-07-14 21:22:09 -0400 |
commit | 53d2b18f7f73d5696fb7cd86d1c164a790dfdcc3 (patch) | |
tree | f95f23aad382879b35860d4d3be3642068fac8a2 | |
parent | eaaae08aeee1fa16a4e1ad0b26fceb42885bfcde (diff) | |
download | tanzanite-53d2b18f7f73d5696fb7cd86d1c164a790dfdcc3.tar.gz tanzanite-53d2b18f7f73d5696fb7cd86d1c164a790dfdcc3.tar.bz2 tanzanite-53d2b18f7f73d5696fb7cd86d1c164a790dfdcc3.zip |
started moving over some other commands
42 files changed, 1800 insertions, 113 deletions
@@ -50,6 +50,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["discord-akairo", "https://github.com/NotEnoughUpdates/discord-akairo.git#commit=65d760faed662e816ced3ba8ae7520e513e37ee2"], ["discord-api-types", "npm:0.19.0-next.f393ba520d7d6d2aacaca7b3ca5d355fab614f6e"], ["discord.js", "https://github.com/NotEnoughUpdates/discord.js.git#commit=20c84839fa43aad6c47ff6ffb11b34cc785e920b"], + ["discord.js-minesweeper", "npm:1.0.6"], ["esbuild", "npm:0.12.15"], ["eslint", "npm:7.30.0"], ["eslint-config-prettier", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:8.3.0"], @@ -932,6 +933,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["discord-akairo", "https://github.com/NotEnoughUpdates/discord-akairo.git#commit=65d760faed662e816ced3ba8ae7520e513e37ee2"], ["discord-api-types", "npm:0.19.0-next.f393ba520d7d6d2aacaca7b3ca5d355fab614f6e"], ["discord.js", "https://github.com/NotEnoughUpdates/discord.js.git#commit=20c84839fa43aad6c47ff6ffb11b34cc785e920b"], + ["discord.js-minesweeper", "npm:1.0.6"], ["esbuild", "npm:0.12.15"], ["eslint", "npm:7.30.0"], ["eslint-config-prettier", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:8.3.0"], @@ -1469,6 +1471,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["discord.js-minesweeper", [ + ["npm:1.0.6", { + "packageLocation": "./.yarn/cache/discord.js-minesweeper-npm-1.0.6-a6247da3d3-8ceaf0c40e.zip/node_modules/discord.js-minesweeper/", + "packageDependencies": [ + ["discord.js-minesweeper", "npm:1.0.6"] + ], + "linkType": "HARD", + }] + ]], ["doctrine", [ ["npm:3.0.0", { "packageLocation": "./.yarn/cache/doctrine-npm-3.0.0-c6f1615f04-fd7673ca77.zip/node_modules/doctrine/", diff --git a/.vscode/launch.json b/.vscode/launch.json index d75268c..c6666c9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,9 +1,15 @@ { - "version": "0.2.0", "configurations": [ { "command": "yarn dev", - "name": "Debug BushBot", + "name": "BushBot (tsc)", + "request": "launch", + "type": "node-terminal", + "skipFiles": ["<node_internals>/**", "**/Yarn/**", "**/.pnp.js", "**/.yarn/releases/**"] + }, + { + "command": "yarn start", + "name": "BushBot (esbuild)", "request": "launch", "type": "node-terminal", "skipFiles": ["<node_internals>/**", "**/Yarn/**", "**/.pnp.js", "**/.yarn/releases/**"] diff --git a/package.json b/package.json index 2b50aba..ba908f8 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "discord-akairo": "NotEnoughUpdates/discord-akairo", "discord-api-types": "0.19.0-next.f393ba520d7d6d2aacaca7b3ca5d355fab614f6e", "discord.js": "NotEnoughUpdates/discord.js", + "discord.js-minesweeper": "^1.0.6", "got": "^11.8.2", "humanize-duration": "^3.27.0", "madge": "^5.0.1", 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(() => {}); } @@ -683,6 +683,7 @@ __metadata: discord-akairo: NotEnoughUpdates/discord-akairo discord-api-types: 0.19.0-next.f393ba520d7d6d2aacaca7b3ca5d355fab614f6e discord.js: NotEnoughUpdates/discord.js + discord.js-minesweeper: ^1.0.6 esbuild: ^0.12.11 eslint: ^7.29.0 eslint-config-prettier: ^8.3.0 @@ -1134,6 +1135,13 @@ discord-akairo@NotEnoughUpdates/discord-akairo: languageName: node linkType: hard +"discord.js-minesweeper@npm:^1.0.6": + version: 1.0.6 + resolution: "discord.js-minesweeper@npm:1.0.6" + checksum: 8ceaf0c40e56d2be7b1ae00da5bb76cdf9f29e381930025bcabd2be7e2efe7b8c745073aceb880ae0a5307aacff8060f7441ccd761fc298eb14921209ad3562e + languageName: node + linkType: hard + discord.js@NotEnoughUpdates/discord.js: version: 13.0.0-dev resolution: "discord.js@https://github.com/NotEnoughUpdates/discord.js.git#commit=20c84839fa43aad6c47ff6ffb11b34cc785e920b" |