149 lines
3.4 KiB
JavaScript
149 lines
3.4 KiB
JavaScript
/**
|
|
* In-memory counter store with TTL windows for pattern detection.
|
|
* Windows: 'today' resets at midnight, 'week' resets Monday 00:00, 'month' resets 1st 00:00.
|
|
*/
|
|
|
|
// store[window][namespace][key] = count
|
|
const store = {
|
|
today: new Map(),
|
|
week: new Map(),
|
|
month: new Map()
|
|
};
|
|
|
|
function getNamespaceMap(window, namespace) {
|
|
const windowMap = store[window];
|
|
if (!windowMap) return null;
|
|
if (!windowMap.has(namespace)) windowMap.set(namespace, new Map());
|
|
return windowMap.get(namespace);
|
|
}
|
|
|
|
function increment(namespace, key, window) {
|
|
const map = getNamespaceMap(window, namespace);
|
|
if (!map) return;
|
|
map.set(key, (map.get(key) || 0) + 1);
|
|
}
|
|
|
|
function get(namespace, key, window) {
|
|
const map = getNamespaceMap(window, namespace);
|
|
if (!map) return 0;
|
|
return map.get(key) || 0;
|
|
}
|
|
|
|
function reset(namespace, window) {
|
|
const windowMap = store[window];
|
|
if (!windowMap) return;
|
|
windowMap.delete(namespace);
|
|
}
|
|
|
|
function getAll(namespace, window) {
|
|
const map = getNamespaceMap(window, namespace);
|
|
if (!map) return new Map();
|
|
return new Map(map);
|
|
}
|
|
|
|
// --- Scheduled resets ---
|
|
|
|
function msUntilNextMidnight() {
|
|
const now = new Date();
|
|
const next = new Date(now);
|
|
next.setHours(24, 0, 0, 0);
|
|
return next.getTime() - now.getTime();
|
|
}
|
|
|
|
function msUntilNextMonday() {
|
|
const now = new Date();
|
|
const day = now.getDay(); // 0=Sun
|
|
const daysUntilMonday = day === 0 ? 1 : (8 - day);
|
|
const next = new Date(now);
|
|
next.setDate(now.getDate() + daysUntilMonday);
|
|
next.setHours(0, 0, 0, 0);
|
|
return next.getTime() - now.getTime();
|
|
}
|
|
|
|
function msUntilNextMonth() {
|
|
const now = new Date();
|
|
const next = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0, 0);
|
|
return next.getTime() - now.getTime();
|
|
}
|
|
|
|
// Callbacks to run on daily reset (e.g. clear firedToday in patternChecker)
|
|
const dailyResetCallbacks = [];
|
|
|
|
function onDailyReset(fn) {
|
|
dailyResetCallbacks.push(fn);
|
|
}
|
|
|
|
function scheduleDailyReset() {
|
|
setTimeout(() => {
|
|
store.today = new Map();
|
|
for (const fn of dailyResetCallbacks) {
|
|
try { fn(); } catch (_) {}
|
|
}
|
|
scheduleDailyReset();
|
|
}, msUntilNextMidnight());
|
|
}
|
|
|
|
function scheduleWeeklyReset() {
|
|
setTimeout(() => {
|
|
store.week = new Map();
|
|
scheduleWeeklyReset();
|
|
}, msUntilNextMonday());
|
|
}
|
|
|
|
function scheduleMonthlyReset() {
|
|
setTimeout(() => {
|
|
store.month = new Map();
|
|
scheduleMonthlyReset();
|
|
}, msUntilNextMonth());
|
|
}
|
|
|
|
function scheduleResets() {
|
|
scheduleDailyReset();
|
|
scheduleWeeklyReset();
|
|
scheduleMonthlyReset();
|
|
}
|
|
|
|
// --- Cooldown store ---
|
|
const cooldowns = new Map();
|
|
|
|
function setCooldown(key) {
|
|
cooldowns.set(key, Date.now());
|
|
}
|
|
|
|
function isOnCooldown(key, cooldownMinutes) {
|
|
const last = cooldowns.get(key);
|
|
if (!last) return false;
|
|
return (Date.now() - last) < cooldownMinutes * 60 * 1000;
|
|
}
|
|
|
|
// --- Staff last-seen tracker (fallback for missing presence intent) ---
|
|
const staffLastSeen = new Map();
|
|
|
|
function updateStaffLastSeen(staffId) {
|
|
staffLastSeen.set(staffId, Date.now());
|
|
}
|
|
|
|
function getStaffLastSeen(staffId) {
|
|
return staffLastSeen.get(staffId) || null;
|
|
}
|
|
|
|
function isStaffRecentlyActive(staffId, withinMinutes = 60) {
|
|
const last = staffLastSeen.get(staffId);
|
|
if (!last) return false;
|
|
return (Date.now() - last) < withinMinutes * 60 * 1000;
|
|
}
|
|
|
|
module.exports = {
|
|
increment,
|
|
get,
|
|
reset,
|
|
getAll,
|
|
scheduleResets,
|
|
onDailyReset,
|
|
setCooldown,
|
|
isOnCooldown,
|
|
updateStaffLastSeen,
|
|
getStaffLastSeen,
|
|
isStaffRecentlyActive
|
|
};
|