diff --git a/handlers/commands/index.js b/handlers/commands/index.js index de7fe2e..bcc849e 100644 --- a/handlers/commands/index.js +++ b/handlers/commands/index.js @@ -17,6 +17,7 @@ const { EmbedBuilder, MessageFlags } = require('discord.js'); const { mongoose } = require('../../db-connection'); const { CONFIG } = require('../../config'); +const { isStaff } = require('../../utils'); const { setNotifyDm } = require('../../services/staffSettings'); const { makeTicketName, resolveCreatorNickname } = require('../../services/tickets'); const { enqueueRename, enqueueMove, enqueueOverwrite, enqueueTopic, enqueueSend } = require('../../services/channelQueue'); @@ -95,11 +96,26 @@ async function handleTransfer(interaction) { const ticket = await findTicketForChannel(interaction); if (!ticket) return; - const staffRoleId = CONFIG.ROLE_TO_PING_ID; - const guildMember = await interaction.guild.members.fetch(member.id).catch(() => null); + // Cache-first member resolution; falls back to a fetch if not in cache. + // GuildMembers intent keeps the cache warm in normal operation. + const guildMember = interaction.guild.members.cache.get(member.id) + || await interaction.guild.members.fetch(member.id).catch(() => null); - if (!guildMember || !guildMember.roles.cache.has(staffRoleId)) { - return interaction.reply({ content: 'The target member must have the staff role.', flags: MessageFlags.Ephemeral }); + // Reject self-transfers and bots; require the target to satisfy isStaff(), + // which covers ROLE_ID_TO_PING + ADDITIONAL_STAFF_ROLES — the same staff + // definition used by every other gate in the bot. The previous check only + // looked at ROLE_TO_PING_ID, missing additional staff roles. + if (!guildMember || guildMember.user.bot || !isStaff(guildMember)) { + return interaction.reply({ + content: 'The target member must have the staff role.', + flags: MessageFlags.Ephemeral + }); + } + if (guildMember.id === interaction.user.id) { + return interaction.reply({ + content: 'You cannot transfer the ticket to yourself.', + flags: MessageFlags.Ephemeral + }); } // Defer before the DB write + rename so the interaction token survives.