/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 { EmbedBuilder, MessageFlags } = require('discord.js');
|
||||||
const { mongoose } = require('../../db-connection');
|
const { mongoose } = require('../../db-connection');
|
||||||
const { CONFIG } = require('../../config');
|
const { CONFIG } = require('../../config');
|
||||||
|
const { isStaff } = require('../../utils');
|
||||||
const { setNotifyDm } = require('../../services/staffSettings');
|
const { setNotifyDm } = require('../../services/staffSettings');
|
||||||
const { makeTicketName, resolveCreatorNickname } = require('../../services/tickets');
|
const { makeTicketName, resolveCreatorNickname } = require('../../services/tickets');
|
||||||
const { enqueueRename, enqueueMove, enqueueOverwrite, enqueueTopic, enqueueSend } = require('../../services/channelQueue');
|
const { enqueueRename, enqueueMove, enqueueOverwrite, enqueueTopic, enqueueSend } = require('../../services/channelQueue');
|
||||||
@@ -95,11 +96,26 @@ async function handleTransfer(interaction) {
|
|||||||
const ticket = await findTicketForChannel(interaction);
|
const ticket = await findTicketForChannel(interaction);
|
||||||
if (!ticket) return;
|
if (!ticket) return;
|
||||||
|
|
||||||
const staffRoleId = CONFIG.ROLE_TO_PING_ID;
|
// Cache-first member resolution; falls back to a fetch if not in cache.
|
||||||
const guildMember = await interaction.guild.members.fetch(member.id).catch(() => null);
|
// 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)) {
|
// Reject self-transfers and bots; require the target to satisfy isStaff(),
|
||||||
return interaction.reply({ content: 'The target member must have the staff role.', flags: MessageFlags.Ephemeral });
|
// 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.
|
// Defer before the DB write + rename so the interaction token survives.
|
||||||
|
|||||||
Reference in New Issue
Block a user