Discord-InterChat/InterChat

View on GitHub
src/commands/slash/Main/hub/create.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { RegisterInteractionHandler } from '#main/decorators/RegisterInteractionHandler.js';
import { HubValidator } from '#main/modules/HubValidator.js';
import { HubCreationData } from '#main/services/HubService.js';
import { CustomID } from '#main/utils/CustomID.js';
import { handleError } from '#main/utils/Utils.js';
import Constants from '#utils/Constants.js';
import { supportedLocaleCodes, t } from '#utils/Locale.js';
import {
  ActionRowBuilder,
  CacheType,
  ChatInputCommandInteraction,
  EmbedBuilder,
  ModalBuilder,
  ModalSubmitInteraction,
  TextInputBuilder,
  TextInputStyle,
} from 'discord.js';
import HubCommand from './index.js';

export default class Create extends HubCommand {
  readonly cooldown = 10 * 60 * 1000; // 10 mins

  async execute(interaction: ChatInputCommandInteraction<CacheType>) {
    const { userManager } = interaction.client;
    const locale = await userManager.getUserLocale(interaction.user.id);

    if (await this.isOnCooldown(interaction, locale)) return;

    const modal = new ModalBuilder()
      .setTitle(t('hub.create.modal.title', locale))
      .setCustomId(new CustomID('hub_create_modal').toString())
      .addComponents(
        new ActionRowBuilder<TextInputBuilder>().addComponents(
          new TextInputBuilder()
            .setLabel(t('hub.create.modal.name.label', locale))
            .setPlaceholder(t('hub.create.modal.name.placeholder', locale))
            .setMinLength(2)
            .setMaxLength(100)
            .setStyle(TextInputStyle.Short)
            .setCustomId('name'),
        ),
        new ActionRowBuilder<TextInputBuilder>().addComponents(
          new TextInputBuilder()
            .setLabel(t('hub.create.modal.description.label', locale))
            .setPlaceholder(t('hub.create.modal.description.placeholder', locale))
            .setMaxLength(1024)
            .setStyle(TextInputStyle.Paragraph)
            .setCustomId('description'),
        ),
        new ActionRowBuilder<TextInputBuilder>().addComponents(
          new TextInputBuilder()
            .setLabel(t('hub.create.modal.icon.label', locale))
            .setPlaceholder(t('hub.create.modal.icon.placeholder', locale))
            .setMaxLength(300)
            .setStyle(TextInputStyle.Short)
            .setRequired(false)
            .setCustomId('icon'),
        ),
        new ActionRowBuilder<TextInputBuilder>().addComponents(
          new TextInputBuilder()
            .setLabel(t('hub.create.modal.banner.label', locale))
            .setPlaceholder(t('hub.create.modal.banner.placeholder', locale))
            .setMaxLength(300)
            .setStyle(TextInputStyle.Short)
            .setRequired(false)
            .setCustomId('banner'),
        ),
        // new ActionRowBuilder<TextInputBuilder>().addComponents(
        //   new TextInputBuilder()
        //     .setLabel('Language')
        //     .setPlaceholder('Pick a language for this hub.')
        //     .setStyle(TextInputStyle.Short)
        //     .setCustomId('language'),
        // ),
      );

    await interaction.showModal(modal);
  }

  private async isOnCooldown(
    interaction: ChatInputCommandInteraction<CacheType>,
    locale: supportedLocaleCodes,
  ): Promise<boolean> {
    const remainingCooldown = await this.getRemainingCooldown(interaction);
    if (remainingCooldown) {
      await this.sendCooldownError(interaction, remainingCooldown, locale);
      return true;
    }
    return false;
  }

  @RegisterInteractionHandler('hub_create_modal')
  async handleModals(interaction: ModalSubmitInteraction): Promise<void> {
    await interaction.deferReply({ ephemeral: true });

    const { userManager } = interaction.client;
    const locale = await userManager.getUserLocale(interaction.user.id);

    try {
      const hubData = this.extractHubData(interaction);
      await this.processHubCreation(interaction, hubData, locale);
    }
    catch (error) {
      handleError(error, interaction);
    }
  }

  private extractHubData(interaction: ModalSubmitInteraction): HubCreationData {
    return {
      name: interaction.fields.getTextInputValue('name'),
      description: interaction.fields.getTextInputValue('description'),
      iconUrl: interaction.fields.getTextInputValue('icon'),
      bannerUrl: interaction.fields.getTextInputValue('banner'),
      ownerId: interaction.user.id,
    };
  }

  private async processHubCreation(
    interaction: ModalSubmitInteraction,
    hubData: HubCreationData,
    locale: supportedLocaleCodes,
  ): Promise<void> {
    const validator = new HubValidator(locale);
    const existingHubs = await this.hubService.getExistingHubs(hubData.ownerId, hubData.name);

    const validationResult = await validator.validateNewHub(hubData, existingHubs);
    if (!validationResult.isValid) {
      await interaction.followUp({
        content: validationResult.error,
        ephemeral: true,
      });
      return;
    }

    await this.hubService.createHub(hubData);
    await this.handleSuccessfulCreation(interaction, hubData.name, locale);
  }

  private async handleSuccessfulCreation(
    interaction: ModalSubmitInteraction,
    hubName: string,
    locale: supportedLocaleCodes,
  ): Promise<void> {
    this.setCooldowns(interaction);

    const successEmbed = new EmbedBuilder()
      .setColor('Green')
      .setDescription(
        t('hub.create.success', locale, {
          name: hubName,
          support_invite: Constants.Links.SupportInvite,
          docs_link: Constants.Links.Docs,
        }),
      )
      .setTimestamp();

    await interaction.editReply({ embeds: [successEmbed] });
  }

  private setCooldowns(interaction: ModalSubmitInteraction): void {
    interaction.client.commandCooldowns.setCooldown(
      `${interaction.user.id}-hub-create`,
      60 * 60 * 1000, // 1 hour
    );

    const command = HubCommand.subcommands.get('create');
    command?.setUserCooldown(interaction);
  }
}