/** * Per-channel rename serialization with coalescing. * Renames route through utils/renamer.js (secondary bot token, RENAMER_BOT), * which has its own Discord-side rate bucket — no in-process throttle needed. * We serialize per channel so concurrent PATCHes don't land out of order, and * coalesce rapid successive calls so only the latest name is written. */ const { logWarn } = require('../services/debugLog'); const { renameChannel } = require('../utils/renamer'); // Per-channel: { chain: Promise, pendingName: string | null }. // enqueueRename updates pendingName synchronously (latest wins) and chains an // executeRename link. executeRename reads the latest pendingName at start. const renameChains = new Map(); async function executeRename(channel, entry) { const currentName = entry.pendingName; if (currentName == null) return; try { await renameChannel(channel.id, currentName); } finally { // Clear only if no newer call arrived during the PATCH. If pendingName // has changed, leave it — the link queued by that newer call picks it up. if (entry.pendingName === currentName) { entry.pendingName = null; } } } function enqueueRename(channel, newName) { let entry = renameChains.get(channel.id); if (!entry) { entry = { chain: Promise.resolve(), pendingName: newName }; renameChains.set(channel.id, entry); } else { entry.pendingName = newName; } const next = entry.chain.catch(() => {}).then(() => executeRename(channel, entry)); entry.chain = next; next.catch((err) => { logWarn('renameQueue', `Rename failed for ${channel.name}: ${err && err.message || err}`).catch(() => {}); }).finally(() => { if (renameChains.get(channel.id) === entry && entry.chain === next && entry.pendingName == null) { renameChains.delete(channel.id); } }); return next; } function enqueueMove(channel, categoryId) { return channel.setParent(categoryId, { lockPermissions: true }); } // Per-channel promise chain for send ordering and to prevent interleaving. const sendChains = new Map(); function enqueueSend(channel, ...args) { if (!channel || typeof channel.send !== 'function') { return Promise.reject(new Error('enqueueSend: invalid channel')); } const prev = sendChains.get(channel.id) || Promise.resolve(); const next = prev.catch(() => {}).then(() => channel.send(...args)); sendChains.set(channel.id, next); next.catch(() => {}).finally(() => { if (sendChains.get(channel.id) === next) sendChains.delete(channel.id); }); return next; } module.exports = { enqueueRename, enqueueMove, enqueueSend };