import { Message, ShouldNeverHappenError } from "./data-types"; import Globals from "./globals"; interface MessagesWithMetadata { messages: Map; totalCharacters: number; lastUsed: Date; } export default class MessageRAMCache { // g#guildId/c#channelId -> list of messages private data = new Map(); private totalCharacters: number; // Removes the least recently used channels until the cache is under the max cached character limit private cullLeastUsedChannelsIfNeeded(): void { if (this.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_SIZE) { let entries = Array.from(this.data.entries()) .map(([ key, value ]) => ({ channelId: key, value: value })) .sort((a, b) => b.value.lastUsed.getTime() - a.value.lastUsed.getTime()); while (this.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_TOTAL_CHARACTERS) { let entry = entries.pop(); if (!entry) throw new ShouldNeverHappenError('could not find an entry to clear'); this.data.delete(entry.channelId); this.totalCharacters -= entry.value.totalCharacters; } } } // Removes the oldest messages from the channel until the channel is under the max cached character limit private trimOldChannelMessagesIfNeeded(guildId: number, channelId: string) { let id = `g#${guildId}/c#${channelId}`; let value = this.data.get(id); if (!value) return; if (value.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_CHARACTERS) { let messages = Array.from(value.messages.values()) .sort((a, b) => a.sent.getTime() - b.sent.getTime()); while (value.totalCharacters > Globals.MAX_RAM_CACHED_MESSAGES_CHANNEL_CHARACTERS) { let message = messages.shift(); if (!message) throw new ShouldNeverHappenError('could not find a message to clear'); value.messages.delete(message.id); value.totalCharacters -= message.text?.length ?? 0; this.totalCharacters -= message.text?.length ?? 0; } } } upsertMessages(guildId: number, channelId: string, messages: Message[]) { let id = `g#${guildId}/c#${channelId}`; if (!this.data.has(id)) { this.data.set(id, { messages: new Map(), totalCharacters: 0, lastUsed: new Date() }); } let value = this.data.get(id); if (!value) throw new ShouldNeverHappenError('unable to get message map'); for (let message of messages) { value.messages.set(message.id, message); value.totalCharacters += message.text?.length ?? 0; this.totalCharacters += message.text?.length ?? 0; } this.cullLeastUsedChannelsIfNeeded(); this.trimOldChannelMessagesIfNeeded(guildId, channelId); } deleteMessages(guildId: number, channelId: string, messages: Message[]) { let id = `g#${guildId}/c#${channelId}`; if (!this.data.has(id)) { return; } let value = this.data.get(id); if (!value) throw new ShouldNeverHappenError('unable to get message map'); for (let message of messages) { let deletedMessage = value.messages.get(message.id); if (!deletedMessage) continue; value.messages.delete(message.id); } } fetchRecentMessages(guildId: number, channelId: string, number: number): Message[] | null { let id = `g#${guildId}/c#${channelId}`; if (!this.data.has(id)) { return null; } let value = this.data.get(id); if (!value) throw new ShouldNeverHappenError('unable to get message map'); value.lastUsed = new Date(); let allRecentMessages = Array.from(value.messages.values()).sort((a, b) => a.sent.getTime() - b.sent.getTime()); let start = Math.min(allRecentMessages.length - number, 0); return allRecentMessages.slice(start, 0); } }