Files
broccolini-bot/commands/register.js
indifferentketchup 56ba8e363a changes
2026-04-07 09:29:24 -05:00

603 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 (16)')
.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 (16)')
.setMinValue(1)
.setMaxValue(6)
.setRequired(true)
),
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('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 };