web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

commit 732ec0f9c84e5d0cdfee4665eacf23a405e1e4fd
parent 8ad05324931f656e17c487f9da64439ac93eaa99
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Wed,  9 Apr 2025 00:46:24 +0000

utils-nostr: edit nostr event service, add follows and classified event ndk utils, add tags utils, add types, refactor. add `@radroots/util`

Diffstat:
Mutils-nostr/package.json | 1+
Mutils-nostr/src/index.ts | 2++
Mutils-nostr/src/lib/ndk.ts | 94+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Autils-nostr/src/lib/tags.ts | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutils-nostr/src/lib/types.ts | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mutils-nostr/src/services/events/lib.ts | 115++++++++++++++++++++++++-------------------------------------------------------
Mutils-nostr/src/services/events/types.ts | 71+++++++----------------------------------------------------------------
Autils-nostr/src/types.ts | 2++
8 files changed, 250 insertions(+), 183 deletions(-)

diff --git a/utils-nostr/package.json b/utils-nostr/package.json @@ -29,6 +29,7 @@ "@noble/curves": "^1.6.0", "@noble/hashes": "^1.4.0", "@nostr-dev-kit/ndk": "^2.11.0", + "@radroots/util": "workspace:*", "nostr-geotags": "workspace:*", "nostr-tools": "^2.10.4", "uuid": "^10.0.0" diff --git a/utils-nostr/src/index.ts b/utils-nostr/src/index.ts @@ -1,9 +1,11 @@ export * from "./lib/events" export * from "./lib/keys" export * from "./lib/ndk" +export * from "./lib/tags" export * from "./lib/types" export * from "./services/events/lib" export * from "./services/events/types" export * from "./services/keys/lib" export * from "./services/keys/types" +export * from "./types" export * from "./util" diff --git a/utils-nostr/src/lib/ndk.ts b/utils-nostr/src/lib/ndk.ts @@ -1,5 +1,10 @@ -import { time_now_ms, type NostrMetadata } from '$root'; -import NDK, { NDKEvent, NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk'; +import { INostrClassified, INostrFollow, NostrEventTags, tags_classified, tags_follow_list, time_now_ms, type INostrMetadata } from '$root'; +import NDK, { NDKEvent, NDKKind, NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk'; + +export type NDKEventFigure<T extends object> = { + $ndk: NDK; + $ndk_user: NDKUser; +} & T; export const ndk_init = async (opts: { $ndk: NDK; @@ -9,7 +14,6 @@ export const ndk_init = async (opts: { const { $ndk: ndk, secret_key } = opts; const signer = new NDKPrivateKeySigner(secret_key); ndk.signer = signer; - const user = await signer.user(); if (user) { user.ndk = ndk; @@ -20,55 +24,73 @@ export const ndk_init = async (opts: { }; }; -export const ndk_event_metadata = async (opts: { - $ndk: NDK; - $ndk_user: NDKUser; - metadata: NostrMetadata -}): Promise<NDKEvent | undefined> => { - try { - const { $ndk, $ndk_user } = opts; - const ev = await ndk_event({ - $ndk, - $ndk_user, - basis: { - kind: 0, - content: JSON.stringify(opts.metadata), - }, - }); - return ev; - } catch (e) { - console.log(`(error) ndk_event_metadata `, e); - } -}; - -export const ndk_event = async (opts: { - $ndk: NDK; - $ndk_user: NDKUser; +export const ndk_event = async (opts: NDKEventFigure<{ basis: { kind: number; content: string; - tags?: string[][]; + tags?: NostrEventTags; } -}): Promise<NDKEvent | undefined> => { +}>): Promise<NDKEvent | undefined> => { try { const { $ndk: ndk, $ndk_user: ndk_user, basis } = opts; const time_now = time_now_ms(); - - const tags: string[][] = [ + const tags: NostrEventTags = [ ['published_at', time_now.toString()], ]; - - if (basis.tags && basis.tags?.length > 0) for (const tag of basis.tags) tags.push(tag); - - const event: NDKEvent = new NDKEvent(ndk, { + 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 event; + return ev; } catch (e) { console.log(`(error) ndk_event `, e); }; }; + +export const ndk_event_metadata = async (opts: NDKEventFigure<{ + metadata: INostrMetadata +}>): Promise<NDKEvent | undefined> => { + const { $ndk, $ndk_user, metadata: param } = opts; + return await ndk_event({ + $ndk, + $ndk_user, + basis: { + kind: 0, + content: JSON.stringify(param), + }, + }); +}; + +export const ndk_event_follows = async (opts: NDKEventFigure<{ + list: INostrFollow[]; +}>): Promise<NDKEvent | undefined> => { + const { $ndk, $ndk_user, list: param } = opts; + return await ndk_event({ + $ndk, + $ndk_user, + basis: { + kind: 3, + content: ``, + tags: tags_follow_list(param), + }, + }); +}; + +export const ndk_event_classified = async (opts: NDKEventFigure<{ + classified: INostrClassified; +}>): Promise<NDKEvent | undefined> => { + const { $ndk, $ndk_user, classified: param } = opts; + return await ndk_event({ + $ndk, + $ndk_user, + basis: { + kind: NDKKind.Classified, + content: ``, + tags: tags_classified(param), + }, + }); +}; diff --git a/utils-nostr/src/lib/tags.ts b/utils-nostr/src/lib/tags.ts @@ -0,0 +1,76 @@ +import { INostrClassified, NostrEventTagClient, NostrEventTagLocation, NostrEventTagMediaUpload, NostrEventTagPrice, NostrEventTagQuantity, type INostrFollow, type NostrEventTag, type NostrEventTags } from "$root"; +import { ngeotags, type InputData as NostrGeotagsInputData } from "nostr-geotags"; + +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 tags_follow_list = (list: INostrFollow[]): NostrEventTags => { + return list.map(({ public_key, relay_url, contact_name }) => { + const entry = [`p`, public_key]; + if (relay_url) entry.push(relay_url); + if (contact_name) entry.push(contact_name); + return entry; + }); +}; + +export const tag_classified_quantity = (opts: NostrEventTagQuantity): NostrEventTag => { + const tag = [`quantity`, opts.amt, opts.unit]; + if (opts.label) tag.push(opts.label); + return tag; +}; + +export const tag_classified_price = (opts: NostrEventTagPrice): NostrEventTag => { + const tag = [`price`, opts.amt, opts.currency, opts.qty_amt, opts.qty_unit]; + return tag; +}; + +export const tag_classified_image = (opts: NostrEventTagMediaUpload): NostrEventTag => { + const tag = [`image`, opts.url]; + if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`) + return tag; +}; + +export const tag_classified_location = (opts: NostrEventTagLocation): NostrEventTag => { + const tag = [`location`]; + if (opts.city) tag.push(opts.city); + if (opts.region_code && !isNaN(parseInt(opts.region_code))) tag.push(opts.region_code); + else if (opts.region) tag.push(opts.region); //@todo + if (opts.country_code) tag.push(opts.country_code); + return tag; +}; + +export const tags_classified_location_geotags = (opts: NostrEventTagLocation): NostrEventTags => { + return ngeotags( + { + lat: opts.lat, + lon: opts.lng, + city: opts.city, + regionName: opts.region, + countryName: opts.country, + countryCode: opts.country_code + } satisfies NostrGeotagsInputData, + { + geohash: true, + gps: true, + city: true, + iso31662: true, + }); +}; + + +export const tags_classified = (opts: INostrClassified): NostrEventTags => { + const { d_tag, listing, quantity, price, location } = 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]); + tags.push(tag_classified_quantity(quantity)); + tags.push(tag_classified_price(price)); + tags.push(tag_classified_location(location)); + if (opts.images) for (const image of opts.images) tags.push(tag_classified_image(image)); + tags.push(...tags_classified_location_geotags(location)); + return tags; +}; +\ No newline at end of file diff --git a/utils-nostr/src/lib/types.ts b/utils-nostr/src/lib/types.ts @@ -1,6 +1,6 @@ import { type EventTemplate as NostrToolsEventTemplate } from "nostr-tools"; -export type NostrMetadata = { +export type INostrMetadata = { name?: string; display_name?: string; about?: string; @@ -13,7 +13,73 @@ export type NostrMetadata = { bot?: boolean; }; +export type INostrFollow = { + public_key: string; + relay_url?: string; + contact_name?: string; +}; + +export type INostrClassified = { + d_tag: string; + listing: NostrEventTagListing; + quantity: NostrEventTagQuantity; + price: NostrEventTagPrice; + location: NostrEventTagLocation; + images?: NostrEventTagMediaUpload[]; + client?: NostrEventTagClient; +}; + export type INostrEventEventSign = { secret_key: string; event: NostrToolsEventTemplate; -} -\ No newline at end of file +} + +export type NostrEventTagListing = { + key: string; + title: string; + category: string; + summary?: string; + process?: string; + lot?: string; + location?: string; + profile?: string; + year?: string; +}; + +export type NostrEventTagPrice = { + amt: string; + currency: string; + qty_amt: string; + qty_unit: string; +}; + +export type NostrEventTagQuantity = { + amt: string; + unit: string; + label?: string; +}; + +export type NostrEventTagLocation = { + city?: string; + region?: string; + region_code?: string; + country?: string; + country_code?: string; + lat: number; + lng: number; + geohash: string; +}; + +export type NostrEventTagMediaUpload = { + url: string; + size?: { + w: number; + h: number; + }; +}; + +export type NostrEventTagClient = { + name: string; + pubkey: string; + relay: string; +}; diff --git a/utils-nostr/src/services/events/lib.ts b/utils-nostr/src/services/events/lib.ts @@ -1,77 +1,25 @@ -import { INostrEventEventSign, INostrEventService, INostrEventServiceFormatTagsBasisNip99, INostrEventServiceNeventEncode, lib_nostr_event_sign, lib_nostr_event_sign_attest, lib_nostr_event_verify, lib_nostr_event_verify_serialized, lib_nostr_nevent_encode, ndk_event, NostrEventTagClient, NostrEventTagLocation, NostrEventTagMediaUpload, NostrEventTagPrice, NostrEventTagQuantity, NostrMetadata } from "$root"; -import NDK, { NDKKind, type NDKEvent } from "@nostr-dev-kit/ndk"; -import { ngeotags, type GeoTags as NostrGeotagsGeotags, type InputData as NostrGeotagsInputData } from "nostr-geotags"; +import { INostrClassified, INostrEventEventSign, INostrEventService, INostrEventServiceEventResolve, INostrEventServiceNeventEncode, INostrFollow, INostrMetadata, lib_nostr_event_sign, lib_nostr_event_sign_attest, lib_nostr_event_verify, lib_nostr_event_verify_serialized, lib_nostr_nevent_encode, ndk_event, ndk_event_classified, ndk_event_follows } from "$root"; +import NDK, { NDKKind, NDKUser, type NDKEvent } from "@nostr-dev-kit/ndk"; +import { err_msg, ErrorMessage } from "@radroots/util"; import { type NostrEvent as NostrToolsEvent } from "nostr-tools"; export class NostrEventService implements INostrEventService { + private resolve_ndk_user = async (ndk: NDK): Promise<NDKUser | ErrorMessage<string>> => { + const user = await ndk.signer?.user(); + if (!user) return err_msg(`error.ndk.user_undefined`); + return user; + } + + private resolve_ndk_event = (ev?: NDKEvent) => { + if (!ev) return err_msg(`error.event_undefined`); + return ev; + } + public first_tag_value = (event: NDKEvent, tag_name: string): string => { const tag = event.getMatchingTags(tag_name)[0]; return tag ? tag[1] : ""; } - private fmt_tag_price = (opts: NostrEventTagPrice): string[] => { - const tag = [`price`, opts.amt, opts.currency, opts.qty_amt, opts.qty_unit]; - return tag; - }; - - private fmt_tag_quantity = (opts: NostrEventTagQuantity): string[] => { - const tag = [`quantity`, opts.amt, opts.unit]; - if (opts.label) tag.push(opts.label); - return tag; - }; - - private fmt_tag_location = (opts: NostrEventTagLocation): string[] => { - const tag = [`location`]; - if (opts.city) tag.push(opts.city); - if (opts.region_code && !isNaN(parseInt(opts.region_code))) tag.push(opts.region_code); - else if (opts.region) tag.push(opts.region); //@todo - if (opts.country_code) tag.push(opts.country_code); - return tag; - }; - - private fmt_tag_image = (opts: NostrEventTagMediaUpload): string[] => { - const tag = [`image`, opts.url]; - if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`) - return tag; - }; - - private fmt_tag_client = (opts: NostrEventTagClient, d_tag?: string): string[] => { - const tag = [`client`, opts.name]; - if (d_tag) tag.push(`31990:${opts.pubkey}:${d_tag}`); - tag.push(opts.relay); - return tag; - }; - - private fmt_tag_geotags = (opts: NostrEventTagLocation): NostrGeotagsGeotags[] => { - const data: NostrGeotagsInputData = { - lat: opts.lat, - lon: opts.lng, - city: opts.city, - regionName: opts.region, - countryName: opts.country, - countryCode: opts.country_code - }; - return ngeotags(data, { - geohash: true, - gps: true, - city: true, - iso31662: true, - }); - }; - - public fmt_tags_basis_nip99 = (opts: INostrEventServiceFormatTagsBasisNip99): string[][] => { - const { d_tag, listing, quantity, price, location } = opts; - const tags: string[][] = [[`d`, d_tag]]; - if (opts.client) tags.push(this.fmt_tag_client(opts.client, opts.d_tag)); - for (const [k, v] of Object.entries(listing)) if (v) tags.push([k, v]); - tags.push(this.fmt_tag_quantity(quantity)); - tags.push(this.fmt_tag_price(price)); - tags.push(this.fmt_tag_location(location)); - if (opts.images) for (const image of opts.images) tags.push(this.fmt_tag_image(image)); - tags.push(...this.fmt_tag_geotags(location)); - return tags; - }; - public nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => { return lib_nostr_event_sign(opts); }; @@ -93,9 +41,9 @@ export class NostrEventService implements INostrEventService { return lib_nostr_nevent_encode(opts); }; - public metadata = async ($ndk: NDK, opts: NostrMetadata): Promise<NDKEvent | undefined> => { - const $ndk_user = await $ndk.signer?.user(); - if (!$ndk_user) return undefined; + public metadata = async ($ndk: NDK, opts: INostrMetadata): Promise<INostrEventServiceEventResolve> => { + const $ndk_user = await this.resolve_ndk_user($ndk); + if (`err` in $ndk_user) return $ndk_user; const ev = await ndk_event({ $ndk, $ndk_user, @@ -104,22 +52,29 @@ export class NostrEventService implements INostrEventService { content: JSON.stringify(opts), }, }); - return ev; + return this.resolve_ndk_event(ev); } - public classified = async ($ndk: NDK, opts: INostrEventServiceFormatTagsBasisNip99): Promise<NDKEvent | undefined> => { - const $ndk_user = await $ndk.signer?.user(); - if (!$ndk_user) return undefined; - const ev = await ndk_event({ + public follows = async ($ndk: NDK, list: INostrFollow[]): Promise<INostrEventServiceEventResolve> => { + const $ndk_user = await this.resolve_ndk_user($ndk); + if (`err` in $ndk_user) return $ndk_user; + const ev = await ndk_event_follows({ $ndk, $ndk_user, - basis: { - kind: NDKKind.Classified, - content: ``, - tags: this.fmt_tags_basis_nip99(opts), - }, + list }); - return ev; + return this.resolve_ndk_event(ev); + } + + public classified = async ($ndk: NDK, classified: INostrClassified): Promise<INostrEventServiceEventResolve> => { + const $ndk_user = await this.resolve_ndk_user($ndk); + if (`err` in $ndk_user) return $ndk_user; + const ev = await ndk_event_classified({ + $ndk, + $ndk_user, + classified + }); + return this.resolve_ndk_event(ev); } } diff --git a/utils-nostr/src/services/events/types.ts b/utils-nostr/src/services/events/types.ts @@ -1,17 +1,8 @@ -import type { INostrEventEventSign, NostrMetadata } from "$root"; +import type { INostrClassified, INostrEventEventSign, INostrFollow, INostrMetadata } from "$root"; import NDK, { NDKEvent } from "@nostr-dev-kit/ndk"; +import { type ResolveError } from "@radroots/util"; import { type NostrEvent as NostrToolsEvent } from "nostr-tools"; -export type INostrEventServiceFormatTagsBasisNip99 = { - d_tag: string; - listing: NostrEventTagListing; - quantity: NostrEventTagQuantity; - price: NostrEventTagPrice; - location: NostrEventTagLocation; - images?: NostrEventTagMediaUpload[]; - client?: NostrEventTagClient; -}; - export type INostrEventServiceNeventEncode = { id: string; relays: string[]; @@ -19,64 +10,16 @@ export type INostrEventServiceNeventEncode = { kind: number; }; +export type INostrEventServiceEventResolve = ResolveError<NDKEvent>; + export type INostrEventService = { first_tag_value(event: NDKEvent, tag_name: string): string; - fmt_tags_basis_nip99: (opts: INostrEventServiceFormatTagsBasisNip99) => string[][]; nostr_event_sign: (opts: INostrEventEventSign) => NostrToolsEvent; nostr_event_sign_attest: (secret_key: string) => NostrToolsEvent; nostr_event_verify_serialized: (event_serialized: string) => boolean; nostr_event_verify: (event: NostrToolsEvent) => boolean; nevent_encode: (opts: INostrEventServiceNeventEncode) => string; - metadata: (ndk: NDK, opts: NostrMetadata) => Promise<NDKEvent | undefined>; - classified: (ndk: NDK, opts: INostrEventServiceFormatTagsBasisNip99) => Promise<NDKEvent | undefined>; -}; - -export type NostrEventTagListing = { - key: string; - title: string; - category: string; - summary?: string; - process?: string; - lot?: string; - location?: string; - profile?: string; - year?: string; -}; - -export type NostrEventTagPrice = { - amt: string; - currency: string; - qty_amt: string; - qty_unit: string; -}; - -export type NostrEventTagQuantity = { - amt: string; - unit: string; - label?: string; -}; - -export type NostrEventTagLocation = { - city?: string; - region?: string; - region_code?: string; - country?: string; - country_code?: string; - lat: number; - lng: number; - geohash: string; -}; - -export type NostrEventTagMediaUpload = { - url: string; - size?: { - w: number; - h: number; - }; -}; - -export type NostrEventTagClient = { - name: string; - pubkey: string; - relay: string; + metadata: (ndk: NDK, opts: INostrMetadata) => Promise<INostrEventServiceEventResolve>; + follows: (ndk: NDK, opts: INostrFollow[]) => Promise<INostrEventServiceEventResolve>; + classified: (ndk: NDK, opts: INostrClassified) => Promise<INostrEventServiceEventResolve>; }; diff --git a/utils-nostr/src/types.ts b/utils-nostr/src/types.ts @@ -0,0 +1,2 @@ +export type NostrEventTag = string[]; +export type NostrEventTags = NostrEventTag[];