/** * Staff discussion threads — creates a private thread on each ticket channel * for staff-only communication. * * Notes: * - The bot requires CREATE_PRIVATE_THREADS and SEND_MESSAGES_IN_THREADS * permissions on every ticket category. * - Private threads (ChannelType.PrivateThread) require the server to have Community features * OR the channel to be in a server with Boost level that unlocks private * threads. If thread creation fails with code 50024 or 160004, a warning * is logged via logWarn. * - invitable: false means only staff with MANAGE_THREADS can add additional * members — this is intentional for privacy. * - guild.members.fetch() in addRoleMembersToThread can be slow on large * servers. The 300ms delay between adds avoids the thread member add rate * limit (approximately 5/second). */ const { ChannelType } = require('discord.js'); const { CONFIG } = require('../config'); const { logError, logWarn } = require('./debugLog'); /** * Create a private staff thread on a ticket channel. * @param {import('discord.js').TextChannel} channel * @param {import('discord.js').Client} client * @returns {Promise} */ async function createStaffThread(channel, client) { if (!CONFIG.STAFF_THREAD_ENABLED) return null; try { const threadName = CONFIG.STAFF_THREAD_NAME.slice(0, 100); const thread = await channel.threads.create({ name: threadName, type: ChannelType.PrivateThread, invitable: false, reason: 'Staff discussion thread for ticket' }); if (CONFIG.STAFF_THREAD_AUTO_ADD_ROLE && CONFIG.STAFF_THREAD_ROLE_ID) { await addRoleMembersToThread(thread, channel.guild, client); } return thread; } catch (err) { // Detect permission / channel type errors if (err.code === 50024 || err.code === 160004) { logWarn('staffThread', `Cannot create private thread in ${channel.name}: server may lack Community features or required boost level (code ${err.code}).`).catch(() => {}); } await logError('staffThread:create', err, null, client).catch(() => {}); return null; } } /** * Add all members of the staff role to the thread. */ async function addRoleMembersToThread(thread, guild, client) { try { const role = await guild.roles.fetch(CONFIG.STAFF_THREAD_ROLE_ID).catch(() => null); if (!role) return; await guild.members.fetch(); const members = guild.members.cache.filter(m => m.roles.cache.has(CONFIG.STAFF_THREAD_ROLE_ID) && !m.user.bot ); for (const [, member] of members) { await thread.members.add(member.id).catch(() => {}); await new Promise(r => setTimeout(r, 300)); } } catch (err) { await logError('staffThread:addMembers', err, null, client).catch(() => {}); } } /** * Add a single member to the staff thread for a ticket channel. * Call this when a ticket is claimed. */ async function addMemberToStaffThread(channel, memberId) { if (!CONFIG.STAFF_THREAD_ENABLED) return; try { const threads = await channel.threads.fetchActive(); const staffThread = threads.threads.find(t => t.name === CONFIG.STAFF_THREAD_NAME && t.type === ChannelType.PrivateThread ); if (!staffThread) return; await staffThread.members.add(memberId); } catch { // non-critical, ignore } } module.exports = { createStaffThread, addMemberToStaffThread };