From 14eb0e617b084080c4cffc5b781b311c65c5f928 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Sun, 28 Aug 2022 21:51:17 -0400 Subject: rebrand v3 --- .eslintrc.cjs | 2 +- .prettierrc.json | 2 +- .vscode/launch.json | 2 +- .vscode/settings.json | 1 + .vscode/typescript.code-snippets | 4 +- README.md | 31 +- ecosystem.config.cjs | 6 +- lib/arguments/abbreviatedNumber.ts | 4 +- lib/arguments/contentWithDuration.ts | 4 +- lib/arguments/discordEmoji.ts | 4 +- lib/arguments/duration.ts | 4 +- lib/arguments/durationSeconds.ts | 4 +- lib/arguments/globalUser.ts | 4 +- lib/arguments/messageLink.ts | 4 +- lib/arguments/permission.ts | 4 +- lib/arguments/roleWithDuration.ts | 4 +- lib/arguments/snowflake.ts | 4 +- lib/arguments/tinyColor.ts | 4 +- lib/automod/AutomodShared.ts | 6 +- lib/automod/MessageAutomod.ts | 10 +- lib/common/BotCache.ts | 26 + lib/common/BushCache.ts | 26 - lib/common/HighlightManager.ts | 2 +- .../discord-akairo/BotArgumentTypeCaster.ts | 3 + lib/extensions/discord-akairo/BotCommand.ts | 597 ++++++++++++++++++++ lib/extensions/discord-akairo/BotCommandHandler.ts | 37 ++ lib/extensions/discord-akairo/BotInhibitor.ts | 18 + .../discord-akairo/BotInhibitorHandler.ts | 3 + lib/extensions/discord-akairo/BotListener.ts | 3 + .../discord-akairo/BotListenerHandler.ts | 3 + lib/extensions/discord-akairo/BotTask.ts | 3 + lib/extensions/discord-akairo/BotTaskHandler.ts | 3 + .../discord-akairo/BushArgumentTypeCaster.ts | 3 - lib/extensions/discord-akairo/BushClient.ts | 601 -------------------- lib/extensions/discord-akairo/BushCommand.ts | 597 -------------------- .../discord-akairo/BushCommandHandler.ts | 37 -- lib/extensions/discord-akairo/BushInhibitor.ts | 19 - .../discord-akairo/BushInhibitorHandler.ts | 3 - lib/extensions/discord-akairo/BushListener.ts | 3 - .../discord-akairo/BushListenerHandler.ts | 3 - lib/extensions/discord-akairo/BushTask.ts | 3 - lib/extensions/discord-akairo/BushTaskHandler.ts | 3 - lib/extensions/discord-akairo/TanzaniteClient.ts | 591 ++++++++++++++++++++ lib/extensions/discord.js/BotClientEvents.ts | 211 +++++++ lib/extensions/discord.js/BushClientEvents.ts | 200 ------- lib/extensions/discord.js/ExtendedGuild.ts | 46 +- lib/extensions/discord.js/ExtendedGuildMember.ts | 148 ++--- lib/extensions/discord.js/ExtendedUser.ts | 4 +- lib/index.ts | 47 +- lib/models/instance/Guild.ts | 4 +- lib/types/BushInspectOptions.ts | 123 ----- lib/types/InspectOptions.ts | 127 +++++ lib/types/misc.ts | 14 + lib/utils/Arg.ts | 36 +- lib/utils/BotClientUtils.ts | 496 +++++++++++++++++ lib/utils/BushClientUtils.ts | 496 ----------------- lib/utils/BushConstants.ts | 554 ------------------- lib/utils/BushLogger.ts | 315 ----------- lib/utils/BushUtils.ts | 615 --------------------- lib/utils/Constants.ts | 554 +++++++++++++++++++ lib/utils/ErrorHandler.ts | 8 +- lib/utils/FormatResponse.ts | 4 +- lib/utils/Logger.ts | 314 +++++++++++ lib/utils/Minecraft.ts | 4 - lib/utils/Minecraft_Test.ts | 10 +- lib/utils/Utils.ts | 615 +++++++++++++++++++++ package.json | 6 +- src/bot.ts | 8 +- src/commands/_fake-command/ironmoon.ts | 4 +- src/commands/admin/channelPermissions.ts | 4 +- src/commands/admin/roleAll.ts | 4 +- src/commands/config/blacklist.ts | 4 +- src/commands/config/config.ts | 4 +- src/commands/config/disable.ts | 6 +- src/commands/config/features.ts | 4 +- src/commands/config/log.ts | 4 +- src/commands/dev/__template.ts | 4 +- src/commands/dev/dm.ts | 4 +- src/commands/dev/eval.ts | 10 +- src/commands/dev/javascript.ts | 4 +- src/commands/dev/reload.ts | 4 +- src/commands/dev/say.ts | 4 +- src/commands/dev/servers.ts | 4 +- src/commands/dev/sh.ts | 4 +- src/commands/dev/superUser.ts | 4 +- src/commands/dev/syncAutomod.ts | 4 +- src/commands/dev/test.ts | 4 +- src/commands/fun/coinFlip.ts | 4 +- src/commands/fun/dice.ts | 4 +- src/commands/fun/eightBall.ts | 4 +- src/commands/fun/minesweeper.ts | 4 +- src/commands/info/avatar.ts | 4 +- src/commands/info/botInfo.ts | 4 +- src/commands/info/color.ts | 4 +- src/commands/info/guildInfo.ts | 16 +- src/commands/info/help.ts | 16 +- src/commands/info/icon.ts | 4 +- src/commands/info/inviteInfo.ts | 4 +- src/commands/info/links.ts | 4 +- src/commands/info/ping.ts | 4 +- src/commands/info/pronouns.ts | 4 +- src/commands/info/snowflake.ts | 8 +- src/commands/info/userInfo.ts | 4 +- src/commands/leveling/leaderboard.ts | 4 +- src/commands/leveling/level.ts | 4 +- src/commands/leveling/levelRoles.ts | 4 +- src/commands/leveling/setLevel.ts | 4 +- src/commands/leveling/setXp.ts | 4 +- src/commands/moderation/_activePunishments.ts | 4 +- src/commands/moderation/ban.ts | 6 +- src/commands/moderation/block.ts | 6 +- src/commands/moderation/evidence.ts | 7 +- src/commands/moderation/hideCase.ts | 7 +- src/commands/moderation/kick.ts | 6 +- src/commands/moderation/lockdown.ts | 4 +- src/commands/moderation/massBan.ts | 13 +- src/commands/moderation/massEvidence.ts | 7 +- src/commands/moderation/modlog.ts | 4 +- src/commands/moderation/mute.ts | 6 +- src/commands/moderation/myLogs.ts | 4 +- src/commands/moderation/purge.ts | 7 +- src/commands/moderation/removeReactionEmoji.ts | 4 +- src/commands/moderation/role.ts | 6 +- src/commands/moderation/slowmode.ts | 4 +- src/commands/moderation/timeout.ts | 6 +- src/commands/moderation/unban.ts | 6 +- src/commands/moderation/unblock.ts | 6 +- src/commands/moderation/unlockdown.ts | 4 +- src/commands/moderation/unmute.ts | 6 +- src/commands/moderation/untimeout.ts | 6 +- src/commands/moderation/warn.ts | 6 +- src/commands/moulberry-bush/capePermissions.ts | 4 +- src/commands/moulberry-bush/capes.ts | 4 +- src/commands/moulberry-bush/giveawayPing.ts | 4 +- src/commands/moulberry-bush/moulHammer.ts | 12 +- src/commands/moulberry-bush/neuRepo.ts | 4 +- src/commands/moulberry-bush/report.ts | 4 +- src/commands/moulberry-bush/rule.ts | 4 +- src/commands/moulberry-bush/serverStatus.ts | 4 +- src/commands/moulberry-bush/solved.ts | 4 +- src/commands/tickets/ticket-!.ts | 4 +- src/commands/utilities/_poll.ts | 4 +- src/commands/utilities/activity.ts | 8 +- src/commands/utilities/calculator.ts | 4 +- src/commands/utilities/decode.ts | 4 +- src/commands/utilities/hash.ts | 4 +- src/commands/utilities/highlight-!.ts | 4 +- src/commands/utilities/highlight-add.ts | 4 +- src/commands/utilities/highlight-block.ts | 4 +- src/commands/utilities/highlight-clear.ts | 4 +- src/commands/utilities/highlight-matches.ts | 4 +- src/commands/utilities/highlight-remove.ts | 4 +- src/commands/utilities/highlight-show.ts | 4 +- src/commands/utilities/highlight-unblock.ts | 4 +- src/commands/utilities/price.ts | 4 +- src/commands/utilities/remind.ts | 4 +- src/commands/utilities/reminders.ts | 4 +- src/commands/utilities/steal.ts | 4 +- src/commands/utilities/suicide.ts | 4 +- src/commands/utilities/uuid.ts | 4 +- src/commands/utilities/viewRaw.ts | 4 +- src/commands/utilities/whoHasRole.ts | 4 +- src/commands/utilities/wolframAlpha.ts | 4 +- src/inhibitors/blacklist/channelGlobalBlacklist.ts | 7 +- src/inhibitors/blacklist/channelGuildBlacklist.ts | 7 +- src/inhibitors/blacklist/guildBlacklist.ts | 5 +- src/inhibitors/blacklist/userGlobalBlacklist.ts | 5 +- src/inhibitors/blacklist/userGuildBlacklist.ts | 5 +- src/inhibitors/checks/fatal.ts | 5 +- src/inhibitors/checks/guildUnavailable.ts | 5 +- src/inhibitors/command/dm.ts | 7 +- src/inhibitors/command/globalDisabledCommand.ts | 7 +- src/inhibitors/command/guild.ts | 7 +- src/inhibitors/command/guildDisabledCommand.ts | 7 +- src/inhibitors/command/nsfw.ts | 7 +- src/inhibitors/command/owner.ts | 7 +- src/inhibitors/command/restrictedChannel.ts | 7 +- src/inhibitors/command/restrictedGuild.ts | 7 +- src/inhibitors/command/superUser.ts | 7 +- src/listeners/automod/automodCreate.ts | 9 +- src/listeners/automod/automodUpdate.ts | 9 +- src/listeners/automod/memberAutomod.ts | 6 +- src/listeners/automod/presenceAutomod.ts | 6 +- src/listeners/bush/appealListener.ts | 9 +- src/listeners/bush/joinAutoBan.ts | 11 +- src/listeners/bush/supportThread.ts | 9 +- src/listeners/bush/userUpdateAutoBan.ts | 11 +- src/listeners/client/akairoDebug.ts | 9 +- src/listeners/client/dcjsDebug.ts | 15 - src/listeners/client/dcjsError.ts | 15 - src/listeners/client/dcjsWarn.ts | 15 - src/listeners/client/djsDebug.ts | 14 + src/listeners/client/djsError.ts | 14 + src/listeners/client/djsWarn.ts | 14 + src/listeners/client/ready.ts | 6 +- src/listeners/commands/commandBlocked.ts | 15 +- src/listeners/commands/commandCooldown.ts | 9 +- src/listeners/commands/commandError.ts | 9 +- src/listeners/commands/commandLocked.ts | 9 +- .../commands/commandMissingPermissions.ts | 13 +- src/listeners/commands/commandStarted.ts | 9 +- src/listeners/commands/messageBlocked.ts | 9 +- src/listeners/commands/slashBlocked.ts | 9 +- src/listeners/commands/slashCommandError.ts | 9 +- src/listeners/commands/slashMissingPermissions.ts | 9 +- src/listeners/commands/slashNotFound.ts | 9 +- src/listeners/commands/slashStarted.ts | 9 +- .../contextCommands/contextCommandBlocked.ts | 7 +- .../contextCommands/contextCommandError.ts | 7 +- .../contextCommands/contextCommandNotFound.ts | 7 +- .../contextCommands/contextCommandStarted.ts | 7 +- src/listeners/guild-custom/bushLockdown.ts | 33 -- src/listeners/guild-custom/bushUnlockdown.ts | 33 -- src/listeners/guild-custom/lockdown.ts | 32 ++ src/listeners/guild-custom/unlockdown.ts | 32 ++ src/listeners/guild/guildCreate.ts | 9 +- src/listeners/guild/guildDelete.ts | 9 +- src/listeners/guild/guildMemberAdd.ts | 9 +- src/listeners/guild/guildMemberRemove.ts | 9 +- src/listeners/guild/joinRoles.ts | 9 +- src/listeners/guild/syncUnbanPunishmentModel.ts | 9 +- src/listeners/interaction/interactionCreate.ts | 9 +- src/listeners/member-custom/bushBan.ts | 33 -- src/listeners/member-custom/bushBlock.ts | 35 -- src/listeners/member-custom/bushKick.ts | 32 -- src/listeners/member-custom/bushLevelUpdate.ts | 64 --- src/listeners/member-custom/bushMute.ts | 33 -- src/listeners/member-custom/bushPunishRole.ts | 32 -- .../member-custom/bushPunishRoleRemove.ts | 33 -- src/listeners/member-custom/bushPurge.ts | 44 -- src/listeners/member-custom/bushRemoveTimeout.ts | 32 -- src/listeners/member-custom/bushTimeout.ts | 33 -- src/listeners/member-custom/bushUnban.ts | 32 -- src/listeners/member-custom/bushUnblock.ts | 33 -- src/listeners/member-custom/bushUnmute.ts | 32 -- src/listeners/member-custom/bushUpdateModlog.ts | 41 -- src/listeners/member-custom/bushUpdateSettings.ts | 34 -- src/listeners/member-custom/bushWarn.ts | 32 -- src/listeners/member-custom/customBan.ts | 32 ++ src/listeners/member-custom/customBlock.ts | 36 ++ src/listeners/member-custom/customKick.ts | 31 ++ src/listeners/member-custom/customMute.ts | 32 ++ src/listeners/member-custom/customPurge.ts | 43 ++ src/listeners/member-custom/customRemoveTimeout.ts | 31 ++ src/listeners/member-custom/customTimeout.ts | 32 ++ src/listeners/member-custom/customUnban.ts | 31 ++ src/listeners/member-custom/customUnblock.ts | 32 ++ src/listeners/member-custom/customUnmute.ts | 31 ++ src/listeners/member-custom/customWarnMember.ts | 31 ++ src/listeners/member-custom/levelUpdate.ts | 63 +++ src/listeners/member-custom/massBan.ts | 11 +- src/listeners/member-custom/massEvidence.ts | 11 +- src/listeners/member-custom/punishRole.ts | 31 ++ src/listeners/member-custom/punishRoleRemove.ts | 32 ++ src/listeners/member-custom/updateModlog.ts | 40 ++ src/listeners/member-custom/updateSettings.ts | 33 ++ src/listeners/message/autoPublisher.ts | 9 +- src/listeners/message/blacklistedFile.ts | 7 +- src/listeners/message/boosterMessage.ts | 9 +- src/listeners/message/directMessage.ts | 9 +- src/listeners/message/highlight.ts | 9 +- src/listeners/message/level.ts | 11 +- src/listeners/message/quoteCreate.ts | 9 +- src/listeners/message/quoteEdit.ts | 7 +- src/listeners/message/verbose.ts | 9 +- src/listeners/other/consoleListener.ts | 116 ++-- src/listeners/other/exit.ts | 4 +- src/listeners/other/promiseRejection.ts | 4 +- src/listeners/other/uncaughtException.ts | 4 +- src/listeners/other/warning.ts | 4 +- src/listeners/rest/rateLimit.ts | 7 +- .../track-manual-punishments/modlogSyncBan.ts | 9 +- .../track-manual-punishments/modlogSyncKick.ts | 9 +- .../track-manual-punishments/modlogSyncTimeout.ts | 9 +- .../track-manual-punishments/modlogSyncUnban.ts | 9 +- src/listeners/ws/INTERACTION_CREATE.ts | 7 +- src/tasks/cache/cpuUsage.ts | 4 +- src/tasks/cache/updateCache.ts | 4 +- src/tasks/cache/updateHighlightCache.ts | 5 +- src/tasks/cache/updateNeuItemCache.ts | 4 +- src/tasks/cache/updatePriceItemCache.ts | 4 +- src/tasks/feature/handleReminders.ts | 4 +- src/tasks/feature/removeExpiredPunishements.ts | 12 +- src/tasks/feature/updateStats.ts | 4 +- src/tasks/stats/guildCount.ts | 5 +- src/tasks/stats/memberCount.ts | 4 +- yarn.lock | 118 ++-- 287 files changed, 5134 insertions(+), 5191 deletions(-) create mode 100644 lib/common/BotCache.ts delete mode 100644 lib/common/BushCache.ts create mode 100644 lib/extensions/discord-akairo/BotArgumentTypeCaster.ts create mode 100644 lib/extensions/discord-akairo/BotCommand.ts create mode 100644 lib/extensions/discord-akairo/BotCommandHandler.ts create mode 100644 lib/extensions/discord-akairo/BotInhibitor.ts create mode 100644 lib/extensions/discord-akairo/BotInhibitorHandler.ts create mode 100644 lib/extensions/discord-akairo/BotListener.ts create mode 100644 lib/extensions/discord-akairo/BotListenerHandler.ts create mode 100644 lib/extensions/discord-akairo/BotTask.ts create mode 100644 lib/extensions/discord-akairo/BotTaskHandler.ts delete mode 100644 lib/extensions/discord-akairo/BushArgumentTypeCaster.ts delete mode 100644 lib/extensions/discord-akairo/BushClient.ts delete mode 100644 lib/extensions/discord-akairo/BushCommand.ts delete mode 100644 lib/extensions/discord-akairo/BushCommandHandler.ts delete mode 100644 lib/extensions/discord-akairo/BushInhibitor.ts delete mode 100644 lib/extensions/discord-akairo/BushInhibitorHandler.ts delete mode 100644 lib/extensions/discord-akairo/BushListener.ts delete mode 100644 lib/extensions/discord-akairo/BushListenerHandler.ts delete mode 100644 lib/extensions/discord-akairo/BushTask.ts delete mode 100644 lib/extensions/discord-akairo/BushTaskHandler.ts create mode 100644 lib/extensions/discord-akairo/TanzaniteClient.ts create mode 100644 lib/extensions/discord.js/BotClientEvents.ts delete mode 100644 lib/extensions/discord.js/BushClientEvents.ts delete mode 100644 lib/types/BushInspectOptions.ts create mode 100644 lib/types/InspectOptions.ts create mode 100644 lib/types/misc.ts create mode 100644 lib/utils/BotClientUtils.ts delete mode 100644 lib/utils/BushClientUtils.ts delete mode 100644 lib/utils/BushConstants.ts delete mode 100644 lib/utils/BushLogger.ts delete mode 100644 lib/utils/BushUtils.ts create mode 100644 lib/utils/Constants.ts create mode 100644 lib/utils/Logger.ts create mode 100644 lib/utils/Utils.ts delete mode 100644 src/listeners/client/dcjsDebug.ts delete mode 100644 src/listeners/client/dcjsError.ts delete mode 100644 src/listeners/client/dcjsWarn.ts create mode 100644 src/listeners/client/djsDebug.ts create mode 100644 src/listeners/client/djsError.ts create mode 100644 src/listeners/client/djsWarn.ts delete mode 100644 src/listeners/guild-custom/bushLockdown.ts delete mode 100644 src/listeners/guild-custom/bushUnlockdown.ts create mode 100644 src/listeners/guild-custom/lockdown.ts create mode 100644 src/listeners/guild-custom/unlockdown.ts delete mode 100644 src/listeners/member-custom/bushBan.ts delete mode 100644 src/listeners/member-custom/bushBlock.ts delete mode 100644 src/listeners/member-custom/bushKick.ts delete mode 100644 src/listeners/member-custom/bushLevelUpdate.ts delete mode 100644 src/listeners/member-custom/bushMute.ts delete mode 100644 src/listeners/member-custom/bushPunishRole.ts delete mode 100644 src/listeners/member-custom/bushPunishRoleRemove.ts delete mode 100644 src/listeners/member-custom/bushPurge.ts delete mode 100644 src/listeners/member-custom/bushRemoveTimeout.ts delete mode 100644 src/listeners/member-custom/bushTimeout.ts delete mode 100644 src/listeners/member-custom/bushUnban.ts delete mode 100644 src/listeners/member-custom/bushUnblock.ts delete mode 100644 src/listeners/member-custom/bushUnmute.ts delete mode 100644 src/listeners/member-custom/bushUpdateModlog.ts delete mode 100644 src/listeners/member-custom/bushUpdateSettings.ts delete mode 100644 src/listeners/member-custom/bushWarn.ts create mode 100644 src/listeners/member-custom/customBan.ts create mode 100644 src/listeners/member-custom/customBlock.ts create mode 100644 src/listeners/member-custom/customKick.ts create mode 100644 src/listeners/member-custom/customMute.ts create mode 100644 src/listeners/member-custom/customPurge.ts create mode 100644 src/listeners/member-custom/customRemoveTimeout.ts create mode 100644 src/listeners/member-custom/customTimeout.ts create mode 100644 src/listeners/member-custom/customUnban.ts create mode 100644 src/listeners/member-custom/customUnblock.ts create mode 100644 src/listeners/member-custom/customUnmute.ts create mode 100644 src/listeners/member-custom/customWarnMember.ts create mode 100644 src/listeners/member-custom/levelUpdate.ts create mode 100644 src/listeners/member-custom/punishRole.ts create mode 100644 src/listeners/member-custom/punishRoleRemove.ts create mode 100644 src/listeners/member-custom/updateModlog.ts create mode 100644 src/listeners/member-custom/updateSettings.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b8ef98d..3666e7a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -161,7 +161,7 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', 'prefer-template': 'warn', '@typescript-eslint/no-this-alias': ['error', { allowDestructuring: true, allowedNames: ['that'] }], - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': 'off' /* ['warn', { argsIgnorePattern: '^_' }] */, 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': ['error'], 'deprecation/deprecation': 'warn', diff --git a/.prettierrc.json b/.prettierrc.json index d704546..5c947a7 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -7,7 +7,7 @@ "endOfLine": "lf", "overrides": [ { - "files": ["*BushClientEvents.ts"], + "files": ["*CustomClientEvents.ts"], "options": { "printWidth": 80 } diff --git a/.vscode/launch.json b/.vscode/launch.json index d6eb609..cce0ba4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,7 @@ { "type": "node-terminal", "command": "yarn start", - "name": "BushBot", + "name": "Tanzanite", "request": "launch", "cwd": "${workspaceFolder}", "program": "${workspaceFolder}/dist/src/bot.js", diff --git a/.vscode/settings.json b/.vscode/settings.json index 651c2c2..600c85e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "source.organizeImports": true, "source.format": true }, + "editor.formatOnSave": true, "diffEditor.wordWrap": "on", "editor.insertSpaces": false, "editor.wordWrap": "on", diff --git a/.vscode/typescript.code-snippets b/.vscode/typescript.code-snippets index 77b2f7f..4e2bec4 100644 --- a/.vscode/typescript.code-snippets +++ b/.vscode/typescript.code-snippets @@ -10,8 +10,8 @@ "description": "A bot command template", "body": [ "import {", - "\tBushCommand,", "\tclientSendAndPermCheck,", + "\tCustomCommand,", "\temojis,", "\ttype ArgType,", "\ttype CommandMessage,", @@ -20,7 +20,7 @@ "} from '#lib';", "", "import { ApplicationCommandOptionType } from 'discord.js';", - "export default class ${1:CommandName} extends BushCommand {", + "export default class ${1:CommandName} extends CustomCommand {", "\tpublic constructor() {", "\t\tsuper('${2:commandId}', {", "\t\t\taliases: ['${3:alias}'],", diff --git a/README.md b/README.md index 714965d..3cc7955 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,38 @@ -

BushBot

+

Tanzanite

- - + + - - CodeFactor + + CodeFactor - - + + - - lines + + lines - + license - - contributors + + contributors -
-BushBot is an open-sourced multi-purpose moderation, and leveling bot. +Tanzanite is a multipurpose moderation and utility bot. It contains an extensive auto-moderation system, leveling features, message highlighting, and information commands.

Set Up

@@ -61,4 +58,4 @@ If you would like to contribute to the bot feel free to open a pull request and

Credits

- discord.js - The main library used to interface with discord -- discord-akairo - The framework the bot is built on +- discord-akairo - The framework the bot is built on diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 3bd532f..32971cf 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -1,7 +1,7 @@ module.exports = { apps: [ ...['', '-beta'].map((e) => ({ - name: `bush-bot${e}`, + name: `tanzanite${e}`, script: 'yarn', args: 'start:raw', out_file: `../bushbot${e}.log`, @@ -22,9 +22,9 @@ module.exports = { 'user': 'pi', 'host': '192.168.1.210', 'ref': `origin/${e === 'production' ? 'master' : 'beta'}`, - 'repo': 'https://github.com/NotEnoughUpdates/bush-bot.git', + 'repo': 'https://github.com/TanzaniteBot/tanzanite.git', 'path': `/code/bush-bot${e === 'beta' ? '-beta' : ''}`, - 'post-deploy': `yarn install && yarn build && pm2 start ecosystem.config.cjs --only bush-bot${ + 'post-deploy': `yarn install && yarn build && pm2 start ecosystem.config.cjs --only tanzanite${ e === 'beta' ? '-beta' : '' }` } diff --git a/lib/arguments/abbreviatedNumber.ts b/lib/arguments/abbreviatedNumber.ts index a7d8ce5..5fe39b5 100644 --- a/lib/arguments/abbreviatedNumber.ts +++ b/lib/arguments/abbreviatedNumber.ts @@ -1,9 +1,9 @@ -import type { BushArgumentTypeCaster } from '#lib'; +import type { BotArgumentTypeCaster } from '#lib'; import assert from 'assert/strict'; import numeral from 'numeral'; assert(typeof numeral === 'function'); -export const abbreviatedNumber: BushArgumentTypeCaster = (_, phrase) => { +export const abbreviatedNumber: BotArgumentTypeCaster = (_, phrase) => { if (!phrase) return null; const num = numeral(phrase?.toLowerCase()).value(); diff --git a/lib/arguments/contentWithDuration.ts b/lib/arguments/contentWithDuration.ts index 0efba39..ec015dc 100644 --- a/lib/arguments/contentWithDuration.ts +++ b/lib/arguments/contentWithDuration.ts @@ -1,5 +1,5 @@ -import { parseDuration, type BushArgumentTypeCaster, type ParsedDuration } from '#lib'; +import { parseDuration, type BotArgumentTypeCaster, type ParsedDuration } from '#lib'; -export const contentWithDuration: BushArgumentTypeCaster> = async (_, phrase) => { +export const contentWithDuration: BotArgumentTypeCaster> = async (_, phrase) => { return parseDuration(phrase); }; diff --git a/lib/arguments/discordEmoji.ts b/lib/arguments/discordEmoji.ts index 92d6502..0a0d168 100644 --- a/lib/arguments/discordEmoji.ts +++ b/lib/arguments/discordEmoji.ts @@ -1,7 +1,7 @@ -import { regex, type BushArgumentTypeCaster } from '#lib'; +import { regex, type BotArgumentTypeCaster } from '#lib'; import type { Snowflake } from 'discord.js'; -export const discordEmoji: BushArgumentTypeCaster = (_, phrase) => { +export const discordEmoji: BotArgumentTypeCaster = (_, phrase) => { if (!phrase) return null; const validEmoji: RegExpExecArray | null = regex.discordEmoji.exec(phrase); if (!validEmoji || !validEmoji.groups) return null; diff --git a/lib/arguments/duration.ts b/lib/arguments/duration.ts index 09dd3d5..4952dc4 100644 --- a/lib/arguments/duration.ts +++ b/lib/arguments/duration.ts @@ -1,5 +1,5 @@ -import { parseDuration, type BushArgumentTypeCaster } from '#lib'; +import { parseDuration, type BotArgumentTypeCaster } from '#lib'; -export const duration: BushArgumentTypeCaster = (_, phrase) => { +export const duration: BotArgumentTypeCaster = (_, phrase) => { return parseDuration(phrase).duration; }; diff --git a/lib/arguments/durationSeconds.ts b/lib/arguments/durationSeconds.ts index d8d6749..8deee5b 100644 --- a/lib/arguments/durationSeconds.ts +++ b/lib/arguments/durationSeconds.ts @@ -1,6 +1,6 @@ -import { parseDuration, type BushArgumentTypeCaster } from '#lib'; +import { parseDuration, type BotArgumentTypeCaster } from '#lib'; -export const durationSeconds: BushArgumentTypeCaster = (_, phrase) => { +export const durationSeconds: BotArgumentTypeCaster = (_, phrase) => { phrase += 's'; return parseDuration(phrase).duration; }; diff --git a/lib/arguments/globalUser.ts b/lib/arguments/globalUser.ts index 4324aa9..4198e3c 100644 --- a/lib/arguments/globalUser.ts +++ b/lib/arguments/globalUser.ts @@ -1,7 +1,7 @@ -import type { BushArgumentTypeCaster } from '#lib'; +import type { BotArgumentTypeCaster } from '#lib'; import type { User } from 'discord.js'; // resolve non-cached users -export const globalUser: BushArgumentTypeCaster> = async (message, phrase) => { +export const globalUser: BotArgumentTypeCaster> = async (message, phrase) => { return message.client.users.resolve(phrase) ?? (await message.client.users.fetch(`${phrase}`).catch(() => null)); }; diff --git a/lib/arguments/messageLink.ts b/lib/arguments/messageLink.ts index c95e42d..ffb48a0 100644 --- a/lib/arguments/messageLink.ts +++ b/lib/arguments/messageLink.ts @@ -1,7 +1,7 @@ -import { BushArgumentTypeCaster, regex } from '#lib'; +import { BotArgumentTypeCaster, regex } from '#lib'; import type { Message } from 'discord.js'; -export const messageLink: BushArgumentTypeCaster> = async (message, phrase) => { +export const messageLink: BotArgumentTypeCaster> = async (message, phrase) => { const match = new RegExp(regex.messageLink).exec(phrase); if (!match || !match.groups) return null; diff --git a/lib/arguments/permission.ts b/lib/arguments/permission.ts index 98bfe74..4d09e9c 100644 --- a/lib/arguments/permission.ts +++ b/lib/arguments/permission.ts @@ -1,7 +1,7 @@ -import type { BushArgumentTypeCaster } from '#lib'; +import type { BotArgumentTypeCaster } from '#lib'; import { PermissionFlagsBits, type PermissionsString } from 'discord.js'; -export const permission: BushArgumentTypeCaster = (_, phrase) => { +export const permission: BotArgumentTypeCaster = (_, phrase) => { if (!phrase) return null; phrase = phrase.toUpperCase().replace(/ /g, '_'); if (!(phrase in PermissionFlagsBits)) { diff --git a/lib/arguments/roleWithDuration.ts b/lib/arguments/roleWithDuration.ts index b97f205..9391c75 100644 --- a/lib/arguments/roleWithDuration.ts +++ b/lib/arguments/roleWithDuration.ts @@ -1,7 +1,7 @@ -import { Arg, BushArgumentTypeCaster, parseDuration } from '#lib'; +import { Arg, BotArgumentTypeCaster, parseDuration } from '#lib'; import type { Role } from 'discord.js'; -export const roleWithDuration: BushArgumentTypeCaster> = async (message, phrase) => { +export const roleWithDuration: BotArgumentTypeCaster> = async (message, phrase) => { // eslint-disable-next-line prefer-const let { duration, content } = parseDuration(phrase); if (content === null || content === undefined) return null; diff --git a/lib/arguments/snowflake.ts b/lib/arguments/snowflake.ts index b98a20f..ab0c7fc 100644 --- a/lib/arguments/snowflake.ts +++ b/lib/arguments/snowflake.ts @@ -1,7 +1,7 @@ -import { BushArgumentTypeCaster, regex } from '#lib'; +import { BotArgumentTypeCaster, regex } from '#lib'; import type { Snowflake } from 'discord.js'; -export const snowflake: BushArgumentTypeCaster = (_, phrase) => { +export const snowflake: BotArgumentTypeCaster = (_, phrase) => { if (!phrase) return null; if (regex.snowflake.test(phrase)) return phrase; return null; diff --git a/lib/arguments/tinyColor.ts b/lib/arguments/tinyColor.ts index 148c078..2eb6ab2 100644 --- a/lib/arguments/tinyColor.ts +++ b/lib/arguments/tinyColor.ts @@ -1,9 +1,9 @@ -import type { BushArgumentTypeCaster } from '#lib'; +import type { BotArgumentTypeCaster } from '#lib'; import assert from 'assert/strict'; import tinycolorModule from 'tinycolor2'; assert(tinycolorModule); -export const tinyColor: BushArgumentTypeCaster = (_message, phrase) => { +export const tinyColor: BotArgumentTypeCaster = (_message, phrase) => { // if the phase is a number it converts it to hex incase it could be representing a color in decimal const newPhase = isNaN(phrase as any) ? phrase : `#${Number(phrase).toString(16)}`; return tinycolorModule(newPhase).isValid() ? newPhase : null; diff --git a/lib/automod/AutomodShared.ts b/lib/automod/AutomodShared.ts index 9cdb020..29b0536 100644 --- a/lib/automod/AutomodShared.ts +++ b/lib/automod/AutomodShared.ts @@ -1,6 +1,6 @@ import * as Moderation from '#lib/common/Moderation.js'; import { unmuteResponse } from '#lib/extensions/discord.js/ExtendedGuildMember.js'; -import { colors, emojis } from '#lib/utils/BushConstants.js'; +import { colors, emojis } from '#lib/utils/Constants.js'; import * as Format from '#lib/utils/Format.js'; import { formatUnmuteResponse } from '#lib/utils/FormatResponse.js'; import { @@ -165,7 +165,7 @@ export async function handleAutomodInteraction(interaction: ButtonInteraction) { const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true; if (check !== true) return interaction.reply({ content: check, ephemeral: true }); - const result = await interaction.guild?.bushBan({ + const result = await interaction.guild?.customBan({ user: userId, reason, moderator: interaction.user.id, @@ -209,7 +209,7 @@ export async function handleAutomodInteraction(interaction: ButtonInteraction) { const check2 = await Moderation.checkMutePermissions(interaction.guild); if (check2 !== true) return interaction.reply({ content: formatUnmuteResponse('/', victim!, check2), ephemeral: true }); - const result = await victim.bushUnmute({ + const result = await victim.customUnmute({ reason, moderator: interaction.member as GuildMember, evidence: (interaction.message as Message).url ?? undefined diff --git a/lib/automod/MessageAutomod.ts b/lib/automod/MessageAutomod.ts index 0abd34c..0b6ebba 100644 --- a/lib/automod/MessageAutomod.ts +++ b/lib/automod/MessageAutomod.ts @@ -2,8 +2,8 @@ import { stripIndent } from '#tags'; import assert from 'assert/strict'; import chalk from 'chalk'; import { EmbedBuilder, GuildTextBasedChannel, PermissionFlagsBits, type Message } from 'discord.js'; -import { colors } from '../utils/BushConstants.js'; -import { format, formatError } from '../utils/BushUtils.js'; +import { colors } from '../utils/Constants.js'; +import { format, formatError } from '../utils/Utils.js'; import { Automod, BadWordDetails, Severity } from './AutomodShared.js'; /** @@ -202,7 +202,7 @@ export class MessageAutomod extends Automod { } case Severity.WARN: { void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.member.bushWarn({ + void this.member.customWarn({ moderator: this.guild!.members.me!, reason: `[Automod] ${highestOffense.reason}` }); @@ -211,7 +211,7 @@ export class MessageAutomod extends Automod { } case Severity.TEMP_MUTE: { void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.member.bushMute({ + void this.member.customMute({ moderator: this.guild!.members.me!, reason: `[Automod] ${highestOffense.reason}`, duration: 900_000 // 15 minutes @@ -221,7 +221,7 @@ export class MessageAutomod extends Automod { } case Severity.PERM_MUTE: { void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.member.bushMute({ + void this.member.customMute({ moderator: this.guild!.members.me!, reason: `[Automod] ${highestOffense.reason}`, duration: 0 // permanent diff --git a/lib/common/BotCache.ts b/lib/common/BotCache.ts new file mode 100644 index 0000000..e91d9e5 --- /dev/null +++ b/lib/common/BotCache.ts @@ -0,0 +1,26 @@ +import { BadWords, GlobalModel, SharedModel, type Guild } from '#lib'; +import { Collection, type Snowflake } from 'discord.js'; + +export class BotCache { + public global = new GlobalCache(); + public shared = new SharedCache(); + public guilds = new GuildCache(); +} + +export class GlobalCache implements Omit { + public disabledCommands: string[] = []; + public blacklistedChannels: Snowflake[] = []; + public blacklistedGuilds: Snowflake[] = []; + public blacklistedUsers: Snowflake[] = []; +} + +export class SharedCache implements Omit { + public superUsers: Snowflake[] = []; + public privilegedUsers: Snowflake[] = []; + public badLinksSecret: string[] = []; + public badLinks: string[] = []; + public badWords: BadWords = {}; + public autoBanCode: string | null = null; +} + +export class GuildCache extends Collection {} diff --git a/lib/common/BushCache.ts b/lib/common/BushCache.ts deleted file mode 100644 index 22a13ef..0000000 --- a/lib/common/BushCache.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { BadWords, GlobalModel, SharedModel, type Guild } from '#lib'; -import { Collection, type Snowflake } from 'discord.js'; - -export class BushCache { - public global = new GlobalCache(); - public shared = new SharedCache(); - public guilds = new GuildCache(); -} - -export class GlobalCache implements Omit { - public disabledCommands: string[] = []; - public blacklistedChannels: Snowflake[] = []; - public blacklistedGuilds: Snowflake[] = []; - public blacklistedUsers: Snowflake[] = []; -} - -export class SharedCache implements Omit { - public superUsers: Snowflake[] = []; - public privilegedUsers: Snowflake[] = []; - public badLinksSecret: string[] = []; - public badLinks: string[] = []; - public badWords: BadWords = {}; - public autoBanCode: string | null = null; -} - -export class GuildCache extends Collection {} diff --git a/lib/common/HighlightManager.ts b/lib/common/HighlightManager.ts index cc31413..ca71a83 100644 --- a/lib/common/HighlightManager.ts +++ b/lib/common/HighlightManager.ts @@ -10,7 +10,7 @@ import { type Snowflake, type TextBasedChannel } from 'discord.js'; -import { colors, Time } from '../utils/BushConstants.js'; +import { colors, Time } from '../utils/Constants.js'; import { sanitizeInputForDiscord } from '../utils/Format.js'; const NOTIFY_COOLDOWN = 5 * Time.Minute; diff --git a/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts b/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts new file mode 100644 index 0000000..5f4f32f --- /dev/null +++ b/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts @@ -0,0 +1,3 @@ +import { type CommandMessage } from '#lib'; + +export type BotArgumentTypeCaster = (message: CommandMessage, phrase: string) => R; diff --git a/lib/extensions/discord-akairo/BotCommand.ts b/lib/extensions/discord-akairo/BotCommand.ts new file mode 100644 index 0000000..abd945e --- /dev/null +++ b/lib/extensions/discord-akairo/BotCommand.ts @@ -0,0 +1,597 @@ +import { type DiscordEmojiInfo, type RoleWithDuration } from '#args'; +import { + type BotArgumentTypeCaster, + type BotCommandHandler, + type BotInhibitor, + type BotListener, + type BotTask, + type ParsedDuration, + type TanzaniteClient +} from '#lib'; +import { + ArgumentMatch, + Command, + CommandUtil, + type AkairoApplicationCommandAutocompleteOption, + type AkairoApplicationCommandChannelOptionData, + type AkairoApplicationCommandChoicesData, + type AkairoApplicationCommandNonOptionsData, + type AkairoApplicationCommandNumericOptionData, + type AkairoApplicationCommandOptionData, + type AkairoApplicationCommandSubCommandData, + type AkairoApplicationCommandSubGroupData, + type ArgumentOptions, + type ArgumentType, + type ArgumentTypeCaster, + type BaseArgumentType, + type CommandOptions, + type ContextMenuCommand, + type MissingPermissionSupplier, + type SlashOption, + type SlashResolveType +} from 'discord-akairo'; +import { + Message, + PermissionsBitField, + User, + type ApplicationCommandOptionChoiceData, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type ApplicationCommandOptionType, + type PermissionResolvable, + type PermissionsString, + type Snowflake +} from 'discord.js'; +import _ from 'lodash'; +import { SlashMessage } from './SlashMessage.js'; + +export interface OverriddenBaseArgumentType extends BaseArgumentType { + commandAlias: BotCommand | null; + command: BotCommand | null; + inhibitor: BotInhibitor | null; + listener: BotListener | null; + task: BotTask | null; + contextMenuCommand: ContextMenuCommand | null; +} + +export interface BaseBotArgumentType extends OverriddenBaseArgumentType { + duration: number | null; + contentWithDuration: ParsedDuration; + permission: PermissionsString | null; + snowflake: Snowflake | null; + discordEmoji: DiscordEmojiInfo | null; + roleWithDuration: RoleWithDuration | null; + abbreviatedNumber: number | null; + globalUser: User | null; + messageLink: Message | null; + durationSeconds: number | null; + tinyColor: string | null; +} + +export type BotArgumentType = keyof BaseBotArgumentType | RegExp; + +interface BaseBotArgumentOptions extends Omit, ExtraArgumentOptions { + id: string; + description: string; + + /** + * The message sent for the prompt and the slash command description. + */ + prompt?: string; + + /** + * The message set for the retry prompt. + */ + retry?: string; + + /** + * Whether or not the argument is optional. + */ + optional?: boolean; + + /** + * The type used for slash commands. Set to false to disable this argument for slash commands. + */ + slashType: AkairoApplicationCommandOptionData['type'] | false; + + /** + * Allows you to get a discord resolved object + * + * ex. get the resolved member object when the type is {@link ApplicationCommandOptionType.User User} + */ + slashResolve?: SlashResolveType; + + /** + * The choices of the option for the user to pick from + */ + choices?: ApplicationCommandOptionChoiceData[]; + + /** + * Whether the option is an autocomplete option + */ + autocomplete?: boolean; + + /** + * When the option type is channel, the allowed types of channels that can be selected + */ + channelTypes?: AkairoApplicationCommandChannelOptionData['channelTypes']; + + /** + * The minimum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option + */ + minValue?: number; + + /** + * The maximum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option + */ + maxValue?: number; +} + +interface ExtraArgumentOptions { + /** + * Restrict this argument to only slash or only text commands. + */ + only?: 'slash' | 'text'; + + /** + * Readable type for the help command. + */ + readableType?: string; + + /** + * Whether the argument is only accessible to the owners. + * @default false + */ + ownerOnly?: boolean; + + /** + * Whether the argument is only accessible to the super users. + * @default false + */ + superUserOnly?: boolean; +} + +export interface BotArgumentOptions extends BaseBotArgumentOptions { + /** + * The type that the argument should be cast to. + * - `string` does not cast to any type. + * - `lowercase` makes the input lowercase. + * - `uppercase` makes the input uppercase. + * - `charCodes` transforms the input to an array of char codes. + * - `number` casts to a number. + * - `integer` casts to an integer. + * - `bigint` casts to a big integer. + * - `url` casts to an `URL` object. + * - `date` casts to a `Date` object. + * - `color` casts a hex code to an integer. + * - `commandAlias` tries to resolve to a command from an alias. + * - `command` matches the ID of a command. + * - `inhibitor` matches the ID of an inhibitor. + * - `listener` matches the ID of a listener. + * + * Possible Discord-related types. + * These types can be plural (add an 's' to the end) and a collection of matching objects will be used. + * - `user` tries to resolve to a user. + * - `member` tries to resolve to a member. + * - `relevant` tries to resolve to a relevant user, works in both guilds and DMs. + * - `channel` tries to resolve to a channel. + * - `textChannel` tries to resolve to a text channel. + * - `voiceChannel` tries to resolve to a voice channel. + * - `stageChannel` tries to resolve to a stage channel. + * - `threadChannel` tries to resolve a thread channel. + * - `role` tries to resolve to a role. + * - `emoji` tries to resolve to a custom emoji. + * - `guild` tries to resolve to a guild. + * - `permission` tries to resolve to a permissions. + * + * Other Discord-related types: + * - `message` tries to fetch a message from an ID within the channel. + * - `guildMessage` tries to fetch a message from an ID within the guild. + * - `relevantMessage` is a combination of the above, works in both guilds and DMs. + * - `invite` tries to fetch an invite object from a link. + * - `userMention` matches a mention of a user. + * - `memberMention` matches a mention of a guild member. + * - `channelMention` matches a mention of a channel. + * - `roleMention` matches a mention of a role. + * - `emojiMention` matches a mention of an emoji. + * + * Misc: + * - `duration` tries to parse duration in milliseconds + * - `contentWithDuration` tries to parse duration in milliseconds and returns the remaining content with the duration + * removed + */ + type?: BotArgumentType | (keyof BaseBotArgumentType)[] | BotArgumentTypeCaster; +} + +export interface CustomBotArgumentOptions extends BaseBotArgumentOptions { + /** + * An array of strings can be used to restrict input to only those strings, case insensitive. + * The array can also contain an inner array of strings, for aliases. + * If so, the first entry of the array will be used as the final argument. + * + * A regular expression can also be used. + * The evaluated argument will be an object containing the `match` and `matches` if global. + */ + customType?: (string | string[])[] | RegExp | string | null; +} + +export type CustomMissingPermissionSupplier = (message: CommandMessage | SlashMessage) => Promise | any; + +interface ExtendedCommandOptions { + /** + * Whether the command is hidden from the help command. + */ + hidden?: boolean; + + /** + * The channels the command is limited to run in. + */ + restrictedChannels?: Snowflake[]; + + /** + * The guilds the command is limited to run in. + */ + restrictedGuilds?: Snowflake[]; + + /** + * Show how to use the command. + */ + usage: string[]; + + /** + * Examples for how to use the command. + */ + examples: string[]; + + /** + * A fake command, completely hidden from the help command. + */ + pseudo?: boolean; + + /** + * Allow this command to be run in channels that are blacklisted. + */ + bypassChannelBlacklist?: boolean; + + /** + * Use instead of {@link BaseBotCommandOptions.args} when using argument generators or custom slashOptions + */ + helpArgs?: ArgsInfo[]; + + /** + * Extra information about the command, displayed in the help command. + */ + note?: string; +} + +export interface BaseBotCommandOptions + extends Omit, + ExtendedCommandOptions { + /** + * The description of the command. + */ + description: string; + + /** + * The arguments for the command. + */ + args?: BotArgumentOptions[] & CustomBotArgumentOptions[]; + + category: string; + + /** + * Permissions required by the client to run this command. + */ + clientPermissions: bigint | bigint[] | CustomMissingPermissionSupplier; + + /** + * Permissions required by the user to run this command. + */ + userPermissions: bigint | bigint[] | CustomMissingPermissionSupplier; + + /** + * Whether the argument is only accessible to the owners. + */ + ownerOnly?: boolean; + + /** + * Whether the argument is only accessible to the super users. + */ + superUserOnly?: boolean; +} + +export type CustomCommandOptions = Omit | Omit; + +export interface ArgsInfo { + /** + * The name of the argument. + */ + name: string; + + /** + * The description of the argument. + */ + description: string; + + /** + * Whether the argument is optional. + * @default false + */ + optional?: boolean; + + /** + * Whether or not the argument has autocomplete enabled. + * @default false + */ + autocomplete?: boolean; + + /** + * Whether the argument is restricted a certain command. + * @default 'slash & text' + */ + only?: 'slash & text' | 'slash' | 'text'; + + /** + * The method that arguments are matched for text commands. + * @default 'phrase' + */ + match?: ArgumentMatch; + + /** + * The readable type of the argument. + */ + type: string; + + /** + * If {@link match} is 'flag' or 'option', these are the flags that are matched + * @default [] + */ + flag?: string[]; + + /** + * Whether the argument is only accessible to the owners. + * @default false + */ + ownerOnly?: boolean; + + /** + * Whether the argument is only accessible to the super users. + * @default false + */ + superUserOnly?: boolean; +} + +export abstract class BotCommand extends Command { + public declare client: TanzaniteClient; + public declare handler: BotCommandHandler; + public declare description: string; + + /** + * Show how to use the command. + */ + public usage: string[]; + + /** + * Examples for how to use the command. + */ + public examples: string[]; + + /** + * The options sent to the constructor + */ + public options: CustomCommandOptions; + + /** + * The options sent to the super call + */ + public parsedOptions: CommandOptions; + + /** + * The channels the command is limited to run in. + */ + public restrictedChannels: Snowflake[] | undefined; + + /** + * The guilds the command is limited to run in. + */ + public restrictedGuilds: Snowflake[] | undefined; + + /** + * Whether the command is hidden from the help command. + */ + public hidden: boolean; + + /** + * A fake command, completely hidden from the help command. + */ + public pseudo: boolean; + + /** + * Allow this command to be run in channels that are blacklisted. + */ + public bypassChannelBlacklist: boolean; + + /** + * Info about the arguments for the help command. + */ + public argsInfo?: ArgsInfo[]; + + /** + * Extra information about the command, displayed in the help command. + */ + public note?: string; + + public constructor(id: string, options: CustomCommandOptions) { + const options_ = options as BaseBotCommandOptions; + + if (options_.args && typeof options_.args !== 'function') { + options_.args.forEach((_, index: number) => { + if ('customType' in (options_.args?.[index] ?? {})) { + if (!options_.args![index]['type']) options_.args![index]['type'] = options_.args![index]['customType']! as any; + delete options_.args![index]['customType']; + } + }); + } + + const newOptions: Partial = {}; + for (const _key in options_) { + const key = _key as keyof typeof options_; // you got to love typescript + if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') { + const newTextArgs: (ArgumentOptions & ExtraArgumentOptions)[] = []; + const newSlashArgs: SlashOption[] = []; + for (const arg of options_.args) { + if (arg.only !== 'slash' && !options_.slashOnly) { + const newArg: ArgumentOptions & ExtraArgumentOptions = {}; + if ('default' in arg) newArg.default = arg.default; + if ('description' in arg) newArg.description = arg.description; + if ('flag' in arg) newArg.flag = arg.flag; + if ('id' in arg) newArg.id = arg.id; + if ('index' in arg) newArg.index = arg.index; + if ('limit' in arg) newArg.limit = arg.limit; + if ('match' in arg) newArg.match = arg.match; + if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise; + if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags; + if ('otherwise' in arg) newArg.otherwise = arg.otherwise; + if ('prompt' in arg || 'retry' in arg || 'optional' in arg) { + newArg.prompt = {}; + if ('prompt' in arg) newArg.prompt.start = arg.prompt; + if ('retry' in arg) newArg.prompt.retry = arg.retry; + if ('optional' in arg) newArg.prompt.optional = arg.optional; + } + if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster; + if ('unordered' in arg) newArg.unordered = arg.unordered; + if ('ownerOnly' in arg) newArg.ownerOnly = arg.ownerOnly; + if ('superUserOnly' in arg) newArg.superUserOnly = arg.superUserOnly; + newTextArgs.push(newArg); + } + if ( + arg.only !== 'text' && + !('slashOptions' in options_) && + (options_.slash || options_.slashOnly) && + arg.slashType !== false + ) { + const newArg: { + [key in SlashOptionKeys]?: any; + } = { + name: arg.id, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + description: arg.prompt || arg.description || 'No description provided.', + type: arg.slashType + }; + if ('slashResolve' in arg) newArg.resolve = arg.slashResolve; + if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete; + if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes; + if ('choices' in arg) newArg.choices = arg.choices; + if ('minValue' in arg) newArg.minValue = arg.minValue; + if ('maxValue' in arg) newArg.maxValue = arg.maxValue; + newArg.required = 'optional' in arg ? !arg.optional : true; + newSlashArgs.push(newArg as SlashOption); + } + } + if (newTextArgs.length > 0) newOptions.args = newTextArgs; + if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; + } else if (key === 'clientPermissions' || key === 'userPermissions') { + newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; + } else { + newOptions[key] = options_[key]; + } + } + + if ( + 'userPermissions' in newOptions && + !('slashDefaultMemberPermissions' in newOptions) && + typeof newOptions.userPermissions !== 'function' + ) { + const perms = new PermissionsBitField(newOptions.userPermissions); + + newOptions.slashDefaultMemberPermissions = perms.toArray().length === 0 ? null : perms; + } + + super(id, newOptions); + + if (options_.args ?? options_.helpArgs) { + const argsInfo: ArgsInfo[] = []; + const combined = (options_.args ?? options_.helpArgs)!.map((arg) => { + const norm = options_.args + ? options_.args.find((_arg) => _arg.id === ('id' in arg ? arg.id : arg.name)) ?? ({} as BotArgumentOptions) + : ({} as BotArgumentOptions); + const help = options_.helpArgs + ? options_.helpArgs.find((_arg) => _arg.name === ('id' in arg ? arg.id : arg.name)) ?? ({} as ArgsInfo) + : ({} as ArgsInfo); + return { ...norm, ...help }; + }); + + for (const arg of combined) { + const name = _.camelCase('id' in arg ? arg.id : arg.name), + description = arg.description || '*No description provided.*', + optional = arg.optional ?? false, + autocomplete = arg.autocomplete ?? false, + only = arg.only ?? 'slash & text', + match = arg.match ?? 'phrase', + type = match === 'flag' ? 'flag' : arg.readableType ?? arg.type ?? 'string', + flag = arg.flag ? (Array.isArray(arg.flag) ? arg.flag : [arg.flag]) : [], + ownerOnly = arg.ownerOnly ?? false, + superUserOnly = arg.superUserOnly ?? false; + + argsInfo.push({ name, description, optional, autocomplete, only, match, type, flag, ownerOnly, superUserOnly }); + } + + this.argsInfo = argsInfo; + } + + this.description = options_.description; + this.usage = options_.usage; + this.examples = options_.examples; + this.options = options_; + this.parsedOptions = newOptions; + this.hidden = !!options_.hidden; + this.restrictedChannels = options_.restrictedChannels; + this.restrictedGuilds = options_.restrictedGuilds; + this.pseudo = !!options_.pseudo; + this.bypassChannelBlacklist = !!options_.bypassChannelBlacklist; + this.note = options_.note; + } + + /** + * Executes the command. + * @param message - Message that triggered the command. + * @param args - Evaluated arguments. + */ + public abstract override exec(message: CommandMessage, args: any): any; + /** + * Executes the command. + * @param message - Message that triggered the command. + * @param args - Evaluated arguments. + */ + public abstract override exec(message: CommandMessage | SlashMessage, args: any): any; +} + +type SlashOptionKeys = + | keyof AkairoApplicationCommandSubGroupData + | keyof AkairoApplicationCommandNonOptionsData + | keyof AkairoApplicationCommandChannelOptionData + | keyof AkairoApplicationCommandChoicesData + | keyof AkairoApplicationCommandAutocompleteOption + | keyof AkairoApplicationCommandNumericOptionData + | keyof AkairoApplicationCommandSubCommandData; + +interface PseudoArguments extends BaseBotArgumentType { + boolean: boolean; + flag: boolean; + regex: { match: RegExpMatchArray; matches: RegExpExecArray[] }; +} + +export type ArgType = NonNullable; +export type OptArgType = PseudoArguments[T]; + +/** + * `util` is always defined for messages after `'all'` inhibitors + */ +export type CommandMessage = Message & { + /** + * Extra properties applied to the Discord.js message object. + * Utilities for command responding. + * Available on all messages after 'all' inhibitors and built-in inhibitors (bot, client). + * Not all properties of the util are available, depending on the input. + * */ + util: CommandUtil; +}; diff --git a/lib/extensions/discord-akairo/BotCommandHandler.ts b/lib/extensions/discord-akairo/BotCommandHandler.ts new file mode 100644 index 0000000..8a4fe60 --- /dev/null +++ b/lib/extensions/discord-akairo/BotCommandHandler.ts @@ -0,0 +1,37 @@ +import { type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { CommandHandler, type Category, type CommandHandlerEvents, type CommandHandlerOptions } from 'discord-akairo'; +import { type Collection, type Message, type PermissionsString } from 'discord.js'; + +export type CustomCommandHandlerOptions = CommandHandlerOptions; + +export interface BotCommandHandlerEvents extends CommandHandlerEvents { + commandBlocked: [message: CommandMessage, command: BotCommand, reason: string]; + commandBreakout: [message: CommandMessage, command: BotCommand, /* no util */ breakMessage: Message]; + commandCancelled: [message: CommandMessage, command: BotCommand, /* no util */ retryMessage?: Message]; + commandFinished: [message: CommandMessage, command: BotCommand, args: any, returnValue: any]; + commandInvalid: [message: CommandMessage, command: BotCommand]; + commandLocked: [message: CommandMessage, command: BotCommand]; + commandStarted: [message: CommandMessage, command: BotCommand, args: any]; + cooldown: [message: CommandMessage | SlashMessage, command: BotCommand, remaining: number]; + error: [error: Error, message: /* no util */ Message, command?: BotCommand]; + inPrompt: [message: /* no util */ Message]; + load: [command: BotCommand, isReload: boolean]; + messageBlocked: [message: /* no util */ Message | CommandMessage | SlashMessage, reason: string]; + messageInvalid: [message: CommandMessage]; + missingPermissions: [message: CommandMessage, command: BotCommand, type: 'client' | 'user', missing: PermissionsString[]]; + remove: [command: BotCommand]; + slashBlocked: [message: SlashMessage, command: BotCommand, reason: string]; + slashError: [error: Error, message: SlashMessage, command: BotCommand]; + slashFinished: [message: SlashMessage, command: BotCommand, args