p-queue
This commit is contained in:
@@ -20,6 +20,7 @@ const { canRename, makeTicketName, minutesFromMs, pickTicketCategoryId, createDi
|
||||
const { sendTicketClosedEmail } = require('../services/gmail');
|
||||
const { getTicketActionRow } = require('../utils/ticketComponents');
|
||||
const { setEmailRouting } = require('../services/guildSettings');
|
||||
const { enqueueRename } = require('../services/channelQueue');
|
||||
const { runEscalation, runDeescalation } = require('./commands');
|
||||
const { trackInteraction, trackError } = require('./analytics');
|
||||
|
||||
@@ -297,7 +298,7 @@ async function handleClaim(interaction, ticket) {
|
||||
guild
|
||||
);
|
||||
try {
|
||||
await interaction.channel.setName(newName);
|
||||
await enqueueRename(interaction.channel, newName);
|
||||
} catch (e) {
|
||||
console.error('Rename error (claim):', e);
|
||||
}
|
||||
@@ -333,8 +334,10 @@ async function handleClaim(interaction, ticket) {
|
||||
.replace(/\{staff_mention\}/g, interaction.user.toString())
|
||||
.replace(/\{staff_name\}/g, interaction.member?.displayName || interaction.user.username);
|
||||
const claimEmbed = new EmbedBuilder()
|
||||
.setTitle('✅ Ticket Claimed')
|
||||
.setDescription(claimText)
|
||||
.setColor(CONFIG.EMBED_COLOR_INFO);
|
||||
.setColor(CONFIG.EMBED_COLOR_CLAIMED)
|
||||
.setFooter({ text: `Claimed by ${claimerLabel}` });
|
||||
await interaction.followUp({ embeds: [claimEmbed] });
|
||||
} else {
|
||||
// Unclaim
|
||||
@@ -352,7 +355,7 @@ async function handleClaim(interaction, ticket) {
|
||||
guild
|
||||
);
|
||||
try {
|
||||
await interaction.channel.setName(newName);
|
||||
await enqueueRename(interaction.channel, newName);
|
||||
} catch (e) {
|
||||
console.error('Rename error (unclaim):', e);
|
||||
}
|
||||
@@ -383,8 +386,10 @@ async function handleClaim(interaction, ticket) {
|
||||
.replace(/\{staff_mention\}/g, interaction.user.toString())
|
||||
.replace(/\{staff_name\}/g, interaction.member?.displayName || interaction.user.username);
|
||||
const unclaimEmbed = new EmbedBuilder()
|
||||
.setTitle('🔓 Ticket Unclaimed')
|
||||
.setDescription(unclaimText)
|
||||
.setColor(CONFIG.EMBED_COLOR_INFO);
|
||||
.setColor(0x808080)
|
||||
.setFooter({ text: `Unclaimed by ${claimerLabel}` });
|
||||
await interaction.followUp({ embeds: [unclaimEmbed] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@ const {
|
||||
} = require('discord.js');
|
||||
const { mongoose } = require('../db-connection');
|
||||
const { CONFIG, TICKET_TAGS } = require('../config');
|
||||
const { getPriorityEmoji, replaceVariables, escapeRegex } = require('../utils');
|
||||
const { getPriorityEmoji, getPriorityColor, replaceVariables, escapeRegex } = require('../utils');
|
||||
const { canRename, makeTicketName, pickTicketCategoryId, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets');
|
||||
const { sendTicketNotificationEmail } = require('../services/gmail');
|
||||
const { getTicketActionRow } = require('../utils/ticketComponents');
|
||||
const { getEmailRouting } = require('../services/guildSettings');
|
||||
const { enqueueRename, enqueueMove } = require('../services/channelQueue');
|
||||
const { trackInteraction, trackError, getAnalyticsSummary } = require('./analytics');
|
||||
const { handleAccountInfoCommand } = require('./accountinfo');
|
||||
const { handleSetupCommand } = require('./setup');
|
||||
@@ -80,7 +81,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
interaction.guild
|
||||
);
|
||||
try {
|
||||
await interaction.channel.setName(newName);
|
||||
await enqueueRename(interaction.channel, newName);
|
||||
} catch (e) {
|
||||
console.error('Rename error (escalate):', e);
|
||||
}
|
||||
@@ -93,7 +94,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
}
|
||||
|
||||
if (!interaction.channel.isThread() && categoryId) {
|
||||
await interaction.channel.setParent(categoryId, { lockPermissions: true });
|
||||
await enqueueMove(interaction.channel, categoryId);
|
||||
}
|
||||
|
||||
const pendingEmbed = new EmbedBuilder()
|
||||
@@ -117,8 +118,10 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
.replace(/\{support_name\}/g, CONFIG.SUPPORT_NAME)
|
||||
+ (reason ? `\n\n**Reason:** ${reason}` : '');
|
||||
const escalatedEmbed = new EmbedBuilder()
|
||||
.setTitle(`🚨 Escalated to ${nextTier === 1 ? 'Tier 2' : 'Tier 3'} Support`)
|
||||
.setDescription(escalationBody)
|
||||
.setColor(CONFIG.EMBED_COLOR_INFO);
|
||||
.setColor(CONFIG.EMBED_COLOR_ESCALATED)
|
||||
.setFooter({ text: `Escalated by ${interaction.member?.displayName || interaction.user.username}` });
|
||||
const updatedTicketForRow = { ...ticket, escalationTier: nextTier, escalated: true };
|
||||
const escalationRow = getTicketActionRow(updatedTicketForRow);
|
||||
await interaction.channel.send({
|
||||
@@ -193,7 +196,7 @@ async function runDeescalation(interaction, ticket) {
|
||||
interaction.guild
|
||||
);
|
||||
try {
|
||||
await interaction.channel.setName(newName);
|
||||
await enqueueRename(interaction.channel, newName);
|
||||
} catch (e) {
|
||||
console.error('Rename error (deescalate):', e);
|
||||
}
|
||||
@@ -206,12 +209,16 @@ async function runDeescalation(interaction, ticket) {
|
||||
}
|
||||
|
||||
if (!interaction.channel.isThread() && categoryId) {
|
||||
await interaction.channel.setParent(categoryId, { lockPermissions: true });
|
||||
await enqueueMove(interaction.channel, categoryId);
|
||||
}
|
||||
|
||||
const tierLabel = newTier === 0 ? 'normal' : newTier === 1 ? 'tier 2' : 'tier 3';
|
||||
const deescalateEmbed = new EmbedBuilder()
|
||||
.setColor(0x00BFFF)
|
||||
.setTitle(`✅ De-escalated to ${tierLabel} Support`)
|
||||
.setFooter({ text: interaction.member?.displayName || interaction.user.username });
|
||||
await interaction.reply({
|
||||
content: `Ticket de‑escalated to **${tierLabel}** support.`,
|
||||
embeds: [deescalateEmbed],
|
||||
ephemeral: true
|
||||
});
|
||||
|
||||
@@ -737,7 +744,16 @@ async function handleCommand(interaction) {
|
||||
{ $set: { priority: level } }
|
||||
);
|
||||
|
||||
await interaction.reply(channelMessage);
|
||||
const priorityTitle =
|
||||
newIdx === oldIdx
|
||||
? 'Priority Set'
|
||||
: `Priority ${newIdx > oldIdx ? 'Upgraded' : 'Downgraded'} → ${levelLabel}`;
|
||||
const priorityEmbed = new EmbedBuilder()
|
||||
.setTitle(priorityTitle)
|
||||
.setDescription(channelMessage)
|
||||
.setColor(getPriorityColor(level))
|
||||
.setFooter({ text: interaction.member?.displayName || interaction.user.username });
|
||||
await interaction.reply({ embeds: [priorityEmbed] });
|
||||
|
||||
if (level === 'high' && ticket.gmailThreadId && !ticket.gmailThreadId.startsWith('discord-')) {
|
||||
await sendTicketNotificationEmail(
|
||||
@@ -1139,11 +1155,4 @@ async function handleAutocomplete(interaction) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
handleCommand,
|
||||
handleContextMenu,
|
||||
handleAutocomplete,
|
||||
runEscalation,
|
||||
runDeescalation,
|
||||
hasStaffRole
|
||||
};
|
||||
module.exports = { handleCommand, handleContextMenu, handleAutocomplete, runEscalation, runDeescalation };
|
||||
|
||||
@@ -1,91 +1,20 @@
|
||||
/**
|
||||
* Discord messageCreate handler – prefix commands and forwards staff replies to Gmail.
|
||||
* Discord messageCreate handler – forwards staff replies to Gmail.
|
||||
*/
|
||||
const { ChannelType, AttachmentBuilder } = require('discord.js');
|
||||
const { mongoose } = require('../db-connection');
|
||||
const { CONFIG } = require('../config');
|
||||
const { extractRawEmail } = require('../utils');
|
||||
const { getGmailClient, sendGmailReply } = require('../services/gmail');
|
||||
const { updateTicketActivity } = require('../services/tickets');
|
||||
const { hasStaffRole } = require('./commands');
|
||||
const { trackInteraction, trackError } = require('./analytics');
|
||||
|
||||
const Ticket = mongoose.model('Ticket');
|
||||
|
||||
/**
|
||||
* `!ids` — list channel and role IDs (same rules as slash staff-gated commands).
|
||||
* @returns {Promise<boolean>} true if the message was consumed
|
||||
*/
|
||||
async function tryHandleIdsCommand(m) {
|
||||
if (m.content?.trim().toLowerCase() !== '!ids') return false;
|
||||
|
||||
if (!m.guild) {
|
||||
await m.reply('This command can only be used in a server.').catch(() => {});
|
||||
return true;
|
||||
}
|
||||
|
||||
const staffConfigured =
|
||||
CONFIG.ROLE_ID_TO_PING || (CONFIG.ADDITIONAL_STAFF_ROLES && CONFIG.ADDITIONAL_STAFF_ROLES.length > 0);
|
||||
if (staffConfigured && !hasStaffRole(m.member)) {
|
||||
const roleMention = CONFIG.ROLE_ID_TO_PING ? `<@&${CONFIG.ROLE_ID_TO_PING}>` : 'support';
|
||||
await m.reply({
|
||||
content: `This command is only available to the support team (${roleMention}).`,
|
||||
allowedMentions: { users: [], roles: [], repliedUser: false }
|
||||
}).catch(() => {});
|
||||
return true;
|
||||
}
|
||||
|
||||
trackInteraction('commands', 'ids-prefix', m.author.tag);
|
||||
|
||||
try {
|
||||
await m.guild.channels.fetch().catch(() => {});
|
||||
|
||||
const channelTypeName = type =>
|
||||
Object.entries(ChannelType).find(([, v]) => v === type)?.[0] ?? String(type);
|
||||
|
||||
const lines = ['**Channels:**'];
|
||||
const channels = [...m.guild.channels.cache.values()].sort((a, b) => a.rawPosition - b.rawPosition);
|
||||
for (const ch of channels) {
|
||||
lines.push(`\`${ch.id}\` — ${ch.name} (${channelTypeName(ch.type)})`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('**Roles:**');
|
||||
const roles = [...m.guild.roles.cache.values()].sort((a, b) => b.position - a.position);
|
||||
for (const role of roles) {
|
||||
lines.push(`\`${role.id}\` — ${role.name}`);
|
||||
}
|
||||
|
||||
const text = lines.join('\n');
|
||||
const baseReply = { allowedMentions: { users: [], roles: [], repliedUser: false } };
|
||||
if (text.length <= 1900) {
|
||||
await m.reply({ content: text, ...baseReply }).catch(() => {});
|
||||
} else {
|
||||
const buf = Buffer.from(text, 'utf8');
|
||||
await m.reply({
|
||||
content: 'List is long; sent as a file.',
|
||||
files: [new AttachmentBuilder(buf, { name: `guild-ids-${m.guild.id}.txt` })],
|
||||
...baseReply
|
||||
}).catch(() => {});
|
||||
}
|
||||
} catch (err) {
|
||||
trackError('ids-prefix-command', err, null);
|
||||
await m.reply({
|
||||
content: 'Failed to build ID list.',
|
||||
allowedMentions: { repliedUser: false }
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a Discord message in a ticket channel → relay to Gmail (email tickets only).
|
||||
*/
|
||||
async function handleDiscordReply(m) {
|
||||
if (m.author.bot || m.interaction) return;
|
||||
|
||||
if (await tryHandleIdsCommand(m)) return;
|
||||
|
||||
const ticket = await Ticket.findOne({ discordThreadId: m.channel.id }).lean();
|
||||
if (!ticket) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user