Files
broccolini-bot/commands/register.js
indifferentketchup c5d7539677 staff notifications
2026-04-06 23:53:32 -05:00

482 lines
17 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('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 };