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
66 changes: 66 additions & 0 deletions src/commands/slash/Main/hub/announce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { emojis } from '#main/config/Constants.js';
import db from '#main/utils/Db.js';
import { InfoEmbed } from '#main/utils/EmbedUtils.js';
import { isHubManager, sendToHub } from '#main/utils/hub/utils.js';
import {
ActionRowBuilder,
ChatInputCommandInteraction,
ModalBuilder,
ModalSubmitInteraction,
TextInputBuilder,
TextInputStyle,
} from 'discord.js';
import HubCommand from './index.js';
import { CustomID } from '#main/utils/CustomID.js';
import { RegisterInteractionHandler } from '#main/decorators/Interaction.js';

export default class AnnounceCommand extends HubCommand {
readonly cooldown = 1 * 60 * 1000;
async execute(interaction: ChatInputCommandInteraction) {
const hubName = interaction.options.getString('hub', true);
const hub = await db.hub.findFirst({ where: { name: hubName } });

if (!hub || !isHubManager(interaction.user.id, hub)) {
await this.replyEmbed(interaction, 'hub.notFound_mod', { ephemeral: true });
return;
}

const isOnCooldown = await this.checkOrSetCooldown(interaction);
if (isOnCooldown) return;

const modal = new ModalBuilder()
.setCustomId(new CustomID('hub_announce', [hub.id]).toString())
.setTitle('Announce something to all connected servers')
.addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
.setCustomId('announcement')
.setLabel('Announcement')
.setPlaceholder('Enter your announcement here')
.setRequired(true)
.setMinLength(5)
.setStyle(TextInputStyle.Paragraph),
),
);

await interaction.showModal(modal);
}

@RegisterInteractionHandler('hub_announce')
async handleAnnounceModal(interaction: ModalSubmitInteraction) {
await interaction.reply(`${emojis.loading} Sending announcement to all connected servers...`);
const [hubId] = CustomID.parseCustomId(interaction.customId).args;
const announcement = interaction.fields.getTextInputValue('announcement');

await sendToHub(hubId, {
embeds: [
new InfoEmbed()
.setTitle('📢 Official Hub Announcement')
.setDescription(announcement)
.setTimestamp(),
],
});

await interaction.editReply(`${emojis.yes} Announcement sent to all connected servers.`);
}
}
11 changes: 2 additions & 9 deletions src/commands/slash/Main/hub/appeal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,8 @@ export default class AppealCommand extends HubCommand {
}

private async runHubChecks(interaction: ChatInputCommandInteraction) {
const hubName = interaction.options.getString('hub') ?? undefined;
const hub = await db.hub.findFirst({
where: {
OR: [
{ name: hubName },
{ moderators: { some: { OR: [{ position: 'manager' }, { position: 'network_mod' }] } } },
],
},
});
const hubName = interaction.options.getString('hub', true);
const hub = await db.hub.findFirst({ where: { name: hubName } });

if (!hub || !isHubMod(interaction.user.id, hub)) {
await this.replyEmbed(
Expand Down
56 changes: 34 additions & 22 deletions src/commands/slash/Main/hub/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,7 @@ export default class HubCommand extends BaseCommand {
name: 'browse',
description: '🔍 Browse public hubs and join them!',
options: [
{
type: ApplicationCommandOptionType.String,
name: 'hub',
description: 'Search for a hub.',
required: false,
autocomplete: true,
},
{ ...hubOption, required: false },
{
type: ApplicationCommandOptionType.String,
name: 'sort',
Expand Down Expand Up @@ -85,7 +79,7 @@ export default class HubCommand extends BaseCommand {
ChannelType.PrivateThread,
],
},
{ ...hubOption, required: false },
{ ...hubOption },
{
type: ApplicationCommandOptionType.String,
name: 'invite',
Expand Down Expand Up @@ -276,7 +270,7 @@ export default class HubCommand extends BaseCommand {
name: 'list',
description: '🔎 List all the settings of the hub.',
type: ApplicationCommandOptionType.Subcommand,
options: [{ ...hubOption, required: false }],
options: [{ ...hubOption }],
},
{
name: 'toggle',
Expand All @@ -290,7 +284,7 @@ export default class HubCommand extends BaseCommand {
required: true,
choices: Object.keys(HubSettingsBits).map((s) => ({ name: s, value: s })),
},
{ ...hubOption, required: false },
{ ...hubOption },
],
},
],
Expand All @@ -304,7 +298,7 @@ export default class HubCommand extends BaseCommand {
type: ApplicationCommandOptionType.Subcommand,
name: 'view',
description: '🔎 View the current log channel & role configuration.',
options: [{ ...hubOption, required: false }],
options: [{ ...hubOption }],
},
{
name: 'set_channel',
Expand All @@ -324,7 +318,7 @@ export default class HubCommand extends BaseCommand {
type: ApplicationCommandOptionType.Channel,
required: true,
},
{ ...hubOption, required: false },
{ ...hubOption },
],
},
{
Expand All @@ -345,7 +339,7 @@ export default class HubCommand extends BaseCommand {
type: ApplicationCommandOptionType.Role,
required: true,
},
{ ...hubOption, required: false },
{ ...hubOption },
],
},
],
Expand All @@ -366,7 +360,7 @@ export default class HubCommand extends BaseCommand {
description: 'The duration. Eg. 1h, 1d, 1w, 1mo',
required: true,
},
{ ...hubOption, required: false },
{ ...hubOption },
],
},
],
Expand Down Expand Up @@ -411,13 +405,16 @@ export default class HubCommand extends BaseCommand {
type: ApplicationCommandOptionType.Subcommand,
name: 'edit',
description: '📝 Edit an existing blocked word rule in your hub.',
options: [hubOption, {
type: ApplicationCommandOptionType.String,
name: 'rule',
description: 'The name of the rule you want to edit.',
required: true,
autocomplete: true,
}],
options: [
hubOption,
{
type: ApplicationCommandOptionType.String,
name: 'rule',
description: 'The name of the rule you want to edit.',
required: true,
autocomplete: true,
},
],
},
{
type: ApplicationCommandOptionType.Subcommand,
Expand All @@ -427,6 +424,12 @@ export default class HubCommand extends BaseCommand {
},
],
},
{
type: ApplicationCommandOptionType.Subcommand,
name: 'announce',
description: '📢 Send an announcement to a hub you moderate.',
options: [hubOption],
},
],
};

Expand All @@ -442,7 +445,16 @@ export default class HubCommand extends BaseCommand {
}

async autocomplete(interaction: AutocompleteInteraction): Promise<void> {
const managerCmds = ['edit', 'settings', 'invite', 'moderator', 'logging', 'appeal', 'blockwords'];
const managerCmds = [
'edit',
'settings',
'invite',
'moderator',
'logging',
'appeal',
'blockwords',
'announce',
];
const modCmds = ['servers'];

const subcommand = interaction.options.getSubcommand();
Expand Down
19 changes: 15 additions & 4 deletions src/core/BaseCommand.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { emojis } from '#main/config/Constants.js';
import { MetadataHandler } from '#main/core/FileLoader.js';
import { InteractionFunction } from '#main/decorators/Interaction.js';
import { TranslationKeys } from '#main/types/locale.js';
import { InfoEmbed } from '#utils/EmbedUtils.js';
import { supportedLocaleCodes, t } from '#utils/Locale.js';
import Logger from '#utils/Logger.js';
Expand Down Expand Up @@ -125,10 +126,11 @@ export default abstract class BaseCommand {
}
}

async replyEmbed(
async replyEmbed<K extends keyof TranslationKeys>(
interaction: RepliableInteraction | MessageComponentInteraction,
desc: string,
desc: K | (string & NonNullable<unknown>),
opts?: {
t?: { [Key in TranslationKeys[K]]: string };
content?: string;
title?: string;
color?: ColorResolvable;
Expand All @@ -141,7 +143,14 @@ export default abstract class BaseCommand {
edit?: boolean;
},
): Promise<InteractionResponse | Message> {
const embed = new InfoEmbed().setDescription(desc).setTitle(opts?.title);
let description = desc as string;

if (t(desc as K, 'en', opts?.t)) {
const locale = await this.getLocale(interaction);
description = t(desc as keyof TranslationKeys, locale);
}

const embed = new InfoEmbed().setDescription(description).setTitle(opts?.title);
const message = { content: opts?.content, embeds: [embed], components: opts?.components };

if (opts?.edit) return await interaction.editReply(message);
Expand Down Expand Up @@ -177,7 +186,9 @@ export default abstract class BaseCommand {
Logger.debug(`Finished adding interactions for command: ${command.data.name}`);
}

protected async getLocale(interaction: Interaction): Promise<supportedLocaleCodes> {
protected async getLocale(
interaction: Interaction | MessageComponentInteraction,
): Promise<supportedLocaleCodes> {
const { userManager } = interaction.client;
return await userManager.getUserLocale(interaction.user.id);
}
Expand Down