Files
broccolini-bot/services/channelQueue.js
indifferentketchup 69c247ed1b huge changes
2026-04-07 01:43:06 -05:00

88 lines
2.4 KiB
JavaScript

/**
* Per-channel rename rate limiting with queue.
* Discord allows 2 channel renames per 10 minutes per channel.
* We use a 9-minute window for safety margin.
*/
const RENAME_WINDOW_MS = 9 * 60 * 1000;
const RENAME_LIMIT = 2;
// Per-channel state: { count, windowStart, queue: [{newName, resolve, reject}], processing }
const renameState = new Map();
function getOrInitState(channelId) {
let state = renameState.get(channelId);
if (!state) {
state = { count: 0, windowStart: 0, queue: [], processing: false };
renameState.set(channelId, state);
}
return state;
}
async function executeRename(channel, newName) {
await channel.setName(newName);
}
function processQueue(channel, state) {
if (state.queue.length === 0 || state.processing) return;
const now = Date.now();
const timeUntilExpiry = (state.windowStart + RENAME_WINDOW_MS) - now;
if (timeUntilExpiry > 0) {
state.processing = true;
setTimeout(async () => {
state.processing = false;
// New window
if (state.queue.length > 3) {
const { logWarn } = require('../services/debugLog');
logWarn('renameQueue', `Channel ${channel.name} has ${state.queue.length} renames queued`).catch(() => {});
}
const item = state.queue.shift();
if (!item) return;
state.count = 1;
state.windowStart = Date.now();
try {
await executeRename(channel, item.newName);
item.resolve();
} catch (err) {
item.reject(err);
}
// Continue processing remaining queue items
processQueue(channel, state);
}, timeUntilExpiry);
}
}
function enqueueRename(channel, newName) {
return new Promise((resolve, reject) => {
const state = getOrInitState(channel.id);
const now = Date.now();
// Window expired — reset
if (now - state.windowStart >= RENAME_WINDOW_MS) {
state.count = 1;
state.windowStart = now;
executeRename(channel, newName).then(resolve).catch(reject);
return;
}
// Within window and under limit
if (state.count < RENAME_LIMIT) {
state.count++;
executeRename(channel, newName).then(resolve).catch(reject);
return;
}
// At limit — queue it
state.queue.push({ newName, resolve, reject });
processQueue(channel, state);
});
}
function enqueueMove(channel, categoryId) {
return channel.setParent(categoryId, { lockPermissions: true });
}
module.exports = { enqueueRename, enqueueMove };