Compare commits
2 Commits
a565450e2d
...
a388d99fdf
| Author | SHA1 | Date | |
|---|---|---|---|
| a388d99fdf | |||
| 3212004fc9 |
@@ -17,9 +17,11 @@
|
|||||||
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 { enqueueMove, enqueueOverwrite, enqueueTopic, enqueueSend } = require('../../services/channelQueue');
|
const { makeTicketName, resolveCreatorNickname } = require('../../services/tickets');
|
||||||
const { logTicketEvent } = require('../../services/debugLog');
|
const { enqueueRename, enqueueMove, enqueueOverwrite, enqueueTopic, enqueueSend } = require('../../services/channelQueue');
|
||||||
|
const { logError, logTicketEvent } = require('../../services/debugLog');
|
||||||
const { findTicketForChannel } = require('../sharedHelpers');
|
const { findTicketForChannel } = require('../sharedHelpers');
|
||||||
|
|
||||||
const { requireStaffRole, fetchLoggingChannel } = require('./helpers');
|
const { requireStaffRole, fetchLoggingChannel } = require('./helpers');
|
||||||
@@ -54,16 +56,20 @@ async function handleAdd(interaction) {
|
|||||||
const ticket = await findTicketForChannel(interaction);
|
const ticket = await findTicketForChannel(interaction);
|
||||||
if (!ticket) return;
|
if (!ticket) return;
|
||||||
|
|
||||||
|
// Defer up front: enqueueOverwrite serializes behind any pending rename/move
|
||||||
|
// on this channel and can exceed Discord's 3s interaction-token window.
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await enqueueOverwrite(interaction.channel, user.id, {
|
await enqueueOverwrite(interaction.channel, user.id, {
|
||||||
ViewChannel: true,
|
ViewChannel: true,
|
||||||
SendMessages: true,
|
SendMessages: true,
|
||||||
ReadMessageHistory: true
|
ReadMessageHistory: true
|
||||||
});
|
});
|
||||||
await interaction.reply({ content: `Added ${user} to this ticket.`, allowedMentions: { parse: ['users'] } });
|
await interaction.editReply({ content: `Added ${user} to this ticket.`, allowedMentions: { parse: ['users'] } });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Add user error:', err);
|
console.error('Add user error:', err);
|
||||||
await interaction.reply({ content: 'Failed to add user.', flags: MessageFlags.Ephemeral });
|
await interaction.editReply({ content: 'Failed to add user.' }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +78,15 @@ async function handleRemove(interaction) {
|
|||||||
const ticket = await findTicketForChannel(interaction);
|
const ticket = await findTicketForChannel(interaction);
|
||||||
if (!ticket) return;
|
if (!ticket) return;
|
||||||
|
|
||||||
|
// Defer up front — same reason as handleAdd.
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await enqueueOverwrite(interaction.channel, user.id, null, 'delete');
|
await enqueueOverwrite(interaction.channel, user.id, null, 'delete');
|
||||||
await interaction.reply({ content: `Removed ${user} from this ticket.`, allowedMentions: { parse: ['users'] } });
|
await interaction.editReply({ content: `Removed ${user} from this ticket.`, allowedMentions: { parse: ['users'] } });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Remove user error:', err);
|
console.error('Remove user error:', err);
|
||||||
await interaction.reply({ content: 'Failed to remove user.', flags: MessageFlags.Ephemeral });
|
await interaction.editReply({ content: 'Failed to remove user.' }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,23 +96,54 @@ 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.
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const claimerLabel = guildMember.displayName || guildMember.user.username;
|
const claimerLabel = guildMember.displayName || guildMember.user.username;
|
||||||
|
|
||||||
await Ticket.updateOne(
|
await Ticket.updateOne(
|
||||||
{ gmailThreadId: ticket.gmailThreadId },
|
{ gmailThreadId: ticket.gmailThreadId },
|
||||||
{ $set: { claimedBy: claimerLabel } }
|
{ $set: { claimedBy: claimerLabel, claimerId: guildMember.id } }
|
||||||
);
|
);
|
||||||
|
ticket.claimedBy = claimerLabel;
|
||||||
|
ticket.claimerId = guildMember.id;
|
||||||
|
|
||||||
|
// Rename the channel to reflect the new claimer — mirrors the /claim
|
||||||
|
// button flow (applyClaim in handlers/buttons.js). Picks the new
|
||||||
|
// claimer's emoji from STAFF_EMOJIS and uses the escalated-claimed
|
||||||
|
// variant when tier >= 1.
|
||||||
|
const claimerEmoji = CONFIG.STAFF_EMOJIS[guildMember.id] || CONFIG.CLAIMER_EMOJI_FALLBACK;
|
||||||
|
const creatorNickname = await resolveCreatorNickname(interaction.guild, ticket);
|
||||||
|
const tier = ticket.escalationTier ?? (ticket.escalated ? 1 : 0);
|
||||||
|
const state = tier >= 1 ? 'escalated-claimed' : 'claimed';
|
||||||
|
enqueueRename(interaction.channel, makeTicketName(state, ticket, creatorNickname, claimerEmoji))
|
||||||
|
.catch(err => logError('rename', err).catch(() => {}));
|
||||||
|
|
||||||
// `reason` is staff-supplied freeform text; gate to user pings so @everyone in it can't mass-ping.
|
// `reason` is staff-supplied freeform text; gate to user pings so @everyone in it can't mass-ping.
|
||||||
await interaction.reply({
|
await interaction.editReply({
|
||||||
content: `Ticket transferred to ${member} by ${interaction.user}.\nReason: ${reason}`,
|
content: `Ticket transferred to ${member} by ${interaction.user}.\nReason: ${reason}`,
|
||||||
allowedMentions: { parse: ['users'] }
|
allowedMentions: { parse: ['users'] }
|
||||||
});
|
});
|
||||||
@@ -117,7 +157,7 @@ async function handleTransfer(interaction) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Transfer error:', err);
|
console.error('Transfer error:', err);
|
||||||
await interaction.reply({ content: 'Failed to transfer ticket.', flags: MessageFlags.Ephemeral });
|
await interaction.editReply({ content: 'Failed to transfer ticket.' }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,9 +166,13 @@ async function handleMove(interaction) {
|
|||||||
const ticket = await findTicketForChannel(interaction);
|
const ticket = await findTicketForChannel(interaction);
|
||||||
if (!ticket) return;
|
if (!ticket) return;
|
||||||
|
|
||||||
|
// Defer up front — enqueueMove serializes behind any pending rename and
|
||||||
|
// setParent itself can take a moment on busy channels.
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await enqueueMove(interaction.channel, category.id);
|
await enqueueMove(interaction.channel, category.id);
|
||||||
await interaction.reply(`Moved ticket to **${category.name}**.`);
|
await interaction.editReply(`Moved ticket to **${category.name}**.`);
|
||||||
|
|
||||||
const logChan = await fetchLoggingChannel(interaction.client);
|
const logChan = await fetchLoggingChannel(interaction.client);
|
||||||
if (logChan) {
|
if (logChan) {
|
||||||
@@ -138,7 +182,7 @@ async function handleMove(interaction) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Move error:', err);
|
console.error('Move error:', err);
|
||||||
await interaction.reply({ content: 'Failed to move ticket.', flags: MessageFlags.Ephemeral });
|
await interaction.editReply({ content: 'Failed to move ticket.' }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,12 +191,15 @@ async function handleTopic(interaction) {
|
|||||||
const ticket = await findTicketForChannel(interaction);
|
const ticket = await findTicketForChannel(interaction);
|
||||||
if (!ticket) return;
|
if (!ticket) return;
|
||||||
|
|
||||||
|
// Defer up front — enqueueTopic serializes behind any pending rename/move.
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await enqueueTopic(interaction.channel, text);
|
await enqueueTopic(interaction.channel, text);
|
||||||
await interaction.reply('Topic updated successfully.');
|
await interaction.editReply('Topic updated successfully.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Topic error:', err);
|
console.error('Topic error:', err);
|
||||||
await interaction.reply({ content: 'Failed to update topic.', flags: MessageFlags.Ephemeral });
|
await interaction.editReply({ content: 'Failed to update topic.' }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user