diff options
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 |
