74 lines
2.6 KiB
JavaScript
74 lines
2.6 KiB
JavaScript
/**
|
|
* 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 };
|