commit 23a87521e2c97476c01d203854eb5b7ce4a533df
parent 085403d8ed7a103fd54bbf39e1089929dbf41e4b
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Fri, 8 Aug 2025 23:20:31 +0000
utils-nostr: add `comment` nip-22 schemas, types, event parsing utils
Diffstat:
7 files changed, 97 insertions(+), 6 deletions(-)
diff --git a/utils-nostr/src/events/comment/lib.ts b/utils-nostr/src/events/comment/lib.ts
@@ -0,0 +1,18 @@
+import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
+import { NostrEventComment } from "../../types/lib";
+import { NDKEventFigure } from "../../types/ndk";
+import { tags_comment } from "../../utils/tags.js";
+import { ndk_event } from "../lib.js";
+
+export const ndk_event_comment = async (opts: NDKEventFigure<{ data: NostrEventComment; }>): Promise<NDKEvent | undefined> => {
+ const { ndk, ndk_user, data } = opts;
+ return await ndk_event({
+ ndk,
+ ndk_user,
+ basis: {
+ kind: NDKKind.GenericReply,
+ content: data.content,
+ tags: tags_comment(data)
+ },
+ });
+};
+\ No newline at end of file
diff --git a/utils-nostr/src/events/comment/parse.ts b/utils-nostr/src/events/comment/parse.ts
@@ -0,0 +1,15 @@
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { nostr_event_comment_schema } from "../../schemas/lib.js";
+import { NostrEventComment } from "../../types/lib.js";
+
+export const parse_nostr_comment_event = (event: NDKEvent): NostrEventComment | undefined => {
+ if (!event || typeof event.content !== 'string' || event.kind !== 1111) return undefined;
+
+ try {
+ const parsed = JSON.parse(event.content);
+ const result = nostr_event_comment_schema.parse(parsed);
+ return result;
+ } catch {
+ return undefined;
+ }
+};
diff --git a/utils-nostr/src/events/listing/lib.ts b/utils-nostr/src/events/listing/lib.ts
@@ -4,9 +4,7 @@ import { NDKEventFigure } from "../../types/ndk";
import { tags_classified } from "../../utils/tags.js";
import { ndk_event } from "../lib.js";
-export const ndk_event_classified = async (opts: NDKEventFigure<{
- data: NostrEventListing;
-}>): Promise<NDKEvent | undefined> => {
+export const ndk_event_classified = async (opts: NDKEventFigure<{ data: NostrEventListing; }>): Promise<NDKEvent | undefined> => {
const { ndk, ndk_user, data } = opts;
return await ndk_event({
ndk,
diff --git a/utils-nostr/src/events/subscription.ts b/utils-nostr/src/events/subscription.ts
@@ -1,11 +1,13 @@
import { NDKEvent } from "@nostr-dev-kit/ndk";
-import { NostrEventListing, type NostrEventMetadata } from "../types/lib.js";
+import { NostrEventComment, NostrEventListing, type NostrEventMetadata } from "../types/lib.js";
+import { parse_nostr_comment_event } from "./comment/parse.js";
import { parse_nostr_listing_event } from "./listing/parse.js";
import { parse_nostr_metadata_event } from "./metadata/parse.js";
export type NdkEventPayload =
| { kind: 0; metadata: NostrEventMetadata; }
| { kind: 30402; listing: NostrEventListing; }
+ | { kind: 1111; listing: NostrEventComment; }
export const on_ndk_event = (event: NDKEvent): NdkEventPayload | undefined => {
if (!event || typeof event.kind !== 'number') return undefined;
@@ -21,6 +23,11 @@ export const on_ndk_event = (event: NDKEvent): NdkEventPayload | undefined => {
if (!data) return;
return { kind: event.kind, listing: data };
};
+ case 1111: {
+ const data = parse_nostr_comment_event(event);
+ if (!data) return;
+ return { kind: event.kind, listing: data };
+ };
default: return undefined;
}
};
diff --git a/utils-nostr/src/schemas/lib.ts b/utils-nostr/src/schemas/lib.ts
@@ -108,4 +108,18 @@ export const nostr_event_listing_schema = z.object({
location: nostr_tag_location_schema.optional(),
images: z.array(nostr_tag_image_schema).optional(),
client: nostr_tag_client_schema.optional()
+});
+
+export const nostr_event_referenced_schema = z.object({
+ id: z.string().regex(/^[0-9a-f]{64}$/i, "expected 64-char hex id"),
+ kind: z.number().int().nonnegative(),
+ author: z.string().regex(/^[0-9a-f]{64}$/i, "expected 64-char hex pubkey"),
+ relays: z.array(z.url()).nonempty().optional(),
+ d_tag: z.string().min(1).optional(),
+});
+
+export const nostr_event_comment_schema = z.object({
+ root_event: nostr_event_referenced_schema,
+ ref_event: nostr_event_referenced_schema.optional(),
+ content: z.string().min(1),
});
\ No newline at end of file
diff --git a/utils-nostr/src/types/lib.ts b/utils-nostr/src/types/lib.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { nostr_event_listing_schema, nostr_event_metadata_schema, nostr_tag_client_schema, nostr_tag_discount_schema, nostr_tag_image_schema, nostr_tag_listing_schema, nostr_tag_location_schema, nostr_tag_price_schema, nostr_tag_quantity_schema } from "../schemas/lib.js";
+import { nostr_event_comment_schema, nostr_event_listing_schema, nostr_event_metadata_schema, nostr_event_referenced_schema, nostr_tag_client_schema, nostr_tag_discount_schema, nostr_tag_image_schema, nostr_tag_listing_schema, nostr_tag_location_schema, nostr_tag_price_schema, nostr_tag_quantity_schema } from "../schemas/lib.js";
export type NostrEventMetadata = z.infer<typeof nostr_event_metadata_schema>;
export type NostrEventListing = z.infer<typeof nostr_event_listing_schema>
@@ -10,6 +10,8 @@ export type NostrTagPriceDiscount = z.infer<typeof nostr_tag_discount_schema>
export type NostrTagPrice = z.infer<typeof nostr_tag_price_schema>
export type NostrTagQuantity = z.infer<typeof nostr_tag_quantity_schema>
export type NostrTagListing = z.infer<typeof nostr_tag_listing_schema>
+export type NostrEventComment = z.infer<typeof nostr_event_comment_schema>
+export type NostrEventReferenced = z.infer<typeof nostr_event_referenced_schema>
export type NostrEventTag = string[];
export type NostrEventTags = NostrEventTag[];
diff --git a/utils-nostr/src/utils/tags.ts b/utils-nostr/src/utils/tags.ts
@@ -1,5 +1,5 @@
import ngeotags, { type InputData as NostrGeotagsInputData } from "nostr-geotags";
-import { NostrEventListing, NostrEventTag, NostrEventTagClient, NostrEventTagImage, NostrEventTagLocation, NostrEventTagPrice, NostrEventTagPriceDiscount, NostrEventTagQuantity, NostrEventTags } from "../types/lib.js";
+import { NostrEventComment, NostrEventListing, NostrEventTag, NostrEventTagClient, NostrEventTagImage, NostrEventTagLocation, NostrEventTagPrice, NostrEventTagPriceDiscount, NostrEventTagQuantity, NostrEventTags } from "../types/lib.js";
export const tag_client = (opts: NostrEventTagClient, d_tag?: string): NostrEventTag => {
const tag = [`client`, opts.name];
@@ -73,3 +73,38 @@ export const tags_classified = (opts: NostrEventListing): NostrEventTags => {
if (opts.images) for (const image_tags of opts.images) tags.push(tag_classified_image(image_tags));
return tags;
};
+
+export const tags_comment = (opts: NostrEventComment): NostrEventTags => {
+ const { root_event, ref_event } = opts;
+
+ const root = {
+ kind: root_event.kind.toString(),
+ author: root_event.author,
+ id: root_event.id,
+ d_tag: root_event.d_tag,
+ relays: root_event.relays || [],
+ };
+
+ const parent = (ref_event && ref_event.id)
+ ? {
+ kind: ref_event.kind.toString(),
+ author: ref_event.author,
+ id: ref_event.id,
+ d_tag: ref_event.d_tag,
+ relays: ref_event.relays || [],
+ }
+ : root;
+
+ const tags: NostrEventTags = [
+ ["E", root.id, ...root.relays],
+ ["P", root.author],
+ ["K", root.kind],
+ ...(root.d_tag ? [["A", `${root.kind}:${root.author}:${root.d_tag}`, ...root.relays]] : []),
+ ["e", parent.id, ...parent.relays],
+ ["p", parent.author],
+ ["k", parent.kind],
+ ...(parent.d_tag ? [["a", `${parent.kind}:${parent.author}:${parent.d_tag}`, ...parent.relays]] : []),
+ ];
+
+ return tags;
+};
+\ No newline at end of file