482 lines
17 KiB
JavaScript
482 lines
17 KiB
JavaScript
/**
|
||
* Slash command and context-menu registration.
|
||
*/
|
||
const {
|
||
REST,
|
||
Routes,
|
||
SlashCommandBuilder,
|
||
PermissionFlagsBits,
|
||
ChannelType,
|
||
InteractionContextType,
|
||
ApplicationIntegrationType,
|
||
ContextMenuCommandBuilder,
|
||
ApplicationCommandType
|
||
} = require('discord.js');
|
||
const { CONFIG, TICKET_TAGS } = 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')
|
||
.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' }
|
||
)
|
||
)
|
||
.addStringOption(opt =>
|
||
opt
|
||
.setName('action')
|
||
.setDescription('Unclaim ticket or keep current claimer')
|
||
.setRequired(true)
|
||
.addChoices(
|
||
{ name: 'Unclaim', value: 'unclaim' },
|
||
{ name: 'Keep', value: 'keep' }
|
||
)
|
||
),
|
||
|
||
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('tag')
|
||
.setDescription('Set ticket category (dropdown)')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||
.addStringOption(o =>
|
||
o
|
||
.setName('category')
|
||
.setDescription('Ticket category tag')
|
||
.setRequired(true)
|
||
.addChoices(...(TICKET_TAGS || []).map(({ value, emoji, name }) => ({ name: `${emoji} ${name}`, value })))
|
||
),
|
||
|
||
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('setup')
|
||
.setDescription('Run the panel setup wizard (name, support role, category, transcript channel, panel channel)')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels),
|
||
|
||
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('email-routing')
|
||
.setDescription('Switch where new email tickets are created: threads or category channels')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild),
|
||
|
||
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('backup')
|
||
.setDescription('Export full ticket list to a .txt file in the backup/export channel')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('export')
|
||
.setDescription('Export tickets (optional filter and limit) to a .txt file in the backup/export channel')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||
.addStringOption(opt =>
|
||
opt
|
||
.setName('status')
|
||
.setDescription('Filter by status')
|
||
.setRequired(false)
|
||
.addChoices(
|
||
{ name: 'Open', value: 'open' },
|
||
{ name: 'Closed', value: 'closed' }
|
||
)
|
||
)
|
||
.addIntegerOption(opt =>
|
||
opt
|
||
.setName('limit')
|
||
.setDescription('Max number of tickets to export (default 500)')
|
||
.setMinValue(1)
|
||
.setMaxValue(5000)
|
||
.setRequired(false)
|
||
),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('priority')
|
||
.setDescription('Set the priority of this ticket')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||
.addStringOption(opt =>
|
||
opt
|
||
.setName('level')
|
||
.setDescription('Priority level')
|
||
.setRequired(true)
|
||
.addChoices(
|
||
{ name: '🟢 Low', value: 'low' },
|
||
{ name: '🟡 Normal', value: 'normal' },
|
||
{ name: '🟠 Medium', value: 'medium' },
|
||
{ name: '🔴 High', value: 'high' }
|
||
)
|
||
),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('search')
|
||
.setDescription('Search for tickets')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||
.addStringOption(opt =>
|
||
opt
|
||
.setName('query')
|
||
.setDescription('Search query (email, subject, or ticket number)')
|
||
.setMinLength(2)
|
||
.setMaxLength(100)
|
||
.setRequired(true)
|
||
)
|
||
.addStringOption(opt =>
|
||
opt
|
||
.setName('status')
|
||
.setDescription('Filter by status')
|
||
.setRequired(false)
|
||
.addChoices(
|
||
{ name: 'Open', value: 'open' },
|
||
{ name: 'Closed', value: 'closed' },
|
||
{ name: 'All', value: 'all' }
|
||
)
|
||
),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('stats')
|
||
.setDescription('View bot statistics and analytics')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('notification')
|
||
.setDescription('Manage your staff notification settings')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||
.addSubcommand(sub =>
|
||
sub
|
||
.setName('set')
|
||
.setDescription('Set your notification cooldown (hours between alerts per ticket)')
|
||
.addIntegerOption(opt =>
|
||
opt
|
||
.setName('hours')
|
||
.setDescription('Cooldown in hours (1–6)')
|
||
.setMinValue(1)
|
||
.setMaxValue(6)
|
||
.setRequired(true)
|
||
)
|
||
)
|
||
.addSubcommand(sub =>
|
||
sub
|
||
.setName('add')
|
||
.setDescription('Create a notification channel for a staff member')
|
||
.addUserOption(opt =>
|
||
opt.setName('member').setDescription('Staff member').setRequired(true)
|
||
)
|
||
),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('staffnotification')
|
||
.setDescription('Override notification cooldown for another staff member (admin only)')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
||
.addUserOption(opt =>
|
||
opt.setName('member').setDescription('Staff member').setRequired(true)
|
||
)
|
||
.addIntegerOption(opt =>
|
||
opt
|
||
.setName('hours')
|
||
.setDescription('Cooldown in hours (1–6)')
|
||
.setMinValue(1)
|
||
.setMaxValue(6)
|
||
.setRequired(true)
|
||
),
|
||
|
||
new SlashCommandBuilder()
|
||
.setName('accountinfo')
|
||
.setDescription('Look up website account info by email or Discord user')
|
||
.setContexts([InteractionContextType.Guild])
|
||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
|
||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
|
||
.addSubcommand(sub =>
|
||
sub
|
||
.setName('email')
|
||
.setDescription('Look up by email address')
|
||
.addStringOption(opt =>
|
||
opt.setName('email').setDescription('Account email').setRequired(true)
|
||
)
|
||
)
|
||
.addSubcommand(sub =>
|
||
sub
|
||
.setName('discord')
|
||
.setDescription('Look up by Discord user')
|
||
.addUserOption(opt =>
|
||
opt.setName('user').setDescription('Discord user').setRequired(true)
|
||
)
|
||
)
|
||
];
|
||
|
||
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 };
|