commit 0eb6c962718b5b56dbc6e5173b90d736360af105
parent 027e986a4fe2ab526c992e9cee2026c702ff12ba
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Fri, 8 Aug 2025 22:37:06 +0000
utils-nostr: refactor adding `metadata` and `listing` utils, `ndk` utils, event tags utils, types. update tsconfig
Diffstat:
18 files changed, 365 insertions(+), 124 deletions(-)
diff --git a/utils-nostr/package.json b/utils-nostr/package.json
@@ -15,7 +15,7 @@
}
},
"scripts": {
- "build:esm": "tsc -p tsconfig.esm.json",
+ "build:esm": "tsc -p tsconfig.json",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build": "npm run clean && npm run build:esm && npm run build:cjs",
"prebuild": "npm run clean",
@@ -23,13 +23,14 @@
"dev": "npm run watch",
"watch": "tsc -w"
},
+ "publishConfig": {
+ "access": "public"
+ },
"devDependencies": {
+ "@radroots/tsconfig": "*",
"rimraf": "^6.0.1",
"typescript": "5.8.3"
},
- "publishConfig": {
- "access": "public"
- },
"dependencies": {
"@noble/curves": "^1.6.0",
"@noble/hashes": "^1.4.0",
diff --git a/utils-nostr/src/events/lib.ts b/utils-nostr/src/events/lib.ts
@@ -1,4 +1,37 @@
-import { NDKTag } from "@nostr-dev-kit/ndk";
+import { NDKEvent, NDKTag } from "@nostr-dev-kit/ndk";
+import { NostrEventTags } from "../types/lib.js";
+import { NDKEventFigure } from "../types/ndk.js";
+import { time_now_ms } from "../utils/lib.js";
+
export const get_event_tag = (tags: NDKTag[], key: string): string => tags.find(t => t[0] === key)?.[1] ?? '';
export const get_event_tags = (tags: NDKTag[], key: string): NDKTag[] => tags.filter(t => t[0] === key);
+
+export const ndk_event = async (opts: NDKEventFigure<{
+ basis: {
+ kind: number;
+ content: string;
+ tags?: NostrEventTags;
+ }
+}>): Promise<NDKEvent | undefined> => {
+ try {
+ const { ndk: ndk, ndk_user: ndk_user, basis } = opts;
+ const time_now = time_now_ms();
+ const published_at = opts.date_published ? Math.floor(opts.date_published.getTime() / 1000).toString()
+ : time_now.toString()
+ const tags: NostrEventTags = [
+ ['published_at', published_at],
+ ];
+ if (basis.tags?.length) tags.push(...basis.tags);
+ const ev = new NDKEvent(ndk, {
+ kind: basis.kind,
+ pubkey: ndk_user.pubkey,
+ content: basis.content,
+ created_at: time_now,
+ tags
+ });
+ return ev;
+ } catch (e) {
+ console.log(`(error) ndk_event `, e);
+ };
+};
+\ No newline at end of file
diff --git a/utils-nostr/src/events/listing/lib.ts b/utils-nostr/src/events/listing/lib.ts
@@ -0,0 +1,20 @@
+import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
+import { NostrEventListing } from "../../types/lib.js";
+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> => {
+ const { ndk, ndk_user, data } = opts;
+ return await ndk_event({
+ ndk,
+ ndk_user,
+ basis: {
+ kind: NDKKind.Classified,
+ content: ``,
+ tags: tags_classified(data),
+ },
+ });
+};
+\ No newline at end of file
diff --git a/utils-nostr/src/events/listing/parse.ts b/utils-nostr/src/events/listing/parse.ts
@@ -0,0 +1,76 @@
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { nostr_event_listing_schema, nostr_tag_listing_schema, nostr_tag_location_schema, nostr_tag_price_schema, nostr_tag_quantity_schema } from "../../schemas/lib.js";
+import { NostrEventListing } from "../../types/lib.js";
+import { get_event_tag, get_event_tags } from "../lib.js";
+
+export const parse_nostr_listing_event = (event: NDKEvent): NostrEventListing | undefined => {
+ if (!event || event.kind !== 30402 || !Array.isArray(event.tags)) return;
+
+ try {
+ const tags = event.tags;
+
+ const d_tag = get_event_tag(tags, 'd');
+
+ const listing_raw = {
+ key: get_event_tag(tags, 'key'),
+ title: get_event_tag(tags, 'title'),
+ category: get_event_tag(tags, 'category'),
+ summary: get_event_tag(tags, 'summary'),
+ process: get_event_tag(tags, 'process'),
+ lot: get_event_tag(tags, 'lot'),
+ location: get_event_tag(tags, 'location'),
+ profile: get_event_tag(tags, 'profile'),
+ year: get_event_tag(tags, 'year')
+ };
+
+ const listing = nostr_tag_listing_schema.parse(listing_raw);
+
+ const quantities = get_event_tags(tags, 'quantity')
+ .map(q => {
+ if (q.length < 3) return undefined;
+ return nostr_tag_quantity_schema.parse({
+ amt: q[1],
+ unit: q[2],
+ label: q[3]
+ });
+ })
+ .filter(Boolean);
+
+ const prices = get_event_tags(tags, 'price')
+ .map(p => {
+ if (p.length < 6) return undefined;
+ return nostr_tag_price_schema.parse({
+ amt: p[1],
+ currency: p[2],
+ qty_amt: p[3],
+ qty_unit: p[4],
+ qty_key: p[5]
+ });
+ })
+ .filter(Boolean);
+
+ const location_parts = get_event_tags(tags, 'location')[0]?.slice(1) ?? [];
+
+ const location_raw: any = {};
+ if (location_parts[0]) location_raw.primary = location_parts[0];
+ if (location_parts[1]) location_raw.city = location_parts[1];
+ if (location_parts[2]) location_raw.region = location_parts[2];
+ if (location_parts[3]) location_raw.country = location_parts[3];
+
+ const location = Object.keys(location_raw).length
+ ? nostr_tag_location_schema.parse(location_raw)
+ : undefined;
+
+ const parsed = nostr_event_listing_schema.parse({
+ d_tag,
+ listing,
+ quantities,
+ prices,
+ location
+ });
+
+ return parsed;
+ } catch {
+ return undefined;
+ }
+};
diff --git a/utils-nostr/src/events/metadata/lib.ts b/utils-nostr/src/events/metadata/lib.ts
@@ -0,0 +1,16 @@
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { NostrEventMetadata } from "../../types/lib.js";
+import { NDKEventFigure } from "../../types/ndk.js";
+import { ndk_event } from "../lib.js";
+
+export const ndk_event_metadata = async (opts: NDKEventFigure<{ data: NostrEventMetadata }>): Promise<NDKEvent | undefined> => {
+ const { ndk, ndk_user, data } = opts;
+ return await ndk_event({
+ ndk,
+ ndk_user,
+ basis: {
+ kind: 0,
+ content: JSON.stringify(data),
+ },
+ });
+};
+\ No newline at end of file
diff --git a/utils-nostr/src/events/metadata/parse.ts b/utils-nostr/src/events/metadata/parse.ts
@@ -0,0 +1,15 @@
+import { NDKEvent } from "@nostr-dev-kit/ndk";
+import { nostr_event_metadata_schema } from "../../schemas/lib.js";
+import { type NostrEventMetadata } from "../../types/lib.js";
+
+export const parse_nostr_metadata_event = (event: NDKEvent): NostrEventMetadata | undefined => {
+ if (!event || typeof event.content !== 'string' || event.kind !== 0) return undefined;
+
+ try {
+ const parsed = JSON.parse(event.content);
+ const result = nostr_event_metadata_schema.parse(parsed);
+ return result;
+ } catch {
+ return undefined;
+ }
+};
diff --git a/utils-nostr/src/events/parse.ts b/utils-nostr/src/events/parse.ts
@@ -1,88 +0,0 @@
-import { NDKEvent } from "@nostr-dev-kit/ndk";
-import { nostr_event_listing_schema, nostr_event_metadata_schema, nostr_tag_listing_schema, nostr_tag_location_schema, nostr_tag_price_schema, nostr_tag_quantity_schema } from "../schemas/lib.js";
-import { NostrEventListing, type NostrEventMetadata } from "../types/lib.js";
-import { get_event_tag, get_event_tags } from "./lib.js";
-
-export const parse_nostr_metadata_event = (event: NDKEvent): NostrEventMetadata | undefined => {
- if (!event || typeof event.content !== 'string' || event.kind !== 0) return undefined;
-
- try {
- const parsed = JSON.parse(event.content);
- const result = nostr_event_metadata_schema.parse(parsed);
- return result;
- } catch {
- return undefined;
- }
-};
-
-export const parse_nostr_listing_event = (event: NDKEvent): NostrEventListing | undefined => {
- if (!event || event.kind !== 30402 || !Array.isArray(event.tags)) return;
-
- try {
- const tags = event.tags;
-
- const d_tag = get_event_tag(tags, 'd');
-
- const listing_raw = {
- key: get_event_tag(tags, 'key'),
- title: get_event_tag(tags, 'title'),
- category: get_event_tag(tags, 'category'),
- summary: get_event_tag(tags, 'summary'),
- process: get_event_tag(tags, 'process'),
- lot: get_event_tag(tags, 'lot'),
- location: get_event_tag(tags, 'location'),
- profile: get_event_tag(tags, 'profile'),
- year: get_event_tag(tags, 'year')
- };
-
- const listing = nostr_tag_listing_schema.parse(listing_raw);
-
- const quantities = get_event_tags(tags, 'quantity')
- .map(q => {
- if (q.length < 3) return undefined;
- return nostr_tag_quantity_schema.parse({
- amt: q[1],
- unit: q[2],
- label: q[3]
- });
- })
- .filter(Boolean);
-
- const prices = get_event_tags(tags, 'price')
- .map(p => {
- if (p.length < 6) return undefined;
- return nostr_tag_price_schema.parse({
- amt: p[1],
- currency: p[2],
- qty_amt: p[3],
- qty_unit: p[4],
- qty_key: p[5]
- });
- })
- .filter(Boolean);
-
- const location_parts = get_event_tags(tags, 'location')[0]?.slice(1) ?? [];
-
- const location_raw: any = {};
- if (location_parts[0]) location_raw.primary = location_parts[0];
- if (location_parts[1]) location_raw.city = location_parts[1];
- if (location_parts[2]) location_raw.region = location_parts[2];
- if (location_parts[3]) location_raw.country = location_parts[3];
-
- const location = Object.keys(location_raw).length
- ? nostr_tag_location_schema.parse(location_raw)
- : undefined;
-
- const parsed = nostr_event_listing_schema.parse({
- d_tag,
- listing,
- quantities,
- prices,
- location
- });
-
- return parsed;
- } catch {
- return undefined;
- }
-};
diff --git a/utils-nostr/src/events/subscription.ts b/utils-nostr/src/events/subscription.ts
@@ -1,6 +1,7 @@
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { NostrEventListing, type NostrEventMetadata } from "../types/lib.js";
-import { parse_nostr_listing_event, parse_nostr_metadata_event } from "./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; }
diff --git a/utils-nostr/src/index.ts b/utils-nostr/src/index.ts
@@ -1,6 +1,11 @@
export * from "./events/lib.js"
-export * from "./events/parse.js"
+export * from "./events/listing/lib.js"
+export * from "./events/listing/parse.js"
+export * from "./events/metadata/parse.js"
export * from "./events/subscription.js"
export * from "./keys/lib.js"
export * from "./schemas/lib.js"
export * from "./types/lib.js"
+export * from "./types/ndk.js"
+export * from "./utils/lib.js"
+export * from "./utils/ndk.js"
diff --git a/utils-nostr/src/types/lib.ts b/utils-nostr/src/types/lib.ts
@@ -10,3 +10,78 @@ 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 NostrEventTag = string[];
+export type NostrEventTags = NostrEventTag[];
+
+export type NostrEventTagClient = {
+ name: string;
+ pubkey: string;
+ relay: string;
+};
+
+export type NostrEventTagQuantity = {
+ amt: string;
+ unit: string;
+ label?: string;
+};
+
+export type NostrEventTagPrice = {
+ amt: string;
+ currency: string;
+ qty_amt: string;
+ qty_unit: string;
+ qty_key: string;
+};
+
+export type NostrEventTagPriceDiscount = (
+ {
+ quantity: {
+ ref_quantity: string;
+ threshold: string;
+ value: string;
+ currency: string;
+ }
+ } |
+ {
+ mass: {
+ unit: string;
+ threshold: string;
+ threshold_unit: string;
+ value: string;
+ currency: string;
+ }
+ } |
+ {
+ subtotal: {
+ threshold: string;
+ currency: string;
+ value: string;
+ measure: string;
+ }
+ } |
+ {
+ total: {
+ total_min: string;
+ value: string;
+ measure: string;
+ }
+ }
+);
+
+export type NostrEventTagLocation = {
+ primary: string;
+ city?: string;
+ region?: string;
+ country?: string;
+ lat?: number;
+ lng?: number;
+};
+
+export type NostrEventTagImage = {
+ url: string;
+ size?: {
+ w: number;
+ h: number;
+ };
+};
+\ No newline at end of file
diff --git a/utils-nostr/src/types/ndk.ts b/utils-nostr/src/types/ndk.ts
@@ -0,0 +1,8 @@
+import NDK, { NDKUser } from "@nostr-dev-kit/ndk";
+
+export type NDKEventFigure<T extends object> = {
+ ndk: NDK;
+ ndk_user: NDKUser;
+ date_published?: Date;
+} & T;
+
diff --git a/utils-nostr/src/utils/lib.ts b/utils-nostr/src/utils/lib.ts
@@ -0,0 +1 @@
+export const time_now_ms = (): number => Math.floor(new Date().getTime() / 1000);
diff --git a/utils-nostr/src/utils/ndk.ts b/utils-nostr/src/utils/ndk.ts
@@ -0,0 +1,13 @@
+import NDK, { NDKCacheAdapter, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
+
+export const create_ndk = (explicitRelayUrls: string[], cacheAdapter?: NDKCacheAdapter): NDK => {
+ return new NDK({
+ explicitRelayUrls,
+ enableOutboxModel: false,
+ cacheAdapter
+ });
+};
+
+export const create_ndk_signer = (secret_key: string): NDKPrivateKeySigner => {
+ return new NDKPrivateKeySigner(secret_key);
+};
+\ No newline at end of file
diff --git a/utils-nostr/src/utils/tags.ts b/utils-nostr/src/utils/tags.ts
@@ -0,0 +1,75 @@
+import ngeotags, { type InputData as NostrGeotagsInputData } from "nostr-geotags";
+import { 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];
+ if (d_tag) tag.push(`31990:${opts.pubkey}:${d_tag}`);
+ tag.push(opts.relay);
+ return tag;
+};
+
+export const tag_classified_quantity = (opts: NostrEventTagQuantity): NostrEventTag => {
+ const tag = [`quantity`, opts.amt, opts.unit];
+ if (opts.label) tag.push(opts.label);
+ return tag.map(i => i.toLowerCase());
+};
+
+export const tag_classified_price = (price: NostrEventTagPrice): NostrEventTag => {
+ const tag = [`price`, price.amt, price.currency, price.qty_amt, price.qty_unit, price.qty_key];
+ return tag.map(i => i.toLowerCase());
+};
+
+export const tag_classified_price_discount = (discount: NostrEventTagPriceDiscount): NostrEventTag => {
+ const tag = [`price-discount-${Object.keys(discount)[0]}`];
+ if (`mass` in discount) tag.push(...Object.values(discount.mass));
+ else if (`quantity` in discount) tag.push(...Object.values(discount.quantity));
+ else if (`subtotal` in discount) tag.push(...Object.values(discount.subtotal));
+ else if (`total` in discount) tag.push(...Object.values(discount.total));
+ return tag.map(i => i.toLowerCase());
+};
+
+export const tag_classified_location = (opts: NostrEventTagLocation): NostrEventTag => {
+ if (!opts.primary) return [];
+ const tag = [`location`, opts.primary];
+ if (opts.city) tag.push(opts.city);
+ if (opts.region) tag.push(opts.region);
+ if (opts.country) tag.push(opts.country);
+ return tag;
+};
+
+export const tags_classified_location_geotags = (opts: NostrEventTagLocation): NostrEventTags => {
+ const { lat, lng: lon, city, region: regionName, country } = opts;
+ const country_raw = country || ``;
+ const countryCode = country_raw && country_raw?.length <= 3 ? country_raw : undefined;
+ const countryName = country_raw && country_raw?.length > 3 ? country_raw : undefined;
+ return ngeotags({ lat, lon, city, regionName, countryCode, countryName } satisfies NostrGeotagsInputData, { geohash: true, gps: true, city: true, iso31662: true });
+};
+
+
+export const tag_classified_image = (opts: NostrEventTagImage): NostrEventTag => {
+ const tag = [`image`, opts.url];
+ if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`)
+ return tag;
+};
+
+export const tags_classified = (opts: NostrEventListing): NostrEventTags => {
+ const { d_tag, listing, quantities, prices } = opts;
+ const tags: NostrEventTags = [[`d`, d_tag]];
+ if (opts.client) tags.push(tag_client(opts.client, opts.d_tag));
+ for (const [k, v] of Object.entries(listing)) if (v) tags.push([k, v]);
+ for (const quantity of quantities) {
+ tags.push(tag_classified_quantity(quantity));
+ }
+ for (const price of prices) {
+ tags.push(tag_classified_price(price));
+ }
+ for (const discount of opts.discounts || []) {
+ tags.push(tag_classified_price_discount(discount));
+ }
+ if (opts.location) {
+ tags.push(tag_classified_location(opts.location));
+ tags.push(...tags_classified_location_geotags(opts.location));
+ }
+ if (opts.images) for (const image_tags of opts.images) tags.push(tag_classified_image(image_tags));
+ return tags;
+};
diff --git a/utils-nostr/tsconfig.base.json b/utils-nostr/tsconfig.base.json
@@ -1,11 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2019",
- "strict": true,
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "skipLibCheck": true,
- "sourceMap": false,
- "rootDir": "src"
- }
-}
diff --git a/utils-nostr/tsconfig.cjs.json b/utils-nostr/tsconfig.cjs.json
@@ -1,10 +1,9 @@
{
- "extends": "./tsconfig.base.json",
+ "extends": "@radroots/internal-tsconfig/tsconfig.cjs.json",
"compilerOptions": {
- "outDir": "./dist/cjs",
- "module": "CommonJS",
- "declaration": false,
- "moduleResolution": "Node"
+ "rootDir": "./src",
+ "outDir": "dist/cjs"
},
- "include": ["src"]
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
}
diff --git a/utils-nostr/tsconfig.esm.json b/utils-nostr/tsconfig.esm.json
@@ -1,12 +0,0 @@
-{
- "extends": "./tsconfig.base.json",
- "compilerOptions": {
- "outDir": "./dist/esm",
- "module": "NodeNext",
- "declaration": true,
- "declarationDir": "./dist/types",
- "moduleResolution": "NodeNext",
- "emitDeclarationOnly": false
- },
- "include": ["src"]
-}
diff --git a/utils-nostr/tsconfig.json b/utils-nostr/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "@radroots/internal-tsconfig/tsconfig.esm.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "dist/esm"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}