/** * Slash command and context-menu registration. */ const { REST, Routes, SlashCommandBuilder, PermissionFlagsBits, ChannelType, InteractionContextType, ApplicationIntegrationType, ContextMenuCommandBuilder, ApplicationCommandType } = require('discord.js'); const { CONFIG } = require('../config'); async function registerCommands() { if (!CONFIG.CLIENT_ID || !CONFIG.DISCORD_GUILD_ID) return; const rest = new REST({ version: '10' }).setToken(CONFIG.DISCORD_TOKEN); const commands = [ new SlashCommandBuilder() .setName('escalate') .setDescription('Escalate this ticket to tier 2 or tier 3 (always unclaims)') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addStringOption(opt => opt .setName('level') .setDescription('Target escalation level') .setRequired(true) .addChoices( { name: 'Tier 2', value: '2' }, { name: 'Tier 3', value: '3' } ) ), new SlashCommandBuilder() .setName('deescalate') .setDescription('De-escalate this ticket (tier 3 → tier 2, or tier 2 → normal)') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), new SlashCommandBuilder() .setName('add') .setDescription('Add a user to this ticket thread') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addUserOption(opt => opt.setName('user').setDescription('User to add').setRequired(true) ), new SlashCommandBuilder() .setName('remove') .setDescription('Remove a user from this ticket thread') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addUserOption(opt => opt.setName('user').setDescription('User to remove').setRequired(true) ), new SlashCommandBuilder() .setName('transfer') .setDescription('Transfer this ticket to another staff member') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addUserOption(opt => opt.setName('member').setDescription('Staff member to transfer to').setRequired(true) ) .addStringOption(opt => opt .setName('reason') .setDescription('Reason for transfer') .setMinLength(10) .setMaxLength(500) .setRequired(false) ), new SlashCommandBuilder() .setName('move') .setDescription('Move this ticket to another category') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) .addChannelOption(opt => opt .setName('category') .setDescription('Category to move to') .setRequired(true) .addChannelTypes(ChannelType.GuildCategory) ), new SlashCommandBuilder() .setName('force-close') .setDescription('Force close this ticket without confirmation') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels), new SlashCommandBuilder() .setName('topic') .setDescription('Set the topic/description for this ticket') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addStringOption(opt => opt .setName('text') .setDescription('Topic text') .setMinLength(5) .setMaxLength(1024) .setRequired(true) ), new SlashCommandBuilder() .setName('response') .setDescription('Saved response tags (custom templates)') .setContexts([InteractionContextType.Guild]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addSubcommand(sub => sub .setName('send') .setDescription('Send a saved response') .addStringOption(opt => opt .setName('name') .setDescription('Tag name') .setRequired(true) .setAutocomplete(true) ) ) .addSubcommand(sub => sub .setName('create') .setDescription('Create a new saved response') .addStringOption(opt => opt .setName('name') .setDescription('Tag name (unique)') .setMinLength(2) .setMaxLength(50) .setRequired(true) ) .addStringOption(opt => opt .setName('content') .setDescription('Tag content (supports variables)') .setMinLength(10) .setMaxLength(2000) .setRequired(true) ) ) .addSubcommand(sub => sub .setName('edit') .setDescription('Edit an existing saved response') .addStringOption(opt => opt .setName('name') .setDescription('Tag name') .setRequired(true) .setAutocomplete(true) ) .addStringOption(opt => opt .setName('content') .setDescription('New tag content') .setMinLength(10) .setMaxLength(2000) .setRequired(true) ) ) .addSubcommand(sub => sub .setName('delete') .setDescription('Delete a saved response') .addStringOption(opt => opt .setName('name') .setDescription('Tag name') .setRequired(true) .setAutocomplete(true) ) ) .addSubcommand(sub => sub.setName('list').setDescription('List all saved responses') ), new SlashCommandBuilder() .setName('help') .setDescription('Show all available commands and information') .setIntegrationTypes([ ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall ]) .setContexts([ InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel ]), new SlashCommandBuilder() .setName('panel') .setDescription('Create a ticket panel for users to open Discord tickets') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) .addChannelOption(opt => opt .setName('channel') .setDescription('Channel to send the panel to') .setRequired(true) .addChannelTypes(ChannelType.GuildText) ) .addStringOption(opt => opt .setName('type') .setDescription('Panel type: thread only, category only, or both') .setRequired(false) .addChoices( { name: 'Thread', value: 'thread' }, { name: 'Category', value: 'category' }, { name: 'Both (thread + category)', value: 'both' } ) ) .addStringOption(opt => opt .setName('title') .setDescription('Panel title') .setMinLength(5) .setMaxLength(100) .setRequired(false) ) .addStringOption(opt => opt .setName('description') .setDescription('Panel description') .setMinLength(10) .setMaxLength(500) .setRequired(false) ), new SlashCommandBuilder() .setName('notifydm') .setDescription('Toggle DM notifications when your ticket receives a customer reply.') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addStringOption(opt => opt .setName('setting') .setDescription('on or off') .setRequired(true) .addChoices( { name: 'on', value: 'on' }, { name: 'off', value: 'off' } ) ), new SlashCommandBuilder() .setName('closetimer') .setDescription('Set the force-close countdown duration') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addStringOption(opt => opt .setName('seconds') .setDescription('Countdown duration') .setRequired(true) .addChoices( { name: '5s', value: '5' }, { name: '10s', value: '10' }, { name: '30s', value: '30' }, { name: '45s', value: '45' }, { name: '1m', value: '60' }, { name: '2m', value: '120' }, { name: '3m', value: '180' }, { name: '4m', value: '240' }, { name: '5m', value: '300' }, { name: '10m', value: '600' } ) ), new SlashCommandBuilder() .setName('staffthread') .setDescription('Manage staff discussion threads on ticket channels') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addSubcommand(sub => sub.setName('toggle').setDescription('Toggle staff threads on/off') ) .addSubcommand(sub => sub .setName('name') .setDescription('Set the staff thread name') .addStringOption(opt => opt.setName('thread_name').setDescription('Thread name').setMaxLength(100).setRequired(true) ) ) .addSubcommand(sub => sub .setName('autorole') .setDescription('Toggle auto-adding role members to staff thread') .addBooleanOption(opt => opt.setName('enabled').setDescription('Enable or disable').setRequired(true) ) ), new SlashCommandBuilder() .setName('pinmessages') .setDescription('Manage auto-pinning of ticket messages') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addSubcommand(sub => sub .setName('initial') .setDescription('Toggle auto-pin of welcome message') .addBooleanOption(opt => opt.setName('enabled').setDescription('Enable or disable').setRequired(true) ) ) .addSubcommand(sub => sub .setName('escalation') .setDescription('Toggle auto-pin of escalation messages') .addBooleanOption(opt => opt.setName('enabled').setDescription('Enable or disable').setRequired(true) ) ) .addSubcommand(sub => sub .setName('suppress') .setDescription('Toggle suppression of pin system messages') .addBooleanOption(opt => opt.setName('enabled').setDescription('Enable or disable').setRequired(true) ) ), new SlashCommandBuilder() .setName('gmailpoll') .setDescription('Set the Gmail poll interval') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addStringOption(opt => opt .setName('interval') .setDescription('Poll interval') .setRequired(true) .addChoices( { name: '30s', value: '30' }, { name: '45s', value: '45' }, { name: '1m', value: '60' }, { name: '2m', value: '120' }, { name: '3m', value: '180' }, { name: '4m', value: '240' }, { name: '5m', value: '300' }, { name: '10m', value: '600' } ) ), new SlashCommandBuilder() .setName('cancel-close') .setDescription('Cancel a pending force-close countdown') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), new SlashCommandBuilder() .setName('signature') .setDescription('Set your personal email signature (valediction, display name, tagline)') .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) ]; const contextMenuCommands = [ new ContextMenuCommandBuilder() .setName('Create Ticket From Message') .setType(ApplicationCommandType.Message) .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), new ContextMenuCommandBuilder() .setName('View User Tickets') .setType(ApplicationCommandType.User) .setContexts([InteractionContextType.Guild]) .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) ]; await rest.put( Routes.applicationGuildCommands(CONFIG.CLIENT_ID, CONFIG.DISCORD_GUILD_ID), { body: [...commands.map(cmd => cmd.toJSON()), ...contextMenuCommands.map(cmd => cmd.toJSON())] } ); console.log(`✅ Registered ${commands.length} slash commands + ${contextMenuCommands.length} context menu commands`); } module.exports = { registerCommands };