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: any, returnValue: any]; + slashMissingPermissions: [message: SlashMessage, command: BotCommand, type: 'client' | 'user', missing: PermissionsString[]]; + slashStarted: [message: SlashMessage, command: BotCommand, args: any]; +} + +export class BotCommandHandler extends CommandHandler { + public declare modules: Collection; + public declare categories: Collection>; +} + +export interface BotCommandHandler extends CommandHandler { + findCommand(name: string): BotCommand; +} diff --git a/lib/extensions/discord-akairo/BotInhibitor.ts b/lib/extensions/discord-akairo/BotInhibitor.ts new file mode 100644 index 0000000..d134eab --- /dev/null +++ b/lib/extensions/discord-akairo/BotInhibitor.ts @@ -0,0 +1,18 @@ +import { type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { Inhibitor } from 'discord-akairo'; +import { Message } from 'discord.js'; + +export abstract class BotInhibitor extends Inhibitor { + /** + * Checks if message should be blocked. + * A return value of true will block the message. + * If returning a Promise, a resolved value of true will block the message. + * + * **Note:** `'all'` type inhibitors do not have {@link Message.util} defined. + * + * @param message - Message being handled. + * @param command - Command to check. + */ + public abstract override exec(message: CommandMessage, command: BotCommand): any; + public abstract override exec(message: CommandMessage | SlashMessage, command: BotCommand): any; +} diff --git a/lib/extensions/discord-akairo/BotInhibitorHandler.ts b/lib/extensions/discord-akairo/BotInhibitorHandler.ts new file mode 100644 index 0000000..c6f318d --- /dev/null +++ b/lib/extensions/discord-akairo/BotInhibitorHandler.ts @@ -0,0 +1,3 @@ +import { InhibitorHandler } from 'discord-akairo'; + +export class BotInhibitorHandler extends InhibitorHandler {} diff --git a/lib/extensions/discord-akairo/BotListener.ts b/lib/extensions/discord-akairo/BotListener.ts new file mode 100644 index 0000000..f4bfd6c --- /dev/null +++ b/lib/extensions/discord-akairo/BotListener.ts @@ -0,0 +1,3 @@ +import { Listener } from 'discord-akairo'; + +export abstract class BotListener extends Listener {} diff --git a/lib/extensions/discord-akairo/BotListenerHandler.ts b/lib/extensions/discord-akairo/BotListenerHandler.ts new file mode 100644 index 0000000..9b3b525 --- /dev/null +++ b/lib/extensions/discord-akairo/BotListenerHandler.ts @@ -0,0 +1,3 @@ +import { ListenerHandler } from 'discord-akairo'; + +export class BotListenerHandler extends ListenerHandler {} diff --git a/lib/extensions/discord-akairo/BotTask.ts b/lib/extensions/discord-akairo/BotTask.ts new file mode 100644 index 0000000..09b30ed --- /dev/null +++ b/lib/extensions/discord-akairo/BotTask.ts @@ -0,0 +1,3 @@ +import { Task } from 'discord-akairo'; + +export abstract class BotTask extends Task {} diff --git a/lib/extensions/discord-akairo/BotTaskHandler.ts b/lib/extensions/discord-akairo/BotTaskHandler.ts new file mode 100644 index 0000000..b522f2c --- /dev/null +++ b/lib/extensions/discord-akairo/BotTaskHandler.ts @@ -0,0 +1,3 @@ +import { TaskHandler } from 'discord-akairo'; + +export class BotTaskHandler extends TaskHandler {} diff --git a/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts b/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts deleted file mode 100644 index def7ad6..0000000 --- a/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { type CommandMessage } from '#lib'; - -export type BushArgumentTypeCaster = (message: CommandMessage, phrase: string) => R; diff --git a/lib/extensions/discord-akairo/BushClient.ts b/lib/extensions/discord-akairo/BushClient.ts deleted file mode 100644 index b311ffd..0000000 --- a/lib/extensions/discord-akairo/BushClient.ts +++ /dev/null @@ -1,601 +0,0 @@ -import { - abbreviatedNumber, - contentWithDuration, - discordEmoji, - duration, - durationSeconds, - globalUser, - messageLink, - permission, - roleWithDuration, - snowflake -} from '#args'; -import type { Config } from '#config'; -import { BushClientEvents, emojis, formatError, inspect, updateEveryCache } from '#lib'; -import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; -import * as Sentry from '@sentry/node'; -import { - AkairoClient, - ArgumentTypeCaster, - ContextMenuCommandHandler, - version as akairoVersion, - type ArgumentPromptData, - type OtherwiseContentSupplier -} from 'discord-akairo'; -import { - ActivityType, - GatewayIntentBits, - MessagePayload, - Options, - Partials, - Structures, - version as discordJsVersion, - type Awaitable, - type If, - type InteractionReplyOptions, - type Message, - type MessageEditOptions, - type MessageOptions, - type ReplyMessageOptions, - type Snowflake, - type UserResolvable, - type WebhookEditMessageOptions -} from 'discord.js'; -import type EventEmitter from 'events'; -import { google } from 'googleapis'; -import path from 'path'; -import readline from 'readline'; -import { Options as SequelizeOptions, Sequelize, Sequelize as SequelizeType } from 'sequelize'; -import { fileURLToPath } from 'url'; -import { tinyColor } from '../../arguments/tinyColor.js'; -import { BushCache } from '../../common/BushCache.js'; -import { HighlightManager } from '../../common/HighlightManager.js'; -import { - ActivePunishment, - Global, - Guild as GuildModel, - GuildCount, - Highlight, - Level, - MemberCount, - ModLog, - Reminder, - Shared, - Stat, - StickyRole -} from '../../models/index.js'; -import { AllowedMentions } from '../../utils/AllowedMentions.js'; -import { BushClientUtils } from '../../utils/BushClientUtils.js'; -import { BushLogger } from '../../utils/BushLogger.js'; -import { ExtendedGuild } from '../discord.js/ExtendedGuild.js'; -import { ExtendedGuildMember } from '../discord.js/ExtendedGuildMember.js'; -import { ExtendedMessage } from '../discord.js/ExtendedMessage.js'; -import { ExtendedUser } from '../discord.js/ExtendedUser.js'; -import { BushCommandHandler } from './BushCommandHandler.js'; -import { BushInhibitorHandler } from './BushInhibitorHandler.js'; -import { BushListenerHandler } from './BushListenerHandler.js'; -import { BushTaskHandler } from './BushTaskHandler.js'; - -declare module 'discord.js' { - export interface Client extends EventEmitter { - /** The ID of the owner(s). */ - ownerID: Snowflake | Snowflake[]; - /** The ID of the superUser(s). */ - superUserID: Snowflake | Snowflake[]; - /** Whether or not the client is ready. */ - customReady: boolean; - /** The configuration for the client. */ - readonly config: Config; - /** Stats for the client. */ - readonly stats: BushStats; - /** The handler for the bot's listeners. */ - readonly listenerHandler: BushListenerHandler; - /** The handler for the bot's command inhibitors. */ - readonly inhibitorHandler: BushInhibitorHandler; - /** The handler for the bot's commands. */ - readonly commandHandler: BushCommandHandler; - /** The handler for the bot's tasks. */ - readonly taskHandler: BushTaskHandler; - /** The handler for the bot's context menu commands. */ - readonly contextMenuCommandHandler: ContextMenuCommandHandler; - /** The database connection for this instance of the bot (production, beta, or development). */ - readonly instanceDB: SequelizeType; - /** The database connection that is shared between all instances of the bot. */ - readonly sharedDB: SequelizeType; - /** A custom logging system for the bot. */ - readonly logger: BushLogger; - /** Cached global and guild database data. */ - readonly cache: BushCache; - /** Sentry error reporting for the bot. */ - readonly sentry: typeof Sentry; - /** Manages most aspects of the highlight command */ - readonly highlightManager: HighlightManager; - /** The perspective api */ - perspective: any; - /** Client utilities. */ - readonly utils: BushClientUtils; - /** A custom logging system for the bot. */ - get console(): BushLogger; - on(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - once(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - emit(event: K, ...args: BushClientEvents[K]): boolean; - off(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - removeAllListeners(event?: K): this; - /** - * Checks if a user is the owner of this bot. - * @param user - User to check. - */ - isOwner(user: UserResolvable): boolean; - /** - * Checks if a user is a super user of this bot. - * @param user - User to check. - */ - isSuperUser(user: UserResolvable): boolean; - } -} - -export type ReplyMessageType = string | MessagePayload | ReplyMessageOptions; -export type EditMessageType = string | MessageEditOptions | MessagePayload; -export type SlashSendMessageType = string | MessagePayload | InteractionReplyOptions; -export type SlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions; -export type SendMessageType = string | MessagePayload | MessageOptions; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -/** - * The main hub for interacting with the Discord API. - */ -export class BushClient extends AkairoClient { - public declare ownerID: Snowflake[]; - public declare superUserID: Snowflake[]; - - /** - * Whether or not the client is ready. - */ - public override customReady = false; - - /** - * Stats for the client. - */ - public override readonly stats: BushStats = { cpu: undefined, commandsUsed: 0n, slashCommandsUsed: 0n }; - - /** - * The handler for the bot's listeners. - */ - public override readonly listenerHandler: BushListenerHandler; - - /** - * The handler for the bot's command inhibitors. - */ - public override readonly inhibitorHandler: BushInhibitorHandler; - - /** - * The handler for the bot's commands. - */ - public override readonly commandHandler: BushCommandHandler; - - /** - * The handler for the bot's tasks. - */ - public override readonly taskHandler: BushTaskHandler; - - /** - * The handler for the bot's context menu commands. - */ - public override readonly contextMenuCommandHandler: ContextMenuCommandHandler; - - /** - * The database connection for this instance of the bot (production, beta, or development). - */ - public override readonly instanceDB: SequelizeType; - - /** - * The database connection that is shared between all instances of the bot. - */ - public override readonly sharedDB: SequelizeType; - - /** - * A custom logging system for the bot. - */ - public override readonly logger: BushLogger = new BushLogger(this); - - /** - * Cached global and guild database data. - */ - public override readonly cache = new BushCache(); - - /** - * Sentry error reporting for the bot. - */ - public override readonly sentry!: typeof Sentry; - - /** - * Manages most aspects of the highlight command - */ - public override readonly highlightManager: HighlightManager = new HighlightManager(this); - - /** - * The perspective api - */ - public override perspective: any; - - /** - * Client utilities. - */ - public override readonly utils: BushClientUtils = new BushClientUtils(this); - - /** - * @param config The configuration for the client. - */ - public constructor( - /** - * The configuration for the client. - */ - public override readonly config: Config - ) { - super({ - ownerID: config.owners, - intents: Object.keys(GatewayIntentBits) - .map((i) => (typeof i === 'string' ? GatewayIntentBits[i as keyof typeof GatewayIntentBits] : i)) - .reduce((acc, p) => acc | p, 0), - partials: Object.keys(Partials).map((p) => Partials[p as keyof typeof Partials]), - presence: { - activities: [{ name: 'Beep Boop', type: ActivityType.Watching }], - status: 'online' - }, - allowedMentions: AllowedMentions.none(), // no mentions by default - makeCache: Options.cacheWithLimits({ - PresenceManager: { - maxSize: 0, - keepOverLimit: (_, key) => { - if (config.owners.includes(key)) return true; - - return HighlightManager.keep.has(key); - } - } - }), - failIfNotExists: false, - rest: { api: 'https://canary.discord.com/api' } - }); - patch(this); - - this.token = config.token as If; - - /* =-=-= handlers =-=-= */ - this.listenerHandler = new BushListenerHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'src', 'listeners'), - extensions: ['.js'], - automateCategories: true - }); - this.inhibitorHandler = new BushInhibitorHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'src', 'inhibitors'), - extensions: ['.js'], - automateCategories: true - }); - this.taskHandler = new BushTaskHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'src', 'tasks'), - extensions: ['.js'], - automateCategories: true - }); - - const modify = async ( - message: Message, - text: string | MessagePayload | MessageOptions | OtherwiseContentSupplier, - data: ArgumentPromptData, - replaceError: boolean - ) => { - const ending = '\n\n Type **cancel** to cancel the command'; - const options = typeof text === 'function' ? await text(message, data) : text; - const search = '{error}', - replace = emojis.error; - - if (typeof options === 'string') return (replaceError ? options.replace(search, replace) : options) + ending; - - if (options instanceof MessagePayload) { - if (options.options.content) { - if (replaceError) options.options.content = options.options.content.replace(search, replace); - options.options.content += ending; - } - } else if (options.content) { - if (replaceError) options.content = options.content.replace(search, replace); - options.content += ending; - } - return options; - }; - - this.commandHandler = new BushCommandHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'src', 'commands'), - extensions: ['.js'], - prefix: async ({ guild }: Message) => { - if (this.config.isDevelopment) return 'dev '; - if (!guild) return this.config.prefix; - const prefix = await guild.getSetting('prefix'); - return (prefix ?? this.config.prefix) as string; - }, - allowMention: true, - handleEdits: true, - commandUtil: true, - commandUtilLifetime: 300_000, // 5 minutes - argumentDefaults: { - prompt: { - start: 'Placeholder argument prompt. **If you see this please tell my developers**.', - retry: 'Placeholder failed argument prompt. **If you see this please tell my developers**.', - modifyStart: (message, text, data) => modify(message, text, data, false), - modifyRetry: (message, text, data) => modify(message, text, data, true), - timeout: ':hourglass: You took too long the command has been cancelled.', - ended: 'You exceeded the maximum amount of tries the command has been cancelled', - cancel: 'The command has been cancelled', - retries: 3, - time: 3e4 - }, - otherwise: '' - }, - automateCategories: false, - autoRegisterSlashCommands: true, - skipBuiltInPostInhibitors: true, - aliasReplacement: /-/g - }); - this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'src', 'context-menu-commands'), - extensions: ['.js'], - automateCategories: true - }); - - /* =-=-= databases =-=-= */ - const sharedDBOptions: SequelizeOptions = { - username: this.config.db.username, - password: this.config.db.password, - dialect: 'postgres', - host: this.config.db.host, - port: this.config.db.port, - logging: this.config.logging.db ? (sql) => this.logger.debug(sql) : false, - timezone: 'America/New_York' - }; - this.instanceDB = new Sequelize({ - ...sharedDBOptions, - database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot' - }); - this.sharedDB = new Sequelize({ - ...sharedDBOptions, - database: 'bushbot-shared' - }); - - this.sentry = Sentry; - } - - /** - * A custom logging system for the bot. - */ - public override get console(): BushLogger { - return this.logger; - } - - /** - * Extends discord.js structures before the client is instantiated. - */ - public static extendStructures(): void { - Structures.extend('GuildMember', () => ExtendedGuildMember); - Structures.extend('Guild', () => ExtendedGuild); - Structures.extend('Message', () => ExtendedMessage); - Structures.extend('User', () => ExtendedUser); - } - - /** - * Initializes the bot. - */ - public async init() { - if (parseInt(process.versions.node.split('.')[0]) < 18) { - void (await this.console.error('version', `Please use node <>, not <<${process.version}>>.`, false)); - process.exit(2); - } - - this.setMaxListeners(20); - - this.perspective = await google.discoverAPI('https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1'); - - this.commandHandler.useInhibitorHandler(this.inhibitorHandler); - this.commandHandler.useListenerHandler(this.listenerHandler); - this.commandHandler.useTaskHandler(this.taskHandler); - this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler); - this.commandHandler.ignorePermissions = this.config.owners; - this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.shared.superUsers])]; - const emitters: Emitters = { - client: this, - commandHandler: this.commandHandler, - inhibitorHandler: this.inhibitorHandler, - listenerHandler: this.listenerHandler, - taskHandler: this.taskHandler, - contextMenuCommandHandler: this.contextMenuCommandHandler, - process, - stdin: rl, - gateway: this.ws, - rest: this.rest, - ws: this.ws - }; - this.listenerHandler.setEmitters(emitters); - this.commandHandler.resolver.addTypes({ - duration: duration, - contentWithDuration: contentWithDuration, - permission: permission, - snowflake: snowflake, - discordEmoji: discordEmoji, - roleWithDuration: roleWithDuration, - abbreviatedNumber: abbreviatedNumber, - durationSeconds: durationSeconds, - globalUser: globalUser, - messageLink: messageLink, - tinyColor: tinyColor - }); - - this.sentry.setTag('process', process.pid.toString()); - this.sentry.setTag('discord.js', discordJsVersion); - this.sentry.setTag('discord-akairo', akairoVersion); - void this.logger.success('startup', `Successfully connected to <>.`, false); - - // loads all the handlers - const handlers = { - commands: this.commandHandler, - contextMenuCommands: this.contextMenuCommandHandler, - listeners: this.listenerHandler, - inhibitors: this.inhibitorHandler, - tasks: this.taskHandler - }; - const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) => - handler - .loadAll() - .then(() => { - void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false); - }) - .catch((e) => { - void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${formatError(e)}`, false); - if (process.argv.includes('dry')) process.exit(1); - }) - ); - await Promise.allSettled(handlerPromises); - } - - /** - * Connects to the database, initializes models, and creates tables if they do not exist. - */ - public async dbPreInit() { - try { - await this.instanceDB.authenticate(); - GuildModel.initModel(this.instanceDB, this); - ModLog.initModel(this.instanceDB); - ActivePunishment.initModel(this.instanceDB); - Level.initModel(this.instanceDB); - StickyRole.initModel(this.instanceDB); - Reminder.initModel(this.instanceDB); - Highlight.initModel(this.instanceDB); - await this.instanceDB.sync({ alter: true }); // Sync all tables to fix everything if updated - await this.console.success('startup', `Successfully connected to <>.`, false); - } catch (e) { - await this.console.error( - 'startup', - `Failed to connect to <> with error:\n${inspect(e, { colors: true, depth: 1 })}`, - false - ); - process.exit(2); - } - try { - await this.sharedDB.authenticate(); - Stat.initModel(this.sharedDB); - Global.initModel(this.sharedDB); - Shared.initModel(this.sharedDB); - MemberCount.initModel(this.sharedDB); - GuildCount.initModel(this.sharedDB); - await this.sharedDB.sync({ - // Sync all tables to fix everything if updated - // if another instance restarts we don't want to overwrite new changes made in development - alter: this.config.isDevelopment - }); - await this.console.success('startup', `Successfully connected to <>.`, false); - } catch (e) { - await this.console.error( - 'startup', - `Failed to connect to <> with error:\n${inspect(e, { colors: true, depth: 1 })}`, - false - ); - process.exit(2); - } - } - - /** - * Starts the bot - */ - public async start() { - this.intercept('ready', async (arg, done) => { - const promises = this.guilds.cache - .filter((g) => g.large) - .map((guild) => { - return guild.members.fetch(); - }); - await Promise.all(promises); - this.customReady = true; - this.taskHandler.startAll(); - return done(null, `intercepted ${arg}`); - }); - - try { - await this.highlightManager.syncCache(); - await updateEveryCache(this); - void this.console.success('startup', `Successfully created <>.`, false); - - const stats = - (await Stat.findByPk(this.config.environment)) ?? (await Stat.create({ environment: this.config.environment })); - this.stats.commandsUsed = stats.commandsUsed; - this.stats.slashCommandsUsed = stats.slashCommandsUsed; - await this.login(this.token!); - } catch (e) { - await this.console.error('start', inspect(e, { colors: true, depth: 1 }), false); - process.exit(1); - } - } - - /** - * Logs out, terminates the connection to Discord, and destroys the client. - */ - public override destroy(relogin = false): void | Promise { - super.destroy(); - if (relogin) { - return this.login(this.token!); - } - } - - public override isOwner(user: UserResolvable): boolean { - return this.config.owners.includes(this.users.resolveId(user!)!); - } - - public override isSuperUser(user: UserResolvable): boolean { - const userID = this.users.resolveId(user)!; - return this.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID); - } -} - -export interface BushClient extends EventEmitter, PatchedElements, AkairoClient { - on(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - once(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - emit(event: K, ...args: BushClientEvents[K]): boolean; - off(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - removeAllListeners(event?: K): this; -} - -/** - * Various statistics - */ -export interface BushStats { - /** - * The average cpu usage of the bot from the past 60 seconds. - */ - cpu: number | undefined; - - /** - * The total number of times any command has been used. - */ - commandsUsed: bigint; - - /** - * The total number of times any slash command has been used. - */ - slashCommandsUsed: bigint; -} - -export interface Emitters { - client: BushClient; - commandHandler: BushClient['commandHandler']; - inhibitorHandler: BushClient['inhibitorHandler']; - listenerHandler: BushClient['listenerHandler']; - taskHandler: BushClient['taskHandler']; - contextMenuCommandHandler: BushClient['contextMenuCommandHandler']; - process: NodeJS.Process; - stdin: readline.Interface; - gateway: BushClient['ws']; - rest: BushClient['rest']; - ws: BushClient['ws']; -} diff --git a/lib/extensions/discord-akairo/BushCommand.ts b/lib/extensions/discord-akairo/BushCommand.ts deleted file mode 100644 index 7201248..0000000 --- a/lib/extensions/discord-akairo/BushCommand.ts +++ /dev/null @@ -1,597 +0,0 @@ -import { type DiscordEmojiInfo, type RoleWithDuration } from '#args'; -import { - type BushArgumentTypeCaster, - type BushClient, - type BushCommandHandler, - type BushInhibitor, - type BushListener, - type BushTask, - type ParsedDuration -} 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: BushCommand | null; - command: BushCommand | null; - inhibitor: BushInhibitor | null; - listener: BushListener | null; - task: BushTask | null; - contextMenuCommand: ContextMenuCommand | null; -} - -export interface BaseBushArgumentType 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 BushArgumentType = keyof BaseBushArgumentType | RegExp; - -interface BaseBushArgumentOptions 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 BushArgumentOptions extends BaseBushArgumentOptions { - /** - * 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?: BushArgumentType | (keyof BaseBushArgumentType)[] | BushArgumentTypeCaster; -} - -export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { - /** - * 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 BushMissingPermissionSupplier = (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 BaseBushCommandOptions.args} when using argument generators or custom slashOptions - */ - helpArgs?: ArgsInfo[]; - - /** - * Extra information about the command, displayed in the help command. - */ - note?: string; -} - -export interface BaseBushCommandOptions - extends Omit, - ExtendedCommandOptions { - /** - * The description of the command. - */ - description: string; - - /** - * The arguments for the command. - */ - args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; - - category: string; - - /** - * Permissions required by the client to run this command. - */ - clientPermissions: bigint | bigint[] | BushMissingPermissionSupplier; - - /** - * Permissions required by the user to run this command. - */ - userPermissions: bigint | bigint[] | BushMissingPermissionSupplier; - - /** - * 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 BushCommandOptions = 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 BushCommand extends Command { - public declare client: BushClient; - public declare handler: BushCommandHandler; - 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: BushCommandOptions; - - /** - * 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: BushCommandOptions) { - const options_ = options as BaseBushCommandOptions; - - 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 BushArgumentOptions) - : ({} as BushArgumentOptions); - 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 BaseBushArgumentType { - 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/BushCommandHandler.ts b/lib/extensions/discord-akairo/BushCommandHandler.ts deleted file mode 100644 index da49af9..0000000 --- a/lib/extensions/discord-akairo/BushCommandHandler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { type BushCommand, 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 BushCommandHandlerOptions = CommandHandlerOptions; - -export interface BushCommandHandlerEvents extends CommandHandlerEvents { - commandBlocked: [message: CommandMessage, command: BushCommand, reason: string]; - commandBreakout: [message: CommandMessage, command: BushCommand, /* no util */ breakMessage: Message]; - commandCancelled: [message: CommandMessage, command: BushCommand, /* no util */ retryMessage?: Message]; - commandFinished: [message: CommandMessage, command: BushCommand, args: any, returnValue: any]; - commandInvalid: [message: CommandMessage, command: BushCommand]; - commandLocked: [message: CommandMessage, command: BushCommand]; - commandStarted: [message: CommandMessage, command: BushCommand, args: any]; - cooldown: [message: CommandMessage | SlashMessage, command: BushCommand, remaining: number]; - error: [error: Error, message: /* no util */ Message, command?: BushCommand]; - inPrompt: [message: /* no util */ Message]; - load: [command: BushCommand, isReload: boolean]; - messageBlocked: [message: /* no util */ Message | CommandMessage | SlashMessage, reason: string]; - messageInvalid: [message: CommandMessage]; - missingPermissions: [message: CommandMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]]; - remove: [command: BushCommand]; - slashBlocked: [message: SlashMessage, command: BushCommand, reason: string]; - slashError: [error: Error, message: SlashMessage, command: BushCommand]; - slashFinished: [message: SlashMessage, command: BushCommand, args: any, returnValue: any]; - slashMissingPermissions: [message: SlashMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]]; - slashStarted: [message: SlashMessage, command: BushCommand, args: any]; -} - -export class BushCommandHandler extends CommandHandler { - public declare modules: Collection; - public declare categories: Collection>; -} - -export interface BushCommandHandler extends CommandHandler { - findCommand(name: string): BushCommand; -} diff --git a/lib/extensions/discord-akairo/BushInhibitor.ts b/lib/extensions/discord-akairo/BushInhibitor.ts deleted file mode 100644 index be396cf..0000000 --- a/lib/extensions/discord-akairo/BushInhibitor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; -import { Inhibitor } from 'discord-akairo'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Message } from 'discord.js'; - -export abstract class BushInhibitor extends Inhibitor { - /** - * Checks if message should be blocked. - * A return value of true will block the message. - * If returning a Promise, a resolved value of true will block the message. - * - * **Note:** `'all'` type inhibitors do not have {@link Message.util} defined. - * - * @param message - Message being handled. - * @param command - Command to check. - */ - public abstract override exec(message: CommandMessage, command: BushCommand): any; - public abstract override exec(message: CommandMessage | SlashMessage, command: BushCommand): any; -} diff --git a/lib/extensions/discord-akairo/BushInhibitorHandler.ts b/lib/extensions/discord-akairo/BushInhibitorHandler.ts deleted file mode 100644 index 5e4fb6c..0000000 --- a/lib/extensions/discord-akairo/BushInhibitorHandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InhibitorHandler } from 'discord-akairo'; - -export class BushInhibitorHandler extends InhibitorHandler {} diff --git a/lib/extensions/discord-akairo/BushListener.ts b/lib/extensions/discord-akairo/BushListener.ts deleted file mode 100644 index 6917641..0000000 --- a/lib/extensions/discord-akairo/BushListener.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Listener } from 'discord-akairo'; - -export abstract class BushListener extends Listener {} diff --git a/lib/extensions/discord-akairo/BushListenerHandler.ts b/lib/extensions/discord-akairo/BushListenerHandler.ts deleted file mode 100644 index 9c3e4af..0000000 --- a/lib/extensions/discord-akairo/BushListenerHandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ListenerHandler } from 'discord-akairo'; - -export class BushListenerHandler extends ListenerHandler {} diff --git a/lib/extensions/discord-akairo/BushTask.ts b/lib/extensions/discord-akairo/BushTask.ts deleted file mode 100644 index 1b70c88..0000000 --- a/lib/extensions/discord-akairo/BushTask.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Task } from 'discord-akairo'; - -export abstract class BushTask extends Task {} diff --git a/lib/extensions/discord-akairo/BushTaskHandler.ts b/lib/extensions/discord-akairo/BushTaskHandler.ts deleted file mode 100644 index 6535abb..0000000 --- a/lib/extensions/discord-akairo/BushTaskHandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TaskHandler } from 'discord-akairo'; - -export class BushTaskHandler extends TaskHandler {} diff --git a/lib/extensions/discord-akairo/TanzaniteClient.ts b/lib/extensions/discord-akairo/TanzaniteClient.ts new file mode 100644 index 0000000..24ce962 --- /dev/null +++ b/lib/extensions/discord-akairo/TanzaniteClient.ts @@ -0,0 +1,591 @@ +import { + abbreviatedNumber, + contentWithDuration, + discordEmoji, + duration, + durationSeconds, + globalUser, + messageLink, + permission, + roleWithDuration, + snowflake +} from '#args'; +import type { Config } from '#config'; +import { BotClientEvents, emojis, formatError, inspect, updateEveryCache } from '#lib'; +import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; +import * as Sentry from '@sentry/node'; +import { + AkairoClient, + ArgumentTypeCaster, + ContextMenuCommandHandler, + version as akairoVersion, + type ArgumentPromptData, + type OtherwiseContentSupplier +} from 'discord-akairo'; +import { + ActivityType, + GatewayIntentBits, + MessagePayload, + Options, + Partials, + Structures, + version as discordJsVersion, + type Awaitable, + type If, + type Message, + type MessageOptions, + type Snowflake, + type UserResolvable +} from 'discord.js'; +import type EventEmitter from 'events'; +import { google } from 'googleapis'; +import path from 'path'; +import readline from 'readline'; +import { Options as SequelizeOptions, Sequelize, Sequelize as SequelizeType } from 'sequelize'; +import { fileURLToPath } from 'url'; +import { tinyColor } from '../../arguments/tinyColor.js'; +import { BotCache } from '../../common/BotCache.js'; +import { HighlightManager } from '../../common/HighlightManager.js'; +import { + ActivePunishment, + Global, + Guild as GuildModel, + GuildCount, + Highlight, + Level, + MemberCount, + ModLog, + Reminder, + Shared, + Stat, + StickyRole +} from '../../models/index.js'; +import { AllowedMentions } from '../../utils/AllowedMentions.js'; +import { BotClientUtils } from '../../utils/BotClientUtils.js'; +import { Logger } from '../../utils/Logger.js'; +import { ExtendedGuild } from '../discord.js/ExtendedGuild.js'; +import { ExtendedGuildMember } from '../discord.js/ExtendedGuildMember.js'; +import { ExtendedMessage } from '../discord.js/ExtendedMessage.js'; +import { ExtendedUser } from '../discord.js/ExtendedUser.js'; +import { BotCommandHandler } from './BotCommandHandler.js'; +import { BotInhibitorHandler } from './BotInhibitorHandler.js'; +import { BotListenerHandler } from './BotListenerHandler.js'; +import { BotTaskHandler } from './BotTaskHandler.js'; + +declare module 'discord.js' { + export interface Client extends EventEmitter { + /** The ID of the owner(s). */ + ownerID: Snowflake | Snowflake[]; + /** The ID of the superUser(s). */ + superUserID: Snowflake | Snowflake[]; + /** Whether or not the client is ready. */ + customReady: boolean; + /** The configuration for the client. */ + readonly config: Config; + /** Stats for the client. */ + readonly stats: BotStats; + /** The handler for the bot's listeners. */ + readonly listenerHandler: BotListenerHandler; + /** The handler for the bot's command inhibitors. */ + readonly inhibitorHandler: BotInhibitorHandler; + /** The handler for the bot's commands. */ + readonly commandHandler: BotCommandHandler; + /** The handler for the bot's tasks. */ + readonly taskHandler: BotTaskHandler; + /** The handler for the bot's context menu commands. */ + readonly contextMenuCommandHandler: ContextMenuCommandHandler; + /** The database connection for this instance of the bot (production, beta, or development). */ + readonly instanceDB: SequelizeType; + /** The database connection that is shared between all instances of the bot. */ + readonly sharedDB: SequelizeType; + /** A custom logging system for the bot. */ + readonly logger: Logger; + /** Cached global and guild database data. */ + readonly cache: BotCache; + /** Sentry error reporting for the bot. */ + readonly sentry: typeof Sentry; + /** Manages most aspects of the highlight command */ + readonly highlightManager: HighlightManager; + /** The perspective api */ + perspective: any; + /** Client utilities. */ + readonly utils: BotClientUtils; + /** A custom logging system for the bot. */ + get console(): Logger; + on(event: K, listener: (...args: BotClientEvents[K]) => Awaitable): this; + once(event: K, listener: (...args: BotClientEvents[K]) => Awaitable): this; + emit(event: K, ...args: BotClientEvents[K]): boolean; + off(event: K, listener: (...args: BotClientEvents[K]) => Awaitable): this; + removeAllListeners(event?: K): this; + /** + * Checks if a user is the owner of this bot. + * @param user - User to check. + */ + isOwner(user: UserResolvable): boolean; + /** + * Checks if a user is a super user of this bot. + * @param user - User to check. + */ + isSuperUser(user: UserResolvable): boolean; + } +} + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * The main hub for interacting with the Discord API. + */ +export class TanzaniteClient extends AkairoClient { + public declare ownerID: Snowflake[]; + public declare superUserID: Snowflake[]; + + /** + * Whether or not the client is ready. + */ + public override customReady = false; + + /** + * Stats for the client. + */ + public override readonly stats: BotStats = { cpu: undefined, commandsUsed: 0n, slashCommandsUsed: 0n }; + + /** + * The handler for the bot's listeners. + */ + public override readonly listenerHandler: BotListenerHandler; + + /** + * The handler for the bot's command inhibitors. + */ + public override readonly inhibitorHandler: BotInhibitorHandler; + + /** + * The handler for the bot's commands. + */ + public override readonly commandHandler: BotCommandHandler; + + /** + * The handler for the bot's tasks. + */ + public override readonly taskHandler: BotTaskHandler; + + /** + * The handler for the bot's context menu commands. + */ + public override readonly contextMenuCommandHandler: ContextMenuCommandHandler; + + /** + * The database connection for this instance of the bot (production, beta, or development). + */ + public override readonly instanceDB: SequelizeType; + + /** + * The database connection that is shared between all instances of the bot. + */ + public override readonly sharedDB: SequelizeType; + + /** + * A custom logging system for the bot. + */ + public override readonly logger: Logger = new Logger(this); + + /** + * Cached global and guild database data. + */ + public override readonly cache = new BotCache(); + + /** + * Sentry error reporting for the bot. + */ + public override readonly sentry!: typeof Sentry; + + /** + * Manages most aspects of the highlight command + */ + public override readonly highlightManager: HighlightManager = new HighlightManager(this); + + /** + * The perspective api + */ + public override perspective: any; + + /** + * Client utilities. + */ + public override readonly utils: BotClientUtils = new BotClientUtils(this); + + /** + * @param config The configuration for the client. + */ + public constructor( + /** + * The configuration for the client. + */ + public override readonly config: Config + ) { + super({ + ownerID: config.owners, + intents: Object.keys(GatewayIntentBits) + .map((i) => (typeof i === 'string' ? GatewayIntentBits[i as keyof typeof GatewayIntentBits] : i)) + .reduce((acc, p) => acc | p, 0), + partials: Object.keys(Partials).map((p) => Partials[p as keyof typeof Partials]), + presence: { + activities: [{ name: 'Beep Boop', type: ActivityType.Watching }], + status: 'online' + }, + allowedMentions: AllowedMentions.none(), // no mentions by default + makeCache: Options.cacheWithLimits({ + PresenceManager: { + maxSize: 0, + keepOverLimit: (_, key) => { + if (config.owners.includes(key)) return true; + + return HighlightManager.keep.has(key); + } + } + }), + failIfNotExists: false, + rest: { api: 'https://canary.discord.com/api' } + }); + patch(this); + + this.token = config.token as If; + + /* =-=-= handlers =-=-= */ + this.listenerHandler = new BotListenerHandler(this, { + directory: path.join(__dirname, '..', '..', '..', 'src', 'listeners'), + extensions: ['.js'], + automateCategories: true + }); + this.inhibitorHandler = new BotInhibitorHandler(this, { + directory: path.join(__dirname, '..', '..', '..', 'src', 'inhibitors'), + extensions: ['.js'], + automateCategories: true + }); + this.taskHandler = new BotTaskHandler(this, { + directory: path.join(__dirname, '..', '..', '..', 'src', 'tasks'), + extensions: ['.js'], + automateCategories: true + }); + + const modify = async ( + message: Message, + text: string | MessagePayload | MessageOptions | OtherwiseContentSupplier, + data: ArgumentPromptData, + replaceError: boolean + ) => { + const ending = '\n\n Type **cancel** to cancel the command'; + const options = typeof text === 'function' ? await text(message, data) : text; + const search = '{error}', + replace = emojis.error; + + if (typeof options === 'string') return (replaceError ? options.replace(search, replace) : options) + ending; + + if (options instanceof MessagePayload) { + if (options.options.content) { + if (replaceError) options.options.content = options.options.content.replace(search, replace); + options.options.content += ending; + } + } else if (options.content) { + if (replaceError) options.content = options.content.replace(search, replace); + options.content += ending; + } + return options; + }; + + this.commandHandler = new BotCommandHandler(this, { + directory: path.join(__dirname, '..', '..', '..', 'src', 'commands'), + extensions: ['.js'], + prefix: async ({ guild }: Message) => { + if (this.config.isDevelopment) return 'dev '; + if (!guild) return this.config.prefix; + const prefix = await guild.getSetting('prefix'); + return (prefix ?? this.config.prefix) as string; + }, + allowMention: true, + handleEdits: true, + commandUtil: true, + commandUtilLifetime: 300_000, // 5 minutes + argumentDefaults: { + prompt: { + start: 'Placeholder argument prompt. **If you see this please tell my developers**.', + retry: 'Placeholder failed argument prompt. **If you see this please tell my developers**.', + modifyStart: (message, text, data) => modify(message, text, data, false), + modifyRetry: (message, text, data) => modify(message, text, data, true), + timeout: ':hourglass: You took too long the command has been cancelled.', + ended: 'You exceeded the maximum amount of tries the command has been cancelled', + cancel: 'The command has been cancelled', + retries: 3, + time: 3e4 + }, + otherwise: '' + }, + automateCategories: false, + autoRegisterSlashCommands: true, + skipBuiltInPostInhibitors: true, + aliasReplacement: /-/g + }); + this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, { + directory: path.join(__dirname, '..', '..', '..', 'src', 'context-menu-commands'), + extensions: ['.js'], + automateCategories: true + }); + + /* =-=-= databases =-=-= */ + const sharedDBOptions: SequelizeOptions = { + username: this.config.db.username, + password: this.config.db.password, + dialect: 'postgres', + host: this.config.db.host, + port: this.config.db.port, + logging: this.config.logging.db ? (sql) => this.logger.debug(sql) : false, + timezone: 'America/New_York' + }; + this.instanceDB = new Sequelize({ + ...sharedDBOptions, + database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot' + }); + this.sharedDB = new Sequelize({ + ...sharedDBOptions, + database: 'bushbot-shared' + }); + + this.sentry = Sentry; + } + + /** + * A custom logging system for the bot. + */ + public override get console(): Logger { + return this.logger; + } + + /** + * Extends discord.js structures before the client is instantiated. + */ + public static extendStructures(): void { + Structures.extend('GuildMember', () => ExtendedGuildMember); + Structures.extend('Guild', () => ExtendedGuild); + Structures.extend('Message', () => ExtendedMessage); + Structures.extend('User', () => ExtendedUser); + } + + /** + * Initializes the bot. + */ + public async init() { + if (parseInt(process.versions.node.split('.')[0]) < 18) { + void (await this.console.error('version', `Please use node <>, not <<${process.version}>>.`, false)); + process.exit(2); + } + + this.setMaxListeners(20); + + this.perspective = await google.discoverAPI('https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1'); + + this.commandHandler.useInhibitorHandler(this.inhibitorHandler); + this.commandHandler.useListenerHandler(this.listenerHandler); + this.commandHandler.useTaskHandler(this.taskHandler); + this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler); + this.commandHandler.ignorePermissions = this.config.owners; + this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.shared.superUsers])]; + const emitters: Emitters = { + client: this, + commandHandler: this.commandHandler, + inhibitorHandler: this.inhibitorHandler, + listenerHandler: this.listenerHandler, + taskHandler: this.taskHandler, + contextMenuCommandHandler: this.contextMenuCommandHandler, + process, + stdin: rl, + gateway: this.ws, + rest: this.rest, + ws: this.ws + }; + this.listenerHandler.setEmitters(emitters); + this.commandHandler.resolver.addTypes({ + duration: duration, + contentWithDuration: contentWithDuration, + permission: permission, + snowflake: snowflake, + discordEmoji: discordEmoji, + roleWithDuration: roleWithDuration, + abbreviatedNumber: abbreviatedNumber, + durationSeconds: durationSeconds, + globalUser: globalUser, + messageLink: messageLink, + tinyColor: tinyColor + }); + + this.sentry.setTag('process', process.pid.toString()); + this.sentry.setTag('discord.js', discordJsVersion); + this.sentry.setTag('discord-akairo', akairoVersion); + void this.logger.success('startup', `Successfully connected to <>.`, false); + + // loads all the handlers + const handlers = { + commands: this.commandHandler, + contextMenuCommands: this.contextMenuCommandHandler, + listeners: this.listenerHandler, + inhibitors: this.inhibitorHandler, + tasks: this.taskHandler + }; + const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) => + handler + .loadAll() + .then(() => { + void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false); + }) + .catch((e) => { + void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${formatError(e)}`, false); + if (process.argv.includes('dry')) process.exit(1); + }) + ); + await Promise.allSettled(handlerPromises); + } + + /** + * Connects to the database, initializes models, and creates tables if they do not exist. + */ + public async dbPreInit() { + try { + await this.instanceDB.authenticate(); + GuildModel.initModel(this.instanceDB, this); + ModLog.initModel(this.instanceDB); + ActivePunishment.initModel(this.instanceDB); + Level.initModel(this.instanceDB); + StickyRole.initModel(this.instanceDB); + Reminder.initModel(this.instanceDB); + Highlight.initModel(this.instanceDB); + await this.instanceDB.sync({ alter: true }); // Sync all tables to fix everything if updated + await this.console.success('startup', `Successfully connected to <>.`, false); + } catch (e) { + await this.console.error( + 'startup', + `Failed to connect to <> with error:\n${inspect(e, { colors: true, depth: 1 })}`, + false + ); + process.exit(2); + } + try { + await this.sharedDB.authenticate(); + Stat.initModel(this.sharedDB); + Global.initModel(this.sharedDB); + Shared.initModel(this.sharedDB); + MemberCount.initModel(this.sharedDB); + GuildCount.initModel(this.sharedDB); + await this.sharedDB.sync({ + // Sync all tables to fix everything if updated + // if another instance restarts we don't want to overwrite new changes made in development + alter: this.config.isDevelopment + }); + await this.console.success('startup', `Successfully connected to <>.`, false); + } catch (e) { + await this.console.error( + 'startup', + `Failed to connect to <> with error:\n${inspect(e, { colors: true, depth: 1 })}`, + false + ); + process.exit(2); + } + } + + /** + * Starts the bot + */ + public async start() { + this.intercept('ready', async (arg, done) => { + const promises = this.guilds.cache + .filter((g) => g.large) + .map((guild) => { + return guild.members.fetch(); + }); + await Promise.all(promises); + this.customReady = true; + this.taskHandler.startAll(); + return done(null, `intercepted ${arg}`); + }); + + try { + await this.highlightManager.syncCache(); + await updateEveryCache(this); + void this.console.success('startup', `Successfully created <>.`, false); + + const stats = + (await Stat.findByPk(this.config.environment)) ?? (await Stat.create({ environment: this.config.environment })); + this.stats.commandsUsed = stats.commandsUsed; + this.stats.slashCommandsUsed = stats.slashCommandsUsed; + await this.login(this.token!); + } catch (e) { + await this.console.error('start', inspect(e, { colors: true, depth: 1 }), false); + process.exit(1); + } + } + + /** + * Logs out, terminates the connection to Discord, and destroys the client. + */ + public override destroy(relogin = false): void | Promise { + super.destroy(); + if (relogin) { + return this.login(this.token!); + } + } + + public override isOwner(user: UserResolvable): boolean { + return this.config.owners.includes(this.users.resolveId(user!)!); + } + + public override isSuperUser(user: UserResolvable): boolean { + const userID = this.users.resolveId(user)!; + return this.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID); + } +} + +export interface TanzaniteClient extends EventEmitter, PatchedElements, AkairoClient { + on(event: K, listener: (...args: BotClientEvents[K]) => Awaitable): this; + once(event: K, listener: (...args: BotClientEvents[K]) => Awaitable): this; + emit(event: K, ...args: BotClientEvents[K]): boolean; + off(event: K, listener: (...args: BotClientEvents[K]) => Awaitable): this; + removeAllListeners(event?: K): this; +} + +/** + * Various statistics + */ +export interface BotStats { + /** + * The average cpu usage of the bot from the past 60 seconds. + */ + cpu: number | undefined; + + /** + * The total number of times any command has been used. + */ + commandsUsed: bigint; + + /** + * The total number of times any slash command has been used. + */ + slashCommandsUsed: bigint; +} + +export interface Emitters { + client: TanzaniteClient; + commandHandler: TanzaniteClient['commandHandler']; + inhibitorHandler: TanzaniteClient['inhibitorHandler']; + listenerHandler: TanzaniteClient['listenerHandler']; + taskHandler: TanzaniteClient['taskHandler']; + contextMenuCommandHandler: TanzaniteClient['contextMenuCommandHandler']; + process: NodeJS.Process; + stdin: readline.Interface; + gateway: TanzaniteClient['ws']; + rest: TanzaniteClient['rest']; + ws: TanzaniteClient['ws']; +} diff --git a/lib/extensions/discord.js/BotClientEvents.ts b/lib/extensions/discord.js/BotClientEvents.ts new file mode 100644 index 0000000..284ea32 --- /dev/null +++ b/lib/extensions/discord.js/BotClientEvents.ts @@ -0,0 +1,211 @@ +import type { BanResponse, CommandMessage, Guild as GuildDB, GuildSettings } from '#lib'; +import type { AkairoClientEvents } from 'discord-akairo'; +import type { + ButtonInteraction, + Collection, + Guild, + GuildMember, + GuildTextBasedChannel, + Message, + ModalSubmitInteraction, + Role, + SelectMenuInteraction, + Snowflake, + User +} from 'discord.js'; + +export enum TanzaniteEvent { + Ban = 'customBan', + Block = 'customBlock', + Kick = 'customKick', + Mute = 'customMute', + PunishRoleAdd = 'punishRoleAdd', + PunishRoleRemove = 'punishRoleRemove', + Purge = 'customPurge', + RemoveTimeout = 'customRemoveTimeout', + Timeout = 'customTimeout', + Unban = 'customUnban', + Unblock = 'customUnblock', + Unmute = 'customUnmute', + UpdateModlog = 'updateModlog', + UpdateSettings = 'updateSettings', + Warn = 'customWarn', + LevelUpdate = 'levelUpdate', + Lockdown = 'lockdown', + Unlockdown = 'unlockdown', + MassBan = 'massBan', + MassEvidence = 'massEvidence', + Button = 'button', + SelectMenu = 'selectMenu', + ModalSubmit = 'modal' +} + +export interface BotClientEvents extends AkairoClientEvents { + [TanzaniteEvent.Ban]: [ + victim: GuildMember | User, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess?: boolean, + evidence?: string + ]; + [TanzaniteEvent.Block]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess: boolean, + channel: GuildTextBasedChannel, + evidence?: string + ]; + [TanzaniteEvent.Kick]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.Mute]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.PunishRoleAdd]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + duration: number, + role: Role, + evidence?: string + ]; + [TanzaniteEvent.PunishRoleRemove]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + role: Role, + evidence?: string + ]; + [TanzaniteEvent.Purge]: [ + moderator: User, + guild: Guild, + channel: GuildTextBasedChannel, + messages: Collection + ]; + [TanzaniteEvent.RemoveTimeout]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.Timeout]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.Unban]: [ + victim: User, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.Unblock]: [ + victim: GuildMember | User, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + channel: GuildTextBasedChannel, + evidence?: string + ]; + [TanzaniteEvent.Unmute]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.UpdateModlog]: [ + moderator: GuildMember, + modlogID: string, + key: 'evidence' | 'hidden', + oldModlog: string | boolean, + newModlog: string | boolean + ]; + [TanzaniteEvent.UpdateSettings]: [ + setting: Setting, + guild: Guild, + oldValue: GuildDB[Setting], + newValue: GuildDB[Setting], + moderator?: GuildMember + ]; + [TanzaniteEvent.Warn]: [ + victim: GuildMember, + moderator: User, + guild: Guild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + evidence?: string + ]; + [TanzaniteEvent.LevelUpdate]: [ + member: GuildMember, + oldLevel: number, + newLevel: number, + currentXp: number, + message: CommandMessage + ]; + [TanzaniteEvent.Lockdown]: [ + moderator: GuildMember, + reason: string | undefined, + channelsSuccessMap: Collection, + all?: boolean + ]; + [TanzaniteEvent.Unlockdown]: [ + moderator: GuildMember, + reason: string | undefined, + channelsSuccessMap: Collection, + all?: boolean + ]; + [TanzaniteEvent.MassBan]: [ + moderator: GuildMember, + guild: Guild, + reason: string | undefined, + results: Collection + ]; + [TanzaniteEvent.MassEvidence]: [moderator: GuildMember, guild: Guild, evidence: string, lines: string[]]; + /* components */ + [TanzaniteEvent.Button]: [button: ButtonInteraction]; + [TanzaniteEvent.SelectMenu]: [selectMenu: SelectMenuInteraction]; + [TanzaniteEvent.ModalSubmit]: [modal: ModalSubmitInteraction]; +} + +type Setting = GuildSettings | 'enabledFeatures' | 'blacklistedChannels' | 'blacklistedUsers' | 'disabledCommands'; diff --git a/lib/extensions/discord.js/BushClientEvents.ts b/lib/extensions/discord.js/BushClientEvents.ts deleted file mode 100644 index 22bae65..0000000 --- a/lib/extensions/discord.js/BushClientEvents.ts +++ /dev/null @@ -1,200 +0,0 @@ -import type { - BanResponse, - CommandMessage, - Guild as GuildDB, - GuildSettings -} from '#lib'; -import type { AkairoClientEvents } from 'discord-akairo'; -import type { - ButtonInteraction, - Collection, - Guild, - GuildMember, - GuildTextBasedChannel, - Message, - ModalSubmitInteraction, - Role, - SelectMenuInteraction, - Snowflake, - User -} from 'discord.js'; - -export interface BushClientEvents extends AkairoClientEvents { - bushBan: [ - victim: GuildMember | User, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess?: boolean, - evidence?: string - ]; - bushBlock: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess: boolean, - channel: GuildTextBasedChannel, - evidence?: string - ]; - bushKick: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushMute: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess: boolean, - evidence?: string - ]; - bushPunishRole: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - role: Role, - evidence?: string - ]; - bushPunishRoleRemove: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - role: Role, - evidence?: string - ]; - bushPurge: [ - moderator: User, - guild: Guild, - channel: GuildTextBasedChannel, - messages: Collection - ]; - bushRemoveTimeout: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushTimeout: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess: boolean, - evidence?: string - ]; - bushUnban: [ - victim: User, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushUnblock: [ - victim: GuildMember | User, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - channel: GuildTextBasedChannel, - evidence?: string - ]; - bushUnmute: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushUpdateModlog: [ - moderator: GuildMember, - modlogID: string, - key: 'evidence' | 'hidden', - oldModlog: string | boolean, - newModlog: string | boolean - ]; - bushUpdateSettings: [ - setting: Setting, - guild: Guild, - oldValue: GuildDB[Setting], - newValue: GuildDB[Setting], - moderator?: GuildMember - ]; - bushWarn: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushLevelUpdate: [ - member: GuildMember, - oldLevel: number, - newLevel: number, - currentXp: number, - message: CommandMessage - ]; - bushLockdown: [ - moderator: GuildMember, - reason: string | undefined, - channelsSuccessMap: Collection, - all?: boolean - ]; - bushUnlockdown: [ - moderator: GuildMember, - reason: string | undefined, - channelsSuccessMap: Collection, - all?: boolean - ]; - massBan: [ - moderator: GuildMember, - guild: Guild, - reason: string | undefined, - results: Collection - ]; - massEvidence: [ - moderator: GuildMember, - guild: Guild, - evidence: string, - lines: string[] - ]; - /* components */ - button: [button: ButtonInteraction]; - selectMenu: [selectMenu: SelectMenuInteraction]; - modal: [modal: ModalSubmitInteraction]; -} - -type Setting = - | GuildSettings - | 'enabledFeatures' - | 'blacklistedChannels' - | 'blacklistedUsers' - | 'disabledCommands'; diff --git a/lib/extensions/discord.js/ExtendedGuild.ts b/lib/extensions/discord.js/ExtendedGuild.ts index 20c3d29..67de5cf 100644 --- a/lib/extensions/discord.js/ExtendedGuild.ts +++ b/lib/extensions/discord.js/ExtendedGuild.ts @@ -6,6 +6,7 @@ import { emojis, permissionsResponse, punishmentEntryRemove, + TanzaniteClient, type BanResponse, type GuildFeatures, type GuildLogType, @@ -13,7 +14,7 @@ import { } from '#lib'; import * as Moderation from '#lib/common/Moderation.js'; import { Guild as GuildDB, ModLogType } from '#lib/models/index.js'; -import { addOrRemoveFromArray } from '#lib/utils/BushUtils.js'; +import { addOrRemoveFromArray } from '#lib/utils/Utils.js'; import assert from 'assert/strict'; import { AttachmentBuilder, @@ -42,8 +43,13 @@ import { type WebhookMessageOptions } from 'discord.js'; import _ from 'lodash'; +import { TanzaniteEvent } from './BotClientEvents.js'; declare module 'discord.js' { + export interface BaseGuild { + client: TanzaniteClient; + } + export interface Guild { /** * Checks if the guild has a certain custom feature. @@ -93,7 +99,7 @@ declare module 'discord.js' { /** * Sends a message to the guild's specified logging channel * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link BushTextChannel.send} + * @param message The parameters for {@link TextChannel.send} */ sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise; /** @@ -107,15 +113,15 @@ declare module 'discord.js' { * @param options Options for banning the user. * @returns A string status message of the ban. */ - bushBan(options: GuildBushBanOptions): Promise; + customBan(options: GuildCustomBanOptions): Promise; /** - * {@link bushBan} with less resolving and checks + * {@link customBan} with less resolving and checks * @param options Options for banning the user. * @returns A string status message of the ban. * **Preconditions:** * - {@link me} has the `BanMembers` permission * **Warning:** - * - Doesn't emit bushBan Event + * - Doesn't emit customBan Event */ massBanOne(options: GuildMassBanOneOptions): Promise; /** @@ -123,7 +129,7 @@ declare module 'discord.js' { * @param options Options for unbanning the user. * @returns A status message of the unban. */ - bushUnban(options: GuildBushUnbanOptions): Promise; + customUnban(options: GuildCustomUnbanOptions): Promise; /** * Denies send permissions in specified channels * @param options The options for locking down the guild @@ -207,7 +213,7 @@ export class ExtendedGuild extends Guild { const oldValue = row[setting] as GuildDB[K]; row[setting] = value; this.client.cache.guilds.set(this.id, row.toJSON() as GuildDB); - this.client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator); + this.client.emit(TanzaniteEvent.UpdateSettings, setting, this, oldValue, row[setting], moderator); return await row.save(); } @@ -229,7 +235,7 @@ export class ExtendedGuild extends Guild { /** * Sends a message to the guild's specified logging channel * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link BushTextChannel.send} + * @param message The parameters for {@link TextChannel.send} */ public override async sendLogChannel( logType: GuildLogType, @@ -265,7 +271,7 @@ export class ExtendedGuild extends Guild { * @param options Options for banning the user. * @returns A string status message of the ban. */ - public override async bushBan(options: GuildBushBanOptions): Promise { + public override async customBan(options: GuildCustomBanOptions): Promise { // checks if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS; @@ -330,7 +336,7 @@ export class ExtendedGuild extends Guild { if (!([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret)) this.client.emit( - 'bushBan', + TanzaniteEvent.Ban, user, moderator, this, @@ -344,13 +350,13 @@ export class ExtendedGuild extends Guild { } /** - * {@link bushBan} with less resolving and checks + * {@link customBan} with less resolving and checks * @param options Options for banning the user. * @returns A string status message of the ban. * **Preconditions:** * - {@link me} has the `BanMembers` permission * **Warning:** - * - Doesn't emit bushBan Event + * - Doesn't emit customBan Event */ public override async massBanOne(options: GuildMassBanOneOptions): Promise { if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED; @@ -414,7 +420,7 @@ export class ExtendedGuild extends Guild { * @param options Options for unbanning the user. * @returns A status message of the unban. */ - public override async bushUnban(options: GuildBushUnbanOptions): Promise { + public override async customUnban(options: GuildCustomUnbanOptions): Promise { // checks if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return unbanResponse.MISSING_PERMISSIONS; @@ -483,7 +489,7 @@ export class ExtendedGuild extends Guild { ) ) this.client.emit( - 'bushUnban', + TanzaniteEvent.Unban, user, moderator, this, @@ -570,7 +576,13 @@ export class ExtendedGuild extends Guild { else return `success: ${success.filter((c) => c === true).size}`; })(); - this.client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all); + this.client.emit( + options.unlock ? TanzaniteEvent.Unlockdown : TanzaniteEvent.Lockdown, + moderator, + options.reason, + success, + options.all + ); return ret; } @@ -783,7 +795,7 @@ export class ExtendedGuild extends Guild { /** * Options for unbanning a user */ -export interface GuildBushUnbanOptions { +export interface GuildCustomUnbanOptions { /** * The user to unban */ @@ -830,7 +842,7 @@ export interface GuildMassBanOneOptions { /** * Options for banning a user */ -export interface GuildBushBanOptions { +export interface GuildCustomBanOptions { /** * The user to ban */ diff --git a/lib/extensions/discord.js/ExtendedGuildMember.ts b/lib/extensions/discord.js/ExtendedGuildMember.ts index f8add83..172f6df 100644 --- a/lib/extensions/discord.js/ExtendedGuildMember.ts +++ b/lib/extensions/discord.js/ExtendedGuildMember.ts @@ -1,5 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { formatError, Moderation, ModLogType, Time, type BushClientEvents, type PunishmentTypeDM, type ValueOf } from '#lib'; +import { + formatError, + Moderation, + ModLogType, + TanzaniteClient, + Time, + type BotClientEvents, + type PunishmentTypeDM, + type ValueOf +} from '#lib'; import { ChannelType, GuildMember, @@ -8,10 +17,13 @@ import { type GuildTextBasedChannel, type Role } from 'discord.js'; +import { TanzaniteEvent } from './BotClientEvents.js'; /* eslint-enable @typescript-eslint/no-unused-vars */ declare module 'discord.js' { export interface GuildMember { + client: TanzaniteClient; + /** * Send a punishment dm to the user. * @param punishment The punishment that the user has received. @@ -21,7 +33,7 @@ declare module 'discord.js' { * @param sendFooter Whether or not to send the guild's punishment footer with the dm. * @returns Whether or not the dm was sent successfully. */ - bushPunishDM( + customPunishDM( punishment: PunishmentTypeDM, reason?: string | null, duration?: number, @@ -32,71 +44,71 @@ declare module 'discord.js' { * Warn the user, create a modlog entry, and send a dm to the user. * @param options Options for warning the user. * @returns An object with the result of the warning, and the case number of the warn. - * @emits {@link BushClientEvents.bushWarn} + * @emits {@link BotClientEvents.warnMember} */ - bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }>; + customWarn(options: CustomPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }>; /** * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment. * @param options Options for adding a role to the user. * @returns A status message for adding the add. - * @emits {@link BushClientEvents.bushPunishRole} + * @emits {@link BotClientEvents.punishRole} */ - bushAddRole(options: AddRoleOptions): Promise; + customAddRole(options: AddRoleOptions): Promise; /** * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment. * @param options Options for removing a role from the user. * @returns A status message for removing the role. - * @emits {@link BushClientEvents.bushPunishRoleRemove} + * @emits {@link BotClientEvents.punishRoleRemove} */ - bushRemoveRole(options: RemoveRoleOptions): Promise; + customRemoveRole(options: RemoveRoleOptions): Promise; /** * Mute the user, create a modlog entry, creates a punishment entry, and dms the user. * @param options Options for muting the user. * @returns A status message for muting the user. - * @emits {@link BushClientEvents.bushMute} + * @emits {@link BotClientEvents.customMute} */ - bushMute(options: BushTimedPunishmentOptions): Promise; + customMute(options: CustomTimedPunishmentOptions): Promise; /** * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user. * @param options Options for unmuting the user. * @returns A status message for unmuting the user. - * @emits {@link BushClientEvents.bushUnmute} + * @emits {@link BotClientEvents.customUnmute} */ - bushUnmute(options: BushPunishmentOptions): Promise; + customUnmute(options: CustomPunishmentOptions): Promise; /** * Kick the user, create a modlog entry, and dm the user. * @param options Options for kicking the user. * @returns A status message for kicking the user. - * @emits {@link BushClientEvents.bushKick} + * @emits {@link BotClientEvents.customKick} */ - bushKick(options: BushPunishmentOptions): Promise; + customKick(options: CustomPunishmentOptions): Promise; /** * Ban the user, create a modlog entry, create a punishment entry, and dm the user. * @param options Options for banning the user. * @returns A status message for banning the user. - * @emits {@link BushClientEvents.bushBan} + * @emits {@link BotClientEvents.customBan} */ - bushBan(options: BushBanOptions): Promise>; + customBan(options: CustomBanOptions): Promise>; /** * Prevents a user from speaking in a channel. * @param options Options for blocking the user. */ - bushBlock(options: BlockOptions): Promise; + customBlock(options: BlockOptions): Promise; /** * Allows a user to speak in a channel. * @param options Options for unblocking the user. */ - bushUnblock(options: UnblockOptions): Promise; + customUnblock(options: UnblockOptions): Promise; /** * Mutes a user using discord's timeout feature. * @param options Options for timing out the user. */ - bushTimeout(options: BushTimeoutOptions): Promise; + customTimeout(options: CustomTimeoutOptions): Promise; /** * Removes a timeout from a user. * @param options Options for removing the timeout. */ - bushRemoveTimeout(options: BushPunishmentOptions): Promise; + customRemoveTimeout(options: CustomPunishmentOptions): Promise; /** * Whether or not the user is an owner of the bot. */ @@ -121,7 +133,7 @@ export class ExtendedGuildMember extends GuildMember { * @param sendFooter Whether or not to send the guild's punishment footer with the dm. * @returns Whether or not the dm was sent successfully. */ - public override async bushPunishDM( + public override async customPunishDM( punishment: PunishmentTypeDM, reason?: string | null, duration?: number, @@ -144,9 +156,9 @@ export class ExtendedGuildMember extends GuildMember { * Warn the user, create a modlog entry, and send a dm to the user. * @param options Options for warning the user. * @returns An object with the result of the warning, and the case number of the warn. - * @emits {@link BushClientEvents.bushWarn} + * @emits {@link BotClientEvents.warnMember} */ - public override async bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> { + public override async customWarn(options: CustomPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> { let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); @@ -172,7 +184,7 @@ export class ExtendedGuildMember extends GuildMember { if (!options.silent) { // dm user - const dmSuccess = await this.bushPunishDM('warned', options.reason); + const dmSuccess = await this.customPunishDM('warned', options.reason); dmSuccessEvent = dmSuccess; if (!dmSuccess) return { result: warnResponse.DM_ERROR, caseNum: result.caseNum }; } @@ -180,7 +192,7 @@ export class ExtendedGuildMember extends GuildMember { return { result: warnResponse.SUCCESS, caseNum: result.caseNum }; })(); if (!([warnResponse.MODLOG_ERROR] as const).includes(ret.result) && !options.silent) - this.client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); + this.client.emit(TanzaniteEvent.Warn, this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); return ret; } @@ -188,9 +200,9 @@ export class ExtendedGuildMember extends GuildMember { * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment. * @param options Options for adding a role to the user. * @returns A status message for adding the add. - * @emits {@link BushClientEvents.bushPunishRole} + * @emits {@link BotClientEvents.punishRole} */ - public override async bushAddRole(options: AddRoleOptions): Promise { + public override async customAddRole(options: AddRoleOptions): Promise { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return addRoleResponse.MISSING_PERMISSIONS; const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); @@ -244,7 +256,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushPunishRole', + TanzaniteEvent.PunishRoleAdd, this, moderator, this.guild, @@ -261,9 +273,9 @@ export class ExtendedGuildMember extends GuildMember { * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment. * @param options Options for removing a role from the user. * @returns A status message for removing the role. - * @emits {@link BushClientEvents.bushPunishRoleRemove} + * @emits {@link BotClientEvents.punishRoleRemove} */ - public override async bushRemoveRole(options: RemoveRoleOptions): Promise { + public override async customRemoveRole(options: RemoveRoleOptions): Promise { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return removeRoleResponse.MISSING_PERMISSIONS; const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); @@ -318,7 +330,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushPunishRoleRemove', + TanzaniteEvent.PunishRoleRemove, this, moderator, this.guild, @@ -354,9 +366,9 @@ export class ExtendedGuildMember extends GuildMember { * Mute the user, create a modlog entry, creates a punishment entry, and dms the user. * @param options Options for muting the user. * @returns A status message for muting the user. - * @emits {@link BushClientEvents.bushMute} + * @emits {@link BotClientEvents.customMute} */ - public override async bushMute(options: BushTimedPunishmentOptions): Promise { + public override async customMute(options: CustomTimedPunishmentOptions): Promise { // checks const checks = await Moderation.checkMutePermissions(this.guild); if (checks !== true) return checks; @@ -410,7 +422,7 @@ export class ExtendedGuildMember extends GuildMember { if (!options.silent) { // dm user - const dmSuccess = await this.bushPunishDM('muted', options.reason, options.duration ?? 0, modlog.id); + const dmSuccess = await this.customPunishDM('muted', options.reason, options.duration ?? 0, modlog.id); dmSuccessEvent = dmSuccess; if (!dmSuccess) return muteResponse.DM_ERROR; } @@ -423,7 +435,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushMute', + TanzaniteEvent.Mute, this, moderator, this.guild, @@ -440,9 +452,9 @@ export class ExtendedGuildMember extends GuildMember { * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user. * @param options Options for unmuting the user. * @returns A status message for unmuting the user. - * @emits {@link BushClientEvents.bushUnmute} + * @emits {@link BotClientEvents.customUnmute} */ - public override async bushUnmute(options: BushPunishmentOptions): Promise { + public override async customUnmute(options: CustomPunishmentOptions): Promise { // checks const checks = await Moderation.checkMutePermissions(this.guild); if (checks !== true) return checks; @@ -492,7 +504,7 @@ export class ExtendedGuildMember extends GuildMember { if (!options.silent) { // dm user - const dmSuccess = await this.bushPunishDM('unmuted', options.reason, undefined, '', false); + const dmSuccess = await this.customPunishDM('unmuted', options.reason, undefined, '', false); dmSuccessEvent = dmSuccess; if (!dmSuccess) return unmuteResponse.DM_ERROR; } @@ -507,7 +519,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushUnmute', + TanzaniteEvent.Unmute, this, moderator, this.guild, @@ -523,9 +535,9 @@ export class ExtendedGuildMember extends GuildMember { * Kick the user, create a modlog entry, and dm the user. * @param options Options for kicking the user. * @returns A status message for kicking the user. - * @emits {@link BushClientEvents.bushKick} + * @emits {@link BotClientEvents.customKick} */ - public override async bushKick(options: BushPunishmentOptions): Promise { + public override async customKick(options: CustomPunishmentOptions): Promise { // checks if (!this.guild.members.me?.permissions.has(PermissionFlagsBits.KickMembers) || !this.kickable) return kickResponse.MISSING_PERMISSIONS; @@ -550,7 +562,7 @@ export class ExtendedGuildMember extends GuildMember { caseID = modlog.id; // dm user - const dmSuccess = options.silent ? null : await this.bushPunishDM('kicked', options.reason, undefined, modlog.id); + const dmSuccess = options.silent ? null : await this.customPunishDM('kicked', options.reason, undefined, modlog.id); dmSuccessEvent = dmSuccess ?? undefined; // kick @@ -562,7 +574,7 @@ export class ExtendedGuildMember extends GuildMember { })(); if (!([kickResponse.ACTION_ERROR, kickResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent) this.client.emit( - 'bushKick', + TanzaniteEvent.Kick, this, moderator, this.guild, @@ -578,9 +590,11 @@ export class ExtendedGuildMember extends GuildMember { * Ban the user, create a modlog entry, create a punishment entry, and dm the user. * @param options Options for banning the user. * @returns A status message for banning the user. - * @emits {@link BushClientEvents.bushBan} + * @emits {@link BotClientEvents.customBan} */ - public override async bushBan(options: BushBanOptions): Promise> { + public override async customBan( + options: CustomBanOptions + ): Promise> { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.BanMembers) || !this.bannable) return banResponse.MISSING_PERMISSIONS; @@ -591,7 +605,7 @@ export class ExtendedGuildMember extends GuildMember { if (!moderator) return banResponse.CANNOT_RESOLVE_USER; // ignore result, they should still be banned even if their mute cannot be removed - await this.bushUnmute({ + await this.customUnmute({ reason: 'User is about to be banned, a mute is no longer necessary.', moderator: this.guild.members.me!, silent: true @@ -616,7 +630,7 @@ export class ExtendedGuildMember extends GuildMember { // dm user const dmSuccess = options.silent ? null - : await this.bushPunishDM('banned', options.reason, options.duration ?? 0, modlog.id); + : await this.customPunishDM('banned', options.reason, options.duration ?? 0, modlog.id); dmSuccessEvent = dmSuccess ?? undefined; // ban @@ -645,7 +659,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushBan', + TanzaniteEvent.Ban, this, moderator, this.guild, @@ -662,7 +676,7 @@ export class ExtendedGuildMember extends GuildMember { * Prevents a user from speaking in a channel. * @param options Options for blocking the user. */ - public override async bushBlock(options: BlockOptions): Promise { + public override async customBlock(options: BlockOptions): Promise { const channel = this.guild.channels.resolve(options.channel); if (!channel || (!channel.isTextBased() && !channel.isThread())) return blockResponse.INVALID_CHANNEL; @@ -737,7 +751,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushBlock', + TanzaniteEvent.Block, this, moderator, this.guild, @@ -755,7 +769,7 @@ export class ExtendedGuildMember extends GuildMember { * Allows a user to speak in a channel. * @param options Options for unblocking the user. */ - public override async bushUnblock(options: UnblockOptions): Promise { + public override async customUnblock(options: UnblockOptions): Promise { const _channel = this.guild.channels.resolve(options.channel); if (!_channel || (_channel.type !== ChannelType.GuildText && !_channel.isThread())) return unblockResponse.INVALID_CHANNEL; const channel = _channel as GuildTextBasedChannel; @@ -828,7 +842,7 @@ export class ExtendedGuildMember extends GuildMember { !options.silent ) this.client.emit( - 'bushUnblock', + TanzaniteEvent.Unblock, this, moderator, this.guild, @@ -845,7 +859,7 @@ export class ExtendedGuildMember extends GuildMember { * Mutes a user using discord's timeout feature. * @param options Options for timing out the user. */ - public override async bushTimeout(options: BushTimeoutOptions): Promise { + public override async customTimeout(options: CustomTimeoutOptions): Promise { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return timeoutResponse.MISSING_PERMISSIONS; @@ -883,7 +897,7 @@ export class ExtendedGuildMember extends GuildMember { if (!options.silent) { // dm user - const dmSuccess = await this.bushPunishDM('timedout', options.reason, options.duration, modlog.id); + const dmSuccess = await this.customPunishDM('timedout', options.reason, options.duration, modlog.id); dmSuccessEvent = dmSuccess; if (!dmSuccess) return timeoutResponse.DM_ERROR; } @@ -893,7 +907,7 @@ export class ExtendedGuildMember extends GuildMember { if (!([timeoutResponse.ACTION_ERROR, timeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent) this.client.emit( - 'bushTimeout', + TanzaniteEvent.Timeout, this, moderator, this.guild, @@ -910,7 +924,7 @@ export class ExtendedGuildMember extends GuildMember { * Removes a timeout from a user. * @param options Options for removing the timeout. */ - public override async bushRemoveTimeout(options: BushPunishmentOptions): Promise { + public override async customRemoveTimeout(options: CustomPunishmentOptions): Promise { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return removeTimeoutResponse.MISSING_PERMISSIONS; @@ -944,7 +958,7 @@ export class ExtendedGuildMember extends GuildMember { if (!options.silent) { // dm user - const dmSuccess = await this.bushPunishDM('untimedout', options.reason, undefined, '', false); + const dmSuccess = await this.customPunishDM('untimedout', options.reason, undefined, '', false); dmSuccessEvent = dmSuccess; if (!dmSuccess) return removeTimeoutResponse.DM_ERROR; } @@ -954,7 +968,7 @@ export class ExtendedGuildMember extends GuildMember { if (!([removeTimeoutResponse.ACTION_ERROR, removeTimeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent) this.client.emit( - 'bushRemoveTimeout', + TanzaniteEvent.RemoveTimeout, this, moderator, this.guild, @@ -984,7 +998,7 @@ export class ExtendedGuildMember extends GuildMember { /** * Options for punishing a user. */ -export interface BushPunishmentOptions { +export interface CustomPunishmentOptions { /** * The reason for the punishment. */ @@ -1009,7 +1023,7 @@ export interface BushPunishmentOptions { /** * Punishment options for punishments that can be temporary. */ -export interface BushTimedPunishmentOptions extends BushPunishmentOptions { +export interface CustomTimedPunishmentOptions extends CustomPunishmentOptions { /** * The duration of the punishment. */ @@ -1019,7 +1033,7 @@ export interface BushTimedPunishmentOptions extends BushPunishmentOptions { /** * Options for a role add punishment. */ -export interface AddRoleOptions extends BushTimedPunishmentOptions { +export interface AddRoleOptions extends CustomTimedPunishmentOptions { /** * The role to add to the user. */ @@ -1034,7 +1048,7 @@ export interface AddRoleOptions extends BushTimedPunishmentOptions { /** * Options for a role remove punishment. */ -export interface RemoveRoleOptions extends BushTimedPunishmentOptions { +export interface RemoveRoleOptions extends CustomTimedPunishmentOptions { /** * The role to remove from the user. */ @@ -1049,7 +1063,7 @@ export interface RemoveRoleOptions extends BushTimedPunishmentOptions { /** * Options for banning a user. */ -export interface BushBanOptions extends BushTimedPunishmentOptions { +export interface CustomBanOptions extends CustomTimedPunishmentOptions { /** * The number of days to delete the user's messages for. */ @@ -1059,7 +1073,7 @@ export interface BushBanOptions extends BushTimedPunishmentOptions { /** * Options for blocking a user from a channel. */ -export interface BlockOptions extends BushTimedPunishmentOptions { +export interface BlockOptions extends CustomTimedPunishmentOptions { /** * The channel to block the user from. */ @@ -1069,7 +1083,7 @@ export interface BlockOptions extends BushTimedPunishmentOptions { /** * Options for unblocking a user from a channel. */ -export interface UnblockOptions extends BushPunishmentOptions { +export interface UnblockOptions extends CustomPunishmentOptions { /** * The channel to unblock the user from. */ @@ -1079,7 +1093,7 @@ export interface UnblockOptions extends BushPunishmentOptions { /** * Punishment options for punishments that can be temporary. */ -export interface BushTimeoutOptions extends BushPunishmentOptions { +export interface CustomTimeoutOptions extends CustomPunishmentOptions { /** * The duration of the punishment. */ @@ -1251,5 +1265,5 @@ export type TimeoutResponse = ValueOf; export type RemoveTimeoutResponse = ValueOf; /** - * @typedef {BushClientEvents} VSCodePleaseDontRemove + * @typedef {BotClientEvents} VSCodePleaseDontRemove */ diff --git a/lib/extensions/discord.js/ExtendedUser.ts b/lib/extensions/discord.js/ExtendedUser.ts index 23de523..65b14c7 100644 --- a/lib/extensions/discord.js/ExtendedUser.ts +++ b/lib/extensions/discord.js/ExtendedUser.ts @@ -1,4 +1,4 @@ -import { User, type Partialize } from 'discord.js'; +import { User } from 'discord.js'; declare module 'discord.js' { export interface User { @@ -13,8 +13,6 @@ declare module 'discord.js' { } } -export type PartialBushUser = Partialize; - /** * Represents a user on Discord. */ diff --git a/lib/index.ts b/lib/index.ts index ca23177..fc7bb4c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,10 +1,9 @@ import './global.js'; - export * from './automod/AutomodShared.js'; export * from './automod/MemberAutomod.js'; export * from './automod/MessageAutomod.js'; export * from './automod/PresenceAutomod.js'; -export * from './common/BushCache.js'; +export * from './common/BotCache.js'; export * from './common/ButtonPaginator.js'; export * from './common/CanvasProgressBar.js'; export * from './common/ConfirmationPrompt.js'; @@ -20,42 +19,32 @@ export type { RemovePunishmentEntryOptions, SimpleCreateModLogEntryOptions } from './common/Moderation.js'; -export * from './extensions/discord-akairo/BushArgumentTypeCaster.js'; -export * from './extensions/discord-akairo/BushClient.js'; -export * from './extensions/discord-akairo/BushCommand.js'; -export * from './extensions/discord-akairo/BushCommandHandler.js'; -export * from './extensions/discord-akairo/BushInhibitor.js'; -export * from './extensions/discord-akairo/BushInhibitorHandler.js'; -export * from './extensions/discord-akairo/BushListener.js'; -export * from './extensions/discord-akairo/BushListenerHandler.js'; -export * from './extensions/discord-akairo/BushTask.js'; -export * from './extensions/discord-akairo/BushTaskHandler.js'; +export * from './extensions/discord-akairo/BotArgumentTypeCaster.js'; +export * from './extensions/discord-akairo/BotCommand.js'; +export * from './extensions/discord-akairo/BotCommandHandler.js'; +export * from './extensions/discord-akairo/BotInhibitor.js'; +export * from './extensions/discord-akairo/BotInhibitorHandler.js'; +export * from './extensions/discord-akairo/BotListener.js'; +export * from './extensions/discord-akairo/BotListenerHandler.js'; +export * from './extensions/discord-akairo/BotTask.js'; +export * from './extensions/discord-akairo/BotTaskHandler.js'; export * from './extensions/discord-akairo/SlashMessage.js'; -export type { BushClientEvents } from './extensions/discord.js/BushClientEvents.js'; +export * from './extensions/discord-akairo/TanzaniteClient.js'; +export * from './extensions/discord.js/BotClientEvents.js'; export * from './extensions/discord.js/ExtendedGuild.js'; export * from './extensions/discord.js/ExtendedGuildMember.js'; export * from './extensions/discord.js/ExtendedMessage.js'; export * from './extensions/discord.js/ExtendedUser.js'; -export * from './models/BaseModel.js'; -export * from './models/instance/ActivePunishment.js'; -export * from './models/instance/Guild.js'; -export * from './models/instance/Highlight.js'; -export * from './models/instance/Level.js'; -export * from './models/instance/ModLog.js'; -export * from './models/instance/Reminder.js'; -export * from './models/instance/StickyRole.js'; -export * from './models/shared/Global.js'; -export * from './models/shared/MemberCount.js'; -export * from './models/shared/Shared.js'; -export * from './models/shared/Stat.js'; -export type { BushInspectOptions } from './types/BushInspectOptions.js'; +export * from './models/index.js'; export type { CodeBlockLang } from './types/CodeBlockLang.js'; +export type { CustomInspectOptions } from './types/InspectOptions.js'; +export * from './types/misc.js'; export * from './utils/AllowedMentions.js'; export * as Arg from './utils/Arg.js'; -export * from './utils/BushConstants.js'; -export * from './utils/BushLogger.js'; -export * from './utils/BushUtils.js'; +export * from './utils/Constants.js'; export * from './utils/ErrorHandler.js'; export * as Format from './utils/Format.js'; export * from './utils/FormatResponse.js'; +export * from './utils/Logger.js'; export * from './utils/UpdateCache.js'; +export * from './utils/Utils.js'; diff --git a/lib/models/instance/Guild.ts b/lib/models/instance/Guild.ts index 763485f..72091ca 100644 --- a/lib/models/instance/Guild.ts +++ b/lib/models/instance/Guild.ts @@ -1,6 +1,6 @@ import config from '#config'; import { BadWordDetails } from '#lib/automod/AutomodShared.js'; -import { type BushClient } from '#lib/extensions/discord-akairo/BushClient.js'; +import { type TanzaniteClient } from '#lib/extensions/discord-akairo/TanzaniteClient.js'; import { ChannelType, Constants, type Snowflake } from 'discord.js'; import { DataTypes, type Sequelize } from 'sequelize'; import { BaseModel } from '../BaseModel.js'; @@ -145,7 +145,7 @@ export class Guild extends BaseModel i * Initializes the model. * @param sequelize The sequelize instance. */ - public static initModel(sequelize: Sequelize, client: BushClient): void { + public static initModel(sequelize: Sequelize, client: TanzaniteClient): void { Guild.init( { id: { type: DataTypes.STRING, primaryKey: true }, diff --git a/lib/types/BushInspectOptions.ts b/lib/types/BushInspectOptions.ts deleted file mode 100644 index 30ed01a..0000000 --- a/lib/types/BushInspectOptions.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { type InspectOptions } from 'util'; - -/** - * {@link https://nodejs.org/api/util.html#utilinspectobject-showhidden-depth-colors util.inspect Options Documentation} - */ -export interface BushInspectOptions extends InspectOptions { - /** - * If `true`, object's non-enumerable symbols and properties are included in the - * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) - * and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries - * are also included as well as user defined prototype properties (excluding method properties). - * - * @default false - */ - showHidden?: boolean | undefined; - - /** - * Specifies the number of times to recurse while formatting `object`. This is useful - * for inspecting large objects. To recurse up to the maximum call stack size pass - * `Infinity` or `null`. - * - * @default 2 - */ - depth?: number | null | undefined; - - /** - * If `true`, the output is styled with ANSI color codes. Colors are customizable. See - * [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors). - * - * @default false - */ - colors?: boolean | undefined; - - /** - * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked. - * - * @default true - */ - customInspect?: boolean | undefined; - - /** - * If `true`, `Proxy` inspection includes the - * [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) - * objects. - * - * @default false - */ - showProxy?: boolean | undefined; - - /** - * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), - * [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and - * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to - * include when formatting. Set to `null` or `Infinity` to show all elements. - * Set to `0` or negative to show no elements. - * - * @default 100 - */ - maxArrayLength?: number | null | undefined; - - /** - * Specifies the maximum number of characters to include when formatting. Set to - * `null` or `Infinity` to show all elements. Set to `0` or negative to show no - * characters. - * - * @default 10000 - */ - maxStringLength?: number | null | undefined; - - /** - * The length at which input values are split across multiple lines. Set to - * `Infinity` to format the input as a single line (in combination with compact set - * to `true` or any number >= `1`). - * - * @default 80 - */ - breakLength?: number | undefined; - - /** - * Setting this to `false` causes each object key to be displayed on a new line. It - * will break on new lines in text that is longer than `breakLength`. If set to a - * number, the most `n` inner elements are united on a single line as long as all - * properties fit into `breakLength`. Short array elements are also grouped together. - * - * @default 3 - */ - compact?: boolean | number | undefined; - - /** - * If set to `true` or a function, all properties of an object, and `Set` and `Map` - * entries are sorted in the resulting string. If set to `true` the - * [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used. - * If set to a function, it is used as a - * [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters). - * - * @default false - */ - sorted?: boolean | ((a: string, b: string) => number) | undefined; - - /** - * If set to `true`, getters are inspected. If set to `'get'`, only getters without a - * corresponding setter are inspected. If set to `'set'`, only getters with a - * corresponding setter are inspected. This might cause side effects depending on - * the getter function. - * - * @default false - */ - getters?: 'get' | 'set' | boolean | undefined; - - /** - * If set to `true`, an underscore is used to separate every three digits in all bigints and numbers. - * - * @default false - */ - numericSeparator?: boolean; - - /** - * Whether or not to inspect strings. - * - * @default false - */ - inspectStrings?: boolean; -} diff --git a/lib/types/InspectOptions.ts b/lib/types/InspectOptions.ts new file mode 100644 index 0000000..1113f2b --- /dev/null +++ b/lib/types/InspectOptions.ts @@ -0,0 +1,127 @@ +import { InspectOptions } from 'node:util'; + +declare module 'util' { + /** + * {@link https://nodejs.org/api/util.html#utilinspectobject-showhidden-depth-colors util.inspect Options Documentation} + */ + export interface InspectOptions { + /** + * If `true`, object's non-enumerable symbols and properties are included in the + * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) + * and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries + * are also included as well as user defined prototype properties (excluding method properties). + * + * @default false + */ + showHidden?: boolean | undefined; + + /** + * Specifies the number of times to recurse while formatting `object`. This is useful + * for inspecting large objects. To recurse up to the maximum call stack size pass + * `Infinity` or `null`. + * + * @default 2 + */ + depth?: number | null | undefined; + + /** + * If `true`, the output is styled with ANSI color codes. Colors are customizable. See + * [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors). + * + * @default false + */ + colors?: boolean | undefined; + + /** + * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked. + * + * @default true + */ + customInspect?: boolean | undefined; + + /** + * If `true`, `Proxy` inspection includes the + * [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) + * objects. + * + * @default false + */ + showProxy?: boolean | undefined; + + /** + * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), + * [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and + * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to + * include when formatting. Set to `null` or `Infinity` to show all elements. + * Set to `0` or negative to show no elements. + * + * @default 100 + */ + maxArrayLength?: number | null | undefined; + + /** + * Specifies the maximum number of characters to include when formatting. Set to + * `null` or `Infinity` to show all elements. Set to `0` or negative to show no + * characters. + * + * @default 10000 + */ + maxStringLength?: number | null | undefined; + + /** + * The length at which input values are split across multiple lines. Set to + * `Infinity` to format the input as a single line (in combination with compact set + * to `true` or any number >= `1`). + * + * @default 80 + */ + breakLength?: number | undefined; + + /** + * Setting this to `false` causes each object key to be displayed on a new line. It + * will break on new lines in text that is longer than `breakLength`. If set to a + * number, the most `n` inner elements are united on a single line as long as all + * properties fit into `breakLength`. Short array elements are also grouped together. + * + * @default 3 + */ + compact?: boolean | number | undefined; + + /** + * If set to `true` or a function, all properties of an object, and `Set` and `Map` + * entries are sorted in the resulting string. If set to `true` the + * [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used. + * If set to a function, it is used as a + * [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters). + * + * @default false + */ + sorted?: boolean | ((a: string, b: string) => number) | undefined; + + /** + * If set to `true`, getters are inspected. If set to `'get'`, only getters without a + * corresponding setter are inspected. If set to `'set'`, only getters with a + * corresponding setter are inspected. This might cause side effects depending on + * the getter function. + * + * @default false + */ + getters?: 'get' | 'set' | boolean | undefined; + + /** + * If set to `true`, an underscore is used to separate every three digits in all bigints and numbers. + * + * @default false + */ + numericSeparator?: boolean; + } +} + +export interface CustomInspectOptions extends InspectOptions { + /** + * Whether or not to inspect strings. + * + * @default false + */ + inspectStrings?: boolean; +} diff --git a/lib/types/misc.ts b/lib/types/misc.ts new file mode 100644 index 0000000..5bf760c --- /dev/null +++ b/lib/types/misc.ts @@ -0,0 +1,14 @@ +import type { + InteractionReplyOptions, + MessageEditOptions, + MessageOptions, + MessagePayload, + ReplyMessageOptions, + WebhookEditMessageOptions +} from 'discord.js'; + +export type ReplyMessageType = string | MessagePayload | ReplyMessageOptions; +export type EditMessageType = string | MessageEditOptions | MessagePayload; +export type SlashSendMessageType = string | MessagePayload | InteractionReplyOptions; +export type SlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions; +export type SendMessageType = string | MessagePayload | MessageOptions; diff --git a/lib/utils/Arg.ts b/lib/utils/Arg.ts index d362225..99060fb 100644 --- a/lib/utils/Arg.ts +++ b/lib/utils/Arg.ts @@ -1,7 +1,7 @@ import { - type BaseBushArgumentType, - type BushArgumentType, - type BushArgumentTypeCaster, + type BaseBotArgumentType, + type BotArgumentType, + type BotArgumentTypeCaster, type CommandMessage, type SlashMessage } from '#lib'; @@ -160,33 +160,33 @@ export function withInput(type: AT | ATC): ATC { return Argument.withInput(type as any); } -type BushArgumentTypeCasterReturn = R extends BushArgumentTypeCaster ? S : R; +type CustomArgumentTypeCasterReturn = R extends BotArgumentTypeCaster ? S : R; /** ```ts - * = BushArgumentTypeCaster + * = CustomArgumentTypeCaster * ``` */ -type ATC = BushArgumentTypeCaster; +type ATC = BotArgumentTypeCaster; /** ```ts - * keyof BaseBushArgumentType + * keyof BaseCustomArgumentType * ``` */ -type KBAT = keyof BaseBushArgumentType; +type KBAT = keyof BaseBotArgumentType; /** ```ts - * = BushArgumentTypeCasterReturn + * = CustomArgumentTypeCasterReturn * ``` */ -type ATCR = BushArgumentTypeCasterReturn; +type ATCR = CustomArgumentTypeCasterReturn; /** ```ts - * BushArgumentType + * CustomArgumentType * ``` */ -type AT = BushArgumentType; +type AT = BotArgumentType; /** ```ts - * BaseBushArgumentType + * BaseCustomArgumentType * ``` */ -type BAT = BaseBushArgumentType; +type BAT = BaseBotArgumentType; /** ```ts - * = BushArgumentTypeCaster> + * = CustomArgumentTypeCaster> * ``` */ -type ATCATCR = BushArgumentTypeCaster>; +type ATCATCR = BotArgumentTypeCaster>; /** ```ts - * = BushArgumentTypeCaster + * = CustomArgumentTypeCaster * ``` */ -type ATCBAT = BushArgumentTypeCaster; +type ATCBAT = BotArgumentTypeCaster; diff --git a/lib/utils/BotClientUtils.ts b/lib/utils/BotClientUtils.ts new file mode 100644 index 0000000..a251bdf --- /dev/null +++ b/lib/utils/BotClientUtils.ts @@ -0,0 +1,496 @@ +import { ConfigChannelKey } from '#config'; +import type { CodeBlockLang, CustomInspectOptions } from '#lib'; +import { GlobalCache, SharedCache } from '#lib/common/BotCache.js'; +import { CommandMessage } from '#lib/extensions/discord-akairo/BotCommand.js'; +import { SlashMessage } from '#lib/extensions/discord-akairo/SlashMessage.js'; +import { Global, Shared } from '#lib/models/index.js'; +import assert from 'assert/strict'; +import { + cleanCodeBlockContent, + DMChannel, + escapeCodeBlock, + GuildMember, + Message, + PartialDMChannel, + Routes, + TextBasedChannel, + ThreadMember, + User, + type APIMessage, + type Client, + type Snowflake, + type UserResolvable +} from 'discord.js'; +import _ from 'lodash'; +import { emojis, Pronoun, PronounCode, pronounMapping, regex } from './Constants.js'; +import { generateErrorEmbed } from './ErrorHandler.js'; +import { addOrRemoveFromArray, formatError, inspect } from './Utils.js'; + +/** + * Utilities that require access to the client. + */ +export class BotClientUtils { + /** + * The hastebin urls used to post to hastebin, attempts to post in order + */ + #hasteURLs: string[] = [ + 'https://hst.sh', + // 'https://hasteb.in', + 'https://hastebin.com', + 'https://mystb.in', + 'https://haste.clicksminuteper.net', + 'https://paste.pythondiscord.com', + 'https://haste.unbelievaboat.com' + // 'https://haste.tyman.tech' + ]; + + public constructor(private readonly client: Client) {} + + /** + * Maps an array of user ids to user objects. + * @param ids The list of IDs to map + * @returns The list of users mapped + */ + public async mapIDs(ids: Snowflake[]): Promise { + return await Promise.all(ids.map((id) => this.client.users.fetch(id))); + } + + /** + * Posts text to hastebin + * @param content The text to post + * @returns The url of the posted text + */ + public async haste(content: string, substr = false): Promise { + let isSubstr = false; + if (content.length > 400_000 && !substr) { + void this.handleError('haste', new Error(`content over 400,000 characters (${content.length.toLocaleString()})`)); + return { error: 'content too long' }; + } else if (content.length > 400_000) { + content = content.substring(0, 400_000); + isSubstr = true; + } + for (const url of this.#hasteURLs) { + try { + const res = (await (await fetch(`${url}/documents`, { method: 'POST', body: content })).json()) as HastebinRes; + return { url: `${url}/${res.key}`, error: isSubstr ? 'substr' : undefined }; + } catch { + void this.client.console.error('haste', `Unable to upload haste to ${url}`); + } + } + return { error: 'unable to post' }; + } + + /** + * Resolves a user-provided string into a user object, if possible + * @param text The text to try and resolve + * @returns The user resolved or null + */ + public async resolveUserAsync(text: string): Promise { + const idReg = /\d{17,19}/; + const idMatch = text.match(idReg); + if (idMatch) { + try { + return await this.client.users.fetch(text as Snowflake); + } catch {} + } + const mentionReg = /<@!?(?\d{17,19})>/; + const mentionMatch = text.match(mentionReg); + if (mentionMatch) { + try { + return await this.client.users.fetch(mentionMatch.groups!.id as Snowflake); + } catch {} + } + const user = this.client.users.cache.find((u) => u.username === text); + if (user) return user; + return null; + } + + /** + * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long. + * * Embed Description Limit = 4096 characters + * * Embed Field Limit = 1024 characters + * @param code The content of the code block. + * @param length The maximum length of the code block. + * @param language The language of the code. + * @param substr Whether or not to substring the code if it is too long. + * @returns The generated code block + */ + public async codeblock(code: string, length: number, language: CodeBlockLang | '' = '', substr = false): Promise { + let hasteOut = ''; + code = escapeCodeBlock(code); + const prefix = `\`\`\`${language}\n`; + const suffix = '\n```'; + if (code.length + (prefix + suffix).length >= length) { + const haste_ = await this.haste(code, substr); + hasteOut = `Too large to display. ${ + haste_.url + ? `Hastebin: ${haste_.url}${language ? `.${language}` : ''}${haste_.error ? ` - ${haste_.error}` : ''}` + : `${emojis.error} Hastebin: ${haste_.error}` + }`; + } + + const FormattedHaste = hasteOut.length ? `\n${hasteOut}` : ''; + const shortenedCode = hasteOut ? code.substring(0, length - (prefix + FormattedHaste + suffix).length) : code; + const code3 = code.length ? prefix + shortenedCode + suffix + FormattedHaste : prefix + suffix; + if (code3.length > length) { + void this.client.console.warn(`codeblockError`, `Required Length: ${length}. Actual Length: ${code3.length}`, true); + void this.client.console.warn(`codeblockError`, code3, true); + throw new Error('code too long'); + } + return code3; + } + + /** + * Maps the key of a credential with a readable version when redacting. + * @param key The key of the credential. + * @returns The readable version of the key or the original key if there isn't a mapping. + */ + #mapCredential(key: string): string { + return ( + { + token: 'Main Token', + devToken: 'Dev Token', + betaToken: 'Beta Token', + hypixelApiKey: 'Hypixel Api Key', + wolframAlphaAppId: 'Wolfram|Alpha App ID', + dbPassword: 'Database Password' + }[key] ?? key + ); + } + + /** + * Redacts credentials from a string. + * @param text The text to redact credentials from. + * @returns The redacted text. + */ + public redact(text: string) { + for (const credentialName in { ...this.client.config.credentials, dbPassword: this.client.config.db.password }) { + const credential = { ...this.client.config.credentials, dbPassword: this.client.config.db.password }[ + credentialName as keyof typeof this.client.config.credentials + ]; + if (credential === null || credential === '') continue; + const replacement = this.#mapCredential(credentialName); + const escapeRegex = /[.*+?^${}()|[\]\\]/g; + text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`); + text = text.replace( + new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'), + `[${replacement} Omitted]` + ); + } + return text; + } + + /** + * Takes an any value, inspects it, redacts credentials, and puts it in a codeblock + * (and uploads to hast if the content is too long). + * @param input The object to be inspect, redacted, and put into a codeblock. + * @param language The language to make the codeblock. + * @param inspectOptions The options for {@link CustomClientUtil.inspect}. + * @param length The maximum length that the codeblock can be. + * @returns The generated codeblock. + */ + public async inspectCleanRedactCodeblock( + input: any, + language?: CodeBlockLang | '', + inspectOptions?: CustomInspectOptions, + length = 1024 + ) { + input = inspect(input, inspectOptions ?? undefined); + if (inspectOptions) inspectOptions.inspectStrings = undefined; + input = cleanCodeBlockContent(input); + input = this.redact(input); + return this.codeblock(input, length, language, true); + } + + /** + * Takes an any value, inspects it, redacts credentials, and uploads it to haste. + * @param input The object to be inspect, redacted, and upload. + * @param inspectOptions The options for {@link BotClientUtils.inspect}. + * @returns The {@link HasteResults}. + */ + public async inspectCleanRedactHaste(input: any, inspectOptions?: CustomInspectOptions): Promise { + input = inspect(input, inspectOptions ?? undefined); + input = this.redact(input); + return this.haste(input, true); + } + + /** + * Takes an any value, inspects it and redacts credentials. + * @param input The object to be inspect and redacted. + * @param inspectOptions The options for {@link BotClientUtils.inspect}. + * @returns The redacted and inspected object. + */ + public inspectAndRedact(input: any, inspectOptions?: CustomInspectOptions): string { + input = inspect(input, inspectOptions ?? undefined); + return this.redact(input); + } + + /** + * Get the global cache. + */ + public getGlobal(): GlobalCache; + /** + * Get a key from the global cache. + * @param key The key to get in the global cache. + */ + public getGlobal(key: K): GlobalCache[K]; + public getGlobal(key?: keyof GlobalCache) { + return key ? this.client.cache.global[key] : this.client.cache.global; + } + + /** + * Get the shared cache. + */ + public getShared(): SharedCache; + /** + * Get a key from the shared cache. + * @param key The key to get in the shared cache. + */ + public getShared(key: K): SharedCache[K]; + public getShared(key?: keyof SharedCache) { + return key ? this.client.cache.shared[key] : this.client.cache.shared; + } + + /** + * Add or remove an element from an array stored in the Globals database. + * @param action Either `add` or `remove` an element. + * @param key The key of the element in the global cache to update. + * @param value The value to add/remove from the array. + */ + public async insertOrRemoveFromGlobal( + action: 'add' | 'remove', + key: K, + value: Client['cache']['global'][K][0] + ): Promise { + const row = + (await Global.findByPk(this.client.config.environment)) ?? + (await Global.create({ environment: this.client.config.environment })); + const oldValue: any[] = row[key]; + const newValue = addOrRemoveFromArray(action, oldValue, value); + row[key] = newValue; + this.client.cache.global[key] = newValue; + return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e)); + } + + /** + * Add or remove an element from an array stored in the Shared database. + * @param action Either `add` or `remove` an element. + * @param key The key of the element in the shared cache to update. + * @param value The value to add/remove from the array. + */ + public async insertOrRemoveFromShared>( + action: 'add' | 'remove', + key: K, + value: Client['cache']['shared'][K][0] + ): Promise { + const row = (await Shared.findByPk(0)) ?? (await Shared.create()); + const oldValue: any[] = row[key]; + const newValue = addOrRemoveFromArray(action, oldValue, value); + row[key] = newValue; + this.client.cache.shared[key] = newValue; + return await row.save().catch((e) => this.handleError('insertOrRemoveFromShared', e)); + } + + /** + * Updates an element in the Globals database. + * @param key The key in the global cache to update. + * @param value The value to set the key to. + */ + public async setGlobal( + key: K, + value: Client['cache']['global'][K] + ): Promise { + const row = + (await Global.findByPk(this.client.config.environment)) ?? + (await Global.create({ environment: this.client.config.environment })); + row[key] = value; + this.client.cache.global[key] = value; + return await row.save().catch((e) => this.handleError('setGlobal', e)); + } + + /** + * Updates an element in the Shared database. + * @param key The key in the shared cache to update. + * @param value The value to set the key to. + */ + public async setShared>( + key: K, + value: Client['cache']['shared'][K] + ): Promise { + const row = (await Shared.findByPk(0)) ?? (await Shared.create()); + row[key] = value; + this.client.cache.shared[key] = value; + return await row.save().catch((e) => this.handleError('setShared', e)); + } + + /** + * Send a message in the error logging channel and console for an error. + * @param context + * @param error + */ + public async handleError(context: string, error: Error) { + await this.client.console.error(_.camelCase(context), `An error occurred:\n${formatError(error, false)}`, false); + await this.client.console.channelError({ + embeds: await generateErrorEmbed(this.client, { type: 'unhandledRejection', error: error, context }) + }); + } + + /** + * Fetches a user from discord. + * @param user The user to fetch + * @returns Undefined if the user is not found, otherwise the user. + */ + public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise { + if (user == null) return undefined; + const resolvedUser = + user instanceof User + ? user + : user instanceof GuildMember + ? user.user + : user instanceof ThreadMember + ? user.user + : user instanceof Message + ? user.author + : undefined; + + return resolvedUser ?? (await this.client.users.fetch(user as Snowflake).catch(() => undefined)); + } + + /** + * Get the pronouns of a discord user from pronoundb.org + * @param user The user to retrieve the promises of. + * @returns The human readable pronouns of the user, or undefined if they do not have any. + */ + public async getPronounsOf(user: User | Snowflake): Promise { + const _user = await this.resolveNonCachedUser(user); + if (!_user) throw new Error(`Cannot find user ${user}`); + const apiRes = (await fetch(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`) + .then((p) => (p.ok ? p.json() : undefined)) + .catch(() => undefined)) as { pronouns: PronounCode } | undefined; + + if (!apiRes) return undefined; + assert(apiRes.pronouns); + + return pronounMapping[apiRes.pronouns!]!; + } + + /** + * Uploads an image to imgur. + * @param image The image to upload. + * @returns The url of the imgur. + */ + public async uploadImageToImgur(image: string) { + const clientId = this.client.config.credentials.imgurClientId; + + const formData = new FormData(); + formData.append('type', 'base64'); + formData.append('image', image); + + const resp = (await fetch('https://api.imgur.com/3/upload', { + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: `Client-ID ${clientId}` + }, + body: formData, + redirect: 'follow' + }) + .then((p) => (p.ok ? p.json() : null)) + .catch(() => null)) as { data: { link: string } } | null; + + return resp?.data?.link ?? null; + } + + /** + * Gets the prefix based off of the message. + * @param message The message to get the prefix from. + * @returns The prefix. + */ + public prefix(message: CommandMessage | SlashMessage): string { + return message.util.isSlash + ? '/' + : this.client.config.isDevelopment + ? 'dev ' + : message.util.parsed?.prefix ?? this.client.config.prefix; + } + + public async resolveMessageLinks(content: string | null): Promise { + const res: MessageLinkParts[] = []; + + if (!content) return res; + + const regex_ = new RegExp(regex.messageLink); + let match: RegExpExecArray | null; + while (((match = regex_.exec(content)), match !== null)) { + const input = match.input; + if (!match.groups || !input) continue; + if (input.startsWith('<') && input.endsWith('>')) continue; + + const { guild_id, channel_id, message_id } = match.groups; + if (!guild_id || !channel_id || !message_id) continue; + + res.push({ guild_id, channel_id, message_id }); + } + + return res; + } + + public async resolveMessagesFromLinks(content: string): Promise { + const res: APIMessage[] = []; + + const links = await this.resolveMessageLinks(content); + if (!links.length) return []; + + for (const { guild_id, channel_id, message_id } of links) { + const guild = this.client.guilds.cache.get(guild_id); + if (!guild) continue; + const channel = guild.channels.cache.get(channel_id); + if (!channel || (!channel.isTextBased() && !channel.isThread())) continue; + + const message = (await this.client.rest + .get(Routes.channelMessage(channel_id, message_id)) + .catch(() => null)) as APIMessage | null; + if (!message) continue; + + res.push(message); + } + + return res; + } + + /** + * Resolves a channel from the config and ensures it is a non-dm-based-text-channel. + * @param channel The channel to retrieve. + */ + public async getConfigChannel( + channel: ConfigChannelKey + ): Promise | null> { + const channels = this.client.config.channels; + if (!(channel in channels)) + throw new TypeError(`Invalid channel provided (${channel}), must be one of ${Object.keys(channels).join(' ')}`); + + const channelId = channels[channel]; + if (channelId === '') return null; + + const res = await this.client.channels.fetch(channelId); + + if (!res?.isTextBased() || res.isDMBased()) return null; + + return res; + } +} + +interface HastebinRes { + key: string; +} + +export interface HasteResults { + url?: string; + error?: 'content too long' | 'substr' | 'unable to post'; +} + +export interface MessageLinkParts { + guild_id: Snowflake; + channel_id: Snowflake; + message_id: Snowflake; +} diff --git a/lib/utils/BushClientUtils.ts b/lib/utils/BushClientUtils.ts deleted file mode 100644 index e468cd7..0000000 --- a/lib/utils/BushClientUtils.ts +++ /dev/null @@ -1,496 +0,0 @@ -import { ConfigChannelKey } from '#config'; -import type { BushInspectOptions, CodeBlockLang } from '#lib'; -import { GlobalCache, SharedCache } from '#lib/common/BushCache.js'; -import { CommandMessage } from '#lib/extensions/discord-akairo/BushCommand.js'; -import { SlashMessage } from '#lib/extensions/discord-akairo/SlashMessage.js'; -import { Global, Shared } from '#lib/models/index.js'; -import assert from 'assert/strict'; -import { - cleanCodeBlockContent, - DMChannel, - escapeCodeBlock, - GuildMember, - Message, - PartialDMChannel, - Routes, - TextBasedChannel, - ThreadMember, - User, - type APIMessage, - type Client, - type Snowflake, - type UserResolvable -} from 'discord.js'; -import _ from 'lodash'; -import { emojis, Pronoun, PronounCode, pronounMapping, regex } from './BushConstants.js'; -import { addOrRemoveFromArray, formatError, inspect } from './BushUtils.js'; -import { generateErrorEmbed } from './ErrorHandler.js'; - -/** - * Utilities that require access to the client. - */ -export class BushClientUtils { - /** - * The hastebin urls used to post to hastebin, attempts to post in order - */ - #hasteURLs: string[] = [ - 'https://hst.sh', - // 'https://hasteb.in', - 'https://hastebin.com', - 'https://mystb.in', - 'https://haste.clicksminuteper.net', - 'https://paste.pythondiscord.com', - 'https://haste.unbelievaboat.com' - // 'https://haste.tyman.tech' - ]; - - public constructor(private readonly client: Client) {} - - /** - * Maps an array of user ids to user objects. - * @param ids The list of IDs to map - * @returns The list of users mapped - */ - public async mapIDs(ids: Snowflake[]): Promise { - return await Promise.all(ids.map((id) => this.client.users.fetch(id))); - } - - /** - * Posts text to hastebin - * @param content The text to post - * @returns The url of the posted text - */ - public async haste(content: string, substr = false): Promise { - let isSubstr = false; - if (content.length > 400_000 && !substr) { - void this.handleError('haste', new Error(`content over 400,000 characters (${content.length.toLocaleString()})`)); - return { error: 'content too long' }; - } else if (content.length > 400_000) { - content = content.substring(0, 400_000); - isSubstr = true; - } - for (const url of this.#hasteURLs) { - try { - const res = (await (await fetch(`${url}/documents`, { method: 'POST', body: content })).json()) as HastebinRes; - return { url: `${url}/${res.key}`, error: isSubstr ? 'substr' : undefined }; - } catch { - void this.client.console.error('haste', `Unable to upload haste to ${url}`); - } - } - return { error: 'unable to post' }; - } - - /** - * Resolves a user-provided string into a user object, if possible - * @param text The text to try and resolve - * @returns The user resolved or null - */ - public async resolveUserAsync(text: string): Promise { - const idReg = /\d{17,19}/; - const idMatch = text.match(idReg); - if (idMatch) { - try { - return await this.client.users.fetch(text as Snowflake); - } catch {} - } - const mentionReg = /<@!?(?\d{17,19})>/; - const mentionMatch = text.match(mentionReg); - if (mentionMatch) { - try { - return await this.client.users.fetch(mentionMatch.groups!.id as Snowflake); - } catch {} - } - const user = this.client.users.cache.find((u) => u.username === text); - if (user) return user; - return null; - } - - /** - * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long. - * * Embed Description Limit = 4096 characters - * * Embed Field Limit = 1024 characters - * @param code The content of the code block. - * @param length The maximum length of the code block. - * @param language The language of the code. - * @param substr Whether or not to substring the code if it is too long. - * @returns The generated code block - */ - public async codeblock(code: string, length: number, language: CodeBlockLang | '' = '', substr = false): Promise { - let hasteOut = ''; - code = escapeCodeBlock(code); - const prefix = `\`\`\`${language}\n`; - const suffix = '\n```'; - if (code.length + (prefix + suffix).length >= length) { - const haste_ = await this.haste(code, substr); - hasteOut = `Too large to display. ${ - haste_.url - ? `Hastebin: ${haste_.url}${language ? `.${language}` : ''}${haste_.error ? ` - ${haste_.error}` : ''}` - : `${emojis.error} Hastebin: ${haste_.error}` - }`; - } - - const FormattedHaste = hasteOut.length ? `\n${hasteOut}` : ''; - const shortenedCode = hasteOut ? code.substring(0, length - (prefix + FormattedHaste + suffix).length) : code; - const code3 = code.length ? prefix + shortenedCode + suffix + FormattedHaste : prefix + suffix; - if (code3.length > length) { - void this.client.console.warn(`codeblockError`, `Required Length: ${length}. Actual Length: ${code3.length}`, true); - void this.client.console.warn(`codeblockError`, code3, true); - throw new Error('code too long'); - } - return code3; - } - - /** - * Maps the key of a credential with a readable version when redacting. - * @param key The key of the credential. - * @returns The readable version of the key or the original key if there isn't a mapping. - */ - #mapCredential(key: string): string { - return ( - { - token: 'Main Token', - devToken: 'Dev Token', - betaToken: 'Beta Token', - hypixelApiKey: 'Hypixel Api Key', - wolframAlphaAppId: 'Wolfram|Alpha App ID', - dbPassword: 'Database Password' - }[key] ?? key - ); - } - - /** - * Redacts credentials from a string. - * @param text The text to redact credentials from. - * @returns The redacted text. - */ - public redact(text: string) { - for (const credentialName in { ...this.client.config.credentials, dbPassword: this.client.config.db.password }) { - const credential = { ...this.client.config.credentials, dbPassword: this.client.config.db.password }[ - credentialName as keyof typeof this.client.config.credentials - ]; - if (credential === null || credential === '') continue; - const replacement = this.#mapCredential(credentialName); - const escapeRegex = /[.*+?^${}()|[\]\\]/g; - text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`); - text = text.replace( - new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'), - `[${replacement} Omitted]` - ); - } - return text; - } - - /** - * Takes an any value, inspects it, redacts credentials, and puts it in a codeblock - * (and uploads to hast if the content is too long). - * @param input The object to be inspect, redacted, and put into a codeblock. - * @param language The language to make the codeblock. - * @param inspectOptions The options for {@link BushClientUtil.inspect}. - * @param length The maximum length that the codeblock can be. - * @returns The generated codeblock. - */ - public async inspectCleanRedactCodeblock( - input: any, - language?: CodeBlockLang | '', - inspectOptions?: BushInspectOptions, - length = 1024 - ) { - input = inspect(input, inspectOptions ?? undefined); - if (inspectOptions) inspectOptions.inspectStrings = undefined; - input = cleanCodeBlockContent(input); - input = this.redact(input); - return this.codeblock(input, length, language, true); - } - - /** - * Takes an any value, inspects it, redacts credentials, and uploads it to haste. - * @param input The object to be inspect, redacted, and upload. - * @param inspectOptions The options for {@link BushClientUtil.inspect}. - * @returns The {@link HasteResults}. - */ - public async inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions): Promise { - input = inspect(input, inspectOptions ?? undefined); - input = this.redact(input); - return this.haste(input, true); - } - - /** - * Takes an any value, inspects it and redacts credentials. - * @param input The object to be inspect and redacted. - * @param inspectOptions The options for {@link BushClientUtil.inspect}. - * @returns The redacted and inspected object. - */ - public inspectAndRedact(input: any, inspectOptions?: BushInspectOptions): string { - input = inspect(input, inspectOptions ?? undefined); - return this.redact(input); - } - - /** - * Get the global cache. - */ - public getGlobal(): GlobalCache; - /** - * Get a key from the global cache. - * @param key The key to get in the global cache. - */ - public getGlobal(key: K): GlobalCache[K]; - public getGlobal(key?: keyof GlobalCache) { - return key ? this.client.cache.global[key] : this.client.cache.global; - } - - /** - * Get the shared cache. - */ - public getShared(): SharedCache; - /** - * Get a key from the shared cache. - * @param key The key to get in the shared cache. - */ - public getShared(key: K): SharedCache[K]; - public getShared(key?: keyof SharedCache) { - return key ? this.client.cache.shared[key] : this.client.cache.shared; - } - - /** - * Add or remove an element from an array stored in the Globals database. - * @param action Either `add` or `remove` an element. - * @param key The key of the element in the global cache to update. - * @param value The value to add/remove from the array. - */ - public async insertOrRemoveFromGlobal( - action: 'add' | 'remove', - key: K, - value: Client['cache']['global'][K][0] - ): Promise { - const row = - (await Global.findByPk(this.client.config.environment)) ?? - (await Global.create({ environment: this.client.config.environment })); - const oldValue: any[] = row[key]; - const newValue = addOrRemoveFromArray(action, oldValue, value); - row[key] = newValue; - this.client.cache.global[key] = newValue; - return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e)); - } - - /** - * Add or remove an element from an array stored in the Shared database. - * @param action Either `add` or `remove` an element. - * @param key The key of the element in the shared cache to update. - * @param value The value to add/remove from the array. - */ - public async insertOrRemoveFromShared>( - action: 'add' | 'remove', - key: K, - value: Client['cache']['shared'][K][0] - ): Promise { - const row = (await Shared.findByPk(0)) ?? (await Shared.create()); - const oldValue: any[] = row[key]; - const newValue = addOrRemoveFromArray(action, oldValue, value); - row[key] = newValue; - this.client.cache.shared[key] = newValue; - return await row.save().catch((e) => this.handleError('insertOrRemoveFromShared', e)); - } - - /** - * Updates an element in the Globals database. - * @param key The key in the global cache to update. - * @param value The value to set the key to. - */ - public async setGlobal( - key: K, - value: Client['cache']['global'][K] - ): Promise { - const row = - (await Global.findByPk(this.client.config.environment)) ?? - (await Global.create({ environment: this.client.config.environment })); - row[key] = value; - this.client.cache.global[key] = value; - return await row.save().catch((e) => this.handleError('setGlobal', e)); - } - - /** - * Updates an element in the Shared database. - * @param key The key in the shared cache to update. - * @param value The value to set the key to. - */ - public async setShared>( - key: K, - value: Client['cache']['shared'][K] - ): Promise { - const row = (await Shared.findByPk(0)) ?? (await Shared.create()); - row[key] = value; - this.client.cache.shared[key] = value; - return await row.save().catch((e) => this.handleError('setShared', e)); - } - - /** - * Send a message in the error logging channel and console for an error. - * @param context - * @param error - */ - public async handleError(context: string, error: Error) { - await this.client.console.error(_.camelCase(context), `An error occurred:\n${formatError(error, false)}`, false); - await this.client.console.channelError({ - embeds: await generateErrorEmbed(this.client, { type: 'unhandledRejection', error: error, context }) - }); - } - - /** - * Fetches a user from discord. - * @param user The user to fetch - * @returns Undefined if the user is not found, otherwise the user. - */ - public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise { - if (user == null) return undefined; - const resolvedUser = - user instanceof User - ? user - : user instanceof GuildMember - ? user.user - : user instanceof ThreadMember - ? user.user - : user instanceof Message - ? user.author - : undefined; - - return resolvedUser ?? (await this.client.users.fetch(user as Snowflake).catch(() => undefined)); - } - - /** - * Get the pronouns of a discord user from pronoundb.org - * @param user The user to retrieve the promises of. - * @returns The human readable pronouns of the user, or undefined if they do not have any. - */ - public async getPronounsOf(user: User | Snowflake): Promise { - const _user = await this.resolveNonCachedUser(user); - if (!_user) throw new Error(`Cannot find user ${user}`); - const apiRes = (await fetch(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`) - .then((p) => (p.ok ? p.json() : undefined)) - .catch(() => undefined)) as { pronouns: PronounCode } | undefined; - - if (!apiRes) return undefined; - assert(apiRes.pronouns); - - return pronounMapping[apiRes.pronouns!]!; - } - - /** - * Uploads an image to imgur. - * @param image The image to upload. - * @returns The url of the imgur. - */ - public async uploadImageToImgur(image: string) { - const clientId = this.client.config.credentials.imgurClientId; - - const formData = new FormData(); - formData.append('type', 'base64'); - formData.append('image', image); - - const resp = (await fetch('https://api.imgur.com/3/upload', { - method: 'POST', - headers: { - Accept: 'application/json', - Authorization: `Client-ID ${clientId}` - }, - body: formData, - redirect: 'follow' - }) - .then((p) => (p.ok ? p.json() : null)) - .catch(() => null)) as { data: { link: string } } | null; - - return resp?.data?.link ?? null; - } - - /** - * Gets the prefix based off of the message. - * @param message The message to get the prefix from. - * @returns The prefix. - */ - public prefix(message: CommandMessage | SlashMessage): string { - return message.util.isSlash - ? '/' - : this.client.config.isDevelopment - ? 'dev ' - : message.util.parsed?.prefix ?? this.client.config.prefix; - } - - public async resolveMessageLinks(content: string | null): Promise { - const res: MessageLinkParts[] = []; - - if (!content) return res; - - const regex_ = new RegExp(regex.messageLink); - let match: RegExpExecArray | null; - while (((match = regex_.exec(content)), match !== null)) { - const input = match.input; - if (!match.groups || !input) continue; - if (input.startsWith('<') && input.endsWith('>')) continue; - - const { guild_id, channel_id, message_id } = match.groups; - if (!guild_id || !channel_id || !message_id) continue; - - res.push({ guild_id, channel_id, message_id }); - } - - return res; - } - - public async resolveMessagesFromLinks(content: string): Promise { - const res: APIMessage[] = []; - - const links = await this.resolveMessageLinks(content); - if (!links.length) return []; - - for (const { guild_id, channel_id, message_id } of links) { - const guild = this.client.guilds.cache.get(guild_id); - if (!guild) continue; - const channel = guild.channels.cache.get(channel_id); - if (!channel || (!channel.isTextBased() && !channel.isThread())) continue; - - const message = (await this.client.rest - .get(Routes.channelMessage(channel_id, message_id)) - .catch(() => null)) as APIMessage | null; - if (!message) continue; - - res.push(message); - } - - return res; - } - - /** - * Resolves a channel from the config and ensures it is a non-dm-based-text-channel. - * @param channel The channel to retrieve. - */ - public async getConfigChannel( - channel: ConfigChannelKey - ): Promise | null> { - const channels = this.client.config.channels; - if (!(channel in channels)) - throw new TypeError(`Invalid channel provided (${channel}), must be one of ${Object.keys(channels).join(' ')}`); - - const channelId = channels[channel]; - if (channelId === '') return null; - - const res = await this.client.channels.fetch(channelId); - - if (!res?.isTextBased() || res.isDMBased()) return null; - - return res; - } -} - -interface HastebinRes { - key: string; -} - -export interface HasteResults { - url?: string; - error?: 'content too long' | 'substr' | 'unable to post'; -} - -export interface MessageLinkParts { - guild_id: Snowflake; - channel_id: Snowflake; - message_id: Snowflake; -} diff --git a/lib/utils/BushConstants.ts b/lib/utils/BushConstants.ts deleted file mode 100644 index 8e4871b..0000000 --- a/lib/utils/BushConstants.ts +++ /dev/null @@ -1,554 +0,0 @@ -import { default as deepLock } from 'deep-lock'; -import { - ArgumentMatches as AkairoArgumentMatches, - ArgumentTypes as AkairoArgumentTypes, - BuiltInReasons, - CommandHandlerEvents as AkairoCommandHandlerEvents -} from 'discord-akairo/dist/src/util/Constants.js'; -import { Colors, GuildFeature, Snowflake } from 'discord.js'; - -const rawCapeUrl = 'https://raw.githubusercontent.com/NotEnoughUpdates/capes/master/'; - -/** - * Time units in milliseconds - */ -export const enum Time { - /** - * One millisecond (1 ms). - */ - Millisecond = 1, - - /** - * One second (1,000 ms). - */ - Second = Millisecond * 1000, - - /** - * One minute (60,000 ms). - */ - Minute = Second * 60, - - /** - * One hour (3,600,000 ms). - */ - Hour = Minute * 60, - - /** - * One day (86,400,000 ms). - */ - Day = Hour * 24, - - /** - * One week (604,800,000 ms). - */ - Week = Day * 7, - - /** - * One month (2,629,800,000 ms). - */ - Month = Day * 30.4375, // average of days in a month (including leap years) - - /** - * One year (31,557,600,000 ms). - */ - Year = Day * 365.25 // average with leap years -} - -export const emojis = Object.freeze({ - success: '<:success:837109864101707807>', - warn: '<:warn:848726900876247050>', - error: '<:error:837123021016924261>', - successFull: '<:success_full:850118767576088646>', - warnFull: '<:warn_full:850118767391539312>', - errorFull: '<:error_full:850118767295201350>', - mad: '<:mad:783046135392239626>', - join: '<:join:850198029809614858>', - leave: '<:leave:850198048205307919>', - loading: '', - offlineCircle: '<:offline:787550565382750239>', - dndCircle: '<:dnd:787550487633330176>', - idleCircle: '<:idle:787550520956551218>', - onlineCircle: '<:online:787550449435803658>', - cross: '<:cross:878319362539421777>', - check: '<:check:878320135297961995>' -} as const); - -export const emojisRaw = Object.freeze({ - success: '837109864101707807', - warn: '848726900876247050', - error: '837123021016924261', - successFull: '850118767576088646', - warnFull: '850118767391539312', - errorFull: '850118767295201350', - mad: '783046135392239626', - join: '850198029809614858', - leave: '850198048205307919', - loading: '853419254619963392', - offlineCircle: '787550565382750239', - dndCircle: '787550487633330176', - idleCircle: '787550520956551218', - onlineCircle: '787550449435803658', - cross: '878319362539421777', - check: '878320135297961995' -} as const); - -export const colors = Object.freeze({ - default: 0x1fd8f1, - error: 0xef4947, - warn: 0xfeba12, - success: 0x3bb681, - info: 0x3b78ff, - red: 0xff0000, - blue: 0x0055ff, - aqua: 0x00bbff, - purple: 0x8400ff, - blurple: 0x5440cd, - newBlurple: 0x5865f2, - pink: 0xff00e6, - green: 0x00ff1e, - darkGreen: 0x008f11, - gold: 0xb59400, - yellow: 0xffff00, - white: 0xffffff, - gray: 0xa6a6a6, - lightGray: 0xcfcfcf, - darkGray: 0x7a7a7a, - black: 0x000000, - orange: 0xe86100, - ...Colors -} as const); - -// Somewhat stolen from @Mzato0001 -export const timeUnits = deepLock({ - milliseconds: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im, - value: Time.Millisecond - }, - seconds: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:seconds?|secs?|s))/im, - value: Time.Second - }, - minutes: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:minutes?|mins?|m))/im, - value: Time.Minute - }, - hours: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:hours?|hrs?|h))/im, - value: Time.Hour - }, - days: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:days?|d))/im, - value: Time.Day - }, - weeks: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:weeks?|w))/im, - value: Time.Week - }, - months: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:months?|mon|mo))/im, - value: Time.Month - }, - years: { - match: / (?:(?-?(?:\d+)?\.?\d+) *(?:years?|y))/im, - value: Time.Year - } -} as const); - -export const regex = deepLock({ - snowflake: /^\d{15,21}$/im, - - discordEmoji: /[a-zA-Z0-9_]+):(?\d{15,21})>/im, - - /* - * Taken with permission from Geek: - * https://github.com/FireDiscordBot/bot/blob/5d1990e5f8b52fcc72261d786aa3c7c7c65ab5e8/lib/util/constants.ts#L276 - */ - /** **This has the global flag, make sure to handle it correctly.** */ - messageLink: - /\d{15,21})\/(?\d{15,21})\/(?\d{15,21})>?/gim -} as const); - -/** - * Maps the response from pronoundb.org to a readable format - */ -export const pronounMapping = Object.freeze({ - unspecified: 'Unspecified', - hh: 'He/Him', - hi: 'He/It', - hs: 'He/She', - ht: 'He/They', - ih: 'It/Him', - ii: 'It/Its', - is: 'It/She', - it: 'It/They', - shh: 'She/He', - sh: 'She/Her', - si: 'She/It', - st: 'She/They', - th: 'They/He', - ti: 'They/It', - ts: 'They/She', - tt: 'They/Them', - any: 'Any pronouns', - other: 'Other pronouns', - ask: 'Ask me my pronouns', - avoid: 'Avoid pronouns, use my name' -} as const); - -/** - * A bunch of mappings - */ -export const mappings = deepLock({ - guilds: { - "Moulberry's Bush": '516977525906341928', - "Moulberry's Tree": '767448775450820639', - 'MB Staff': '784597260465995796', - "IRONM00N's Space Ship": '717176538717749358' - }, - - channels: { - 'neu-support': '714332750156660756', - 'giveaways': '767782084981817344' - }, - - users: { - IRONM00N: '322862723090219008', - Moulberry: '211288288055525376', - nopo: '384620942577369088', - Bestower: '496409778822709251' - }, - - permissions: { - CreateInstantInvite: { name: 'Create Invite', important: false }, - KickMembers: { name: 'Kick Members', important: true }, - BanMembers: { name: 'Ban Members', important: true }, - Administrator: { name: 'Administrator', important: true }, - ManageChannels: { name: 'Manage Channels', important: true }, - ManageGuild: { name: 'Manage Server', important: true }, - AddReactions: { name: 'Add Reactions', important: false }, - ViewAuditLog: { name: 'View Audit Log', important: true }, - PrioritySpeaker: { name: 'Priority Speaker', important: true }, - Stream: { name: 'Video', important: false }, - ViewChannel: { name: 'View Channel', important: false }, - SendMessages: { name: 'Send Messages', important: false }, - SendTTSMessages: { name: 'Send Text-to-Speech Messages', important: true }, - ManageMessages: { name: 'Manage Messages', important: true }, - EmbedLinks: { name: 'Embed Links', important: false }, - AttachFiles: { name: 'Attach Files', important: false }, - ReadMessageHistory: { name: 'Read Message History', important: false }, - MentionEveryone: { name: 'Mention @\u200Beveryone, @\u200Bhere, and All Roles', important: true }, // name has a zero-width space to prevent accidents - UseExternalEmojis: { name: 'Use External Emoji', important: false }, - ViewGuildInsights: { name: 'View Server Insights', important: true }, - Connect: { name: 'Connect', important: false }, - Speak: { name: 'Speak', important: false }, - MuteMembers: { name: 'Mute Members', important: true }, - DeafenMembers: { name: 'Deafen Members', important: true }, - MoveMembers: { name: 'Move Members', important: true }, - UseVAD: { name: 'Use Voice Activity', important: false }, - ChangeNickname: { name: 'Change Nickname', important: false }, - ManageNicknames: { name: 'Change Nicknames', important: true }, - ManageRoles: { name: 'Manage Roles', important: true }, - ManageWebhooks: { name: 'Manage Webhooks', important: true }, - ManageEmojisAndStickers: { name: 'Manage Emojis and Stickers', important: true }, - UseApplicationCommands: { name: 'Use Slash Commands', important: false }, - RequestToSpeak: { name: 'Request to Speak', important: false }, - ManageEvents: { name: 'Manage Events', important: true }, - ManageThreads: { name: 'Manage Threads', important: true }, - CreatePublicThreads: { name: 'Create Public Threads', important: false }, - CreatePrivateThreads: { name: 'Create Private Threads', important: false }, - UseExternalStickers: { name: 'Use External Stickers', important: false }, - SendMessagesInThreads: { name: 'Send Messages In Threads', important: false }, - StartEmbeddedActivities: { name: 'Start Activities', important: false }, - ModerateMembers: { name: 'Timeout Members', important: true }, - UseEmbeddedActivities: { name: 'Use Activities', important: false } - }, - - // prettier-ignore - features: { - [GuildFeature.Verified]: { name: 'Verified', important: true, emoji: '<:verified:850795049817473066>', weight: 0 }, - [GuildFeature.Partnered]: { name: 'Partnered', important: true, emoji: '<:partneredServer:850794851955507240>', weight: 1 }, - [GuildFeature.MoreStickers]: { name: 'More Stickers', important: true, emoji: null, weight: 2 }, - MORE_EMOJIS: { name: 'More Emoji', important: true, emoji: '<:moreEmoji:850786853497602080>', weight: 3 }, - [GuildFeature.Featurable]: { name: 'Featurable', important: true, emoji: '<:featurable:850786776372084756>', weight: 4 }, - [GuildFeature.RelayEnabled]: { name: 'Relay Enabled', important: true, emoji: '<:relayEnabled:850790531441229834>', weight: 5 }, - [GuildFeature.Discoverable]: { name: 'Discoverable', important: true, emoji: '<:discoverable:850786735360966656>', weight: 6 }, - ENABLED_DISCOVERABLE_BEFORE: { name: 'Enabled Discovery Before', important: false, emoji: '<:enabledDiscoverableBefore:850786754670624828>', weight: 7 }, - [GuildFeature.MonetizationEnabled]: { name: 'Monetization Enabled', important: true, emoji: null, weight: 8 }, - [GuildFeature.TicketedEventsEnabled]: { name: 'Ticketed Events Enabled', important: true, emoji: null, weight: 9 }, - [GuildFeature.PreviewEnabled]: { name: 'Preview Enabled', important: true, emoji: '<:previewEnabled:850790508266913823>', weight: 10 }, - COMMERCE: { name: 'Store Channels', important: true, emoji: '<:storeChannels:850786692432396338>', weight: 11 }, - [GuildFeature.VanityURL]: { name: 'Vanity URL', important: false, emoji: '<:vanityURL:850790553079644160>', weight: 12 }, - [GuildFeature.VIPRegions]: { name: 'VIP Regions', important: false, emoji: '<:VIPRegions:850794697496854538>', weight: 13 }, - [GuildFeature.AnimatedIcon]: { name: 'Animated Icon', important: false, emoji: '<:animatedIcon:850774498071412746>', weight: 14 }, - [GuildFeature.Banner]: { name: 'Banner', important: false, emoji: '<:banner:850786673150787614>', weight: 15 }, - [GuildFeature.InviteSplash]: { name: 'Invite Splash', important: false, emoji: '<:inviteSplash:850786798246559754>', weight: 16 }, - [GuildFeature.PrivateThreads]: { name: 'Private Threads', important: false, emoji: '<:privateThreads:869763711894700093>', weight: 17 }, - THREE_DAY_THREAD_ARCHIVE: { name: 'Three Day Thread Archive', important: false, emoji: '<:threeDayThreadArchive:869767841652564008>', weight: 19 }, - SEVEN_DAY_THREAD_ARCHIVE: { name: 'Seven Day Thread Archive', important: false, emoji: '<:sevenDayThreadArchive:869767896123998288>', weight: 20 }, - [GuildFeature.RoleIcons]: { name: 'Role Icons', important: false, emoji: '<:roleIcons:876993381929222175>', weight: 21 }, - [GuildFeature.News]: { name: 'Announcement Channels', important: false, emoji: '<:announcementChannels:850790491796013067>', weight: 22 }, - [GuildFeature.MemberVerificationGateEnabled]: { name: 'Membership Verification Gate', important: false, emoji: '<:memberVerificationGateEnabled:850786829984858212>', weight: 23 }, - [GuildFeature.WelcomeScreenEnabled]: { name: 'Welcome Screen Enabled', important: false, emoji: '<:welcomeScreenEnabled:850790575875817504>', weight: 24 }, - [GuildFeature.Community]: { name: 'Community', important: false, emoji: '<:community:850786714271875094>', weight: 25 }, - THREADS_ENABLED: {name: 'Threads Enabled', important: false, emoji: '<:threadsEnabled:869756035345317919>', weight: 26 }, - THREADS_ENABLED_TESTING: {name: 'Threads Enabled Testing', important: false, emoji: null, weight: 27 }, - [GuildFeature.AnimatedBanner]: { name: 'Animated Banner', important: false, emoji: '<:animatedBanner:1010580022018449482>', weight: 28 }, - [GuildFeature.HasDirectoryEntry]: { name: 'Has Directory Entry', important: true, emoji: null, weight: 29 }, - [GuildFeature.Hub]: { name: 'Hub', important: true, emoji: null, weight: 30 }, - [GuildFeature.LinkedToHub]: { name: 'Linked To Hub', important: true, emoji: null, weight: 31 }, - TEXT_IN_VOICE_ENABLED: { name: 'Text In Voice Enabled', important: false, emoji: '<:textInVoiceEnabled:1010578210150424617>', weight: 32 }, - AUTO_MODERATION: { name: 'Auto Moderation', important: false, emoji: '<:autoModeration:1010579417942200321>', weight: 33 }, - MEMBER_PROFILES: { name: 'Member Profiles', important: false, emoji: '<:memberProfiles:1010580480409747547>', weight: 34 }, - NEW_THREAD_PERMISSIONS: { name: 'New Thread Permissions', important: false, emoji: '<:newThreadPermissions:1010580968442171492>', weight: 35 }, - }, - - regions: { - 'automatic': ':united_nations: Automatic', - 'brazil': ':flag_br: Brazil', - 'europe': ':flag_eu: Europe', - 'hongkong': ':flag_hk: Hongkong', - 'india': ':flag_in: India', - 'japan': ':flag_jp: Japan', - 'russia': ':flag_ru: Russia', - 'singapore': ':flag_sg: Singapore', - 'southafrica': ':flag_za: South Africa', - 'sydney': ':flag_au: Sydney', - 'us-central': ':flag_us: US Central', - 'us-east': ':flag_us: US East', - 'us-south': ':flag_us: US South', - 'us-west': ':flag_us: US West' - }, - - otherEmojis: { - ServerBooster1: '<:serverBooster1:848740052091142145>', - ServerBooster2: '<:serverBooster2:848740090506510388>', - ServerBooster3: '<:serverBooster3:848740124992077835>', - ServerBooster6: '<:serverBooster6:848740155245461514>', - ServerBooster9: '<:serverBooster9:848740188846030889>', - ServerBooster12: '<:serverBooster12:848740304365551668>', - ServerBooster15: '<:serverBooster15:848740354890137680>', - ServerBooster18: '<:serverBooster18:848740402886606868>', - ServerBooster24: '<:serverBooster24:848740444628320256>', - Nitro: '<:nitro:848740498054971432>', - Booster: '<:booster:848747775020892200>', - Owner: '<:owner:848746439311753286>', - Admin: '<:admin:848963914628333598>', - Superuser: '<:superUser:848947986326224926>', - Developer: '<:developer:848954538111139871>', - Bot: '<:bot:1006929813203853427>', - BushVerified: '<:verfied:853360152090771497>', - BoostTier1: '<:boostitle:853363736679940127>', - BoostTier2: '<:boostitle:853363752728789075>', - BoostTier3: '<:boostitle:853363769132056627>', - ChannelText: '<:text:853375537791893524>', - ChannelNews: '<:announcements:853375553531674644>', - ChannelVoice: '<:voice:853375566735212584>', - ChannelStage: '<:stage:853375583521210468>', - // ChannelStore: '<:store:853375601175691266>', - ChannelCategory: '<:category:853375615260819476>', - ChannelThread: '<:thread:865033845753249813>' - }, - - userFlags: { - Staff: '<:discordEmployee:848742947826434079>', - Partner: '<:partneredServerOwner:848743051593777152>', - Hypesquad: '<:hypeSquadEvents:848743108283072553>', - BugHunterLevel1: '<:bugHunter:848743239850393640>', - HypeSquadOnlineHouse1: '<:hypeSquadBravery:848742910563844127>', - HypeSquadOnlineHouse2: '<:hypeSquadBrilliance:848742840649646101>', - HypeSquadOnlineHouse3: '<:hypeSquadBalance:848742877537370133>', - PremiumEarlySupporter: '<:earlySupporter:848741030102171648>', - TeamPseudoUser: 'TeamPseudoUser', - BugHunterLevel2: '<:bugHunterGold:848743283080822794>', - VerifiedBot: '<:verifiedbot1:938928232667947028><:verifiedbot2:938928355707879475>', - VerifiedDeveloper: '<:earlyVerifiedBotDeveloper:848741079875846174>', - CertifiedModerator: '<:discordCertifiedModerator:877224285901582366>', - BotHTTPInteractions: 'BotHTTPInteractions', - Spammer: 'Spammer', - Quarantined: 'Quarantined' - }, - - status: { - online: '<:online:848937141639577690>', - idle: '<:idle:848937158261211146>', - dnd: '<:dnd:848937173780135986>', - offline: '<:offline:848939387277672448>', - streaming: '<:streaming:848937187479519242>' - }, - - maybeNitroDiscrims: ['1111', '2222', '3333', '4444', '5555', '6666', '6969', '7777', '8888', '9999'], - - capes: [ - /* supporter capes */ - { name: 'patreon1', purchasable: false /* moulberry no longer offers */ }, - { name: 'patreon2', purchasable: false /* moulberry no longer offers */ }, - { name: 'fade', custom: `${rawCapeUrl}fade.gif`, purchasable: true }, - { name: 'lava', custom: `${rawCapeUrl}lava.gif`, purchasable: true }, - { name: 'mcworld', custom: `${rawCapeUrl}mcworld_compressed.gif`, purchasable: true }, - { name: 'negative', custom: `${rawCapeUrl}negative_compressed.gif`, purchasable: true }, - { name: 'space', custom: `${rawCapeUrl}space_compressed.gif`, purchasable: true }, - { name: 'void', custom: `${rawCapeUrl}void.gif`, purchasable: true }, - { name: 'tunnel', custom: `${rawCapeUrl}tunnel.gif`, purchasable: true }, - /* Staff capes */ - { name: 'contrib' }, - { name: 'mbstaff' }, - { name: 'ironmoon' }, - { name: 'gravy' }, - { name: 'nullzee' }, - /* partner capes */ - { name: 'thebakery' }, - { name: 'dsm' }, - { name: 'packshq' }, - { name: 'furf' }, - { name: 'skytils' }, - { name: 'sbp' }, - { name: 'subreddit_light' }, - { name: 'subreddit_dark' }, - { name: 'skyclient' }, - { name: 'sharex' }, - { name: 'sharex_white' }, - /* streamer capes */ - { name: 'alexxoffi' }, - { name: 'jakethybro' }, - { name: 'krusty' }, - { name: 'krusty_day' }, - { name: 'krusty_night' }, - { name: 'krusty_sunset' }, - { name: 'soldier' }, - { name: 'zera' }, - { name: 'secondpfirsisch' }, - { name: 'stormy_lh' } - ].map((value, index) => ({ ...value, index })), - - roleMap: [ - { name: '*', id: '792453550768390194' }, - { name: 'Admin Perms', id: '746541309853958186' }, - { name: 'Sr. Moderator', id: '782803470205190164' }, - { name: 'Moderator', id: '737308259823910992' }, - { name: 'Helper', id: '737440116230062091' }, - { name: 'Trial Helper', id: '783537091946479636' }, - { name: 'Contributor', id: '694431057532944425' }, - { name: 'Giveaway Donor', id: '784212110263451649' }, - { name: 'Giveaway (200m)', id: '810267756426690601' }, - { name: 'Giveaway (100m)', id: '801444430522613802' }, - { name: 'Giveaway (50m)', id: '787497512981757982' }, - { name: 'Giveaway (25m)', id: '787497515771232267' }, - { name: 'Giveaway (10m)', id: '787497518241153025' }, - { name: 'Giveaway (5m)', id: '787497519768403989' }, - { name: 'Giveaway (1m)', id: '787497521084891166' }, - { name: 'Suggester', id: '811922322767609877' }, - { name: 'Partner', id: '767324547312779274' }, - { name: 'Level Locked', id: '784248899044769792' }, - { name: 'No Files', id: '786421005039173633' }, - { name: 'No Reactions', id: '786421270924361789' }, - { name: 'No Links', id: '786421269356740658' }, - { name: 'No Bots', id: '786804858765312030' }, - { name: 'No VC', id: '788850482554208267' }, - { name: 'No Giveaways', id: '808265422334984203' }, - { name: 'No Support', id: '790247359824396319' } - ], - - roleWhitelist: { - 'Partner': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Suggester': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper', 'Trial Helper', 'Contributor'], - 'Level Locked': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'No Files': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'No Reactions': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'No Links': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'No Bots': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'No VC': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'No Giveaways': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper'], - 'No Support': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway Donor': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (200m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (100m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (50m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (25m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (10m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (5m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], - 'Giveaway (1m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'] - } -} as const); - -export const bots: Record = { - // MEE6#4876 - '159985870458322944': { - applicationId: '159985415099514880' - }, - // Dyno - '155149108183695360': { - applicationId: '161660517914509312' - }, - // Tatsu#8792 - '172002275412279296': { - applicationId: '172002255350792192' - }, - // YAGPDB.xyz#8760 - '204255221017214977': { - applicationId: '204255083083333633' - } -}; - -export const ArgumentMatches = Object.freeze({ - ...AkairoArgumentMatches -} as const); - -export const ArgumentTypes = Object.freeze({ - ...AkairoArgumentTypes, - DURATION: 'duration', - CONTENT_WITH_DURATION: 'contentWithDuration', - PERMISSION: 'permission', - SNOWFLAKE: 'snowflake', - DISCORD_EMOJI: 'discordEmoji', - ROLE_WITH_DURATION: 'roleWithDuration', - ABBREVIATED_NUMBER: 'abbreviatedNumber', - GLOBAL_USER: 'globalUser' -} as const); - -export const BlockedReasons = Object.freeze({ - ...BuiltInReasons, - DISABLED_GUILD: 'disabledGuild', - DISABLED_GLOBAL: 'disabledGlobal', - ROLE_BLACKLIST: 'roleBlacklist', - USER_GUILD_BLACKLIST: 'userGuildBlacklist', - USER_GLOBAL_BLACKLIST: 'userGlobalBlacklist', - RESTRICTED_GUILD: 'restrictedGuild', - CHANNEL_GUILD_BLACKLIST: 'channelGuildBlacklist', - CHANNEL_GLOBAL_BLACKLIST: 'channelGlobalBlacklist', - RESTRICTED_CHANNEL: 'restrictedChannel' -} as const); - -export const CommandHandlerEvents = Object.freeze({ - ...AkairoCommandHandlerEvents -} as const); - -export const moulberryBushRoleMap = deepLock([ - { name: '*', id: '792453550768390194' }, - { name: 'Admin Perms', id: '746541309853958186' }, - { name: 'Sr. Moderator', id: '782803470205190164' }, - { name: 'Moderator', id: '737308259823910992' }, - { name: 'Helper', id: '737440116230062091' }, - { name: 'Trial Helper', id: '783537091946479636' }, - { name: 'Contributor', id: '694431057532944425' }, - { name: 'Giveaway Donor', id: '784212110263451649' }, - { name: 'Giveaway (200m)', id: '810267756426690601' }, - { name: 'Giveaway (100m)', id: '801444430522613802' }, - { name: 'Giveaway (50m)', id: '787497512981757982' }, - { name: 'Giveaway (25m)', id: '787497515771232267' }, - { name: 'Giveaway (10m)', id: '787497518241153025' }, - { name: 'Giveaway (5m)', id: '787497519768403989' }, - { name: 'Giveaway (1m)', id: '787497521084891166' }, - { name: 'Suggester', id: '811922322767609877' }, - { name: 'Partner', id: '767324547312779274' }, - { name: 'Level Locked', id: '784248899044769792' }, - { name: 'No Files', id: '786421005039173633' }, - { name: 'No Reactions', id: '786421270924361789' }, - { name: 'No Links', id: '786421269356740658' }, - { name: 'No Bots', id: '786804858765312030' }, - { name: 'No VC', id: '788850482554208267' }, - { name: 'No Giveaways', id: '808265422334984203' }, - { name: 'No Support', id: '790247359824396319' } -] as const); - -export type PronounCode = keyof typeof pronounMapping; -export type Pronoun = typeof pronounMapping[PronounCode]; diff --git a/lib/utils/BushLogger.ts b/lib/utils/BushLogger.ts deleted file mode 100644 index f575b50..0000000 --- a/lib/utils/BushLogger.ts +++ /dev/null @@ -1,315 +0,0 @@ -import chalk from 'chalk'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { type SendMessageType } from '#lib/extensions/discord-akairo/BushClient.js'; -import { bold, Client, EmbedBuilder, escapeMarkdown, PartialTextBasedChannelFields, type Message } from 'discord.js'; -import { stripVTControlCharacters as stripColor } from 'node:util'; -import repl, { REPLServer, REPL_MODE_STRICT } from 'repl'; -import { WriteStream } from 'tty'; -import { colors } from './BushConstants.js'; -import { inspect } from './BushUtils.js'; - -let REPL: REPLServer; -let replGone = false; - -export function init() { - const kFormatForStdout = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStdout)')!; - const kFormatForStderr = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStderr)')!; - - REPL = repl.start({ - useColors: true, - terminal: true, - useGlobal: true, - replMode: REPL_MODE_STRICT, - breakEvalOnSigint: true, - ignoreUndefined: true - }); - - const apply = (stream: WriteStream, symbol: symbol): ProxyHandler['apply'] => - function apply(target, thisArg, args) { - if (stream.isTTY) { - stream.moveCursor(0, -1); - stream.write('\n'); - stream.clearLine(0); - } - - const ret = target(...args); - - if (stream.isTTY) { - const formatted = (console as any)[symbol](args) as string; - - stream.moveCursor(0, formatted.split('\n').length); - if (!replGone) { - REPL.displayPrompt(true); - } - } - - return ret; - }; - - global.console.log = new Proxy(console.log, { - apply: apply(process.stdout, kFormatForStdout) - }); - - global.console.warn = new Proxy(console.warn, { - apply: apply(process.stderr, kFormatForStderr) - }); - - REPL.on('exit', () => { - replGone = true; - process.exit(0); - }); -} - -/** - * Parses the content surrounding by `<<>>` and emphasizes it with the given color or by making it bold. - * @param content The content to parse. - * @param color The color to emphasize the content with. - * @param discordFormat Whether or not to format the content for discord. - * @returns The formatted content. - */ -function parseFormatting( - content: any, - color: 'blueBright' | 'blackBright' | 'redBright' | 'yellowBright' | 'greenBright' | '', - discordFormat = false -): string | typeof content { - if (typeof content !== 'string') return content; - return content - .split(/<<|>>/) - .map((value, index) => { - if (discordFormat) { - return index % 2 === 0 ? escapeMarkdown(value) : bold(escapeMarkdown(value)); - } else { - return index % 2 === 0 || !color ? value : chalk[color](value); - } - }) - .join(''); -} - -/** - * Inspects the content and returns a string. - * @param content The content to inspect. - * @param depth The depth the content will inspected. Defaults to `2`. - * @param colors Whether or not to use colors in the output. Defaults to `true`. - * @returns The inspected content. - */ -function inspectContent(content: any, depth = 2, colors = true): string { - if (typeof content !== 'string') { - return inspect(content, { depth, colors }); - } - return content; -} - -/** - * Generates a formatted timestamp for logging. - * @returns The formatted timestamp. - */ -function getTimeStamp(): string { - const now = new Date(); - const minute = pad(now.getMinutes()); - const hour = pad(now.getHours()); - const date = `${pad(now.getMonth() + 1)}/${pad(now.getDate())}`; - return `${date} ${hour}:${minute}`; -} - -/** - * Pad a two-digit number. - */ -function pad(num: number) { - return num.toString().padStart(2, '0'); -} - -/** - * Custom logging utility for the bot. - */ -export class BushLogger { - /** - * @param client The client. - */ - public constructor(public client: Client) {} - - /** - * Logs information. Highlight information by surrounding it in `<<>>`. - * @param header The header displayed before the content, displayed in cyan. - * @param content The content to log, highlights displayed in bright blue. - * @param sendChannel Should this also be logged to discord? Defaults to false. - * @param depth The depth the content will inspected. Defaults to 0. - */ - public get log() { - return this.info; - } - - /** - * Sends a message to the log channel. - * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}. - * @returns The message sent. - */ - public async channelLog(message: SendMessageType): Promise { - const channel = await this.client.utils.getConfigChannel('log'); - if (channel === null) return null; - return await channel.send(message).catch(() => null); - } - - /** - * Sends a message to the error channel. - * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}. - * @returns The message sent. - */ - public async channelError(message: SendMessageType): Promise { - const channel = await this.client.utils.getConfigChannel('error'); - if (!channel) { - void this.error( - 'BushLogger', - `Could not find error channel, was originally going to send: \n${inspect(message, { - colors: true - })}\n${new Error().stack?.substring(8)}`, - false - ); - return null; - } - return await channel.send(message); - } - - /** - * Logs debug information. Only works in dev is enabled in the config. - * @param content The content to log. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public debug(content: any, depth = 0): void { - if (!this.client.config.isDevelopment) return; - const newContent = inspectContent(content, depth, true); - console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')} ${newContent}`); - } - - /** - * Logs raw debug information. Only works in dev is enabled in the config. - * @param content The content to log. - */ - public debugRaw(...content: any): void { - if (!this.client.config.isDevelopment) return; - console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')}`, ...content); - } - - /** - * Logs verbose information. Highlight information by surrounding it in `<<>>`. - * @param header The header printed before the content, displayed in grey. - * @param content The content to log, highlights displayed in bright black. - * @param sendChannel Should this also be logged to discord? Defaults to `false`. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public async verbose(header: string, content: any, sendChannel = false, depth = 0): Promise { - if (!this.client.config.logging.verbose) return; - const newContent = inspectContent(content, depth, true); - console.log(`${chalk.bgGrey(getTimeStamp())} ${chalk.grey(`[${header}]`)} ${parseFormatting(newContent, 'blackBright')}`); - if (!sendChannel) return; - const embed = new EmbedBuilder() - .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) - .setColor(colors.gray) - .setTimestamp(); - await this.channelLog({ embeds: [embed] }); - } - - /** - * Logs very verbose information. Highlight information by surrounding it in `<<>>`. - * @param header The header printed before the content, displayed in purple. - * @param content The content to log, highlights displayed in bright black. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public async superVerbose(header: string, content: any, depth = 0): Promise { - if (!this.client.config.logging.verbose) return; - const newContent = inspectContent(content, depth, true); - console.log( - `${chalk.bgHex('#949494')(getTimeStamp())} ${chalk.hex('#949494')(`[${header}]`)} ${chalk.hex('#b3b3b3')(newContent)}` - ); - } - - /** - * Logs raw very verbose information. - * @param header The header printed before the content, displayed in purple. - * @param content The content to log. - */ - public async superVerboseRaw(header: string, ...content: any[]): Promise { - if (!this.client.config.logging.verbose) return; - console.log(`${chalk.bgHex('#a3a3a3')(getTimeStamp())} ${chalk.hex('#a3a3a3')(`[${header}]`)}`, ...content); - } - - /** - * Logs information. Highlight information by surrounding it in `<<>>`. - * @param header The header displayed before the content, displayed in cyan. - * @param content The content to log, highlights displayed in bright blue. - * @param sendChannel Should this also be logged to discord? Defaults to `false`. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public async info(header: string, content: any, sendChannel = true, depth = 0): Promise { - if (!this.client.config.logging.info) return; - const newContent = inspectContent(content, depth, true); - console.log(`${chalk.bgCyan(getTimeStamp())} ${chalk.cyan(`[${header}]`)} ${parseFormatting(newContent, 'blueBright')}`); - if (!sendChannel) return; - const embed = new EmbedBuilder() - .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) - .setColor(colors.info) - .setTimestamp(); - await this.channelLog({ embeds: [embed] }); - } - - /** - * Logs warnings. Highlight information by surrounding it in `<<>>`. - * @param header The header displayed before the content, displayed in yellow. - * @param content The content to log, highlights displayed in bright yellow. - * @param sendChannel Should this also be logged to discord? Defaults to `false`. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public async warn(header: string, content: any, sendChannel = true, depth = 0): Promise { - const newContent = inspectContent(content, depth, true); - console.warn( - `${chalk.bgYellow(getTimeStamp())} ${chalk.yellow(`[${header}]`)} ${parseFormatting(newContent, 'yellowBright')}` - ); - - if (!sendChannel) return; - const embed = new EmbedBuilder() - .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) - .setColor(colors.warn) - .setTimestamp(); - await this.channelError({ embeds: [embed] }); - } - - /** - * Logs errors. Highlight information by surrounding it in `<<>>`. - * @param header The header displayed before the content, displayed in bright red. - * @param content The content to log, highlights displayed in bright red. - * @param sendChannel Should this also be logged to discord? Defaults to `false`. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public async error(header: string, content: any, sendChannel = true, depth = 0): Promise { - const newContent = inspectContent(content, depth, true); - console.warn( - `${chalk.bgRedBright(getTimeStamp())} ${chalk.redBright(`[${header}]`)} ${parseFormatting(newContent, 'redBright')}` - ); - if (!sendChannel) return; - const embed = new EmbedBuilder() - .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) - .setColor(colors.error) - .setTimestamp(); - await this.channelError({ embeds: [embed] }); - return; - } - - /** - * Logs successes. Highlight information by surrounding it in `<<>>`. - * @param header The header displayed before the content, displayed in green. - * @param content The content to log, highlights displayed in bright green. - * @param sendChannel Should this also be logged to discord? Defaults to `false`. - * @param depth The depth the content will inspected. Defaults to `0`. - */ - public async success(header: string, content: any, sendChannel = true, depth = 0): Promise { - const newContent = inspectContent(content, depth, true); - console.log( - `${chalk.bgGreen(getTimeStamp())} ${chalk.greenBright(`[${header}]`)} ${parseFormatting(newContent, 'greenBright')}` - ); - if (!sendChannel) return; - const embed = new EmbedBuilder() - .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) - .setColor(colors.success) - .setTimestamp(); - await this.channelLog({ embeds: [embed] }).catch(() => {}); - } -} diff --git a/lib/utils/BushUtils.ts b/lib/utils/BushUtils.ts deleted file mode 100644 index 1922204..0000000 --- a/lib/utils/BushUtils.ts +++ /dev/null @@ -1,615 +0,0 @@ -import { - Arg, - BushClient, - CommandMessage, - SlashEditMessageType, - SlashSendMessageType, - timeUnits, - type BaseBushArgumentType, - type BushInspectOptions, - type SlashMessage -} from '#lib'; -import { humanizeDuration as humanizeDurationMod } from '@notenoughupdates/humanize-duration'; -import assert from 'assert/strict'; -import cp from 'child_process'; -import deepLock from 'deep-lock'; -import { Util as AkairoUtil } from 'discord-akairo'; -import { - Constants as DiscordConstants, - EmbedBuilder, - Message, - OAuth2Scopes, - PermissionFlagsBits, - PermissionsBitField, - type APIEmbed, - type APIMessage, - type CommandInteraction, - type InteractionReplyOptions, - type PermissionsString -} from 'discord.js'; -import { DeepWritable } from 'ts-essentials'; -import { inspect as inspectUtil, promisify } from 'util'; -import * as Format from './Format.js'; - -export type StripPrivate = { [K in keyof T]: T[K] extends Record ? StripPrivate : T[K] }; -export type ValueOf = T[keyof T]; - -/** - * Capitalizes the first letter of the given text - * @param text The text to capitalize - * @returns The capitalized text - */ -export function capitalize(text: string): string { - return text.charAt(0).toUpperCase() + text.slice(1); -} - -export const exec = promisify(cp.exec); - -/** - * Runs a shell command and gives the output - * @param command The shell command to run - * @returns The stdout and stderr of the shell command - */ -export async function shell(command: string): Promise<{ stdout: string; stderr: string }> { - return await exec(command); -} - -/** - * Appends the correct ordinal to the given number - * @param n The number to append an ordinal to - * @returns The number with the ordinal - */ -export function ordinal(n: number): string { - const s = ['th', 'st', 'nd', 'rd'], - v = n % 100; - return n + (s[(v - 20) % 10] || s[v] || s[0]); -} - -/** - * Chunks an array to the specified size - * @param arr The array to chunk - * @param perChunk The amount of items per chunk - * @returns The chunked array - */ -export function chunk(arr: T[], perChunk: number): T[][] { - return arr.reduce((all, one, i) => { - const ch: number = Math.floor(i / perChunk); - (all as any[])[ch] = [].concat(all[ch] || [], one as any); - return all; - }, []); -} - -/** - * Fetches a user's uuid from the mojang api. - * @param username The username to get the uuid of. - * @returns The the uuid of the user. - */ -export async function mcUUID(username: string, dashed = false): Promise { - const apiRes = (await fetch(`https://api.ashcon.app/mojang/v2/user/${username}`).then((p) => - p.ok ? p.json() : undefined - )) as UuidRes; - - // this will throw an error if response is not ok - return dashed ? apiRes.uuid : apiRes.uuid.replace(/-/g, ''); -} - -export interface UuidRes { - uuid: string; - username: string; - username_history?: { username: string }[] | null; - textures: { - custom: boolean; - slim: boolean; - skin: { - url: string; - data: string; - }; - raw: { - value: string; - signature: string; - }; - }; - created_at: string; -} - -/** - * Generate defaults for {@link inspect}. - * @param options The options to create defaults with. - * @returns The default options combined with the specified options. - */ -function getDefaultInspectOptions(options?: BushInspectOptions): BushInspectOptions { - return { - showHidden: options?.showHidden ?? false, - depth: options?.depth ?? 2, - colors: options?.colors ?? false, - customInspect: options?.customInspect ?? true, - showProxy: options?.showProxy ?? false, - maxArrayLength: options?.maxArrayLength ?? Infinity, - maxStringLength: options?.maxStringLength ?? Infinity, - breakLength: options?.breakLength ?? 80, - compact: options?.compact ?? 3, - sorted: options?.sorted ?? false, - getters: options?.getters ?? true, - numericSeparator: options?.numericSeparator ?? true - }; -} - -/** - * Uses {@link inspect} with custom defaults. - * @param object - The object you would like to inspect. - * @param options - The options you would like to use to inspect the object. - * @returns The inspected object. - */ -export function inspect(object: any, options?: BushInspectOptions): string { - const optionsWithDefaults = getDefaultInspectOptions(options); - - if (!optionsWithDefaults.inspectStrings && typeof object === 'string') return object; - - return inspectUtil(object, optionsWithDefaults); -} - -/** - * Responds to a slash command interaction. - * @param interaction The interaction to respond to. - * @param responseOptions The options for the response. - * @returns The message sent. - */ -export async function slashRespond( - interaction: CommandInteraction, - responseOptions: SlashSendMessageType | SlashEditMessageType -): Promise { - const newResponseOptions = typeof responseOptions === 'string' ? { content: responseOptions } : responseOptions; - if (interaction.replied || interaction.deferred) { - delete (newResponseOptions as InteractionReplyOptions).ephemeral; // Cannot change a preexisting message to be ephemeral - return (await interaction.editReply(newResponseOptions)) as Message | APIMessage; - } else { - await interaction.reply(newResponseOptions); - return await interaction.fetchReply().catch(() => undefined); - } -} - -/** - * Takes an array and combines the elements using the supplied conjunction. - * @param array The array to combine. - * @param conjunction The conjunction to use. - * @param ifEmpty What to return if the array is empty. - * @returns The combined elements or `ifEmpty`. - * - * @example - * const permissions = oxford(['Administrator', 'SendMessages', 'ManageMessages'], 'and', 'none'); - * console.log(permissions); // Administrator, SendMessages and ManageMessages - */ -export function oxford(array: string[], conjunction: string, ifEmpty?: string): string | undefined { - const l = array.length; - if (!l) return ifEmpty; - if (l < 2) return array[0]; - if (l < 3) return array.join(` ${conjunction} `); - array = array.slice(); - array[l - 1] = `${conjunction} ${array[l - 1]}`; - return array.join(', '); -} - -/** - * Add or remove an item from an array. All duplicates will be removed. - * @param action Either `add` or `remove` an element. - * @param array The array to add/remove an element from. - * @param value The element to add/remove from the array. - */ -export function addOrRemoveFromArray(action: 'add' | 'remove', array: T[], value: T): T[] { - const set = new Set(array); - action === 'add' ? set.add(value) : set.delete(value); - return [...set]; -} - -/** - * Remove an item from an array. All duplicates will be removed. - * @param array The array to remove an element from. - * @param value The element to remove from the array. - */ -export function removeFromArray(array: T[], value: T): T[] { - return addOrRemoveFromArray('remove', array, value); -} - -/** - * Add an item from an array. All duplicates will be removed. - * @param array The array to add an element to. - * @param value The element to add to the array. - */ -export function addToArray(array: T[], value: T): T[] { - return addOrRemoveFromArray('add', array, value); -} - -/** - * Surrounds a string to the begging an end of each element in an array. - * @param array The array you want to surround. - * @param surroundChar1 The character placed in the beginning of the element. - * @param surroundChar2 The character placed in the end of the element. Defaults to `surroundChar1`. - */ -export function surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] { - return array.map((a) => `${surroundChar1}${a}${surroundChar2 ?? surroundChar1}`); -} - -/** - * Gets the duration from a specified string. - * @param content The string to look for a duration in. - * @param remove Whether or not to remove the duration from the original string. - * @returns The {@link ParsedDuration}. - */ -export function parseDuration(content: string, remove = true): ParsedDuration { - if (!content) return { duration: 0, content: null }; - - // eslint-disable-next-line prefer-const - let duration: number | null = null; - // Try to reduce false positives by requiring a space before the duration, this makes sure it still matches if it is - // in the beginning of the argument - let contentWithoutTime = ` ${content}`; - - for (const unit in timeUnits) { - const regex = timeUnits[unit as keyof typeof timeUnits].match; - const match = regex.exec(contentWithoutTime); - const value = Number(match?.groups?.[unit]); - if (!isNaN(value)) duration! += value * timeUnits[unit as keyof typeof timeUnits].value; - - if (remove) contentWithoutTime = contentWithoutTime.replace(regex, ''); - } - // remove the space added earlier - if (contentWithoutTime.startsWith(' ')) contentWithoutTime.replace(' ', ''); - return { duration, content: contentWithoutTime }; -} - -export interface ParsedDuration { - duration: number | null; - content: string | null; -} - -/** - * Converts a duration in milliseconds to a human readable form. - * @param duration The duration in milliseconds to convert. - * @param largest The maximum number of units to display for the duration. - * @param round Whether or not to round the smallest unit displayed. - * @returns A humanized string of the duration. - */ -export function humanizeDuration(duration: number, largest?: number, round = true): string { - if (largest) return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, largest, round })!; - else return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, round })!; -} - -/** - * Creates a formatted relative timestamp from a duration in milliseconds. - * @param duration The duration in milliseconds. - * @returns The formatted relative timestamp. - */ -export function timestampDuration(duration: number): string { - return ``; -} - -/** - * Creates a timestamp from a date. - * @param date The date to create a timestamp from. - * @param style The style of the timestamp. - * @returns The formatted timestamp. - * - * @see - * **Styles:** - * - **t**: Short Time ex. `16:20` - * - **T**: Long Time ex. `16:20:30 ` - * - **d**: Short Date ex. `20/04/2021` - * - **D**: Long Date ex. `20 April 2021` - * - **f**: Short Date/Time ex. `20 April 2021 16:20` - * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20` - * - **R**: Relative Time ex. `2 months ago` - */ -export function timestamp( - date: D, - style: TimestampStyle = 'f' -): D extends Date ? string : undefined { - if (!date) return date as unknown as D extends Date ? string : undefined; - return `` as unknown as D extends Date ? string : undefined; -} - -export type TimestampStyle = 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R'; - -/** - * Creates a human readable representation between a date and the current time. - * @param date The date to be compared with the current time. - * @param largest The maximum number of units to display for the duration. - * @param round Whether or not to round the smallest unit displayed. - * @returns A humanized string of the delta. - */ -export function dateDelta(date: Date, largest = 3, round = true): string { - return humanizeDuration(new Date().getTime() - date.getTime(), largest, round); -} - -/** - * Combines {@link timestamp} and {@link dateDelta} - * @param date The date to be compared with the current time. - * @param style The style of the timestamp. - * @returns The formatted timestamp. - * - * @see - * **Styles:** - * - **t**: Short Time ex. `16:20` - * - **T**: Long Time ex. `16:20:30 ` - * - **d**: Short Date ex. `20/04/2021` - * - **D**: Long Date ex. `20 April 2021` - * - **f**: Short Date/Time ex. `20 April 2021 16:20` - * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20` - * - **R**: Relative Time ex. `2 months ago` - */ -export function timestampAndDelta(date: Date, style: TimestampStyle = 'D'): string { - return `${timestamp(date, style)} (${dateDelta(date)} ago)`; -} - -/** - * Convert a hex code to an rbg value. - * @param hex The hex code to convert. - * @returns The rbg value. - */ -export function hexToRgb(hex: string): string { - const arrBuff = new ArrayBuffer(4); - const vw = new DataView(arrBuff); - vw.setUint32(0, parseInt(hex, 16), false); - const arrByte = new Uint8Array(arrBuff); - - return `${arrByte[1]}, ${arrByte[2]}, ${arrByte[3]}`; -} - -/** - * Wait an amount in milliseconds. - * @returns A promise that resolves after the specified amount of milliseconds - */ -export const sleep = promisify(setTimeout); - -/** - * List the methods of an object. - * @param obj The object to get the methods of. - * @returns A string with each method on a new line. - */ -export function getMethods(obj: Record): string { - // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class/31055217#31055217 - let props: string[] = []; - let obj_: Record = new Object(obj); - - do { - const l = Object.getOwnPropertyNames(obj_) - .concat(Object.getOwnPropertySymbols(obj_).map((s) => s.toString())) - .sort() - .filter( - (p, i, arr) => - typeof Object.getOwnPropertyDescriptor(obj_, p)?.['get'] !== 'function' && // ignore getters - typeof Object.getOwnPropertyDescriptor(obj_, p)?.['set'] !== 'function' && // ignore setters - typeof obj_[p] === 'function' && // only the methods - p !== 'constructor' && // not the constructor - (i == 0 || p !== arr[i - 1]) && // not overriding in this prototype - props.indexOf(p) === -1 // not overridden in a child - ); - - const reg = /\(([\s\S]*?)\)/; - props = props.concat( - l.map( - (p) => - `${obj_[p] && obj_[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${ - reg.exec(obj_[p].toString())?.[1] - ? reg - .exec(obj_[p].toString())?.[1] - .split(', ') - .map((arg) => arg.split('=')[0].trim()) - .join(', ') - : '' - });` - ) - ); - } while ( - (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain - Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...) - ); - - return props.join('\n'); -} - -/** - * List the symbols of an object. - * @param obj The object to get the symbols of. - * @returns An array of the symbols of the object. - */ -export function getSymbols(obj: Record): symbol[] { - let symbols: symbol[] = []; - let obj_: Record = new Object(obj); - - do { - const l = Object.getOwnPropertySymbols(obj_).sort(); - - symbols = [...symbols, ...l]; - } while ( - (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain - Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...) - ); - - return symbols; -} - -/** - * Checks if a user has a certain guild permission (doesn't check channel permissions). - * @param message The message to check the user from. - * @param permissions The permissions to check for. - * @returns The missing permissions or null if none are missing. - */ -export function userGuildPermCheck( - message: CommandMessage | SlashMessage, - permissions: typeof PermissionFlagsBits[keyof typeof PermissionFlagsBits][] -): PermissionsString[] | null { - if (!message.inGuild()) return null; - const missing = message.member?.permissions.missing(permissions) ?? []; - - return missing.length ? missing : null; -} - -/** - * Check if the client has certain permissions in the guild (doesn't check channel permissions). - * @param message The message to check the client user from. - * @param permissions The permissions to check for. - * @returns The missing permissions or null if none are missing. - */ -export function clientGuildPermCheck(message: CommandMessage | SlashMessage, permissions: bigint[]): PermissionsString[] | null { - const missing = message.guild?.members.me?.permissions.missing(permissions) ?? []; - - return missing.length ? missing : null; -} - -/** - * Check if the client has permission to send messages in the channel as well as check if they have other permissions - * in the guild (or the channel if `checkChannel` is `true`). - * @param message The message to check the client user from. - * @param permissions The permissions to check for. - * @param checkChannel Whether to check the channel permissions instead of the guild permissions. - * @returns The missing permissions or null if none are missing. - */ -export function clientSendAndPermCheck( - message: CommandMessage | SlashMessage, - permissions: bigint[] = [], - checkChannel = false -): PermissionsString[] | null { - if (!message.inGuild() || !message.channel) return null; - - const missing: PermissionsString[] = []; - const sendPerm = message.channel.isThread() ? 'SendMessages' : 'SendMessagesInThreads'; - - // todo: remove once forum channels are fixed - if (message.channel.parent === null && message.channel.isThread()) return null; - - if (!message.guild.members.me!.permissionsIn(message.channel!.id).has(sendPerm)) missing.push(sendPerm); - - missing.push( - ...(checkChannel - ? message.guild!.members.me!.permissionsIn(message.channel!.id!).missing(permissions) - : clientGuildPermCheck(message, permissions) ?? []) - ); - - return missing.length ? missing : null; -} - -export { deepLock as deepFreeze }; -export { Arg as arg }; -export { Format as format }; -export { DiscordConstants as discordConstants }; -export { AkairoUtil as akairo }; - -/** - * The link to invite the bot with all permissions. - */ -export function invite(client: BushClient) { - return client.generateInvite({ - permissions: - PermissionsBitField.All - - PermissionFlagsBits.UseEmbeddedActivities - - PermissionFlagsBits.ViewGuildInsights - - PermissionFlagsBits.Stream, - scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands] - }); -} - -/** - * Asset multiple statements at a time. - * @param args - */ -export function assertAll(...args: any[]): void { - for (let i = 0; i < args.length; i++) { - assert(args[i], `assertAll index ${i} failed`); - } -} - -/** - * Casts a string to a duration and reason for slash commands. - * @param arg The argument received. - * @param message The message that triggered the command. - * @returns The casted argument. - */ -export async function castDurationContent( - arg: string | ParsedDuration | null, - message: CommandMessage | SlashMessage -): Promise { - const res = typeof arg === 'string' ? await Arg.cast('contentWithDuration', message, arg) : arg; - - return { duration: res?.duration ?? 0, content: res?.content ?? '' }; -} - -export interface ParsedDurationRes { - duration: number; - content: string; -} - -/** - * Casts a string to a the specified argument type. - * @param type The type of the argument to cast to. - * @param arg The argument received. - * @param message The message that triggered the command. - * @returns The casted argument. - */ -export async function cast( - type: T, - arg: BaseBushArgumentType[T] | string, - message: CommandMessage | SlashMessage -) { - return typeof arg === 'string' ? await Arg.cast(type, message, arg) : arg; -} - -/** - * Overflows the description of an embed into multiple embeds. - * @param embed The options to be applied to the (first) embed. - * @param lines Each line of the description as an element in an array. - */ -export function overflowEmbed(embed: Omit, lines: string[], maxLength = 4096): EmbedBuilder[] { - const embeds: EmbedBuilder[] = []; - - const makeEmbed = () => { - embeds.push(new EmbedBuilder().setColor(embed.color ?? null)); - return embeds.at(-1)!; - }; - - for (const line of lines) { - let current = embeds.length ? embeds.at(-1)! : makeEmbed(); - let joined = current.data.description ? `${current.data.description}\n${line}` : line; - if (joined.length > maxLength) { - current = makeEmbed(); - joined = line; - } - - current.setDescription(joined); - } - - if (!embeds.length) makeEmbed(); - - if (embed.author) embeds.at(0)?.setAuthor(embed.author); - if (embed.title) embeds.at(0)?.setTitle(embed.title); - if (embed.url) embeds.at(0)?.setURL(embed.url); - if (embed.fields) embeds.at(-1)?.setFields(embed.fields); - if (embed.thumbnail) embeds.at(-1)?.setThumbnail(embed.thumbnail.url); - if (embed.footer) embeds.at(-1)?.setFooter(embed.footer); - if (embed.image) embeds.at(-1)?.setImage(embed.image.url); - if (embed.timestamp) embeds.at(-1)?.setTimestamp(new Date(embed.timestamp)); - - return embeds; -} - -/** - * Formats an error into a string. - * @param error The error to format. - * @param colors Whether to use colors in the output. - * @returns The formatted error. - */ -export function formatError(error: Error | any, colors = false): string { - if (!error) return error; - if (typeof error !== 'object') return String.prototype.toString.call(error); - if ( - getSymbols(error) - .map((s) => s.toString()) - .includes('Symbol(nodejs.util.inspect.custom)') - ) - return inspect(error, { colors }); - - return error.stack; -} - -export function deepWriteable(obj: T): DeepWritable { - return obj as DeepWritable; -} diff --git a/lib/utils/Constants.ts b/lib/utils/Constants.ts new file mode 100644 index 0000000..8e4871b --- /dev/null +++ b/lib/utils/Constants.ts @@ -0,0 +1,554 @@ +import { default as deepLock } from 'deep-lock'; +import { + ArgumentMatches as AkairoArgumentMatches, + ArgumentTypes as AkairoArgumentTypes, + BuiltInReasons, + CommandHandlerEvents as AkairoCommandHandlerEvents +} from 'discord-akairo/dist/src/util/Constants.js'; +import { Colors, GuildFeature, Snowflake } from 'discord.js'; + +const rawCapeUrl = 'https://raw.githubusercontent.com/NotEnoughUpdates/capes/master/'; + +/** + * Time units in milliseconds + */ +export const enum Time { + /** + * One millisecond (1 ms). + */ + Millisecond = 1, + + /** + * One second (1,000 ms). + */ + Second = Millisecond * 1000, + + /** + * One minute (60,000 ms). + */ + Minute = Second * 60, + + /** + * One hour (3,600,000 ms). + */ + Hour = Minute * 60, + + /** + * One day (86,400,000 ms). + */ + Day = Hour * 24, + + /** + * One week (604,800,000 ms). + */ + Week = Day * 7, + + /** + * One month (2,629,800,000 ms). + */ + Month = Day * 30.4375, // average of days in a month (including leap years) + + /** + * One year (31,557,600,000 ms). + */ + Year = Day * 365.25 // average with leap years +} + +export const emojis = Object.freeze({ + success: '<:success:837109864101707807>', + warn: '<:warn:848726900876247050>', + error: '<:error:837123021016924261>', + successFull: '<:success_full:850118767576088646>', + warnFull: '<:warn_full:850118767391539312>', + errorFull: '<:error_full:850118767295201350>', + mad: '<:mad:783046135392239626>', + join: '<:join:850198029809614858>', + leave: '<:leave:850198048205307919>', + loading: '', + offlineCircle: '<:offline:787550565382750239>', + dndCircle: '<:dnd:787550487633330176>', + idleCircle: '<:idle:787550520956551218>', + onlineCircle: '<:online:787550449435803658>', + cross: '<:cross:878319362539421777>', + check: '<:check:878320135297961995>' +} as const); + +export const emojisRaw = Object.freeze({ + success: '837109864101707807', + warn: '848726900876247050', + error: '837123021016924261', + successFull: '850118767576088646', + warnFull: '850118767391539312', + errorFull: '850118767295201350', + mad: '783046135392239626', + join: '850198029809614858', + leave: '850198048205307919', + loading: '853419254619963392', + offlineCircle: '787550565382750239', + dndCircle: '787550487633330176', + idleCircle: '787550520956551218', + onlineCircle: '787550449435803658', + cross: '878319362539421777', + check: '878320135297961995' +} as const); + +export const colors = Object.freeze({ + default: 0x1fd8f1, + error: 0xef4947, + warn: 0xfeba12, + success: 0x3bb681, + info: 0x3b78ff, + red: 0xff0000, + blue: 0x0055ff, + aqua: 0x00bbff, + purple: 0x8400ff, + blurple: 0x5440cd, + newBlurple: 0x5865f2, + pink: 0xff00e6, + green: 0x00ff1e, + darkGreen: 0x008f11, + gold: 0xb59400, + yellow: 0xffff00, + white: 0xffffff, + gray: 0xa6a6a6, + lightGray: 0xcfcfcf, + darkGray: 0x7a7a7a, + black: 0x000000, + orange: 0xe86100, + ...Colors +} as const); + +// Somewhat stolen from @Mzato0001 +export const timeUnits = deepLock({ + milliseconds: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im, + value: Time.Millisecond + }, + seconds: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:seconds?|secs?|s))/im, + value: Time.Second + }, + minutes: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:minutes?|mins?|m))/im, + value: Time.Minute + }, + hours: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:hours?|hrs?|h))/im, + value: Time.Hour + }, + days: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:days?|d))/im, + value: Time.Day + }, + weeks: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:weeks?|w))/im, + value: Time.Week + }, + months: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:months?|mon|mo))/im, + value: Time.Month + }, + years: { + match: / (?:(?-?(?:\d+)?\.?\d+) *(?:years?|y))/im, + value: Time.Year + } +} as const); + +export const regex = deepLock({ + snowflake: /^\d{15,21}$/im, + + discordEmoji: /[a-zA-Z0-9_]+):(?\d{15,21})>/im, + + /* + * Taken with permission from Geek: + * https://github.com/FireDiscordBot/bot/blob/5d1990e5f8b52fcc72261d786aa3c7c7c65ab5e8/lib/util/constants.ts#L276 + */ + /** **This has the global flag, make sure to handle it correctly.** */ + messageLink: + /\d{15,21})\/(?\d{15,21})\/(?\d{15,21})>?/gim +} as const); + +/** + * Maps the response from pronoundb.org to a readable format + */ +export const pronounMapping = Object.freeze({ + unspecified: 'Unspecified', + hh: 'He/Him', + hi: 'He/It', + hs: 'He/She', + ht: 'He/They', + ih: 'It/Him', + ii: 'It/Its', + is: 'It/She', + it: 'It/They', + shh: 'She/He', + sh: 'She/Her', + si: 'She/It', + st: 'She/They', + th: 'They/He', + ti: 'They/It', + ts: 'They/She', + tt: 'They/Them', + any: 'Any pronouns', + other: 'Other pronouns', + ask: 'Ask me my pronouns', + avoid: 'Avoid pronouns, use my name' +} as const); + +/** + * A bunch of mappings + */ +export const mappings = deepLock({ + guilds: { + "Moulberry's Bush": '516977525906341928', + "Moulberry's Tree": '767448775450820639', + 'MB Staff': '784597260465995796', + "IRONM00N's Space Ship": '717176538717749358' + }, + + channels: { + 'neu-support': '714332750156660756', + 'giveaways': '767782084981817344' + }, + + users: { + IRONM00N: '322862723090219008', + Moulberry: '211288288055525376', + nopo: '384620942577369088', + Bestower: '496409778822709251' + }, + + permissions: { + CreateInstantInvite: { name: 'Create Invite', important: false }, + KickMembers: { name: 'Kick Members', important: true }, + BanMembers: { name: 'Ban Members', important: true }, + Administrator: { name: 'Administrator', important: true }, + ManageChannels: { name: 'Manage Channels', important: true }, + ManageGuild: { name: 'Manage Server', important: true }, + AddReactions: { name: 'Add Reactions', important: false }, + ViewAuditLog: { name: 'View Audit Log', important: true }, + PrioritySpeaker: { name: 'Priority Speaker', important: true }, + Stream: { name: 'Video', important: false }, + ViewChannel: { name: 'View Channel', important: false }, + SendMessages: { name: 'Send Messages', important: false }, + SendTTSMessages: { name: 'Send Text-to-Speech Messages', important: true }, + ManageMessages: { name: 'Manage Messages', important: true }, + EmbedLinks: { name: 'Embed Links', important: false }, + AttachFiles: { name: 'Attach Files', important: false }, + ReadMessageHistory: { name: 'Read Message History', important: false }, + MentionEveryone: { name: 'Mention @\u200Beveryone, @\u200Bhere, and All Roles', important: true }, // name has a zero-width space to prevent accidents + UseExternalEmojis: { name: 'Use External Emoji', important: false }, + ViewGuildInsights: { name: 'View Server Insights', important: true }, + Connect: { name: 'Connect', important: false }, + Speak: { name: 'Speak', important: false }, + MuteMembers: { name: 'Mute Members', important: true }, + DeafenMembers: { name: 'Deafen Members', important: true }, + MoveMembers: { name: 'Move Members', important: true }, + UseVAD: { name: 'Use Voice Activity', important: false }, + ChangeNickname: { name: 'Change Nickname', important: false }, + ManageNicknames: { name: 'Change Nicknames', important: true }, + ManageRoles: { name: 'Manage Roles', important: true }, + ManageWebhooks: { name: 'Manage Webhooks', important: true }, + ManageEmojisAndStickers: { name: 'Manage Emojis and Stickers', important: true }, + UseApplicationCommands: { name: 'Use Slash Commands', important: false }, + RequestToSpeak: { name: 'Request to Speak', important: false }, + ManageEvents: { name: 'Manage Events', important: true }, + ManageThreads: { name: 'Manage Threads', important: true }, + CreatePublicThreads: { name: 'Create Public Threads', important: false }, + CreatePrivateThreads: { name: 'Create Private Threads', important: false }, + UseExternalStickers: { name: 'Use External Stickers', important: false }, + SendMessagesInThreads: { name: 'Send Messages In Threads', important: false }, + StartEmbeddedActivities: { name: 'Start Activities', important: false }, + ModerateMembers: { name: 'Timeout Members', important: true }, + UseEmbeddedActivities: { name: 'Use Activities', important: false } + }, + + // prettier-ignore + features: { + [GuildFeature.Verified]: { name: 'Verified', important: true, emoji: '<:verified:850795049817473066>', weight: 0 }, + [GuildFeature.Partnered]: { name: 'Partnered', important: true, emoji: '<:partneredServer:850794851955507240>', weight: 1 }, + [GuildFeature.MoreStickers]: { name: 'More Stickers', important: true, emoji: null, weight: 2 }, + MORE_EMOJIS: { name: 'More Emoji', important: true, emoji: '<:moreEmoji:850786853497602080>', weight: 3 }, + [GuildFeature.Featurable]: { name: 'Featurable', important: true, emoji: '<:featurable:850786776372084756>', weight: 4 }, + [GuildFeature.RelayEnabled]: { name: 'Relay Enabled', important: true, emoji: '<:relayEnabled:850790531441229834>', weight: 5 }, + [GuildFeature.Discoverable]: { name: 'Discoverable', important: true, emoji: '<:discoverable:850786735360966656>', weight: 6 }, + ENABLED_DISCOVERABLE_BEFORE: { name: 'Enabled Discovery Before', important: false, emoji: '<:enabledDiscoverableBefore:850786754670624828>', weight: 7 }, + [GuildFeature.MonetizationEnabled]: { name: 'Monetization Enabled', important: true, emoji: null, weight: 8 }, + [GuildFeature.TicketedEventsEnabled]: { name: 'Ticketed Events Enabled', important: true, emoji: null, weight: 9 }, + [GuildFeature.PreviewEnabled]: { name: 'Preview Enabled', important: true, emoji: '<:previewEnabled:850790508266913823>', weight: 10 }, + COMMERCE: { name: 'Store Channels', important: true, emoji: '<:storeChannels:850786692432396338>', weight: 11 }, + [GuildFeature.VanityURL]: { name: 'Vanity URL', important: false, emoji: '<:vanityURL:850790553079644160>', weight: 12 }, + [GuildFeature.VIPRegions]: { name: 'VIP Regions', important: false, emoji: '<:VIPRegions:850794697496854538>', weight: 13 }, + [GuildFeature.AnimatedIcon]: { name: 'Animated Icon', important: false, emoji: '<:animatedIcon:850774498071412746>', weight: 14 }, + [GuildFeature.Banner]: { name: 'Banner', important: false, emoji: '<:banner:850786673150787614>', weight: 15 }, + [GuildFeature.InviteSplash]: { name: 'Invite Splash', important: false, emoji: '<:inviteSplash:850786798246559754>', weight: 16 }, + [GuildFeature.PrivateThreads]: { name: 'Private Threads', important: false, emoji: '<:privateThreads:869763711894700093>', weight: 17 }, + THREE_DAY_THREAD_ARCHIVE: { name: 'Three Day Thread Archive', important: false, emoji: '<:threeDayThreadArchive:869767841652564008>', weight: 19 }, + SEVEN_DAY_THREAD_ARCHIVE: { name: 'Seven Day Thread Archive', important: false, emoji: '<:sevenDayThreadArchive:869767896123998288>', weight: 20 }, + [GuildFeature.RoleIcons]: { name: 'Role Icons', important: false, emoji: '<:roleIcons:876993381929222175>', weight: 21 }, + [GuildFeature.News]: { name: 'Announcement Channels', important: false, emoji: '<:announcementChannels:850790491796013067>', weight: 22 }, + [GuildFeature.MemberVerificationGateEnabled]: { name: 'Membership Verification Gate', important: false, emoji: '<:memberVerificationGateEnabled:850786829984858212>', weight: 23 }, + [GuildFeature.WelcomeScreenEnabled]: { name: 'Welcome Screen Enabled', important: false, emoji: '<:welcomeScreenEnabled:850790575875817504>', weight: 24 }, + [GuildFeature.Community]: { name: 'Community', important: false, emoji: '<:community:850786714271875094>', weight: 25 }, + THREADS_ENABLED: {name: 'Threads Enabled', important: false, emoji: '<:threadsEnabled:869756035345317919>', weight: 26 }, + THREADS_ENABLED_TESTING: {name: 'Threads Enabled Testing', important: false, emoji: null, weight: 27 }, + [GuildFeature.AnimatedBanner]: { name: 'Animated Banner', important: false, emoji: '<:animatedBanner:1010580022018449482>', weight: 28 }, + [GuildFeature.HasDirectoryEntry]: { name: 'Has Directory Entry', important: true, emoji: null, weight: 29 }, + [GuildFeature.Hub]: { name: 'Hub', important: true, emoji: null, weight: 30 }, + [GuildFeature.LinkedToHub]: { name: 'Linked To Hub', important: true, emoji: null, weight: 31 }, + TEXT_IN_VOICE_ENABLED: { name: 'Text In Voice Enabled', important: false, emoji: '<:textInVoiceEnabled:1010578210150424617>', weight: 32 }, + AUTO_MODERATION: { name: 'Auto Moderation', important: false, emoji: '<:autoModeration:1010579417942200321>', weight: 33 }, + MEMBER_PROFILES: { name: 'Member Profiles', important: false, emoji: '<:memberProfiles:1010580480409747547>', weight: 34 }, + NEW_THREAD_PERMISSIONS: { name: 'New Thread Permissions', important: false, emoji: '<:newThreadPermissions:1010580968442171492>', weight: 35 }, + }, + + regions: { + 'automatic': ':united_nations: Automatic', + 'brazil': ':flag_br: Brazil', + 'europe': ':flag_eu: Europe', + 'hongkong': ':flag_hk: Hongkong', + 'india': ':flag_in: India', + 'japan': ':flag_jp: Japan', + 'russia': ':flag_ru: Russia', + 'singapore': ':flag_sg: Singapore', + 'southafrica': ':flag_za: South Africa', + 'sydney': ':flag_au: Sydney', + 'us-central': ':flag_us: US Central', + 'us-east': ':flag_us: US East', + 'us-south': ':flag_us: US South', + 'us-west': ':flag_us: US West' + }, + + otherEmojis: { + ServerBooster1: '<:serverBooster1:848740052091142145>', + ServerBooster2: '<:serverBooster2:848740090506510388>', + ServerBooster3: '<:serverBooster3:848740124992077835>', + ServerBooster6: '<:serverBooster6:848740155245461514>', + ServerBooster9: '<:serverBooster9:848740188846030889>', + ServerBooster12: '<:serverBooster12:848740304365551668>', + ServerBooster15: '<:serverBooster15:848740354890137680>', + ServerBooster18: '<:serverBooster18:848740402886606868>', + ServerBooster24: '<:serverBooster24:848740444628320256>', + Nitro: '<:nitro:848740498054971432>', + Booster: '<:booster:848747775020892200>', + Owner: '<:owner:848746439311753286>', + Admin: '<:admin:848963914628333598>', + Superuser: '<:superUser:848947986326224926>', + Developer: '<:developer:848954538111139871>', + Bot: '<:bot:1006929813203853427>', + BushVerified: '<:verfied:853360152090771497>', + BoostTier1: '<:boostitle:853363736679940127>', + BoostTier2: '<:boostitle:853363752728789075>', + BoostTier3: '<:boostitle:853363769132056627>', + ChannelText: '<:text:853375537791893524>', + ChannelNews: '<:announcements:853375553531674644>', + ChannelVoice: '<:voice:853375566735212584>', + ChannelStage: '<:stage:853375583521210468>', + // ChannelStore: '<:store:853375601175691266>', + ChannelCategory: '<:category:853375615260819476>', + ChannelThread: '<:thread:865033845753249813>' + }, + + userFlags: { + Staff: '<:discordEmployee:848742947826434079>', + Partner: '<:partneredServerOwner:848743051593777152>', + Hypesquad: '<:hypeSquadEvents:848743108283072553>', + BugHunterLevel1: '<:bugHunter:848743239850393640>', + HypeSquadOnlineHouse1: '<:hypeSquadBravery:848742910563844127>', + HypeSquadOnlineHouse2: '<:hypeSquadBrilliance:848742840649646101>', + HypeSquadOnlineHouse3: '<:hypeSquadBalance:848742877537370133>', + PremiumEarlySupporter: '<:earlySupporter:848741030102171648>', + TeamPseudoUser: 'TeamPseudoUser', + BugHunterLevel2: '<:bugHunterGold:848743283080822794>', + VerifiedBot: '<:verifiedbot1:938928232667947028><:verifiedbot2:938928355707879475>', + VerifiedDeveloper: '<:earlyVerifiedBotDeveloper:848741079875846174>', + CertifiedModerator: '<:discordCertifiedModerator:877224285901582366>', + BotHTTPInteractions: 'BotHTTPInteractions', + Spammer: 'Spammer', + Quarantined: 'Quarantined' + }, + + status: { + online: '<:online:848937141639577690>', + idle: '<:idle:848937158261211146>', + dnd: '<:dnd:848937173780135986>', + offline: '<:offline:848939387277672448>', + streaming: '<:streaming:848937187479519242>' + }, + + maybeNitroDiscrims: ['1111', '2222', '3333', '4444', '5555', '6666', '6969', '7777', '8888', '9999'], + + capes: [ + /* supporter capes */ + { name: 'patreon1', purchasable: false /* moulberry no longer offers */ }, + { name: 'patreon2', purchasable: false /* moulberry no longer offers */ }, + { name: 'fade', custom: `${rawCapeUrl}fade.gif`, purchasable: true }, + { name: 'lava', custom: `${rawCapeUrl}lava.gif`, purchasable: true }, + { name: 'mcworld', custom: `${rawCapeUrl}mcworld_compressed.gif`, purchasable: true }, + { name: 'negative', custom: `${rawCapeUrl}negative_compressed.gif`, purchasable: true }, + { name: 'space', custom: `${rawCapeUrl}space_compressed.gif`, purchasable: true }, + { name: 'void', custom: `${rawCapeUrl}void.gif`, purchasable: true }, + { name: 'tunnel', custom: `${rawCapeUrl}tunnel.gif`, purchasable: true }, + /* Staff capes */ + { name: 'contrib' }, + { name: 'mbstaff' }, + { name: 'ironmoon' }, + { name: 'gravy' }, + { name: 'nullzee' }, + /* partner capes */ + { name: 'thebakery' }, + { name: 'dsm' }, + { name: 'packshq' }, + { name: 'furf' }, + { name: 'skytils' }, + { name: 'sbp' }, + { name: 'subreddit_light' }, + { name: 'subreddit_dark' }, + { name: 'skyclient' }, + { name: 'sharex' }, + { name: 'sharex_white' }, + /* streamer capes */ + { name: 'alexxoffi' }, + { name: 'jakethybro' }, + { name: 'krusty' }, + { name: 'krusty_day' }, + { name: 'krusty_night' }, + { name: 'krusty_sunset' }, + { name: 'soldier' }, + { name: 'zera' }, + { name: 'secondpfirsisch' }, + { name: 'stormy_lh' } + ].map((value, index) => ({ ...value, index })), + + roleMap: [ + { name: '*', id: '792453550768390194' }, + { name: 'Admin Perms', id: '746541309853958186' }, + { name: 'Sr. Moderator', id: '782803470205190164' }, + { name: 'Moderator', id: '737308259823910992' }, + { name: 'Helper', id: '737440116230062091' }, + { name: 'Trial Helper', id: '783537091946479636' }, + { name: 'Contributor', id: '694431057532944425' }, + { name: 'Giveaway Donor', id: '784212110263451649' }, + { name: 'Giveaway (200m)', id: '810267756426690601' }, + { name: 'Giveaway (100m)', id: '801444430522613802' }, + { name: 'Giveaway (50m)', id: '787497512981757982' }, + { name: 'Giveaway (25m)', id: '787497515771232267' }, + { name: 'Giveaway (10m)', id: '787497518241153025' }, + { name: 'Giveaway (5m)', id: '787497519768403989' }, + { name: 'Giveaway (1m)', id: '787497521084891166' }, + { name: 'Suggester', id: '811922322767609877' }, + { name: 'Partner', id: '767324547312779274' }, + { name: 'Level Locked', id: '784248899044769792' }, + { name: 'No Files', id: '786421005039173633' }, + { name: 'No Reactions', id: '786421270924361789' }, + { name: 'No Links', id: '786421269356740658' }, + { name: 'No Bots', id: '786804858765312030' }, + { name: 'No VC', id: '788850482554208267' }, + { name: 'No Giveaways', id: '808265422334984203' }, + { name: 'No Support', id: '790247359824396319' } + ], + + roleWhitelist: { + 'Partner': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Suggester': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper', 'Trial Helper', 'Contributor'], + 'Level Locked': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'No Files': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'No Reactions': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'No Links': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'No Bots': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'No VC': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'No Giveaways': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper'], + 'No Support': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway Donor': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (200m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (100m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (50m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (25m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (10m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (5m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], + 'Giveaway (1m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'] + } +} as const); + +export const bots: Record = { + // MEE6#4876 + '159985870458322944': { + applicationId: '159985415099514880' + }, + // Dyno + '155149108183695360': { + applicationId: '161660517914509312' + }, + // Tatsu#8792 + '172002275412279296': { + applicationId: '172002255350792192' + }, + // YAGPDB.xyz#8760 + '204255221017214977': { + applicationId: '204255083083333633' + } +}; + +export const ArgumentMatches = Object.freeze({ + ...AkairoArgumentMatches +} as const); + +export const ArgumentTypes = Object.freeze({ + ...AkairoArgumentTypes, + DURATION: 'duration', + CONTENT_WITH_DURATION: 'contentWithDuration', + PERMISSION: 'permission', + SNOWFLAKE: 'snowflake', + DISCORD_EMOJI: 'discordEmoji', + ROLE_WITH_DURATION: 'roleWithDuration', + ABBREVIATED_NUMBER: 'abbreviatedNumber', + GLOBAL_USER: 'globalUser' +} as const); + +export const BlockedReasons = Object.freeze({ + ...BuiltInReasons, + DISABLED_GUILD: 'disabledGuild', + DISABLED_GLOBAL: 'disabledGlobal', + ROLE_BLACKLIST: 'roleBlacklist', + USER_GUILD_BLACKLIST: 'userGuildBlacklist', + USER_GLOBAL_BLACKLIST: 'userGlobalBlacklist', + RESTRICTED_GUILD: 'restrictedGuild', + CHANNEL_GUILD_BLACKLIST: 'channelGuildBlacklist', + CHANNEL_GLOBAL_BLACKLIST: 'channelGlobalBlacklist', + RESTRICTED_CHANNEL: 'restrictedChannel' +} as const); + +export const CommandHandlerEvents = Object.freeze({ + ...AkairoCommandHandlerEvents +} as const); + +export const moulberryBushRoleMap = deepLock([ + { name: '*', id: '792453550768390194' }, + { name: 'Admin Perms', id: '746541309853958186' }, + { name: 'Sr. Moderator', id: '782803470205190164' }, + { name: 'Moderator', id: '737308259823910992' }, + { name: 'Helper', id: '737440116230062091' }, + { name: 'Trial Helper', id: '783537091946479636' }, + { name: 'Contributor', id: '694431057532944425' }, + { name: 'Giveaway Donor', id: '784212110263451649' }, + { name: 'Giveaway (200m)', id: '810267756426690601' }, + { name: 'Giveaway (100m)', id: '801444430522613802' }, + { name: 'Giveaway (50m)', id: '787497512981757982' }, + { name: 'Giveaway (25m)', id: '787497515771232267' }, + { name: 'Giveaway (10m)', id: '787497518241153025' }, + { name: 'Giveaway (5m)', id: '787497519768403989' }, + { name: 'Giveaway (1m)', id: '787497521084891166' }, + { name: 'Suggester', id: '811922322767609877' }, + { name: 'Partner', id: '767324547312779274' }, + { name: 'Level Locked', id: '784248899044769792' }, + { name: 'No Files', id: '786421005039173633' }, + { name: 'No Reactions', id: '786421270924361789' }, + { name: 'No Links', id: '786421269356740658' }, + { name: 'No Bots', id: '786804858765312030' }, + { name: 'No VC', id: '788850482554208267' }, + { name: 'No Giveaways', id: '808265422334984203' }, + { name: 'No Support', id: '790247359824396319' } +] as const); + +export type PronounCode = keyof typeof pronounMapping; +export type Pronoun = typeof pronounMapping[PronounCode]; diff --git a/lib/utils/ErrorHandler.ts b/lib/utils/ErrorHandler.ts index 923da75..3f8be89 100644 --- a/lib/utils/ErrorHandler.ts +++ b/lib/utils/ErrorHandler.ts @@ -1,14 +1,14 @@ import { AkairoMessage, Command } from 'discord-akairo'; import { ChannelType, Client, EmbedBuilder, escapeInlineCode, GuildTextBasedChannel, Message } from 'discord.js'; -import { BushCommandHandlerEvents } from '../extensions/discord-akairo/BushCommandHandler.js'; +import { BotCommandHandlerEvents } from '../extensions/discord-akairo/BotCommandHandler.js'; import { SlashMessage } from '../extensions/discord-akairo/SlashMessage.js'; -import { colors } from './BushConstants.js'; -import { capitalize, formatError } from './BushUtils.js'; +import { colors } from './Constants.js'; import { bold, input } from './Format.js'; +import { capitalize, formatError } from './Utils.js'; export async function handleCommandError( client: Client, - ...[error, message, _command]: BushCommandHandlerEvents['error'] | BushCommandHandlerEvents['slashError'] + ...[error, message, _command]: BotCommandHandlerEvents['error'] | BotCommandHandlerEvents['slashError'] ) { try { const isSlash = message.util?.isSlash; diff --git a/lib/utils/FormatResponse.ts b/lib/utils/FormatResponse.ts index f094601..470fea7 100644 --- a/lib/utils/FormatResponse.ts +++ b/lib/utils/FormatResponse.ts @@ -1,8 +1,8 @@ import type { GuildMember } from 'discord.js'; import { unmuteResponse, UnmuteResponse } from '../extensions/discord.js/ExtendedGuildMember.js'; -import { emojis } from './BushConstants.js'; -import { format } from './BushUtils.js'; +import { emojis } from './Constants.js'; import { input } from './Format.js'; +import { format } from './Utils.js'; export function formatUnmuteResponse(prefix: string, member: GuildMember, code: UnmuteResponse): string { const error = emojis.error; diff --git a/lib/utils/Logger.ts b/lib/utils/Logger.ts new file mode 100644 index 0000000..872ff3e --- /dev/null +++ b/lib/utils/Logger.ts @@ -0,0 +1,314 @@ +import chalk from 'chalk'; +import { bold, Client, EmbedBuilder, escapeMarkdown, PartialTextBasedChannelFields, type Message } from 'discord.js'; +import { stripVTControlCharacters as stripColor } from 'node:util'; +import repl, { REPLServer, REPL_MODE_STRICT } from 'repl'; +import { WriteStream } from 'tty'; +import type { SendMessageType } from '../types/misc.js'; +import { colors } from './Constants.js'; +import { inspect } from './Utils.js'; + +let REPL: REPLServer; +let replGone = false; + +export function init() { + const kFormatForStdout = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStdout)')!; + const kFormatForStderr = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStderr)')!; + + REPL = repl.start({ + useColors: true, + terminal: true, + useGlobal: true, + replMode: REPL_MODE_STRICT, + breakEvalOnSigint: true, + ignoreUndefined: true + }); + + const apply = (stream: WriteStream, symbol: symbol): ProxyHandler['apply'] => + function apply(target, _thisArg, args) { + if (stream.isTTY) { + stream.moveCursor(0, -1); + stream.write('\n'); + stream.clearLine(0); + } + + const ret = target(...args); + + if (stream.isTTY) { + const formatted = (console as any)[symbol](args) as string; + + stream.moveCursor(0, formatted.split('\n').length); + if (!replGone) { + REPL.displayPrompt(true); + } + } + + return ret; + }; + + global.console.log = new Proxy(console.log, { + apply: apply(process.stdout, kFormatForStdout) + }); + + global.console.warn = new Proxy(console.warn, { + apply: apply(process.stderr, kFormatForStderr) + }); + + REPL.on('exit', () => { + replGone = true; + process.exit(0); + }); +} + +/** + * Parses the content surrounding by `<<>>` and emphasizes it with the given color or by making it bold. + * @param content The content to parse. + * @param color The color to emphasize the content with. + * @param discordFormat Whether or not to format the content for discord. + * @returns The formatted content. + */ +function parseFormatting( + content: any, + color: 'blueBright' | 'blackBright' | 'redBright' | 'yellowBright' | 'greenBright' | '', + discordFormat = false +): string | typeof content { + if (typeof content !== 'string') return content; + return content + .split(/<<|>>/) + .map((value, index) => { + if (discordFormat) { + return index % 2 === 0 ? escapeMarkdown(value) : bold(escapeMarkdown(value)); + } else { + return index % 2 === 0 || !color ? value : chalk[color](value); + } + }) + .join(''); +} + +/** + * Inspects the content and returns a string. + * @param content The content to inspect. + * @param depth The depth the content will inspected. Defaults to `2`. + * @param colors Whether or not to use colors in the output. Defaults to `true`. + * @returns The inspected content. + */ +function inspectContent(content: any, depth = 2, colors = true): string { + if (typeof content !== 'string') { + return inspect(content, { depth, colors }); + } + return content; +} + +/** + * Generates a formatted timestamp for logging. + * @returns The formatted timestamp. + */ +function getTimeStamp(): string { + const now = new Date(); + const minute = pad(now.getMinutes()); + const hour = pad(now.getHours()); + const date = `${pad(now.getMonth() + 1)}/${pad(now.getDate())}`; + return `${date} ${hour}:${minute}`; +} + +/** + * Pad a two-digit number. + */ +function pad(num: number) { + return num.toString().padStart(2, '0'); +} + +/** + * Custom logging utility for the bot. + */ +export class Logger { + /** + * @param client The client. + */ + public constructor(public client: Client) {} + + /** + * Logs information. Highlight information by surrounding it in `<<>>`. + * @param header The header displayed before the content, displayed in cyan. + * @param content The content to log, highlights displayed in bright blue. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. + */ + public get log() { + return this.info; + } + + /** + * Sends a message to the log channel. + * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}. + * @returns The message sent. + */ + public async channelLog(message: SendMessageType): Promise { + const channel = await this.client.utils.getConfigChannel('log'); + if (channel === null) return null; + return await channel.send(message).catch(() => null); + } + + /** + * Sends a message to the error channel. + * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}. + * @returns The message sent. + */ + public async channelError(message: SendMessageType): Promise { + const channel = await this.client.utils.getConfigChannel('error'); + if (!channel) { + void this.error( + 'BushLogger', + `Could not find error channel, was originally going to send: \n${inspect(message, { + colors: true + })}\n${new Error().stack?.substring(8)}`, + false + ); + return null; + } + return await channel.send(message); + } + + /** + * Logs debug information. Only works in dev is enabled in the config. + * @param content The content to log. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public debug(content: any, depth = 0): void { + if (!this.client.config.isDevelopment) return; + const newContent = inspectContent(content, depth, true); + console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')} ${newContent}`); + } + + /** + * Logs raw debug information. Only works in dev is enabled in the config. + * @param content The content to log. + */ + public debugRaw(...content: any): void { + if (!this.client.config.isDevelopment) return; + console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')}`, ...content); + } + + /** + * Logs verbose information. Highlight information by surrounding it in `<<>>`. + * @param header The header printed before the content, displayed in grey. + * @param content The content to log, highlights displayed in bright black. + * @param sendChannel Should this also be logged to discord? Defaults to `false`. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public async verbose(header: string, content: any, sendChannel = false, depth = 0): Promise { + if (!this.client.config.logging.verbose) return; + const newContent = inspectContent(content, depth, true); + console.log(`${chalk.bgGrey(getTimeStamp())} ${chalk.grey(`[${header}]`)} ${parseFormatting(newContent, 'blackBright')}`); + if (!sendChannel) return; + const embed = new EmbedBuilder() + .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) + .setColor(colors.gray) + .setTimestamp(); + await this.channelLog({ embeds: [embed] }); + } + + /** + * Logs very verbose information. Highlight information by surrounding it in `<<>>`. + * @param header The header printed before the content, displayed in purple. + * @param content The content to log, highlights displayed in bright black. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public async superVerbose(header: string, content: any, depth = 0): Promise { + if (!this.client.config.logging.verbose) return; + const newContent = inspectContent(content, depth, true); + console.log( + `${chalk.bgHex('#949494')(getTimeStamp())} ${chalk.hex('#949494')(`[${header}]`)} ${chalk.hex('#b3b3b3')(newContent)}` + ); + } + + /** + * Logs raw very verbose information. + * @param header The header printed before the content, displayed in purple. + * @param content The content to log. + */ + public async superVerboseRaw(header: string, ...content: any[]): Promise { + if (!this.client.config.logging.verbose) return; + console.log(`${chalk.bgHex('#a3a3a3')(getTimeStamp())} ${chalk.hex('#a3a3a3')(`[${header}]`)}`, ...content); + } + + /** + * Logs information. Highlight information by surrounding it in `<<>>`. + * @param header The header displayed before the content, displayed in cyan. + * @param content The content to log, highlights displayed in bright blue. + * @param sendChannel Should this also be logged to discord? Defaults to `false`. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public async info(header: string, content: any, sendChannel = true, depth = 0): Promise { + if (!this.client.config.logging.info) return; + const newContent = inspectContent(content, depth, true); + console.log(`${chalk.bgCyan(getTimeStamp())} ${chalk.cyan(`[${header}]`)} ${parseFormatting(newContent, 'blueBright')}`); + if (!sendChannel) return; + const embed = new EmbedBuilder() + .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) + .setColor(colors.info) + .setTimestamp(); + await this.channelLog({ embeds: [embed] }); + } + + /** + * Logs warnings. Highlight information by surrounding it in `<<>>`. + * @param header The header displayed before the content, displayed in yellow. + * @param content The content to log, highlights displayed in bright yellow. + * @param sendChannel Should this also be logged to discord? Defaults to `false`. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public async warn(header: string, content: any, sendChannel = true, depth = 0): Promise { + const newContent = inspectContent(content, depth, true); + console.warn( + `${chalk.bgYellow(getTimeStamp())} ${chalk.yellow(`[${header}]`)} ${parseFormatting(newContent, 'yellowBright')}` + ); + + if (!sendChannel) return; + const embed = new EmbedBuilder() + .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) + .setColor(colors.warn) + .setTimestamp(); + await this.channelError({ embeds: [embed] }); + } + + /** + * Logs errors. Highlight information by surrounding it in `<<>>`. + * @param header The header displayed before the content, displayed in bright red. + * @param content The content to log, highlights displayed in bright red. + * @param sendChannel Should this also be logged to discord? Defaults to `false`. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public async error(header: string, content: any, sendChannel = true, depth = 0): Promise { + const newContent = inspectContent(content, depth, true); + console.warn( + `${chalk.bgRedBright(getTimeStamp())} ${chalk.redBright(`[${header}]`)} ${parseFormatting(newContent, 'redBright')}` + ); + if (!sendChannel) return; + const embed = new EmbedBuilder() + .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) + .setColor(colors.error) + .setTimestamp(); + await this.channelError({ embeds: [embed] }); + return; + } + + /** + * Logs successes. Highlight information by surrounding it in `<<>>`. + * @param header The header displayed before the content, displayed in green. + * @param content The content to log, highlights displayed in bright green. + * @param sendChannel Should this also be logged to discord? Defaults to `false`. + * @param depth The depth the content will inspected. Defaults to `0`. + */ + public async success(header: string, content: any, sendChannel = true, depth = 0): Promise { + const newContent = inspectContent(content, depth, true); + console.log( + `${chalk.bgGreen(getTimeStamp())} ${chalk.greenBright(`[${header}]`)} ${parseFormatting(newContent, 'greenBright')}` + ); + if (!sendChannel) return; + const embed = new EmbedBuilder() + .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`) + .setColor(colors.success) + .setTimestamp(); + await this.channelLog({ embeds: [embed] }).catch(() => {}); + } +} diff --git a/lib/utils/Minecraft.ts b/lib/utils/Minecraft.ts index 50c44ef..e189b66 100644 --- a/lib/utils/Minecraft.ts +++ b/lib/utils/Minecraft.ts @@ -2,10 +2,6 @@ import { Byte, Int } from '@ironm00n/nbt-ts'; import { BitField } from 'discord.js'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); export enum FormattingCodes { Black = '§0', diff --git a/lib/utils/Minecraft_Test.ts b/lib/utils/Minecraft_Test.ts index fce9d5b..7dae3f6 100644 --- a/lib/utils/Minecraft_Test.ts +++ b/lib/utils/Minecraft_Test.ts @@ -110,7 +110,7 @@ export class NeuItem { this.petLoreReplacements(); } - private petLoreReplacements(level = -1) { + private petLoreReplacements(_level = -1) { if (/.*?;[0-5]$/.test(this.internalName) && this.displayName.includes('LVL')) { const maxLevel = neuPets?.custom_pet_leveling?.[this.internalName]?.max_level ?? 100; this.displayName = this.displayName.replace('LVL', `1➡${maxLevel}`); @@ -122,14 +122,14 @@ export class NeuItem { const petInfoTier = nums[teir]; if (!petInfoTier) throw new Error(`Pet (${this.internalName}) has no pet nums for ${teir} rarity.`); - const curve = petInfoTier?.stats_levelling_curve?.split(';'); + // const curve = petInfoTier?.stats_levelling_curve?.split(';'); // todo: finish copying from neu - const minStatsLevel = parseInt(curve?.[0] ?? '0'); - const maxStatsLevel = parseInt(curve?.[0] ?? '100'); + // const minStatsLevel = parseInt(curve?.[0] ?? '0'); + // const maxStatsLevel = parseInt(curve?.[0] ?? '100'); - const lore = ''; + // const lore = ''; } } } diff --git a/lib/utils/Utils.ts b/lib/utils/Utils.ts new file mode 100644 index 0000000..f7404e1 --- /dev/null +++ b/lib/utils/Utils.ts @@ -0,0 +1,615 @@ +import { + Arg, + CommandMessage, + SlashEditMessageType, + SlashSendMessageType, + TanzaniteClient, + timeUnits, + type BaseBotArgumentType, + type CustomInspectOptions, + type SlashMessage +} from '#lib'; +import { humanizeDuration as humanizeDurationMod } from '@notenoughupdates/humanize-duration'; +import assert from 'assert/strict'; +import cp from 'child_process'; +import deepLock from 'deep-lock'; +import { Util as AkairoUtil } from 'discord-akairo'; +import { + Constants as DiscordConstants, + EmbedBuilder, + Message, + OAuth2Scopes, + PermissionFlagsBits, + PermissionsBitField, + type APIEmbed, + type APIMessage, + type CommandInteraction, + type InteractionReplyOptions, + type PermissionsString +} from 'discord.js'; +import { DeepWritable } from 'ts-essentials'; +import { inspect as inspectUtil, promisify } from 'util'; +import * as Format from './Format.js'; + +export type StripPrivate = { [K in keyof T]: T[K] extends Record ? StripPrivate : T[K] }; +export type ValueOf = T[keyof T]; + +/** + * Capitalizes the first letter of the given text + * @param text The text to capitalize + * @returns The capitalized text + */ +export function capitalize(text: string): string { + return text.charAt(0).toUpperCase() + text.slice(1); +} + +export const exec = promisify(cp.exec); + +/** + * Runs a shell command and gives the output + * @param command The shell command to run + * @returns The stdout and stderr of the shell command + */ +export async function shell(command: string): Promise<{ stdout: string; stderr: string }> { + return await exec(command); +} + +/** + * Appends the correct ordinal to the given number + * @param n The number to append an ordinal to + * @returns The number with the ordinal + */ +export function ordinal(n: number): string { + const s = ['th', 'st', 'nd', 'rd'], + v = n % 100; + return n + (s[(v - 20) % 10] || s[v] || s[0]); +} + +/** + * Chunks an array to the specified size + * @param arr The array to chunk + * @param perChunk The amount of items per chunk + * @returns The chunked array + */ +export function chunk(arr: T[], perChunk: number): T[][] { + return arr.reduce((all, one, i) => { + const ch: number = Math.floor(i / perChunk); + (all as any[])[ch] = [].concat(all[ch] || [], one as any); + return all; + }, []); +} + +/** + * Fetches a user's uuid from the mojang api. + * @param username The username to get the uuid of. + * @returns The the uuid of the user. + */ +export async function mcUUID(username: string, dashed = false): Promise { + const apiRes = (await fetch(`https://api.ashcon.app/mojang/v2/user/${username}`).then((p) => + p.ok ? p.json() : undefined + )) as UuidRes; + + // this will throw an error if response is not ok + return dashed ? apiRes.uuid : apiRes.uuid.replace(/-/g, ''); +} + +export interface UuidRes { + uuid: string; + username: string; + username_history?: { username: string }[] | null; + textures: { + custom: boolean; + slim: boolean; + skin: { + url: string; + data: string; + }; + raw: { + value: string; + signature: string; + }; + }; + created_at: string; +} + +/** + * Generate defaults for {@link inspect}. + * @param options The options to create defaults with. + * @returns The default options combined with the specified options. + */ +function getDefaultInspectOptions(options?: CustomInspectOptions): CustomInspectOptions { + return { + showHidden: options?.showHidden ?? false, + depth: options?.depth ?? 2, + colors: options?.colors ?? false, + customInspect: options?.customInspect ?? true, + showProxy: options?.showProxy ?? false, + maxArrayLength: options?.maxArrayLength ?? Infinity, + maxStringLength: options?.maxStringLength ?? Infinity, + breakLength: options?.breakLength ?? 80, + compact: options?.compact ?? 3, + sorted: options?.sorted ?? false, + getters: options?.getters ?? true, + numericSeparator: options?.numericSeparator ?? true + }; +} + +/** + * Uses {@link inspect} with custom defaults. + * @param object - The object you would like to inspect. + * @param options - The options you would like to use to inspect the object. + * @returns The inspected object. + */ +export function inspect(object: any, options?: CustomInspectOptions): string { + const optionsWithDefaults = getDefaultInspectOptions(options); + + if (!optionsWithDefaults.inspectStrings && typeof object === 'string') return object; + + return inspectUtil(object, optionsWithDefaults); +} + +/** + * Responds to a slash command interaction. + * @param interaction The interaction to respond to. + * @param responseOptions The options for the response. + * @returns The message sent. + */ +export async function slashRespond( + interaction: CommandInteraction, + responseOptions: SlashSendMessageType | SlashEditMessageType +): Promise { + const newResponseOptions = typeof responseOptions === 'string' ? { content: responseOptions } : responseOptions; + if (interaction.replied || interaction.deferred) { + delete (newResponseOptions as InteractionReplyOptions).ephemeral; // Cannot change a preexisting message to be ephemeral + return (await interaction.editReply(newResponseOptions)) as Message | APIMessage; + } else { + await interaction.reply(newResponseOptions); + return await interaction.fetchReply().catch(() => undefined); + } +} + +/** + * Takes an array and combines the elements using the supplied conjunction. + * @param array The array to combine. + * @param conjunction The conjunction to use. + * @param ifEmpty What to return if the array is empty. + * @returns The combined elements or `ifEmpty`. + * + * @example + * const permissions = oxford(['Administrator', 'SendMessages', 'ManageMessages'], 'and', 'none'); + * console.log(permissions); // Administrator, SendMessages and ManageMessages + */ +export function oxford(array: string[], conjunction: string, ifEmpty?: string): string | undefined { + const l = array.length; + if (!l) return ifEmpty; + if (l < 2) return array[0]; + if (l < 3) return array.join(` ${conjunction} `); + array = array.slice(); + array[l - 1] = `${conjunction} ${array[l - 1]}`; + return array.join(', '); +} + +/** + * Add or remove an item from an array. All duplicates will be removed. + * @param action Either `add` or `remove` an element. + * @param array The array to add/remove an element from. + * @param value The element to add/remove from the array. + */ +export function addOrRemoveFromArray(action: 'add' | 'remove', array: T[], value: T): T[] { + const set = new Set(array); + action === 'add' ? set.add(value) : set.delete(value); + return [...set]; +} + +/** + * Remove an item from an array. All duplicates will be removed. + * @param array The array to remove an element from. + * @param value The element to remove from the array. + */ +export function removeFromArray(array: T[], value: T): T[] { + return addOrRemoveFromArray('remove', array, value); +} + +/** + * Add an item from an array. All duplicates will be removed. + * @param array The array to add an element to. + * @param value The element to add to the array. + */ +export function addToArray(array: T[], value: T): T[] { + return addOrRemoveFromArray('add', array, value); +} + +/** + * Surrounds a string to the begging an end of each element in an array. + * @param array The array you want to surround. + * @param surroundChar1 The character placed in the beginning of the element. + * @param surroundChar2 The character placed in the end of the element. Defaults to `surroundChar1`. + */ +export function surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] { + return array.map((a) => `${surroundChar1}${a}${surroundChar2 ?? surroundChar1}`); +} + +/** + * Gets the duration from a specified string. + * @param content The string to look for a duration in. + * @param remove Whether or not to remove the duration from the original string. + * @returns The {@link ParsedDuration}. + */ +export function parseDuration(content: string, remove = true): ParsedDuration { + if (!content) return { duration: 0, content: null }; + + // eslint-disable-next-line prefer-const + let duration: number | null = null; + // Try to reduce false positives by requiring a space before the duration, this makes sure it still matches if it is + // in the beginning of the argument + let contentWithoutTime = ` ${content}`; + + for (const unit in timeUnits) { + const regex = timeUnits[unit as keyof typeof timeUnits].match; + const match = regex.exec(contentWithoutTime); + const value = Number(match?.groups?.[unit]); + if (!isNaN(value)) duration! += value * timeUnits[unit as keyof typeof timeUnits].value; + + if (remove) contentWithoutTime = contentWithoutTime.replace(regex, ''); + } + // remove the space added earlier + if (contentWithoutTime.startsWith(' ')) contentWithoutTime.replace(' ', ''); + return { duration, content: contentWithoutTime }; +} + +export interface ParsedDuration { + duration: number | null; + content: string | null; +} + +/** + * Converts a duration in milliseconds to a human readable form. + * @param duration The duration in milliseconds to convert. + * @param largest The maximum number of units to display for the duration. + * @param round Whether or not to round the smallest unit displayed. + * @returns A humanized string of the duration. + */ +export function humanizeDuration(duration: number, largest?: number, round = true): string { + if (largest) return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, largest, round })!; + else return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, round })!; +} + +/** + * Creates a formatted relative timestamp from a duration in milliseconds. + * @param duration The duration in milliseconds. + * @returns The formatted relative timestamp. + */ +export function timestampDuration(duration: number): string { + return ``; +} + +/** + * Creates a timestamp from a date. + * @param date The date to create a timestamp from. + * @param style The style of the timestamp. + * @returns The formatted timestamp. + * + * @see + * **Styles:** + * - **t**: Short Time ex. `16:20` + * - **T**: Long Time ex. `16:20:30 ` + * - **d**: Short Date ex. `20/04/2021` + * - **D**: Long Date ex. `20 April 2021` + * - **f**: Short Date/Time ex. `20 April 2021 16:20` + * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20` + * - **R**: Relative Time ex. `2 months ago` + */ +export function timestamp( + date: D, + style: TimestampStyle = 'f' +): D extends Date ? string : undefined { + if (!date) return date as unknown as D extends Date ? string : undefined; + return `` as unknown as D extends Date ? string : undefined; +} + +export type TimestampStyle = 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R'; + +/** + * Creates a human readable representation between a date and the current time. + * @param date The date to be compared with the current time. + * @param largest The maximum number of units to display for the duration. + * @param round Whether or not to round the smallest unit displayed. + * @returns A humanized string of the delta. + */ +export function dateDelta(date: Date, largest = 3, round = true): string { + return humanizeDuration(new Date().getTime() - date.getTime(), largest, round); +} + +/** + * Combines {@link timestamp} and {@link dateDelta} + * @param date The date to be compared with the current time. + * @param style The style of the timestamp. + * @returns The formatted timestamp. + * + * @see + * **Styles:** + * - **t**: Short Time ex. `16:20` + * - **T**: Long Time ex. `16:20:30 ` + * - **d**: Short Date ex. `20/04/2021` + * - **D**: Long Date ex. `20 April 2021` + * - **f**: Short Date/Time ex. `20 April 2021 16:20` + * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20` + * - **R**: Relative Time ex. `2 months ago` + */ +export function timestampAndDelta(date: Date, style: TimestampStyle = 'D'): string { + return `${timestamp(date, style)} (${dateDelta(date)} ago)`; +} + +/** + * Convert a hex code to an rbg value. + * @param hex The hex code to convert. + * @returns The rbg value. + */ +export function hexToRgb(hex: string): string { + const arrBuff = new ArrayBuffer(4); + const vw = new DataView(arrBuff); + vw.setUint32(0, parseInt(hex, 16), false); + const arrByte = new Uint8Array(arrBuff); + + return `${arrByte[1]}, ${arrByte[2]}, ${arrByte[3]}`; +} + +/** + * Wait an amount in milliseconds. + * @returns A promise that resolves after the specified amount of milliseconds + */ +export const sleep = promisify(setTimeout); + +/** + * List the methods of an object. + * @param obj The object to get the methods of. + * @returns A string with each method on a new line. + */ +export function getMethods(obj: Record): string { + // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class/31055217#31055217 + let props: string[] = []; + let obj_: Record = new Object(obj); + + do { + const l = Object.getOwnPropertyNames(obj_) + .concat(Object.getOwnPropertySymbols(obj_).map((s) => s.toString())) + .sort() + .filter( + (p, i, arr) => + typeof Object.getOwnPropertyDescriptor(obj_, p)?.['get'] !== 'function' && // ignore getters + typeof Object.getOwnPropertyDescriptor(obj_, p)?.['set'] !== 'function' && // ignore setters + typeof obj_[p] === 'function' && // only the methods + p !== 'constructor' && // not the constructor + (i == 0 || p !== arr[i - 1]) && // not overriding in this prototype + props.indexOf(p) === -1 // not overridden in a child + ); + + const reg = /\(([\s\S]*?)\)/; + props = props.concat( + l.map( + (p) => + `${obj_[p] && obj_[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${ + reg.exec(obj_[p].toString())?.[1] + ? reg + .exec(obj_[p].toString())?.[1] + .split(', ') + .map((arg) => arg.split('=')[0].trim()) + .join(', ') + : '' + });` + ) + ); + } while ( + (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain + Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...) + ); + + return props.join('\n'); +} + +/** + * List the symbols of an object. + * @param obj The object to get the symbols of. + * @returns An array of the symbols of the object. + */ +export function getSymbols(obj: Record): symbol[] { + let symbols: symbol[] = []; + let obj_: Record = new Object(obj); + + do { + const l = Object.getOwnPropertySymbols(obj_).sort(); + + symbols = [...symbols, ...l]; + } while ( + (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain + Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...) + ); + + return symbols; +} + +/** + * Checks if a user has a certain guild permission (doesn't check channel permissions). + * @param message The message to check the user from. + * @param permissions The permissions to check for. + * @returns The missing permissions or null if none are missing. + */ +export function userGuildPermCheck( + message: CommandMessage | SlashMessage, + permissions: typeof PermissionFlagsBits[keyof typeof PermissionFlagsBits][] +): PermissionsString[] | null { + if (!message.inGuild()) return null; + const missing = message.member?.permissions.missing(permissions) ?? []; + + return missing.length ? missing : null; +} + +/** + * Check if the client has certain permissions in the guild (doesn't check channel permissions). + * @param message The message to check the client user from. + * @param permissions The permissions to check for. + * @returns The missing permissions or null if none are missing. + */ +export function clientGuildPermCheck(message: CommandMessage | SlashMessage, permissions: bigint[]): PermissionsString[] | null { + const missing = message.guild?.members.me?.permissions.missing(permissions) ?? []; + + return missing.length ? missing : null; +} + +/** + * Check if the client has permission to send messages in the channel as well as check if they have other permissions + * in the guild (or the channel if `checkChannel` is `true`). + * @param message The message to check the client user from. + * @param permissions The permissions to check for. + * @param checkChannel Whether to check the channel permissions instead of the guild permissions. + * @returns The missing permissions or null if none are missing. + */ +export function clientSendAndPermCheck( + message: CommandMessage | SlashMessage, + permissions: bigint[] = [], + checkChannel = false +): PermissionsString[] | null { + if (!message.inGuild() || !message.channel) return null; + + const missing: PermissionsString[] = []; + const sendPerm = message.channel.isThread() ? 'SendMessages' : 'SendMessagesInThreads'; + + // todo: remove once forum channels are fixed + if (message.channel.parent === null && message.channel.isThread()) return null; + + if (!message.guild.members.me!.permissionsIn(message.channel!.id).has(sendPerm)) missing.push(sendPerm); + + missing.push( + ...(checkChannel + ? message.guild!.members.me!.permissionsIn(message.channel!.id!).missing(permissions) + : clientGuildPermCheck(message, permissions) ?? []) + ); + + return missing.length ? missing : null; +} + +export { deepLock as deepFreeze }; +export { Arg as arg }; +export { Format as format }; +export { DiscordConstants as discordConstants }; +export { AkairoUtil as akairo }; + +/** + * The link to invite the bot with all permissions. + */ +export function invite(client: TanzaniteClient) { + return client.generateInvite({ + permissions: + PermissionsBitField.All - + PermissionFlagsBits.UseEmbeddedActivities - + PermissionFlagsBits.ViewGuildInsights - + PermissionFlagsBits.Stream, + scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands] + }); +} + +/** + * Asset multiple statements at a time. + * @param args + */ +export function assertAll(...args: any[]): void { + for (let i = 0; i < args.length; i++) { + assert(args[i], `assertAll index ${i} failed`); + } +} + +/** + * Casts a string to a duration and reason for slash commands. + * @param arg The argument received. + * @param message The message that triggered the command. + * @returns The casted argument. + */ +export async function castDurationContent( + arg: string | ParsedDuration | null, + message: CommandMessage | SlashMessage +): Promise { + const res = typeof arg === 'string' ? await Arg.cast('contentWithDuration', message, arg) : arg; + + return { duration: res?.duration ?? 0, content: res?.content ?? '' }; +} + +export interface ParsedDurationRes { + duration: number; + content: string; +} + +/** + * Casts a string to a the specified argument type. + * @param type The type of the argument to cast to. + * @param arg The argument received. + * @param message The message that triggered the command. + * @returns The casted argument. + */ +export async function cast( + type: T, + arg: BaseBotArgumentType[T] | string, + message: CommandMessage | SlashMessage +) { + return typeof arg === 'string' ? await Arg.cast(type, message, arg) : arg; +} + +/** + * Overflows the description of an embed into multiple embeds. + * @param embed The options to be applied to the (first) embed. + * @param lines Each line of the description as an element in an array. + */ +export function overflowEmbed(embed: Omit, lines: string[], maxLength = 4096): EmbedBuilder[] { + const embeds: EmbedBuilder[] = []; + + const makeEmbed = () => { + embeds.push(new EmbedBuilder().setColor(embed.color ?? null)); + return embeds.at(-1)!; + }; + + for (const line of lines) { + let current = embeds.length ? embeds.at(-1)! : makeEmbed(); + let joined = current.data.description ? `${current.data.description}\n${line}` : line; + if (joined.length > maxLength) { + current = makeEmbed(); + joined = line; + } + + current.setDescription(joined); + } + + if (!embeds.length) makeEmbed(); + + if (embed.author) embeds.at(0)?.setAuthor(embed.author); + if (embed.title) embeds.at(0)?.setTitle(embed.title); + if (embed.url) embeds.at(0)?.setURL(embed.url); + if (embed.fields) embeds.at(-1)?.setFields(embed.fields); + if (embed.thumbnail) embeds.at(-1)?.setThumbnail(embed.thumbnail.url); + if (embed.footer) embeds.at(-1)?.setFooter(embed.footer); + if (embed.image) embeds.at(-1)?.setImage(embed.image.url); + if (embed.timestamp) embeds.at(-1)?.setTimestamp(new Date(embed.timestamp)); + + return embeds; +} + +/** + * Formats an error into a string. + * @param error The error to format. + * @param colors Whether to use colors in the output. + * @returns The formatted error. + */ +export function formatError(error: Error | any, colors = false): string { + if (!error) return error; + if (typeof error !== 'object') return String.prototype.toString.call(error); + if ( + getSymbols(error) + .map((s) => s.toString()) + .includes('Symbol(nodejs.util.inspect.custom)') + ) + return inspect(error, { colors }); + + return error.stack; +} + +export function deepWriteable(obj: T): DeepWritable { + return obj as DeepWritable; +} diff --git a/package.json b/package.json index 3106ca0..8db194c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "bush-bot", - "version": "3.0.2", + "name": "tanzanite", + "version": "3.1.0", "description": "An open-sourced multi-purpose moderation, and leveling bot.", "main": "dist/src/bot.js", "type": "module", - "repository": "https://github.com/NotEnoughUpdates/bush-bot", + "repository": "https://github.com/TanzaniteBot/tanzanite", "author": "IRONM00N#0001 (@IRONM00N)", "private": true, "contributors": [ diff --git a/src/bot.ts b/src/bot.ts index c9a7a49..486eb2e 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,6 +1,6 @@ console.log('Tanzanite is Starting'); -import { init } from '../lib/utils/BushLogger.js'; +import { init } from '../lib/utils/Logger.js'; // creates proxies on console.log and console.warn // also starts a REPL session init(); @@ -9,12 +9,12 @@ import { config } from '#config'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; import { Sentry } from '../lib/common/Sentry.js'; -import { BushClient } from '../lib/extensions/discord-akairo/BushClient.js'; +import { TanzaniteClient } from '../lib/extensions/discord-akairo/TanzaniteClient.js'; const isDry = process.argv.includes('dry'); if (!isDry && config.credentials.sentryDsn !== null) new Sentry(dirname(fileURLToPath(import.meta.url)) || process.cwd(), config); -BushClient.extendStructures(); -const client = new BushClient(config); +TanzaniteClient.extendStructures(); +const client = new TanzaniteClient(config); // @ts-ignore: for debugging purposes global.client = client; diff --git a/src/commands/_fake-command/ironmoon.ts b/src/commands/_fake-command/ironmoon.ts index cb50c7e..bb4fd88 100644 --- a/src/commands/_fake-command/ironmoon.ts +++ b/src/commands/_fake-command/ironmoon.ts @@ -1,6 +1,6 @@ -import { BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class IronmoonCommand extends BushCommand { +export default class IronmoonCommand extends BotCommand { public constructor() { super('ironmoon', { category: 'fake-commands', diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts index 0b09e54..e17a85b 100644 --- a/src/commands/admin/channelPermissions.ts +++ b/src/commands/admin/channelPermissions.ts @@ -1,6 +1,6 @@ import { Arg, - BushCommand, + BotCommand, ButtonPaginator, clientSendAndPermCheck, emojis, @@ -12,7 +12,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ChannelPermissionsCommand extends BushCommand { +export default class ChannelPermissionsCommand extends BotCommand { public constructor() { super('channelPermissions', { aliases: ['channel-perms', 'cperms', 'cperm', 'chanperms', 'chanperm', 'channel-permissions'], diff --git a/src/commands/admin/roleAll.ts b/src/commands/admin/roleAll.ts index dda536f..3675891 100644 --- a/src/commands/admin/roleAll.ts +++ b/src/commands/admin/roleAll.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, type ArgType, @@ -10,7 +10,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class RoleAllCommand extends BushCommand { +export default class RoleAllCommand extends BotCommand { public constructor() { super('roleAll', { aliases: ['role-all', 'rall'], diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts index a098f9d..5419658 100644 --- a/src/commands/config/blacklist.ts +++ b/src/commands/config/blacklist.ts @@ -2,7 +2,7 @@ import { addOrRemoveFromArray, AllowedMentions, Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -13,7 +13,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, GuildMember, PermissionFlagsBits, User } from 'discord.js'; -export default class BlacklistCommand extends BushCommand { +export default class BlacklistCommand extends BotCommand { public constructor() { super('blacklist', { aliases: ['blacklist', 'unblacklist'], diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index 39a44d1..a37d67c 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -1,6 +1,6 @@ import { addOrRemoveFromArray, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -47,7 +47,7 @@ export type Action = typeof allActions[number]; type SlashArgType = 'ROLE' | 'STRING' | 'CHANNEL' | 'USER'; type BaseSettingTypes = 'string' | 'channel' | 'role' | 'user' | 'custom'; -export default class ConfigCommand extends BushCommand { +export default class ConfigCommand extends BotCommand { public constructor() { super('config', { aliases: ['config', 'settings', 'setting', 'configure'], diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts index f9abb4a..a4a876c 100644 --- a/src/commands/config/disable.ts +++ b/src/commands/config/disable.ts @@ -2,7 +2,7 @@ import { addOrRemoveFromArray, AllowedMentions, Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, type ArgType, @@ -15,7 +15,7 @@ import { default as Fuse } from 'fuse.js'; assert(Fuse); -export default class DisableCommand extends BushCommand { +export default class DisableCommand extends BotCommand { private static blacklistedCommands = ['eval', 'disable']; public constructor() { @@ -72,7 +72,7 @@ export default class DisableCommand extends BushCommand { let action = (args.action ?? message.util?.parsed?.alias ?? 'toggle') as 'disable' | 'enable' | 'toggle'; const global = args.global && message.author.isOwner(); const commandID = - args.command instanceof BushCommand ? args.command.id : (await Arg.cast('commandAlias', message, args.command))?.id; + args.command instanceof BotCommand ? args.command.id : (await Arg.cast('commandAlias', message, args.command))?.id; if (!commandID) return await message.util.reply(`${emojis.error} Invalid command.`); diff --git a/src/commands/config/features.ts b/src/commands/config/features.ts index 0036349..55f99bd 100644 --- a/src/commands/config/features.ts +++ b/src/commands/config/features.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -20,7 +20,7 @@ import { type SelectMenuInteraction } from 'discord.js'; -export default class FeaturesCommand extends BushCommand { +export default class FeaturesCommand extends BotCommand { public constructor() { super('features', { aliases: ['features'], diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts index f79f60e..f058670 100644 --- a/src/commands/config/log.ts +++ b/src/commands/config/log.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, guildLogsArr, @@ -13,7 +13,7 @@ import assert from 'assert/strict'; import { ArgumentGeneratorReturn } from 'discord-akairo'; import { ApplicationCommandOptionType, ChannelType, PermissionFlagsBits } from 'discord.js'; -export default class LogCommand extends BushCommand { +export default class LogCommand extends BotCommand { public constructor() { super('log', { aliases: ['log', 'logging'], diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts index df4d146..fbb88ac 100644 --- a/src/commands/dev/__template.ts +++ b/src/commands/dev/__template.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, type ArgType, @@ -9,7 +9,7 @@ import { } from '#lib'; import { ApplicationCommandOptionType } from 'discord.js'; -export default class TemplateCommand extends BushCommand { +export default class TemplateCommand extends BotCommand { public constructor() { super('template', { aliases: ['template'], diff --git a/src/commands/dev/dm.ts b/src/commands/dev/dm.ts index c1340b1..e51651e 100644 --- a/src/commands/dev/dm.ts +++ b/src/commands/dev/dm.ts @@ -1,7 +1,7 @@ -import { BushCommand, clientSendAndPermCheck, emojis, format, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, emojis, format, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import { ApplicationCommandOptionType } from 'discord.js'; -export default class DMCommand extends BushCommand { +export default class DMCommand extends BotCommand { public constructor() { super('dm', { aliases: ['dm'], diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index dd21454..3929f99 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -2,10 +2,10 @@ import { ActivePunishment, assertAll, - BushCommand, - BushInspectOptions, + BotCommand, CodeBlockLang, colors, + CustomInspectOptions, emojis, getMethods, Global, @@ -56,9 +56,9 @@ const { transpile } = ts, /* eslint-enable @typescript-eslint/no-unused-vars */ // prettier-ignore -assertAll(ActivePunishment, BushCommand, Global, Guild, Level, ModLog, Shared, StickyRole, Snowflake_, canvas, exec, ActionRow, ButtonComponent, ButtonInteraction, Collection, Collector, CommandInteraction, ContextMenuCommandInteraction, DMChannel, Embed, Emoji, InteractionCollector, Message, Attachment, MessageCollector, OAuth2Scopes, PermissionFlagsBits, PermissionsBitField, ReactionCollector, SelectMenuComponent, path, ts, fileURLToPath, promisify, assert, transpile, sh, SnowflakeUtil, __dirname); +assertAll(ActivePunishment, BotCommand, Global, Guild, Level, ModLog, Shared, StickyRole, Snowflake_, canvas, exec, ActionRow, ButtonComponent, ButtonInteraction, Collection, Collector, CommandInteraction, ContextMenuCommandInteraction, DMChannel, Embed, Emoji, InteractionCollector, Message, Attachment, MessageCollector, OAuth2Scopes, PermissionFlagsBits, PermissionsBitField, ReactionCollector, SelectMenuComponent, path, ts, fileURLToPath, promisify, assert, transpile, sh, SnowflakeUtil, __dirname); -export default class EvalCommand extends BushCommand { +export default class EvalCommand extends BotCommand { public constructor() { super('eval', { aliases: ['eval', 'ev', 'evaluate'], @@ -316,7 +316,7 @@ export default class EvalCommand extends BushCommand { } } -type CodeBlockOptions = BushInspectOptions & { inspectStrings?: boolean }; +type CodeBlockOptions = CustomInspectOptions & { inspectStrings?: boolean }; interface CodeBlockCustomOptions extends CodeBlockOptions { prototype?: boolean; methods?: boolean; diff --git a/src/commands/dev/javascript.ts b/src/commands/dev/javascript.ts index 12ad6f7..43ccf94 100644 --- a/src/commands/dev/javascript.ts +++ b/src/commands/dev/javascript.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -13,7 +13,7 @@ import { ApplicationCommandOptionType, EmbedBuilder } from 'discord.js'; import { VM } from 'vm2'; assert(VM); -export default class JavascriptCommand extends BushCommand { +export default class JavascriptCommand extends BotCommand { public constructor() { super('javascript', { aliases: ['javascript', 'js'], diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts index 8125015..2d5b9d9 100644 --- a/src/commands/dev/reload.ts +++ b/src/commands/dev/reload.ts @@ -1,6 +1,6 @@ -import { BushCommand, clientSendAndPermCheck, emojis, formatError, shell, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, emojis, formatError, shell, type CommandMessage, type SlashMessage } from '#lib'; -export default class ReloadCommand extends BushCommand { +export default class ReloadCommand extends BotCommand { public constructor() { super('reload', { aliases: ['reload'], diff --git a/src/commands/dev/say.ts b/src/commands/dev/say.ts index c7b3c40..22ad9ba 100644 --- a/src/commands/dev/say.ts +++ b/src/commands/dev/say.ts @@ -1,7 +1,7 @@ -import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import { ApplicationCommandOptionType } from 'discord.js'; -export default class SayCommand extends BushCommand { +export default class SayCommand extends BotCommand { public constructor() { super('say', { aliases: ['say'], diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts index ab66f1c..6bba93c 100644 --- a/src/commands/dev/servers.ts +++ b/src/commands/dev/servers.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, ButtonPaginator, chunk, clientSendAndPermCheck, @@ -11,7 +11,7 @@ import { import { stripIndent } from '#tags'; import { type APIEmbed, type Guild } from 'discord.js'; -export default class ServersCommand extends BushCommand { +export default class ServersCommand extends BotCommand { public constructor() { super('servers', { aliases: ['servers', 'guilds'], diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts index 56a3b69..2d2a404 100644 --- a/src/commands/dev/sh.ts +++ b/src/commands/dev/sh.ts @@ -1,4 +1,4 @@ -import { ArgType, BushCommand, colors, emojis, formatError, type CommandMessage, type SlashMessage } from '#lib'; +import { ArgType, BotCommand, colors, emojis, formatError, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import chalk from 'chalk'; import { exec } from 'child_process'; @@ -15,7 +15,7 @@ const clean = (text: string | any) => { } else return text; }; -export default class ShCommand extends BushCommand { +export default class ShCommand extends BotCommand { public constructor() { super('sh', { aliases: ['sh', 'shell', 'cmd'], diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts index 24e8c9a..6a034bc 100644 --- a/src/commands/dev/superUser.ts +++ b/src/commands/dev/superUser.ts @@ -1,7 +1,7 @@ -import { BushCommand, clientSendAndPermCheck, emojis, format, type ArgType, type CommandMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, emojis, format, type ArgType, type CommandMessage } from '#lib'; import { type ArgumentGeneratorReturn, type ArgumentTypeCasterReturn } from 'discord-akairo'; -export default class SuperUserCommand extends BushCommand { +export default class SuperUserCommand extends BotCommand { public constructor() { super('superUser', { aliases: ['super-user', 'su'], diff --git a/src/commands/dev/syncAutomod.ts b/src/commands/dev/syncAutomod.ts index d0fc5e5..7a0fb58 100644 --- a/src/commands/dev/syncAutomod.ts +++ b/src/commands/dev/syncAutomod.ts @@ -1,8 +1,8 @@ -import { BushCommand, clientSendAndPermCheck, emojis, Shared, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, emojis, Shared, type CommandMessage, type SlashMessage } from '#lib'; import typescript from 'typescript'; import { NodeVM } from 'vm2'; -export default class SyncAutomodCommand extends BushCommand { +export default class SyncAutomodCommand extends BotCommand { public constructor() { super('syncAutomod', { aliases: ['sync-automod'], diff --git a/src/commands/dev/test.ts b/src/commands/dev/test.ts index 0606497..cc32657 100644 --- a/src/commands/dev/test.ts +++ b/src/commands/dev/test.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, ButtonPaginator, clientSendAndPermCheck, colors, @@ -22,7 +22,7 @@ import badLinksSecretArray from '../../../lib/badlinks-secret.js'; import badLinksArray from '../../../lib/badlinks.js'; import badWords from '../../../lib/badwords.js'; -export default class TestCommand extends BushCommand { +export default class TestCommand extends BotCommand { public constructor() { super('test', { aliases: ['test'], diff --git a/src/commands/fun/coinFlip.ts b/src/commands/fun/coinFlip.ts index 3adf7f2..66b6016 100644 --- a/src/commands/fun/coinFlip.ts +++ b/src/commands/fun/coinFlip.ts @@ -1,6 +1,6 @@ -import { BushCommand, clientSendAndPermCheck, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, type CommandMessage, type SlashMessage } from '#lib'; -export default class CoinFlipCommand extends BushCommand { +export default class CoinFlipCommand extends BotCommand { public constructor() { super('coinFlip', { aliases: ['coin-flip', 'cf'], diff --git a/src/commands/fun/dice.ts b/src/commands/fun/dice.ts index e7e5927..5941564 100644 --- a/src/commands/fun/dice.ts +++ b/src/commands/fun/dice.ts @@ -1,6 +1,6 @@ -import { BushCommand, clientSendAndPermCheck, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, type CommandMessage, type SlashMessage } from '#lib'; -export default class DiceCommand extends BushCommand { +export default class DiceCommand extends BotCommand { public constructor() { super('dice', { aliases: ['dice', 'die'], diff --git a/src/commands/fun/eightBall.ts b/src/commands/fun/eightBall.ts index eb5aee8..be9c7b5 100644 --- a/src/commands/fun/eightBall.ts +++ b/src/commands/fun/eightBall.ts @@ -1,7 +1,7 @@ -import { BushCommand, clientSendAndPermCheck, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, type CommandMessage, type SlashMessage } from '#lib'; import { ApplicationCommandOptionType } from 'discord.js'; -export default class EightBallCommand extends BushCommand { +export default class EightBallCommand extends BotCommand { public constructor() { super('eightBall', { aliases: ['eightball', '8ball'], diff --git a/src/commands/fun/minesweeper.ts b/src/commands/fun/minesweeper.ts index f2db6ee..ec21b11 100644 --- a/src/commands/fun/minesweeper.ts +++ b/src/commands/fun/minesweeper.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, OptArgType, @@ -12,7 +12,7 @@ import assert from 'assert/strict'; import { ApplicationCommandOptionType } from 'discord.js'; assert(Minesweeper); -export default class MinesweeperCommand extends BushCommand { +export default class MinesweeperCommand extends BotCommand { public constructor() { super('minesweeper', { aliases: ['minesweeper'], diff --git a/src/commands/info/avatar.ts b/src/commands/info/avatar.ts index 3eae98c..b97364f 100644 --- a/src/commands/info/avatar.ts +++ b/src/commands/info/avatar.ts @@ -1,7 +1,7 @@ -import { Arg, BushCommand, clientSendAndPermCheck, colors, type CommandMessage, type OptArgType, type SlashMessage } from '#lib'; +import { Arg, BotCommand, clientSendAndPermCheck, colors, type CommandMessage, type OptArgType, type SlashMessage } from '#lib'; import { ApplicationCommandOptionType, EmbedBuilder, GuildMember, PermissionFlagsBits } from 'discord.js'; -export default class AvatarCommand extends BushCommand { +export default class AvatarCommand extends BotCommand { public constructor() { super('avatar', { aliases: ['avatar', 'av'], diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts index d84fd4e..64ecf1d 100644 --- a/src/commands/info/botInfo.ts +++ b/src/commands/info/botInfo.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, colors, humanizeDuration, @@ -14,7 +14,7 @@ const { default: prettyBytes } = await import('pretty-bytes'); assert(prettyBytes); assert(os); -export default class BotInfoCommand extends BushCommand { +export default class BotInfoCommand extends BotCommand { public constructor() { super('botInfo', { aliases: ['bot-info', 'stats'], diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts index 7601562..c139e1a 100644 --- a/src/commands/info/color.ts +++ b/src/commands/info/color.ts @@ -1,7 +1,7 @@ import { AllowedMentions, Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, type ArgType, @@ -13,7 +13,7 @@ import { ApplicationCommandOptionType, EmbedBuilder, GuildMember, PermissionFlag import tinycolor from 'tinycolor2'; assert(tinycolor); -export default class ColorCommand extends BushCommand { +export default class ColorCommand extends BotCommand { public constructor() { super('color', { aliases: ['color'], diff --git a/src/commands/info/guildInfo.ts b/src/commands/info/guildInfo.ts index e67cdf4..97e4be3 100644 --- a/src/commands/info/guildInfo.ts +++ b/src/commands/info/guildInfo.ts @@ -1,7 +1,7 @@ import { akairo, Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -31,7 +31,7 @@ import { type Vanity } from 'discord.js'; -export default class GuildInfoCommand extends BushCommand { +export default class GuildInfoCommand extends BotCommand { public constructor() { super('guildInfo', { aliases: ['guild-info', 'serverinfo', 'guild', 'server', 'g'], @@ -222,9 +222,9 @@ export default class GuildInfoCommand extends BushCommand { const guildSecurity: string[] = []; guildSecurity.push( - `**Verification Level:** ${BushGuildVerificationLevel[guild.verificationLevel]}`, - `**Explicit Content Filter:** ${BushGuildExplicitContentFilter[guild.explicitContentFilter]}`, - `**Default Message Notifications:** ${BushGuildDefaultMessageNotifications[guild.defaultMessageNotifications]}`, + `**Verification Level:** ${MappedGuildVerificationLevel[guild.verificationLevel]}`, + `**Explicit Content Filter:** ${MappedGuildExplicitContentFilter[guild.explicitContentFilter]}`, + `**Default Message Notifications:** ${MappedGuildDefaultMessageNotifications[guild.defaultMessageNotifications]}`, `**2FA Required:** ${guild.mfaLevel === GuildMFALevel.Elevated ? 'True' : 'False'}` ); @@ -248,7 +248,7 @@ type RTCRegion = | 'india' | 'automatic'; -enum BushGuildVerificationLevel { +enum MappedGuildVerificationLevel { 'None' = GuildVerificationLevel.None, 'Low' = GuildVerificationLevel.Low, 'Medium' = GuildVerificationLevel.Medium, @@ -256,13 +256,13 @@ enum BushGuildVerificationLevel { 'Very High' = GuildVerificationLevel.VeryHigh } -enum BushGuildExplicitContentFilter { +enum MappedGuildExplicitContentFilter { 'Disabled' = GuildExplicitContentFilter.Disabled, 'Members Without Roles' = GuildExplicitContentFilter.MembersWithoutRoles, 'All Members' = GuildExplicitContentFilter.AllMembers } -enum BushGuildDefaultMessageNotifications { +enum MappedGuildDefaultMessageNotifications { 'All Messages' = GuildDefaultMessageNotifications.AllMessages, 'Only Mentions' = GuildDefaultMessageNotifications.OnlyMentions } diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index 70c8f20..6460b99 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, colors, format, @@ -26,7 +26,7 @@ import packageDotJSON from '../../../package.json' assert { type: 'json' }; assert(Fuse); assert(packageDotJSON); -export default class HelpCommand extends BushCommand { +export default class HelpCommand extends BotCommand { public constructor() { super('help', { aliases: ['help'], @@ -109,7 +109,7 @@ export default class HelpCommand extends BushCommand { return message.util.reply({ embeds: [embed], components: row.components.length ? [row] : undefined }); } - private helpIndividual(message: CommandMessage | SlashMessage, row: ActionRowBuilder, command: BushCommand) { + private helpIndividual(message: CommandMessage | SlashMessage, row: ActionRowBuilder, command: BotCommand) { const embed = new EmbedBuilder().setColor(colors.default).setTitle(`${command.id} Command`); let description = `${command.description ?? '*This command does not have a description.*'}`; @@ -127,7 +127,7 @@ export default class HelpCommand extends BushCommand { return message.util.reply(params); } - private addCommandUsage(embed: EmbedBuilder, command: BushCommand): void { + private addCommandUsage(embed: EmbedBuilder, command: BotCommand): void { if (command.usage?.length) { embed.addFields({ name: `» Usage${command.usage.length > 1 ? 's' : ''}`, @@ -136,7 +136,7 @@ export default class HelpCommand extends BushCommand { } } - private addCommandExamples(embed: EmbedBuilder, command: BushCommand): void { + private addCommandExamples(embed: EmbedBuilder, command: BotCommand): void { if (command.examples?.length) { embed.addFields({ name: `» Example${command.examples.length > 1 ? 's' : ''}`, @@ -145,7 +145,7 @@ export default class HelpCommand extends BushCommand { } } - private addCommandAliases(embed: EmbedBuilder, command: BushCommand): void { + private addCommandAliases(embed: EmbedBuilder, command: BotCommand): void { if (command.aliases?.length > 1) embed.addFields({ name: '» Aliases', @@ -153,7 +153,7 @@ export default class HelpCommand extends BushCommand { }); } - private addCommandArguments(embed: EmbedBuilder, command: BushCommand, isOwner = false, isSuperUser = false): void { + private addCommandArguments(embed: EmbedBuilder, command: BotCommand, isOwner = false, isSuperUser = false): void { const format = (id: string, req: boolean) => `${req ? '<' : '['}${id}${req ? '>' : ']'}`; const args = (command.argsInfo ?? []).filter((arg) => { if (arg.ownerOnly && !isOwner) return false; @@ -182,7 +182,7 @@ export default class HelpCommand extends BushCommand { } } - private addCommandRestrictions(embed: EmbedBuilder, command: BushCommand): void { + private addCommandRestrictions(embed: EmbedBuilder, command: BotCommand): void { if ( command.ownerOnly || command.superUserOnly || diff --git a/src/commands/info/icon.ts b/src/commands/info/icon.ts index b3434ec..71f02f9 100644 --- a/src/commands/info/icon.ts +++ b/src/commands/info/icon.ts @@ -1,8 +1,8 @@ -import { BushCommand, clientSendAndPermCheck, colors, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, colors, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { EmbedBuilder, escapeMarkdown, PermissionFlagsBits } from 'discord.js'; -export default class IconCommand extends BushCommand { +export default class IconCommand extends BotCommand { public constructor() { super('icon', { aliases: ['icon', 'guildavatar', 'severicon', 'guildicon'], diff --git a/src/commands/info/inviteInfo.ts b/src/commands/info/inviteInfo.ts index 5df86ad..590bf16 100644 --- a/src/commands/info/inviteInfo.ts +++ b/src/commands/info/inviteInfo.ts @@ -1,7 +1,7 @@ -import { Arg, ArgType, BushCommand, clientSendAndPermCheck, colors, type CommandMessage, type SlashMessage } from '#lib'; +import { Arg, ArgType, BotCommand, clientSendAndPermCheck, colors, type CommandMessage, type SlashMessage } from '#lib'; import { ApplicationCommandOptionType, EmbedBuilder, Invite, PermissionFlagsBits } from 'discord.js'; -export default class InviteInfoCommand extends BushCommand { +export default class InviteInfoCommand extends BotCommand { public constructor() { super('inviteInfo', { aliases: ['invite-info', 'ii'], diff --git a/src/commands/info/links.ts b/src/commands/info/links.ts index 8003c65..ecc09c8 100644 --- a/src/commands/info/links.ts +++ b/src/commands/info/links.ts @@ -1,11 +1,11 @@ -import { BushCommand, clientSendAndPermCheck, invite, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, invite, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import packageDotJSON from '../../../package.json' assert { type: 'json' }; assert(packageDotJSON); -export default class LinksCommand extends BushCommand { +export default class LinksCommand extends BotCommand { public constructor() { super('links', { aliases: ['links', 'invite', 'inv', 'support', 'github', 'source', 'oss'], diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts index 484c010..91bec1d 100644 --- a/src/commands/info/ping.ts +++ b/src/commands/info/ping.ts @@ -1,7 +1,7 @@ -import { BushCommand, clientSendAndPermCheck, colors, format, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, colors, format, type CommandMessage, type SlashMessage } from '#lib'; import { EmbedBuilder, PermissionFlagsBits, type Message } from 'discord.js'; -export default class PingCommand extends BushCommand { +export default class PingCommand extends BotCommand { public constructor() { super('ping', { aliases: ['ping'], diff --git a/src/commands/info/pronouns.ts b/src/commands/info/pronouns.ts index 0063f4c..47061fc 100644 --- a/src/commands/info/pronouns.ts +++ b/src/commands/info/pronouns.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, type CommandMessage, type OptArgType, @@ -8,7 +8,7 @@ import { } from '#lib'; import { ApplicationCommandOptionType, EmbedBuilder, escapeMarkdown, PermissionFlagsBits } from 'discord.js'; -export default class PronounsCommand extends BushCommand { +export default class PronounsCommand extends BotCommand { public constructor() { super('pronouns', { aliases: ['pronouns', 'pronoun'], diff --git a/src/commands/info/snowflake.ts b/src/commands/info/snowflake.ts index f30330a..deaf41c 100644 --- a/src/commands/info/snowflake.ts +++ b/src/commands/info/snowflake.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, colors, timestamp, @@ -19,7 +19,7 @@ import { type Snowflake } from 'discord.js'; -export default class SnowflakeCommand extends BushCommand { +export default class SnowflakeCommand extends BotCommand { public constructor() { super('snowflake', { aliases: ['snowflake', 'info', 'sf'], @@ -50,7 +50,7 @@ export default class SnowflakeCommand extends BushCommand { // Channel if (this.client.channels.cache.has(snowflake)) { const channel = this.client.channels.resolve(snowflake)!; - const channelInfo = [`**Type:** ${BushChannelType[channel.type] ?? ChannelType[channel.type]}`]; + const channelInfo = [`**Type:** ${MappedChannelType[channel.type] ?? ChannelType[channel.type]}`]; if (channel.type === ChannelType.DM) { channelInfo.push( `**Recipient:** ${escapeMarkdown(channel.recipient?.tag ?? '¯\\_(ツ)_/¯')} (${channel.recipient?.id ?? '¯\\_(ツ)_/¯'})` @@ -136,7 +136,7 @@ export default class SnowflakeCommand extends BushCommand { } } -enum BushChannelType { +enum MappedChannelType { 'Text' = 0, 'DM' = 1, 'Voice' = 2, diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts index 1f139b5..52875ef 100644 --- a/src/commands/info/userInfo.ts +++ b/src/commands/info/userInfo.ts @@ -1,7 +1,7 @@ import { Arg, + BotCommand, bots, - BushCommand, clientSendAndPermCheck, colors, emojis, @@ -30,7 +30,7 @@ import { type User } from 'discord.js'; -export default class UserInfoCommand extends BushCommand { +export default class UserInfoCommand extends BotCommand { public constructor() { super('userInfo', { aliases: ['user-info', 'user', 'u'], diff --git a/src/commands/leveling/leaderboard.ts b/src/commands/leveling/leaderboard.ts index bb41a12..109f398 100644 --- a/src/commands/leveling/leaderboard.ts +++ b/src/commands/leveling/leaderboard.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, ButtonPaginator, chunk, clientSendAndPermCheck, @@ -12,7 +12,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class LeaderboardCommand extends BushCommand { +export default class LeaderboardCommand extends BotCommand { public constructor() { super('leaderboard', { aliases: ['leaderboard', 'lb'], diff --git a/src/commands/leveling/level.ts b/src/commands/leveling/level.ts index ea2a724..2547b06 100644 --- a/src/commands/leveling/level.ts +++ b/src/commands/leveling/level.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, CanvasProgressBar, clientSendAndPermCheck, emojis, @@ -16,7 +16,7 @@ import { ApplicationCommandOptionType, AttachmentBuilder, Guild, PermissionFlags assert(canvas); assert(SimplifyNumber); -export default class LevelCommand extends BushCommand { +export default class LevelCommand extends BotCommand { public constructor() { super('level', { aliases: ['level', 'rank', 'lvl'], diff --git a/src/commands/leveling/levelRoles.ts b/src/commands/leveling/levelRoles.ts index 312623c..4973b01 100644 --- a/src/commands/leveling/levelRoles.ts +++ b/src/commands/leveling/levelRoles.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, type ArgType, @@ -11,7 +11,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; -export default class LevelRolesCommand extends BushCommand { +export default class LevelRolesCommand extends BotCommand { public constructor() { super('levelRole', { aliases: ['level-role', 'level-roles', 'lr'], diff --git a/src/commands/leveling/setLevel.ts b/src/commands/leveling/setLevel.ts index 8dc1cdf..cd30978 100644 --- a/src/commands/leveling/setLevel.ts +++ b/src/commands/leveling/setLevel.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -12,7 +12,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; -export default class SetLevelCommand extends BushCommand { +export default class SetLevelCommand extends BotCommand { public constructor() { super('setLevel', { aliases: ['set-level'], diff --git a/src/commands/leveling/setXp.ts b/src/commands/leveling/setXp.ts index 5cd3a61..23f4463 100644 --- a/src/commands/leveling/setXp.ts +++ b/src/commands/leveling/setXp.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -12,7 +12,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; -export default class SetXpCommand extends BushCommand { +export default class SetXpCommand extends BotCommand { public constructor() { super('setXp', { aliases: ['set-xp'], diff --git a/src/commands/moderation/_activePunishments.ts b/src/commands/moderation/_activePunishments.ts index 06e33f2..4ded902 100644 --- a/src/commands/moderation/_activePunishments.ts +++ b/src/commands/moderation/_activePunishments.ts @@ -1,6 +1,6 @@ // import { -// BushCommand, // clientSendAndPermCheck, +// CustomCommand, // emojis, // ModLog, // ModLogModel, @@ -14,7 +14,7 @@ // const punishmentTypes = ['ban', 'kick', 'mute', 'warn', 'role'] as const; -// export default class ActivePunishmentsCommand extends BushCommand { +// export default class ActivePunishmentsCommand extends CustomCommand { // public constructor() { // super('activePunishments', { // aliases: ['active-punishments', 'ap'], diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 00fde01..fdb8033 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -2,7 +2,7 @@ import { AllowedMentions, Arg, banResponse, - BushCommand, + BotCommand, castDurationContent, emojis, format, @@ -16,7 +16,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type User } from 'discord.js'; -export default class BanCommand extends BushCommand { +export default class BanCommand extends BotCommand { public constructor() { super('ban', { aliases: ['ban', 'force-ban', 'dban'], @@ -108,7 +108,7 @@ export default class BanCommand extends BushCommand { const opts = { reason: content, moderator: message.member, duration: duration, deleteDays: args.days }; - const responseCode = member ? await member.bushBan(opts) : await message.guild.bushBan({ user, ...opts }); + const responseCode = member ? await member.customBan(opts) : await message.guild.customBan({ user, ...opts }); return await message.util.reply({ content: BanCommand.formatCode(user, responseCode), diff --git a/src/commands/moderation/block.ts b/src/commands/moderation/block.ts index 00a1996..a3f7bc1 100644 --- a/src/commands/moderation/block.ts +++ b/src/commands/moderation/block.ts @@ -1,7 +1,7 @@ import { AllowedMentions, blockResponse, - BushCommand, + BotCommand, castDurationContent, clientSendAndPermCheck, emojis, @@ -17,7 +17,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class BlockCommand extends BushCommand { +export default class BlockCommand extends BotCommand { public constructor() { super('block', { aliases: ['block'], @@ -91,7 +91,7 @@ export default class BlockCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushBlock({ + const responseCode = await member.customBlock({ reason: content, moderator: message.member, duration: duration, diff --git a/src/commands/moderation/evidence.ts b/src/commands/moderation/evidence.ts index d36000f..bc4cdb2 100644 --- a/src/commands/moderation/evidence.ts +++ b/src/commands/moderation/evidence.ts @@ -1,11 +1,12 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, ModLog, OptArgType, regex, + TanzaniteEvent, userGuildPermCheck, type ArgType, type CommandMessage, @@ -15,7 +16,7 @@ import assert from 'assert/strict'; import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; import { ApplicationCommandOptionType, PermissionFlagsBits, type Message } from 'discord.js'; -export default class EvidenceCommand extends BushCommand { +export default class EvidenceCommand extends BotCommand { public constructor() { super('evidence', { aliases: ['evidence'], @@ -122,7 +123,7 @@ export default class EvidenceCommand extends BushCommand { entry.evidence = _evidence.trim(); await entry.save(); - this.client.emit('bushUpdateModlog', message.member!, entry.id, 'evidence', oldEntry, entry.evidence); + this.client.emit(TanzaniteEvent.UpdateModlog, message.member!, entry.id, 'evidence', oldEntry, entry.evidence); return message.util.reply(`${emojis.success} Successfully updated the evidence for case ${format.input(entry.id)}.`); } diff --git a/src/commands/moderation/hideCase.ts b/src/commands/moderation/hideCase.ts index 9bd1d24..ca87ea5 100644 --- a/src/commands/moderation/hideCase.ts +++ b/src/commands/moderation/hideCase.ts @@ -1,9 +1,10 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, ModLog, + TanzaniteEvent, userGuildPermCheck, type CommandMessage, type SlashMessage @@ -11,7 +12,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; -export default class HideCaseCommand extends BushCommand { +export default class HideCaseCommand extends BotCommand { public constructor() { super('hideCase', { aliases: ['hide-case', 'hide_case', 'show-case', 'show_case', 'cover-up-mod-abuse', 'cover_up_mod_abuse'], @@ -47,7 +48,7 @@ export default class HideCaseCommand extends BushCommand { entry.hidden = !entry.hidden; await entry.save(); - this.client.emit('bushUpdateModlog', message.member!, entry.id, 'hidden', oldEntry, entry.hidden); + this.client.emit(TanzaniteEvent.UpdateModlog, message.member!, entry.id, 'hidden', oldEntry, entry.hidden); return await message.util.reply(`${emojis.success} CaseID ${format.input(caseID)} is ${action}.`); } diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index 7807166..a5938f8 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -15,7 +15,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class KickCommand extends BushCommand { +export default class KickCommand extends BotCommand { public constructor() { super('kick', { aliases: ['kick'], @@ -77,7 +77,7 @@ export default class KickCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushKick({ + const responseCode = await member.customKick({ reason, moderator: message.member }); diff --git a/src/commands/moderation/lockdown.ts b/src/commands/moderation/lockdown.ts index 0561767..57f9caf 100644 --- a/src/commands/moderation/lockdown.ts +++ b/src/commands/moderation/lockdown.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, ConfirmationPrompt, @@ -23,7 +23,7 @@ import { VoiceChannel } from 'discord.js'; -export default class LockdownCommand extends BushCommand { +export default class LockdownCommand extends BotCommand { public constructor() { super('lockdown', { aliases: ['lockdown', 'lock'], diff --git a/src/commands/moderation/massBan.ts b/src/commands/moderation/massBan.ts index db50c44..4ba4f47 100644 --- a/src/commands/moderation/massBan.ts +++ b/src/commands/moderation/massBan.ts @@ -2,12 +2,13 @@ import { Arg, BanResponse, banResponse, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, overflowEmbed, regex, + TanzaniteEvent, type ArgType, type CommandMessage, type OptArgType, @@ -16,7 +17,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, Collection, PermissionFlagsBits } from 'discord.js'; -export default class MassBanCommand extends BushCommand { +export default class MassBanCommand extends BotCommand { public constructor() { super('massBan', { aliases: ['mass-ban', 'mass-dban'], @@ -96,7 +97,13 @@ export default class MassBanCommand extends BushCommand { const res = await Promise.all(promises); const map = new Collection(res.map((r, i) => [ids[i], r])); - this.client.emit('massBan', message.member!, message.guild!, args.reason ? args.reason.trim() : 'No reason provided.', map); + this.client.emit( + TanzaniteEvent.MassBan, + message.member!, + message.guild!, + args.reason ? args.reason.trim() : 'No reason provided.', + map + ); const success = (res: BanResponse): boolean => [banResponse.SUCCESS, banResponse.DM_ERROR].includes(res as any); diff --git a/src/commands/moderation/massEvidence.ts b/src/commands/moderation/massEvidence.ts index cecf273..b44060e 100644 --- a/src/commands/moderation/massEvidence.ts +++ b/src/commands/moderation/massEvidence.ts @@ -1,11 +1,12 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, ModLog, overflowEmbed, regex, + TanzaniteEvent, type ArgType, type CommandMessage, type OptArgType, @@ -15,7 +16,7 @@ import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; import EvidenceCommand from './evidence.js'; -export default class MassEvidenceCommand extends BushCommand { +export default class MassEvidenceCommand extends BotCommand { public constructor() { super('massEvidence', { aliases: ['mass-evidence'], @@ -93,7 +94,7 @@ export default class MassEvidenceCommand extends BushCommand { return `${emojis.success} ${id} - ${case_.id}`; }); - this.client.emit('massEvidence', message.member!, message.guild, evidence, lines); + this.client.emit(TanzaniteEvent.MassEvidence, message.member!, message.guild, evidence, lines); const embeds = overflowEmbed( { diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts index 527ba36..08e5397 100644 --- a/src/commands/moderation/modlog.ts +++ b/src/commands/moderation/modlog.ts @@ -1,6 +1,6 @@ import { Arg, - BushCommand, + BotCommand, ButtonPaginator, chunk, clientSendAndPermCheck, @@ -17,7 +17,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, escapeMarkdown, PermissionFlagsBits, User } from 'discord.js'; -export default class ModlogCommand extends BushCommand { +export default class ModlogCommand extends BotCommand { public static separator = '\n━━━━━━━━━━━━━━━\n'; public constructor() { diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 4627f2c..debcf03 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, castDurationContent, clientSendAndPermCheck, emojis, @@ -17,7 +17,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class MuteCommand extends BushCommand { +export default class MuteCommand extends BotCommand { public constructor() { super('mute', { aliases: ['mute'], @@ -86,7 +86,7 @@ export default class MuteCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushMute({ + const responseCode = await member.customMute({ reason: content, moderator: message.member, duration diff --git a/src/commands/moderation/myLogs.ts b/src/commands/moderation/myLogs.ts index c1cc448..34ab8e0 100644 --- a/src/commands/moderation/myLogs.ts +++ b/src/commands/moderation/myLogs.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, ButtonPaginator, chunk, clientSendAndPermCheck, @@ -14,7 +14,7 @@ import { import { ApplicationCommandOptionType } from 'discord.js'; import { input, sanitizeInputForDiscord } from '../../../lib/utils/Format.js'; import ModlogCommand from './modlog.js'; -export default class MyLogsCommand extends BushCommand { +export default class MyLogsCommand extends BotCommand { public constructor() { super('myLogs', { aliases: ['my-logs', 'my-log', 'my-modlogs', 'my-modlog'], diff --git a/src/commands/moderation/purge.ts b/src/commands/moderation/purge.ts index 383c439..106f394 100644 --- a/src/commands/moderation/purge.ts +++ b/src/commands/moderation/purge.ts @@ -1,9 +1,10 @@ import { Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, OptArgType, + TanzaniteEvent, type ArgType, type CommandMessage, type SlashMessage @@ -11,7 +12,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, Collection, PermissionFlagsBits, type Message } from 'discord.js'; -export default class PurgeCommand extends BushCommand { +export default class PurgeCommand extends BotCommand { public constructor() { super('purge', { aliases: ['purge', 'clear'], @@ -83,7 +84,7 @@ export default class PurgeCommand extends BushCommand { const purged = await message.channel!.bulkDelete(messages, true).catch(() => null); if (!purged) return message.util.reply(`${emojis.error} Failed to purge messages.`).catch(() => null); else { - this.client.emit('bushPurge', message.author, message.guild, message.channel!, messages); + this.client.emit(TanzaniteEvent.Purge, message.author, message.guild, message.channel!, messages); await message.util.send(`${emojis.success} Successfully purged **${purged.size}** messages.`); /* .then(async (purgeMessage) => { if (!message.util.isSlashMessage(message)) { diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts index cc3713c..5baeffc 100644 --- a/src/commands/moderation/removeReactionEmoji.ts +++ b/src/commands/moderation/removeReactionEmoji.ts @@ -1,6 +1,6 @@ import { Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -11,7 +11,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, Message, PermissionFlagsBits } from 'discord.js'; -export default class RemoveReactionEmojiCommand extends BushCommand { +export default class RemoveReactionEmojiCommand extends BotCommand { public constructor() { super('removeReactionEmoji', { aliases: ['remove-reaction-emoji', 'rre'], diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts index 5251be7..6b874da 100644 --- a/src/commands/moderation/role.ts +++ b/src/commands/moderation/role.ts @@ -1,7 +1,7 @@ import { addRoleResponse, AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -17,7 +17,7 @@ import assert from 'assert/strict'; import { type ArgumentGeneratorReturn } from 'discord-akairo'; import { ApplicationCommandOptionType, PermissionFlagsBits, type Snowflake } from 'discord.js'; -export default class RoleCommand extends BushCommand { +export default class RoleCommand extends BotCommand { public constructor() { super('role', { aliases: ['role', 'rr', 'ar', 'ra'], @@ -169,7 +169,7 @@ export default class RoleCommand extends BushCommand { const shouldLog = this.punishmentRoleNames.includes(args.role.name); - const responseCode = await args.member[`bush${args.action === 'add' ? 'Add' : 'Remove'}Role`]({ + const responseCode = await args.member[`custom${args.action === 'add' ? 'Add' : 'Remove'}Role`]({ moderator: message.member!, addToModlog: shouldLog, role: args.role, diff --git a/src/commands/moderation/slowmode.ts b/src/commands/moderation/slowmode.ts index 2e603c6..66bc2a0 100644 --- a/src/commands/moderation/slowmode.ts +++ b/src/commands/moderation/slowmode.ts @@ -1,6 +1,6 @@ import { Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -14,7 +14,7 @@ import assert from 'assert/strict'; import { Argument } from 'discord-akairo'; import { ApplicationCommandOptionType, ChannelType, PermissionFlagsBits } from 'discord.js'; -export default class SlowmodeCommand extends BushCommand { +export default class SlowmodeCommand extends BotCommand { public constructor() { super('slowmode', { aliases: ['slowmode', 'slow'], diff --git a/src/commands/moderation/timeout.ts b/src/commands/moderation/timeout.ts index 3e2b10d..5ab62e4 100644 --- a/src/commands/moderation/timeout.ts +++ b/src/commands/moderation/timeout.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, castDurationContent, clientSendAndPermCheck, emojis, @@ -15,7 +15,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class TimeoutCommand extends BushCommand { +export default class TimeoutCommand extends BotCommand { public constructor() { super('timeout', { aliases: ['timeout', 'to'], @@ -80,7 +80,7 @@ export default class TimeoutCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushTimeout({ + const responseCode = await member.customTimeout({ reason: content, moderator: message.member, duration: duration diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index 3a6221a..29dedab 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -1,7 +1,7 @@ import { AllowedMentions, Arg, - BushCommand, + BotCommand, emojis, format, unbanResponse, @@ -14,7 +14,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type User } from 'discord.js'; -export default class UnbanCommand extends BushCommand { +export default class UnbanCommand extends BotCommand { public constructor() { super('unban', { aliases: ['unban'], @@ -55,7 +55,7 @@ export default class UnbanCommand extends BushCommand { ) { assert(message.inGuild()); - const responseCode = await message.guild.bushUnban({ + const responseCode = await message.guild.customUnban({ user, moderator: message.author, reason diff --git a/src/commands/moderation/unblock.ts b/src/commands/moderation/unblock.ts index 6533da0..e342f0f 100644 --- a/src/commands/moderation/unblock.ts +++ b/src/commands/moderation/unblock.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -16,7 +16,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class UnblockCommand extends BushCommand { +export default class UnblockCommand extends BotCommand { public constructor() { super('unblock', { aliases: ['unblock'], @@ -83,7 +83,7 @@ export default class UnblockCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushUnblock({ + const responseCode = await member.customUnblock({ reason: args.reason ?? '', moderator: message.member, channel: message.channel diff --git a/src/commands/moderation/unlockdown.ts b/src/commands/moderation/unlockdown.ts index 873ea87..38d2fe6 100644 --- a/src/commands/moderation/unlockdown.ts +++ b/src/commands/moderation/unlockdown.ts @@ -1,8 +1,8 @@ import { LockdownCommand } from '#commands'; -import { BushCommand, clientSendAndPermCheck, type ArgType, type CommandMessage, type OptArgType, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, type ArgType, type CommandMessage, type OptArgType, type SlashMessage } from '#lib'; import { ApplicationCommandOptionType, Constants, PermissionFlagsBits } from 'discord.js'; -export default class UnlockdownCommand extends BushCommand { +export default class UnlockdownCommand extends BotCommand { public constructor() { super('unlockdown', { aliases: ['unlockdown', 'unlock', 'lockup'], diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index f1e74ab..4f408eb 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -11,9 +11,9 @@ import { } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; -import { BushCommand } from '../../../lib/extensions/discord-akairo/BushCommand.js'; +import { BotCommand } from '../../../lib/extensions/discord-akairo/BotCommand.js'; -export default class UnmuteCommand extends BushCommand { +export default class UnmuteCommand extends BotCommand { public constructor() { super('unmute', { aliases: ['unmute'], @@ -74,7 +74,7 @@ export default class UnmuteCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushUnmute({ + const responseCode = await member.customUnmute({ reason, moderator: message.member }); diff --git a/src/commands/moderation/untimeout.ts b/src/commands/moderation/untimeout.ts index 6ca28f4..8bc977b 100644 --- a/src/commands/moderation/untimeout.ts +++ b/src/commands/moderation/untimeout.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -15,7 +15,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class UntimeoutCommand extends BushCommand { +export default class UntimeoutCommand extends BotCommand { public constructor() { super('untimeout', { aliases: ['untimeout', 'remove-timeout'], @@ -80,7 +80,7 @@ export default class UntimeoutCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.bushRemoveTimeout({ + const responseCode = await member.customRemoveTimeout({ reason: args.reason ?? undefined, moderator: message.member }); diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index e1d1e90..9547583 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -17,7 +17,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; -export default class WarnCommand extends BushCommand { +export default class WarnCommand extends BotCommand { public constructor() { super('warn', { aliases: ['warn'], @@ -77,7 +77,7 @@ export default class WarnCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const { result: responseCode, caseNum } = await member.bushWarn({ + const { result: responseCode, caseNum } = await member.customWarn({ reason, moderator: message.member }); diff --git a/src/commands/moulberry-bush/capePermissions.ts b/src/commands/moulberry-bush/capePermissions.ts index 793ac59..fdeddd8 100644 --- a/src/commands/moulberry-bush/capePermissions.ts +++ b/src/commands/moulberry-bush/capePermissions.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -12,7 +12,7 @@ import { } from '#lib'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class CapePermissionsCommand extends BushCommand { +export default class CapePermissionsCommand extends BotCommand { public constructor() { super('capePermissions', { aliases: ['cape-permissions', 'cape-perms', 'cape-perm'], diff --git a/src/commands/moulberry-bush/capes.ts b/src/commands/moulberry-bush/capes.ts index 6ffc540..79c163f 100644 --- a/src/commands/moulberry-bush/capes.ts +++ b/src/commands/moulberry-bush/capes.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, ButtonPaginator, clientSendAndPermCheck, colors, @@ -18,7 +18,7 @@ import { default as Fuse } from 'fuse.js'; assert(Fuse); -export default class CapesCommand extends BushCommand { +export default class CapesCommand extends BotCommand { public constructor() { super('capes', { aliases: ['capes', 'cape'], diff --git a/src/commands/moulberry-bush/giveawayPing.ts b/src/commands/moulberry-bush/giveawayPing.ts index 8f8941f..aebcc52 100644 --- a/src/commands/moulberry-bush/giveawayPing.ts +++ b/src/commands/moulberry-bush/giveawayPing.ts @@ -1,8 +1,8 @@ -import { AllowedMentions, BushCommand, clientSendAndPermCheck, emojis, mappings, type CommandMessage } from '#lib'; +import { AllowedMentions, BotCommand, clientSendAndPermCheck, emojis, mappings, type CommandMessage } from '#lib'; import assert from 'assert/strict'; import { PermissionFlagsBits } from 'discord.js'; -export default class GiveawayPingCommand extends BushCommand { +export default class GiveawayPingCommand extends BotCommand { public constructor() { super('giveawayPing', { aliases: ['giveaway-ping', 'giveaway-pong'], diff --git a/src/commands/moulberry-bush/moulHammer.ts b/src/commands/moulberry-bush/moulHammer.ts index 7bb514e..0407428 100644 --- a/src/commands/moulberry-bush/moulHammer.ts +++ b/src/commands/moulberry-bush/moulHammer.ts @@ -1,16 +1,8 @@ -import { - BushCommand, - clientSendAndPermCheck, - colors, - mappings, - type ArgType, - type CommandMessage, - type SlashMessage -} from '#lib'; +import { BotCommand, clientSendAndPermCheck, colors, mappings, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class MoulHammerCommand extends BushCommand { +export default class MoulHammerCommand extends BotCommand { public constructor() { super('moulHammer', { aliases: ['moul-hammer'], diff --git a/src/commands/moulberry-bush/neuRepo.ts b/src/commands/moulberry-bush/neuRepo.ts index 4baae7d..d8eb7ed 100644 --- a/src/commands/moulberry-bush/neuRepo.ts +++ b/src/commands/moulberry-bush/neuRepo.ts @@ -1,4 +1,4 @@ -import { BushCommand, clientSendAndPermCheck, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import canvas from '@napi-rs/canvas'; import { ApplicationCommandOptionType, @@ -11,7 +11,7 @@ import { join } from 'path'; import tinycolor from 'tinycolor2'; import { formattingInfo, RawNeuItem } from '../../../lib/utils/Minecraft.js'; -export default class NeuRepoCommand extends BushCommand { +export default class NeuRepoCommand extends BotCommand { public static items: { name: string; id: string }[] = []; public constructor() { diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts index d0c4f33..8eb412c 100644 --- a/src/commands/moulberry-bush/report.ts +++ b/src/commands/moulberry-bush/report.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -13,7 +13,7 @@ import { stripIndent } from '#tags'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ReportCommand extends BushCommand { +export default class ReportCommand extends BotCommand { public constructor() { super('report', { aliases: ['report'], diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts index ab5500d..5f2155f 100644 --- a/src/commands/moulberry-bush/rule.ts +++ b/src/commands/moulberry-bush/rule.ts @@ -1,7 +1,7 @@ import { AllowedMentions, Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, mappings, type CommandMessage, @@ -67,7 +67,7 @@ const rules = [ } ]; -export default class RuleCommand extends BushCommand { +export default class RuleCommand extends BotCommand { public constructor() { super('rule', { aliases: ['rule', 'rules'], diff --git a/src/commands/moulberry-bush/serverStatus.ts b/src/commands/moulberry-bush/serverStatus.ts index cde3f04..709de26 100644 --- a/src/commands/moulberry-bush/serverStatus.ts +++ b/src/commands/moulberry-bush/serverStatus.ts @@ -1,7 +1,7 @@ -import { BushCommand, clientSendAndPermCheck, colors, emojis, type CommandMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, colors, emojis, type CommandMessage } from '#lib'; import { EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ServerStatusCommand extends BushCommand { +export default class ServerStatusCommand extends BotCommand { public constructor() { super('serverStatus', { aliases: ['server-status', 'ss'], diff --git a/src/commands/moulberry-bush/solved.ts b/src/commands/moulberry-bush/solved.ts index ac7684a..197a3ce 100644 --- a/src/commands/moulberry-bush/solved.ts +++ b/src/commands/moulberry-bush/solved.ts @@ -1,7 +1,7 @@ -import { BushCommand, clientSendAndPermCheck, emojis, mappings, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, emojis, mappings, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; -export default class Solved extends BushCommand { +export default class Solved extends BotCommand { public constructor() { super('solved', { aliases: ['solved'], diff --git a/src/commands/tickets/ticket-!.ts b/src/commands/tickets/ticket-!.ts index 6ec4093..d1462fb 100644 --- a/src/commands/tickets/ticket-!.ts +++ b/src/commands/tickets/ticket-!.ts @@ -1,4 +1,4 @@ -import { BushCommand, clientSendAndPermCheck, deepWriteable, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, deepWriteable, type SlashMessage } from '#lib'; import { Flag, type ArgumentGeneratorReturn, type SlashOption } from 'discord-akairo'; import { ApplicationCommandOptionType } from 'discord.js'; @@ -18,7 +18,7 @@ export const ticketSubcommands = deepWriteable({ } } as const); -export default class TicketCommand extends BushCommand { +export default class TicketCommand extends BotCommand { public constructor() { super('ticket', { aliases: ['ticket'], diff --git a/src/commands/utilities/_poll.ts b/src/commands/utilities/_poll.ts index 1ceb13a..4655f36 100644 --- a/src/commands/utilities/_poll.ts +++ b/src/commands/utilities/_poll.ts @@ -1,7 +1,7 @@ -// import { BushCommand, clientSendAndPermCheck, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +// import { clientSendAndPermCheck, CustomCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; // import { ApplicationCommandOptionType, ComponentType } from 'discord.js'; -// export default class PollCommand extends BushCommand { +// export default class PollCommand extends CustomCommand { // public constructor() { // super('poll', { // aliases: ['poll', 'quick-poll'], diff --git a/src/commands/utilities/activity.ts b/src/commands/utilities/activity.ts index e5c2cdd..414e6a2 100644 --- a/src/commands/utilities/activity.ts +++ b/src/commands/utilities/activity.ts @@ -1,10 +1,10 @@ import { - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, regex, type ArgType, - type BushArgumentTypeCaster, + type BotArgumentTypeCaster, type CommandMessage, type SlashMessage } from '#lib'; @@ -75,14 +75,14 @@ function map(phase: string): Activity | null { return null; } -const activityTypeCaster: BushArgumentTypeCaster = (message: CommandMessage, phrase: string) => { +const activityTypeCaster: BotArgumentTypeCaster = (message: CommandMessage, phrase: string) => { const parsedPhrase = phrase ?? message.util.parsed?.alias !== 'activity' ? message.util.parsed?.alias : undefined; if (!parsedPhrase) return null; const mappedPhrase = map(parsedPhrase)?.id; return mappedPhrase ?? null; }; -export default class ActivityCommand extends BushCommand { +export default class ActivityCommand extends BotCommand { public constructor() { super('activity', { aliases: ['activity', ...Object.values(activityMap).flatMap((a) => a.aliases)], diff --git a/src/commands/utilities/calculator.ts b/src/commands/utilities/calculator.ts index dc5593b..c9d300c 100644 --- a/src/commands/utilities/calculator.ts +++ b/src/commands/utilities/calculator.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -13,7 +13,7 @@ import { evaluate } from 'mathjs'; assert(evaluate); -export default class CalculatorCommand extends BushCommand { +export default class CalculatorCommand extends BotCommand { public constructor() { super('calculator', { aliases: ['calculator', 'calc', 'math'], diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts index 065e9e5..12a016b 100644 --- a/src/commands/utilities/decode.ts +++ b/src/commands/utilities/decode.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, capitalize, clientSendAndPermCheck, colors, @@ -13,7 +13,7 @@ import { ApplicationCommandOptionType, EmbedBuilder } from 'discord.js'; const encodingTypesArray = ['ascii', 'utf8', 'utf-8', 'utf16le', 'ucs2', 'ucs-2', 'base64', 'latin1', 'binary', 'hex']; const encodingTypesString = encodingTypesArray.map((e) => `\`${e}\``).join(', '); -export default class DecodeCommand extends BushCommand { +export default class DecodeCommand extends BotCommand { public constructor() { super('decode', { aliases: ['decode', 'encode'], diff --git a/src/commands/utilities/hash.ts b/src/commands/utilities/hash.ts index 251d5e6..1c741bd 100644 --- a/src/commands/utilities/hash.ts +++ b/src/commands/utilities/hash.ts @@ -1,4 +1,4 @@ -// import { BushCommand, clientSendAndPermCheck, type CommandMessage } from '#lib'; +// import { clientSendAndPermCheck, CustomCommand, type CommandMessage } from '#lib'; // import assert from 'assert/strict'; // import crypto from 'crypto'; // import { ApplicationCommandOptionType } from 'discord.js'; @@ -7,7 +7,7 @@ // assert(crypto); // assert(got); -// export default class HashCommand extends BushCommand { +// export default class HashCommand extends CustomCommand { // public constructor() { // super('hash', { // aliases: ['hash'], diff --git a/src/commands/utilities/highlight-!.ts b/src/commands/utilities/highlight-!.ts index f2ee259..4995e97 100644 --- a/src/commands/utilities/highlight-!.ts +++ b/src/commands/utilities/highlight-!.ts @@ -1,4 +1,4 @@ -import { BushCommand, clientSendAndPermCheck, deepWriteable, Highlight, HighlightWord, type SlashMessage } from '#lib'; +import { BotCommand, clientSendAndPermCheck, deepWriteable, Highlight, HighlightWord, type SlashMessage } from '#lib'; import { Flag, type ArgumentGeneratorReturn, type SlashOption } from 'discord-akairo'; import { ApplicationCommandOptionType, Constants, type AutocompleteInteraction, type CacheType } from 'discord.js'; @@ -133,7 +133,7 @@ export const highlightSubcommands = deepWriteable({ } } as const); -export default class HighlightCommand extends BushCommand { +export default class HighlightCommand extends BotCommand { public constructor() { super('highlight', { aliases: ['highlight', 'hl'], diff --git a/src/commands/utilities/highlight-add.ts b/src/commands/utilities/highlight-add.ts index e7d3ec6..101b26e 100644 --- a/src/commands/utilities/highlight-add.ts +++ b/src/commands/utilities/highlight-add.ts @@ -1,8 +1,8 @@ -import { AllowedMentions, BushCommand, emojis, format, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, emojis, format, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightAddCommand extends BushCommand { +export default class HighlightAddCommand extends BotCommand { public constructor() { super('highlight-add', { aliases: [], diff --git a/src/commands/utilities/highlight-block.ts b/src/commands/utilities/highlight-block.ts index 58e7766..b16852e 100644 --- a/src/commands/utilities/highlight-block.ts +++ b/src/commands/utilities/highlight-block.ts @@ -1,11 +1,11 @@ -import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; import { BaseChannel, GuildMember, User } from 'discord.js'; import { HighlightBlockResult } from '../../../lib/common/HighlightManager.js'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightBlockCommand extends BushCommand { +export default class HighlightBlockCommand extends BotCommand { public constructor() { super('highlight-block', { aliases: [], diff --git a/src/commands/utilities/highlight-clear.ts b/src/commands/utilities/highlight-clear.ts index 5451e4e..d02cda6 100644 --- a/src/commands/utilities/highlight-clear.ts +++ b/src/commands/utilities/highlight-clear.ts @@ -1,8 +1,8 @@ -import { BushCommand, ConfirmationPrompt, emojis, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, ConfirmationPrompt, emojis, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightClearCommand extends BushCommand { +export default class HighlightClearCommand extends BotCommand { public constructor() { super('highlight-clear', { aliases: [], diff --git a/src/commands/utilities/highlight-matches.ts b/src/commands/utilities/highlight-matches.ts index 863445e..d54fd4a 100644 --- a/src/commands/utilities/highlight-matches.ts +++ b/src/commands/utilities/highlight-matches.ts @@ -1,10 +1,10 @@ -import { BushCommand, ButtonPaginator, chunk, colors, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { BotCommand, ButtonPaginator, chunk, colors, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { type ArgumentGeneratorReturn } from 'discord-akairo'; import { type APIEmbed } from 'discord.js'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightMatchesCommand extends BushCommand { +export default class HighlightMatchesCommand extends BotCommand { public constructor() { super('highlight-matches', { aliases: [], diff --git a/src/commands/utilities/highlight-remove.ts b/src/commands/utilities/highlight-remove.ts index fd2fa90..b91bc8c 100644 --- a/src/commands/utilities/highlight-remove.ts +++ b/src/commands/utilities/highlight-remove.ts @@ -1,8 +1,8 @@ -import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightRemoveCommand extends BushCommand { +export default class HighlightRemoveCommand extends BotCommand { public constructor() { super('highlight-remove', { aliases: [], diff --git a/src/commands/utilities/highlight-show.ts b/src/commands/utilities/highlight-show.ts index 2ed5ed1..6d386e9 100644 --- a/src/commands/utilities/highlight-show.ts +++ b/src/commands/utilities/highlight-show.ts @@ -1,9 +1,9 @@ -import { AllowedMentions, BushCommand, colors, emojis, Highlight, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, colors, emojis, Highlight, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert/strict'; import { EmbedBuilder } from 'discord.js'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightShowCommand extends BushCommand { +export default class HighlightShowCommand extends BotCommand { public constructor() { super('highlight-show', { aliases: [], diff --git a/src/commands/utilities/highlight-unblock.ts b/src/commands/utilities/highlight-unblock.ts index 2238831..0f2dd78 100644 --- a/src/commands/utilities/highlight-unblock.ts +++ b/src/commands/utilities/highlight-unblock.ts @@ -1,11 +1,11 @@ -import { AllowedMentions, BushCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, emojis, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; import assert from 'assert'; import { Argument, ArgumentGeneratorReturn } from 'discord-akairo'; import { BaseChannel, GuildMember, User } from 'discord.js'; import { HighlightUnblockResult } from '../../../lib/common/HighlightManager.js'; import { highlightSubcommands } from './highlight-!.js'; -export default class HighlightUnblockCommand extends BushCommand { +export default class HighlightUnblockCommand extends BotCommand { public constructor() { super('highlight-unblock', { aliases: [], diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts index bfe1f10..a1645dc 100644 --- a/src/commands/utilities/price.ts +++ b/src/commands/utilities/price.ts @@ -1,11 +1,11 @@ -import { ArgType, BushCommand, clientSendAndPermCheck, colors, emojis, format, oxford, type CommandMessage } from '#lib'; +import { ArgType, BotCommand, clientSendAndPermCheck, colors, emojis, format, oxford, type CommandMessage } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, AutocompleteInteraction, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; import { default as Fuse } from 'fuse.js'; assert(Fuse); -export default class PriceCommand extends BushCommand { +export default class PriceCommand extends BotCommand { public static cachedItemList: string[] = []; public static readonly urls = [ { url: 'https://api.hypixel.net/skyblock/bazaar', error: 'bazaar' }, diff --git a/src/commands/utilities/remind.ts b/src/commands/utilities/remind.ts index 3a1cd18..498a63d 100644 --- a/src/commands/utilities/remind.ts +++ b/src/commands/utilities/remind.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, castDurationContent, clientSendAndPermCheck, dateDelta, @@ -14,7 +14,7 @@ import { } from '#lib'; import { ApplicationCommandOptionType } from 'discord.js'; -export default class RemindCommand extends BushCommand { +export default class RemindCommand extends BotCommand { public constructor() { super('remind', { aliases: ['remind', 'remindme', 'reminder'], diff --git a/src/commands/utilities/reminders.ts b/src/commands/utilities/reminders.ts index fdce981..f98166f 100644 --- a/src/commands/utilities/reminders.ts +++ b/src/commands/utilities/reminders.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, ButtonPaginator, chunk, clientSendAndPermCheck, @@ -16,7 +16,7 @@ import { Op } from 'sequelize'; assert(Op); -export default class RemindersCommand extends BushCommand { +export default class RemindersCommand extends BotCommand { public constructor() { super('reminders', { aliases: ['reminders', 'view-reminders', 'list-reminders'], diff --git a/src/commands/utilities/steal.ts b/src/commands/utilities/steal.ts index b07338f..7f70830 100644 --- a/src/commands/utilities/steal.ts +++ b/src/commands/utilities/steal.ts @@ -1,6 +1,6 @@ import { Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -28,7 +28,7 @@ const enum lang { nameDescription = 'The name to give the new emoji.' } -export default class StealCommand extends BushCommand { +export default class StealCommand extends BotCommand { public constructor() { super('steal', { aliases: ['steal', 'copy-emoji', 'emoji'], diff --git a/src/commands/utilities/suicide.ts b/src/commands/utilities/suicide.ts index f4c76be..bc76d9c 100644 --- a/src/commands/utilities/suicide.ts +++ b/src/commands/utilities/suicide.ts @@ -1,8 +1,8 @@ -import { AllowedMentions, BushCommand, clientSendAndPermCheck, colors, type CommandMessage, type SlashMessage } from '#lib'; +import { AllowedMentions, BotCommand, clientSendAndPermCheck, colors, type CommandMessage, type SlashMessage } from '#lib'; import { stripIndent } from '#tags'; import { EmbedBuilder } from 'discord.js'; -export default class SuicideCommand extends BushCommand { +export default class SuicideCommand extends BotCommand { public constructor() { super('suicide', { aliases: ['suicide'], diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts index 04d4013..041acfc 100644 --- a/src/commands/utilities/uuid.ts +++ b/src/commands/utilities/uuid.ts @@ -1,7 +1,7 @@ import { AllowedMentions, ArgType, - BushCommand, + BotCommand, clientSendAndPermCheck, emojis, format, @@ -11,7 +11,7 @@ import { } from '#lib'; import { ApplicationCommandOptionType } from 'discord.js'; -export default class UuidCommand extends BushCommand { +export default class UuidCommand extends BotCommand { public constructor() { super('uuid', { aliases: ['uuid'], diff --git a/src/commands/utilities/viewRaw.ts b/src/commands/utilities/viewRaw.ts index a7d8b25..63125c3 100644 --- a/src/commands/utilities/viewRaw.ts +++ b/src/commands/utilities/viewRaw.ts @@ -1,6 +1,6 @@ import { Arg, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -13,7 +13,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, Constants, EmbedBuilder, Message, PermissionFlagsBits } from 'discord.js'; -export default class ViewRawCommand extends BushCommand { +export default class ViewRawCommand extends BotCommand { public constructor() { super('view-raw', { aliases: ['view-raw', 'vr'], diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts index 789701c..23eccd6 100644 --- a/src/commands/utilities/whoHasRole.ts +++ b/src/commands/utilities/whoHasRole.ts @@ -1,5 +1,5 @@ import { - BushCommand, + BotCommand, ButtonPaginator, chunk, clientSendAndPermCheck, @@ -13,7 +13,7 @@ import { import assert from 'assert/strict'; import { ApplicationCommandOptionType, escapeMarkdown, type CommandInteraction, type Role } from 'discord.js'; -export default class WhoHasRoleCommand extends BushCommand { +export default class WhoHasRoleCommand extends BotCommand { public constructor() { super('whoHasRole', { aliases: ['who-has-role', 'whr', 'dump'], diff --git a/src/commands/utilities/wolframAlpha.ts b/src/commands/utilities/wolframAlpha.ts index 5ba55f7..863b16a 100644 --- a/src/commands/utilities/wolframAlpha.ts +++ b/src/commands/utilities/wolframAlpha.ts @@ -1,6 +1,6 @@ import { AllowedMentions, - BushCommand, + BotCommand, clientSendAndPermCheck, colors, emojis, @@ -14,7 +14,7 @@ import { ApplicationCommandOptionType, EmbedBuilder, type MessageOptions } from assert(WolframAlphaAPI); -export default class WolframAlphaCommand extends BushCommand { +export default class WolframAlphaCommand extends BotCommand { public constructor() { super('wolframAlpha', { aliases: ['wolfram-alpha', 'wolfram', 'alpha', 'wolf', 'wa'], diff --git a/src/inhibitors/blacklist/channelGlobalBlacklist.ts b/src/inhibitors/blacklist/channelGlobalBlacklist.ts index 7f23604..988931b 100644 --- a/src/inhibitors/blacklist/channelGlobalBlacklist.ts +++ b/src/inhibitors/blacklist/channelGlobalBlacklist.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class UserGlobalBlacklistInhibitor extends BushInhibitor { +export default class UserGlobalBlacklistInhibitor extends BotInhibitor { public constructor() { super('channelGlobalBlacklist', { reason: 'channelGlobalBlacklist', - category: 'blacklist', type: 'post', priority: 500 }); } - public exec(message: CommandMessage | SlashMessage, command: BushCommand): boolean { + public exec(message: CommandMessage | SlashMessage, command: BotCommand): boolean { if (!message.author || !message.inGuild()) return false; // do not change to message.author.isOwner() if (this.client.isOwner(message.author) || this.client.user!.id === message.author.id) return false; diff --git a/src/inhibitors/blacklist/channelGuildBlacklist.ts b/src/inhibitors/blacklist/channelGuildBlacklist.ts index ae087bd..4bf42d2 100644 --- a/src/inhibitors/blacklist/channelGuildBlacklist.ts +++ b/src/inhibitors/blacklist/channelGuildBlacklist.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class ChannelGuildBlacklistInhibitor extends BushInhibitor { +export default class ChannelGuildBlacklistInhibitor extends BotInhibitor { public constructor() { super('channelGuildBlacklist', { reason: 'channelGuildBlacklist', - category: 'blacklist', type: 'post', priority: 499 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (!message.author || !message.inGuild()) return false; // do not change to message.author.isOwner() if (this.client.isOwner(message.author) || this.client.user!.id === message.author.id) return false; diff --git a/src/inhibitors/blacklist/guildBlacklist.ts b/src/inhibitors/blacklist/guildBlacklist.ts index b7df41a..636d0a3 100644 --- a/src/inhibitors/blacklist/guildBlacklist.ts +++ b/src/inhibitors/blacklist/guildBlacklist.ts @@ -1,10 +1,9 @@ -import { BushInhibitor, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type CommandMessage, type SlashMessage } from '#lib'; -export default class GuildBlacklistInhibitor extends BushInhibitor { +export default class GuildBlacklistInhibitor extends BotInhibitor { public constructor() { super('guildBlacklist', { reason: 'guildBlacklist', - category: 'blacklist', type: 'all', priority: 50 }); diff --git a/src/inhibitors/blacklist/userGlobalBlacklist.ts b/src/inhibitors/blacklist/userGlobalBlacklist.ts index e8a1306..f5b15df 100644 --- a/src/inhibitors/blacklist/userGlobalBlacklist.ts +++ b/src/inhibitors/blacklist/userGlobalBlacklist.ts @@ -1,10 +1,9 @@ -import { BushInhibitor, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type CommandMessage, type SlashMessage } from '#lib'; -export default class UserGlobalBlacklistInhibitor extends BushInhibitor { +export default class UserGlobalBlacklistInhibitor extends BotInhibitor { public constructor() { super('userGlobalBlacklist', { reason: 'userGlobalBlacklist', - category: 'blacklist', type: 'pre', priority: 30 }); diff --git a/src/inhibitors/blacklist/userGuildBlacklist.ts b/src/inhibitors/blacklist/userGuildBlacklist.ts index a661606..3186d59 100644 --- a/src/inhibitors/blacklist/userGuildBlacklist.ts +++ b/src/inhibitors/blacklist/userGuildBlacklist.ts @@ -1,10 +1,9 @@ -import { BushInhibitor, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type CommandMessage, type SlashMessage } from '#lib'; -export default class UserGuildBlacklistInhibitor extends BushInhibitor { +export default class UserGuildBlacklistInhibitor extends BotInhibitor { public constructor() { super('userGuildBlacklist', { reason: 'userGuildBlacklist', - category: 'blacklist', type: 'pre', priority: 20 }); diff --git a/src/inhibitors/checks/fatal.ts b/src/inhibitors/checks/fatal.ts index 9fda504..4364d48 100644 --- a/src/inhibitors/checks/fatal.ts +++ b/src/inhibitors/checks/fatal.ts @@ -1,12 +1,11 @@ -import { BushInhibitor, type SlashMessage } from '#lib'; +import { BotInhibitor, type SlashMessage } from '#lib'; import { type Message } from 'discord.js'; -export default class FatalInhibitor extends BushInhibitor { +export default class FatalInhibitor extends BotInhibitor { public constructor() { super('fatal', { reason: 'fatal', type: 'all', - category: 'checks', priority: 100 }); } diff --git a/src/inhibitors/checks/guildUnavailable.ts b/src/inhibitors/checks/guildUnavailable.ts index f5b62f4..4439d69 100644 --- a/src/inhibitors/checks/guildUnavailable.ts +++ b/src/inhibitors/checks/guildUnavailable.ts @@ -1,12 +1,11 @@ -import { BushInhibitor, type SlashMessage } from '#lib'; +import { BotInhibitor, type SlashMessage } from '#lib'; import { type Message } from 'discord.js'; -export default class GuildUnavailableInhibitor extends BushInhibitor { +export default class GuildUnavailableInhibitor extends BotInhibitor { public constructor() { super('guildUnavailable', { reason: 'guildUnavailable', type: 'all', - category: 'checks', priority: 70 }); } diff --git a/src/inhibitors/command/dm.ts b/src/inhibitors/command/dm.ts index 5516c81..f25f542 100644 --- a/src/inhibitors/command/dm.ts +++ b/src/inhibitors/command/dm.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class DMInhibitor extends BushInhibitor { +export default class DMInhibitor extends BotInhibitor { public constructor() { super('dm', { reason: 'dm', - category: 'command', type: 'post', priority: 75 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.channel === 'dm' && message.guild) { void this.client.console.verbose( 'dm', diff --git a/src/inhibitors/command/globalDisabledCommand.ts b/src/inhibitors/command/globalDisabledCommand.ts index f013183..4a93f2f 100644 --- a/src/inhibitors/command/globalDisabledCommand.ts +++ b/src/inhibitors/command/globalDisabledCommand.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class DisabledGuildCommandInhibitor extends BushInhibitor { +export default class DisabledGuildCommandInhibitor extends BotInhibitor { public constructor() { super('disabledGlobalCommand', { reason: 'disabledGlobal', - category: 'command', type: 'post', priority: 300 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (message.author.isOwner()) return false; if (this.client.cache.global.disabledCommands.includes(command?.id)) { void this.client.console.verbose( diff --git a/src/inhibitors/command/guild.ts b/src/inhibitors/command/guild.ts index ea52d99..1d70c7d 100644 --- a/src/inhibitors/command/guild.ts +++ b/src/inhibitors/command/guild.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class GuildInhibitor extends BushInhibitor { +export default class GuildInhibitor extends BotInhibitor { public constructor() { super('guild', { reason: 'guild', - category: 'command', type: 'post', priority: 80 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.channel === 'guild' && !message.guild) { void this.client.console.verbose( 'guild', diff --git a/src/inhibitors/command/guildDisabledCommand.ts b/src/inhibitors/command/guildDisabledCommand.ts index 7fef78a..97ac995 100644 --- a/src/inhibitors/command/guildDisabledCommand.ts +++ b/src/inhibitors/command/guildDisabledCommand.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class DisabledGuildCommandInhibitor extends BushInhibitor { +export default class DisabledGuildCommandInhibitor extends BotInhibitor { public constructor() { super('disabledGuildCommand', { reason: 'disabledGuild', - category: 'command', type: 'post', priority: 250 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (!message.guild || !message.guild) return false; if (message.author.isOwner() || message.author.isSuperUser()) return false; // super users bypass guild disabled commands diff --git a/src/inhibitors/command/nsfw.ts b/src/inhibitors/command/nsfw.ts index ed55b00..623115e 100644 --- a/src/inhibitors/command/nsfw.ts +++ b/src/inhibitors/command/nsfw.ts @@ -1,17 +1,16 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; import { type TextChannel } from 'discord.js'; -export default class NsfwInhibitor extends BushInhibitor { +export default class NsfwInhibitor extends BotInhibitor { public constructor() { super('nsfw', { reason: 'notNsfw', - category: 'command', type: 'post', priority: 25 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.onlyNsfw && !(message.channel as TextChannel).nsfw) { void this.client.console.verbose( 'notNsfw', diff --git a/src/inhibitors/command/owner.ts b/src/inhibitors/command/owner.ts index 7a39063..15643be 100644 --- a/src/inhibitors/command/owner.ts +++ b/src/inhibitors/command/owner.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class OwnerInhibitor extends BushInhibitor { +export default class OwnerInhibitor extends BotInhibitor { public constructor() { super('owner', { reason: 'owner', - category: 'command', type: 'post', priority: 100 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.ownerOnly) { if (!this.client.isOwner(message.author)) { void this.client.console.verbose( diff --git a/src/inhibitors/command/restrictedChannel.ts b/src/inhibitors/command/restrictedChannel.ts index 849166a..ec23604 100644 --- a/src/inhibitors/command/restrictedChannel.ts +++ b/src/inhibitors/command/restrictedChannel.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class RestrictedChannelInhibitor extends BushInhibitor { +export default class RestrictedChannelInhibitor extends BotInhibitor { public constructor() { super('restrictedChannel', { reason: 'restrictedChannel', - category: 'command', type: 'post', priority: 10 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.restrictedChannels?.length && message.channel) { if (!command.restrictedChannels.includes(message.channel.id)) { void this.client.console.verbose( diff --git a/src/inhibitors/command/restrictedGuild.ts b/src/inhibitors/command/restrictedGuild.ts index 1e2d1b2..ec0ad2c 100644 --- a/src/inhibitors/command/restrictedGuild.ts +++ b/src/inhibitors/command/restrictedGuild.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class RestrictedGuildInhibitor extends BushInhibitor { +export default class RestrictedGuildInhibitor extends BotInhibitor { public constructor() { super('restrictedGuild', { reason: 'restrictedGuild', - category: 'command', type: 'post', priority: 5 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.restrictedChannels?.length && message.channel) { if (!command.restrictedChannels.includes(message.channel.id)) { void this.client.console.verbose( diff --git a/src/inhibitors/command/superUser.ts b/src/inhibitors/command/superUser.ts index 69e95a2..a923c1a 100644 --- a/src/inhibitors/command/superUser.ts +++ b/src/inhibitors/command/superUser.ts @@ -1,16 +1,15 @@ -import { BushInhibitor, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; +import { BotInhibitor, type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -export default class SuperUserInhibitor extends BushInhibitor { +export default class SuperUserInhibitor extends BotInhibitor { public constructor() { super('superUser', { reason: 'superUser', - category: 'command', type: 'post', priority: 99 }); } - public async exec(message: CommandMessage | SlashMessage, command: BushCommand): Promise { + public async exec(message: CommandMessage | SlashMessage, command: BotCommand): Promise { if (command.superUserOnly) { if (!this.client.isSuperUser(message.author)) { void this.client.console.verbose( diff --git a/src/listeners/automod/automodCreate.ts b/src/listeners/automod/automodCreate.ts index 529651c..e2454e6 100644 --- a/src/listeners/automod/automodCreate.ts +++ b/src/listeners/automod/automodCreate.ts @@ -1,15 +1,14 @@ -import { BushListener, MessageAutomod, type BushClientEvents } from '#lib'; +import { BotListener, MessageAutomod, type BotClientEvents } from '#lib'; -export default class AutomodMessageCreateListener extends BushListener { +export default class AutomodMessageCreateListener extends BotListener { public constructor() { super('automodCreate', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']) { + public async exec(...[message]: BotClientEvents['messageCreate']) { if (message.member === null) return; return new MessageAutomod(message); } diff --git a/src/listeners/automod/automodUpdate.ts b/src/listeners/automod/automodUpdate.ts index d68759f..0609e67 100644 --- a/src/listeners/automod/automodUpdate.ts +++ b/src/listeners/automod/automodUpdate.ts @@ -1,15 +1,14 @@ -import { BushListener, MessageAutomod, type BushClientEvents } from '#lib'; +import { BotListener, MessageAutomod, type BotClientEvents } from '#lib'; -export default class AutomodMessageUpdateListener extends BushListener { +export default class AutomodMessageUpdateListener extends BotListener { public constructor() { super('automodUpdate', { emitter: 'client', - event: 'messageUpdate', - category: 'message' + event: 'messageUpdate' }); } - public async exec(...[_, newMessage]: BushClientEvents['messageUpdate']) { + public async exec(...[_, newMessage]: BotClientEvents['messageUpdate']) { const fullMessage = newMessage.partial ? await newMessage.fetch().catch(() => null) : newMessage; if (!fullMessage?.member) return; return new MessageAutomod(fullMessage); diff --git a/src/listeners/automod/memberAutomod.ts b/src/listeners/automod/memberAutomod.ts index 01eb56c..557d13a 100644 --- a/src/listeners/automod/memberAutomod.ts +++ b/src/listeners/automod/memberAutomod.ts @@ -1,7 +1,7 @@ -import { BushClientEvents, BushListener, MemberAutomod } from '#lib'; +import { BotClientEvents, BotListener, MemberAutomod } from '#lib'; import chalk from 'chalk'; -export default class PresenceAutomodListener extends BushListener { +export default class PresenceAutomodListener extends BotListener { public constructor() { super('memberAutomod', { emitter: 'client', @@ -9,7 +9,7 @@ export default class PresenceAutomodListener extends BushListener { }); } - public async exec(...[_, newMember]: BushClientEvents['guildMemberUpdate']) { + public async exec(...[_, newMember]: BotClientEvents['guildMemberUpdate']) { if (!(await newMember.guild.hasFeature('automodMembers'))) return; if (!(await newMember.guild.hasFeature('automod'))) return; diff --git a/src/listeners/automod/presenceAutomod.ts b/src/listeners/automod/presenceAutomod.ts index eb536bf..a89d45c 100644 --- a/src/listeners/automod/presenceAutomod.ts +++ b/src/listeners/automod/presenceAutomod.ts @@ -1,6 +1,6 @@ -import { BushClientEvents, BushListener, PresenceAutomod } from '#lib'; +import { BotClientEvents, BotListener, PresenceAutomod } from '#lib'; -export default class PresenceAutomodListener extends BushListener { +export default class PresenceAutomodListener extends BotListener { public constructor() { super('presenceAutomod', { emitter: 'client', @@ -8,7 +8,7 @@ export default class PresenceAutomodListener extends BushListener { }); } - public async exec(...[_, newPresence]: BushClientEvents['presenceUpdate']) { + public async exec(...[_, newPresence]: BotClientEvents['presenceUpdate']) { if (!newPresence.member || !newPresence.guild) return; if (!newPresence.activities.length) return; diff --git a/src/listeners/bush/appealListener.ts b/src/listeners/bush/appealListener.ts index a4e1f00..ecc65c5 100644 --- a/src/listeners/bush/appealListener.ts +++ b/src/listeners/bush/appealListener.ts @@ -1,19 +1,18 @@ -import { BushListener, colors, mappings, ModLog, type BushClientEvents } from '#lib'; +import { BotListener, colors, mappings, ModLog, type BotClientEvents } from '#lib'; import assert from 'assert/strict'; import { EmbedBuilder } from 'discord.js'; import UserInfoCommand from '../../commands/info/userInfo.js'; import ModlogCommand from '../../commands/moderation/modlog.js'; -export default class AppealListener extends BushListener { +export default class AppealListener extends BotListener { public constructor() { super('appealListener', { emitter: 'client', - event: 'messageCreate', - category: 'bush' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']): Promise { + public async exec(...[message]: BotClientEvents['messageCreate']): Promise { if (!this.client.config.isProduction || !message.inGuild() || message.guildId !== mappings.guilds["Moulberry's Bush"]) return; if (message.author.id !== '855446927688335370' || message.embeds.length < 1) return; diff --git a/src/listeners/bush/joinAutoBan.ts b/src/listeners/bush/joinAutoBan.ts index 4370e86..66fdf54 100644 --- a/src/listeners/bush/joinAutoBan.ts +++ b/src/listeners/bush/joinAutoBan.ts @@ -1,16 +1,15 @@ -import { AllowedMentions, BushListener, colors, emojis, format, mappings, type BushClientEvents } from '#lib'; +import { AllowedMentions, BotListener, colors, emojis, format, mappings, type BotClientEvents } from '#lib'; import { TextChannel } from 'discord.js'; -export default class JoinAutoBanListener extends BushListener { +export default class JoinAutoBanListener extends BotListener { public constructor() { super('joinAutoBan', { emitter: 'client', - event: 'guildMemberAdd', - category: 'bush' + event: 'guildMemberAdd' }); } - public async exec(...[member]: BushClientEvents['guildMemberAdd']): Promise { + public async exec(...[member]: BotClientEvents['guildMemberAdd']): Promise { if (!this.client.config.isProduction) return; if (member.guild.id !== mappings.guilds["Moulberry's Bush"]) return; const guild = member.guild; @@ -20,7 +19,7 @@ export default class JoinAutoBanListener extends BushListener { const code = this.client.utils.getShared('autoBanCode'); if (!code) return; if (eval(code)) { - const res = await member.bushBan({ + const res = await member.customBan({ reason: '[AutoBan] Impersonation is not allowed.', moderator: member.guild.members.me! }); diff --git a/src/listeners/bush/supportThread.ts b/src/listeners/bush/supportThread.ts index 5145ff2..3e806e2 100644 --- a/src/listeners/bush/supportThread.ts +++ b/src/listeners/bush/supportThread.ts @@ -1,18 +1,17 @@ -import { BushListener, colors, mappings, type BushClientEvents } from '#lib'; +import { BotListener, colors, mappings, type BotClientEvents } from '#lib'; import { stripIndent } from '#tags'; import assert from 'assert/strict'; import { EmbedBuilder, MessageType, PermissionFlagsBits, TextChannel } from 'discord.js'; -export default class SupportThreadListener extends BushListener { +export default class SupportThreadListener extends BotListener { public constructor() { super('supportThread', { emitter: 'client', - event: 'messageCreate', - category: 'bush' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']): Promise { + public async exec(...[message]: BotClientEvents['messageCreate']): Promise { if (!this.client.config.isProduction || !message.inGuild()) return; if (![MessageType.Default, MessageType.Reply].includes(message.type)) return; if (message.thread) return; diff --git a/src/listeners/bush/userUpdateAutoBan.ts b/src/listeners/bush/userUpdateAutoBan.ts index adfb80c..ae8bca3 100644 --- a/src/listeners/bush/userUpdateAutoBan.ts +++ b/src/listeners/bush/userUpdateAutoBan.ts @@ -1,16 +1,15 @@ -import { AllowedMentions, BushListener, colors, emojis, format, mappings, type BushClientEvents } from '#lib'; +import { AllowedMentions, BotListener, colors, emojis, format, mappings, type BotClientEvents } from '#lib'; import { GuildMember, type TextChannel } from 'discord.js'; -export default class UserUpdateAutoBanListener extends BushListener { +export default class UserUpdateAutoBanListener extends BotListener { public constructor() { super('userUpdateAutoBan', { emitter: 'client', - event: 'userUpdate', - category: 'bush' + event: 'userUpdate' }); } - public async exec(...[_oldUser, newUser]: BushClientEvents['userUpdate']): Promise { + public async exec(...[_oldUser, newUser]: BotClientEvents['userUpdate']): Promise { if (!this.client.config.isProduction) return; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -26,7 +25,7 @@ export default class UserUpdateAutoBanListener extends BushListener { const guild = member.guild; - const res = await member.bushBan({ + const res = await member.customBan({ reason: '[AutoBan] Impersonation is not allowed.', moderator: member.guild.members.me! }); diff --git a/src/listeners/client/akairoDebug.ts b/src/listeners/client/akairoDebug.ts index 3fa7977..af973bf 100644 --- a/src/listeners/client/akairoDebug.ts +++ b/src/listeners/client/akairoDebug.ts @@ -1,15 +1,14 @@ -import { BushListener, type BushClientEvents } from '#lib'; +import { BotListener, type BotClientEvents } from '#lib'; -export default class DiscordJsDebugListener extends BushListener { +export default class DiscordJsDebugListener extends BotListener { public constructor() { super('akairoDebug', { emitter: 'client', - event: 'akairoDebug', - category: 'client' + event: 'akairoDebug' }); } - public async exec(...[message, ...other]: BushClientEvents['debug']): Promise { + public async exec(...[message, ...other]: BotClientEvents['debug']): Promise { if (other.length && !message.includes('[registerInteractionCommands]')) void this.client.console.superVerboseRaw('akairoDebug', message, ...other); else void this.client.console.superVerbose('akairoDebug', message); diff --git a/src/listeners/client/dcjsDebug.ts b/src/listeners/client/dcjsDebug.ts deleted file mode 100644 index 4b80c65..0000000 --- a/src/listeners/client/dcjsDebug.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BushListener, type BushClientEvents } from '#lib'; - -export default class DiscordJsDebugListener extends BushListener { - public constructor() { - super('discordJsDebug', { - emitter: 'client', - event: 'debug', - category: 'client' - }); - } - - public async exec(...[message]: BushClientEvents['debug']): Promise { - void this.client.console.superVerbose('dc.js-debug', message); - } -} diff --git a/src/listeners/client/dcjsError.ts b/src/listeners/client/dcjsError.ts deleted file mode 100644 index a39a92d..0000000 --- a/src/listeners/client/dcjsError.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BushListener, type BushClientEvents } from '#lib'; - -export default class DiscordJsErrorListener extends BushListener { - public constructor() { - super('discordJsError', { - emitter: 'client', - event: 'error', - category: 'client' - }); - } - - public async exec(...[error]: BushClientEvents['error']): Promise { - void this.client.console.superVerbose('dc.js-error', error); - } -} diff --git a/src/listeners/client/dcjsWarn.ts b/src/listeners/client/dcjsWarn.ts deleted file mode 100644 index b187f0c..0000000 --- a/src/listeners/client/dcjsWarn.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BushListener, type BushClientEvents } from '#lib'; - -export default class DiscordJsWarnListener extends BushListener { - public constructor() { - super('discordJsWarn', { - emitter: 'client', - event: 'warn', - category: 'client' - }); - } - - public async exec(...[message]: BushClientEvents['warn']): Promise { - void this.client.console.superVerbose('dc.js-warn', message); - } -} diff --git a/src/listeners/client/djsDebug.ts b/src/listeners/client/djsDebug.ts new file mode 100644 index 0000000..28bac7f --- /dev/null +++ b/src/listeners/client/djsDebug.ts @@ -0,0 +1,14 @@ +import { BotListener, type BotClientEvents } from '#lib'; + +export default class DiscordJsDebugListener extends BotListener { + public constructor() { + super('discordJsDebug', { + emitter: 'client', + event: 'debug' + }); + } + + public async exec(...[message]: BotClientEvents['debug']): Promise { + void this.client.console.superVerbose('dc.js-debug', message); + } +} diff --git a/src/listeners/client/djsError.ts b/src/listeners/client/djsError.ts new file mode 100644 index 0000000..04fbfaa --- /dev/null +++ b/src/listeners/client/djsError.ts @@ -0,0 +1,14 @@ +import { BotListener, type BotClientEvents } from '#lib'; + +export default class DiscordJsErrorListener extends BotListener { + public constructor() { + super('discordJsError', { + emitter: 'client', + event: 'error' + }); + } + + public async exec(...[error]: BotClientEvents['error']): Promise { + void this.client.console.superVerbose('dc.js-error', error); + } +} diff --git a/src/listeners/client/djsWarn.ts b/src/listeners/client/djsWarn.ts new file mode 100644 index 0000000..9162a36 --- /dev/null +++ b/src/listeners/client/djsWarn.ts @@ -0,0 +1,14 @@ +import { BotListener, type BotClientEvents } from '#lib'; + +export default class DiscordJsWarnListener extends BotListener { + public constructor() { + super('discordJsWarn', { + emitter: 'client', + event: 'warn' + }); + } + + public async exec(...[message]: BotClientEvents['warn']): Promise { + void this.client.console.superVerbose('dc.js-warn', message); + } +} diff --git a/src/listeners/client/ready.ts b/src/listeners/client/ready.ts index a6a289c..1c887ed 100644 --- a/src/listeners/client/ready.ts +++ b/src/listeners/client/ready.ts @@ -1,7 +1,7 @@ -import { BushClientEvents, BushListener, Guild } from '#lib'; +import { BotClientEvents, BotListener, Guild } from '#lib'; import chalk from 'chalk'; -export default class ReadyListener extends BushListener { +export default class ReadyListener extends BotListener { public constructor() { super('ready', { emitter: 'client', @@ -11,7 +11,7 @@ export default class ReadyListener extends BushListener { } // eslint-disable-next-line no-empty-pattern - public async exec(...[]: BushClientEvents['ready']) { + public async exec(...[]: BotClientEvents['ready']) { process.emit('ready' as any); const tag = `<<${this.client.user?.tag}>>`, diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts index 1324d5f..d9a95c1 100644 --- a/src/listeners/commands/commandBlocked.ts +++ b/src/listeners/commands/commandBlocked.ts @@ -1,33 +1,32 @@ import { BlockedReasons, - BushListener, + BotListener, emojis, format, oxford, - type BushCommand, - type BushCommandHandlerEvents, + type BotCommand, + type BotCommandHandlerEvents, type CommandMessage, type SlashMessage } from '#lib'; import { type Client, type InteractionReplyOptions, type ReplyMessageOptions } from 'discord.js'; -export default class CommandBlockedListener extends BushListener { +export default class CommandBlockedListener extends BotListener { public constructor() { super('commandBlocked', { emitter: 'commandHandler', - event: 'commandBlocked', - category: 'commands' + event: 'commandBlocked' }); } - public async exec(...[message, command, reason]: BushCommandHandlerEvents['commandBlocked']) { + public async exec(...[message, command, reason]: BotCommandHandlerEvents['commandBlocked']) { return await CommandBlockedListener.handleBlocked(this.client, message, command, reason); } public static async handleBlocked( client: Client, message: CommandMessage | SlashMessage, - command: BushCommand | null, + command: BotCommand | null, reason?: string ) { const isSlash = !!command && !!message.util?.isSlash; diff --git a/src/listeners/commands/commandCooldown.ts b/src/listeners/commands/commandCooldown.ts index 5f4d70e..1a5b790 100644 --- a/src/listeners/commands/commandCooldown.ts +++ b/src/listeners/commands/commandCooldown.ts @@ -1,15 +1,14 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; -export default class CommandCooldownListener extends BushListener { +export default class CommandCooldownListener extends BotListener { public constructor() { super('commandCooldown', { emitter: 'commandHandler', - event: 'cooldown', - category: 'commands' + event: 'cooldown' }); } - public async exec(...[message, command, remaining]: BushCommandHandlerEvents['cooldown']) { + public async exec(...[message, command, remaining]: BotCommandHandlerEvents['cooldown']) { void this.client.console.info( 'commandCooldown', `<<${message.author.tag}>> tried to run <<${ diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 7e14bc3..b96b8de 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -1,15 +1,14 @@ -import { BushListener, handleCommandError, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, handleCommandError, type BotCommandHandlerEvents } from '#lib'; -export default class CommandErrorListener extends BushListener { +export default class CommandErrorListener extends BotListener { public constructor() { super('commandError', { emitter: 'commandHandler', - event: 'error', - category: 'commands' + event: 'error' }); } - public exec(...[error, message, command]: BushCommandHandlerEvents['error']) { + public exec(...[error, message, command]: BotCommandHandlerEvents['error']) { return handleCommandError(this.client, error, message, command); } } diff --git a/src/listeners/commands/commandLocked.ts b/src/listeners/commands/commandLocked.ts index 22ed8e1..fbceca8 100644 --- a/src/listeners/commands/commandLocked.ts +++ b/src/listeners/commands/commandLocked.ts @@ -1,15 +1,14 @@ -import { BushListener, emojis, format, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, emojis, format, type BotCommandHandlerEvents } from '#lib'; -export default class CommandLockedListener extends BushListener { +export default class CommandLockedListener extends BotListener { public constructor() { super('commandLocked', { emitter: 'commandHandler', - event: 'commandLocked', - category: 'commands' + event: 'commandLocked' }); } - public async exec(...[message, command]: BushCommandHandlerEvents['commandLocked']) { + public async exec(...[message, command]: BotCommandHandlerEvents['commandLocked']) { return message.util.reply( `${emojis.error} You cannot use the ${format.input(command.id)} command because it is already in use.` ); diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts index 4d39264..a2ec461 100644 --- a/src/listeners/commands/commandMissingPermissions.ts +++ b/src/listeners/commands/commandMissingPermissions.ts @@ -1,24 +1,23 @@ -import { BushListener, emojis, format, mappings, oxford, surroundArray, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, emojis, format, mappings, oxford, surroundArray, type BotCommandHandlerEvents } from '#lib'; import { Client, type PermissionsString } from 'discord.js'; -export default class CommandMissingPermissionsListener extends BushListener { +export default class CommandMissingPermissionsListener extends BotListener { public constructor() { super('commandMissingPermissions', { emitter: 'commandHandler', - event: 'missingPermissions', - category: 'commands' + event: 'missingPermissions' }); } - public async exec(...[message, command, type, missing]: BushCommandHandlerEvents['missingPermissions']) { + public async exec(...[message, command, type, missing]: BotCommandHandlerEvents['missingPermissions']) { return await CommandMissingPermissionsListener.handleMissing(this.client, message, command, type, missing); } public static async handleMissing( client: Client, ...[message, command, type, missing]: - | BushCommandHandlerEvents['missingPermissions'] - | BushCommandHandlerEvents['slashMissingPermissions'] + | BotCommandHandlerEvents['missingPermissions'] + | BotCommandHandlerEvents['slashMissingPermissions'] ) { const niceMissing = (missing.includes('Administrator') ? (['Administrator'] as PermissionsString[]) : missing).map( (perm) => mappings.permissions[perm]?.name ?? missing diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts index 9d0e4cb..407662c 100644 --- a/src/listeners/commands/commandStarted.ts +++ b/src/listeners/commands/commandStarted.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; import { ChannelType } from 'discord.js'; -export default class CommandStartedListener extends BushListener { +export default class CommandStartedListener extends BotListener { public constructor() { super('commandStarted', { emitter: 'commandHandler', - event: 'commandStarted', - category: 'commands' + event: 'commandStarted' }); } - public exec(...[message, command]: BushCommandHandlerEvents['commandStarted']): void { + public exec(...[message, command]: BotCommandHandlerEvents['commandStarted']): void { this.client.sentry.addBreadcrumb({ message: `[commandStarted] The ${command.id} was started by ${message.author.tag}.`, level: 'info', diff --git a/src/listeners/commands/messageBlocked.ts b/src/listeners/commands/messageBlocked.ts index d73cff1..3d92d32 100644 --- a/src/listeners/commands/messageBlocked.ts +++ b/src/listeners/commands/messageBlocked.ts @@ -1,15 +1,14 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; -export default class MessageBlockedListener extends BushListener { +export default class MessageBlockedListener extends BotListener { public constructor() { super('messageBlocked', { emitter: 'commandHandler', - event: 'messageBlocked', - category: 'commands' + event: 'messageBlocked' }); } - public async exec(...[message, reason]: BushCommandHandlerEvents['messageBlocked']) { + public async exec(...[message, reason]: BotCommandHandlerEvents['messageBlocked']) { if (['client', 'bot'].includes(reason)) return; // return await CommandBlockedListener.handleBlocked(message as Message, null, reason); return void this.client.console.verbose( diff --git a/src/listeners/commands/slashBlocked.ts b/src/listeners/commands/slashBlocked.ts index c877708..528c97b 100644 --- a/src/listeners/commands/slashBlocked.ts +++ b/src/listeners/commands/slashBlocked.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; import CommandBlockedListener from './commandBlocked.js'; -export default class SlashBlockedListener extends BushListener { +export default class SlashBlockedListener extends BotListener { public constructor() { super('slashBlocked', { emitter: 'commandHandler', - event: 'slashBlocked', - category: 'commands' + event: 'slashBlocked' }); } - public async exec(...[message, command, reason]: BushCommandHandlerEvents['slashBlocked']) { + public async exec(...[message, command, reason]: BotCommandHandlerEvents['slashBlocked']) { return await CommandBlockedListener.handleBlocked(this.client, message, command, reason); } } diff --git a/src/listeners/commands/slashCommandError.ts b/src/listeners/commands/slashCommandError.ts index aca7c5b..03eb34a 100644 --- a/src/listeners/commands/slashCommandError.ts +++ b/src/listeners/commands/slashCommandError.ts @@ -1,15 +1,14 @@ -import { BushListener, handleCommandError, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, handleCommandError, type BotCommandHandlerEvents } from '#lib'; -export default class SlashCommandErrorListener extends BushListener { +export default class SlashCommandErrorListener extends BotListener { public constructor() { super('slashError', { emitter: 'commandHandler', - event: 'slashError', - category: 'commands' + event: 'slashError' }); } - public async exec(...[error, message, command]: BushCommandHandlerEvents['slashError']) { + public async exec(...[error, message, command]: BotCommandHandlerEvents['slashError']) { return await handleCommandError(this.client, error, message, command); } } diff --git a/src/listeners/commands/slashMissingPermissions.ts b/src/listeners/commands/slashMissingPermissions.ts index 0a1383b..68388bf 100644 --- a/src/listeners/commands/slashMissingPermissions.ts +++ b/src/listeners/commands/slashMissingPermissions.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; import CommandMissingPermissionsListener from './commandMissingPermissions.js'; -export default class SlashMissingPermissionsListener extends BushListener { +export default class SlashMissingPermissionsListener extends BotListener { public constructor() { super('slashMissingPermissions', { emitter: 'commandHandler', - event: 'slashMissingPermissions', - category: 'commands' + event: 'slashMissingPermissions' }); } - public async exec(...[message, command, type, missing]: BushCommandHandlerEvents['slashMissingPermissions']) { + public async exec(...[message, command, type, missing]: BotCommandHandlerEvents['slashMissingPermissions']) { return await CommandMissingPermissionsListener.handleMissing(this.client, message, command, type, missing); } } diff --git a/src/listeners/commands/slashNotFound.ts b/src/listeners/commands/slashNotFound.ts index cc14969..7e76fe6 100644 --- a/src/listeners/commands/slashNotFound.ts +++ b/src/listeners/commands/slashNotFound.ts @@ -1,15 +1,14 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; -export default class SlashNotFoundListener extends BushListener { +export default class SlashNotFoundListener extends BotListener { public constructor() { super('slashNotFound', { emitter: 'commandHandler', - event: 'slashNotFound', - category: 'commands' + event: 'slashNotFound' }); } - public async exec(...[interaction]: BushCommandHandlerEvents['slashNotFound']) { + public async exec(...[interaction]: BotCommandHandlerEvents['slashNotFound']) { void this.client.console.info('slashNotFound', `<<${interaction?.commandName}>> could not be found.`); } } diff --git a/src/listeners/commands/slashStarted.ts b/src/listeners/commands/slashStarted.ts index c2ece5e..898af13 100644 --- a/src/listeners/commands/slashStarted.ts +++ b/src/listeners/commands/slashStarted.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, type BotCommandHandlerEvents } from '#lib'; import { ChannelType } from 'discord.js'; -export default class SlashStartedListener extends BushListener { +export default class SlashStartedListener extends BotListener { public constructor() { super('slashStarted', { emitter: 'commandHandler', - event: 'slashStarted', - category: 'commands' + event: 'slashStarted' }); } - public async exec(...[message, command]: BushCommandHandlerEvents['slashStarted']) { + public async exec(...[message, command]: BotCommandHandlerEvents['slashStarted']) { this.client.sentry.addBreadcrumb({ message: `[slashStarted] The ${command.id} was started by ${message.author.tag}.`, level: 'info', diff --git a/src/listeners/contextCommands/contextCommandBlocked.ts b/src/listeners/contextCommands/contextCommandBlocked.ts index bb237a0..d8d002e 100644 --- a/src/listeners/contextCommands/contextCommandBlocked.ts +++ b/src/listeners/contextCommands/contextCommandBlocked.ts @@ -1,12 +1,11 @@ -import { BlockedReasons, BushListener, emojis, format } from '#lib'; +import { BlockedReasons, BotListener, emojis, format } from '#lib'; import { type ContextMenuCommandHandlerEvents } from 'discord-akairo'; -export default class ContextCommandBlockedListener extends BushListener { +export default class ContextCommandBlockedListener extends BotListener { public constructor() { super('contextCommandBlocked', { emitter: 'contextMenuCommandHandler', - event: 'blocked', - category: 'contextCommands' + event: 'blocked' }); } diff --git a/src/listeners/contextCommands/contextCommandError.ts b/src/listeners/contextCommands/contextCommandError.ts index 6951ce3..091bee9 100644 --- a/src/listeners/contextCommands/contextCommandError.ts +++ b/src/listeners/contextCommands/contextCommandError.ts @@ -1,13 +1,12 @@ -import { BushListener, colors, format, formatError, getErrorHaste, getErrorStack, IFuckedUpError } from '#lib'; +import { BotListener, colors, format, formatError, getErrorHaste, getErrorStack, IFuckedUpError } from '#lib'; import { type ContextMenuCommand, type ContextMenuCommandHandlerEvents } from 'discord-akairo'; import { ChannelType, Client, ContextMenuCommandInteraction, EmbedBuilder, GuildTextBasedChannel } from 'discord.js'; -export default class ContextCommandErrorListener extends BushListener { +export default class ContextCommandErrorListener extends BotListener { public constructor() { super('contextCommandError', { emitter: 'contextMenuCommandHandler', - event: 'error', - category: 'contextCommands' + event: 'error' }); } diff --git a/src/listeners/contextCommands/contextCommandNotFound.ts b/src/listeners/contextCommands/contextCommandNotFound.ts index f5097f3..4bb397e 100644 --- a/src/listeners/contextCommands/contextCommandNotFound.ts +++ b/src/listeners/contextCommands/contextCommandNotFound.ts @@ -1,12 +1,11 @@ -import { BushListener } from '#lib'; +import { BotListener } from '#lib'; import { type ContextMenuCommandHandlerEvents } from 'discord-akairo'; -export default class ContextCommandNotFoundListener extends BushListener { +export default class ContextCommandNotFoundListener extends BotListener { public constructor() { super('contextCommandNotFound', { emitter: 'contextMenuCommandHandler', - event: 'notFound', - category: 'contextCommands' + event: 'notFound' }); } diff --git a/src/listeners/contextCommands/contextCommandStarted.ts b/src/listeners/contextCommands/contextCommandStarted.ts index 2d1e9ef..867af54 100644 --- a/src/listeners/contextCommands/contextCommandStarted.ts +++ b/src/listeners/contextCommands/contextCommandStarted.ts @@ -1,13 +1,12 @@ -import { BushListener } from '#lib'; +import { BotListener } from '#lib'; import { ContextMenuCommandHandlerEvents } from 'discord-akairo'; import { ApplicationCommandType, ChannelType } from 'discord.js'; -export default class ContextCommandStartedListener extends BushListener { +export default class ContextCommandStartedListener extends BotListener { public constructor() { super('contextCommandStarted', { emitter: 'contextMenuCommandHandler', - event: 'started', - category: 'contextCommands' + event: 'started' }); } diff --git a/src/listeners/guild-custom/bushLockdown.ts b/src/listeners/guild-custom/bushLockdown.ts deleted file mode 100644 index 51d1c3d..0000000 --- a/src/listeners/guild-custom/bushLockdown.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, emojis, type BushClientEvents } from '#lib'; -import { EmbedBuilder } from 'discord.js'; - -export default class BushLockdownListener extends BushListener { - public constructor() { - super('bushLockdown', { - emitter: 'client', - event: 'bushLockdown', - category: 'guild-custom' - }); - } - - public async exec(...[moderator, reason, channelsSuccessMap, _all]: BushClientEvents['bushLockdown']) { - const logChannel = await moderator.guild.getLogChannel('moderation'); - if (!logChannel) return; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Blurple) - .setTimestamp() - .addFields( - { name: '**Action**', value: `${'Lockdown'}` }, - { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }, - { - name: `**Channel${channelsSuccessMap.size > 1 ? 's' : ''}**`, - value: channelsSuccessMap - .map((success, channel) => `<#${channel}> ${success ? emojis.success : emojis.error}`) - .join('\n') - } - ); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/guild-custom/bushUnlockdown.ts b/src/listeners/guild-custom/bushUnlockdown.ts deleted file mode 100644 index 18cb792..0000000 --- a/src/listeners/guild-custom/bushUnlockdown.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, emojis, type BushClientEvents } from '#lib'; -import { EmbedBuilder } from 'discord.js'; - -export default class BushUnlockdownListener extends BushListener { - public constructor() { - super('bushUnlockdown', { - emitter: 'client', - event: 'bushUnlockdown', - category: 'guild-custom' - }); - } - - public async exec(...[moderator, reason, channelsSuccessMap, _all]: BushClientEvents['bushUnlockdown']) { - const logChannel = await moderator.guild.getLogChannel('moderation'); - if (!logChannel) return; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Blurple) - .setTimestamp() - .addFields( - { name: '**Action**', value: `${'Unlockdown'}` }, - { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }, - { - name: `**Channel${channelsSuccessMap.size > 1 ? 's' : ''}**`, - value: channelsSuccessMap - .map((success, channel) => `<#${channel}> ${success ? emojis.success : emojis.error}`) - .join('\n') - } - ); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/guild-custom/lockdown.ts b/src/listeners/guild-custom/lockdown.ts new file mode 100644 index 0000000..bf3ee7c --- /dev/null +++ b/src/listeners/guild-custom/lockdown.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, emojis, type BotClientEvents } from '#lib'; +import { EmbedBuilder } from 'discord.js'; + +export default class LockdownListener extends BotListener { + public constructor() { + super('lockdown', { + emitter: 'client', + event: 'lockdown' + }); + } + + public async exec(...[moderator, reason, channelsSuccessMap, _all]: BotClientEvents['lockdown']) { + const logChannel = await moderator.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Blurple) + .setTimestamp() + .addFields( + { name: '**Action**', value: `${'Lockdown'}` }, + { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }, + { + name: `**Channel${channelsSuccessMap.size > 1 ? 's' : ''}**`, + value: channelsSuccessMap + .map((success, channel) => `<#${channel}> ${success ? emojis.success : emojis.error}`) + .join('\n') + } + ); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/guild-custom/unlockdown.ts b/src/listeners/guild-custom/unlockdown.ts new file mode 100644 index 0000000..e08a42b --- /dev/null +++ b/src/listeners/guild-custom/unlockdown.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, emojis, type BotClientEvents } from '#lib'; +import { EmbedBuilder } from 'discord.js'; + +export default class UnlockdownListener extends BotListener { + public constructor() { + super('unlockdown', { + emitter: 'client', + event: 'unlockdown' + }); + } + + public async exec(...[moderator, reason, channelsSuccessMap, _all]: BotClientEvents['unlockdown']) { + const logChannel = await moderator.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Blurple) + .setTimestamp() + .addFields( + { name: '**Action**', value: `${'Unlockdown'}` }, + { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }, + { + name: `**Channel${channelsSuccessMap.size > 1 ? 's' : ''}**`, + value: channelsSuccessMap + .map((success, channel) => `<#${channel}> ${success ? emojis.success : emojis.error}`) + .join('\n') + } + ); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/guild/guildCreate.ts b/src/listeners/guild/guildCreate.ts index 9f5f56c..3db28d6 100644 --- a/src/listeners/guild/guildCreate.ts +++ b/src/listeners/guild/guildCreate.ts @@ -1,15 +1,14 @@ -import { BushListener, colors, emojis, format, Guild, type BushClientEvents } from '#lib'; +import { BotListener, colors, emojis, format, Guild, type BotClientEvents } from '#lib'; -export default class GuildCreateListener extends BushListener { +export default class GuildCreateListener extends BotListener { public constructor() { super('guildCreate', { emitter: 'client', - event: 'guildCreate', // when the bot joins a guild - category: 'guild' + event: 'guildCreate' // when the bot joins a guild }); } - public async exec(...[guild]: BushClientEvents['guildCreate']) { + public async exec(...[guild]: BotClientEvents['guildCreate']) { void this.client.console.info( 'guildCreate', `Joined <<${guild.name}>> with <<${guild.memberCount?.toLocaleString()}>> members.` diff --git a/src/listeners/guild/guildDelete.ts b/src/listeners/guild/guildDelete.ts index 62d98e3..2cc23f1 100644 --- a/src/listeners/guild/guildDelete.ts +++ b/src/listeners/guild/guildDelete.ts @@ -1,15 +1,14 @@ -import { BushListener, colors, emojis, format, type BushClientEvents } from '#lib'; +import { BotListener, colors, emojis, format, type BotClientEvents } from '#lib'; -export default class GuildDeleteListener extends BushListener { +export default class GuildDeleteListener extends BotListener { public constructor() { super('guildDelete', { emitter: 'client', - event: 'guildDelete', // when the bot leaves a guild - category: 'guild' + event: 'guildDelete' // when the bot leaves a guild }); } - public async exec(...[guild]: BushClientEvents['guildDelete']) { + public async exec(...[guild]: BotClientEvents['guildDelete']) { void this.client.console.info( 'guildDelete', `Left <<${guild.name}>> with <<${guild.memberCount?.toLocaleString()}>> members.` diff --git a/src/listeners/guild/guildMemberAdd.ts b/src/listeners/guild/guildMemberAdd.ts index f1f90af..9268f01 100644 --- a/src/listeners/guild/guildMemberAdd.ts +++ b/src/listeners/guild/guildMemberAdd.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, emojis, format, type BushClientEvents } from '#lib'; +import { BotListener, colors, emojis, format, type BotClientEvents } from '#lib'; import { EmbedBuilder, type GuildMember, type TextChannel } from 'discord.js'; -export default class GuildMemberAddListener extends BushListener { +export default class GuildMemberAddListener extends BotListener { public constructor() { super('guildMemberAdd', { emitter: 'client', - event: 'guildMemberAdd', - category: 'guild' + event: 'guildMemberAdd' }); } - public async exec(...[member]: BushClientEvents['guildMemberAdd']) { + public async exec(...[member]: BotClientEvents['guildMemberAdd']) { void this.sendWelcomeMessage(member); } diff --git a/src/listeners/guild/guildMemberRemove.ts b/src/listeners/guild/guildMemberRemove.ts index 39bab24..ee626d6 100644 --- a/src/listeners/guild/guildMemberRemove.ts +++ b/src/listeners/guild/guildMemberRemove.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, emojis, format, sleep, StickyRole, Time, type BushClientEvents } from '#lib'; +import { BotListener, colors, emojis, format, sleep, StickyRole, Time, type BotClientEvents } from '#lib'; import { EmbedBuilder, type GuildMember, type PartialGuildMember, type TextChannel } from 'discord.js'; -export default class GuildMemberRemoveListener extends BushListener { +export default class GuildMemberRemoveListener extends BotListener { public constructor() { super('guildMemberRemove', { emitter: 'client', - event: 'guildMemberRemove', - category: 'guild' + event: 'guildMemberRemove' }); } - public async exec(...[member]: BushClientEvents['guildMemberRemove']) { + public async exec(...[member]: BotClientEvents['guildMemberRemove']) { void this.sendWelcomeMessage(member); void this.stickyRoles(member); } diff --git a/src/listeners/guild/joinRoles.ts b/src/listeners/guild/joinRoles.ts index 539fa71..142d4cd 100644 --- a/src/listeners/guild/joinRoles.ts +++ b/src/listeners/guild/joinRoles.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, format, StickyRole, type BushClientEvents } from '#lib'; +import { BotListener, colors, format, StickyRole, type BotClientEvents } from '#lib'; import { type GuildMember, type Snowflake } from 'discord.js'; -export default class JoinRolesListener extends BushListener { +export default class JoinRolesListener extends BotListener { public constructor() { super('joinRoles', { emitter: 'client', - event: 'guildMemberUpdate', // listens to guildMemberUpdate so that the role's aren't given before the member accepts the welcome screen - category: 'guild' + event: 'guildMemberUpdate' // listens to guildMemberUpdate so that the role's aren't given before the member accepts the welcome screen }); } - public async exec(...[oldMember, newMember]: BushClientEvents['guildMemberUpdate']) { + public async exec(...[oldMember, newMember]: BotClientEvents['guildMemberUpdate']) { if (this.client.config.isDevelopment) return; if (oldMember.pending && !newMember.pending) { const feat = { diff --git a/src/listeners/guild/syncUnbanPunishmentModel.ts b/src/listeners/guild/syncUnbanPunishmentModel.ts index 80a8ce2..352d704 100644 --- a/src/listeners/guild/syncUnbanPunishmentModel.ts +++ b/src/listeners/guild/syncUnbanPunishmentModel.ts @@ -1,15 +1,14 @@ -import { ActivePunishment, ActivePunishmentType, BushListener, type BushClientEvents } from '#lib'; +import { ActivePunishment, ActivePunishmentType, BotListener, type BotClientEvents } from '#lib'; -export default class SyncUnbanListener extends BushListener { +export default class SyncUnbanListener extends BotListener { public constructor() { super('syncUnbanPunishmentModel', { emitter: 'client', - event: 'guildBanRemove', - category: 'guild' + event: 'guildBanRemove' }); } - public async exec(...[ban]: BushClientEvents['guildBanRemove']) { + public async exec(...[ban]: BotClientEvents['guildBanRemove']) { const bans = await ActivePunishment.findAll({ where: { user: ban.user.id, diff --git a/src/listeners/interaction/interactionCreate.ts b/src/listeners/interaction/interactionCreate.ts index 8dd753b..d8a5cc9 100644 --- a/src/listeners/interaction/interactionCreate.ts +++ b/src/listeners/interaction/interactionCreate.ts @@ -1,16 +1,15 @@ -import { BushListener, emojis, format, handleAutomodInteraction, oxford, surroundArray, type BushClientEvents } from '#lib'; +import { BotListener, emojis, format, handleAutomodInteraction, oxford, surroundArray, type BotClientEvents } from '#lib'; import { InteractionType } from 'discord.js'; -export default class InteractionCreateListener extends BushListener { +export default class InteractionCreateListener extends BotListener { public constructor() { super('interactionCreate', { emitter: 'client', - event: 'interactionCreate', - category: 'interaction' + event: 'interactionCreate' }); } - public async exec(...[interaction]: BushClientEvents['interactionCreate']) { + public async exec(...[interaction]: BotClientEvents['interactionCreate']) { if (!interaction) return; if ('customId' in interaction && (interaction as any)['customId'].startsWith('test')) return; void this.client.console.verbose( diff --git a/src/listeners/member-custom/bushBan.ts b/src/listeners/member-custom/bushBan.ts deleted file mode 100644 index 2cde91d..0000000 --- a/src/listeners/member-custom/bushBan.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, humanizeDuration, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushBanListener extends BushListener { - public constructor() { - super('bushBan', { - emitter: 'client', - event: 'bushBan', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BushClientEvents['bushBan']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Red) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${duration ? 'Temp Ban' : 'Perm Ban'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason.substring(0, 1024) : '[No Reason Provided]'}` } - ); - if (duration) logEmbed.addFields({ name: '**Duration**', value: humanizeDuration(duration) }); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushBlock.ts b/src/listeners/member-custom/bushBlock.ts deleted file mode 100644 index a3af924..0000000 --- a/src/listeners/member-custom/bushBlock.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BushListener, colors, humanizeDuration, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushBlockListener extends BushListener { - public constructor() { - super('bushBlock', { - emitter: 'client', - event: 'bushBlock', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess, channel]: BushClientEvents['bushBlock']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Purple) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${duration ? 'Temp Block' : 'Perm Block'}` }, - { name: '**Channel**', value: `<#${channel.id}>` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - - if (duration) logEmbed.addFields({ name: '**Duration**', value: `${humanizeDuration(duration) || duration}` }); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushKick.ts b/src/listeners/member-custom/bushKick.ts deleted file mode 100644 index ff3e40e..0000000 --- a/src/listeners/member-custom/bushKick.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushKickListener extends BushListener { - public constructor() { - super('bushKick', { - emitter: 'client', - event: 'bushKick', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushKick']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Red) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Kick'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushLevelUpdate.ts b/src/listeners/member-custom/bushLevelUpdate.ts deleted file mode 100644 index 702f7cc..0000000 --- a/src/listeners/member-custom/bushLevelUpdate.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { BushListener, format, type BushClientEvents } from '#lib'; -import assert from 'assert/strict'; -import { type TextChannel } from 'discord.js'; - -type Args = BushClientEvents['bushLevelUpdate']; - -export default class BushLevelUpdateListener extends BushListener { - public constructor() { - super('bushLevelUpdate', { - emitter: 'client', - event: 'bushLevelUpdate', - category: 'member-custom' - }); - } - - public async exec(...[member, _oldLevel, newLevel, _currentXp, message]: Args) { - void this.sendLevelUpMessages(member, newLevel, message); - void this.assignLevelRoles(member, newLevel, message); - } - - private async sendLevelUpMessages(member: Args[0], newLevel: Args[2], message: Args[4]) { - assert(message.inGuild()); - if (!(await message.guild.hasFeature('sendLevelUpMessages'))) return; - - const channel = ((await message.guild.channels - .fetch((await message.guild.getSetting('levelUpChannel')) ?? message.channelId) - .catch(() => null)) ?? message.channel) as TextChannel; - - const success = await channel - .send(`${format.input(member.user.tag)} leveled up to level ${format.input(`${newLevel}`)}.`) - .catch(() => null); - - if (!success) - await message.guild.error( - 'bushLevelUpdate', - `Could not send level up message for ${member.user.tag} in <#${message.channel.id}>.` - ); - } - - private async assignLevelRoles(member: Args[0], newLevel: Args[2], message: Args[4]) { - assert(message.inGuild()); - const levelRoles = await message.guild.getSetting('levelRoles'); - - if (!Object.keys(levelRoles).length) return; - - const promises = []; - for (let i = 1; i <= newLevel; i++) { - if (levelRoles[i]) { - if (member.roles.cache.has(levelRoles[i])) continue; - else promises.push(member.roles.add(levelRoles[i], `[LevelRoles] Role given for reaching level ${i}`)); - } - } - try { - if (promises.length) await Promise.all(promises); - } catch (e: any) { - await member.guild.error( - 'bushLevelUpdate', - `There was an error adding level roles to ${member.user.tag} upon reaching to level ${newLevel}.\n${ - 'message' in e ? e.message : e - }` - ); - } - } -} diff --git a/src/listeners/member-custom/bushMute.ts b/src/listeners/member-custom/bushMute.ts deleted file mode 100644 index 73f9490..0000000 --- a/src/listeners/member-custom/bushMute.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, humanizeDuration, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushMuteListener extends BushListener { - public constructor() { - super('bushMute', { - emitter: 'client', - event: 'bushMute', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BushClientEvents['bushMute']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Orange) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${duration ? 'Temp Mute' : 'Perm Mute'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (duration) logEmbed.addFields({ name: '**Duration**', value: `${humanizeDuration(duration) || duration}` }); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushPunishRole.ts b/src/listeners/member-custom/bushPunishRole.ts deleted file mode 100644 index 1f28811..0000000 --- a/src/listeners/member-custom/bushPunishRole.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BushListener, colors, humanizeDuration, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushPunishRoleListener extends BushListener { - public constructor() { - super('bushPunishRole', { - emitter: 'client', - event: 'bushPunishRole', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, duration]: BushClientEvents['bushPunishRole']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Yellow) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${duration ? 'Temp Punishment Role' : 'Perm Punishment Role'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (duration) logEmbed.addFields({ name: '**Duration**', value: humanizeDuration(duration) }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushPunishRoleRemove.ts b/src/listeners/member-custom/bushPunishRoleRemove.ts deleted file mode 100644 index ef1d429..0000000 --- a/src/listeners/member-custom/bushPunishRoleRemove.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushPunishRoleRemoveListener extends BushListener { - public constructor() { - super('bushPunishRoleRemove', { - emitter: 'client', - event: 'bushPunishRoleRemove', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, role]: BushClientEvents['bushPunishRoleRemove']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Green) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Remove Punishment Role'}` }, - { name: '**Role**', value: `${role}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushPurge.ts b/src/listeners/member-custom/bushPurge.ts deleted file mode 100644 index 75faaeb..0000000 --- a/src/listeners/member-custom/bushPurge.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { BushListener, colors, emojis, type BushClientEvents } from '#lib'; -import { EmbedBuilder } from 'discord.js'; - -export default class BushPurgeListener extends BushListener { - public constructor() { - super('bushPurge', { - emitter: 'client', - event: 'bushPurge', - category: 'member-custom' - }); - } - - public async exec(...[moderator, guild, channel, messages]: BushClientEvents['bushPurge']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - - const mappedMessages = messages.map((m) => ({ - id: m.id, - author: `${m.author.tag} (${m.id})`, - content: m.content, - embeds: m.embeds, - attachments: [...m.attachments.values()] - })); - const haste = await this.client.utils.inspectCleanRedactHaste(mappedMessages); - - const logEmbed = new EmbedBuilder() - .setColor(colors.DarkPurple) - .setTimestamp() - .setFooter({ text: `${messages.size.toLocaleString()} Messages` }) - .setAuthor({ name: moderator.tag, iconURL: moderator.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Purge'}` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Channel**', value: `<#${channel.id}> (${channel.name})` }, - { - name: '**Messages**', - value: `${ - haste.url ? `[haste](${haste.url})${haste.error ? `- ${haste.error}` : ''}` : `${emojis.error} ${haste.error}` - }` - } - ); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushRemoveTimeout.ts b/src/listeners/member-custom/bushRemoveTimeout.ts deleted file mode 100644 index c389538..0000000 --- a/src/listeners/member-custom/bushRemoveTimeout.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushRemoveTimeoutListener extends BushListener { - public constructor() { - super('bushRemoveTimeout', { - emitter: 'client', - event: 'bushRemoveTimeout', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushRemoveTimeout']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Green) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Remove Timeout'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushTimeout.ts b/src/listeners/member-custom/bushTimeout.ts deleted file mode 100644 index 82169d6..0000000 --- a/src/listeners/member-custom/bushTimeout.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, humanizeDuration, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushTimeoutListener extends BushListener { - public constructor() { - super('bushTimeout', { - emitter: 'client', - event: 'bushTimeout', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BushClientEvents['bushTimeout']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Orange) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Timeout'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }, - { name: '**Duration**', value: `${humanizeDuration(duration) || duration}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushUnban.ts b/src/listeners/member-custom/bushUnban.ts deleted file mode 100644 index 40394cd..0000000 --- a/src/listeners/member-custom/bushUnban.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushUnbanListener extends BushListener { - public constructor() { - super('bushUnban', { - emitter: 'client', - event: 'bushUnban', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushUnban']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Green) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Unban'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushUnblock.ts b/src/listeners/member-custom/bushUnblock.ts deleted file mode 100644 index 867d391..0000000 --- a/src/listeners/member-custom/bushUnblock.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushUnblockListener extends BushListener { - public constructor() { - super('bushUnblock', { - emitter: 'client', - event: 'bushUnblock', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess, channel]: BushClientEvents['bushUnblock']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Green) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Unblock'}` }, - { name: '**Channel**', value: `<#${channel.id}>` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushUnmute.ts b/src/listeners/member-custom/bushUnmute.ts deleted file mode 100644 index 2ff53b9..0000000 --- a/src/listeners/member-custom/bushUnmute.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushUnmuteListener extends BushListener { - public constructor() { - super('bushUnmute', { - emitter: 'client', - event: 'bushUnmute', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushUnmute']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Green) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: `${'Unmute'}` }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushUpdateModlog.ts b/src/listeners/member-custom/bushUpdateModlog.ts deleted file mode 100644 index 1896ede..0000000 --- a/src/listeners/member-custom/bushUpdateModlog.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder } from 'discord.js'; - -export default class BushUpdateModlogListener extends BushListener { - public constructor() { - super('bushUpdateModlog', { - emitter: 'client', - event: 'bushUpdateModlog', - category: 'member-custom' - }); - } - - public async exec(...[moderator, modlogID, key, oldModlog, newModlog]: BushClientEvents['bushUpdateModlog']) { - const logChannel = await moderator.guild.getLogChannel('moderation'); - if (!logChannel) return; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Blurple) - .setTimestamp() - .setAuthor({ - name: moderator.user.tag, - iconURL: moderator.user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined - }) - .addFields( - { name: '**Action**', value: 'Update Modlog' }, - { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }, - { name: '**ModLog Changed**', value: modlogID }, - { name: '**Value Changed**', value: key }, - { - name: '**Old Value**', - value: await this.client.utils.inspectCleanRedactCodeblock(oldModlog, undefined, undefined, 1024) - }, - { - name: '**New Value**', - value: await this.client.utils.inspectCleanRedactCodeblock(newModlog, undefined, undefined, 1024) - } - ); - - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushUpdateSettings.ts b/src/listeners/member-custom/bushUpdateSettings.ts deleted file mode 100644 index a0be2f9..0000000 --- a/src/listeners/member-custom/bushUpdateSettings.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder } from 'discord.js'; - -export default class BushUpdateSettingsListener extends BushListener { - public constructor() { - super('bushUpdateSettings', { - emitter: 'client', - event: 'bushUpdateSettings', - category: 'member-custom' - }); - } - - public async exec(...[setting, guild, oldSettings, newSettings, moderator]: BushClientEvents['bushUpdateSettings']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - - const logEmbed = new EmbedBuilder().setColor(colors.Blurple).setTimestamp(); - - if (moderator) - logEmbed.setAuthor({ - name: moderator.user.tag, - iconURL: moderator.user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined - }); - logEmbed.addFields({ name: '**Action**', value: `${'Update Settings'}` }); - if (moderator) logEmbed.addFields({ name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }); - logEmbed.addFields( - { name: '**Setting Changed**', value: setting }, - { name: '**Old Value**', value: await this.client.utils.inspectCleanRedactCodeblock(oldSettings, 'js', undefined, 1024) }, - { name: '**New Value**', value: await this.client.utils.inspectCleanRedactCodeblock(newSettings, 'js', undefined, 1024) } - ); - - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/bushWarn.ts b/src/listeners/member-custom/bushWarn.ts deleted file mode 100644 index 822a491..0000000 --- a/src/listeners/member-custom/bushWarn.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; -import { EmbedBuilder, GuildMember } from 'discord.js'; - -export default class BushWarnListener extends BushListener { - public constructor() { - super('bushWarn', { - emitter: 'client', - event: 'bushWarn', - category: 'member-custom' - }); - } - - public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushWarn']) { - const logChannel = await guild.getLogChannel('moderation'); - if (!logChannel) return; - const user = victim instanceof GuildMember ? victim.user : victim; - - const logEmbed = new EmbedBuilder() - .setColor(colors.Yellow) - .setTimestamp() - .setFooter({ text: `CaseID: ${caseID}` }) - .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) - .addFields( - { name: '**Action**', value: 'Warn' }, - { name: '**User**', value: `${user} (${user.tag})` }, - { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, - { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } - ); - if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); - return await logChannel.send({ embeds: [logEmbed] }); - } -} diff --git a/src/listeners/member-custom/customBan.ts b/src/listeners/member-custom/customBan.ts new file mode 100644 index 0000000..5b199bb --- /dev/null +++ b/src/listeners/member-custom/customBan.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, humanizeDuration, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomBanListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Ban, { + emitter: 'client', + event: TanzaniteEvent.Ban + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BotClientEvents[TanzaniteEvent.Ban]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Red) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${duration ? 'Temp Ban' : 'Perm Ban'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason.substring(0, 1024) : '[No Reason Provided]'}` } + ); + if (duration) logEmbed.addFields({ name: '**Duration**', value: humanizeDuration(duration) }); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customBlock.ts b/src/listeners/member-custom/customBlock.ts new file mode 100644 index 0000000..8faa0b7 --- /dev/null +++ b/src/listeners/member-custom/customBlock.ts @@ -0,0 +1,36 @@ +import { BotListener, colors, humanizeDuration, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomBlockListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Block, { + emitter: 'client', + event: TanzaniteEvent.Block + }); + } + + public async exec( + ...[victim, moderator, guild, reason, caseID, duration, dmSuccess, channel]: BotClientEvents[TanzaniteEvent.Block] + ) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Purple) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${duration ? 'Temp Block' : 'Perm Block'}` }, + { name: '**Channel**', value: `<#${channel.id}>` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + + if (duration) logEmbed.addFields({ name: '**Duration**', value: `${humanizeDuration(duration) || duration}` }); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customKick.ts b/src/listeners/member-custom/customKick.ts new file mode 100644 index 0000000..bf3e2fe --- /dev/null +++ b/src/listeners/member-custom/customKick.ts @@ -0,0 +1,31 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomKickListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Kick, { + emitter: 'client', + event: TanzaniteEvent.Kick + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BotClientEvents[TanzaniteEvent.Kick]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Red) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Kick'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customMute.ts b/src/listeners/member-custom/customMute.ts new file mode 100644 index 0000000..21a24e8 --- /dev/null +++ b/src/listeners/member-custom/customMute.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, humanizeDuration, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomMuteListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Mute, { + emitter: 'client', + event: TanzaniteEvent.Mute + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BotClientEvents[TanzaniteEvent.Mute]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Orange) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${duration ? 'Temp Mute' : 'Perm Mute'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (duration) logEmbed.addFields({ name: '**Duration**', value: `${humanizeDuration(duration) || duration}` }); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customPurge.ts b/src/listeners/member-custom/customPurge.ts new file mode 100644 index 0000000..956fcb7 --- /dev/null +++ b/src/listeners/member-custom/customPurge.ts @@ -0,0 +1,43 @@ +import { BotListener, colors, emojis, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder } from 'discord.js'; + +export default class CustomPurgeListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Purge, { + emitter: 'client', + event: TanzaniteEvent.Purge + }); + } + + public async exec(...[moderator, guild, channel, messages]: BotClientEvents[TanzaniteEvent.Purge]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + + const mappedMessages = messages.map((m) => ({ + id: m.id, + author: `${m.author.tag} (${m.id})`, + content: m.content, + embeds: m.embeds, + attachments: [...m.attachments.values()] + })); + const haste = await this.client.utils.inspectCleanRedactHaste(mappedMessages); + + const logEmbed = new EmbedBuilder() + .setColor(colors.DarkPurple) + .setTimestamp() + .setFooter({ text: `${messages.size.toLocaleString()} Messages` }) + .setAuthor({ name: moderator.tag, iconURL: moderator.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Purge'}` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Channel**', value: `<#${channel.id}> (${channel.name})` }, + { + name: '**Messages**', + value: `${ + haste.url ? `[haste](${haste.url})${haste.error ? `- ${haste.error}` : ''}` : `${emojis.error} ${haste.error}` + }` + } + ); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customRemoveTimeout.ts b/src/listeners/member-custom/customRemoveTimeout.ts new file mode 100644 index 0000000..00454bd --- /dev/null +++ b/src/listeners/member-custom/customRemoveTimeout.ts @@ -0,0 +1,31 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomRemoveTimeoutListener extends BotListener { + public constructor() { + super(TanzaniteEvent.RemoveTimeout, { + emitter: 'client', + event: TanzaniteEvent.RemoveTimeout + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BotClientEvents[TanzaniteEvent.RemoveTimeout]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Green) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Remove Timeout'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customTimeout.ts b/src/listeners/member-custom/customTimeout.ts new file mode 100644 index 0000000..6b142ec --- /dev/null +++ b/src/listeners/member-custom/customTimeout.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, humanizeDuration, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomTimeoutListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Timeout, { + emitter: 'client', + event: TanzaniteEvent.Timeout + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BotClientEvents[TanzaniteEvent.Timeout]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Orange) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Timeout'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` }, + { name: '**Duration**', value: `${humanizeDuration(duration) || duration}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customUnban.ts b/src/listeners/member-custom/customUnban.ts new file mode 100644 index 0000000..aa4cd75 --- /dev/null +++ b/src/listeners/member-custom/customUnban.ts @@ -0,0 +1,31 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomUnbanListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Unban, { + emitter: 'client', + event: TanzaniteEvent.Unban + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BotClientEvents[TanzaniteEvent.Unban]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Green) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Unban'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customUnblock.ts b/src/listeners/member-custom/customUnblock.ts new file mode 100644 index 0000000..a7553d9 --- /dev/null +++ b/src/listeners/member-custom/customUnblock.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomUnblockListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Unblock, { + emitter: 'client', + event: TanzaniteEvent.Unblock + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess, channel]: BotClientEvents[TanzaniteEvent.Unblock]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Green) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Unblock'}` }, + { name: '**Channel**', value: `<#${channel.id}>` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customUnmute.ts b/src/listeners/member-custom/customUnmute.ts new file mode 100644 index 0000000..ef7b22f --- /dev/null +++ b/src/listeners/member-custom/customUnmute.ts @@ -0,0 +1,31 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomUnmuteListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Unmute, { + emitter: 'client', + event: TanzaniteEvent.Unmute + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BotClientEvents[TanzaniteEvent.Unmute]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Green) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Unmute'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/customWarnMember.ts b/src/listeners/member-custom/customWarnMember.ts new file mode 100644 index 0000000..3ca7e08 --- /dev/null +++ b/src/listeners/member-custom/customWarnMember.ts @@ -0,0 +1,31 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class CustomWarnListener extends BotListener { + public constructor() { + super(TanzaniteEvent.Warn, { + emitter: 'client', + event: TanzaniteEvent.Warn + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BotClientEvents[TanzaniteEvent.Warn]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Yellow) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: 'Warn' }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (dmSuccess === false) logEmbed.addFields({ name: '**Additional Info**', value: 'Could not dm user.' }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/levelUpdate.ts b/src/listeners/member-custom/levelUpdate.ts new file mode 100644 index 0000000..64dd388 --- /dev/null +++ b/src/listeners/member-custom/levelUpdate.ts @@ -0,0 +1,63 @@ +import { BotListener, format, TanzaniteEvent, type BotClientEvents } from '#lib'; +import assert from 'assert/strict'; +import { type TextChannel } from 'discord.js'; + +type Args = BotClientEvents[TanzaniteEvent.LevelUpdate]; + +export default class LevelUpdateListener extends BotListener { + public constructor() { + super(TanzaniteEvent.LevelUpdate, { + emitter: 'client', + event: TanzaniteEvent.LevelUpdate + }); + } + + public async exec(...[member, _oldLevel, newLevel, _currentXp, message]: Args) { + void this.sendLevelUpMessages(member, newLevel, message); + void this.assignLevelRoles(member, newLevel, message); + } + + private async sendLevelUpMessages(member: Args[0], newLevel: Args[2], message: Args[4]) { + assert(message.inGuild()); + if (!(await message.guild.hasFeature('sendLevelUpMessages'))) return; + + const channel = ((await message.guild.channels + .fetch((await message.guild.getSetting('levelUpChannel')) ?? message.channelId) + .catch(() => null)) ?? message.channel) as TextChannel; + + const success = await channel + .send(`${format.input(member.user.tag)} leveled up to level ${format.input(`${newLevel}`)}.`) + .catch(() => null); + + if (!success) + await message.guild.error( + 'LevelUpdate', + `Could not send level up message for ${member.user.tag} in <#${message.channel.id}>.` + ); + } + + private async assignLevelRoles(member: Args[0], newLevel: Args[2], message: Args[4]) { + assert(message.inGuild()); + const levelRoles = await message.guild.getSetting('levelRoles'); + + if (!Object.keys(levelRoles).length) return; + + const promises = []; + for (let i = 1; i <= newLevel; i++) { + if (levelRoles[i]) { + if (member.roles.cache.has(levelRoles[i])) continue; + else promises.push(member.roles.add(levelRoles[i], `[LevelRoles] Role given for reaching level ${i}`)); + } + } + try { + if (promises.length) await Promise.all(promises); + } catch (e: any) { + await member.guild.error( + 'LevelUpdate', + `There was an error adding level roles to ${member.user.tag} upon reaching to level ${newLevel}.\n${ + 'message' in e ? e.message : e + }` + ); + } + } +} diff --git a/src/listeners/member-custom/massBan.ts b/src/listeners/member-custom/massBan.ts index ca41f29..7a6cd34 100644 --- a/src/listeners/member-custom/massBan.ts +++ b/src/listeners/member-custom/massBan.ts @@ -1,15 +1,14 @@ -import { BanResponse, banResponse, BushListener, colors, emojis, overflowEmbed, type BushClientEvents } from '#lib'; +import { BanResponse, banResponse, BotListener, colors, emojis, overflowEmbed, TanzaniteEvent, type BotClientEvents } from '#lib'; -export default class MassBanListener extends BushListener { +export default class MassBanListener extends BotListener { public constructor() { - super('massBan', { + super(TanzaniteEvent.MassBan, { emitter: 'client', - event: 'massBan', - category: 'member-custom' + event: TanzaniteEvent.MassBan }); } - public async exec(...[moderator, guild, reason, results]: BushClientEvents['massBan']) { + public async exec(...[moderator, guild, reason, results]: BotClientEvents[TanzaniteEvent.MassBan]) { const logChannel = await guild.getLogChannel('moderation'); if (!logChannel) return; diff --git a/src/listeners/member-custom/massEvidence.ts b/src/listeners/member-custom/massEvidence.ts index f0f75df..acfa4f7 100644 --- a/src/listeners/member-custom/massEvidence.ts +++ b/src/listeners/member-custom/massEvidence.ts @@ -1,15 +1,14 @@ -import { BushListener, colors, overflowEmbed, type BushClientEvents } from '#lib'; +import { BotListener, colors, overflowEmbed, TanzaniteEvent, type BotClientEvents } from '#lib'; -export default class MassEvidenceListener extends BushListener { +export default class MassEvidenceListener extends BotListener { public constructor() { - super('massEvidence', { + super(TanzaniteEvent.MassEvidence, { emitter: 'client', - event: 'massEvidence', - category: 'member-custom' + event: TanzaniteEvent.MassEvidence }); } - public async exec(...[moderator, guild, evidence, lines]: BushClientEvents['massEvidence']) { + public async exec(...[moderator, guild, evidence, lines]: BotClientEvents[TanzaniteEvent.MassEvidence]) { const logChannel = await guild.getLogChannel('moderation'); if (!logChannel) return; diff --git a/src/listeners/member-custom/punishRole.ts b/src/listeners/member-custom/punishRole.ts new file mode 100644 index 0000000..70a04d7 --- /dev/null +++ b/src/listeners/member-custom/punishRole.ts @@ -0,0 +1,31 @@ +import { BotListener, colors, humanizeDuration, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class PunishRoleListener extends BotListener { + public constructor() { + super(TanzaniteEvent.PunishRoleAdd, { + emitter: 'client', + event: TanzaniteEvent.PunishRoleAdd + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, duration]: BotClientEvents[TanzaniteEvent.PunishRoleAdd]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Yellow) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${duration ? 'Temp Punishment Role' : 'Perm Punishment Role'}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + if (duration) logEmbed.addFields({ name: '**Duration**', value: humanizeDuration(duration) }); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/punishRoleRemove.ts b/src/listeners/member-custom/punishRoleRemove.ts new file mode 100644 index 0000000..f144284 --- /dev/null +++ b/src/listeners/member-custom/punishRoleRemove.ts @@ -0,0 +1,32 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder, GuildMember } from 'discord.js'; + +export default class PunishRoleRemoveListener extends BotListener { + public constructor() { + super(TanzaniteEvent.PunishRoleRemove, { + emitter: 'client', + event: TanzaniteEvent.PunishRoleRemove + }); + } + + public async exec(...[victim, moderator, guild, reason, caseID, role]: BotClientEvents[TanzaniteEvent.PunishRoleRemove]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Green) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined }) + .addFields( + { name: '**Action**', value: `${'Remove Punishment Role'}` }, + { name: '**Role**', value: `${role}` }, + { name: '**User**', value: `${user} (${user.tag})` }, + { name: '**Moderator**', value: `${moderator} (${moderator.tag})` }, + { name: '**Reason**', value: `${reason ? reason : '[No Reason Provided]'}` } + ); + + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/updateModlog.ts b/src/listeners/member-custom/updateModlog.ts new file mode 100644 index 0000000..f5f910d --- /dev/null +++ b/src/listeners/member-custom/updateModlog.ts @@ -0,0 +1,40 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder } from 'discord.js'; + +export default class UpdateModlogListener extends BotListener { + public constructor() { + super(TanzaniteEvent.UpdateModlog, { + emitter: 'client', + event: TanzaniteEvent.UpdateModlog + }); + } + + public async exec(...[moderator, modlogID, key, oldModlog, newModlog]: BotClientEvents[TanzaniteEvent.UpdateModlog]) { + const logChannel = await moderator.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new EmbedBuilder() + .setColor(colors.Blurple) + .setTimestamp() + .setAuthor({ + name: moderator.user.tag, + iconURL: moderator.user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined + }) + .addFields( + { name: '**Action**', value: 'Update Modlog' }, + { name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }, + { name: '**ModLog Changed**', value: modlogID }, + { name: '**Value Changed**', value: key }, + { + name: '**Old Value**', + value: await this.client.utils.inspectCleanRedactCodeblock(oldModlog, undefined, undefined, 1024) + }, + { + name: '**New Value**', + value: await this.client.utils.inspectCleanRedactCodeblock(newModlog, undefined, undefined, 1024) + } + ); + + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/member-custom/updateSettings.ts b/src/listeners/member-custom/updateSettings.ts new file mode 100644 index 0000000..329212a --- /dev/null +++ b/src/listeners/member-custom/updateSettings.ts @@ -0,0 +1,33 @@ +import { BotListener, colors, TanzaniteEvent, type BotClientEvents } from '#lib'; +import { EmbedBuilder } from 'discord.js'; + +export default class UpdateSettingsListener extends BotListener { + public constructor() { + super(TanzaniteEvent.UpdateSettings, { + emitter: 'client', + event: TanzaniteEvent.UpdateSettings + }); + } + + public async exec(...[setting, guild, oldSettings, newSettings, moderator]: BotClientEvents[TanzaniteEvent.UpdateSettings]) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new EmbedBuilder().setColor(colors.Blurple).setTimestamp(); + + if (moderator) + logEmbed.setAuthor({ + name: moderator.user.tag, + iconURL: moderator.user.avatarURL({ extension: 'png', size: 4096 }) ?? undefined + }); + logEmbed.addFields({ name: '**Action**', value: `${'Update Settings'}` }); + if (moderator) logEmbed.addFields({ name: '**Moderator**', value: `${moderator} (${moderator.user.tag})` }); + logEmbed.addFields( + { name: '**Setting Changed**', value: setting }, + { name: '**Old Value**', value: await this.client.utils.inspectCleanRedactCodeblock(oldSettings, 'js', undefined, 1024) }, + { name: '**New Value**', value: await this.client.utils.inspectCleanRedactCodeblock(newSettings, 'js', undefined, 1024) } + ); + + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/message/autoPublisher.ts b/src/listeners/message/autoPublisher.ts index a6fb7e0..36dcbe2 100644 --- a/src/listeners/message/autoPublisher.ts +++ b/src/listeners/message/autoPublisher.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushClientEvents } from '#lib'; +import { BotListener, type BotClientEvents } from '#lib'; import { ChannelType } from 'discord.js'; -export default class autoPublisherListener extends BushListener { +export default class autoPublisherListener extends BotListener { public constructor() { super('autoPublisher', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']) { + public async exec(...[message]: BotClientEvents['messageCreate']) { if (!message.guild || !(await message.guild.hasFeature('autoPublish'))) return; const autoPublishChannels = await message.guild.getSetting('autoPublishChannels'); if (autoPublishChannels) { diff --git a/src/listeners/message/blacklistedFile.ts b/src/listeners/message/blacklistedFile.ts index f3e082c..31fbe9d 100644 --- a/src/listeners/message/blacklistedFile.ts +++ b/src/listeners/message/blacklistedFile.ts @@ -1,9 +1,9 @@ -// import { BushListener, type BushClientEvents } from '#lib'; +// import { CustomListener, type CustomClientEvents } from '#lib'; // import * as crypto from 'crypto'; // import { ChannelType } from 'discord.js'; // import got from 'got'; -// export default class BlacklistedFileListener extends BushListener { +// export default class BlacklistedFileListener extends CustomListener { // #blacklistedFiles: { hash: string[]; name: string; description: string }[] = [ // { // hash: ['a0f5e30426234bc9d09306ffc9474422'], @@ -61,11 +61,10 @@ // super('blacklistedFile', { // emitter: 'client', // event: 'messageCreate', -// category: 'message' // }); // } -// public async exec(...[message]: BushClientEvents['messageCreate']) { +// public async exec(...[message]: CustomClientEvents['messageCreate']) { // if (!message.guild || !(await message.guild.hasFeature('blacklistedFile'))) return; // // const embedAttachments = message.embeds.filter((e) => ['image', 'video', 'gifv'].includes(e.type)); // const foundEmojis = [...message.content.matchAll(/<(?a?):\w+:(?\d+)>/g)]; diff --git a/src/listeners/message/boosterMessage.ts b/src/listeners/message/boosterMessage.ts index 0879ced..43fb04e 100644 --- a/src/listeners/message/boosterMessage.ts +++ b/src/listeners/message/boosterMessage.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushClientEvents } from '#lib'; +import { BotListener, type BotClientEvents } from '#lib'; import { MessageType } from 'discord.js'; -export default class BoosterMessageListener extends BushListener { +export default class BoosterMessageListener extends BotListener { public constructor() { super('boosterMessage', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']) { + public async exec(...[message]: BotClientEvents['messageCreate']) { if (!message.guild || !(await message.guild?.hasFeature('boosterMessageReact'))) return; if ( [MessageType.GuildBoost, MessageType.GuildBoostTier1, MessageType.GuildBoostTier2, MessageType.GuildBoostTier3].includes( diff --git a/src/listeners/message/directMessage.ts b/src/listeners/message/directMessage.ts index 7278e63..efa5bb6 100644 --- a/src/listeners/message/directMessage.ts +++ b/src/listeners/message/directMessage.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, type BushClientEvents } from '#lib'; +import { BotListener, colors, type BotClientEvents } from '#lib'; import { ChannelType, EmbedBuilder } from 'discord.js'; -export default class DirectMessageListener extends BushListener { +export default class DirectMessageListener extends BotListener { public constructor() { super('directMessage', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']) { + public async exec(...[message]: BotClientEvents['messageCreate']) { if (message.channel.type === ChannelType.DM) { if (!(message.author.id == this.client.user!.id) && message.author.bot) return; if (this.client.cache.global.blacklistedUsers.includes(message.author.id)) return; diff --git a/src/listeners/message/highlight.ts b/src/listeners/message/highlight.ts index d9d3c0b..d5d8304 100644 --- a/src/listeners/message/highlight.ts +++ b/src/listeners/message/highlight.ts @@ -1,15 +1,14 @@ -import { BushListener, type BushClientEvents } from '#lib'; +import { BotListener, type BotClientEvents } from '#lib'; -export default class HighlightListener extends BushListener { +export default class HighlightListener extends BotListener { public constructor() { super('highlight', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']) { + public async exec(...[message]: BotClientEvents['messageCreate']) { if (!message.inGuild()) return; if (message.author.bot || message.system) return; if (!(await message.guild.hasFeature('highlight'))) return; // allows highlighting to be disabled on a guild-by-guild basis diff --git a/src/listeners/message/level.ts b/src/listeners/message/level.ts index 526dac9..5c314a8 100644 --- a/src/listeners/message/level.ts +++ b/src/listeners/message/level.ts @@ -1,17 +1,16 @@ -import { BushListener, Level, type BushCommandHandlerEvents } from '#lib'; +import { BotListener, Level, TanzaniteEvent, type BotCommandHandlerEvents } from '#lib'; import { MessageType } from 'discord.js'; -export default class LevelListener extends BushListener { +export default class LevelListener extends BotListener { #levelCooldowns: Set = new Set(); public constructor() { super('level', { emitter: 'commandHandler', - event: 'messageInvalid', // Using messageInvalid here so commands don't give xp - category: 'message' + event: 'messageInvalid' // Using messageInvalid here so commands don't give xp }); } - public async exec(...[message]: BushCommandHandlerEvents['messageInvalid']) { + public async exec(...[message]: BotCommandHandlerEvents['messageInvalid']) { if (message.author.bot || !message.author || !message.inGuild()) return; if (!(await message.guild.hasFeature('leveling'))) return; if (this.#levelCooldowns.has(`${message.guildId}-${message.author.id}`)) return; @@ -38,7 +37,7 @@ export default class LevelListener extends BushListener { }); const newLevel = Level.convertXpToLevel(user.xp); if (previousLevel !== newLevel) - this.client.emit('bushLevelUpdate', message.member!, previousLevel, newLevel, user.xp, message); + this.client.emit(TanzaniteEvent.LevelUpdate, message.member!, previousLevel, newLevel, user.xp, message); if (success) void this.client.logger.verbose(`level`, `Gave <<${xpToGive}>> XP to <<${message.author.tag}>> in <<${message.guild}>>.`); this.#levelCooldowns.add(`${message.guildId}-${message.author.id}`); diff --git a/src/listeners/message/quoteCreate.ts b/src/listeners/message/quoteCreate.ts index 166bbb7..9bad9b8 100644 --- a/src/listeners/message/quoteCreate.ts +++ b/src/listeners/message/quoteCreate.ts @@ -1,15 +1,14 @@ -import { BushListener, mappings, type BushClientEvents } from '#lib'; +import { BotListener, mappings, type BotClientEvents } from '#lib'; -export default class QuoteCreateListener extends BushListener { +export default class QuoteCreateListener extends BotListener { public constructor() { super('quoteCreate', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public async exec(...[message]: BushClientEvents['messageCreate']) { + public async exec(...[message]: BotClientEvents['messageCreate']) { if (message.author.id !== mappings.users['IRONM00N'] || !this.client.config.isProduction) return; if (!message.inGuild()) return; diff --git a/src/listeners/message/quoteEdit.ts b/src/listeners/message/quoteEdit.ts index 7da0d95..8422026 100644 --- a/src/listeners/message/quoteEdit.ts +++ b/src/listeners/message/quoteEdit.ts @@ -1,15 +1,14 @@ -// import { BushListener, type BushClientEvents } from '#lib'; +// import { CustomListener, type CustomClientEvents } from '#lib'; -// export default class QuoteEditListener extends BushListener { +// export default class QuoteEditListener extends CustomListener { // public constructor() { // super('quoteEdit', { // emitter: 'client', // event: 'messageUpdate', -// category: 'message' // }); // } -// public async exec(...[_, newMessage]: BushClientEvents['messageUpdate']) { +// public async exec(...[_, newMessage]: CustomClientEvents['messageUpdate']) { // return; // // if (newMessage.partial) newMessage = await newMessage.fetch(); // // return new QuoteCreateListener().exec(newMessage); diff --git a/src/listeners/message/verbose.ts b/src/listeners/message/verbose.ts index f5e94de..9c4ac3a 100644 --- a/src/listeners/message/verbose.ts +++ b/src/listeners/message/verbose.ts @@ -1,16 +1,15 @@ -import { BushListener, type BushClientEvents } from '#lib'; +import { BotListener, type BotClientEvents } from '#lib'; import { ChannelType } from 'discord.js'; -export default class MessageVerboseListener extends BushListener { +export default class MessageVerboseListener extends BotListener { public constructor() { super('messageVerbose', { emitter: 'client', - event: 'messageCreate', - category: 'message' + event: 'messageCreate' }); } - public exec(...[message]: BushClientEvents['messageCreate']): void { + public exec(...[message]: BotClientEvents['messageCreate']): void { if (this.client.customReady) { if (message.channel?.type === ChannelType.DM) return; void this.client.console.verbose( diff --git a/src/listeners/other/consoleListener.ts b/src/listeners/other/consoleListener.ts index b9da46e..dc6b66b 100644 --- a/src/listeners/other/consoleListener.ts +++ b/src/listeners/other/consoleListener.ts @@ -1,60 +1,60 @@ -import { BushListener } from '#lib'; -import { exec } from 'child_process'; -import { promisify } from 'util'; +// import { CustomListener } from '#lib'; +// import { exec } from 'child_process'; +// import { promisify } from 'util'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -/* export default */ class ConsoleListener extends BushListener { - public constructor() { - super('console', { - emitter: 'stdin', - event: 'line' - }); - } +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// /* export default */ class ConsoleListener extends CustomListener { +// public constructor() { +// super('console', { +// emitter: 'stdin', +// event: 'line' +// }); +// } - public async exec(line: string) { - if (line.startsWith('eval ') || line.startsWith('ev ')) { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const sh = promisify(exec), - bot = this.client, - client = this.client, - config = this.client.config, - { ActivePunishment, Global, Guild, Level, ModLog, StickyRole } = await import('#lib'), - { - ButtonInteraction, - Collector, - CommandInteraction, - Message, - ActionRow, - Attachment, - ButtonComponent, - MessageCollector, - InteractionCollector, - Embed, - SelectMenuComponent, - ReactionCollector, - Collection - } = await import('discord.js'); - /* eslint-enable @typescript-eslint/no-unused-vars */ - try { - const depth = /--depth (?\d+)/.exec(line)?.groups?.depth ?? undefined; - const hidden = /--hidden/.test(line); - if (depth) line = line.replace(/--depth \d+/, ''); - if (hidden) line = line.replace(/--hidden/, ''); - const input = line.replace('eval ', '').replace('ev ', ''); - const output = await eval(input); - console.dir(output, { - colors: true, - getters: true, - maxArrayLength: Infinity, - maxStringLength: Infinity, - depth: +(depth ?? 2), - showHidden: hidden - }); - } catch (e) { - console.error(e); - } - } else if (line.startsWith('stop')) { - process.exit(0); - } - } -} +// public async exec(line: string) { +// if (line.startsWith('eval ') || line.startsWith('ev ')) { +// /* eslint-disable @typescript-eslint/no-unused-vars */ +// const sh = promisify(exec), +// bot = this.client, +// client = this.client, +// config = this.client.config, +// { ActivePunishment, Global, Guild, Level, ModLog, StickyRole } = await import('#lib'), +// { +// ButtonInteraction, +// Collector, +// CommandInteraction, +// Message, +// ActionRow, +// Attachment, +// ButtonComponent, +// MessageCollector, +// InteractionCollector, +// Embed, +// SelectMenuComponent, +// ReactionCollector, +// Collection +// } = await import('discord.js'); +// /* eslint-enable @typescript-eslint/no-unused-vars */ +// try { +// const depth = /--depth (?\d+)/.exec(line)?.groups?.depth ?? undefined; +// const hidden = /--hidden/.test(line); +// if (depth) line = line.replace(/--depth \d+/, ''); +// if (hidden) line = line.replace(/--hidden/, ''); +// const input = line.replace('eval ', '').replace('ev ', ''); +// const output = await eval(input); +// console.dir(output, { +// colors: true, +// getters: true, +// maxArrayLength: Infinity, +// maxStringLength: Infinity, +// depth: +(depth ?? 2), +// showHidden: hidden +// }); +// } catch (e) { +// console.error(e); +// } +// } else if (line.startsWith('stop')) { +// process.exit(0); +// } +// } +// } diff --git a/src/listeners/other/exit.ts b/src/listeners/other/exit.ts index ac074df..e1c8fee 100644 --- a/src/listeners/other/exit.ts +++ b/src/listeners/other/exit.ts @@ -1,6 +1,6 @@ -import { BushListener } from '#lib'; +import { BotListener } from '#lib'; -export default class ExitListener extends BushListener { +export default class ExitListener extends BotListener { public constructor() { super('exit', { emitter: 'process', diff --git a/src/listeners/other/promiseRejection.ts b/src/listeners/other/promiseRejection.ts index 4ff6c0e..b56cef9 100644 --- a/src/listeners/other/promiseRejection.ts +++ b/src/listeners/other/promiseRejection.ts @@ -1,6 +1,6 @@ -import { BushListener, formatError, generateErrorEmbed } from '#lib'; +import { BotListener, formatError, generateErrorEmbed } from '#lib'; -export default class PromiseRejectionListener extends BushListener { +export default class PromiseRejectionListener extends BotListener { public constructor() { super('promiseRejection', { emitter: 'process', diff --git a/src/listeners/other/uncaughtException.ts b/src/listeners/other/uncaughtException.ts index c976a22..ab1c1b7 100644 --- a/src/listeners/other/uncaughtException.ts +++ b/src/listeners/other/uncaughtException.ts @@ -1,6 +1,6 @@ -import { BushListener, formatError, generateErrorEmbed } from '#lib'; +import { BotListener, formatError, generateErrorEmbed } from '#lib'; -export default class UncaughtExceptionListener extends BushListener { +export default class UncaughtExceptionListener extends BotListener { public constructor() { super('uncaughtException', { emitter: 'process', diff --git a/src/listeners/other/warning.ts b/src/listeners/other/warning.ts index 5cf9764..7fdcd46 100644 --- a/src/listeners/other/warning.ts +++ b/src/listeners/other/warning.ts @@ -1,6 +1,6 @@ -import { BushListener, colors, formatError, generateErrorEmbed } from '#lib'; +import { BotListener, colors, formatError, generateErrorEmbed } from '#lib'; -export default class WarningListener extends BushListener { +export default class WarningListener extends BotListener { public constructor() { super('warning', { emitter: 'process', diff --git a/src/listeners/rest/rateLimit.ts b/src/listeners/rest/rateLimit.ts index 9071416..46e5471 100644 --- a/src/listeners/rest/rateLimit.ts +++ b/src/listeners/rest/rateLimit.ts @@ -1,12 +1,11 @@ -import { BushListener } from '#lib'; +import { BotListener } from '#lib'; import type { RestEvents } from '@discordjs/rest'; -export default class RateLimitedListener extends BushListener { +export default class RateLimitedListener extends BotListener { public constructor() { super('rateLimited', { emitter: 'rest', - event: 'rateLimited', - category: 'rest' + event: 'rateLimited' }); } diff --git a/src/listeners/track-manual-punishments/modlogSyncBan.ts b/src/listeners/track-manual-punishments/modlogSyncBan.ts index 3c0ff34..5ecb8f3 100644 --- a/src/listeners/track-manual-punishments/modlogSyncBan.ts +++ b/src/listeners/track-manual-punishments/modlogSyncBan.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BushClientEvents } from '#lib'; +import { BotListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BotClientEvents } from '#lib'; import { AuditLogEvent, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ModlogSyncBanListener extends BushListener { +export default class ModlogSyncBanListener extends BotListener { public constructor() { super('modlogSyncBan', { emitter: 'client', - event: 'guildBanAdd', - category: 'guild' + event: 'guildBanAdd' }); } - public async exec(...[ban]: BushClientEvents['guildBanAdd']) { + public async exec(...[ban]: BotClientEvents['guildBanAdd']) { if (!(await ban.guild.hasFeature('logManualPunishments'))) return; if (!ban.guild.members.me) return; // bot was banned if (!ban.guild.members.me.permissions.has(PermissionFlagsBits.ViewAuditLog)) { diff --git a/src/listeners/track-manual-punishments/modlogSyncKick.ts b/src/listeners/track-manual-punishments/modlogSyncKick.ts index 7f7e9bc..fc3fab9 100644 --- a/src/listeners/track-manual-punishments/modlogSyncKick.ts +++ b/src/listeners/track-manual-punishments/modlogSyncKick.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BushClientEvents } from '#lib'; +import { BotListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BotClientEvents } from '#lib'; import { AuditLogEvent, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ModlogSyncKickListener extends BushListener { +export default class ModlogSyncKickListener extends BotListener { public constructor() { super('modlogSyncKick', { emitter: 'client', - event: 'guildMemberRemove', - category: 'guild' + event: 'guildMemberRemove' }); } - public async exec(...[member]: BushClientEvents['guildMemberRemove']) { + public async exec(...[member]: BotClientEvents['guildMemberRemove']) { if (!(await member.guild.hasFeature('logManualPunishments'))) return; if (!member.guild.members.me) return; // bot was removed from guild if (!member.guild.members.me.permissions.has(PermissionFlagsBits.ViewAuditLog)) { diff --git a/src/listeners/track-manual-punishments/modlogSyncTimeout.ts b/src/listeners/track-manual-punishments/modlogSyncTimeout.ts index 50d1331..6248654 100644 --- a/src/listeners/track-manual-punishments/modlogSyncTimeout.ts +++ b/src/listeners/track-manual-punishments/modlogSyncTimeout.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BushClientEvents } from '#lib'; +import { BotListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BotClientEvents } from '#lib'; import { AuditLogEvent, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ModlogSyncTimeoutListener extends BushListener { +export default class ModlogSyncTimeoutListener extends BotListener { public constructor() { super('modlogSyncTimeout', { emitter: 'client', - event: 'guildMemberUpdate', - category: 'guild' + event: 'guildMemberUpdate' }); } - public async exec(...[_oldMember, newMember]: BushClientEvents['guildMemberUpdate']) { + public async exec(...[_oldMember, newMember]: BotClientEvents['guildMemberUpdate']) { if (!(await newMember.guild.hasFeature('logManualPunishments'))) return; if (!newMember.guild.members.me!.permissions.has(PermissionFlagsBits.ViewAuditLog)) { return newMember.guild.error( diff --git a/src/listeners/track-manual-punishments/modlogSyncUnban.ts b/src/listeners/track-manual-punishments/modlogSyncUnban.ts index 8dfd39b..96de018 100644 --- a/src/listeners/track-manual-punishments/modlogSyncUnban.ts +++ b/src/listeners/track-manual-punishments/modlogSyncUnban.ts @@ -1,16 +1,15 @@ -import { BushListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BushClientEvents } from '#lib'; +import { BotListener, colors, humanizeDuration, Moderation, ModLogType, sleep, Time, type BotClientEvents } from '#lib'; import { AuditLogEvent, EmbedBuilder, PermissionFlagsBits } from 'discord.js'; -export default class ModlogSyncUnbanListener extends BushListener { +export default class ModlogSyncUnbanListener extends BotListener { public constructor() { super('modlogSyncUnban', { emitter: 'client', - event: 'guildBanRemove', - category: 'guild' + event: 'guildBanRemove' }); } - public async exec(...[ban]: BushClientEvents['guildBanRemove']) { + public async exec(...[ban]: BotClientEvents['guildBanRemove']) { if (!(await ban.guild.hasFeature('logManualPunishments'))) return; if (!ban.guild.members.me!.permissions.has(PermissionFlagsBits.ViewAuditLog)) { return ban.guild.error( diff --git a/src/listeners/ws/INTERACTION_CREATE.ts b/src/listeners/ws/INTERACTION_CREATE.ts index 67fe56b..250d8b5 100644 --- a/src/listeners/ws/INTERACTION_CREATE.ts +++ b/src/listeners/ws/INTERACTION_CREATE.ts @@ -1,4 +1,4 @@ -import { BushListener, capitalize, colors, emojis, Moderation, PunishmentTypePresent } from '#lib'; +import { BotListener, capitalize, colors, emojis, Moderation, PunishmentTypePresent } from '#lib'; import assert from 'assert/strict'; import { ActionRowBuilder, @@ -21,12 +21,11 @@ import { type APIModalInteractionResponse } from 'discord.js'; -export default class WsInteractionCreateListener extends BushListener { +export default class WsInteractionCreateListener extends BotListener { public constructor() { super('wsInteractionCreate', { emitter: 'ws', - event: GatewayDispatchEvents.InteractionCreate, - category: 'ws' + event: GatewayDispatchEvents.InteractionCreate }); } diff --git a/src/tasks/cache/cpuUsage.ts b/src/tasks/cache/cpuUsage.ts index 61e7a54..3cd52ba 100644 --- a/src/tasks/cache/cpuUsage.ts +++ b/src/tasks/cache/cpuUsage.ts @@ -1,7 +1,7 @@ -import { BushTask, Time } from '#lib'; +import { BotTask, Time } from '#lib'; import os from 'node:os'; -export default class CpuUsageTask extends BushTask { +export default class CpuUsageTask extends BotTask { public constructor() { super('cpuUsage', { delay: Time.Minute, diff --git a/src/tasks/cache/updateCache.ts b/src/tasks/cache/updateCache.ts index 190e2a4..8b42ad3 100644 --- a/src/tasks/cache/updateCache.ts +++ b/src/tasks/cache/updateCache.ts @@ -1,6 +1,6 @@ -import { BushTask, Time, updateEveryCache } from '#lib'; +import { BotTask, Time, updateEveryCache } from '#lib'; -export default class UpdateCacheTask extends BushTask { +export default class UpdateCacheTask extends BotTask { public constructor() { super('updateCache', { delay: 5 * Time.Minute, diff --git a/src/tasks/cache/updateHighlightCache.ts b/src/tasks/cache/updateHighlightCache.ts index 4ab5544..90139a6 100644 --- a/src/tasks/cache/updateHighlightCache.ts +++ b/src/tasks/cache/updateHighlightCache.ts @@ -1,7 +1,6 @@ -import { BushTask } from '../../../lib/extensions/discord-akairo/BushTask.js'; -import { Time } from '../../../lib/utils/BushConstants.js'; +import { BotTask, Time } from '#lib'; -export default class UpdateHighlightCacheTask extends BushTask { +export default class UpdateHighlightCacheTask extends BotTask { public constructor() { super('updateHighlightCache', { delay: 5 * Time.Minute, diff --git a/src/tasks/cache/updateNeuItemCache.ts b/src/tasks/cache/updateNeuItemCache.ts index 14c107b..5c88377 100644 --- a/src/tasks/cache/updateNeuItemCache.ts +++ b/src/tasks/cache/updateNeuItemCache.ts @@ -1,6 +1,6 @@ -import { BushTask, Time } from '#lib'; +import { BotTask, Time } from '#lib'; -export default class UpdateNeuItemCache extends BushTask { +export default class UpdateNeuItemCache extends BotTask { public constructor() { super('updateNeuItemCache', { delay: 1 * Time.Hour, diff --git a/src/tasks/cache/updatePriceItemCache.ts b/src/tasks/cache/updatePriceItemCache.ts index bafbfaf..04ae19a 100644 --- a/src/tasks/cache/updatePriceItemCache.ts +++ b/src/tasks/cache/updatePriceItemCache.ts @@ -1,7 +1,7 @@ -import { BushTask, Time } from '#lib'; +import { BotTask, Time } from '#lib'; import PriceCommand, { AuctionAverages, Bazaar, LowestBIN } from '../../commands/utilities/price.js'; -export default class UpdatePriceItemCache extends BushTask { +export default class UpdatePriceItemCache extends BotTask { public constructor() { super('updatePriceItemCache', { delay: 10 * Time.Minute, diff --git a/src/tasks/feature/handleReminders.ts b/src/tasks/feature/handleReminders.ts index a9f5658..afe6dd6 100644 --- a/src/tasks/feature/handleReminders.ts +++ b/src/tasks/feature/handleReminders.ts @@ -1,7 +1,7 @@ -import { BushTask, dateDelta, format, Reminder, Time } from '#lib'; +import { BotTask, dateDelta, format, Reminder, Time } from '#lib'; import { Op } from 'sequelize'; -export default class HandlerRemindersTask extends BushTask { +export default class HandlerRemindersTask extends BotTask { public constructor() { super('handlerReminders', { delay: 30 * Time.Second, diff --git a/src/tasks/feature/removeExpiredPunishements.ts b/src/tasks/feature/removeExpiredPunishements.ts index 30b8eba..eac325a 100644 --- a/src/tasks/feature/removeExpiredPunishements.ts +++ b/src/tasks/feature/removeExpiredPunishements.ts @@ -1,8 +1,8 @@ -import { ActivePunishment, ActivePunishmentType, BushTask, Time } from '#lib'; +import { ActivePunishment, ActivePunishmentType, BotTask, Time } from '#lib'; import assert from 'assert/strict'; import { Op } from 'sequelize'; -export default class RemoveExpiredPunishmentsTask extends BushTask { +export default class RemoveExpiredPunishmentsTask extends BotTask { public constructor() { super('removeExpiredPunishments', { delay: 15 * Time.Second, @@ -37,7 +37,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { switch (entry.type) { case ActivePunishmentType.BAN: { assert(user); - const result = await guild.bushUnban({ user: user, reason: 'Punishment expired' }); + const result = await guild.customUnban({ user: user, reason: 'Punishment expired' }); if (['success', 'user not banned', 'cannot resolve user'].includes(result)) await entry.destroy(); else throw new Error(result); void this.client.logger.verbose(`removeExpiredPunishments`, `Unbanned ${entry.user}.`); @@ -48,7 +48,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { await entry.destroy(); // channel overrides are removed when the member leaves the guild return; } - const result = await member.bushUnblock({ reason: 'Punishment expired', channel: entry.extraInfo }); + const result = await member.customUnblock({ reason: 'Punishment expired', channel: entry.extraInfo }); if (['success', 'user not blocked'].includes(result)) await entry.destroy(); else throw new Error(result); void this.client.logger.verbose(`removeExpiredPunishments`, `Unblocked ${entry.user}.`); @@ -56,7 +56,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { } case ActivePunishmentType.MUTE: { if (!member) return; - const result = await member.bushUnmute({ reason: 'Punishment expired' }); + const result = await member.customUnmute({ reason: 'Punishment expired' }); if (['success', 'failed to dm'].includes(result)) await entry.destroy(); else throw new Error(result); void this.client.logger.verbose(`removeExpiredPunishments`, `Unmuted ${entry.user}.`); @@ -66,7 +66,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { if (!member) return; const role = guild?.roles?.cache?.get(entry.extraInfo); if (!role) throw new Error(`Cannot unmute ${member.user.tag} because I cannot find the mute role.`); - const result = await member.bushRemoveRole({ + const result = await member.customRemoveRole({ reason: 'Punishment expired', role: role, addToModlog: true diff --git a/src/tasks/feature/updateStats.ts b/src/tasks/feature/updateStats.ts index 77b7c30..ded7aa1 100644 --- a/src/tasks/feature/updateStats.ts +++ b/src/tasks/feature/updateStats.ts @@ -1,6 +1,6 @@ -import { BushTask, Stat, Time } from '#lib'; +import { BotTask, Stat, Time } from '#lib'; -export default class UpdateStatsTask extends BushTask { +export default class UpdateStatsTask extends BotTask { public constructor() { super('updateStats', { delay: 10 * Time.Minute, diff --git a/src/tasks/stats/guildCount.ts b/src/tasks/stats/guildCount.ts index f52dc95..177c959 100644 --- a/src/tasks/stats/guildCount.ts +++ b/src/tasks/stats/guildCount.ts @@ -1,7 +1,6 @@ -import { BushTask, Time } from '#lib'; -import { GuildCount } from '../../../lib/models/shared/GuildCount.js'; +import { BotTask, GuildCount, Time } from '#lib'; -export default class GuildCountTask extends BushTask { +export default class GuildCountTask extends BotTask { public constructor() { super('guildCount', { delay: 15 * Time.Minute, diff --git a/src/tasks/stats/memberCount.ts b/src/tasks/stats/memberCount.ts index 9c31c5b..7cafde5 100644 --- a/src/tasks/stats/memberCount.ts +++ b/src/tasks/stats/memberCount.ts @@ -1,7 +1,7 @@ -import { BushTask, MemberCount, Time } from '#lib'; +import { BotTask, MemberCount, Time } from '#lib'; import assert from 'assert/strict'; -export default class MemberCountTask extends BushTask { +export default class MemberCountTask extends BotTask { public constructor() { super('memberCount', { delay: Time.Minute, diff --git a/yarn.lock b/yarn.lock index 430a36b..13f66b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1013,65 +1013,6 @@ __metadata: languageName: node linkType: hard -"bush-bot@workspace:.": - version: 0.0.0-use.local - resolution: "bush-bot@workspace:." - dependencies: - "@discordjs/builders": ^1.1.0 - "@discordjs/rest": ^1.0.1 - "@ironm00n/nbt-ts": ^1.4.0 - "@napi-rs/canvas": ^0.1.29 - "@notenoughupdates/discord.js-minesweeper": ^1.0.10 - "@notenoughupdates/events-intercept": ^3.0.1 - "@notenoughupdates/humanize-duration": ^4.0.1 - "@notenoughupdates/simplify-number": ^1.0.1 - "@notenoughupdates/wolfram-alpha-api": ^1.0.2 - "@sapphire/snowflake": ^3.2.2 - "@sentry/integrations": ^7.11.1 - "@sentry/node": ^7.11.1 - "@sentry/tracing": ^7.11.1 - "@sentry/types": ^7.11.1 - "@types/eslint": ^8.4.6 - "@types/express": ^4.17.13 - "@types/lodash": ^4.14.184 - "@types/node": ^18.7.13 - "@types/numeral": ^2.0.2 - "@types/pg": ^8.6.5 - "@types/prettier": ^2.7.0 - "@types/rimraf": ^3.0.2 - "@types/tinycolor2": ^1.4.3 - "@types/validator": ^13.7.5 - "@typescript-eslint/eslint-plugin": ^5.35.1 - "@typescript-eslint/parser": ^5.35.1 - chalk: ^5.0.1 - deep-lock: ^1.0.0 - discord-akairo: "npm:@notenoughupdates/discord-akairo@dev" - discord-api-types: 0.37.1 - discord.js: "npm:@notenoughupdates/discord.js@forum" - eslint: ^8.23.0 - eslint-config-prettier: ^8.5.0 - eslint-plugin-deprecation: ^1.3.2 - fuse.js: ^6.6.2 - gif-to-apng: ^0.1.2 - googleapis: ^107.0.0 - lodash: ^4.17.21 - mathjs: ^11.1.0 - nanoid: ^4.0.0 - numeral: ^2.0.6 - pg: ^8.8.0 - pg-hstore: ^2.3.4 - prettier: ^2.7.1 - pretty-bytes: ^6.0.0 - rimraf: ^3.0.2 - sequelize: 6.21.4 - tinycolor2: ^1.4.2 - ts-essentials: ^9.3.0 - typescript: ^4.8.2 - vitest: ^0.22.1 - vm2: ^3.9.10 - languageName: unknown - linkType: soft - "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -3906,6 +3847,65 @@ __metadata: languageName: node linkType: hard +"tanzanite@workspace:.": + version: 0.0.0-use.local + resolution: "tanzanite@workspace:." + dependencies: + "@discordjs/builders": ^1.1.0 + "@discordjs/rest": ^1.0.1 + "@ironm00n/nbt-ts": ^1.4.0 + "@napi-rs/canvas": ^0.1.29 + "@notenoughupdates/discord.js-minesweeper": ^1.0.10 + "@notenoughupdates/events-intercept": ^3.0.1 + "@notenoughupdates/humanize-duration": ^4.0.1 + "@notenoughupdates/simplify-number": ^1.0.1 + "@notenoughupdates/wolfram-alpha-api": ^1.0.2 + "@sapphire/snowflake": ^3.2.2 + "@sentry/integrations": ^7.11.1 + "@sentry/node": ^7.11.1 + "@sentry/tracing": ^7.11.1 + "@sentry/types": ^7.11.1 + "@types/eslint": ^8.4.6 + "@types/express": ^4.17.13 + "@types/lodash": ^4.14.184 + "@types/node": ^18.7.13 + "@types/numeral": ^2.0.2 + "@types/pg": ^8.6.5 + "@types/prettier": ^2.7.0 + "@types/rimraf": ^3.0.2 + "@types/tinycolor2": ^1.4.3 + "@types/validator": ^13.7.5 + "@typescript-eslint/eslint-plugin": ^5.35.1 + "@typescript-eslint/parser": ^5.35.1 + chalk: ^5.0.1 + deep-lock: ^1.0.0 + discord-akairo: "npm:@notenoughupdates/discord-akairo@dev" + discord-api-types: 0.37.1 + discord.js: "npm:@notenoughupdates/discord.js@forum" + eslint: ^8.23.0 + eslint-config-prettier: ^8.5.0 + eslint-plugin-deprecation: ^1.3.2 + fuse.js: ^6.6.2 + gif-to-apng: ^0.1.2 + googleapis: ^107.0.0 + lodash: ^4.17.21 + mathjs: ^11.1.0 + nanoid: ^4.0.0 + numeral: ^2.0.6 + pg: ^8.8.0 + pg-hstore: ^2.3.4 + prettier: ^2.7.1 + pretty-bytes: ^6.0.0 + rimraf: ^3.0.2 + sequelize: 6.21.4 + tinycolor2: ^1.4.2 + ts-essentials: ^9.3.0 + typescript: ^4.8.2 + vitest: ^0.22.1 + vm2: ^3.9.10 + languageName: unknown + linkType: soft + "tar-stream@npm:^1.5.2": version: 1.6.2 resolution: "tar-stream@npm:1.6.2" -- cgit