Browse Source

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
master
limina1 8 months ago
parent
commit
d154d14e8f
  1. 91
      src/lib/services/publisher.ts
  2. 5
      src/lib/utils/event_search.ts
  3. 5
      src/lib/utils/nostrUtils.ts
  4. 18
      src/lib/utils/search_types.ts

91
src/lib/services/publisher.ts

@ -3,6 +3,7 @@ import { ndkInstance } from "../ndk.ts";
import { getMimeTags } from "../utils/mime.ts"; import { getMimeTags } from "../utils/mime.ts";
import { parseAsciiDocSections } from "../utils/ZettelParser.ts"; import { parseAsciiDocSections } from "../utils/ZettelParser.ts";
import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk"; import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk";
import { nip19 } from "nostr-tools";
export interface PublishResult { export interface PublishResult {
success: boolean; 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<PublishResult[]> {
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 { function generateDTag(title: string): string {
return title return title
.toLowerCase() .toLowerCase()

5
src/lib/utils/event_search.ts

@ -1,7 +1,8 @@
import { ndkInstance } from "../ndk.ts"; import { ndkInstance } from "../ndk.ts";
import { fetchEventWithFallback } from "./nostrUtils.ts"; import { fetchEventWithFallback } from "./nostrUtils.ts";
import { nip19 } from "nostr-tools"; 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 { get } from "svelte/store";
import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts"; import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts";
import { TIMEOUTS, VALIDATION } from "./search_constants.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<NDKEvent | null> { export async function searchEvent(query: string): Promise<NDKEvent | null> {
// Clean the query and normalize to lowercase // Clean the query and normalize to lowercase
const cleanedQuery = query.replace(/^nostr:/, "").toLowerCase(); 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 it's a valid hex string, try as event id first, then as pubkey (profile)
if ( if (

5
src/lib/utils/nostrUtils.ts

@ -3,7 +3,8 @@ import { nip19 } from "nostr-tools";
import { ndkInstance } from "../ndk.ts"; import { ndkInstance } from "../ndk.ts";
import { npubCache } from "./npubCache.ts"; import { npubCache } from "./npubCache.ts";
import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk"; 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 { communityRelays, secondaryRelays } from "../consts.ts";
import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts"; import { activeInboxRelays, activeOutboxRelays } from "../ndk.ts";
import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk"; import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk";
@ -436,7 +437,7 @@ Promise.prototype.withTimeout = function <T>(
*/ */
export async function fetchEventWithFallback( export async function fetchEventWithFallback(
ndk: NDK, ndk: NDK,
filterOrId: string | NDKFilter<NDKKind>, filterOrId: string | Filter,
timeoutMs: number = 3000, timeoutMs: number = 3000,
): Promise<NDKEvent | null> { ): Promise<NDKEvent | null> {
// Use both inbox and outbox relays for better event discovery // Use both inbox and outbox relays for better event discovery

18
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 * Extended NostrProfile interface for search results
@ -45,7 +59,7 @@ export type SearchSubscriptionType = "d" | "t" | "n";
* Search filter configuration * Search filter configuration
*/ */
export interface SearchFilter { export interface SearchFilter {
filter: NDKFilter; filter: Filter;
subscriptionType: string; subscriptionType: string;
} }

Loading…
Cancel
Save