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:
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[];