Files
broccolini-bot/commands/register.js

499 lines
18 KiB
JavaScript

/**
* 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('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('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('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: '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('cancel-close')
.setDescription('Cancel a pending force-close countdown')
.setContexts([InteractionContextType.Guild])
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
new SlashCommandBuilder()
.setName('fix-stale-tickets')
.setDescription('Admin only: backfill lastActivity on open tickets where it is null (sets to createdAt).')
.setContexts([InteractionContextType.Guild])
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
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 };