import { createHash } from "node:crypto" ;
import { formatIMessageChatTarget } from "../targets.js" ;
type SelfChatCacheKeyParts = {
accountId: string;
sender: string;
isGroup: boolean ;
chatId?: number;
};
export type SelfChatLookup = SelfChatCacheKeyParts & {
text?: string;
createdAt?: number;
};
export type SelfChatCache = {
remember: (lookup: SelfChatLookup) => void ;
has: (lookup: SelfChatLookup) => boolean ;
};
const SELF_CHAT_TTL_MS = 10 _000 ;
const MAX_SELF_CHAT_CACHE_ENTRIES = 512 ;
const CLEANUP_MIN_INTERVAL_MS = 1 _000 ;
function normalizeText(text: string | undefined): string | null {
if (!text) {
return null ;
}
const normalized = text.replace(/\r\n?/g, "\n" ).trim();
return normalized ? normalized : null ;
}
function isUsableTimestamp(createdAt: number | undefined): createdAt is number {
return typeof createdAt === "number" && Number.isFinite(createdAt);
}
function digestText(text: string): string {
return createHash("sha256" ).update(text).digest("hex" );
}
function buildScope(parts: SelfChatCacheKeyParts): string {
if (!parts.isGroup) {
return `${parts.accountId}:imessage:${parts.sender}`;
}
const chatTarget = formatIMessageChatTarget(parts.chatId) || "chat_id:unknown" ;
return `${parts.accountId}:${chatTarget}:imessage:${parts.sender}`;
}
class DefaultSelfChatCache implements SelfChatCache {
private cache = new Map<string, number>();
private lastCleanupAt = 0 ;
private buildKey(lookup: SelfChatLookup): string | null {
const text = normalizeText(lookup.text);
if (!text || !isUsableTimestamp(lookup.createdAt)) {
return null ;
}
return `${buildScope(lookup)}:${lookup.createdAt}:${digestText(text)}`;
}
remember(lookup: SelfChatLookup): void {
const key = this .buildKey(lookup);
if (!key) {
return ;
}
this .cache.set(key, Date.now());
this .maybeCleanup();
}
has(lookup: SelfChatLookup): boolean {
this .maybeCleanup();
const key = this .buildKey(lookup);
if (!key) {
return false ;
}
const timestamp = this .cache.get(key);
return typeof timestamp === "number" && Date.now() - timestamp <= SELF_CHAT_TTL_MS;
}
private maybeCleanup(): void {
const now = Date.now();
if (now - this .lastCleanupAt < CLEANUP_MIN_INTERVAL_MS) {
return ;
}
this .lastCleanupAt = now;
for (const [key, timestamp] of this .cache.entries()) {
if (now - timestamp > SELF_CHAT_TTL_MS) {
this .cache.delete (key);
}
}
while (this .cache.size > MAX_SELF_CHAT_CACHE_ENTRIES) {
const oldestKey = this .cache.keys().next().value;
if (typeof oldestKey !== "string" ) {
break ;
}
this .cache.delete (oldestKey);
}
}
}
export function createSelfChatCache(): SelfChatCache {
return new DefaultSelfChatCache();
}
Messung V0.5 in Prozent C=95 H=99 G=96
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland