diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-07-17 10:25:46 -0400 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-07-17 10:25:46 -0400 |
commit | d1724227abfb8f0fcd9e573f7e9772cf0be8257a (patch) | |
tree | 52c9dbae1fbbbd3c777d9c16ab643c477141ae21 | |
parent | 53d2b18f7f73d5696fb7cd86d1c164a790dfdcc3 (diff) | |
download | tanzanite-d1724227abfb8f0fcd9e573f7e9772cf0be8257a.tar.gz tanzanite-d1724227abfb8f0fcd9e573f7e9772cf0be8257a.tar.bz2 tanzanite-d1724227abfb8f0fcd9e573f7e9772cf0be8257a.zip |
honestly no idea what I did at this point
57 files changed, 1551 insertions, 214 deletions
@@ -41,6 +41,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/humanize-duration", "npm:3.25.1"], ["@types/module-alias", "npm:2.0.1"], ["@types/node", "npm:14.17.5"], + ["@types/tinycolor2", "npm:1.4.3"], ["@types/uuid", "npm:8.3.1"], ["@typescript-eslint/eslint-plugin", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:4.28.2"], ["@typescript-eslint/parser", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:4.28.2"], @@ -54,6 +55,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["esbuild", "npm:0.12.15"], ["eslint", "npm:7.30.0"], ["eslint-config-prettier", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:8.3.0"], + ["fuse.js", "npm:6.4.6"], ["got", "npm:11.8.2"], ["humanize-duration", "npm:3.27.0"], ["madge", "npm:5.0.1"], @@ -65,6 +67,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["rimraf", "npm:3.0.2"], ["sequelize", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:6.6.5"], ["source-map-support", "npm:0.5.19"], + ["tinycolor2", "npm:1.4.2"], ["typescript", "patch:typescript@npm%3A4.2.4#~builtin<compat/typescript>::version=4.2.4&hash=d8b4e7"], ["uuid", "npm:8.3.2"] ], @@ -445,6 +448,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/tinycolor2", [ + ["npm:1.4.3", { + "packageLocation": "./.yarn/cache/@types-tinycolor2-npm-1.4.3-90e6bf0ed8-61984b2825.zip/node_modules/@types/tinycolor2/", + "packageDependencies": [ + ["@types/tinycolor2", "npm:1.4.3"] + ], + "linkType": "HARD", + }] + ]], ["@types/uuid", [ ["npm:8.3.1", { "packageLocation": "./.yarn/cache/@types-uuid-npm-8.3.1-4239b14bac-b41bdc5e86.zip/node_modules/@types/uuid/", @@ -924,6 +936,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/humanize-duration", "npm:3.25.1"], ["@types/module-alias", "npm:2.0.1"], ["@types/node", "npm:14.17.5"], + ["@types/tinycolor2", "npm:1.4.3"], ["@types/uuid", "npm:8.3.1"], ["@typescript-eslint/eslint-plugin", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:4.28.2"], ["@typescript-eslint/parser", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:4.28.2"], @@ -937,6 +950,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["esbuild", "npm:0.12.15"], ["eslint", "npm:7.30.0"], ["eslint-config-prettier", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:8.3.0"], + ["fuse.js", "npm:6.4.6"], ["got", "npm:11.8.2"], ["humanize-duration", "npm:3.27.0"], ["madge", "npm:5.0.1"], @@ -948,6 +962,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["rimraf", "npm:3.0.2"], ["sequelize", "virtual:d7ae587dddcefd495158f5c047acecbca3203324d75e681c7d8657c07f901f74e152f0b39978f7428d3a91daad7b5020c47ece28de69c22fcbd49d04707bf15c#npm:6.6.5"], ["source-map-support", "npm:0.5.19"], + ["tinycolor2", "npm:1.4.2"], ["typescript", "patch:typescript@npm%3A4.2.4#~builtin<compat/typescript>::version=4.2.4&hash=d8b4e7"], ["uuid", "npm:8.3.2"] ], @@ -1940,6 +1955,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["fuse.js", [ + ["npm:6.4.6", { + "packageLocation": "./.yarn/cache/fuse.js-npm-6.4.6-0fa81ef443-012dfacdc9.zip/node_modules/fuse.js/", + "packageDependencies": [ + ["fuse.js", "npm:6.4.6"] + ], + "linkType": "HARD", + }] + ]], ["get-amd-module-type", [ ["npm:3.0.0", { "packageLocation": "./.yarn/cache/get-amd-module-type-npm-3.0.0-2fcd610976-a2df61d329.zip/node_modules/get-amd-module-type/", @@ -3792,6 +3816,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["tinycolor2", [ + ["npm:1.4.2", { + "packageLocation": "./.yarn/cache/tinycolor2-npm-1.4.2-462ba30c26-57ed262e08.zip/node_modules/tinycolor2/", + "packageDependencies": [ + ["tinycolor2", "npm:1.4.2"] + ], + "linkType": "HARD", + }] + ]], ["to-fast-properties", [ ["npm:2.0.0", { "packageLocation": "./.yarn/cache/to-fast-properties-npm-2.0.0-0dc60cc481-be2de62fe5.zip/node_modules/to-fast-properties/", diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d3de569..d56ae08 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,8 @@ "dbaeumer.vscode-eslint", "eamodio.gitlens", "esbenp.prettier-vscode", - "streetsidesoftware.code-spell-checker" + "streetsidesoftware.code-spell-checker", + "github.vscode-pull-request-github", + "ckolkman.vscode-postgres" ] -} +}
\ No newline at end of file diff --git a/package.json b/package.json index ba908f8..2a4b4a1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "scripts": { "build-esbuild": "yarn rimraf dist && yarn esbuild --sourcemap=inline --minify-whitespace --minify-syntax --outdir=dist --platform=node --target=es2020 --format=cjs --log-level=warning src/**/*.ts", "build-tsc": "yarn rimraf dist && yarn tsc", - "start": "yarn build-esbuild && node --trace-warnings -r source-map-support/register dist/bot.js", + "_start": "yarn build-esbuild && node --trace-warnings -r source-map-support/register dist/bot.js", + "start": "yarn build-tsc && node --trace-warnings -r source-map-support/register dist/bot.js", "dev": "yarn build-tsc && node --trace-warnings -r source-map-support/register dist/bot.js", "test": "yarn lint && yarn tsc --noEmit", "format": "yarn prettier . --write", @@ -26,6 +27,7 @@ "@types/humanize-duration": "^3", "@types/module-alias": "^2", "@types/node": "^14.14.22", + "@types/tinycolor2": "^1", "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.14.1", "@typescript-eslint/parser": "^4.14.1", @@ -46,6 +48,7 @@ "discord-api-types": "0.19.0-next.f393ba520d7d6d2aacaca7b3ca5d355fab614f6e", "discord.js": "NotEnoughUpdates/discord.js", "discord.js-minesweeper": "^1.0.6", + "fuse.js": "^6.4.6", "got": "^11.8.2", "humanize-duration": "^3.27.0", "madge": "^5.0.1", @@ -54,6 +57,7 @@ "pg": "^8.5.1", "pg-hstore": "^2.3.3", "sequelize": "^6.5.0", + "tinycolor2": "^1.4.2", "uuid": "^8.3.2" }, "eslintConfig": { diff --git a/src/commands/_fake-command/test.ts b/src/commands/_fake-command/test.ts new file mode 100644 index 0000000..8eeca9e --- /dev/null +++ b/src/commands/_fake-command/test.ts @@ -0,0 +1,18 @@ +import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; + +export default class TestCommand extends BushCommand { + public constructor() { + super('test', { + category: 'fake-commands', + description: { content: '', examples: '', usage: '' }, + condition: (message: BushMessage) => { + if (message.content.toLowerCase().includes('ironmoon')) return true; + else return false; + }, + completelyHide: true + }); + } + public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { + return await message.util.reply('Your message included the word ironmoon.'); + } +} diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts new file mode 100644 index 0000000..249789d --- /dev/null +++ b/src/commands/admin/channelPermissions.ts @@ -0,0 +1,97 @@ +import { Argument, Constants } from 'discord-akairo'; +import { GuildChannel, GuildMember, MessageEmbed, Role } from 'discord.js'; +import { BushCommand, BushMessage } from '../../lib'; + +export default class ChannelPermissionsCommand extends BushCommand { + public constructor() { + super('channelpermissions', { + aliases: ['channelperms', 'cperms', 'cperm', 'chanperms', 'chanperm', 'channelpermissions'], + category: 'admin', + typing: true, + description: { + content: 'Use to mass change the channel ', + usage: 'ChannelPerms <role_id> <perm> <state>', + examples: ['ChannelPerms 783794633129197589 read_messages deny'] + }, + args: [ + { + id: 'target', + type: Argument.union(Constants.ArgumentTypes.ROLE, Constants.ArgumentTypes.MEMBER), + match: Constants.ArgumentMatches.PHRASE, + prompt: { + start: 'What user/role would you like to change?', + retry: 'Invalid response. What user/role would you like to change?' + } + }, + { + id: 'permission', + type: 'permission', + match: Constants.ArgumentMatches.PHRASE, + prompt: { + start: 'What permission would you like to change?', + retry: '{error} Choose a valid permission.' + } + }, + { + id: 'state', + type: [ + ['true', '1', 'yes', 'enable', 'allow'], + ['false', '0', 'no', 'disable', 'disallow', 'deny'], + ['neutral', 'remove', 'none'] + ], + match: Constants.ArgumentMatches.PHRASE, + prompt: { + start: 'What should that permission be set to?', + retry: '{error} Set the state to either `enable`, `disable`, or `remove`.' + } + } + ], + ratelimit: 4, + cooldown: 4000, + clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES'], + userPermissions: ['ADMINISTRATOR'], + channel: 'guild' + }); + } + + public async exec( + message: BushMessage, + { + target, + permission, + state + }: { + target: Role | GuildMember; + permission: string; + state: 'true' | 'false' | 'neutral'; + } + ): Promise<void> { + const failedChannels = []; + for (const channel of message.guild.channels.cache.array()) { + try { + if (channel.isThread()) return; + if (channel.permissionsLocked) return; + const permissionState = state === 'true' ? true : state === 'false' ? false : null; + await channel.permissionOverwrites.create( + target.id, + { [permission]: permissionState }, + { reason: 'Changing overwrites for mass channel channel perms command' } + ); + } catch (e) { + this.client.console.debug(e.stack); + failedChannels.push(channel); + } + } + const failure = failedChannels.map((e: GuildChannel) => `<#${e.id}>`).join(' '); + if (failure.length > 2000) { + const paginate: MessageEmbed[] = []; + for (let i = 0; i < failure.length; i += 2000) { + paginate.push(new MessageEmbed().setDescription(failure.substring(i, Math.min(failure.length, i + 2000)))); + } + const normalMessage = `Finished changing perms! Failed channels:`; + this.client.util.buttonPaginate(message, paginate, normalMessage); + } else { + await message.util.reply({ content: `Finished changing perms! Failed channels:`, embeds: [{ description: failure }] }); + } + } +} diff --git a/src/commands/config/autoPublishChannel.ts b/src/commands/config/autoPublishChannel.ts index 421e030..a2692e2 100644 --- a/src/commands/config/autoPublishChannel.ts +++ b/src/commands/config/autoPublishChannel.ts @@ -1,4 +1,4 @@ -import { BushCommand, BushMessage } from '@lib'; +import { AllowedMentions, BushCommand, BushMessage } from '@lib'; import { Channel } from 'discord.js'; export default class AutoPublishChannelCommand extends BushCommand { @@ -40,14 +40,17 @@ export default class AutoPublishChannelCommand extends BushCommand { public async exec(message: BushMessage, { channel }: { channel: Channel }): Promise<unknown> { const autoPublishChannels = await message.guild.getSetting('autoPublishChannels'); - autoPublishChannels.includes(channel.id) - ? autoPublishChannels.splice(autoPublishChannels.indexOf(channel.id), 1) - : autoPublishChannels.push(channel.id); - await message.guild.setSetting('autoPublishChannels', autoPublishChannels); - return await message.util.reply( - `${this.client.util.emojis.success} Successfully ${ - autoPublishChannels.includes(channel.id) ? 'disabled' : 'enabled' - } auto publishing in <#${channel.id}>.` + const newValue = this.client.util.addOrRemoveFromArray( + autoPublishChannels.includes(channel.id) ? 'remove' : 'add', + autoPublishChannels, + channel.id ); + await message.guild.setSetting('autoPublishChannels', newValue); + return await message.util.reply({ + content: `${this.client.util.emojis.success} Successfully ${ + autoPublishChannels.includes(channel.id) ? 'disabled' : 'enabled' + } auto publishing in <#${channel.id}>.`, + allowedMentions: AllowedMentions.none() + }); } } diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts new file mode 100644 index 0000000..4706041 --- /dev/null +++ b/src/commands/config/blacklist.ts @@ -0,0 +1,132 @@ +import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, Global } from '@lib'; +import { Argument } from 'discord-akairo'; +import { Channel, User } from 'discord.js'; + +export default class BlacklistCommand extends BushCommand { + public constructor() { + super('blacklist', { + aliases: ['blacklist', 'unblacklist'], + category: 'config', + description: { + content: 'A command to blacklist users and channels.', + usage: 'blacklist|unblacklist <user|channel>', + examples: ['blacklist @user', 'unblacklist #channel'] + }, + args: [ + { + id: 'target', + type: Argument.union('channel', 'user'), + match: 'phrase', + prompt: { + start: 'What channel or user that you would like to blacklist/unblacklist?', + retry: '{error} Pick a valid command.', + optional: false + } + }, + { + id: 'global', + match: 'flag', + flag: '--global' + } + ], + slash: true, + slashOptions: [ + { + name: 'action', + description: 'Would you like to add or remove someone or something from/to the blacklist?', + type: 'STRING', + choices: [ + { + name: 'blacklist', + value: 'blacklist' + }, + { + name: 'unblacklist', + value: 'unblacklist' + } + ], + required: true + }, + { + name: 'target', + description: 'What channel or user that you would like to blacklist/unblacklist?', + type: 'STRING', + required: true + } + ], + channel: 'guild', + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + }); + } + + public async exec( + message: BushMessage | BushSlashMessage, + args: { action: 'blacklist' | 'unblacklist'; target: Channel | User | string; global: boolean } + ): Promise<unknown> { + let action: 'blacklist' | 'unblacklist' | 'toggle' = + args.action ?? (message?.util?.parsed?.alias as 'blacklist' | 'unblacklist') ?? 'toggle'; + const global = args.global && message.author.isOwner(); + const target = + typeof args.target === 'string' + ? (await Argument.cast('channel', this.client.commandHandler.resolver, message as BushMessage, args.target)) ?? + (await Argument.cast('user', this.client.commandHandler.resolver, message as BushMessage, args.target)) + : args.target; + if (!target) return await message.util.reply(`${this.client.util.emojis.error} Choose a valid channel or user.`); + const targetID = target.id; + + if (global) { + if (action === 'toggle') { + const blacklistedUsers = (await Global.findByPk(this.client.config.dev ? 'development' : 'production')) + .blacklistedUsers; + const blacklistedChannels = (await Global.findByPk(this.client.config.dev ? 'development' : 'production')) + .blacklistedChannels; + action = blacklistedUsers.includes(targetID) || blacklistedChannels.includes(targetID) ? 'unblacklist' : 'blacklist'; + } + const success = await this.client.util + .insertOrRemoveFromGlobal( + action === 'blacklist' ? 'add' : 'remove', + target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels', + targetID + ) + .catch(() => false); + if (!success) + return await message.util.reply({ + content: `${this.client.util.emojis.error} There was an error globally **${action}ing** ${ + target?.tag ?? target.name + }.`, + allowedMentions: AllowedMentions.none() + }); + else + return await message.util.reply({ + content: `${this.client.util.emojis.success} Successfully **${action}ed** ${target?.tag ?? target.name} globally.`, + allowedMentions: AllowedMentions.none() + }); + // guild disable + } else { + const blacklistedChannels = await message.guild.getSetting('blacklistedChannels'); + const blacklistedUsers = await message.guild.getSetting('blacklistedUsers'); + if (action === 'toggle') { + action = blacklistedChannels.includes(targetID) ?? blacklistedUsers.includes(targetID) ? 'unblacklist' : 'blacklist'; + } + const newValue = this.client.util.addOrRemoveFromArray( + action === 'blacklist' ? 'add' : 'remove', + target instanceof User ? blacklistedUsers : blacklistedChannels, + targetID + ); + const success = await message.guild + .setSetting(target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels', newValue) + .catch(() => false); + if (!success) + return await message.util.reply({ + content: `${this.client.util.emojis.error} There was an error **${action}ing** ${target?.tag ?? target.name}.`, + allowedMentions: AllowedMentions.none() + }); + else + return await message.util.reply({ + content: `${this.client.util.emojis.success} Successfully **${action}ed** ${target?.tag ?? target.name}.`, + allowedMentions: AllowedMentions.none() + }); + } + } +} diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts new file mode 100644 index 0000000..007cdb1 --- /dev/null +++ b/src/commands/config/disable.ts @@ -0,0 +1,128 @@ +import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, Global } from '@lib'; + +export default class DisableCommand extends BushCommand { + public constructor() { + super('disable', { + aliases: ['disable', 'enable'], + category: 'config', + description: { + content: 'A command to disable and enable commands.', + usage: 'disable|enable <command>', + examples: ['enable ban', 'disable kick'] + }, + args: [ + { + id: 'command', + type: 'commandAlias', + match: 'phrase', + prompt: { + start: 'What command would you like to enable/disable?', + retry: '{error} Pick a valid command.', + optional: false + } + }, + { + id: 'global', + match: 'flag', + flag: '--global' + } + ], + slash: true, + slashOptions: [ + { + name: 'action', + description: 'Would you like to disable or enable a command?', + type: 'STRING', + choices: [ + { + name: 'enable', + value: 'enable' + }, + { + name: 'disable', + value: 'disable' + } + ], + required: true + }, + { + name: 'command', + description: 'What command would you like to enable/disable?', + type: 'STRING', + required: true + } + ], + channel: 'guild', + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + }); + } + + blacklistedCommands = ['eval', 'disable']; + + public async exec( + message: BushMessage | BushSlashMessage, + args: { action: 'enable' | 'disable'; command: BushCommand | string; global: boolean } + ): Promise<unknown> { + let action: 'disable' | 'enable' | 'toggle' = + args.action ?? (message?.util?.parsed?.alias as 'disable' | 'enable') ?? 'toggle'; + const global = args.global && message.author.isOwner(); + const commandID = (args.command as BushCommand).id; + + if (global) { + if (action === 'toggle') { + const disabledCommands = (await Global.findByPk(this.client.config.dev ? 'development' : 'production')) + .disabledCommands; + action = disabledCommands.includes(commandID) ? 'disable' : 'enable'; + } + const success = await this.client.util + .insertOrRemoveFromGlobal(action === 'disable' ? 'remove' : 'add', 'disabledCommands', commandID) + .catch(() => false); + if (!success) + return await message.util.reply({ + content: `${this.client.util.emojis.error} There was an error globally **${action.substr( + 0, + action.length - 2 + )}ing** the **${commandID}** command.`, + allowedMentions: AllowedMentions.none() + }); + else + return await message.util.reply({ + content: `${this.client.util.emojis.success} Successfully **${action.substr( + 0, + action.length - 2 + )}ed** the **${commandID}** command globally.`, + allowedMentions: AllowedMentions.none() + }); + + // guild disable + } else { + const disabledCommands = await message.guild.getSetting('disabledCommands'); + if (action === 'toggle') { + action = disabledCommands.includes(commandID) ? 'disable' : 'enable'; + } + const newValue = this.client.util.addOrRemoveFromArray( + action === 'disable' ? 'remove' : 'add', + disabledCommands, + commandID + ); + const success = await message.guild.setSetting('disabledCommands', newValue).catch(() => false); + if (!success) + return await message.util.reply({ + content: `${this.client.util.emojis.error} There was an error **${action.substr( + 0, + action.length - 2 + )}ing** the **${commandID}** command.`, + allowedMentions: AllowedMentions.none() + }); + else + return await message.util.reply({ + content: `${this.client.util.emojis.success} Successfully **${action.substr( + 0, + action.length - 2 + )}ed** the **${commandID}** command.`, + allowedMentions: AllowedMentions.none() + }); + } + } +} diff --git a/src/commands/config/prefix.ts b/src/commands/config/prefix.ts index 79956be..380442b 100644 --- a/src/commands/config/prefix.ts +++ b/src/commands/config/prefix.ts @@ -1,4 +1,4 @@ -import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; +import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib'; export default class PrefixCommand extends BushCommand { public constructor() { @@ -40,15 +40,17 @@ export default class PrefixCommand extends BushCommand { const oldPrefix = await message.guild.getSetting('prefix'); await message.guild.setSetting('prefix', args.prefix ?? this.client.config.prefix); if (args.prefix) { - return await message.util.send( - `${this.client.util.emojis.success} changed the server's prefix ${oldPrefix ? `from \`${oldPrefix}\`` : ''} to \`${ - args.prefix - }\`.` - ); + return await message.util.send({ + content: `${this.client.util.emojis.success} changed the server's prefix ${ + oldPrefix ? `from \`${oldPrefix}\`` : '' + } to \`${args.prefix}\`.`, + allowedMentions: AllowedMentions.none() + }); } else { - return await message.util.send( - `${this.client.util.emojis.success} reset the server's prefix to \`${this.client.config.prefix}\`.` - ); + return await message.util.send({ + content: `${this.client.util.emojis.success} reset the server's prefix to \`${this.client.config.prefix}\`.`, + allowedMentions: AllowedMentions.none() + }); } } } diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts new file mode 100644 index 0000000..ffc67ae --- /dev/null +++ b/src/commands/dev/__template.ts @@ -0,0 +1,61 @@ +import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; + +export default class TemplateCommand extends BushCommand { + public constructor() { + super('template', { + aliases: ['template'], + category: 'template', + description: { + content: 'Command description.', + usage: 'template <requiredArg> [optionalArg]', + examples: ['template 1 2'] + }, + args: [ + { + id: 'required_argument', + type: 'string', + match: 'phrase', + prompt: { + start: 'What would you like to set your first argument to be?', + retry: '{error} Pick a valid argument.', + optional: false + } + }, + { + id: 'optional_argument', + type: 'string', + match: 'phrase', + prompt: { + start: 'What would you like to set your second argument to be?', + retry: '{error} Pick a valid argument.', + optional: true + } + } + ], + slash: false, //set this to true + slashOptions: [ + { + name: 'required_argument', + description: 'What would you like to set your first argument to be?', + type: 'STRING', + required: true + }, + { + name: 'optional_argument', + description: 'What would you like to set your second argument to be?', + type: 'STRING', + required: false + } + ], + superUserOnly: true, + ownerOnly: true, + channel: 'guild', + hidden: true, + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES'] + }); + } + public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { + return await message.util.reply(`${this.client.util.emojis.error} Do not use the template command.`); + } +} diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index f3a30ab..76a78ba 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -20,7 +20,7 @@ export default class EvalCommand extends BushCommand { aliases: ['eval', 'ev'], category: 'dev', description: { - content: 'Use the command to eval stuff in the bot.', + content: 'Evaluate code.', usage: 'eval [--depth #] <code> [--sudo] [--silent] [--delete] [--proto] [--hidden] [--ts]', examples: ['eval message.guild.name', 'eval this.client.ownerID'] }, @@ -72,7 +72,6 @@ export default class EvalCommand extends BushCommand { } } ], - ownerOnly: true, slash: true, slashOptions: [ { @@ -117,7 +116,8 @@ export default class EvalCommand extends BushCommand { type: 'BOOLEAN', required: false } - ] + ], + ownerOnly: true }); } @@ -141,8 +141,8 @@ export default class EvalCommand extends BushCommand { } const code: { js?: string | null; ts?: string | null; lang?: 'js' | 'ts' } = {}; args.code = args.code.replace(/[“”]/g, '"'); - args.code = args.code.replace(/```/g, ''); - if (args.typescript) { + args.code = args.code.replace(/```*(?:js|ts)?/g, ''); + if (args.typescript || message) { code.ts = args.code; code.js = transpile(args.code); code.lang = 'ts'; diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts new file mode 100644 index 0000000..08f74e8 --- /dev/null +++ b/src/commands/dev/servers.ts @@ -0,0 +1,48 @@ +import { Guild, MessageEmbed } from 'discord.js'; +import { BushCommand, BushMessage, BushSlashMessage } from '../../lib'; + +export default class ServersCommand extends BushCommand { + public constructor() { + super('servers', { + aliases: ['servers'], + category: 'dev', + description: { + content: 'Displays all the severs the bot is in', + usage: 'servers', + examples: ['servers'] + }, + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES'], + superUserOnly: true + }); + } + + public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { + const maxLength = 10; + const guilds = this.client.guilds.cache.sort((a, b) => (a.memberCount < b.memberCount ? 1 : -1)).array(); + const chunkedGuilds: Guild[][] = []; + const embeds: MessageEmbed[] = []; + + for (let i = 0, j = guilds.length; i < j; i += maxLength) { + chunkedGuilds.push(guilds.slice(i, i + maxLength)); + } + + chunkedGuilds.forEach((c: Guild[]) => { + const embed = new MessageEmbed(); + c.forEach((g: Guild) => { + const owner = this.client.users.cache.get(g.ownerId)?.tag; + embed + .addField( + `**${g.name}**`, + `**ID:** ${g.id}\n**Owner:** ${owner ? owner : g.ownerId}\n**Members:** ${g.memberCount.toLocaleString()}`, + false + ) + .setTitle('Server List') + .setColor(this.client.util.colors.default); + }); + embeds.push(embed); + }); + + return await this.client.util.buttonPaginate(message, embeds); + } +} diff --git a/src/commands/dev/setLevel.ts b/src/commands/dev/setLevel.ts index f2ae6c7..4ec4c08 100644 --- a/src/commands/dev/setLevel.ts +++ b/src/commands/dev/setLevel.ts @@ -54,10 +54,12 @@ export default class SetLevelCommand extends BushCommand { const [levelEntry] = await Level.findOrBuild({ where: { - id: user.id + user: user.id, + guild: message.guild.id }, defaults: { - id: user.id + user: user.id, + guild: message.guild.id } }); await levelEntry.update({ xp: Level.convertLevelToXp(level) }); diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts new file mode 100644 index 0000000..d53e500 --- /dev/null +++ b/src/commands/dev/sh.ts @@ -0,0 +1,84 @@ +import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; +import chalk from 'chalk'; +import { exec } from 'child_process'; +import { Constants } from 'discord-akairo'; +import { MessageEmbed, Util } from 'discord.js'; +import { promisify } from 'util'; + +const sh = promisify(exec); +const clean = (text) => { + chalk.toString; + if (typeof text === 'string') { + return (text = Util.cleanCodeBlockContent(text)); + } else return text; +}; +export default class ShCommand extends BushCommand { + public constructor() { + super('sh', { + aliases: ['sh', 'shell', 'cmd'], + category: 'dev', + description: { + content: 'Run shell commands.', + usage: 'sh <command>', + examples: ['sh git pull'] + }, + args: [ + { + id: 'command', + type: Constants.ArgumentTypes.STRING, + match: Constants.ArgumentMatches.REST, + prompt: { + start: 'What would you like run', + retry: '{error} Invalid command to run.' + } + } + ], + ownerOnly: true + }); + } + + public async exec(message: BushMessage | BushSlashMessage, { command }: { command: string }): Promise<unknown> { + if (!this.client.config.owners.includes(message.author.id)) + return await message.util.reply(`${this.client.util.emojis.error} Only my developers can run this command.`); + const input = clean(command); + + const embed = new MessageEmbed() + .setColor(this.client.util.colors.gray) + .setFooter(message.author.tag, message.author.avatarURL({ dynamic: true })) + .setTimestamp() + .setTitle('Shell Command') + .addField('📥 Input', await this.client.util.codeblock(input, 1024, 'sh')) + .addField('Running', this.client.util.emojis.loading); + + await message.util.reply({ embeds: [embed] }); + + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' + ].join('|'); + function strip(abc: string): string { + return abc.replace(new RegExp(pattern, 'g'), ''); + } + try { + const output = await sh(command); + const stdout = strip(clean(output.stdout)); + const stderr = strip(clean(output.stderr)); + + embed + .setTitle(`${this.client.util.emojis.successFull} Executed command successfully.`) + .setColor(this.client.util.colors.success) + .spliceFields(1, 1); + + if (stdout) embed.addField('📤 stdout', await this.client.util.codeblock(stdout, 1024, 'json')); + if (stderr) embed.addField('📤 stderr', await this.client.util.codeblock(stderr, 1024, 'json')); + } catch (e) { + embed + .setTitle(`${this.client.util.emojis.errorFull} An error occurred while executing.`) + .setColor(this.client.util.colors.error) + .spliceFields(1, 1); + + embed.addField('📤 Output', await this.client.util.codeblock(e?.stack, 1024, 'js')); + } + await message.util.edit({ embeds: [embed] }); + } +} diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts index 4db20e7..45c2545 100644 --- a/src/commands/info/color.ts +++ b/src/commands/info/color.ts @@ -1,6 +1,15 @@ -import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -import { Constants } from 'discord-akairo'; -import { ColorResolvable, MessageEmbed } from 'discord.js'; +import { BushCommand, BushGuildMember, BushMessage, BushRole, BushSlashMessage } from '@lib'; +import { Argument } from 'discord-akairo'; +import { ColorResolvable, MessageEmbed, Role } from 'discord.js'; +import { Constructor } from 'tinycolor2'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const tinycolor: Constructor = require('tinycolor2'); // this is the only way I got it to work consistently +const isValidTinyColor = (_message: BushMessage, phase: string) => { + // if the phase is a number it converts it to hex incase it could be representing a color in decimal + const newPhase = Number.isNaN(phase) ? phase : `#${Number(phase).toString(16)}`; + return tinycolor(newPhase).isValid() ? newPhase : null; +}; export default class ColorCommand extends BushCommand { public constructor() { @@ -8,18 +17,17 @@ export default class ColorCommand extends BushCommand { aliases: ['color'], category: 'info', description: { - content: 'See what color a hex code is.', - usage: 'color <color>', + content: 'Find the color of a hex code, user, or role.', + usage: 'color <color|role|user>', examples: ['color #0000FF'] }, args: [ { id: 'color', - type: /^#?(?<code>[0-9A-F]{6})$/i, - match: Constants.ArgumentMatches.PHRASE, + type: Argument.union(isValidTinyColor, 'role', 'member'), prompt: { - start: 'What color value would you like to see the color of', - retry: '{error} Choose a valid hex color code.' + start: 'What color code, role, or user would you like to find the color of?', + retry: '{error} Choose a valid color, role, or member.' } } ], @@ -28,14 +36,27 @@ export default class ColorCommand extends BushCommand { }); } + public removePrefixAndParenthesis(color: string): string{ + return color.substr(4, color.length-5) + } + public async exec( message: BushMessage | BushSlashMessage, - { color: { match } }: { color: { match: RegExpMatchArray; matches: RegExpMatchArray[] } } + args: { color: string | BushRole | BushGuildMember } ): Promise<unknown> { + const color = + typeof args.color === 'string' + ? tinycolor(args.color) + : args.color instanceof Role + ? tinycolor(args.color.hexColor) + : tinycolor(args.color.displayHexColor); + const embed = new MessageEmbed() - .addField('Hex', match.groups.code, false) - .addField('RGB', this.client.util.hexToRgb(match.groups.code), false) - .setColor(match.groups.code as ColorResolvable); + .addField('» Hexadecimal', color.toHexString()) + .addField('» Decimal', `${parseInt(color.toHex(), 16)}`) + .addField('» HSL', this.removePrefixAndParenthesis(color.toHslString())) + .addField('» RGB', this.removePrefixAndParenthesis(color.toRgbString())) + .setColor(color.toHex() as ColorResolvable); return await message.util.reply({ embeds: [embed] }); } diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index 0977c36..a0b03a0 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -47,16 +47,19 @@ export default class HelpCommand extends BushCommand { args: { command: BushCommand | string; showHidden?: boolean } ): Promise<unknown> { const prefix = this.client.config.dev ? 'dev ' : message.util.parsed.prefix; - let ButtonRow: MessageActionRow; - if (!this.client.config.dev) { - ButtonRow = new MessageActionRow().addComponents( - new MessageButton({ - style: 'LINK', - label: 'Invite Me', - url: `https://discord.com/api/oauth2/authorize?client_id=${this.client.user.id}&permissions=2147483647&scope=bot%20applications.commands` - }) - ); - } + const components = + !this.client.config.dev || !this.client.guilds.cache.some((guild) => guild.ownerId === message.author.id) + ? [ + new MessageActionRow().addComponents( + new MessageButton({ + style: 'LINK', + label: 'Invite Me', + url: `https://discord.com/api/oauth2/authorize?client_id=${this.client.user.id}&permissions=2147483647&scope=bot%20applications.commands` + }) + ) + ] + : undefined; + const isOwner = this.client.isOwner(message.author); const isSuperUser = this.client.isSuperUser(message.author); const command = args.command @@ -65,20 +68,21 @@ export default class HelpCommand extends BushCommand { : args.command : null; if (!isOwner) args.showHidden = false; - if (!command) { + if (!command || command.completelyHide) { const embed = new MessageEmbed().setColor(this.client.util.colors.default).setTimestamp(); if (message.guild) { - embed.setFooter(`For more information about a command use '${prefix}help <command>'`); + embed.setFooter(`For more information about a command use ${prefix}help <command>`); } for (const [, category] of this.handler.categories) { const categoryFilter = category.filter((command) => { + if (command.completelyHide) return false; if (command.hidden && !args.showHidden) return false; if (command.channel == 'guild' && !message.guild && !args.showHidden) return false; if (command.ownerOnly && !isOwner) return false; if (command.superUserOnly && !isSuperUser) { return false; } - return !(command.restrictedGuilds?.includes(message.guild.id) == false && !args.showHidden); + return !(command.restrictedGuilds?.includes(message.guild.id) === false && !args.showHidden); }); const categoryNice = category.id .replace(/(\b\w)/gi, (lc): string => lc.toUpperCase()) @@ -90,24 +94,24 @@ export default class HelpCommand extends BushCommand { embed.addField(`${categoryNice}`, `${categoryCommands.join(' ')}`); } } - return await message.util.reply({ embeds: [embed], components: ButtonRow ? [ButtonRow] : undefined }); + return await message.util.reply({ embeds: [embed], components }); } const embed = new MessageEmbed() .setColor(this.client.util.colors.default) - .setTitle(`\`${command.description?.usage ? command.description.usage : 'This command does not have usages.'}\``) + .setTitle(`\`${command.description?.usage || `${this.client.util.emojis.error} This command does not have usages.`}\``) .addField( 'Description', - `${command.description?.content ? command.description.content : '*This command does not have a description.*'} ${ - command.ownerOnly ? '\n__Dev Only__' : '' + `${command.description?.content || `${this.client.util.emojis.error} This command does not have a description.`} ${ + command.ownerOnly ? '\n__Developer Only__' : '' } ${command.superUserOnly ? '\n__Super User Only__' : ''}` ); if (command.aliases?.length > 1) embed.addField('Aliases', `\`${command.aliases.join('` `')}\``, true); - if (command.description?.examples && command.description.examples.length) { + if (command.description?.examples?.length) { embed.addField('Examples', `\`${command.description.examples.join('`\n`')}\``, true); } - return await message.util.reply({ embeds: [embed], components: ButtonRow ? [ButtonRow] : undefined }); + return await message.util.reply({ embeds: [embed], components }); } } diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts index 50756a6..5e70323 100644 --- a/src/commands/info/userInfo.ts +++ b/src/commands/info/userInfo.ts @@ -78,7 +78,7 @@ export default class UserInfoCommand extends BushCommand { if (user.premiumSinceTimestamp) emojis.push(this.client.consts.mappings.otherEmojis.BOOSTER); const createdAt = user.user.createdAt.toLocaleString(), - createdAtDelta = moment(user.user.createdAt).diff(moment()).toLocaleString(), + createdAtDelta = moment(moment(user.user.createdAt).diff(moment())).toLocaleString(), joinedAt = user.joinedAt?.toLocaleString(), joinedAtDelta = moment(user.joinedAt)?.diff(moment()).toLocaleString(), premiumSince = user.premiumSince?.toLocaleString(), diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index c5833fc..874d5ed 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -37,6 +37,11 @@ export default class BanCommand extends BushCommand { match: 'option', type: Argument.range('integer', 0, 7, true), default: 0 + }, + { + id: 'force', + flag: '--force', + match: 'flag' } ], slash: true, @@ -77,10 +82,16 @@ export default class BanCommand extends BushCommand { } async exec( message: BushMessage | BushSlashMessage, - { user, reason, days }: { user: User; reason?: { duration: number; contentWithoutTime: string }; days?: number } + { + user, + reason, + days, + force + }: { user: User; reason?: { duration: number; contentWithoutTime: string }; days?: number; force: boolean } ): Promise<unknown> { const member = message.guild.members.cache.get(user.id) as BushGuildMember; - const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'ban'); + const useForce = force && message.author.isOwner(); + const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'ban', true, useForce); if (canModerateResponse !== true) { return message.util.reply(canModerateResponse); diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index ccf35a9..74ace94 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -28,6 +28,11 @@ export default class KickCommand extends BushCommand { retry: '{error} Choose a valid kick reason.', optional: true } + }, + { + id: 'force', + flag: '--force', + match: 'flag' } ], slash: true, @@ -50,9 +55,13 @@ export default class KickCommand extends BushCommand { }); } - async exec(message: BushMessage | BushSlashMessage, { user, reason }: { user: BushUser; reason?: string }): Promise<unknown> { + async exec( + message: BushMessage | BushSlashMessage, + { user, reason, force }: { user: BushUser; reason?: string; force: boolean } + ): Promise<unknown> { const member = message.guild.members.cache.get(user.id) as BushGuildMember; - const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'kick'); + const useForce = force && message.author.isOwner(); + const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'kick', true, useForce); if (canModerateResponse !== true) { return message.util.reply(canModerateResponse); diff --git a/src/commands/moderation/lockdown.ts b/src/commands/moderation/lockdown.ts new file mode 100644 index 0000000..e67d110 --- /dev/null +++ b/src/commands/moderation/lockdown.ts @@ -0,0 +1,45 @@ +import { BushCommand, BushMessage, BushNewsChannel, BushSlashMessage, BushTextChannel } from '@lib'; + +export default class LockdownCommand extends BushCommand { + public constructor() { + super('lockdown', { + aliases: ['lockdown', 'unlockdown'], + category: 'moderation', + description: { + content: 'Allows you to lockdown a channel or all configured channels..', + usage: 'lockdown [--all]', + examples: ['lockdown', 'lockdown --all'] + }, + args: [ + { + id: 'all', + type: 'flag', + flag: '--all' + } + ], + slash: true, + slashOptions: [ + { + name: 'all', + description: 'Would you like to lockdown all channels?', + type: 'BOOLEAN', + required: false + } + ], + channel: 'guild', + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES'] + }); + } + public async exec(message: BushMessage | BushSlashMessage, { all }: { all: boolean }): Promise<unknown> { + return await message.util.reply('no'); + if (!all) { + if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(message.channel.type)) + return message.util.reply(`${this.client.util.emojis.error} You can only lock down text and announcement channels.`); + const lockdownSuccess = await this.client.util.lockdownChannel({ + channel: message.channel as BushTextChannel | BushNewsChannel, + moderator: message.author + }); + } + } +} diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts index 4b57a4f..e3da45f 100644 --- a/src/commands/moderation/modlog.ts +++ b/src/commands/moderation/modlog.ts @@ -72,7 +72,7 @@ export default class ModlogCommand extends BushCommand { color: this.client.util.colors.default }) ); - this.client.util.buttonPaginate(message, embedPages, '', true); + return await this.client.util.buttonPaginate(message, embedPages, '', true); } else if (search) { const entry = await ModLog.findByPk(search as string); if (!entry) return message.util.send(`${this.client.util.emojis.error} That modlog does not exist.`); diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 7443c55..2004eb8 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -29,6 +29,11 @@ export default class MuteCommand extends BushCommand { retry: '{error} Choose a valid mute reason and duration.', optional: true } + }, + { + id: 'force', + flag: '--force', + match: 'flag' } ], slash: true, @@ -53,11 +58,12 @@ export default class MuteCommand extends BushCommand { } async exec( message: BushMessage | BushSlashMessage, - { user, reason }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string } } + { user, reason, force }: { user: BushUser; reason?: { duration: number; contentWithoutTime: string }; force: boolean } ): Promise<unknown> { const error = this.client.util.emojis.error; const member = message.guild.members.cache.get(user.id) as BushGuildMember; - const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'mute'); + const useForce = force && message.author.isOwner(); + const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'mute', true, useForce); const victimBoldTag = `**${member.user.tag}**`; if (canModerateResponse !== true) { diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index 3d353ca..d5bf009 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -28,6 +28,11 @@ export default class WarnCommand extends BushCommand { retry: '{error} Choose a valid warn reason.', optional: true } + }, + { + id: 'force', + flag: '--force', + match: 'flag' } ], slash: true, @@ -52,10 +57,11 @@ export default class WarnCommand extends BushCommand { } public async exec( message: BushMessage | BushSlashMessage, - { user, reason }: { user: BushUser; reason: string } + { user, reason, force }: { user: BushUser; reason: string; force: boolean } ): Promise<unknown> { const member = message.guild.members.cache.get(user.id) as BushGuildMember; - const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'warn'); + const useForce = force && message.author.isOwner(); + const canModerateResponse = this.client.util.moderationPermissionCheck(message.member, member, 'warn', true, useForce); const victimBoldTag = `**${member.user.tag}**`; if (canModerateResponse !== true) { diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts index b19d3bc..08b42ff 100644 --- a/src/commands/moulberry-bush/capePerms.ts +++ b/src/commands/moulberry-bush/capePerms.ts @@ -4,34 +4,6 @@ import { MessageEmbed } from 'discord.js'; import got from 'got'; export default class CapePermissionsCommand extends BushCommand { - private nameMap = { - patreon1: 'Patreon Tier 1', - patreon2: 'Patreon Tier 2', - fade: 'Fade', - contrib: 'Contributor', - nullzee: 'Patreon Tier 1', - gravy: 'Patreon Tier 1', - space: 'Patreon Tier 1', - mcworld: 'Patreon Tier 1', - lava: 'Patreon Tier 1', - packshq: 'Patreon Tier 1', - mbstaff: 'Patreon Tier 1', - thebakery: 'Patreon Tier 1', - negative: 'Patreon Tier 1', - void: 'Patreon Tier 1', - ironmoon: 'Patreon Tier 1', - krusty: 'Patreon Tier 1', - furf: 'Patreon Tier 1', - soldier: 'Patreon Tier 1', - dsm: 'Patreon Tier 1', - zera: 'Patreon Tier 1', - tunnel: 'Patreon Tier 1', - alexxoffi: 'Patreon Tier 1', - parallax: 'Patreon Tier 1', - jakethybro: 'Patreon Tier 1', - planets: 'Patreon Tier 1' - }; - public constructor() { super('capepermissions', { aliases: ['capeperms', 'capeperm', 'capepermissions'], @@ -80,7 +52,7 @@ export default class CapePermissionsCommand extends BushCommand { let capeperms: Capeperms, uuid: string; try { - uuid = await this.client.util.mcUUID(args.ign); + uuid = await this.client.util.findUUID(args.ign); } catch (e) { return await message.util.reply( `${this.client.util.emojis.error} \`${args.ign}\` doesn't appear to be a valid username.` @@ -88,7 +60,7 @@ export default class CapePermissionsCommand extends BushCommand { } try { - capeperms = await got.get('https://moulberry.codes/permscapes.json').json(); + capeperms = await got.get('http://moulberry.codes/permscapes.json').json(); } catch (error) { capeperms = null; } @@ -105,6 +77,7 @@ export default class CapePermissionsCommand extends BushCommand { index = i; break; } + continue; } if (index == null) return await message.util.reply( diff --git a/src/commands/moulberry-bush/level.ts b/src/commands/moulberry-bush/level.ts index fc1e93e..86ab985 100644 --- a/src/commands/moulberry-bush/level.ts +++ b/src/commands/moulberry-bush/level.ts @@ -1,4 +1,4 @@ -import { BushCommand, BushMessage, BushSlashMessage, BushUser, Level } from '@lib'; +import { BushCommand, BushGuild, BushMessage, BushSlashMessage, BushUser, Level } from '@lib'; /* import canvas from 'canvas'; import { MessageAttachment } from 'discord.js'; @@ -127,8 +127,8 @@ export default class LevelCommand extends BushCommand { return image.toBuffer(); } */ - private async getResponse(user: BushUser): Promise<string> { - const userLevelRow = await Level.findByPk(user.id); + private async getResponse(user: BushUser, guild: BushGuild): Promise<string> { + const userLevelRow = await Level.findOne({ where: { user: user.id, guild: guild.id } }); if (userLevelRow) { return `${user ? `${user.tag}'s` : 'Your'} level is ${userLevelRow.level} (${userLevelRow.xp} XP)`; } else { @@ -143,6 +143,6 @@ export default class LevelCommand extends BushCommand { // 'lel.png' // ) // ); - await message.reply(await this.getResponse(user || message.author)); + await message.reply(await this.getResponse(user || message.author, message.guild)); } } diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts new file mode 100644 index 0000000..ebc8f1d --- /dev/null +++ b/src/commands/moulberry-bush/report.ts @@ -0,0 +1,121 @@ +import { Constants } from 'discord-akairo'; +import { GuildMember, MessageEmbed, TextChannel } from 'discord.js'; +import moment from 'moment'; +import { AllowedMentions, BushCommand, BushMessage } from '../../lib'; + +export default class ReportCommand extends BushCommand { + public constructor() { + super('report', { + aliases: ['report'], + category: "Moulberry's Bush", + description: { + content: 'A command to report a user..', + usage: 'report <user> <reason/evidence>', + examples: ['report IRONM00N'] + }, + args: [ + { + id: 'member', + type: Constants.ArgumentTypes.MEMBER, + match: Constants.ArgumentMatches.PHRASE, + prompt: { + start: 'Who would you like to report?', + retry: `{error} Choose a valid user to report.`, + optional: false + } + }, + { + id: 'evidence', + type: Constants.ArgumentTypes.STRING, + match: Constants.ArgumentMatches.REST, + prompt: { + start: 'What evidence do you have?', + retry: `{error} Provide what did they do wrong.`, + optional: true + } + } + ], + clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], + channel: 'guild', + restrictedGuilds: ['516977525906341928'], + slash: true, + slashOptions: [ + { + name: 'user', + description: 'The user you would like to report.', + type: 'USER', + required: true + }, + { + name: 'evidence', + description: 'What did the user do wrong?', + type: 'STRING', + required: false + } + ], + slashGuilds: ['516977525906341928'] + }); + } + + public async exec(message: BushMessage, { member, evidence }: { member: GuildMember; evidence: string }): Promise<unknown> { + if (message.guild.id != this.client.consts.mappings.guilds.bush) + return await message.util.reply(`${this.client.util.emojis.error} This command can only be run in Moulberry's bush.`); + if (!member) return await message.util.reply(`${this.client.util.emojis.error} Choose someone to report`); + if (member.user.id === '322862723090219008') + return await message.util.reply({ + content: `Thank you for your report! We take these allegations very seriously and have reported <@${member.user.id}> to the FBI!`, + allowedMentions: AllowedMentions.none() + }); + if (member.user.bot) + return await message.util.reply( + `${this.client.util.emojis.error} You cannot report a bot <:WeirdChamp:756283321301860382>.` + ); + + //// if (!evidence) evidence = 'No Evidence.'; + //todo: Add channel id to db instead of hard coding it & allow in any guild + //The formatting of the report is mostly copied from carl since it is pretty good when it actually works + const reportEmbed = new MessageEmbed() + .setFooter(`Reporter ID: ${message.author.id} Reported ID: ${member.user.id}`) + .setTimestamp() + .setAuthor(`Report From: ${message.author.tag}`, message.author.avatarURL({ dynamic: true })) + .setTitle('New Report') + .setColor(this.client.util.colors.red) + .setDescription(evidence) + .addField( + 'Reporter', + `**Name:**${message.author.tag} <@${message.author.id}>\n**Joined:** ${moment( + message.member.joinedTimestamp + ).fromNow()}\n**Created:** ${moment(message.author.createdTimestamp).fromNow()}\n**Sent From**: <#${ + message.channel.id + }> [Jump to context](${message.url})`, + true + ) + .addField( + 'Reported User', + `**Name:**${member.user.tag} <@${member.user.id}>\n**Joined:** ${moment( + member.joinedTimestamp + ).fromNow()}\n**Created:** ${moment(member.user.createdTimestamp).fromNow()}`, + true + ); + + //reusing code pog + if (message.attachments.size > 0) { + const fileName = message.attachments.first().name.toLowerCase(); + if (fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.gif') || fileName.endsWith('.webp')) { + reportEmbed.setImage(message.attachments.first().url); + } else { + reportEmbed.addField('Attachment', message.attachments.first().url); + } + } + const reportChannel = <TextChannel>this.client.channels.cache.get('782972723654688848'); + await reportChannel.send({ embeds: [reportEmbed] }).then(async (ReportMessage) => { + try { + await ReportMessage.react(this.client.util.emojis.success); + await ReportMessage.react(this.client.util.emojis.error); + } catch { + this.client.console.warn('ReportCommand', 'Could not react to report message.'); + } + }); + return await message.util.reply('Successfully made a report.'); + } +} diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts index a2b8c78..1681a1b 100644 --- a/src/commands/moulberry-bush/rule.ts +++ b/src/commands/moulberry-bush/rule.ts @@ -1,6 +1,6 @@ -import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { Argument, Constants } from 'discord-akairo'; import { MessageEmbed, User } from 'discord.js'; +import { AllowedMentions, BushCommand, BushMessage } from '../../lib'; const rules = [ { @@ -106,10 +106,7 @@ export default class RuleCommand extends BushCommand { }); } - public async exec( - message: BushMessage | BushSlashMessage, - { rule, user }: { rule: undefined | number; user: User } - ): Promise<unknown> { + public async exec(message: BushMessage, { rule, user }: { rule: undefined | number; user: User }): Promise<unknown> { const rulesEmbed = new MessageEmbed() .setColor('#ef3929') .setFooter(`Triggered by ${message.author.tag}`, message.author.avatarURL({ dynamic: true })) @@ -133,19 +130,21 @@ export default class RuleCommand extends BushCommand { return; async function respond(): Promise<unknown> { if (!user) { - // If the original message was a reply -> imitate it - (message as BushMessage).reference?.messageId && !message.util.isSlash - ? await message.channel.messages.fetch((message as BushMessage).reference.messageId).then(async (message) => { - await message.util.reply({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() }); - }) - : await message.util.send({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() }); + return ( + // If the original message was a reply -> imitate it + message.reference?.messageId && !message.util.isSlash + ? await message.channel.messages.fetch(message.reference.messageId).then(async (message) => { + await message.util.reply({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() }); + }) + : await message.util.send({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() }) + ); } else { - return (message as BushMessage).reference?.messageId && !message.util.isSlash + return message.reference?.messageId && !message.util.isSlash ? await message.util.send({ content: `<@!${user.id}>`, embeds: [rulesEmbed], allowedMentions: AllowedMentions.users(), - reply: { messageReference: (message as BushMessage).reference.messageId } + reply: { messageReference: message.reference.messageId } }) : await message.util.send({ content: `<@!${user.id}>`, diff --git a/src/commands/skyblockReborn/chooseColorCommand.ts b/src/commands/skyblock-reborn/chooseColorCommand.ts index ce70419..ce70419 100644 --- a/src/commands/skyblockReborn/chooseColorCommand.ts +++ b/src/commands/skyblock-reborn/chooseColorCommand.ts diff --git a/src/commands/utilities/_whoHasRole.ts b/src/commands/utilities/_whoHasRole.ts deleted file mode 100644 index e69de29..0000000 --- a/src/commands/utilities/_whoHasRole.ts +++ /dev/null diff --git a/src/commands/utilities/hash.ts b/src/commands/utilities/hash.ts new file mode 100644 index 0000000..4b5b01c --- /dev/null +++ b/src/commands/utilities/hash.ts @@ -0,0 +1,42 @@ +import crypto from 'crypto'; +import { Constants } from 'discord-akairo'; +import got from 'got'; +import { BushCommand, BushMessage } from '../../lib'; + +export default class HashCommand extends BushCommand { + constructor() { + super('hash', { + aliases: ['hash'], + category: 'utilities', + description: { + content: 'Gets the file hash of the given discord link', + usage: 'hash <file url>', + examples: ['hash https://cdn.discordapp.com/emojis/782630946435366942.png?v=1'] //nice + }, + args: [ + { + id: 'url', + type: Constants.ArgumentTypes.URL, + match: Constants.ArgumentMatches.PHRASE, + prompt: { + start: 'What url would you like to find the hash of?', + retry: '{error} Enter a valid url.' + } + } + ], + clientPermissions: ['SEND_MESSAGES'] + }); + } + + public async exec(message: BushMessage, { url }: { url: string }): Promise<void> { + try { + const req = await got.get(url); + const rawHash = crypto.createHash('md5'); + rawHash.update(req.rawBody.toString('binary')); + const hash = rawHash.digest('hex'); + await message.util.reply(`\`${hash}\``); + } catch { + await message.util.reply('Unable to calculate hash.'); + } + } +} diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts new file mode 100644 index 0000000..231930c --- /dev/null +++ b/src/commands/utilities/price.ts @@ -0,0 +1,220 @@ +import { Constants } from 'discord-akairo'; +import { ColorResolvable, MessageEmbed } from 'discord.js'; +import Fuse from 'fuse.js'; +import got from 'got'; +import { BushCommand, BushMessage } from '../../lib'; + +interface Summary { + amount: number; + pricePerUnit: number; + orders: number; +} + +interface Bazaar { + success: boolean; + lastUpdated: number; + products: { + [key: string]: { + product_id: string; + sell_summary: Summary[]; + buy_summary: Summary[]; + quick_status: { + productId: string; + sellPrice: number; + sellVolume: number; + sellMovingWeek: number; + sellOrders: number; + buyPrice: number; + buyVolume: number; + buyMovingWeek: number; + buyOrders: number; + }; + }; + }; +} + +interface LowestBIN { + [key: string]: number; +} + +interface AuctionAverages { + [key: string]: { + price?: number; + count?: number; + sales?: number; + clean_price?: number; + clean_sales?: number; + }; +} + +export default class PriceCommand extends BushCommand { + public constructor() { + super('price', { + aliases: ['price'], + category: 'utilities', + clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], + description: { + usage: 'price <item id>', + examples: ['price ASPECT_OF_THE_END'], + content: 'Finds the price information of an item.' + }, + ratelimit: 4, + cooldown: 4000, + typing: true, + args: [ + { + id: 'item', + match: Constants.ArgumentMatches.CONTENT, + type: Constants.ArgumentTypes.STRING, + prompt: { + start: 'What item would you like to find the price of?', + retry: '{error} Choose a valid item.' + } + }, + { + id: 'strict', + match: Constants.ArgumentMatches.FLAG, + flag: '--strict', + default: false + } + ], + slash: true, + slashOptions: [ + { + name: 'item', + description: 'The item that you would you like to find the price of.', + type: 'STRING', + required: true + }, + { + name: 'strict', + description: 'Whether or not to bypass the fuzzy search.', + type: 'BOOLEAN', + required: false + } + ] + }); + } + + public async exec(message: BushMessage, { item, strict }: { item: string; strict: boolean }): Promise<unknown> { + const errors = new Array<string>(); + const bazaar: Bazaar = await get('https://api.hypixel.net/skyblock/bazaar').catch(() => errors.push('bazaar')); + const currentLowestBIN: LowestBIN = await get('https://moulberry.codes/lowestbin.json').catch(() => + errors.push('current lowest BIN') + ); + const averageLowestBIN: LowestBIN = await get('https://moulberry.codes/auction_averages_lbin/3day.json').catch(() => + errors.push('average Lowest BIN') + ); + const auctionAverages: AuctionAverages = await get('https://moulberry.codes/auction_averages/3day.json').catch(() => + errors.push('auction average') + ); + // adds _ to item name + let parsedItem = item.toString().toUpperCase().replace(/ /g, '_').replace(/'S/g, ''); + const priceEmbed = new MessageEmbed(); + + if (errors?.length) { + priceEmbed.setFooter; + } + + //combines all the item names from each + const itemNames = Array.from( + new Set( + (averageLowestBIN ? Object.keys(averageLowestBIN) : []).concat( + currentLowestBIN ? Object.keys(currentLowestBIN) : [], + auctionAverages ? Object.keys(auctionAverages) : [], + bazaar?.products ? Object.keys(bazaar.products) : [] + ) + ) + ); + + // fuzzy search + if (!strict) { + parsedItem = new Fuse(itemNames)?.search(parsedItem)[0]?.item; + } + + // If bazaar item then it there should not be any ah data + if (bazaar['products'][parsedItem]) { + const bazaarPriceEmbed = new MessageEmbed() + .setColor( + errors?.length + ? (this.client.util.emojis.warn as ColorResolvable) + : (this.client.util.colors.success as ColorResolvable) + ) + .setTitle(`Bazaar Information for \`${parsedItem}\``) + .addField('Sell Price', Bazaar('sellPrice', 2, true)) + .addField('Buy Price', Bazaar('buyPrice', 2, true)) + .addField('Margin', (Number(Bazaar('buyPrice', 2, false)) - Number(Bazaar('sellPrice', 2, false))).toLocaleString()) + .addField('Current Sell Orders', Bazaar('sellOrders', 0, true)) + .addField('Current Buy Orders', Bazaar('buyOrders', 0, true)); + return await message.util.reply({ embeds: [bazaarPriceEmbed] }); + } + + // Checks if the item exists in any of the action information otherwise it is not a valid item + if (currentLowestBIN?.[parsedItem] || averageLowestBIN?.[parsedItem] || auctionAverages?.[parsedItem]) { + priceEmbed + .setColor(this.client.util.colors.success) + .setTitle(`Price Information for \`${parsedItem}\``) + .setFooter('All information is based on the last 3 days.'); + } else { + const errorEmbed = new MessageEmbed(); + errorEmbed + .setColor(this.client.util.colors.error) + .setDescription( + `${this.client.util.emojis.error} \`${parsedItem}\` is not a valid item id, or it has no auction data.` + ); + return await message.util.reply({ embeds: [errorEmbed] }); + } + + if (currentLowestBIN?.[parsedItem]) { + const currentLowestBINPrice = currentLowestBIN[parsedItem].toLocaleString(); + priceEmbed.addField('Current Lowest BIN', currentLowestBINPrice); + } + if (averageLowestBIN?.[parsedItem]) { + const averageLowestBINPrice = averageLowestBIN[parsedItem].toLocaleString(); + priceEmbed.addField('Average Lowest BIN', averageLowestBINPrice); + } + if (auctionAverages?.[parsedItem]?.price) { + const auctionAveragesPrice = auctionAverages[parsedItem].price.toLocaleString(); + priceEmbed.addField('Average Auction Price', auctionAveragesPrice); + } + if (auctionAverages?.[parsedItem]?.count) { + const auctionAveragesCountPrice = auctionAverages[parsedItem].count.toLocaleString(); + priceEmbed.addField('Average Auction Count', auctionAveragesCountPrice); + } + if (auctionAverages?.[parsedItem]?.sales) { + const auctionAveragesSalesPrice = auctionAverages[parsedItem].sales.toLocaleString(); + priceEmbed.addField('Average Auction Sales', auctionAveragesSalesPrice); + } + if (auctionAverages?.[parsedItem]?.clean_price) { + const auctionAveragesCleanPrice = auctionAverages[parsedItem].clean_price.toLocaleString(); + priceEmbed.addField('Average Auction Clean Price', auctionAveragesCleanPrice); + } + if (auctionAverages?.[parsedItem]?.clean_sales) { + const auctionAveragesCleanSales = auctionAverages[parsedItem].clean_sales.toLocaleString(); + priceEmbed.addField('Average Auction Clean Sales', auctionAveragesCleanSales); + } + return await message.util.reply({ embeds: [priceEmbed] }); + + //Helper functions + function Bazaar(Information: string, digits: number, commas: boolean): string { + const price = bazaar?.products[parsedItem]?.quick_status?.[Information]; + const a = Number(Number(price).toFixed(digits)); + return commas ? a?.toLocaleString() : a?.toString(); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async function get(url: string): Promise<any> { + const data = await got.get(url).catch((error) => { + this.client.console.warn('PriceCommand', `There was an problem fetching data from <<${url}>> with error:\n${error}`); + throw 'Error Fetching price data'; + }); + try { + const json = JSON.parse(data.body); + return json; + } catch (error) { + this.client.console.warn('PriceCommand', `There was an problem parsing data from <<${url}>> with error:\n${error}`); + throw 'json error'; + } + } + } +} diff --git a/src/commands/utilities/viewraw.ts b/src/commands/utilities/viewraw.ts index 7642b2a..3658bde 100644 --- a/src/commands/utilities/viewraw.ts +++ b/src/commands/utilities/viewraw.ts @@ -7,7 +7,7 @@ export default class ViewRawCommand extends BushCommand { public constructor() { super('viewraw', { aliases: ['viewraw'], - category: 'info', + category: 'utilities', clientPermissions: ['EMBED_LINKS'], description: { usage: 'viewraw <message id> <channel>', diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts new file mode 100644 index 0000000..1828c95 --- /dev/null +++ b/src/commands/utilities/whoHasRole.ts @@ -0,0 +1,53 @@ +import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; +import { MessageEmbed, Role, Util } from 'discord.js'; + +export default class WhoHasRoleCommand extends BushCommand { + public constructor() { + super('whohasrole', { + aliases: ['whohasrole'], + category: 'utilities', + description: { + content: 'Allows you to view what users have a certain role.', + usage: 'template <requiredArg> [optionalArg]', + examples: ['template 1 2'] + }, + args: [ + { + id: 'role', + type: 'role', + prompt: { + start: 'What role would you like to find the users of?', + retry: '{error} Pick a valid role.', + optional: false + } + } + ], + slash: true, + slashOptions: [ + { + name: 'role', + description: 'What role would you like to find the users of?', + type: 'ROLE', + required: true + } + ], + channel: 'guild', + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES'] + }); + } + public async exec(message: BushMessage | BushSlashMessage, args: { role: Role }): Promise<unknown> { + const roleMembers = args.role.members.map((member) => `${member.user} (${Util.escapeMarkdown(member.user.tag)})`); + + const chunkedRoleMembers = this.client.util.chunk(roleMembers, 30); + const embedPages = chunkedRoleMembers.map( + (chunk) => + new MessageEmbed({ + title: `${args.role.name}'s Members`, + description: chunk.join('\n'), + color: this.client.util.colors.default + }) + ); + return await this.client.util.buttonPaginate(message, embedPages, null, true); + } +} diff --git a/src/config/example-options.ts b/src/config/example-options.ts index fadd640..1b5d15a 100644 --- a/src/config/example-options.ts +++ b/src/config/example-options.ts @@ -16,11 +16,10 @@ export const owners: Snowflake[] = [ export const prefix = '-' as string; export const dev = true as boolean; export const devGuild = '1000000000000000' as Snowflake; -export const channels: { log: Snowflake; error: Snowflake; dm: Snowflake; command: Snowflake } = { +export const channels: { log: Snowflake; error: Snowflake; dm: Snowflake; } = { log: '1000000000000000', error: '1000000000000000', dm: '1000000000000000', - command: '1000000000000000' }; // Database specific diff --git a/src/inhibitors/blacklist/channelGlobalBlacklist.ts b/src/inhibitors/blacklist/channelGlobalBlacklist.ts new file mode 100644 index 0000000..9dc5df9 --- /dev/null +++ b/src/inhibitors/blacklist/channelGlobalBlacklist.ts @@ -0,0 +1,25 @@ +import { BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; + +export default class UserGlobalBlacklistInhibitor extends BushInhibitor { + public constructor() { + super('channelGlobalBlacklist', { + reason: 'channelGlobalBlacklist', + category: 'blacklist', + type: 'all' + }); + } + + public exec(message: BushMessage | BushSlashMessage): boolean { + if (!message.author) return false; + if ( + this.client.isOwner(message.author) || + this.client.isSuperUser(message.author) || + this.client.user.id === message.author.id + ) + return false; + if (this.client.cache.global.blacklistedChannels.includes(message.channel.id)) { + this.client.console.debug(`channelGlobalBlacklist blocked message.`); + return true; + } + } +} diff --git a/src/inhibitors/blacklist/channelGuildBlacklist.ts b/src/inhibitors/blacklist/channelGuildBlacklist.ts new file mode 100644 index 0000000..cc72182 --- /dev/null +++ b/src/inhibitors/blacklist/channelGuildBlacklist.ts @@ -0,0 +1,25 @@ +import { BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; + +export default class ChannelGuildBlacklistInhibitor extends BushInhibitor { + public constructor() { + super('channelGuildBlacklist', { + reason: 'channelGuildBlacklist', + category: 'blacklist', + type: 'all' + }); + } + + public async exec(message: BushMessage | BushSlashMessage): Promise<boolean> { + if (!message.author || !message.guild) return false; + if ( + this.client.isOwner(message.author) || + this.client.isSuperUser(message.author) || + this.client.user.id === message.author.id + ) + return false; + if ((await message.guild.getSetting('blacklistedChannels'))?.includes(message.channel.id)) { + this.client.console.debug(`channelGuildBlacklist blocked message.`); + return true; + } + } +} diff --git a/src/inhibitors/blacklist/guildBlacklist.ts b/src/inhibitors/blacklist/guildBlacklist.ts index d9a42da..7d08157 100644 --- a/src/inhibitors/blacklist/guildBlacklist.ts +++ b/src/inhibitors/blacklist/guildBlacklist.ts @@ -11,7 +11,13 @@ export default class GuildBlacklistInhibitor extends BushInhibitor { public exec(message: BushMessage | BushSlashMessage): boolean { if (!message.guild) return false; - if (message.author && (this.client.isOwner(message.author) || this.client.isSuperUser(message.author))) return false; + if ( + message.author && + (this.client.isOwner(message.author) || + this.client.isSuperUser(message.author) || + this.client.user.id === message.author.id) + ) + return false; if (this.client.cache.global.blacklistedGuilds.includes(message.guild.id)) { this.client.console.debug(`GuildBlacklistInhibitor blocked message.`); return true; diff --git a/src/inhibitors/blacklist/userBlacklist.ts b/src/inhibitors/blacklist/userBlacklist.ts deleted file mode 100644 index f3cc642..0000000 --- a/src/inhibitors/blacklist/userBlacklist.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; - -export default class UserBlacklistInhibitor extends BushInhibitor { - public constructor() { - super('userBlacklist', { - reason: 'userBlacklist', - category: 'blacklist', - type: 'all' - }); - } - - public exec(message: BushMessage | BushSlashMessage): boolean { - if (!message.author) return false; - if (this.client.isOwner(message.author) || this.client.isSuperUser(message.author)) return false; - if (this.client.cache.global.blacklistedUsers.includes(message.author.id)) { - this.client.console.debug(`UserBlacklistInhibitor blocked message.`); - return true; - } - } -} diff --git a/src/inhibitors/blacklist/userGlobalBlacklist.ts b/src/inhibitors/blacklist/userGlobalBlacklist.ts new file mode 100644 index 0000000..061bae9 --- /dev/null +++ b/src/inhibitors/blacklist/userGlobalBlacklist.ts @@ -0,0 +1,25 @@ +import { BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; + +export default class UserGlobalBlacklistInhibitor extends BushInhibitor { + public constructor() { + super('userGlobalBlacklist', { + reason: 'userGlobalBlacklist', + category: 'blacklist', + type: 'all' + }); + } + + public exec(message: BushMessage | BushSlashMessage): boolean { + if (!message.author) return false; + if ( + this.client.isOwner(message.author) || + this.client.isSuperUser(message.author) || + this.client.user.id === message.author.id + ) + return false; + if (this.client.cache.global.blacklistedUsers.includes(message.author.id)) { + this.client.console.debug(`userGlobalBlacklist blocked message.`); + return true; + } + } +} diff --git a/src/inhibitors/blacklist/userGuildBlacklist.ts b/src/inhibitors/blacklist/userGuildBlacklist.ts new file mode 100644 index 0000000..02a3762 --- /dev/null +++ b/src/inhibitors/blacklist/userGuildBlacklist.ts @@ -0,0 +1,25 @@ +import { BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; + +export default class UserGuildBlacklistInhibitor extends BushInhibitor { + public constructor() { + super('userGuildBlacklist', { + reason: 'userGuildBlacklist', + category: 'blacklist', + type: 'all' + }); + } + + public async exec(message: BushMessage | BushSlashMessage): Promise<boolean> { + if (!message.author || !message.guild) return false; + if ( + this.client.isOwner(message.author) || + this.client.isSuperUser(message.author) || + this.client.user.id === message.author.id + ) + return false; + if ((await message.guild.getSetting('blacklistedUsers'))?.includes(message.author.id)) { + this.client.console.debug(`userGuildBlacklist blocked message.`); + return true; + } + } +} diff --git a/src/inhibitors/commands/disabledCommand.ts b/src/inhibitors/commands/disabledCommand.ts deleted file mode 100644 index fb31375..0000000 --- a/src/inhibitors/commands/disabledCommand.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BushCommand, BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; - -export default class DisabledCommandInhibitor extends BushInhibitor { - public constructor() { - super('disabledCommand', { - reason: 'disabled', - type: 'pre', - priority: 3 - }); - } - - public async exec(message: BushMessage | BushSlashMessage, command: BushCommand): Promise<boolean> { - if (this.client.isOwner(message.author)) return false; - if (this.client.cache.global.disabledCommands.includes(command?.id)) { - this.client.console.debug(`DisabledCommandInhibitor blocked message.`); - return true; - } - } -} diff --git a/src/inhibitors/commands/globalDisabledCommand.ts b/src/inhibitors/commands/globalDisabledCommand.ts new file mode 100644 index 0000000..3ce39c2 --- /dev/null +++ b/src/inhibitors/commands/globalDisabledCommand.ts @@ -0,0 +1,19 @@ +import { BushCommand, BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; + +export default class DisabledGuildCommandInhibitor extends BushInhibitor { + public constructor() { + super('disabledGlobalCommand', { + reason: 'disabledGlobal', + type: 'pre', + priority: 4 + }); + } + + public async exec(message: BushMessage | BushSlashMessage, command: BushCommand): Promise<boolean> { + if (message.author.isOwner()) return false; + if (this.client.cache.global.disabledCommands?.includes(command?.id)) { + this.client.console.debug(`disabledGlobalCommand blocked message.`); + return true; + } + } +} diff --git a/src/inhibitors/commands/guildDisabledCommand.ts b/src/inhibitors/commands/guildDisabledCommand.ts new file mode 100644 index 0000000..a036ec5 --- /dev/null +++ b/src/inhibitors/commands/guildDisabledCommand.ts @@ -0,0 +1,21 @@ +import { BushCommand, BushInhibitor, BushMessage, BushSlashMessage } from '@lib'; + +export default class DisabledGuildCommandInhibitor extends BushInhibitor { + public constructor() { + super('disabledGuildCommand', { + reason: 'disabledGuild', + type: 'pre', + priority: 3 + }); + } + + public async exec(message: BushMessage | BushSlashMessage, command: BushCommand): Promise<boolean> { + if (!message.guild || !message.guild) return; + if (message.author.isOwner() || message.author.isSuperUser()) return false; // super users bypass guild disabled commands + + if ((await message.guild.getSetting('disabledCommands'))?.includes(command?.id)) { + this.client.console.debug(`disabledGuildCommand blocked message.`); + return true; + } + } +} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 6538ebf..6a08c54 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -45,6 +45,9 @@ import got from 'got'; import humanizeDuration from 'humanize-duration'; import { Op } from 'sequelize'; import { promisify } from 'util'; +import { BushNewsChannel } from '../discord.js/BushNewsChannel'; +import { BushTextChannel } from '../discord.js/BushTextChannel'; +import { BushUserResolvable } from './BushClient'; interface hastebinRes { key: string; @@ -322,7 +325,7 @@ export class BushClientUtil extends ClientUtil { let curPage = 0; if (typeof embeds !== 'object') throw 'embeds must be an object'; const msg: Message = await message.util.reply({ - content: text, + content: text || null, embeds: [embeds[curPage]], components: [getPaginationRow()] }); @@ -527,16 +530,22 @@ export class BushClientUtil extends ClientUtil { const environment = this.client.config.dev ? 'development' : 'production'; const row = await Global.findByPk(environment); const oldValue: any[] = row[key]; + const newValue = this.addOrRemoveFromArray(action, oldValue, value); + row[key] = newValue; + this.client.cache.global[key] = newValue; + return await row.save().catch((e) => this.client.logger.error('insertOrRemoveFromGlobal', e?.stack || e)); + } + + public addOrRemoveFromArray(action: 'add' | 'remove', array: any[], value: any): any[] { let newValue: any[]; + if (!array) return null; if (action === 'add') { - if (!oldValue.includes(action)) oldValue.push(value); - newValue = oldValue; + if (!array.includes(action)) array.push(value); + newValue = array; } else { - newValue = oldValue.filter((ae) => ae !== value); + newValue = array.filter((ae) => ae !== value); } - row[key] = newValue; - this.client.cache.global[key] = newValue; - return await row.save().catch((e) => this.client.logger.error('insertOrRemoveFromGlobal', e?.stack || e)); + return newValue; } /** @@ -587,11 +596,13 @@ export class BushClientUtil extends ClientUtil { moderator: BushGuildMember, victim: BushGuildMember, type: 'mute' | 'unmute' | 'warn' | 'kick' | 'ban' | 'unban' | 'add a punishment role to' | 'remove a punishment role from', - checkModerator = true + checkModerator = true, + force = false ): true | string { if (moderator.guild.id !== victim.guild.id) { throw 'moderator and victim not in same guild'; } + if (force) return true; const isOwner = moderator.guild.ownerId === moderator.id; if (moderator.id === victim.id) { return `${this.client.util.emojis.error} You cannot ${type} yourself.`; @@ -708,7 +719,6 @@ 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: { @@ -768,4 +778,6 @@ export class BushClientUtil extends ClientUtil { return arrByte[1] + ', ' + arrByte[2] + ', ' + arrByte[3]; } + + public async lockdownChannel(options: { channel: BushTextChannel | BushNewsChannel; moderator: BushUserResolvable }) {} } diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index 90c68df..6cf981b 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -86,6 +86,7 @@ export interface BushCommandOptions extends CommandOptions { }; args?: BushArgumentOptions[] | ArgumentGenerator; category: string; + completelyHide?: boolean; } export class BushCommand extends Command { @@ -104,12 +105,16 @@ export class BushCommand extends Command { /** Whether the command is hidden from the help command. */ public hidden: boolean; + /** Completely hide this command from the help command. */ + public completelyHide: boolean; + public constructor(id: string, options?: BushCommandOptions) { super(id, options); this.options = options; this.hidden = options.hidden || false; this.restrictedChannels = options.restrictedChannels; this.restrictedGuilds = options.restrictedGuilds; + this.completelyHide = options.completelyHide; } public exec(message: BushMessage, args: any): any; diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 6eca44d..f695f8b 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -12,7 +12,7 @@ export class BushGuild extends Guild { } public async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> { - return ((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id })).get(setting); + return ((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }))[setting]; } public async setSetting<K extends keyof GuildModel>(setting: K, value: GuildDB[K]): Promise<GuildDB> { diff --git a/src/lib/models/Global.ts b/src/lib/models/Global.ts index ba77302..8664365 100644 --- a/src/lib/models/Global.ts +++ b/src/lib/models/Global.ts @@ -60,7 +60,8 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes set: function (val: Snowflake[]) { return this.setDataValue('superUsers', JSON.stringify(val) as unknown as Snowflake[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' }, disabledCommands: { type: DataTypes.STRING, @@ -70,7 +71,8 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes set: function (val: Snowflake[]) { return this.setDataValue('disabledCommands', JSON.stringify(val) as unknown as string[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' }, blacklistedUsers: { type: DataTypes.STRING, @@ -80,7 +82,8 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes set: function (val: Snowflake[]) { return this.setDataValue('blacklistedUsers', JSON.stringify(val) as unknown as Snowflake[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' }, blacklistedGuilds: { type: DataTypes.STRING, @@ -90,7 +93,8 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes set: function (val: Snowflake[]) { return this.setDataValue('blacklistedGuilds', JSON.stringify(val) as unknown as Snowflake[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' }, blacklistedChannels: { type: DataTypes.STRING, @@ -100,7 +104,8 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes set: function (val: Snowflake[]) { return this.setDataValue('blacklistedChannels', JSON.stringify(val) as unknown as Snowflake[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' } }, { sequelize: sequelize } diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index 43fcd64..30072b3 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -8,9 +8,12 @@ export interface GuildModel { prefix: string; autoPublishChannels: Snowflake[]; blacklistedChannels: Snowflake[]; + blacklistedUsers: Snowflake[]; welcomeChannel: Snowflake; muteRole: Snowflake; punishmentEnding: string; + disabledCommands: string[]; + lockdownChannels: Snowflake[]; } export interface GuildModelCreationAttributes { @@ -18,9 +21,12 @@ export interface GuildModelCreationAttributes { prefix?: string; autoPublishChannels?: Snowflake[]; blacklistedChannels?: Snowflake[]; + blacklistedUsers?: Snowflake[]; welcomeChannel?: Snowflake; muteRole?: Snowflake; punishmentEnding?: string; + disabledCommands?: string[]; + lockdownChannels?: Snowflake[]; } export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel { @@ -28,9 +34,12 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i prefix!: string; autoPublishChannels: Snowflake[]; blacklistedChannels: Snowflake[]; + blacklistedUsers: Snowflake[]; welcomeChannel: Snowflake; muteRole: Snowflake; punishmentEnding: string; + disabledCommands: string[]; + lockdownChannels: Snowflake[]; static initModel(sequelize: Sequelize, client: BushClient): void { Guild.init( @@ -52,7 +61,8 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i set: function (val: Snowflake[]) { return this.setDataValue('autoPublishChannels', JSON.stringify(val) as unknown as Snowflake[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' }, blacklistedChannels: { type: DataTypes.STRING, @@ -62,7 +72,19 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i set: function (val: Snowflake[]) { return this.setDataValue('blacklistedChannels', JSON.stringify(val) as unknown as Snowflake[]); }, - allowNull: true + allowNull: false, + defaultValue: '[]' + }, + blacklistedUsers: { + type: DataTypes.STRING, + get: function () { + return JSON.parse(this.getDataValue('blacklistedUsers') as unknown as string); + }, + set: function (val: Snowflake[]) { + return this.setDataValue('blacklistedUsers', JSON.stringify(val) as unknown as Snowflake[]); + }, + allowNull: false, + defaultValue: '[]' }, welcomeChannel: { type: DataTypes.STRING, @@ -75,6 +97,28 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i punishmentEnding: { type: DataTypes.TEXT, allowNull: true + }, + disabledCommands: { + type: DataTypes.STRING, + get: function () { + return JSON.parse(this.getDataValue('disabledCommands') as unknown as string); + }, + set: function (val: string[]) { + return this.setDataValue('disabledCommands', JSON.stringify(val) as unknown as string[]); + }, + allowNull: false, + defaultValue: '[]' + }, + lockdownChannels: { + type: DataTypes.STRING, + get: function () { + return JSON.parse(this.getDataValue('lockdownChannels') as unknown as string); + }, + set: function (val: Snowflake[]) { + return this.setDataValue('lockdownChannels', JSON.stringify(val) as unknown as Snowflake[]); + }, + allowNull: false, + defaultValue: '[]' } }, { sequelize: sequelize } diff --git a/src/lib/models/Level.ts b/src/lib/models/Level.ts index b834992..755b1c6 100644 --- a/src/lib/models/Level.ts +++ b/src/lib/models/Level.ts @@ -3,12 +3,14 @@ import { DataTypes, Sequelize } from 'sequelize'; import { BaseModel } from './BaseModel'; export interface LevelModel { - id: Snowflake; + user: Snowflake; + guild: Snowflake; xp: number; } export interface LevelModelCreationAttributes { - id: Snowflake; + user: Snowflake; + guild: Snowflake; xp?: number; } @@ -16,7 +18,11 @@ export class Level extends BaseModel<LevelModel, LevelModelCreationAttributes> { /** * The user's id. */ - public id: Snowflake; + public user: Snowflake; + /** + * The guild where the user is gaining xp. + */ + public guild: Snowflake; /** * The user's xp. */ @@ -28,9 +34,12 @@ export class Level extends BaseModel<LevelModel, LevelModelCreationAttributes> { static initModel(sequelize: Sequelize): void { Level.init( { - id: { + user: { + type: DataTypes.STRING, + allowNull: false + }, + guild: { type: DataTypes.STRING, - primaryKey: true, allowNull: false }, xp: { diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts index 747c6d6..3ca2e8c 100644 --- a/src/lib/utils/BushConstants.ts +++ b/src/lib/utils/BushConstants.ts @@ -351,9 +351,11 @@ export class BushConstants { DISABLED_GUILD: 'disabledGuild', DISABLED_GLOBAL: 'disabledGlobal', ROLE_BLACKLIST: 'roleBlacklist', - USER_BLACKLIST: 'userBlacklist', + USER_GUILD_BLACKLIST: 'userGuildBlacklist', + USER_GLOBAL_BLACKLIST: 'userGlobalBlacklist', RESTRICTED_GUILD: 'restrictedGuild', - CHANNEL_BLACKLIST: 'channelBlacklist', + CHANNEL_GUILD_BLACKLIST: 'channelGuildBlacklist', + CHANNEL_GLOBAL_BLACKLIST: 'channelGlobalBlacklist', RESTRICTED_CHANNEL: 'restrictedChannel' }; diff --git a/src/listeners/client/interaction.ts b/src/listeners/client/interactionCreate.ts index c848d15..1183004 100644 --- a/src/listeners/client/interaction.ts +++ b/src/listeners/client/interactionCreate.ts @@ -1,11 +1,11 @@ import { BushListener } from '@lib'; import { ButtonInteraction, CommandInteraction, Interaction, SelectMenuInteraction } from 'discord.js'; -export default class InteractionListener extends BushListener { +export default class InteractionCreateListener extends BushListener { public constructor() { - super('interaction', { + super('interactionCreate', { emitter: 'client', - event: 'interaction', + event: 'interactionCreate', category: 'client' }); } diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts index 03050c2..24d46af 100644 --- a/src/listeners/commands/commandBlocked.ts +++ b/src/listeners/commands/commandBlocked.ts @@ -37,12 +37,10 @@ export default class CommandBlockedListener extends BushListener { content: `${this.client.util.emojis.error} The \`${command.aliases[0]}\` command is currently disabled in \`${message.guild.name}\`.` }); } - case reasons.CHANNEL_BLACKLIST: { - return; - } - case reasons.USER_BLACKLIST: { - return; - } + case reasons.CHANNEL_GLOBAL_BLACKLIST: + case reasons.CHANNEL_GUILD_BLACKLIST: + case reasons.USER_GLOBAL_BLACKLIST: + case reasons.USER_GUILD_BLACKLIST: case reasons.ROLE_BLACKLIST: { return; } diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 9d51ea8..696b59b 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -38,20 +38,20 @@ export default class CommandErrorListener extends BushListener { errorUserEmbed.setDescription( `Oh no! While running the command \`${command.id}\`, an error occurred. Please give the developers code \`${errorNo}\`.` ); - await message.util.send({ embeds: [errorUserEmbed] }).catch((e) => { + (await message.util?.send({ embeds: [errorUserEmbed] }).catch((e) => { const channel = message.channel.type === 'DM' ? message.channel.recipient.tag : message.channel.name; this.client.console.warn('CommandError', `Failed to send user error embed in <<${channel}>>:\n` + e?.stack || e); - }); + })) ?? this.client.console.error('CommandError', `Failed to send user error embed.` + error?.stack || error, false); } else { const errorDevEmbed = new MessageEmbed() .setTitle('A Command Error Occurred') .setColor(this.client.util.colors.error) .setTimestamp() .setDescription(await this.client.util.codeblock(`${error?.stack || error}`, 2048, 'js')); - await message.util.send({ embeds: [errorDevEmbed] }).catch((e) => { + (await message.util?.send({ embeds: [errorDevEmbed] }).catch((e) => { const channel = message.channel.type === 'DM' ? message.channel.recipient.tag : message.channel.name; this.client.console.warn('CommandError', `Failed to send owner error stack in <<${channel}>>.` + e?.stack || e); - }); + })) ?? this.client.console.error('CommandError', `Failed to send owner error stack.` + error?.stack || error, false); } } const channel = message.channel.type === 'DM' ? message.channel.recipient.tag : message.channel.name; diff --git a/src/listeners/commands/slashBlocked.ts b/src/listeners/commands/slashBlocked.ts index bf98734..2443efb 100644 --- a/src/listeners/commands/slashBlocked.ts +++ b/src/listeners/commands/slashBlocked.ts @@ -38,12 +38,10 @@ export default class SlashBlockedListener extends BushListener { content: `${this.client.util.emojis.error} The \`${command.aliases[0]}\` command is currently disabled in \`${message.guild.name}\`.` }); } - case reasons.CHANNEL_BLACKLIST: { - return; - } - case reasons.USER_BLACKLIST: { - return; - } + case reasons.CHANNEL_GLOBAL_BLACKLIST: + case reasons.CHANNEL_GUILD_BLACKLIST: + case reasons.USER_GLOBAL_BLACKLIST: + case reasons.USER_GUILD_BLACKLIST: case reasons.ROLE_BLACKLIST: { return; } diff --git a/src/listeners/commands/slashCommandError.ts b/src/listeners/commands/slashCommandError.ts index 9bf5f6a..8abe788 100644 --- a/src/listeners/commands/slashCommandError.ts +++ b/src/listeners/commands/slashCommandError.ts @@ -37,18 +37,18 @@ export default class SlashCommandErrorListener extends BushListener { errorUserEmbed.setDescription( `Oh no! While running the command \`${command.id}\`, an error occurred. Please give the developers code \`${errorNo}\`.` ); - await message.util.send({ embeds: [errorUserEmbed] }).catch((e) => { + (await message.util?.send({ embeds: [errorUserEmbed] }).catch((e) => { this.client.console.warn('SlashError', `Failed to send user error embed in <<${channel}>>:\n` + e?.stack || e); - }); + })) ?? this.client.console.error('SlashError', `Failed to send user error embed.` + error?.stack || error, false); } else { const errorDevEmbed = new MessageEmbed() .setTitle('A Slash Command Error Occurred') .setColor(this.client.util.colors.error) .setTimestamp() .setDescription(await this.client.util.codeblock(`${error?.stack || error}`, 2048, 'js')); - await message.util.send({ embeds: [errorDevEmbed] }).catch((e) => { + (await message.util?.send({ embeds: [errorDevEmbed] }).catch((e) => { this.client.console.warn('SlashError', `Failed to send owner error stack in <<${channel}>>.` + e?.stack || e); - }); + })) ?? this.client.console.error('SlashError', `Failed to send user error embed.` + error?.stack || error, false); } } const channel = (message.channel as GuildChannel)?.name || message.interaction.user.tag; diff --git a/src/listeners/message/level.ts b/src/listeners/message/level.ts index a35766b..a50f580 100644 --- a/src/listeners/message/level.ts +++ b/src/listeners/message/level.ts @@ -12,27 +12,37 @@ export default class LevelListener extends BushListener { } async exec(message: Message): Promise<void> { if (message.author.bot) return; + if (!message.author) return; + if (!message.guild) return; if (message.util?.parsed?.command) return; - if (this.levelCooldowns.has(message.author.id)) return; - if (!this.client.config.dev && message.guild.id != '516977525906341928') return; + if (this.levelCooldowns.has(message.guild.id + message.author.id)) return; if (this.blacklistedChannels.includes(message.channel.id)) return; if (!['DEFAULT', 'REPLY'].includes(message.type)) return; //checks for join messages, slash commands, booster messages etc const [user] = await Level.findOrBuild({ where: { - id: message.author.id + user: message.author.id, + guild: message.guild.id }, defaults: { - id: message.author.id + user: message.author.id, + guild: message.guild.id } }); const xpToGive = Level.genRandomizedXp(); - user.xp = user.xp + xpToGive; + user.increment('xp', { by: xpToGive }); const success = await user.save().catch((e) => { + console.debug(`User: ${message.author.id}`); + console.debug(`Guild: ${message.author.id}`); + console.debug(`Model: ${user}`); this.client.logger.error('LevelMessageListener', e?.stack || e); return false; }); - if (success) this.client.logger.verbose(`LevelMessageListener`, `Gave <<${xpToGive}>> XP to <<${message.author.tag}>>.`); - this.levelCooldowns.add(message.author.id); + if (success) + this.client.logger.verbose( + `LevelMessageListener`, + `Gave <<${xpToGive}>> XP to <<${message.author.tag}>> in <<${message.guild}>>.` + ); + this.levelCooldowns.add(message.guild.id + message.author.id); setTimeout(() => this.levelCooldowns.delete(message.author.id), 60_000); } } @@ -329,6 +329,13 @@ __metadata: languageName: node linkType: hard +"@types/tinycolor2@npm:^1": + version: 1.4.3 + resolution: "@types/tinycolor2@npm:1.4.3" + checksum: 61984b2825d4ee902016ef24777787bb2fb9e4999ccd4f7e5a709442c00cf90ba4afa510b9c78f18dcc83c03305d597d5fe3825a6aad38354f95c68af70ebc1b + languageName: node + linkType: hard + "@types/uuid@npm:^8.3.0": version: 8.3.1 resolution: "@types/uuid@npm:8.3.1" @@ -674,6 +681,7 @@ __metadata: "@types/humanize-duration": ^3 "@types/module-alias": ^2 "@types/node": ^14.14.22 + "@types/tinycolor2": ^1 "@types/uuid": ^8.3.0 "@typescript-eslint/eslint-plugin": ^4.14.1 "@typescript-eslint/parser": ^4.14.1 @@ -687,6 +695,7 @@ __metadata: esbuild: ^0.12.11 eslint: ^7.29.0 eslint-config-prettier: ^8.3.0 + fuse.js: ^6.4.6 got: ^11.8.2 humanize-duration: ^3.27.0 madge: ^5.0.1 @@ -698,6 +707,7 @@ __metadata: rimraf: ^3.0.2 sequelize: ^6.5.0 source-map-support: ^0.5.19 + tinycolor2: ^1.4.2 typescript: 4.2.4 uuid: ^8.3.2 dependenciesMeta: @@ -1560,6 +1570,13 @@ discord.js@NotEnoughUpdates/discord.js: languageName: node linkType: hard +"fuse.js@npm:^6.4.6": + version: 6.4.6 + resolution: "fuse.js@npm:6.4.6" + checksum: 012dfacdc9a3c065d05d031b4eaaad7dd460f66dca100fcef182ed4e70a6b763b6753071702589d0b28def34b48ad077f01426d4ac1d75ef374564f6baa943fd + languageName: node + linkType: hard + "get-amd-module-type@npm:^3.0.0": version: 3.0.0 resolution: "get-amd-module-type@npm:3.0.0" @@ -3150,6 +3167,13 @@ resolve@^1.19.0: languageName: node linkType: hard +"tinycolor2@npm:^1.4.2": + version: 1.4.2 + resolution: "tinycolor2@npm:1.4.2" + checksum: 57ed262e08815a4ab0ed933edafdbc6555a17081781766149813b44a080ecbe58b3ee281e81c0e75b42e4d41679f138cfa98eabf043f829e0683c04adb12c031 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" |