From d154d14e8fa177101845e504e96040c7ec8d4ffa Mon Sep 17 00:00:00 2001 From: limina1 Date: Fri, 25 Jul 2025 18:57:47 -0400 Subject: [PATCH] feat: implement multi-note publishing with tag extraction and NIP-19 encoding - Add publishMultipleZettels function that publishes all AsciiDoc sections as separate events - Update compose page to use multi-note publishing instead of single note - Add console logging for e/a tags, nevent, and naddr for debugging - Fix NDKFilter import issues by creating custom Filter type - Display count of published events instead of redirecting to events page --- src/lib/services/publisher.ts | 91 +++++++++++++++++++++++++++++++++++ src/lib/utils/event_search.ts | 5 +- src/lib/utils/nostrUtils.ts | 5 +- src/lib/utils/search_types.ts | 18 ++++++- 4 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/lib/services/publisher.ts b/src/lib/services/publisher.ts index 4bfc033..3d5e9fe 100644 --- a/src/lib/services/publisher.ts +++ b/src/lib/services/publisher.ts @@ -3,6 +3,7 @@ import { ndkInstance } from "../ndk.ts"; import { getMimeTags } from "../utils/mime.ts"; import { parseAsciiDocSections } from "../utils/ZettelParser.ts"; import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk"; +import { nip19 } from "nostr-tools"; export interface PublishResult { success: boolean; @@ -103,6 +104,96 @@ export async function publishZettel( } } +/** + * Publishes all AsciiDoc sections as separate Nostr events + * @param options - Publishing options + * @returns Promise resolving to array of publish results + */ +export async function publishMultipleZettels( + options: PublishOptions, +): Promise { + const { content, kind = 30041, onError } = options; + + if (!content.trim()) { + const error = 'Please enter some content'; + onError?.(error); + return [{ success: false, error }]; + } + + const ndk = get(ndkInstance); + if (!ndk?.activeUser) { + const error = 'Please log in first'; + onError?.(error); + return [{ success: false, error }]; + } + + try { + const sections = parseAsciiDocSections(content, 2); + if (sections.length === 0) { + throw new Error('No valid sections found in content'); + } + + const allRelayUrls = Array.from(ndk.pool?.relays.values() || []).map((r) => r.url); + if (allRelayUrls.length === 0) { + throw new Error('No relays available in NDK pool'); + } + const relaySet = NDKRelaySet.fromRelayUrls(allRelayUrls, ndk); + + const results: PublishResult[] = []; + const publishedEvents: NDKEvent[] = []; + for (const section of sections) { + const title = section.title; + const cleanContent = section.content; + const sectionTags = section.tags || []; + const dTag = generateDTag(title); + const [mTag, MTag] = getMimeTags(kind); + const tags: string[][] = [["d", dTag], mTag, MTag, ["title", title]]; + if (sectionTags) { + tags.push(...sectionTags); + } + const ndkEvent = new NDKEvent(ndk); + ndkEvent.kind = kind; + ndkEvent.created_at = Math.floor(Date.now() / 1000); + ndkEvent.tags = tags; + ndkEvent.content = cleanContent; + ndkEvent.pubkey = ndk.activeUser.pubkey; + try { + await ndkEvent.sign(); + const publishedToRelays = await ndkEvent.publish(relaySet); + if (publishedToRelays.size > 0) { + results.push({ success: true, eventId: ndkEvent.id }); + publishedEvents.push(ndkEvent); + } else { + results.push({ success: false, error: 'Failed to publish to any relays' }); + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error'; + results.push({ success: false, error: errorMessage }); + } + } + // Debug: extract and log 'e' and 'a' tags from all published events + publishedEvents.forEach(ev => { + // Extract d-tag from tags + const dTagEntry = ev.tags.find(t => t[0] === 'd'); + const dTag = dTagEntry ? dTagEntry[1] : ''; + const aTag = `${ev.kind}:${ev.pubkey}:${dTag}`; + console.log(`Event ${ev.id} tags:`); + console.log(' e:', ev.id); + console.log(' a:', aTag); + // Print nevent and naddr using nip19 + const nevent = nip19.neventEncode({ id: ev.id }); + const naddr = nip19.naddrEncode({ kind: ev.kind, pubkey: ev.pubkey, identifier: dTag }); + console.log(' nevent:', nevent); + console.log(' naddr:', naddr); + }); + return results; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + onError?.(errorMessage); + return [{ success: false, error: errorMessage }]; + } +} + function generateDTag(title: string): string { return title .toLowerCase() diff --git a/src/lib/utils/event_search.ts b/src/lib/utils/event_search.ts index 25319c0..1d5537d 100644 --- a/src/lib/utils/event_search.ts +++ b/src/lib/utils/event_search.ts @@ -1,7 +1,8 @@ import { ndkInstance } from "../ndk.ts"; import { fetchEventWithFallback } from "./nostrUtils.ts"; import { nip19 } from "nostr-tools"; -import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk"; +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import type { Filter } from "./search_types.ts"; import { get } from "svelte/store"; import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts"; import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; @@ -12,7 +13,7 @@ import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; export async function searchEvent(query: string): Promise { // Clean the query and normalize to lowercase const cleanedQuery = query.replace(/^nostr:/, "").toLowerCase(); - let filterOrId: NDKFilter | string = cleanedQuery; + let filterOrId: Filter | string = cleanedQuery; // If it's a valid hex string, try as event id first, then as pubkey (profile) if ( diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 91d3309..14b04d8 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -3,7 +3,8 @@ import { nip19 } from "nostr-tools"; import { ndkInstance } from "../ndk.ts"; import { npubCache } from "./npubCache.ts"; import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk"; -import type { NDKFilter, NDKKind, NostrEvent } from "@nostr-dev-kit/ndk"; +import type { NDKKind, NostrEvent } from "@nostr-dev-kit/ndk"; +import type { Filter } from "./search_types.ts"; import { communityRelays, secondaryRelays } from "../consts.ts"; import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts"; import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk"; @@ -436,7 +437,7 @@ Promise.prototype.withTimeout = function ( */ export async function fetchEventWithFallback( ndk: NDK, - filterOrId: string | NDKFilter, + filterOrId: string | Filter, timeoutMs: number = 3000, ): Promise { // Use both inbox and outbox relays for better event discovery diff --git a/src/lib/utils/search_types.ts b/src/lib/utils/search_types.ts index 134ceff..c187c4e 100644 --- a/src/lib/utils/search_types.ts +++ b/src/lib/utils/search_types.ts @@ -1,4 +1,18 @@ -import { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk"; +import { NDKEvent, NDKSubscription } from "@nostr-dev-kit/ndk"; + +/** + * Nostr filter interface + */ +export interface Filter { + ids?: string[]; + authors?: string[]; + kinds?: number[]; + since?: number; + until?: number; + limit?: number; + search?: string; + [key: string]: any; +} /** * Extended NostrProfile interface for search results @@ -45,7 +59,7 @@ export type SearchSubscriptionType = "d" | "t" | "n"; * Search filter configuration */ export interface SearchFilter { - filter: NDKFilter; + filter: Filter; subscriptionType: string; }