/transfer: validate target via isStaff() — covers ADDITIONAL_STAFF_ROLES
The transfer-target check previously matched only against CONFIG.ROLE_TO_PING_ID, so a member with one of CONFIG.ADDITIONAL_STAFF_ROLES (a recognized staff role everywhere else in the bot, including requireStaffRole and the messages.js claimer-DM path) was rejected as a transfer target. Switch to isStaff() so the transfer-target gate matches the rest of the codebase's staff definition. Also: - Reject bots as transfer targets (guildMember.user.bot). - Reject self-transfer (transferring to interaction.user.id) — the rename + DB write would no-op but the log line claimed a transfer that didn't happen. - Resolve the target member cache-first to avoid an unnecessary REST round-trip when the GuildMembers intent has the user cached.
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user