From 8af50e765c8cecc5539ae6a12ccdb23bc284c830 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Fri, 26 Jul 2024 19:54:23 +0530 Subject: [PATCH] refactor(decorators): decorated command methods now utilize `this` class property --- src/commands/context-menu/blacklist.ts | 53 ++++++------- src/commands/context-menu/editMsg.ts | 10 +-- src/commands/context-menu/messageInfo.ts | 15 ++-- src/commands/context-menu/translate.ts | 2 +- src/commands/slash/Information/about.ts | 8 +- src/commands/slash/Information/stats.ts | 2 +- .../slash/Main/connection/customize.ts | 6 +- src/commands/slash/Main/hub/delete.ts | 2 +- src/commands/slash/Main/hub/leave.ts | 2 +- src/commands/slash/Main/hub/manage.ts | 28 +++---- src/commands/slash/Main/hub/servers.ts | 13 ++- src/commands/slash/Staff/unban.ts | 7 +- src/commands/slash/Support/support/report.ts | 2 +- src/index.ts | 10 +-- src/managers/EventManager.ts | 2 +- src/scripts/network/onboarding.ts | 10 +-- src/tasks/pauseIdleConnections.ts | 4 +- src/utils/LoadCommands.ts | 79 ++++++++++++------- src/utils/RandomComponents.ts | 6 +- src/utils/Utils.ts | 6 +- tsconfig.json | 2 +- 21 files changed, 137 insertions(+), 132 deletions(-) diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts index aa316c6f..d7d38e29 100644 --- a/src/commands/context-menu/blacklist.ts +++ b/src/commands/context-menu/blacklist.ts @@ -1,4 +1,7 @@ +import { stripIndents } from 'common-tags'; import { + type ModalSubmitInteraction, + type RESTPostAPIApplicationCommandsJSONBody, ActionRowBuilder, ApplicationCommandType, ButtonBuilder, @@ -7,24 +10,21 @@ import { MessageComponentInteraction, MessageContextMenuCommandInteraction, ModalBuilder, - ModalSubmitInteraction, - RESTPostAPIApplicationCommandsJSONBody, TextInputBuilder, TextInputStyle, time, } from 'discord.js'; -import db from '../../utils/Db.js'; import parse from 'parse-duration'; import BaseCommand from '../../core/BaseCommand.js'; -import { t } from '../../utils/Locale.js'; +import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; +import { deleteConnections } from '../../utils/ConnectedList.js'; import { colors, emojis } from '../../utils/Constants.js'; import { CustomID } from '../../utils/CustomID.js'; -import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; -import { checkIfStaff, getUserLocale, simpleEmbed } from '../../utils/Utils.js'; -import { stripIndents } from 'common-tags'; +import db from '../../utils/Db.js'; import { logBlacklist } from '../../utils/HubLogger/ModLogs.js'; -import { deleteConnections } from '../../utils/ConnectedList.js'; +import { t } from '../../utils/Locale.js'; import Logger from '../../utils/Logger.js'; +import { checkIfStaff, getUserLocale } from '../../utils/Utils.js'; export default class Blacklist extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { @@ -48,14 +48,11 @@ export default class Blacklist extends BaseCommand { const isStaffOrHubMod = checkIfStaff(interaction.user.id) || isHubMod; if (!messageInDb || !isStaffOrHubMod) { - await interaction.reply({ - embeds: [ - simpleEmbed( - t({ phrase: 'errors.messageNotSentOrExpired', locale }, { emoji: emojis.info }), - ), - ], - ephemeral: true, - }); + await this.replyEmbed( + interaction, + t({ phrase: 'errors.messageNotSentOrExpired', locale }, { emoji: emojis.info }), + { ephemeral: true }, + ); return; } @@ -106,15 +103,16 @@ export default class Blacklist extends BaseCommand { } @RegisterInteractionHandler('blacklist') - static override async handleComponents(interaction: MessageComponentInteraction): Promise { + override async handleComponents(interaction: MessageComponentInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); const locale = await getUserLocale(interaction.user.id); if (interaction.user.id !== customId.args[0]) { - await interaction.reply({ - embeds: [simpleEmbed(t({ phrase: 'errors.notYourAction', locale }, { emoji: emojis.no }))], - ephemeral: true, - }); + await this.replyEmbed( + interaction, + t({ phrase: 'errors.notYourAction', locale }, { emoji: emojis.no }), + { ephemeral: true }, + ); return; } @@ -190,14 +188,11 @@ export default class Blacklist extends BaseCommand { const user = await interaction.client.users.fetch(originalMsg.authorId).catch(() => null); if (!user) { - await interaction.reply({ - embeds: [ - simpleEmbed( - `${emojis.neutral} Unable to fetch user. They may have deleted their account?`, - ), - ], - ephemeral: true, - }); + await this.replyEmbed( + interaction, + `${emojis.neutral} Unable to fetch user. They may have deleted their account?`, + { ephemeral: true }, + ); return; } diff --git a/src/commands/context-menu/editMsg.ts b/src/commands/context-menu/editMsg.ts index c00e6c1b..6e607319 100644 --- a/src/commands/context-menu/editMsg.ts +++ b/src/commands/context-menu/editMsg.ts @@ -103,7 +103,7 @@ export default class EditMessage extends BaseCommand { } @RegisterInteractionHandler('editMsg') - static async handleModals(interaction: ModalSubmitInteraction): Promise { + async handleModals(interaction: ModalSubmitInteraction): Promise { await interaction.deferReply({ ephemeral: true }); const customId = CustomID.parseCustomId(interaction.customId); @@ -144,7 +144,7 @@ export default class EditMessage extends BaseCommand { const hubSettings = new HubSettingsBitField(originalMsg.hub.settings); const newMessage = hubSettings.has('HideLinks') ? replaceLinks(userInput) : userInput; const { newEmbed, censoredEmbed, compactMsg, censoredCmpctMsg } = - await EditMessage.fabricateNewMsg(interaction.user, target, newMessage, originalMsg.serverId); + await this.fabricateNewMsg(interaction.user, target, newMessage, originalMsg.serverId); if (hubSettings.has('BlockInvites') && containsInviteLinks(newMessage)) { await interaction.editReply( @@ -201,7 +201,7 @@ export default class EditMessage extends BaseCommand { ); } - static async getImageUrls(target: Message, newMessage: string) { + private async getImageUrls(target: Message, newMessage: string) { // get image from embed // get image from content const oldImageUrl = target.content @@ -211,7 +211,7 @@ export default class EditMessage extends BaseCommand { return { oldImageUrl, newImageUrl }; } - static async buildNewEmbed( + private async buildNewEmbed( user: User, target: Message, newMessage: string, @@ -246,7 +246,7 @@ export default class EditMessage extends BaseCommand { .setFooter({ text: `Server: ${guild?.name}` }); } - static async fabricateNewMsg(user: User, target: Message, newMessage: string, serverId: string) { + private async fabricateNewMsg(user: User, target: Message, newMessage: string, serverId: string) { const { oldImageUrl, newImageUrl } = await this.getImageUrls(target, newMessage); const newEmbed = await this.buildNewEmbed(user, target, newMessage, serverId, { oldImageUrl, diff --git a/src/commands/context-menu/messageInfo.ts b/src/commands/context-menu/messageInfo.ts index 1514b38d..556f3a8a 100644 --- a/src/commands/context-menu/messageInfo.ts +++ b/src/commands/context-menu/messageInfo.ts @@ -73,8 +73,7 @@ export default class MessageInfo extends BaseCommand { .setThumbnail(`https://cdn.discordapp.com/icons/${server?.id}/${server?.icon}.png`) .setColor('Random'); - const components = MessageInfo.buildButtons(target.id, locale); - + const components = this.buildButtons(target.id, locale); const guildConnected = (await getAllConnections())?.find( (c) => c.serverId === originalMsg.serverId && c.hubId === originalMsg.hub?.id, ); @@ -97,7 +96,7 @@ export default class MessageInfo extends BaseCommand { } @RegisterInteractionHandler('msgInfo') - static override async handleComponents(interaction: MessageComponentInteraction) { + override async handleComponents(interaction: MessageComponentInteraction) { // create a variable to store the profile card buffer const customId = CustomID.parseCustomId(interaction.customId); const [messageId] = customId.args; @@ -187,7 +186,7 @@ export default class MessageInfo extends BaseCommand { .setFooter({ text: `ID: ${server.id}` }); // disable the server info button - MessageInfo.greyOutButton(components[0], 1); + this.greyOutButton(components[0], 1); await interaction.update({ embeds: [serverEmbed], components, files: [] }); break; @@ -218,7 +217,7 @@ export default class MessageInfo extends BaseCommand { .setTimestamp(); // disable the user info button - MessageInfo.greyOutButton(components[0], 2); + this.greyOutButton(components[0], 2); await interaction.editReply({ // attach the profile card to the message @@ -262,7 +261,7 @@ export default class MessageInfo extends BaseCommand { ) .setColor('Random'); - MessageInfo.greyOutButton(components[0], 0); + this.greyOutButton(components[0], 0); await interaction.update({ embeds: [embed], components, files: [] }); break; @@ -352,12 +351,12 @@ export default class MessageInfo extends BaseCommand { } // utility methods - static greyOutButton(buttons: ActionRowBuilder, disableElement: number) { + private greyOutButton(buttons: ActionRowBuilder, disableElement: number) { buttons.components.forEach((c) => c.setDisabled(false)); buttons.components[disableElement].setDisabled(true); } - static buildButtons(messageId: string, locale: supportedLocaleCodes = 'en') { + private buildButtons(messageId: string, locale: supportedLocaleCodes = 'en') { return [ new ActionRowBuilder().addComponents( new ButtonBuilder() diff --git a/src/commands/context-menu/translate.ts b/src/commands/context-menu/translate.ts index 0b5e5b01..0f8ad64a 100644 --- a/src/commands/context-menu/translate.ts +++ b/src/commands/context-menu/translate.ts @@ -94,7 +94,7 @@ export default class Translate extends BaseCommand { } @RegisterInteractionHandler('translate') - static override async handleComponents(interaction: ButtonInteraction): Promise { + override async handleComponents(interaction: ButtonInteraction): Promise { const modal = new ModalBuilder() .setCustomId(new CustomID('translate_modal').toString()) .setTitle('Specify Language') diff --git a/src/commands/slash/Information/about.ts b/src/commands/slash/Information/about.ts index 4e2f4bb6..605cca45 100644 --- a/src/commands/slash/Information/about.ts +++ b/src/commands/slash/Information/about.ts @@ -5,13 +5,13 @@ import { ButtonStyle, Client, } from 'discord.js'; -import { badgeEmojis, emojis, LINKS } from '../../../utils/Constants.js'; -import { getCredits, simpleEmbed } from '../../../utils/Utils.js'; +import { badgeEmojis, emojis, LINKS } from '#main/utils/Constants.js'; +import { getCredits, simpleEmbed } from '#main/utils/Utils.js'; import { stripIndents } from 'common-tags'; -import BaseCommand, { CommandBody } from '../../../core/BaseCommand.js'; +import BaseCommand, { CmdData } from '#main/core/BaseCommand.js'; export default class About extends BaseCommand { - public readonly data: CommandBody = { + public readonly data: CmdData = { name: 'about', description: 'Learn more about the InterChat team and project.', }; diff --git a/src/commands/slash/Information/stats.ts b/src/commands/slash/Information/stats.ts index aef60207..1971916b 100644 --- a/src/commands/slash/Information/stats.ts +++ b/src/commands/slash/Information/stats.ts @@ -107,7 +107,7 @@ export default class Stats extends BaseCommand { } @RegisterInteractionHandler('stats') - static override async handleComponents(interaction: ButtonInteraction) { + override async handleComponents(interaction: ButtonInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const allCusterData = await interaction.client.cluster.broadcastEval((client) => diff --git a/src/commands/slash/Main/connection/customize.ts b/src/commands/slash/Main/connection/customize.ts index 4716cf3d..c64ef5f3 100644 --- a/src/commands/slash/Main/connection/customize.ts +++ b/src/commands/slash/Main/connection/customize.ts @@ -75,7 +75,7 @@ export default class Customize extends Connection { } @RegisterInteractionHandler('connectionModal') - static override async handleModals(interaction: ModalSubmitInteraction): Promise { + async handleModals(interaction: ModalSubmitInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); const locale = await getUserLocale(interaction.user.id); if (customId.suffix === 'invite') { @@ -150,7 +150,7 @@ export default class Customize extends Connection { } @RegisterInteractionHandler('connection') - static async handleStringSelects(interaction: StringSelectMenuInteraction) { + async handleStringSelects(interaction: StringSelectMenuInteraction) { if (!interaction.isStringSelectMenu()) return; const customId = CustomID.parseCustomId(interaction.customId); @@ -244,7 +244,7 @@ export default class Customize extends Connection { } @RegisterInteractionHandler('connection', 'change_channel') - static async handleChannelSelects(interaction: ChannelSelectMenuInteraction) { + async handleChannelSelects(interaction: ChannelSelectMenuInteraction) { if (!interaction.isChannelSelectMenu()) return; await interaction.deferUpdate(); diff --git a/src/commands/slash/Main/hub/delete.ts b/src/commands/slash/Main/hub/delete.ts index d1fb33d6..25b04deb 100644 --- a/src/commands/slash/Main/hub/delete.ts +++ b/src/commands/slash/Main/hub/delete.ts @@ -68,7 +68,7 @@ export default class Delete extends Hub { } @RegisterInteractionHandler('hub_delete') - static override async handleComponents(interaction: ButtonInteraction) { + override async handleComponents(interaction: ButtonInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const [userId, hubId] = customId.args; const locale = await getUserLocale(interaction.user.id); diff --git a/src/commands/slash/Main/hub/leave.ts b/src/commands/slash/Main/hub/leave.ts index 2f4b85c2..5b00ce9a 100644 --- a/src/commands/slash/Main/hub/leave.ts +++ b/src/commands/slash/Main/hub/leave.ts @@ -81,7 +81,7 @@ export default class Leave extends Hub { } @RegisterInteractionHandler('hub_leave') - static override async handleComponents(interaction: MessageComponentInteraction): Promise { + override async handleComponents(interaction: MessageComponentInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); const [channelId] = customId.args; diff --git a/src/commands/slash/Main/hub/manage.ts b/src/commands/slash/Main/hub/manage.ts index 544f8bf8..caacab58 100644 --- a/src/commands/slash/Main/hub/manage.ts +++ b/src/commands/slash/Main/hub/manage.ts @@ -97,10 +97,10 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage', 'settingsSelect') - static async handleSettingsSelect(interaction: MessageComponentInteraction) { + async handleSettingsSelect(interaction: MessageComponentInteraction) { if (!interaction.isStringSelectMenu()) return; - const initialData = await Manage.componentChecks(interaction); + const initialData = await this.componentChecks(interaction); if (!initialData) return; const { hubInDb, customId } = initialData; @@ -152,10 +152,10 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage', 'logsSelect') - static async handleLogsSelect(interaction: MessageComponentInteraction) { + async handleLogsSelect(interaction: MessageComponentInteraction) { if (!interaction.isStringSelectMenu()) return; - const initialData = await Manage.componentChecks(interaction); + const initialData = await this.componentChecks(interaction); if (!initialData) return; const { hubInDb, customId, locale } = initialData; @@ -254,10 +254,10 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage', 'actions') - static async handleActionsSelect(interaction: MessageComponentInteraction) { + async handleActionsSelect(interaction: MessageComponentInteraction) { if (!interaction.isStringSelectMenu()) return; - const initialData = await Manage.componentChecks(interaction); + const initialData = await this.componentChecks(interaction); if (!initialData) return; const { hubInDb, customId, locale } = initialData; @@ -387,10 +387,10 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage', 'logsChSel') - static async handleChannelSelects(interaction: MessageComponentInteraction) { + async handleChannelSelects(interaction: MessageComponentInteraction) { if (!interaction.isChannelSelectMenu()) return; - const initialData = await Manage.componentChecks(interaction); + const initialData = await this.componentChecks(interaction); if (!initialData) return; const { hubInDb, customId, locale } = initialData; @@ -421,10 +421,10 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage') - static async handleRoleSelects(interaction: MessageComponentInteraction) { + async handleRoleSelects(interaction: MessageComponentInteraction) { if (!interaction.isRoleSelectMenu()) return; - const initialData = await Manage.componentChecks(interaction); + const initialData = await this.componentChecks(interaction); if (!initialData) return; const { hubInDb, customId, locale } = initialData; @@ -469,7 +469,7 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage_modal') - static override async handleModals(interaction: ModalSubmitInteraction) { + async handleModals(interaction: ModalSubmitInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const [hubId] = customId.args; const locale = await getUserLocale(interaction.user.id); @@ -590,10 +590,10 @@ export default class Manage extends Hub { } @RegisterInteractionHandler('hub_manage') - static async handleButtons(interaction: MessageComponentInteraction) { + async handleButtons(interaction: MessageComponentInteraction) { if (!interaction.isButton()) return; - const initialData = await Manage.componentChecks(interaction); + const initialData = await this.componentChecks(interaction); if (!initialData) return; const { hubInDb, customId, locale } = initialData; @@ -696,7 +696,7 @@ export default class Manage extends Hub { } } - static async componentChecks(interaction: MessageComponentInteraction) { + private async componentChecks(interaction: MessageComponentInteraction) { const customId = CustomID.parseCustomId(interaction.customId); const locale = await getUserLocale(interaction.user.id); diff --git a/src/commands/slash/Main/hub/servers.ts b/src/commands/slash/Main/hub/servers.ts index 1851c055..ceb726eb 100644 --- a/src/commands/slash/Main/hub/servers.ts +++ b/src/commands/slash/Main/hub/servers.ts @@ -1,10 +1,10 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { colors, emojis } from '#main/utils/Constants.js'; +import db from '#main/utils/Db.js'; +import { t } from '#main/utils/Locale.js'; +import { paginate } from '#main/utils/Pagination.js'; +import { getUserLocale, resolveEval, simpleEmbed } from '#main/utils/Utils.js'; +import { type ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; import Hub from './index.js'; -import { colors, emojis } from '../../../../utils/Constants.js'; -import { paginate } from '../../../../utils/Pagination.js'; -import db from '../../../../utils/Db.js'; -import { getUserLocale, resolveEval, simpleEmbed } from '../../../../utils/Utils.js'; -import { t } from '../../../../utils/Locale.js'; export default class Servers extends Hub { async execute(interaction: ChatInputCommandInteraction): Promise { @@ -112,7 +112,6 @@ export default class Servers extends Hub { const value = t( { phrase: 'hub.servers.connectionInfo', locale }, { - total: `${hub.connections.length}`, channelName: `${evalRes?.channelName}`, channelId: connection.channelId, joinedAt: ``, diff --git a/src/commands/slash/Staff/unban.ts b/src/commands/slash/Staff/unban.ts index 30102ac1..74fbe4cd 100644 --- a/src/commands/slash/Staff/unban.ts +++ b/src/commands/slash/Staff/unban.ts @@ -1,16 +1,15 @@ -import BaseCommand from '#main/core/BaseCommand.js'; +import BaseCommand, { type CmdData } from '#main/core/BaseCommand.js'; import { emojis } from '#main/utils/Constants.js'; import db from '#main/utils/Db.js'; import { getDbUser, simpleEmbed } from '#main/utils/Utils.js'; import { + type ChatInputCommandInteraction, ApplicationCommandOptionType, - ChatInputCommandInteraction, - RESTPostAPIChatInputApplicationCommandsJSONBody, } from 'discord.js'; export default class Unban extends BaseCommand { readonly staffOnly = true; - data: RESTPostAPIChatInputApplicationCommandsJSONBody = { + data: CmdData = { name: 'unban', description: '🔨 Unban a user from using the bot (Staff Only)', options: [ diff --git a/src/commands/slash/Support/support/report.ts b/src/commands/slash/Support/support/report.ts index ed486dfa..e80281f7 100644 --- a/src/commands/slash/Support/support/report.ts +++ b/src/commands/slash/Support/support/report.ts @@ -92,7 +92,7 @@ export default class Report extends Support { } @RegisterInteractionHandler('report') - static override async handleComponents(interaction: MessageComponentInteraction) { + override async handleComponents(interaction: MessageComponentInteraction) { const locale = await getUserLocale(interaction.user.id); if (interaction.isStringSelectMenu()) { diff --git a/src/index.ts b/src/index.ts index 18d1166a..25846cd3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,10 @@ import './instrument.js'; -import Logger from './utils/Logger.js'; import SuperClient from './core/Client.js'; -import EventManager from './managers/EventManager.js'; import { eventMethods } from './decorators/GatewayEvent.js'; -import { RandomComponents } from './utils/RandomComponents.js'; +import Logger from './utils/Logger.js'; const client = new SuperClient(); -// dum classes -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const _randomComponentHandlers = RandomComponents; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const _eventManager = EventManager; - // decorator events eventMethods.forEach((methods, eventName) => { methods.forEach((method) => client.on(eventName, method)); diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 0a552080..ff340764 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -49,7 +49,7 @@ import { VoiceChannel, } from 'discord.js'; -export default abstract class EventManager { +export default class EventManager { @GatewayEvent('ready') static onReady(client: Client) { Logger.info(`Logged in as ${client.user?.tag}!`); diff --git a/src/scripts/network/onboarding.ts b/src/scripts/network/onboarding.ts index 37794c4d..691b62fe 100644 --- a/src/scripts/network/onboarding.ts +++ b/src/scripts/network/onboarding.ts @@ -1,15 +1,15 @@ import { LINKS, colors } from '#main/utils/Constants.js'; -import { supportedLocaleCodes, t } from '#main/utils/Locale.js'; +import { type supportedLocaleCodes, t } from '#main/utils/Locale.js'; import { getReplyMethod, getUserLocale } from '#main/utils/Utils.js'; import { + type ButtonInteraction, + type RepliableInteraction, ActionRowBuilder, ButtonBuilder, - ButtonInteraction, ButtonStyle, Collection, ComponentType, EmbedBuilder, - RepliableInteraction, } from 'discord.js'; const onboardingInProgress = new Collection(); @@ -84,9 +84,7 @@ export const showOnboarding = async ( const embed = new EmbedBuilder() .setTitle(t({ phrase: `${embedPhrase}.title`, locale }, { hubName })) - .setDescription( - t({ phrase: `${embedPhrase}.description`, locale }, { hubName, docs_link: LINKS.DOCS }), - ) + .setDescription(t({ phrase: `${embedPhrase}.description`, locale }, { docs_link: LINKS.DOCS })) .setColor(colors.interchatBlue) .setFooter({ text: t({ phrase: `${embedPhrase}.footer`, locale }, { version: interaction.client.version }), diff --git a/src/tasks/pauseIdleConnections.ts b/src/tasks/pauseIdleConnections.ts index 9b95893e..12c90ad0 100644 --- a/src/tasks/pauseIdleConnections.ts +++ b/src/tasks/pauseIdleConnections.ts @@ -9,7 +9,7 @@ import { emojis } from '../utils/Constants.js'; import 'dotenv/config'; export default async (manager: ClusterManager) => { - const idk = (await getAllConnections()); + const idk = await getAllConnections(); const connections = idk?.filter( ({ lastActive }) => lastActive && lastActive <= new Date(Date.now() - (24 * 60 * 60 * 1000)), ); @@ -42,7 +42,7 @@ export default async (manager: ClusterManager) => { const embed = simpleEmbed( stripIndents` ### ${emojis.timeout} Paused Due to Inactivity - Connection to this hub has been stopped because no messages were sent for past day. **Click the button** below to resume chatting (or alternatively, \`/connection\`). + Connection to this hub has been stopped to save resources because no messages were sent for past day. **Click the button** below to resume chatting (or alternatively, \`/connection\`). `, ).toJSON(); diff --git a/src/utils/LoadCommands.ts b/src/utils/LoadCommands.ts index 41f71de8..0d51a250 100644 --- a/src/utils/LoadCommands.ts +++ b/src/utils/LoadCommands.ts @@ -1,14 +1,15 @@ -import { InteractionFunction } from '#main/decorators/Interaction.js'; +import 'reflect-metadata'; +import BaseCommand from '#main/core/BaseCommand.js'; +import { type InteractionFunction } from '#main/decorators/Interaction.js'; import Logger from '#main/utils/Logger.js'; import { type ChatInputCommandInteraction, - Collection, type ContextMenuCommandInteraction, + Collection, } from 'discord.js'; import { readdirSync, statSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; -import BaseCommand from '#main/core/BaseCommand.js'; export type CmdInteraction = ChatInputCommandInteraction | ContextMenuCommandInteraction; export const commandsMap = new Collection(); @@ -16,12 +17,46 @@ export const interactionsMap = new Collection { + Logger.debug(`Adding interactions for command: ${command.data.name}`); + + const metadata = Reflect.getMetadata('command:interactions', command.constructor) as + | { customId: string; methodName: string }[] + | undefined; + if (!metadata?.length) return; + + metadata.forEach(({ customId, methodName }) => { + Logger.debug(`Adding interaction: ${customId} with method ${methodName}`); + + // @ts-expect-error The names of child class properties can be custom + const method = command[methodName]; + // console.log(method, methodName, customId); + + interactionsMap.set(customId, method.bind(command)); + }); + + Logger.debug(`Finished adding interactions for command: ${command.data.name}`); +}; + +const loadCommand = (command: BaseCommand) => { + // If the command extends BaseCommand (i.e., is not a subcommand), add it to the commands map + Logger.debug(`Adding command: ${command.data.name}`); + commandsMap.set(command.data.name, command); +}; + +const loadSubCommand = (command: BaseCommand, opts: { fileName: string }) => { + const parentCommand = Object.getPrototypeOf(command.constructor); + parentCommand.subcommands.set(opts.fileName.replace('.js', ''), command); +}; /** * Recursively loads all command files from the given directory and its subdirectories. * @param commandDir The directory to load command files from. */ -const loadCommandFiles = async (commandDir = join(__dirname, '..', 'commands')) => { - Logger.debug(`Called loadCommandFiles with directory: ${commandDir}`); // Log function call +const loadCommandFiles = async (opts?: { commandDir?: string; loadInteractions?: boolean }) => { + const commandDir = opts?.commandDir ?? join(__dirname, '..', 'commands'); + const loadInteractions = Boolean(opts?.loadInteractions); + + Logger.debug(`Called loadCommandFiles with directory: ${commandDir}`); const importPrefix = process.platform === 'win32' ? 'file://' : ''; try { @@ -32,38 +67,24 @@ const loadCommandFiles = async (commandDir = join(__dirname, '..', 'commands')) // If the item is a directory, recursively read its files if (stats.isDirectory()) { - Logger.debug(`Entering directory: ${filePath}`); // Log directory entry - await loadCommandFiles(filePath); + Logger.debug(`Entering directory: ${filePath}`); + await loadCommandFiles({ commandDir: filePath }); } - else if (file.endsWith('.js') && file !== 'BaseCommand.js') { - Logger.debug(`Importing command file: ${filePath}`); // Log file import + else if (file.endsWith('.js')) { + Logger.debug(`Importing command file: ${filePath}`); const imported = await import(importPrefix + filePath); const command: BaseCommand = new imported.default(); + // load related button/select/modal etc. interaction listeners + if (loadInteractions) loadCommandInteractions(command); - // If the command extends BaseCommand (i.e., is not a subcommand), add it to the commands map - if (Object.getPrototypeOf(command.constructor) === BaseCommand) { - Logger.debug(`Adding command: ${command.data.name}`); // Log command addition - commandsMap.set(command.data.name, command); - } - else { - const subcommandFile = join(commandDir, '.', 'index.js'); - try { - if (statSync(subcommandFile).isFile()) { - const parentCommand = Object.getPrototypeOf(command.constructor); - parentCommand.subcommands.set(file.replace('.js', ''), command); - } - } - catch (err) { - // Handle error if subcommandFile does not exist or is not a file - if (err.code !== 'ENOENT') throw err; - } - } + if (Object.getPrototypeOf(command.constructor) === BaseCommand) loadCommand(command); + else loadSubCommand(command, { fileName: file }); } } - Logger.debug(`Finished loading commands from: ${commandDir}`); // Log completion + Logger.debug(`Finished loading commands from: ${commandDir}`); } catch (error) { - Logger.error(`Error loading command files from ${commandDir}:`, error); // Log any errors + Logger.error(`Error loading command files from ${commandDir}:`, error); } }; diff --git a/src/utils/RandomComponents.ts b/src/utils/RandomComponents.ts index a958b501..ebcd6309 100644 --- a/src/utils/RandomComponents.ts +++ b/src/utils/RandomComponents.ts @@ -21,10 +21,10 @@ import { t } from './Locale.js'; import { getEmojiId, getUserLocale, simpleEmbed, sortReactions } from './Utils.js'; // skipcq: JS-0327 -export abstract class RandomComponents { +export class RandomComponents { /** Listens for a reaction button or select menu interaction and updates the reactions accordingly. */ @RegisterInteractionHandler('reaction_') - static async listenForReactionButton( + async listenForReactionButton( interaction: ButtonInteraction | AnySelectMenuInteraction, ): Promise { await interaction.deferUpdate(); @@ -197,7 +197,7 @@ export abstract class RandomComponents { } @RegisterInteractionHandler('inactiveConnect', 'toggle') - static async inactiveConnect(interaction: ButtonInteraction): Promise { + async inactiveConnect(interaction: ButtonInteraction): Promise { const customId = CustomID.parseCustomId(interaction.customId); const [channelId] = customId.args; diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 288d14f3..679228f2 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -20,6 +20,7 @@ import { MediaChannel, Message, MessageActionRowComponent, + MessageComponentInteraction, NewsChannel, RepliableInteraction, Snowflake, @@ -308,8 +309,9 @@ const genCommandErrMsg = (locale: supportedLocaleCodes, errorId: string) => { errorId, emoji: emojis.no, support_invite: LINKS.SUPPORT_INVITE }, ); -export const getReplyMethod = (interaction: RepliableInteraction | CommandInteraction) => - interaction.replied || interaction.deferred ? 'followUp' : 'reply'; +export const getReplyMethod = ( + interaction: RepliableInteraction | CommandInteraction | MessageComponentInteraction, +) => (interaction.replied || interaction.deferred ? 'followUp' : 'reply'); /** Invoke this method to handle errors that occur during command execution. diff --git a/tsconfig.json b/tsconfig.json index 16412312..e0f8e019 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,7 @@ "forceConsistentCasingInFileNames": true, "paths": { "#main/*": ["src/*"], - "#commands/*": ["./src/commands/*"] + "#commands/*": ["./src/commands/*"] } }, "include": ["src/**/*.ts", "src/**/*.json"],