Files
openclaw/src/discord/directory-cache.ts

112 lines
3.1 KiB
TypeScript

import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/account-id.js";
const DISCORD_DIRECTORY_CACHE_MAX_ENTRIES = 4000;
const DISCORD_DISCRIMINATOR_SUFFIX = /#\d{4}$/;
const DIRECTORY_HANDLE_CACHE = new Map<string, Map<string, string>>();
function normalizeAccountCacheKey(accountId?: string | null): string {
const normalized = normalizeAccountId(accountId ?? DEFAULT_ACCOUNT_ID);
return normalized || DEFAULT_ACCOUNT_ID;
}
function normalizeSnowflake(value: string | number | bigint): string | null {
const text = String(value ?? "").trim();
if (!/^\d+$/.test(text)) {
return null;
}
return text;
}
function normalizeHandleKey(raw: string): string | null {
let handle = raw.trim();
if (!handle) {
return null;
}
if (handle.startsWith("@")) {
handle = handle.slice(1).trim();
}
if (!handle || /\s/.test(handle)) {
return null;
}
return handle.toLowerCase();
}
function ensureAccountCache(accountId?: string | null): Map<string, string> {
const cacheKey = normalizeAccountCacheKey(accountId);
const existing = DIRECTORY_HANDLE_CACHE.get(cacheKey);
if (existing) {
return existing;
}
const created = new Map<string, string>();
DIRECTORY_HANDLE_CACHE.set(cacheKey, created);
return created;
}
function setCacheEntry(cache: Map<string, string>, key: string, userId: string): void {
if (cache.has(key)) {
cache.delete(key);
}
cache.set(key, userId);
if (cache.size <= DISCORD_DIRECTORY_CACHE_MAX_ENTRIES) {
return;
}
const oldest = cache.keys().next();
if (!oldest.done) {
cache.delete(oldest.value);
}
}
export function rememberDiscordDirectoryUser(params: {
accountId?: string | null;
userId: string | number | bigint;
handles: Array<string | null | undefined>;
}): void {
const userId = normalizeSnowflake(params.userId);
if (!userId) {
return;
}
const cache = ensureAccountCache(params.accountId);
for (const candidate of params.handles) {
if (typeof candidate !== "string") {
continue;
}
const handle = normalizeHandleKey(candidate);
if (!handle) {
continue;
}
setCacheEntry(cache, handle, userId);
const withoutDiscriminator = handle.replace(DISCORD_DISCRIMINATOR_SUFFIX, "");
if (withoutDiscriminator && withoutDiscriminator !== handle) {
setCacheEntry(cache, withoutDiscriminator, userId);
}
}
}
export function resolveDiscordDirectoryUserId(params: {
accountId?: string | null;
handle: string;
}): string | undefined {
const cache = DIRECTORY_HANDLE_CACHE.get(normalizeAccountCacheKey(params.accountId));
if (!cache) {
return undefined;
}
const handle = normalizeHandleKey(params.handle);
if (!handle) {
return undefined;
}
const direct = cache.get(handle);
if (direct) {
return direct;
}
const withoutDiscriminator = handle.replace(DISCORD_DISCRIMINATOR_SUFFIX, "");
if (!withoutDiscriminator || withoutDiscriminator === handle) {
return undefined;
}
return cache.get(withoutDiscriminator);
}
export function __resetDiscordDirectoryCacheForTest(): void {
DIRECTORY_HANDLE_CACHE.clear();
}