Add NIP-50 thread search helper
This commit is contained in:
parent
5887a5705e
commit
043be1d3fd
1 changed files with 71 additions and 0 deletions
71
src/lib/search.ts
Normal file
71
src/lib/search.ts
Normal 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);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue