applesauce-core 0.0.0-next-20241115160057 → 0.0.0-next-20241119160247

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,4 +5,5 @@ export type ParsedInvoice = {
5
5
  timestamp: number;
6
6
  expiry: number;
7
7
  };
8
+ /** Parses a lightning invoice */
8
9
  export declare function parseBolt11(paymentRequest: string): ParsedInvoice;
@@ -1,4 +1,5 @@
1
1
  import { decode } from "light-bolt11-decoder";
2
+ /** Parses a lightning invoice */
2
3
  export function parseBolt11(paymentRequest) {
3
4
  const decoded = decode(paymentRequest);
4
5
  const timestamp = decoded.sections.find((s) => s.name === "timestamp")?.value ?? 0;
@@ -1,2 +1,9 @@
1
1
  import { EventTemplate, NostrEvent } from "nostr-tools";
2
2
  export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string];
3
+ /** Returns the name of a NIP-30 emoji pack */
4
+ export declare function getPackName(pack: NostrEvent): string | undefined;
5
+ /** Returns an array of emojis from a NIP-30 emoji pack */
6
+ export declare function getEmojis(pack: NostrEvent): {
7
+ name: string;
8
+ url: string;
9
+ }[];
@@ -1,4 +1,15 @@
1
+ import { getTagValue } from "./event.js";
1
2
  export function getEmojiTag(event, code) {
2
3
  code = code.replace(/^:|:$/g, "").toLocaleLowerCase();
3
4
  return event.tags.filter((t) => t[0] === "emoji" && t[1] && t[2]).find((t) => t[1].toLowerCase() === code);
4
5
  }
6
+ /** Returns the name of a NIP-30 emoji pack */
7
+ export function getPackName(pack) {
8
+ return getTagValue(pack, "title") || getTagValue(pack, "d");
9
+ }
10
+ /** Returns an array of emojis from a NIP-30 emoji pack */
11
+ export function getEmojis(pack) {
12
+ return pack.tags
13
+ .filter((t) => t[0] === "emoji" && t[1] && t[2])
14
+ .map((t) => ({ name: t[1], url: t[2] }));
15
+ }
@@ -0,0 +1,39 @@
1
+ import { EventTemplate, NostrEvent, UnsignedEvent } from "nostr-tools";
2
+ import { EventStore } from "applesauce-core";
3
+ export type HiddenTagsSigner = {
4
+ nip04: {
5
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
6
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
7
+ };
8
+ };
9
+ export type TagOperation = (tags: string[][]) => string[][];
10
+ export declare const HiddenTagsSymbol: unique symbol;
11
+ export declare const EventsWithHiddenTags: number[];
12
+ /** Checks if an event can have hidden tags */
13
+ export declare function canHaveHiddenTags(event: NostrEvent | EventTemplate): boolean;
14
+ /** Checks if an event has hidden tags */
15
+ export declare function hasHiddenTags(event: NostrEvent | EventTemplate): boolean;
16
+ /** Returns the hidden tags from an event if they are unlocked */
17
+ export declare function getHiddenTags(event: NostrEvent | EventTemplate): string[][] | undefined;
18
+ export declare function isHiddenTagsLocked(event: NostrEvent): boolean;
19
+ /**
20
+ * Decrypts the private list
21
+ * @param event The list event to decrypt
22
+ * @param signer A signer to use to decrypt the tags
23
+ * @param store An optional EventStore to notify about the update
24
+ */
25
+ export declare function unlockHiddenTags(event: NostrEvent, signer: HiddenTagsSigner, store?: EventStore): Promise<NostrEvent>;
26
+ /**
27
+ * Modifies tags and returns an EventTemplate
28
+ * @param event Event to modify
29
+ * @param operations Operations for hidden and public tags
30
+ * @param signer A signer to use to decrypt the tags
31
+ */
32
+ export declare function modifyEventTags(event: NostrEvent | UnsignedEvent, operations: {
33
+ public?: TagOperation;
34
+ hidden?: TagOperation;
35
+ }, signer?: HiddenTagsSigner): Promise<EventTemplate>;
36
+ /**
37
+ * Override the hidden tags in an event
38
+ */
39
+ export declare function overrideHiddenTags(event: NostrEvent, hidden: string[][], signer: HiddenTagsSigner): Promise<EventTemplate>;
@@ -0,0 +1,90 @@
1
+ import { unixNow } from "applesauce-core/helpers";
2
+ import { kinds } from "nostr-tools";
3
+ export const HiddenTagsSymbol = Symbol.for("hidden-tags");
4
+ export const EventsWithHiddenTags = [
5
+ 37375, // NIP-60 wallet
6
+ // NIP-51 lists
7
+ kinds.BookmarkList,
8
+ kinds.InterestsList,
9
+ kinds.Mutelist,
10
+ kinds.CommunitiesList,
11
+ kinds.PublicChatsList,
12
+ kinds.SearchRelaysList,
13
+ kinds.SearchRelaysList,
14
+ 10009, // NIP-29 groups
15
+ // NIP-51 sets
16
+ kinds.Bookmarksets,
17
+ kinds.Relaysets,
18
+ kinds.Followsets,
19
+ kinds.Curationsets,
20
+ kinds.Interestsets,
21
+ ];
22
+ /** Checks if an event can have hidden tags */
23
+ export function canHaveHiddenTags(event) {
24
+ return EventsWithHiddenTags.includes(event.kind);
25
+ }
26
+ /** Checks if an event has hidden tags */
27
+ export function hasHiddenTags(event) {
28
+ return canHaveHiddenTags(event) && event.content.length > 0;
29
+ }
30
+ /** Returns the hidden tags from an event if they are unlocked */
31
+ export function getHiddenTags(event) {
32
+ return Reflect.get(event, HiddenTagsSymbol);
33
+ }
34
+ export function isHiddenTagsLocked(event) {
35
+ return hasHiddenTags(event) && getHiddenTags(event) === undefined;
36
+ }
37
+ /**
38
+ * Decrypts the private list
39
+ * @param event The list event to decrypt
40
+ * @param signer A signer to use to decrypt the tags
41
+ * @param store An optional EventStore to notify about the update
42
+ */
43
+ export async function unlockHiddenTags(event, signer, store) {
44
+ const plaintext = await signer.nip04.decrypt(event.pubkey, event.content);
45
+ const parsed = JSON.parse(plaintext);
46
+ if (!Array.isArray(parsed))
47
+ throw new Error("Content is not an array of tags");
48
+ // Convert array to tags array string[][]
49
+ const tags = parsed.filter((t) => Array.isArray(t)).map((t) => t.map((v) => String(v)));
50
+ Reflect.set(event, HiddenTagsSymbol, tags);
51
+ if (store)
52
+ store.update(event);
53
+ return event;
54
+ }
55
+ /**
56
+ * Modifies tags and returns an EventTemplate
57
+ * @param event Event to modify
58
+ * @param operations Operations for hidden and public tags
59
+ * @param signer A signer to use to decrypt the tags
60
+ */
61
+ export async function modifyEventTags(event, operations, signer) {
62
+ const draft = { content: event.content, tags: event.tags, kind: event.kind, created_at: unixNow() };
63
+ if (operations.public) {
64
+ draft.tags = operations.public(event.tags);
65
+ }
66
+ if (operations.hidden) {
67
+ if (!signer)
68
+ throw new Error("Missing signer for hidden tags");
69
+ if (!canHaveHiddenTags(event))
70
+ throw new Error("Event can not have hidden tags");
71
+ const hidden = hasHiddenTags(event) ? getHiddenTags(event) : [];
72
+ if (!hidden)
73
+ throw new Error("Hidden tags are locked");
74
+ const newHidden = operations.hidden(hidden);
75
+ draft.content = await signer.nip04.encrypt(event.pubkey, JSON.stringify(newHidden));
76
+ }
77
+ return draft;
78
+ }
79
+ /**
80
+ * Override the hidden tags in an event
81
+ */
82
+ export async function overrideHiddenTags(event, hidden, signer) {
83
+ const ciphertext = await signer.nip04.encrypt(event.pubkey, JSON.stringify(hidden));
84
+ return {
85
+ kind: event.kind,
86
+ content: ciphertext,
87
+ created_at: unixNow(),
88
+ tags: event.tags,
89
+ };
90
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { getHiddenTags, unixNow, unlockHiddenTags } from "applesauce-core/helpers";
2
+ import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools";
3
+ const key = generateSecretKey();
4
+ const pubkey = getPublicKey(key);
5
+ const signer = {
6
+ nip04: {
7
+ encrypt: (pubkey, plaintext) => nip04.encrypt(key, pubkey, plaintext),
8
+ decrypt: (pubkey, ciphertext) => nip04.decrypt(key, pubkey, ciphertext),
9
+ },
10
+ };
11
+ describe("Private Lists", () => {
12
+ describe("unlockHiddenTags", () => {
13
+ let list;
14
+ beforeEach(async () => {
15
+ list = finalizeEvent({
16
+ kind: kinds.Mutelist,
17
+ created_at: unixNow(),
18
+ content: await nip04.encrypt(key, pubkey, JSON.stringify([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]])),
19
+ tags: [],
20
+ }, key);
21
+ });
22
+ it("should unlock hidden tags", async () => {
23
+ await unlockHiddenTags(list, signer);
24
+ expect(getHiddenTags(list)).toEqual(expect.arrayContaining([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]]));
25
+ });
26
+ });
27
+ });
@@ -14,3 +14,4 @@ export * from "./hashtag.js";
14
14
  export * from "./url.js";
15
15
  export * from "./zap.js";
16
16
  export * from "./bolt11.js";
17
+ export * from "./hidden-tags.js";
@@ -14,3 +14,4 @@ export * from "./hashtag.js";
14
14
  export * from "./url.js";
15
15
  export * from "./zap.js";
16
16
  export * from "./bolt11.js";
17
+ export * from "./hidden-tags.js";
@@ -1 +1,2 @@
1
+ /** Returns the parsed JSON or undefined if invalid */
1
2
  export declare function safeParse<T extends unknown = any>(str: string): T | undefined;
@@ -1,3 +1,4 @@
1
+ /** Returns the parsed JSON or undefined if invalid */
1
2
  export function safeParse(str) {
2
3
  try {
3
4
  return JSON.parse(str);
@@ -1,12 +1,6 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  export declare const MailboxesInboxesSymbol: unique symbol;
3
3
  export declare const MailboxesOutboxesSymbol: unique symbol;
4
- declare module "nostr-tools" {
5
- interface Event {
6
- [MailboxesInboxesSymbol]?: string[];
7
- [MailboxesOutboxesSymbol]?: string[];
8
- }
9
- }
10
4
  /**
11
5
  * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
12
6
  */
@@ -1,11 +1,12 @@
1
1
  import { safeRelayUrl } from "./relays.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
2
3
  export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
3
4
  export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
4
5
  /**
5
6
  * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
6
7
  */
7
8
  export function getInboxes(event) {
8
- if (!event[MailboxesInboxesSymbol]) {
9
+ return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
9
10
  const inboxes = [];
10
11
  for (const tag of event.tags) {
11
12
  if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
@@ -14,15 +15,14 @@ export function getInboxes(event) {
14
15
  inboxes.push(url);
15
16
  }
16
17
  }
17
- event[MailboxesInboxesSymbol] = inboxes;
18
- }
19
- return event[MailboxesInboxesSymbol];
18
+ return inboxes;
19
+ });
20
20
  }
21
21
  /**
22
22
  * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
23
23
  */
24
24
  export function getOutboxes(event) {
25
- if (!event[MailboxesOutboxesSymbol]) {
25
+ return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
26
26
  const outboxes = [];
27
27
  for (const tag of event.tags) {
28
28
  if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
@@ -31,7 +31,6 @@ export function getOutboxes(event) {
31
31
  outboxes.push(url);
32
32
  }
33
33
  }
34
- event[MailboxesOutboxesSymbol] = outboxes;
35
- }
36
- return event[MailboxesOutboxesSymbol];
34
+ return outboxes;
35
+ });
37
36
  }
@@ -16,3 +16,5 @@ export type ProfileContent = {
16
16
  };
17
17
  /** Returns the parsed profile content for a kind 0 event */
18
18
  export declare function getProfileContent(event: NostrEvent): ProfileContent;
19
+ /** Checks if the content of the kind 0 event is valid JSON */
20
+ export declare function isValidProfile(profile?: NostrEvent): boolean;
@@ -1,3 +1,4 @@
1
+ import { kinds } from "nostr-tools";
1
2
  import { getOrComputeCachedValue } from "./cache.js";
2
3
  export const ProfileContentSymbol = Symbol.for("profile-content");
3
4
  /** Returns the parsed profile content for a kind 0 event */
@@ -14,3 +15,17 @@ export function getProfileContent(event) {
14
15
  return profile;
15
16
  });
16
17
  }
18
+ /** Checks if the content of the kind 0 event is valid JSON */
19
+ export function isValidProfile(profile) {
20
+ if (!profile)
21
+ return false;
22
+ if (profile.kind !== kinds.Metadata)
23
+ return false;
24
+ try {
25
+ getProfileContent(profile);
26
+ return true;
27
+ }
28
+ catch (error) {
29
+ return false;
30
+ }
31
+ }
@@ -1,4 +1,10 @@
1
+ /** Tests if a string is hex */
1
2
  export declare function isHex(str?: string): boolean;
3
+ /** Tests if a string is a 64 length hex string */
2
4
  export declare function isHexKey(key?: string): boolean;
5
+ /**
6
+ * Remove invisible characters from a string
7
+ * @see read more https://www.regular-expressions.info/unicode.html#category
8
+ */
3
9
  export declare function stripInvisibleChar(str: string): string;
4
10
  export declare function stripInvisibleChar(str?: string | undefined): string | undefined;
@@ -1,8 +1,10 @@
1
+ /** Tests if a string is hex */
1
2
  export function isHex(str) {
2
3
  if (str?.match(/^[0-9a-f]+$/i))
3
4
  return true;
4
5
  return false;
5
6
  }
7
+ /** Tests if a string is a 64 length hex string */
6
8
  export function isHexKey(key) {
7
9
  if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/))
8
10
  return true;
@@ -11,3 +11,4 @@ export declare function getZapAddressPointer(zap: NostrEvent): import("nostr-too
11
11
  export declare function getZapEventPointer(zap: NostrEvent): import("nostr-tools/nip19").EventPointer | null;
12
12
  export declare function getZapPreimage(zap: NostrEvent): string | undefined;
13
13
  export declare function getZapRequest(zap: NostrEvent): import("nostr-tools").Event;
14
+ export declare function isValidZap(zap?: NostrEvent): boolean;
@@ -1,4 +1,4 @@
1
- import { nip57 } from "nostr-tools";
1
+ import { kinds, nip57 } from "nostr-tools";
2
2
  import { getOrComputeCachedValue } from "./cache.js";
3
3
  import { getTagValue } from "./event.js";
4
4
  import { isATag, isETag } from "./tags.js";
@@ -50,3 +50,17 @@ export function getZapRequest(zap) {
50
50
  return JSON.parse(description);
51
51
  });
52
52
  }
53
+ export function isValidZap(zap) {
54
+ if (!zap)
55
+ return false;
56
+ if (zap.kind !== kinds.Zap)
57
+ return false;
58
+ try {
59
+ getZapRequest(zap);
60
+ getZapRecipient(zap);
61
+ return true;
62
+ }
63
+ catch (error) {
64
+ return false;
65
+ }
66
+ }
@@ -1,11 +1,11 @@
1
1
  import { kinds } from "nostr-tools";
2
- import { map } from "rxjs/operators";
3
- import { getProfileContent } from "../helpers/profile.js";
2
+ import { filter, map } from "rxjs/operators";
3
+ import { getProfileContent, isValidProfile } from "../helpers/profile.js";
4
4
  export function ProfileQuery(pubkey) {
5
5
  return {
6
6
  key: pubkey,
7
7
  run: (events) => {
8
- return events.replaceable(kinds.Metadata, pubkey).pipe(map((event) => event && getProfileContent(event)));
8
+ return events.replaceable(kinds.Metadata, pubkey).pipe(filter(isValidProfile), map((event) => event && getProfileContent(event)));
9
9
  },
10
10
  };
11
11
  }
@@ -1,15 +1,19 @@
1
+ import { map } from "rxjs";
1
2
  import { kinds } from "nostr-tools";
2
3
  import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
4
+ import { isValidZap } from "../helpers/zap.js";
3
5
  export function EventZapsQuery(id) {
4
6
  return {
5
7
  key: JSON.stringify(id),
6
8
  run: (events) => {
7
9
  if (isAddressPointer(id)) {
8
- return events.timeline([{ kinds: [kinds.Zap], "#a": [getCoordinateFromAddressPointer(id)] }]);
10
+ return events
11
+ .timeline([{ kinds: [kinds.Zap], "#a": [getCoordinateFromAddressPointer(id)] }])
12
+ .pipe(map((events) => events.filter(isValidZap)));
9
13
  }
10
14
  else {
11
15
  id = typeof id === "string" ? id : id.id;
12
- return events.timeline([{ kinds: [kinds.Zap], "#e": [id] }]);
16
+ return events.timeline([{ kinds: [kinds.Zap], "#e": [id] }]).pipe(map((events) => events.filter(isValidZap)));
13
17
  }
14
18
  },
15
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.0.0-next-20241115160057",
3
+ "version": "0.0.0-next-20241119160247",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",