import { NostrEvent, NostrFilter, NPool, NRelay1 } from "@nostrify/nostrify"; import { nip19 } from "@nostr/tools"; import { lookupUsernameByPubkey } from "./directory.ts"; import config from "./config.ts"; const relayPool = new NPool({ open: (url) => new NRelay1(url), // deno-lint-ignore require-await reqRouter: async (filters) => new Map( config.relay_urls.map((url) => [url, filters]), ), // deno-lint-ignore require-await eventRouter: async (_event) => [], }); async function fetchWithTimeout(filters: NostrFilter[]) { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("relay timeout")), config.query_timeout) ); const eventsPromise = relayPool.query(filters); const events = await Promise.race([eventsPromise, timeoutPromise]); return events; } export async function fetchReplaceableEvent( pubkey: string, identifier: string, ) { let events = await fetchWithTimeout([{ authors: [pubkey], kinds: [30023], "#d": [identifier], limit: 1, }]) as NostrEvent[]; if (events.length > 0) { return events[0]; } else { events = await fetchWithTimeout([{ authors: [pubkey], kinds: [30024], "#d": [identifier], limit: 1, }]) as NostrEvent[]; return events.length > 0 ? events[0] : null; } } export async function fetchArticlesByAuthor(pubkey: string) { const events = await fetchWithTimeout([{ authors: [pubkey], kinds: [30023], limit: 10, }]) as NostrEvent[]; return events; } export async function fetchProfileEvent(pubkey: string) { const events = await fetchWithTimeout([{ authors: [pubkey], kinds: [0], limit: 1, }]) as NostrEvent[]; return events.length > 0 ? events[0] : null; } export async function nostrUriToUrl(uri: string): Promise { const bech32 = uri.replace(/^nostr:/, ""); if (bech32.match(/^(naddr|nprofile|npub)/)) { try { const r = nip19.decode(bech32); let username; switch (r.type) { case "naddr": username = await lookupUsernameByPubkey(r.data.pubkey); if (username) return `/${bech32}`; break; case "nprofile": username = await lookupUsernameByPubkey(r.data.pubkey); if (username) return `/@${username}`; break; case "npub": username = await lookupUsernameByPubkey(r.data); if (username) return `/@${username}`; break; } } catch (e) { console.error(e); } } return `${config.njump_url}/${bech32}`; } export async function replaceNostrUris(markdown: string): Promise { const nostrUriRegex = /\((nostr:|nprofile|naddr|nevent|nrelay|npub)[a-z0-9]+\)/g; const matches = markdown.match(nostrUriRegex); if (!matches) return markdown; for (const match of matches) { const uri = match.slice(1, -1); const url = await nostrUriToUrl(uri); markdown = markdown.replace(match, `(${url})`); } return markdown; }