Add NIP-50 thread search helper

This commit is contained in:
dtonon 2026-06-09 21:23:13 +01:00
parent 5887a5705e
commit 043be1d3fd

71
src/lib/search.ts Normal file
View file

@ -0,0 +1,71 @@
import type { Event } from "@nostr/tools/core";
import type { Filter } from "@nostr/tools/filter";
import { queryForum } from "$lib/relay";
import { GROUP_ID, MODE } from "$lib/config";
import { groupsStore } from "$lib/groups.svelte";
export type SearchResult = {
threadId: string;
title: string;
snippet: string;
matchKind: "thread" | "reply";
createdAt: number;
};
function snippetOf(content: string): string {
const flat = content.replace(/\s+/g, " ").trim();
return flat.length > 140 ? flat.slice(0, 140) + "…" : flat;
}
// NIP-50 search over the forum relay: thread OPs (kind 11) and replies
// (kind 1111), deduped by thread. Simple mode scopes to the single group;
// full mode spans every visible room.
export async function searchThreads(query: string): Promise<SearchResult[]> {
const filter: Filter = { kinds: [11, 1111], search: query, limit: 30 };
if (MODE === "simple") filter["#h"] = [GROUP_ID];
else if (groupsStore.list.length > 0)
filter["#h"] = groupsStore.list.map((g) => g.id);
const events = await queryForum(filter, { label: "search" });
const byThread = new Map<string, SearchResult>();
const opless: SearchResult[] = [];
for (const e of events) {
if (e.kind === 11) {
const existing = byThread.get(e.id);
// An OP match wins over a reply match on the same thread
if (existing && existing.matchKind === "thread") continue;
byThread.set(e.id, {
threadId: e.id,
title: e.tags.find((t) => t[0] === "title")?.[1] ?? "(untitled)",
snippet: snippetOf(e.content),
matchKind: "thread",
createdAt: e.created_at,
});
} else {
const root = e.tags.find((t) => t[0] === "E")?.[1];
if (!root || byThread.has(root)) continue;
const r: SearchResult = {
threadId: root,
title: "",
snippet: snippetOf(e.content),
matchKind: "reply",
createdAt: e.created_at,
};
byThread.set(root, r);
opless.push(r);
}
}
// Backfill titles for reply-only matches; the group relay truncates
// multi-id queries, so fetch each OP on its own
await Promise.all(
opless.map(async (r) => {
const ops = await queryForum({ kinds: [11], ids: [r.threadId] });
const op: Event | undefined = ops[0];
r.title = op?.tags.find((t) => t[0] === "title")?.[1] ?? "(untitled)";
}),
);
return [...byThread.values()].sort((a, b) => b.createdAt - a.createdAt);
}