Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 13 additions & 45 deletions src/commands/slash/Main/hub/edit.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 });

Expand Down Expand Up @@ -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),
Expand All @@ -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 } },
Expand All @@ -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;
}
Expand Down
45 changes: 43 additions & 2 deletions src/commands/slash/Main/hub/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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' },
],
},
],
},
],
};

Expand All @@ -447,15 +466,15 @@ export default class HubCommand extends BaseCommand {
async autocomplete(interaction: AutocompleteInteraction): Promise<void> {
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();
Expand All @@ -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);
}
Expand All @@ -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: {
Expand Down
75 changes: 75 additions & 0 deletions src/commands/slash/Main/hub/visibility.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
8 changes: 1 addition & 7 deletions src/utils/hub/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<StringSelectMenuBuilder>().addComponents(
new StringSelectMenuBuilder()
.setCustomId(
Expand All @@ -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',
Expand Down