import { all, chunks, placeholders } from "./db";
/** Same handle grammar as the markdown reference linker. */
const MENTION = /(?:^|[^\w@/])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))/g;
const MAX_MENTIONS = 200;
export type MentionTarget = { id: string; name: string };
/** Lowercased handle as written (current or former name) to the account it names today. */
export type MentionMap = Map<string, MentionTarget>;
export function mentionHandles(texts: string[]): string[] {
const names = new Set<string>();
for (const text of texts) {
for (const match of text.matchAll(MENTION)) {
names.add(match[1].toLowerCase());
if (names.size >= MAX_MENTIONS) return [...names];
}
}
return [...names];
}
/**
* Resolves `@handle`s to accounts. Former names stay reserved for the renamed account forever
* (account_redirects; deleted accounts' names are retired), so an old handle names exactly one account
* and mentions keep pointing at the same person across renames, rendered under their current name.
*/
export async function resolveMentions(texts: string[]): Promise<MentionMap> {
const out: MentionMap = new Map();
const handles = mentionHandles(texts);
for (const group of chunks(handles)) {
const rows = await all<{ handle: string; id: string; name: string }>(
`SELECT LOWER(name) AS handle, id, name FROM accounts WHERE name IN (${placeholders(group.length)})`,
...group,
);
for (const row of rows) out.set(row.handle, { id: row.id, name: row.name });
}
const former = handles.filter((handle) => !out.has(handle));
for (const group of chunks(former)) {
const rows = await all<{ handle: string; id: string; name: string }>(
`SELECT LOWER(r.old_name) AS handle, a.id, a.name FROM account_redirects r JOIN accounts a ON a.id = r.account_id