change in ticket renaming and flow
This commit is contained in:
@@ -117,6 +117,8 @@ CLAIM_TIMEOUT_HOURS=48
|
|||||||
AUTO_UNCLAIM_ENABLED=false
|
AUTO_UNCLAIM_ENABLED=false
|
||||||
AUTO_UNCLAIM_AFTER_HOURS=24
|
AUTO_UNCLAIM_AFTER_HOURS=24
|
||||||
ALLOW_CLAIM_OVERWRITE=false
|
ALLOW_CLAIM_OVERWRITE=false
|
||||||
|
STAFF_EMOJIS=224692549225283584:🍅 # userId:emoji pairs, comma-separated
|
||||||
|
CLAIMER_EMOJI_FALLBACK=🎫 # fallback if claimer has no entry in STAFF_EMOJIS
|
||||||
|
|
||||||
# --- Thread-style tickets (legacy) ---
|
# --- Thread-style tickets (legacy) ---
|
||||||
USE_THREADS=false
|
USE_THREADS=false
|
||||||
|
|||||||
16
config.js
16
config.js
@@ -134,6 +134,22 @@ const CONFIG = {
|
|||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
})(),
|
})(),
|
||||||
|
STAFF_EMOJIS: (() => {
|
||||||
|
const raw = process.env.STAFF_EMOJIS;
|
||||||
|
const map = new Map();
|
||||||
|
if (!raw || !String(raw).trim()) return map;
|
||||||
|
for (const part of String(raw).split(',')) {
|
||||||
|
const seg = part.trim();
|
||||||
|
if (!seg) continue;
|
||||||
|
const idx = seg.indexOf(':');
|
||||||
|
if (idx === -1) continue;
|
||||||
|
const userId = seg.slice(0, idx).trim();
|
||||||
|
const emoji = seg.slice(idx + 1).trim();
|
||||||
|
if (userId && emoji) map.set(userId, emoji);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
})(),
|
||||||
|
CLAIMER_EMOJI_FALLBACK: process.env.CLAIMER_EMOJI_FALLBACK || '🎫',
|
||||||
STAFF_T1_CATEGORY: process.env.STAFF_T1_CATEGORY || null,
|
STAFF_T1_CATEGORY: process.env.STAFF_T1_CATEGORY || null,
|
||||||
STAFF_T2_CATEGORY: process.env.STAFF_T2_CATEGORY || null,
|
STAFF_T2_CATEGORY: process.env.STAFF_T2_CATEGORY || null,
|
||||||
STAFF_T3_CATEGORY: process.env.STAFF_T3_CATEGORY || null,
|
STAFF_T3_CATEGORY: process.env.STAFF_T3_CATEGORY || null,
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ const {
|
|||||||
} = require('discord.js');
|
} = require('discord.js');
|
||||||
const { mongoose } = require('../db-connection');
|
const { mongoose } = require('../db-connection');
|
||||||
const { CONFIG } = require('../config');
|
const { CONFIG } = require('../config');
|
||||||
const { canRename, makeTicketName, minutesFromMs, getOrCreateTicketCategory, cleanupEmptyOverflowCategory, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets');
|
const { canRename, makeTicketName, minutesFromMs, getOrCreateTicketCategory, cleanupEmptyOverflowCategory, createDiscordTicketAsThread, checkTicketCreationRateLimit, getSenderLocal } = require('../services/tickets');
|
||||||
const { sendTicketClosedEmail } = require('../services/gmail');
|
const { sendTicketClosedEmail } = require('../services/gmail');
|
||||||
const { getTicketActionRow } = require('../utils/ticketComponents');
|
const { getTicketActionRow } = require('../utils/ticketComponents');
|
||||||
const { setEmailRouting } = require('../services/guildSettings');
|
const { setEmailRouting } = require('../services/guildSettings');
|
||||||
const { enqueueRename, enqueueMove } = require('../services/channelQueue');
|
const { enqueueRename } = require('../services/channelQueue');
|
||||||
const { createStaffChannel } = require('../services/staffChannel');
|
|
||||||
const { runEscalation, runDeescalation } = require('./commands');
|
const { runEscalation, runDeescalation } = require('./commands');
|
||||||
const { trackInteraction, trackError } = require('./analytics');
|
const { trackInteraction, trackError } = require('./analytics');
|
||||||
|
|
||||||
@@ -301,12 +300,30 @@ async function handleClaim(interaction, ticket) {
|
|||||||
freshTicket.claimedBy = claimerLabel;
|
freshTicket.claimedBy = claimerLabel;
|
||||||
freshTicket.claimerId = interaction.user.id;
|
freshTicket.claimerId = interaction.user.id;
|
||||||
|
|
||||||
|
// Resolve claimerEmoji from STAFF_EMOJIS map (fallback to CLAIMER_EMOJI_FALLBACK)
|
||||||
|
const claimerEmoji = CONFIG.STAFF_EMOJIS.get(interaction.user.id) || CONFIG.CLAIMER_EMOJI_FALLBACK;
|
||||||
|
// Resolve creatorNickname: displayName for Discord tickets, senderLocal for email tickets
|
||||||
|
let creatorNickname;
|
||||||
|
if (freshTicket.gmailThreadId.startsWith('discord-')) {
|
||||||
|
const creatorUserId = freshTicket.gmailThreadId.split('-').pop();
|
||||||
|
try {
|
||||||
|
const creatorMember = await guild.members.fetch(creatorUserId);
|
||||||
|
creatorNickname = creatorMember.displayName;
|
||||||
|
} catch {
|
||||||
|
creatorNickname = freshTicket.senderEmail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
creatorNickname = getSenderLocal(freshTicket.senderEmail);
|
||||||
|
}
|
||||||
|
|
||||||
const renameInfo = await canRename(freshTicket);
|
const renameInfo = await canRename(freshTicket);
|
||||||
if (renameInfo.ok) {
|
if (renameInfo.ok) {
|
||||||
const newName = makeTicketName(
|
const newName = makeTicketName(
|
||||||
{ escalated: !!freshTicket.escalated, claimed: true },
|
{ escalated: !!freshTicket.escalated, claimed: true },
|
||||||
freshTicket,
|
freshTicket,
|
||||||
guild
|
guild,
|
||||||
|
claimerEmoji,
|
||||||
|
creatorNickname
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await enqueueRename(interaction.channel, newName);
|
await enqueueRename(interaction.channel, newName);
|
||||||
@@ -321,25 +338,6 @@ async function handleClaim(interaction, ticket) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const staffCategoryId = CONFIG.STAFF_CATEGORIES.get(interaction.user.id);
|
|
||||||
if (staffCategoryId) await enqueueMove(interaction.channel, staffCategoryId);
|
|
||||||
const staffChan = await createStaffChannel(
|
|
||||||
interaction.guild,
|
|
||||||
freshTicket,
|
|
||||||
interaction.user.id,
|
|
||||||
interaction.channel.name
|
|
||||||
);
|
|
||||||
if (staffChan) {
|
|
||||||
await Ticket.updateOne(
|
|
||||||
{ gmailThreadId: freshTicket.gmailThreadId },
|
|
||||||
{ $set: { staffChannelId: staffChan.id } }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Staff channel / category (claim):', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseLabel = `Unclaim (${claimerLabel})`;
|
const baseLabel = `Unclaim (${claimerLabel})`;
|
||||||
const label = renameInfo.ok
|
const label = renameInfo.ok
|
||||||
? baseLabel
|
? baseLabel
|
||||||
@@ -371,15 +369,6 @@ async function handleClaim(interaction, ticket) {
|
|||||||
await interaction.followUp({ embeds: [claimEmbed] });
|
await interaction.followUp({ embeds: [claimEmbed] });
|
||||||
} else {
|
} else {
|
||||||
// Unclaim
|
// Unclaim
|
||||||
try {
|
|
||||||
if (freshTicket.staffChannelId) {
|
|
||||||
const { deleteStaffChannel } = require('../services/staffChannel');
|
|
||||||
await deleteStaffChannel(interaction.guild, freshTicket.staffChannelId);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Delete staff channel (unclaim):', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Ticket.updateOne(
|
await Ticket.updateOne(
|
||||||
{ gmailThreadId: freshTicket.gmailThreadId },
|
{ gmailThreadId: freshTicket.gmailThreadId },
|
||||||
{ $set: { claimedBy: null, claimerId: null, staffChannelId: null } }
|
{ $set: { claimedBy: null, claimerId: null, staffChannelId: null } }
|
||||||
@@ -390,17 +379,11 @@ async function handleClaim(interaction, ticket) {
|
|||||||
|
|
||||||
const renameInfo = await canRename(freshTicket);
|
const renameInfo = await canRename(freshTicket);
|
||||||
if (renameInfo.ok) {
|
if (renameInfo.ok) {
|
||||||
const currentName = interaction.channel.name.replace(/^[🟢🟡🔴]/, '');
|
|
||||||
try {
|
try {
|
||||||
await enqueueRename(interaction.channel, `🟢${currentName}`);
|
await enqueueRename(interaction.channel, `ticket-${freshTicket.ticketNumber}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Rename error (unclaim):', e);
|
console.error('Rename error (unclaim):', e);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
if (CONFIG.STAFF_T1_CATEGORY) await enqueueMove(interaction.channel, CONFIG.STAFF_T1_CATEGORY);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Move error (unclaim):', e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const unlockAtMs = Date.now() + renameInfo.waitMs;
|
const unlockAtMs = Date.now() + renameInfo.waitMs;
|
||||||
const unlockAtUnix = Math.floor(unlockAtMs / 1000);
|
const unlockAtUnix = Math.floor(unlockAtMs / 1000);
|
||||||
|
|||||||
@@ -33,14 +33,29 @@ function getSenderLocal(senderEmail) {
|
|||||||
return (senderEmail || 'unknown').split('@')[0].toLowerCase();
|
return (senderEmail || 'unknown').split('@')[0].toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTicketName({ escalated, claimed }, ticket, guild) {
|
function toDiscordSafeName(str) {
|
||||||
|
return str
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/[^\p{L}\p{N}\p{Emoji_Presentation}-]/gu, '')
|
||||||
|
.replace(/-{2,}/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '')
|
||||||
|
.slice(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// claimerEmoji and creatorNickname are only used in the claimed branch.
|
||||||
|
// Callers that do not pass them (e.g. escalation rename) get the unclaimed name as before.
|
||||||
|
function makeTicketName({ escalated, claimed }, ticket, guild, claimerEmoji, creatorNickname) {
|
||||||
const senderLocal = getSenderLocal(ticket.senderEmail);
|
const senderLocal = getSenderLocal(ticket.senderEmail);
|
||||||
const num = ticket.ticketNumber || 1;
|
const num = ticket.ticketNumber || 1;
|
||||||
if (escalated) {
|
if (escalated) {
|
||||||
return claimed
|
return (claimed && claimerEmoji && creatorNickname)
|
||||||
? `e-ticket-${senderLocal}-${num}`
|
? toDiscordSafeName(`e-${claimerEmoji}-${creatorNickname}-${num}`)
|
||||||
: `escalated-ticket-${senderLocal}-${num}`;
|
: `escalated-ticket-${senderLocal}-${num}`;
|
||||||
}
|
}
|
||||||
|
if (claimed && claimerEmoji && creatorNickname) {
|
||||||
|
return toDiscordSafeName(`${claimerEmoji}-${creatorNickname}-${num}`);
|
||||||
|
}
|
||||||
return `ticket-${senderLocal}-${num}`;
|
return `ticket-${senderLocal}-${num}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user