diff --git a/src/commands/slash/Main/hub/edit.ts b/src/commands/slash/Main/hub/edit.ts index 5e2853713..abd5cd228 100644 --- a/src/commands/slash/Main/hub/edit.ts +++ b/src/commands/slash/Main/hub/edit.ts @@ -1,13 +1,12 @@ -import { emojis } from '#main/config/Constants.js'; +import Constants, { emojis } from '#main/config/Constants.js'; import { RegisterInteractionHandler } from '#main/decorators/Interaction.js'; import HubLogManager, { LogConfigTypes as HubConfigTypes } from '#main/managers/HubLogManager.js'; import { setComponentExpiry } from '#utils/ComponentUtils.js'; import { CustomID } from '#utils/CustomID.js'; import db from '#utils/Db.js'; import { InfoEmbed } from '#utils/EmbedUtils.js'; -import { actionsSelect, hubEmbed } from '#utils/hub/edit.js'; +import { hubEditSelects, hubEmbed } from '#utils/hub/edit.js'; import { sendToHub } from '#utils/hub/utils.js'; -import { checkAndFetchImgurUrl } from '#utils/ImageUtils.js'; import { type supportedLocaleCodes, t } from '#utils/Locale.js'; import type { Hub } from '@prisma/client'; import { @@ -28,7 +27,7 @@ export default class HubEdit extends HubCommand { await interaction.reply({ embeds: [await hubEmbed(hubInDb)], - components: [actionsSelect(hubInDb.id, interaction.user.id, locale)], + components: [hubEditSelects(hubInDb.id, interaction.user.id, locale)], }); await this.setComponentExpiry(interaction); @@ -122,9 +121,6 @@ export default class HubEdit extends HubCommand { case 'banner': await this.showModal(interaction, hubInDb.id, action, locale); break; - case 'visibility': - await this.toggleVisibility(interaction, hubInDb, locale); - break; case 'toggle_lock': await this.toggleLock(interaction, hubInDb); break; @@ -164,28 +160,6 @@ export default class HubEdit extends HubCommand { await interaction.showModal(modal); } - private async toggleVisibility( - interaction: MessageComponentInteraction, - hubInDb: Hub, - locale: supportedLocaleCodes, - ) { - const updatedHub = await db.hub.update({ - where: { id: hubInDb?.id }, - data: { private: !hubInDb?.private }, - include: { connections: true }, - }); - - await interaction.reply({ - content: t('hub.manage.visibility.success', locale, { - emoji: updatedHub.private ? '🔒' : '🔓', - visibility: updatedHub.private ? 'private' : 'public', - }), - ephemeral: true, - }); - - await interaction.message.edit({ embeds: [await hubEmbed(updatedHub)] }).catch(() => null); - } - private async toggleLock(interaction: MessageComponentInteraction, hubInDb: Hub) { await interaction.deferReply({ ephemeral: true }); @@ -266,21 +240,15 @@ export default class HubEdit extends HubCommand { hubId: string, locale: supportedLocaleCodes, ) { - const newIcon = interaction.fields.getTextInputValue('icon'); - const iconUrl = await checkAndFetchImgurUrl(newIcon); + const iconUrl = interaction.fields.getTextInputValue('icon'); - if (!iconUrl) { - await interaction.reply({ - content: t('hub.invalidImgurUrl', locale, { emoji: emojis.no }), - ephemeral: true, - }); + const regex = Constants.Regex.ImageURL; + if (!regex.test(iconUrl)) { + await interaction.editReply(t('hub.invalidImgurUrl', locale, { emoji: emojis.no })); return; } - await db.hub.update({ - where: { id: hubId }, - data: { iconUrl }, - }); + await db.hub.update({ where: { id: hubId }, data: { iconUrl } }); await interaction.reply({ content: t('hub.manage.icon.changed', locale), @@ -295,9 +263,9 @@ export default class HubEdit extends HubCommand { ) { await interaction.deferReply({ ephemeral: true }); - const newBanner = interaction.fields.getTextInputValue('banner'); + const bannerUrl = interaction.fields.getTextInputValue('banner'); - if (!newBanner) { + if (!bannerUrl) { await db.hub.update({ where: { id: hubId }, data: { bannerUrl: { unset: true } }, @@ -307,9 +275,9 @@ export default class HubEdit extends HubCommand { return; } - const bannerUrl = await checkAndFetchImgurUrl(newBanner); - - if (!bannerUrl) { + // check if imgur url is a valid jpg, png, jpeg or gif and NOT a gallery or album link + const regex = Constants.Regex.ImageURL; + if (!regex.test(bannerUrl)) { await interaction.editReply(t('hub.invalidImgurUrl', locale, { emoji: emojis.no })); return; } diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts index ebc320a9a..931fd1543 100644 --- a/src/commands/slash/Main/hub/index.ts +++ b/src/commands/slash/Main/hub/index.ts @@ -29,6 +29,7 @@ const logTypeOpt = [ { name: 'Profanity', value: 'profanity' }, { name: 'Join/Leave', value: 'joinLeaves' }, { name: 'Appeals', value: 'appeals' }, + { name: 'Network Alerts', value: 'networkAlerts' }, ]; export default class HubCommand extends BaseCommand { @@ -430,6 +431,24 @@ export default class HubCommand extends BaseCommand { description: '📢 Send an announcement to a hub you moderate.', options: [hubOption], }, + { + type: ApplicationCommandOptionType.Subcommand, + name: 'visibility', + description: '👀 Toggle the visibility of a hub (Public/Private).', + options: [ + hubOption, + { + type: ApplicationCommandOptionType.String, + name: 'visibility', + description: 'The visibility of the hub.', + required: true, + choices: [ + { name: 'Public', value: 'public' }, + { name: 'Private', value: 'private' }, + ], + }, + ], + }, ], }; @@ -447,15 +466,15 @@ export default class HubCommand extends BaseCommand { async autocomplete(interaction: AutocompleteInteraction): Promise { const managerCmds = [ 'edit', + 'visibility', 'settings', - 'invite', 'moderator', 'logging', 'appeal', 'blockwords', 'announce', ]; - const modCmds = ['servers']; + const modCmds = ['servers', 'invite']; const subcommand = interaction.options.getSubcommand(); const subcommandGroup = interaction.options.getSubcommandGroup(); @@ -465,6 +484,11 @@ export default class HubCommand extends BaseCommand { if (subcommand === 'browse' || subcommand === 'join') { hubChoices = await this.getPublicHubs(focusedValue); } + else if (subcommand === 'edit' && subcommandGroup === 'blockwords') { + const choices = await this.getBlockWordsRules(interaction); + await interaction.respond(choices ?? []); + return; + } else if (modCmds.includes(subcommandGroup || subcommand)) { hubChoices = await this.getModeratedHubs(focusedValue, interaction.user.id); } @@ -489,6 +513,23 @@ export default class HubCommand extends BaseCommand { await interaction.respond(choices ?? []); } + private async getBlockWordsRules(interaction: AutocompleteInteraction) { + const focused = interaction.options.getFocused(true); + const hubName = interaction.options.getString('hub'); + + if (focused.name === 'rule') { + if (!hubName) return [{ name: 'Please select a hub first.', value: '' }]; + + const rules = await db.messageBlockList.findMany({ + where: { hub: { name: hubName } }, + select: { id: true, name: true }, + }); + + return rules.map((rule) => ({ name: rule.name, value: rule.name })); + } + return null; + } + private async getPublicHubs(focusedValue: string) { return await db.hub.findMany({ where: { diff --git a/src/commands/slash/Main/hub/visibility.ts b/src/commands/slash/Main/hub/visibility.ts new file mode 100644 index 000000000..8577fdcb5 --- /dev/null +++ b/src/commands/slash/Main/hub/visibility.ts @@ -0,0 +1,75 @@ +import { emojis } from '#main/config/Constants.js'; +import HubLogManager from '#main/managers/HubLogManager.js'; +import db from '#main/utils/Db.js'; +import { isHubManager } from '#main/utils/hub/utils.js'; +import { Hub } from '@prisma/client'; +import { stripIndents } from 'common-tags'; +import { ChatInputCommandInteraction, time } from 'discord.js'; +import HubCommand from './index.js'; +import { InfoEmbed } from '#main/utils/EmbedUtils.js'; +import { wait } from '#main/utils/Utils.js'; + +export default class VisibilityCommnd extends HubCommand { + async execute(interaction: ChatInputCommandInteraction) { + await interaction.deferReply({ ephemeral: true }); + + const hubName = interaction.options.getString('hub', true); + const visibility = interaction.options.getString('visibility', true) as 'public' | 'private'; + const hub = await db.hub.findFirst({ where: { name: hubName } }); + + if (!hub || !isHubManager(interaction.user.id, hub)) { + await this.replyEmbed(interaction, 'hub.notFound_mod', { + t: { emoji: emojis.no }, + ephemeral: true, + }); + return; + } + + if (visibility === 'public') { + await interaction.followUp(`${emojis.offline_anim} Checking requirements...`); + const passedChecks = await this.runPublicRequirementChecks(interaction, hub); + if (!passedChecks) return; + } + + const updatedHub = await db.hub.update({ + where: { id: hub.id }, + data: { private: visibility === 'private' }, + }); + + await this.replyEmbed(interaction, 'hub.manage.visibility.success', { + content: ' ', + ephemeral: true, + edit: true, + t: { + emoji: updatedHub.private ? '🔒' : '🔓', + visibility: updatedHub.private ? 'private' : 'public', + }, + }); + } + + private async runPublicRequirementChecks(interaction: ChatInputCommandInteraction, hub: Hub) { + const logConfig = await HubLogManager.create(hub.id); + const requirements = [ + { name: 'Hub is older than 24 hours', check: hub.createdAt < new Date(Date.now() + 24 * 60 * 60 * 1000) }, + { name: 'Hub has more than 2 moderators', check: hub.moderators.length >= 2 }, + { name: 'Hub has users reports & report log channel setup', check: logConfig.config.reports !== null }, + ]; + + const passed = requirements.every((r) => r.check); + const embed = new InfoEmbed().setTitle('Requirement Summary:').setDescription(stripIndents` + Result: **${passed ? `${emojis.yes} Passed` : `${emojis.no} Failed`}** + + ${requirements.map((r) => `${r.check ? emojis.yes : emojis.no} ${r.name}`).join('\n')} + ${!passed ? `\n-# ${emojis.info} Please fix failed requirements and/or try again later.` : ''} + `); + + await interaction.editReply({ + content: passed ? `Continuing ${time(new Date(Date.now() + 10_000), 'R')}...` : null, + embeds: [embed], + }); + + await wait(8000); + + return passed; + } +} diff --git a/src/utils/hub/edit.ts b/src/utils/hub/edit.ts index cc993f3a0..72e9bb37b 100644 --- a/src/utils/hub/edit.ts +++ b/src/utils/hub/edit.ts @@ -6,7 +6,7 @@ import { connectedList, Hub } from '@prisma/client'; import { stripIndents } from 'common-tags'; import { ActionRowBuilder, EmbedBuilder, StringSelectMenuBuilder } from 'discord.js'; -export const actionsSelect = (hubId: string, userId: string, locale: supportedLocaleCodes = 'en') => +export const hubEditSelects = (hubId: string, userId: string, locale: supportedLocaleCodes = 'en') => new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .setCustomId( @@ -23,12 +23,6 @@ export const actionsSelect = (hubId: string, userId: string, locale: supportedLo description: t('hub.manage.description.selects.description', locale), emoji: '📝', }, - { - label: t('hub.manage.visibility.selects.label', locale), - value: 'visibility', - description: t('hub.manage.visibility.selects.description', locale), - emoji: '🔎', - }, { label: t('hub.manage.icon.selects.label', locale), value: 'icon',