applesauce-core 0.9.0 → 0.10.0

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.
Files changed (74) hide show
  1. package/README.md +1 -1
  2. package/dist/event-store/database.d.ts +17 -13
  3. package/dist/event-store/database.js +50 -33
  4. package/dist/event-store/event-store.d.ts +37 -14
  5. package/dist/event-store/event-store.js +249 -96
  6. package/dist/helpers/bolt11.d.ts +1 -0
  7. package/dist/helpers/bolt11.js +1 -0
  8. package/dist/helpers/comment.d.ts +48 -0
  9. package/dist/helpers/comment.js +116 -0
  10. package/dist/helpers/content.d.ts +3 -0
  11. package/dist/helpers/content.js +8 -0
  12. package/dist/helpers/delete.d.ts +3 -0
  13. package/dist/helpers/delete.js +7 -0
  14. package/dist/helpers/emoji.d.ts +10 -1
  15. package/dist/helpers/emoji.js +12 -0
  16. package/dist/helpers/event.d.ts +9 -1
  17. package/dist/helpers/event.js +25 -1
  18. package/dist/helpers/external-id.d.ts +29 -0
  19. package/dist/helpers/external-id.js +20 -0
  20. package/dist/helpers/filter.d.ts +0 -2
  21. package/dist/helpers/filter.js +3 -7
  22. package/dist/helpers/hidden-tags.d.ts +48 -0
  23. package/dist/helpers/hidden-tags.js +108 -0
  24. package/dist/helpers/hidden-tags.test.d.ts +1 -0
  25. package/dist/helpers/hidden-tags.test.js +28 -0
  26. package/dist/helpers/index.d.ts +16 -8
  27. package/dist/helpers/index.js +16 -8
  28. package/dist/helpers/json.d.ts +1 -0
  29. package/dist/helpers/json.js +1 -0
  30. package/dist/helpers/lnurl.d.ts +4 -0
  31. package/dist/helpers/lnurl.js +40 -0
  32. package/dist/helpers/mailboxes.test.js +13 -12
  33. package/dist/helpers/media-attachment.d.ts +33 -0
  34. package/dist/helpers/media-attachment.js +60 -0
  35. package/dist/helpers/pointers.d.ts +38 -5
  36. package/dist/helpers/pointers.js +101 -17
  37. package/dist/helpers/profile.js +1 -1
  38. package/dist/helpers/string.d.ts +6 -0
  39. package/dist/helpers/string.js +2 -0
  40. package/dist/helpers/tags.d.ts +6 -0
  41. package/dist/helpers/tags.js +6 -0
  42. package/dist/helpers/threading.d.ts +6 -6
  43. package/dist/helpers/threading.js +30 -9
  44. package/dist/helpers/threading.test.d.ts +1 -0
  45. package/dist/helpers/threading.test.js +41 -0
  46. package/dist/helpers/url.d.ts +4 -1
  47. package/dist/helpers/url.js +4 -3
  48. package/dist/helpers/zap.d.ts +25 -0
  49. package/dist/helpers/zap.js +32 -3
  50. package/dist/observable/{getValue.d.ts → get-value.d.ts} +1 -0
  51. package/dist/observable/{getValue.js → get-value.js} +1 -0
  52. package/dist/observable/index.d.ts +1 -1
  53. package/dist/observable/index.js +1 -1
  54. package/dist/promise/deferred.d.ts +1 -0
  55. package/dist/promise/deferred.js +1 -0
  56. package/dist/queries/comments.d.ts +4 -0
  57. package/dist/queries/comments.js +14 -0
  58. package/dist/queries/index.d.ts +3 -2
  59. package/dist/queries/index.js +3 -2
  60. package/dist/queries/mailboxes.d.ts +1 -0
  61. package/dist/queries/mailboxes.js +1 -0
  62. package/dist/queries/profile.d.ts +1 -0
  63. package/dist/queries/profile.js +1 -0
  64. package/dist/queries/reactions.d.ts +1 -1
  65. package/dist/queries/reactions.js +1 -1
  66. package/dist/queries/simple.d.ts +3 -3
  67. package/dist/queries/simple.js +13 -13
  68. package/dist/queries/thread.d.ts +2 -0
  69. package/dist/queries/thread.js +29 -3
  70. package/dist/queries/zaps.d.ts +1 -0
  71. package/dist/queries/zaps.js +1 -0
  72. package/dist/query-store/index.d.ts +22 -12
  73. package/dist/query-store/index.js +36 -30
  74. package/package.json +10 -18
@@ -1,2 +1,11 @@
1
1
  import { EventTemplate, NostrEvent } from "nostr-tools";
2
- export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string];
2
+ /** Gets an "emoji" tag that matches an emoji code */
3
+ export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string] | undefined;
4
+ /** Returns the name of a NIP-30 emoji pack */
5
+ export declare function getPackName(pack: NostrEvent): string | undefined;
6
+ export type Emoji = {
7
+ name: string;
8
+ url: string;
9
+ };
10
+ /** Returns an array of emojis from a NIP-30 emoji pack */
11
+ export declare function getEmojis(pack: NostrEvent): Emoji[];
@@ -1,4 +1,16 @@
1
+ import { getTagValue } from "./event.js";
2
+ /** Gets an "emoji" tag that matches an emoji code */
1
3
  export function getEmojiTag(event, code) {
2
4
  code = code.replace(/^:|:$/g, "").toLocaleLowerCase();
3
5
  return event.tags.filter((t) => t[0] === "emoji" && t[1] && t[2]).find((t) => t[1].toLowerCase() === code);
4
6
  }
7
+ /** Returns the name of a NIP-30 emoji pack */
8
+ export function getPackName(pack) {
9
+ return getTagValue(pack, "title") || getTagValue(pack, "d");
10
+ }
11
+ /** Returns an array of emojis from a NIP-30 emoji pack */
12
+ export function getEmojis(pack) {
13
+ return pack.tags
14
+ .filter((t) => t[0] === "emoji" && t[1] && t[2])
15
+ .map((t) => ({ name: t[1], url: t[2] }));
16
+ }
@@ -9,6 +9,11 @@ declare module "nostr-tools" {
9
9
  [FromCacheSymbol]?: boolean;
10
10
  }
11
11
  }
12
+ /**
13
+ * Checks if an object is a nostr event
14
+ * NOTE: does not validation the signature on the event
15
+ */
16
+ export declare function isEvent(event: any): event is NostrEvent;
12
17
  /**
13
18
  * Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )
14
19
  * or parameterized replaceable ( 30000 <= n < 40000 )
@@ -24,7 +29,10 @@ export declare function getEventUID(event: NostrEvent): string;
24
29
  export declare function getReplaceableUID(kind: number, pubkey: string, d?: string): string;
25
30
  /** Returns a Set of tag names and values that are indexable */
26
31
  export declare function getIndexableTags(event: NostrEvent): Set<string>;
27
- /** Returns the second index ( tag[1] ) of the first tag that matches the name */
32
+ /**
33
+ * Returns the second index ( tag[1] ) of the first tag that matches the name
34
+ * If the event has any hidden tags they will be searched first
35
+ */
28
36
  export declare function getTagValue(event: NostrEvent, name: string): string | undefined;
29
37
  /** Sets events verified flag without checking anything */
30
38
  export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
@@ -1,8 +1,25 @@
1
1
  import { kinds, verifiedSymbol } from "nostr-tools";
2
2
  import { INDEXABLE_TAGS } from "../event-store/common.js";
3
+ import { getHiddenTags } from "./hidden-tags.js";
3
4
  export const EventUIDSymbol = Symbol.for("event-uid");
4
5
  export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
5
6
  export const FromCacheSymbol = Symbol.for("from-cache");
7
+ /**
8
+ * Checks if an object is a nostr event
9
+ * NOTE: does not validation the signature on the event
10
+ */
11
+ export function isEvent(event) {
12
+ if (event === undefined || event === null)
13
+ return false;
14
+ return (event.id?.length === 64 &&
15
+ typeof event.sig === "string" &&
16
+ typeof event.pubkey === "string" &&
17
+ event.pubkey.length === 64 &&
18
+ typeof event.content === "string" &&
19
+ Array.isArray(event.tags) &&
20
+ typeof event.created_at === "number" &&
21
+ event.created_at > 0);
22
+ }
6
23
  /**
7
24
  * Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )
8
25
  * or parameterized replaceable ( 30000 <= n < 40000 )
@@ -46,8 +63,15 @@ export function getIndexableTags(event) {
46
63
  }
47
64
  return indexable;
48
65
  }
49
- /** Returns the second index ( tag[1] ) of the first tag that matches the name */
66
+ /**
67
+ * Returns the second index ( tag[1] ) of the first tag that matches the name
68
+ * If the event has any hidden tags they will be searched first
69
+ */
50
70
  export function getTagValue(event, name) {
71
+ const hidden = getHiddenTags(event);
72
+ const hiddenValue = hidden?.find((t) => t[0] === name)?.[1];
73
+ if (hiddenValue)
74
+ return hiddenValue;
51
75
  return event.tags.find((t) => t[0] === name)?.[1];
52
76
  }
53
77
  /** Sets events verified flag without checking anything */
@@ -0,0 +1,29 @@
1
+ export type ExternalIdentifiers = {
2
+ "#": `#${string}`;
3
+ geo: `geo:${string}`;
4
+ isbn: `isbn:${string}`;
5
+ "podcast:guid": `podcast:guid:${string}`;
6
+ "podcast:item:guid": `podcast:item:guid:${string}`;
7
+ "podcast:publisher:guid": `podcast:publisher:guid:${string}`;
8
+ isan: `isan:${string}`;
9
+ doi: `doi:${string}`;
10
+ };
11
+ export type ExternalPointer<Prefix extends keyof ExternalIdentifiers> = {
12
+ kind: Prefix;
13
+ identifier: ExternalIdentifiers[Prefix];
14
+ };
15
+ export type ParseResult = {
16
+ [P in keyof ExternalIdentifiers]: ExternalPointer<P>;
17
+ }[keyof ExternalIdentifiers];
18
+ /**
19
+ * Parses a NIP-73 external identifier
20
+ * @throws
21
+ */
22
+ export declare function parseExternalPointer<Prefix extends keyof ExternalIdentifiers>(identifier: `${Prefix}1${string}`): ExternalPointer<Prefix>;
23
+ export declare function parseExternalPointer(identifier: string): ParseResult;
24
+ /**
25
+ * Gets an ExternalPointer for a "i" tag
26
+ * @throws
27
+ */
28
+ export declare function getExternalPointerFromTag<Prefix extends keyof ExternalIdentifiers>(tag: string[]): ExternalPointer<Prefix>;
29
+ export declare function getExternalPointerFromTag(tag: string[]): ParseResult;
@@ -0,0 +1,20 @@
1
+ export function parseExternalPointer(identifier) {
2
+ if (identifier.startsWith("#"))
3
+ return { kind: "#", identifier: identifier };
4
+ if (identifier.startsWith("geo:"))
5
+ return { kind: "geo", identifier: identifier };
6
+ if (identifier.startsWith("podcast:guid:"))
7
+ return { kind: "podcast:guid", identifier: identifier };
8
+ if (identifier.startsWith("podcast:item:guid:"))
9
+ return { kind: "podcast:item:guid", identifier: identifier };
10
+ if (identifier.startsWith("podcast:publisher:guid:"))
11
+ return { kind: "podcast:publisher:guid", identifier: identifier };
12
+ if (identifier.startsWith("isan:"))
13
+ return { kind: "isan", identifier: identifier };
14
+ if (identifier.startsWith("doi:"))
15
+ return { kind: "doi", identifier: identifier };
16
+ throw new Error("Failed to parse external identifier");
17
+ }
18
+ export function getExternalPointerFromTag(tag) {
19
+ return parseExternalPointer(tag[1]);
20
+ }
@@ -6,7 +6,5 @@ import { Filter, NostrEvent } from "nostr-tools";
6
6
  export declare function matchFilter(filter: Filter, event: NostrEvent): boolean;
7
7
  /** Copied from nostr-tools */
8
8
  export declare function matchFilters(filters: Filter[], event: NostrEvent): boolean;
9
- /** Stringify filters in a predictable way */
10
- export declare function stringifyFilter(filter: Filter | Filter[]): string;
11
9
  /** Check if two filters are equal */
12
10
  export declare function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]): boolean;
@@ -1,5 +1,5 @@
1
1
  import { getIndexableTags } from "./event.js";
2
- import stringify from "json-stringify-deterministic";
2
+ import equal from "fast-deep-equal";
3
3
  /**
4
4
  * Copied from nostr-tools and modified to use getIndexableTags
5
5
  * @see https://github.com/nbd-wtf/nostr-tools/blob/a61cde77eacc9518001f11d7f67f1a50ae05fd80/filter.ts
@@ -20,7 +20,7 @@ export function matchFilter(filter, event) {
20
20
  let values = filter[`#${tagName}`];
21
21
  if (values) {
22
22
  const tags = getIndexableTags(event);
23
- if (values.some((v) => !tags.has(tagName + ":" + v)))
23
+ if (values.some((v) => tags.has(tagName + ":" + v)) === false)
24
24
  return false;
25
25
  }
26
26
  }
@@ -40,11 +40,7 @@ export function matchFilters(filters, event) {
40
40
  }
41
41
  return false;
42
42
  }
43
- /** Stringify filters in a predictable way */
44
- export function stringifyFilter(filter) {
45
- return stringify(filter);
46
- }
47
43
  /** Check if two filters are equal */
48
44
  export function isFilterEqual(a, b) {
49
- return stringifyFilter(a) === stringifyFilter(b);
45
+ return equal(a, b);
50
46
  }
@@ -0,0 +1,48 @@
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
+ nip44?: {
9
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
10
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
11
+ };
12
+ };
13
+ export type TagOperation = (tags: string[][]) => string[][];
14
+ export declare const HiddenTagsSymbol: unique symbol;
15
+ /** Various event kinds that can have encrypted tags in their content and which encryption method they use */
16
+ export declare const EventEncryptionMethod: Record<number, "nip04" | "nip44">;
17
+ /** Checks if an event can have hidden tags */
18
+ export declare function canHaveHiddenTags(kind: number): boolean;
19
+ /** Checks if an event has hidden tags */
20
+ export declare function hasHiddenTags(event: NostrEvent | EventTemplate): boolean;
21
+ /** Returns the hidden tags for an event if they are unlocked */
22
+ export declare function getHiddenTags(event: NostrEvent | EventTemplate): string[][] | undefined;
23
+ /** Checks if the hidden tags are locked */
24
+ export declare function isHiddenTagsLocked(event: NostrEvent): boolean;
25
+ /**
26
+ * Decrypts the private list
27
+ * @param event The list event to decrypt
28
+ * @param signer A signer to use to decrypt the tags
29
+ * @param store An optional EventStore to notify about the update
30
+ * @throws
31
+ */
32
+ export declare function unlockHiddenTags(event: NostrEvent, signer: HiddenTagsSigner, store?: EventStore): Promise<NostrEvent>;
33
+ /**
34
+ * Modifies tags and returns an EventTemplate
35
+ * @param event Event to modify
36
+ * @param operations Operations for hidden and public tags
37
+ * @param signer A signer to use to decrypt the tags
38
+ * @throws
39
+ */
40
+ export declare function modifyEventTags(event: NostrEvent | UnsignedEvent, operations: {
41
+ public?: TagOperation;
42
+ hidden?: TagOperation;
43
+ }, signer?: HiddenTagsSigner): Promise<EventTemplate>;
44
+ /**
45
+ * Override the hidden tags in an event
46
+ * @throws
47
+ */
48
+ export declare function overrideHiddenTags(event: NostrEvent, hidden: string[][], signer: HiddenTagsSigner): Promise<EventTemplate>;
@@ -0,0 +1,108 @@
1
+ import { unixNow } from "applesauce-core/helpers";
2
+ import { kinds } from "nostr-tools";
3
+ export const HiddenTagsSymbol = Symbol.for("hidden-tags");
4
+ /** Various event kinds that can have encrypted tags in their content and which encryption method they use */
5
+ export const EventEncryptionMethod = {
6
+ // NIP-60 wallet
7
+ 37375: "nip44",
8
+ // NIP-51 lists
9
+ [kinds.BookmarkList]: "nip04",
10
+ [kinds.InterestsList]: "nip04",
11
+ [kinds.Mutelist]: "nip04",
12
+ [kinds.CommunitiesList]: "nip04",
13
+ [kinds.PublicChatsList]: "nip04",
14
+ [kinds.SearchRelaysList]: "nip04",
15
+ // NIP-51 sets
16
+ [kinds.Bookmarksets]: "nip04",
17
+ [kinds.Relaysets]: "nip04",
18
+ [kinds.Followsets]: "nip04",
19
+ [kinds.Curationsets]: "nip04",
20
+ [kinds.Interestsets]: "nip04",
21
+ };
22
+ /** Checks if an event can have hidden tags */
23
+ export function canHaveHiddenTags(kind) {
24
+ return EventEncryptionMethod[kind] !== undefined;
25
+ }
26
+ /** Checks if an event has hidden tags */
27
+ export function hasHiddenTags(event) {
28
+ return canHaveHiddenTags(event.kind) && event.content.length > 0;
29
+ }
30
+ /** Returns the hidden tags for an event if they are unlocked */
31
+ export function getHiddenTags(event) {
32
+ return Reflect.get(event, HiddenTagsSymbol);
33
+ }
34
+ /** Checks if the hidden tags are locked */
35
+ export function isHiddenTagsLocked(event) {
36
+ return hasHiddenTags(event) && getHiddenTags(event) === undefined;
37
+ }
38
+ function getEventEncryption(kind, signer) {
39
+ const method = EventEncryptionMethod[kind];
40
+ const encryption = signer[method];
41
+ if (!encryption)
42
+ throw new Error(`Signer does not support ${method} encryption`);
43
+ return encryption;
44
+ }
45
+ /**
46
+ * Decrypts the private list
47
+ * @param event The list event to decrypt
48
+ * @param signer A signer to use to decrypt the tags
49
+ * @param store An optional EventStore to notify about the update
50
+ * @throws
51
+ */
52
+ export async function unlockHiddenTags(event, signer, store) {
53
+ if (!canHaveHiddenTags(event.kind))
54
+ throw new Error("Event kind does not support hidden tags");
55
+ const encryption = getEventEncryption(event.kind, signer);
56
+ const plaintext = await encryption.decrypt(event.pubkey, event.content);
57
+ const parsed = JSON.parse(plaintext);
58
+ if (!Array.isArray(parsed))
59
+ throw new Error("Content is not an array of tags");
60
+ // Convert array to tags array string[][]
61
+ const tags = parsed.filter((t) => Array.isArray(t)).map((t) => t.map((v) => String(v)));
62
+ Reflect.set(event, HiddenTagsSymbol, tags);
63
+ if (store)
64
+ store.update(event);
65
+ return event;
66
+ }
67
+ /**
68
+ * Modifies tags and returns an EventTemplate
69
+ * @param event Event to modify
70
+ * @param operations Operations for hidden and public tags
71
+ * @param signer A signer to use to decrypt the tags
72
+ * @throws
73
+ */
74
+ export async function modifyEventTags(event, operations, signer) {
75
+ const draft = { content: event.content, tags: event.tags, kind: event.kind, created_at: unixNow() };
76
+ if (operations.public) {
77
+ draft.tags = operations.public(event.tags);
78
+ }
79
+ if (operations.hidden) {
80
+ if (!signer)
81
+ throw new Error("Missing signer for hidden tags");
82
+ if (!canHaveHiddenTags(event.kind))
83
+ throw new Error("Event kind does not support hidden tags");
84
+ const hidden = hasHiddenTags(event) ? getHiddenTags(event) : [];
85
+ if (!hidden)
86
+ throw new Error("Hidden tags are locked");
87
+ const newHidden = operations.hidden(hidden);
88
+ const encryption = getEventEncryption(event.kind, signer);
89
+ draft.content = await encryption.encrypt(event.pubkey, JSON.stringify(newHidden));
90
+ }
91
+ return draft;
92
+ }
93
+ /**
94
+ * Override the hidden tags in an event
95
+ * @throws
96
+ */
97
+ export async function overrideHiddenTags(event, hidden, signer) {
98
+ if (!canHaveHiddenTags(event.kind))
99
+ throw new Error("Event kind does not support hidden tags");
100
+ const encryption = getEventEncryption(event.kind, signer);
101
+ const ciphertext = await encryption.encrypt(event.pubkey, JSON.stringify(hidden));
102
+ return {
103
+ kind: event.kind,
104
+ content: ciphertext,
105
+ created_at: unixNow(),
106
+ tags: event.tags,
107
+ };
108
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { describe, beforeEach, it, expect } from "vitest";
2
+ import { getHiddenTags, unixNow, unlockHiddenTags } from "applesauce-core/helpers";
3
+ import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools";
4
+ const key = generateSecretKey();
5
+ const pubkey = getPublicKey(key);
6
+ const signer = {
7
+ nip04: {
8
+ encrypt: (pubkey, plaintext) => nip04.encrypt(key, pubkey, plaintext),
9
+ decrypt: (pubkey, ciphertext) => nip04.decrypt(key, pubkey, ciphertext),
10
+ },
11
+ };
12
+ describe("Private Lists", () => {
13
+ describe("unlockHiddenTags", () => {
14
+ let list;
15
+ beforeEach(async () => {
16
+ list = finalizeEvent({
17
+ kind: kinds.Mutelist,
18
+ created_at: unixNow(),
19
+ content: await nip04.encrypt(key, pubkey, JSON.stringify([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]])),
20
+ tags: [],
21
+ }, key);
22
+ });
23
+ it("should unlock hidden tags", async () => {
24
+ await unlockHiddenTags(list, signer);
25
+ expect(getHiddenTags(list)).toEqual(expect.arrayContaining([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]]));
26
+ });
27
+ });
28
+ });
@@ -1,16 +1,24 @@
1
- export * from "./profile.js";
2
- export * from "./relays.js";
1
+ export * from "./bolt11.js";
2
+ export * from "./cache.js";
3
+ export * from "./comment.js";
4
+ export * from "./content.js";
5
+ export * from "./delete.js";
6
+ export * from "./emoji.js";
3
7
  export * from "./event.js";
8
+ export * from "./external-id.js";
4
9
  export * from "./filter.js";
10
+ export * from "./hashtag.js";
11
+ export * from "./hidden-tags.js";
12
+ export * from "./lnurl.js";
13
+ export * from "./lru.js";
5
14
  export * from "./mailboxes.js";
6
- export * from "./threading.js";
15
+ export * from "./media-attachment.js";
7
16
  export * from "./pointers.js";
17
+ export * from "./profile.js";
18
+ export * from "./relays.js";
8
19
  export * from "./string.js";
9
- export * from "./time.js";
10
20
  export * from "./tags.js";
11
- export * from "./emoji.js";
12
- export * from "./lru.js";
13
- export * from "./hashtag.js";
21
+ export * from "./threading.js";
22
+ export * from "./time.js";
14
23
  export * from "./url.js";
15
24
  export * from "./zap.js";
16
- export * from "./bolt11.js";
@@ -1,16 +1,24 @@
1
- export * from "./profile.js";
2
- export * from "./relays.js";
1
+ export * from "./bolt11.js";
2
+ export * from "./cache.js";
3
+ export * from "./comment.js";
4
+ export * from "./content.js";
5
+ export * from "./delete.js";
6
+ export * from "./emoji.js";
3
7
  export * from "./event.js";
8
+ export * from "./external-id.js";
4
9
  export * from "./filter.js";
10
+ export * from "./hashtag.js";
11
+ export * from "./hidden-tags.js";
12
+ export * from "./lnurl.js";
13
+ export * from "./lru.js";
5
14
  export * from "./mailboxes.js";
6
- export * from "./threading.js";
15
+ export * from "./media-attachment.js";
7
16
  export * from "./pointers.js";
17
+ export * from "./profile.js";
18
+ export * from "./relays.js";
8
19
  export * from "./string.js";
9
- export * from "./time.js";
10
20
  export * from "./tags.js";
11
- export * from "./emoji.js";
12
- export * from "./lru.js";
13
- export * from "./hashtag.js";
21
+ export * from "./threading.js";
22
+ export * from "./time.js";
14
23
  export * from "./url.js";
15
24
  export * from "./zap.js";
16
- export * from "./bolt11.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);
@@ -0,0 +1,4 @@
1
+ export declare function parseLightningAddress(address: string): URL | undefined;
2
+ export declare function decodeLNURL(lnurl: string): URL | undefined;
3
+ export declare function parseLNURLOrAddress(addressOrLNURL: string): URL | undefined;
4
+ export declare function getInvoice(callback: URL): Promise<string>;
@@ -0,0 +1,40 @@
1
+ import { bech32 } from "@scure/base";
2
+ import { parseBolt11 } from "./bolt11.js";
3
+ const decoder = new TextDecoder();
4
+ export function parseLightningAddress(address) {
5
+ let [name, domain] = address.split("@");
6
+ if (!name || !domain)
7
+ return;
8
+ return new URL(`https://${domain}/.well-known/lnurlp/${name}`);
9
+ }
10
+ export function decodeLNURL(lnurl) {
11
+ try {
12
+ const { words, prefix } = bech32.decode(lnurl);
13
+ if (prefix !== "lnurl")
14
+ return;
15
+ const str = decoder.decode(bech32.fromWords(words));
16
+ return new URL(str);
17
+ }
18
+ catch (error) { }
19
+ return undefined;
20
+ }
21
+ export function parseLNURLOrAddress(addressOrLNURL) {
22
+ if (addressOrLNURL.includes("@")) {
23
+ return parseLightningAddress(addressOrLNURL);
24
+ }
25
+ return decodeLNURL(addressOrLNURL);
26
+ }
27
+ export async function getInvoice(callback) {
28
+ const { pr: payRequest } = await fetch(callback).then((res) => res.json());
29
+ const amount = callback.searchParams.get("amount");
30
+ if (!amount)
31
+ throw new Error("Missing amount");
32
+ if (payRequest) {
33
+ const parsed = parseBolt11(payRequest);
34
+ if (parsed.amount !== parseInt(amount))
35
+ throw new Error("Incorrect amount");
36
+ return payRequest;
37
+ }
38
+ else
39
+ throw new Error("Failed to get invoice");
40
+ }
@@ -1,3 +1,4 @@
1
+ import { describe, test, expect } from "vitest";
1
2
  import { getInboxes, getOutboxes } from "./mailboxes.js";
2
3
  const emptyEvent = {
3
4
  kind: 10002,
@@ -14,33 +15,33 @@ describe("Mailboxes", () => {
14
15
  expect(Array.from(getInboxes({
15
16
  ...emptyEvent,
16
17
  tags: [["r", "wss://inbox.com"]],
17
- }))).toIncludeAllMembers(["wss://inbox.com/"]);
18
+ }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
18
19
  });
19
20
  test("should remove bad urls", () => {
20
21
  expect(Array.from(getInboxes({
21
22
  ...emptyEvent,
22
23
  tags: [["r", "bad://inbox.com"]],
23
- }))).toBeArrayOfSize(0);
24
+ }))).toHaveLength(0);
24
25
  expect(Array.from(getInboxes({
25
26
  ...emptyEvent,
26
27
  tags: [["r", "something that is not a url"]],
27
- }))).toBeArrayOfSize(0);
28
+ }))).toHaveLength(0);
28
29
  expect(Array.from(getInboxes({
29
30
  ...emptyEvent,
30
31
  tags: [["r", "wss://inbox.com,wss://inbox.org"]],
31
- }))).toBeArrayOfSize(0);
32
+ }))).toHaveLength(0);
32
33
  });
33
34
  test("without marker", () => {
34
35
  expect(Array.from(getInboxes({
35
36
  ...emptyEvent,
36
37
  tags: [["r", "wss://inbox.com/"]],
37
- }))).toIncludeAllMembers(["wss://inbox.com/"]);
38
+ }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
38
39
  });
39
40
  test("with marker", () => {
40
41
  expect(Array.from(getInboxes({
41
42
  ...emptyEvent,
42
43
  tags: [["r", "wss://inbox.com/", "read"]],
43
- }))).toIncludeAllMembers(["wss://inbox.com/"]);
44
+ }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
44
45
  });
45
46
  });
46
47
  describe("getOutboxes", () => {
@@ -48,33 +49,33 @@ describe("Mailboxes", () => {
48
49
  expect(Array.from(getOutboxes({
49
50
  ...emptyEvent,
50
51
  tags: [["r", "wss://outbox.com"]],
51
- }))).toIncludeAllMembers(["wss://outbox.com/"]);
52
+ }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
52
53
  });
53
54
  test("should remove bad urls", () => {
54
55
  expect(Array.from(getOutboxes({
55
56
  ...emptyEvent,
56
57
  tags: [["r", "bad://inbox.com"]],
57
- }))).toBeArrayOfSize(0);
58
+ }))).toHaveLength(0);
58
59
  expect(Array.from(getOutboxes({
59
60
  ...emptyEvent,
60
61
  tags: [["r", "something that is not a url"]],
61
- }))).toBeArrayOfSize(0);
62
+ }))).toHaveLength(0);
62
63
  expect(Array.from(getOutboxes({
63
64
  ...emptyEvent,
64
65
  tags: [["r", "wss://outbox.com,wss://inbox.org"]],
65
- }))).toBeArrayOfSize(0);
66
+ }))).toHaveLength(0);
66
67
  });
67
68
  test("without marker", () => {
68
69
  expect(Array.from(getOutboxes({
69
70
  ...emptyEvent,
70
71
  tags: [["r", "wss://outbox.com/"]],
71
- }))).toIncludeAllMembers(["wss://outbox.com/"]);
72
+ }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
72
73
  });
73
74
  test("with marker", () => {
74
75
  expect(Array.from(getOutboxes({
75
76
  ...emptyEvent,
76
77
  tags: [["r", "wss://outbox.com/", "write"]],
77
- }))).toIncludeAllMembers(["wss://outbox.com/"]);
78
+ }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
78
79
  });
79
80
  });
80
81
  });
@@ -0,0 +1,33 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export type MediaAttachment = {
3
+ /** URL of the file */
4
+ url: string;
5
+ /** mime type */
6
+ type?: string;
7
+ /** sha256 hash of the file */
8
+ sha256?: string;
9
+ /** size of the file in bytes */
10
+ size?: number;
11
+ /** size of file in pixels in the form <width>x<height> */
12
+ dimensions?: string;
13
+ /** magnet */
14
+ magnet?: string;
15
+ /** torrent infohash */
16
+ infohash?: string;
17
+ /** URL to a thumbnail */
18
+ thumb?: string;
19
+ /** URL to a preview image with the same dimensions */
20
+ image?: string;
21
+ /** summary */
22
+ summary?: string;
23
+ /** description for accessability */
24
+ alt?: string;
25
+ };
26
+ /**
27
+ * Parses a imeta tag into a {@link MediaAttachment}
28
+ * @throws
29
+ */
30
+ export declare function parseMediaAttachmentTag(tag: string[]): MediaAttachment;
31
+ export declare const MediaAttachmentsSymbol: unique symbol;
32
+ /** Gets all the media attachments on an event */
33
+ export declare function getMediaAttachments(event: NostrEvent): MediaAttachment[];