import { PermissionFlagsBits } from 'discord.js'; import { t, tf } from '@nexumi/shared'; import type { SlashCommand } from '../../types.js'; import { getGuildLocale } from '../../i18n.js'; import { requirePermission } from '../../permissions.js'; import { voiceCommandData } from './command-definitions.js'; import { claimTempChannel, fetchCategory, getTempVoiceChannelForMember, getTempVoiceConfig, hideTempChannel, kickFromTempChannel, lockTempChannel, renameTempChannel, sendControlPanelToChannel, setTempChannelLimit, transferTempChannelOwnership, updateTempVoiceConfig } from './service.js'; async function requireOwnedTempChannel( interaction: Parameters[0], context: Parameters[1], locale: 'de' | 'en' ) { if (!interaction.inGuild()) { await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true }); return null; } const guildMember = interaction.guild!.members.cache.get(interaction.user.id) ?? (await interaction.guild!.members.fetch(interaction.user.id)); const owned = await getTempVoiceChannelForMember(context.prisma, interaction.guildId!, guildMember); if (!owned || owned.record.ownerId !== interaction.user.id) { await interaction.reply({ content: t(locale, 'tempvoice.error.notOwner'), ephemeral: true }); return null; } return owned; } const voiceCommand: SlashCommand = { data: voiceCommandData, async execute(interaction, context) { const locale = await getGuildLocale(context.prisma, interaction.guildId); if (!interaction.inGuild()) { await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true }); return; } const guildId = interaction.guildId!; const sub = interaction.options.getSubcommand(); if (sub === 'setup') { if (!(await requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale))) { return; } const hub = interaction.options.getChannel('hub_channel', true); const category = interaction.options.getChannel('category', true); const defaultName = interaction.options.getString('default_name'); const limit = interaction.options.getInteger('limit'); const categoryChannel = await fetchCategory(interaction.guild!, category.id); if (!categoryChannel) { await interaction.reply({ content: t(locale, 'tempvoice.error.invalidCategory'), ephemeral: true }); return; } await updateTempVoiceConfig(context.prisma, guildId, { enabled: true, hubChannelId: hub.id, categoryId: category.id, defaultName: defaultName ?? undefined, defaultLimit: limit ?? undefined }); await interaction.reply({ content: t(locale, 'tempvoice.setup.done'), ephemeral: true }); return; } const config = await getTempVoiceConfig(context.prisma, guildId); if (!config.enabled) { await interaction.reply({ content: t(locale, 'tempvoice.error.disabled'), ephemeral: true }); return; } if (sub === 'panel') { const owned = await requireOwnedTempChannel(interaction, context, locale); if (!owned) { return; } await sendControlPanelToChannel(owned.channel, locale); await interaction.reply({ content: t(locale, 'tempvoice.panel.sent'), ephemeral: true }); return; } if (sub === 'claim') { const member = await interaction.guild!.members.fetch(interaction.user.id); const inChannel = await getTempVoiceChannelForMember(context.prisma, guildId, member); if (!inChannel) { await interaction.reply({ content: t(locale, 'tempvoice.error.notInChannel'), ephemeral: true }); return; } const result = await claimTempChannel(context, inChannel.record, inChannel.channel, member, locale); if (result === 'owner_present') { await interaction.reply({ content: t(locale, 'tempvoice.claim.ownerPresent'), ephemeral: true }); return; } if (result === 'not_in_channel') { await interaction.reply({ content: t(locale, 'tempvoice.error.notInChannel'), ephemeral: true }); return; } await interaction.reply({ content: t(locale, 'tempvoice.claim.success'), ephemeral: true }); return; } const owned = await requireOwnedTempChannel(interaction, context, locale); if (!owned) { return; } if (sub === 'rename') { const name = interaction.options.getString('name', true); await renameTempChannel(owned.channel, name); await interaction.reply({ content: tf(locale, 'tempvoice.rename.success', { name: name.slice(0, 100) }), ephemeral: true }); return; } if (sub === 'limit') { const limit = interaction.options.getInteger('limit', true); await setTempChannelLimit(owned.channel, limit); await interaction.reply({ content: tf(locale, 'tempvoice.limit.success', { limit }), ephemeral: true }); return; } if (sub === 'lock') { await lockTempChannel(owned.channel); await interaction.reply({ content: t(locale, 'tempvoice.lock.success'), ephemeral: true }); return; } if (sub === 'hide') { await hideTempChannel(owned.channel); await interaction.reply({ content: t(locale, 'tempvoice.hide.success'), ephemeral: true }); return; } if (sub === 'kick') { const user = interaction.options.getUser('user', true); if (user.id === interaction.user.id) { await interaction.reply({ content: t(locale, 'tempvoice.kick.self'), ephemeral: true }); return; } const ok = await kickFromTempChannel(owned.channel, user.id); if (!ok) { await interaction.reply({ content: t(locale, 'tempvoice.kick.notInChannel'), ephemeral: true }); return; } await interaction.reply({ content: tf(locale, 'tempvoice.kick.success', { user: `<@${user.id}>` }), ephemeral: true }); return; } if (sub === 'transfer') { const user = interaction.options.getUser('user', true); if (user.id === interaction.user.id) { await interaction.reply({ content: t(locale, 'tempvoice.transfer.self'), ephemeral: true }); return; } const target = await interaction.guild!.members.fetch(user.id).catch(() => null); if (!target || !owned.channel.members.has(target.id)) { await interaction.reply({ content: t(locale, 'tempvoice.transfer.notInChannel'), ephemeral: true }); return; } await transferTempChannelOwnership(context, owned.record, owned.channel, target, locale); await interaction.reply({ content: tf(locale, 'tempvoice.transfer.success', { user: `<@${user.id}>` }), ephemeral: true }); } } }; export const tempVoiceCommands: SlashCommand[] = [voiceCommand];