applesauce-core 0.10.0 → 0.11.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 (120) hide show
  1. package/dist/__tests__/fixtures.d.ts +8 -0
  2. package/dist/__tests__/fixtures.js +20 -0
  3. package/dist/event-store/__tests__/event-store.test.js +259 -0
  4. package/dist/event-store/database.d.ts +6 -4
  5. package/dist/event-store/database.js +13 -7
  6. package/dist/event-store/event-store.d.ts +30 -16
  7. package/dist/event-store/event-store.js +248 -309
  8. package/dist/helpers/__tests__/blossom.test.js +13 -0
  9. package/dist/helpers/__tests__/comment.test.js +235 -0
  10. package/dist/helpers/__tests__/emoji.test.d.ts +1 -0
  11. package/dist/helpers/__tests__/emoji.test.js +15 -0
  12. package/dist/helpers/__tests__/event.test.d.ts +1 -0
  13. package/dist/helpers/__tests__/event.test.js +36 -0
  14. package/dist/helpers/__tests__/file-metadata.test.d.ts +1 -0
  15. package/dist/helpers/__tests__/file-metadata.test.js +103 -0
  16. package/dist/helpers/__tests__/hidden-tags.test.d.ts +1 -0
  17. package/dist/helpers/{hidden-tags.test.js → __tests__/hidden-tags.test.js} +2 -1
  18. package/dist/helpers/__tests__/mailboxes.test.d.ts +1 -0
  19. package/dist/helpers/{mailboxes.test.js → __tests__/mailboxes.test.js} +1 -1
  20. package/dist/helpers/__tests__/relays.test.d.ts +1 -0
  21. package/dist/helpers/__tests__/relays.test.js +21 -0
  22. package/dist/helpers/__tests__/tags.test.d.ts +1 -0
  23. package/dist/helpers/__tests__/tags.test.js +24 -0
  24. package/dist/helpers/__tests__/threading.test.d.ts +1 -0
  25. package/dist/helpers/{threading.test.js → __tests__/threading.test.js} +1 -1
  26. package/dist/helpers/blossom.d.ts +9 -0
  27. package/dist/helpers/blossom.js +22 -0
  28. package/dist/helpers/bookmarks.d.ts +15 -0
  29. package/dist/helpers/bookmarks.js +27 -0
  30. package/dist/helpers/channels.d.ts +10 -0
  31. package/dist/helpers/channels.js +27 -0
  32. package/dist/helpers/comment.d.ts +3 -4
  33. package/dist/helpers/comment.js +20 -16
  34. package/dist/helpers/contacts.d.ts +3 -0
  35. package/dist/helpers/contacts.js +25 -0
  36. package/dist/helpers/dns-identity.d.ts +7 -0
  37. package/dist/helpers/dns-identity.js +10 -0
  38. package/dist/helpers/emoji.d.ts +3 -1
  39. package/dist/helpers/emoji.js +2 -2
  40. package/dist/helpers/event.d.ts +8 -2
  41. package/dist/helpers/event.js +29 -11
  42. package/dist/helpers/file-metadata.d.ts +55 -0
  43. package/dist/helpers/file-metadata.js +99 -0
  44. package/dist/helpers/filter.d.ts +4 -0
  45. package/dist/helpers/filter.js +34 -1
  46. package/dist/helpers/groups.d.ts +24 -0
  47. package/dist/helpers/groups.js +39 -0
  48. package/dist/helpers/hidden-tags.d.ts +15 -15
  49. package/dist/helpers/hidden-tags.js +9 -31
  50. package/dist/helpers/index.d.ts +13 -1
  51. package/dist/helpers/index.js +13 -1
  52. package/dist/helpers/lists.d.ts +28 -0
  53. package/dist/helpers/lists.js +65 -0
  54. package/dist/helpers/mailboxes.js +16 -9
  55. package/dist/helpers/mutes.d.ts +14 -0
  56. package/dist/helpers/mutes.js +23 -0
  57. package/dist/helpers/picture-post.d.ts +4 -0
  58. package/dist/helpers/picture-post.js +6 -0
  59. package/dist/helpers/pointers.js +13 -17
  60. package/dist/helpers/profile.d.ts +6 -1
  61. package/dist/helpers/profile.js +4 -0
  62. package/dist/helpers/relays.d.ts +6 -3
  63. package/dist/helpers/relays.js +25 -18
  64. package/dist/helpers/share.d.ts +4 -0
  65. package/dist/helpers/share.js +12 -0
  66. package/dist/helpers/tags.d.ts +17 -0
  67. package/dist/helpers/tags.js +28 -6
  68. package/dist/helpers/threading.js +3 -3
  69. package/dist/helpers/url.d.ts +7 -0
  70. package/dist/helpers/url.js +27 -0
  71. package/dist/helpers/user-status.d.ts +18 -0
  72. package/dist/helpers/user-status.js +21 -0
  73. package/dist/observable/__tests__/claim-events.test.d.ts +1 -0
  74. package/dist/observable/__tests__/claim-events.test.js +23 -0
  75. package/dist/observable/__tests__/claim-latest.test.d.ts +1 -0
  76. package/dist/observable/__tests__/claim-latest.test.js +37 -0
  77. package/dist/observable/__tests__/simple-timeout.test.d.ts +1 -0
  78. package/dist/observable/__tests__/simple-timeout.test.js +34 -0
  79. package/dist/observable/claim-events.d.ts +5 -0
  80. package/dist/observable/claim-events.js +28 -0
  81. package/dist/observable/claim-latest.d.ts +4 -0
  82. package/dist/observable/claim-latest.js +20 -0
  83. package/dist/observable/{get-value.d.ts → get-observable-value.d.ts} +1 -1
  84. package/dist/observable/{get-value.js → get-observable-value.js} +3 -8
  85. package/dist/observable/index.d.ts +2 -1
  86. package/dist/observable/index.js +2 -1
  87. package/dist/observable/share-latest-value.d.ts +2 -4
  88. package/dist/observable/share-latest-value.js +19 -16
  89. package/dist/observable/simple-timeout.d.ts +4 -0
  90. package/dist/observable/simple-timeout.js +6 -0
  91. package/dist/queries/blossom.d.ts +2 -0
  92. package/dist/queries/blossom.js +10 -0
  93. package/dist/queries/bookmarks.d.ts +8 -0
  94. package/dist/queries/bookmarks.js +23 -0
  95. package/dist/queries/channels.d.ts +11 -0
  96. package/dist/queries/channels.js +73 -0
  97. package/dist/queries/contacts.d.ts +3 -0
  98. package/dist/queries/contacts.js +12 -0
  99. package/dist/queries/index.d.ts +6 -0
  100. package/dist/queries/index.js +6 -0
  101. package/dist/queries/mutes.d.ts +8 -0
  102. package/dist/queries/mutes.js +23 -0
  103. package/dist/queries/pins.d.ts +3 -0
  104. package/dist/queries/pins.js +12 -0
  105. package/dist/queries/simple.d.ts +2 -2
  106. package/dist/queries/thread.js +1 -1
  107. package/dist/queries/user-status.d.ts +11 -0
  108. package/dist/queries/user-status.js +39 -0
  109. package/dist/query-store/index.d.ts +1 -57
  110. package/dist/query-store/index.js +1 -66
  111. package/dist/query-store/query-store.d.ts +51 -0
  112. package/dist/query-store/query-store.js +88 -0
  113. package/dist/query-store/query-store.test.d.ts +1 -0
  114. package/dist/query-store/query-store.test.js +33 -0
  115. package/package.json +19 -8
  116. package/dist/helpers/media-attachment.d.ts +0 -33
  117. package/dist/helpers/media-attachment.js +0 -60
  118. /package/dist/{helpers/hidden-tags.test.d.ts → event-store/__tests__/event-store.test.d.ts} +0 -0
  119. /package/dist/helpers/{mailboxes.test.d.ts → __tests__/blossom.test.d.ts} +0 -0
  120. /package/dist/helpers/{threading.test.d.ts → __tests__/comment.test.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { getExternalPointerFromTag } from "./external-id.js";
2
2
  import { getOrComputeCachedValue } from "./cache.js";
3
3
  import { getAddressPointerFromATag } from "./pointers.js";
4
- import { safeRelayUrl } from "./relays.js";
4
+ import { isSafeRelayURL } from "./relays.js";
5
5
  export const COMMENT_KIND = 1111;
6
6
  export const CommentRootPointerSymbol = Symbol.for("comment-root-pointer");
7
7
  export const CommentReplyPointerSymbol = Symbol.for("comment-reply-pointer");
@@ -10,16 +10,18 @@ export const CommentReplyPointerSymbol = Symbol.for("comment-reply-pointer");
10
10
  * @throws
11
11
  */
12
12
  export function getCommentEventPointer(tags, root = false) {
13
- const tag = tags.find((t) => t[0] === (root ? "E" : "e"));
13
+ const eTag = tags.find((t) => t[0] === (root ? "E" : "e"));
14
14
  const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
15
- if (tag) {
15
+ if (eTag) {
16
16
  if (!kind)
17
17
  throw new Error("Missing kind tag");
18
+ // only the root pubkey can be gotten from the tags, since due to quotes and mentions there will be many "p" tags for replies
19
+ const rootPubkey = root ? tags.find((t) => t[0] === "P")?.[1] : undefined;
18
20
  const pointer = {
19
- id: tag[1],
21
+ id: eTag[1],
20
22
  kind: parseInt(kind),
21
- pubkey: tag[3] || undefined,
22
- relay: tag[2] && (safeRelayUrl(tag[2]) ?? undefined),
23
+ pubkey: eTag[3] || rootPubkey || undefined,
24
+ relay: eTag[2] && isSafeRelayURL(eTag[2]) ? eTag[2] : undefined,
23
25
  };
24
26
  return pointer;
25
27
  }
@@ -30,16 +32,19 @@ export function getCommentEventPointer(tags, root = false) {
30
32
  * @throws
31
33
  */
32
34
  export function getCommentAddressPointer(tags, root = false) {
33
- const tag = tags.find((t) => t[0] === (root ? "A" : "a"));
34
- const id = tags.find((t) => t[0] === (root ? "E" : "e"))?.[1];
35
+ const aTag = tags.find((t) => t[0] === (root ? "A" : "a"));
36
+ const eTag = tags.find((t) => t[0] === (root ? "E" : "e"));
35
37
  const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
36
- if (tag) {
38
+ if (aTag) {
37
39
  if (!kind)
38
40
  throw new Error("Missing kind tag");
41
+ const addressPointer = getAddressPointerFromATag(aTag);
39
42
  const pointer = {
40
- id,
41
- ...getAddressPointerFromATag(tag),
42
- kind: parseInt(kind),
43
+ id: eTag?.[1],
44
+ pubkey: addressPointer.pubkey,
45
+ identifier: addressPointer.identifier,
46
+ kind: addressPointer.kind || parseInt(kind),
47
+ relay: addressPointer.relays?.[0] || eTag?.[2],
43
48
  };
44
49
  return pointer;
45
50
  }
@@ -50,13 +55,12 @@ export function getCommentAddressPointer(tags, root = false) {
50
55
  * @throws
51
56
  */
52
57
  export function getCommentExternalPointer(tags, root = false) {
53
- const tag = tags.find((t) => t[0] === (root ? "I" : "i"));
58
+ const iTag = tags.find((t) => t[0] === (root ? "I" : "i"));
54
59
  const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
55
- if (tag) {
60
+ if (iTag) {
56
61
  if (!kind)
57
62
  throw new Error("Missing kind tag");
58
- const pointer = getExternalPointerFromTag(tag);
59
- return pointer;
63
+ return getExternalPointerFromTag(iTag);
60
64
  }
61
65
  return null;
62
66
  }
@@ -0,0 +1,3 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const ContactsRelaysSymbol: unique symbol;
3
+ export declare function getRelaysFromContactsEvent(event: NostrEvent): Map<string, "all" | "inbox" | "outbox"> | null;
@@ -0,0 +1,25 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ import { isSafeRelayURL } from "./relays.js";
3
+ export const ContactsRelaysSymbol = Symbol.for("contacts-relays");
4
+ export function getRelaysFromContactsEvent(event) {
5
+ return getOrComputeCachedValue(event, ContactsRelaysSymbol, () => {
6
+ try {
7
+ const relayJson = JSON.parse(event.content);
8
+ const relays = new Map();
9
+ for (const [url, opts] of Object.entries(relayJson)) {
10
+ if (!isSafeRelayURL(url))
11
+ continue;
12
+ if (opts.write && opts.read)
13
+ relays.set(url, "all");
14
+ else if (opts.read)
15
+ relays.set(url, "inbox");
16
+ else if (opts.write)
17
+ relays.set(url, "outbox");
18
+ }
19
+ return relays;
20
+ }
21
+ catch (error) {
22
+ return null;
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,7 @@
1
+ import { isNip05, NIP05_REGEX } from "nostr-tools/nip05";
2
+ /** Returns the name and domain for a NIP-05 address */
3
+ export declare function parseNIP05Address(address: string): {
4
+ name: string;
5
+ domain: string;
6
+ } | null;
7
+ export { isNip05, NIP05_REGEX };
@@ -0,0 +1,10 @@
1
+ import { isNip05, NIP05_REGEX } from "nostr-tools/nip05";
2
+ /** Returns the name and domain for a NIP-05 address */
3
+ export function parseNIP05Address(address) {
4
+ const match = address.toLowerCase().match(NIP05_REGEX);
5
+ if (!match)
6
+ return null;
7
+ const [, name = "_", domain] = match;
8
+ return { name, domain };
9
+ }
10
+ export { isNip05, NIP05_REGEX };
@@ -4,7 +4,9 @@ export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: str
4
4
  /** Returns the name of a NIP-30 emoji pack */
5
5
  export declare function getPackName(pack: NostrEvent): string | undefined;
6
6
  export type Emoji = {
7
- name: string;
7
+ /** The emoji shortcode (without the ::) */
8
+ shortcode: string;
9
+ /** The URL to the emoji image */
8
10
  url: string;
9
11
  };
10
12
  /** Returns an array of emojis from a NIP-30 emoji pack */
@@ -2,7 +2,7 @@ import { getTagValue } from "./event.js";
2
2
  /** Gets an "emoji" tag that matches an emoji code */
3
3
  export function getEmojiTag(event, code) {
4
4
  code = code.replace(/^:|:$/g, "").toLocaleLowerCase();
5
- return event.tags.filter((t) => t[0] === "emoji" && t[1] && t[2]).find((t) => t[1].toLowerCase() === code);
5
+ return event.tags.find((t) => t[0] === "emoji" && t.length >= 3 && t[1].toLowerCase() === code);
6
6
  }
7
7
  /** Returns the name of a NIP-30 emoji pack */
8
8
  export function getPackName(pack) {
@@ -12,5 +12,5 @@ export function getPackName(pack) {
12
12
  export function getEmojis(pack) {
13
13
  return pack.tags
14
14
  .filter((t) => t[0] === "emoji" && t[1] && t[2])
15
- .map((t) => ({ name: t[1], url: t[2] }));
15
+ .map((t) => ({ shortcode: t[1], url: t[2] }));
16
16
  }
@@ -1,7 +1,8 @@
1
- import { NostrEvent, VerifiedEvent } from "nostr-tools";
1
+ import { EventTemplate, NostrEvent, VerifiedEvent } from "nostr-tools";
2
2
  export declare const EventUIDSymbol: unique symbol;
3
3
  export declare const EventIndexableTagsSymbol: unique symbol;
4
4
  export declare const FromCacheSymbol: unique symbol;
5
+ export declare const ReplaceableIdentifierSymbol: unique symbol;
5
6
  declare module "nostr-tools" {
6
7
  interface Event {
7
8
  [EventUIDSymbol]?: string;
@@ -33,10 +34,15 @@ export declare function getIndexableTags(event: NostrEvent): Set<string>;
33
34
  * Returns the second index ( tag[1] ) of the first tag that matches the name
34
35
  * If the event has any hidden tags they will be searched first
35
36
  */
36
- export declare function getTagValue(event: NostrEvent, name: string): string | undefined;
37
+ export declare function getTagValue(event: NostrEvent | EventTemplate, name: string): string | undefined;
37
38
  /** Sets events verified flag without checking anything */
38
39
  export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
39
40
  /** Marks an event as being from a cache */
40
41
  export declare function markFromCache(event: NostrEvent): void;
41
42
  /** Returns if an event was from a cache */
42
43
  export declare function isFromCache(event: NostrEvent): boolean;
44
+ /**
45
+ * Returns the replaceable identifier for a replaceable event
46
+ * @throws
47
+ */
48
+ export declare function getReplaceableIdentifier(event: NostrEvent): string;
@@ -1,9 +1,12 @@
1
1
  import { kinds, verifiedSymbol } from "nostr-tools";
2
2
  import { INDEXABLE_TAGS } from "../event-store/common.js";
3
3
  import { getHiddenTags } from "./hidden-tags.js";
4
+ import { getOrComputeCachedValue } from "./cache.js";
5
+ import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
4
6
  export const EventUIDSymbol = Symbol.for("event-uid");
5
7
  export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
6
8
  export const FromCacheSymbol = Symbol.for("from-cache");
9
+ export const ReplaceableIdentifierSymbol = Symbol.for("replaceable-identifier");
7
10
  /**
8
11
  * Checks if an object is a nostr event
9
12
  * NOTE: does not validation the signature on the event
@@ -34,20 +37,20 @@ export function isReplaceable(kind) {
34
37
  * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
35
38
  */
36
39
  export function getEventUID(event) {
37
- let id = event[EventUIDSymbol];
38
- if (!id) {
40
+ let uid = event[EventUIDSymbol];
41
+ if (!uid) {
39
42
  if (isReplaceable(event.kind)) {
40
- const d = event.tags.find((t) => t[0] === "d")?.[1];
41
- id = getReplaceableUID(event.kind, event.pubkey, d);
42
- }
43
- else {
44
- id = event.id;
43
+ let d = event.tags.find((t) => t[0] === "d")?.[1];
44
+ uid = getReplaceableUID(event.kind, event.pubkey, d);
45
45
  }
46
+ else
47
+ uid = event.id;
48
+ event[EventUIDSymbol] = uid;
46
49
  }
47
- return id;
50
+ return uid;
48
51
  }
49
52
  export function getReplaceableUID(kind, pubkey, d) {
50
- return d ? `${kind}:${pubkey}:${d}` : `${kind}:${pubkey}`;
53
+ return d ? kind + ":" + pubkey + ":" + d : kind + ":" + pubkey;
51
54
  }
52
55
  /** Returns a Set of tag names and values that are indexable */
53
56
  export function getIndexableTags(event) {
@@ -55,7 +58,7 @@ export function getIndexableTags(event) {
55
58
  if (!indexable) {
56
59
  const tags = new Set();
57
60
  for (const tag of event.tags) {
58
- if (tag[0] && INDEXABLE_TAGS.has(tag[0]) && tag[1]) {
61
+ if (tag.length >= 2 && tag[0].length === 1 && INDEXABLE_TAGS.has(tag[0])) {
59
62
  tags.add(tag[0] + ":" + tag[1]);
60
63
  }
61
64
  }
@@ -76,7 +79,8 @@ export function getTagValue(event, name) {
76
79
  }
77
80
  /** Sets events verified flag without checking anything */
78
81
  export function fakeVerifyEvent(event) {
79
- return (event[verifiedSymbol] = true);
82
+ event[verifiedSymbol] = true;
83
+ return true;
80
84
  }
81
85
  /** Marks an event as being from a cache */
82
86
  export function markFromCache(event) {
@@ -86,3 +90,17 @@ export function markFromCache(event) {
86
90
  export function isFromCache(event) {
87
91
  return !!event[FromCacheSymbol];
88
92
  }
93
+ /**
94
+ * Returns the replaceable identifier for a replaceable event
95
+ * @throws
96
+ */
97
+ export function getReplaceableIdentifier(event) {
98
+ if (!isParameterizedReplaceableKind(event.kind))
99
+ throw new Error("Event is not replaceable");
100
+ return getOrComputeCachedValue(event, ReplaceableIdentifierSymbol, () => {
101
+ const d = getTagValue(event, "d");
102
+ if (d === undefined)
103
+ throw new Error("Event missing identifier");
104
+ return d;
105
+ });
106
+ }
@@ -0,0 +1,55 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export type FileMetadata = {
3
+ /** URL of the file */
4
+ url: string;
5
+ /** MIME type */
6
+ type?: string;
7
+ /** sha256 hash of the file */
8
+ sha256?: string;
9
+ /**
10
+ * The original sha256 hash before the file was transformed
11
+ * @deprecated
12
+ */
13
+ originalSha256?: string;
14
+ /** size of the file in bytes */
15
+ size?: number;
16
+ /** size of file in pixels in the form <width>x<height> */
17
+ dimensions?: string;
18
+ /** magnet */
19
+ magnet?: string;
20
+ /** torrent infohash */
21
+ infohash?: string;
22
+ /** URL to a thumbnail */
23
+ thumbnail?: string;
24
+ /** URL to a preview image with the same dimensions */
25
+ image?: string;
26
+ /** summary */
27
+ summary?: string;
28
+ /** description for accessability */
29
+ alt?: string;
30
+ /** blurhash */
31
+ blurhash?: string;
32
+ /** fallback URLs */
33
+ fallback?: string[];
34
+ };
35
+ export type MediaAttachment = FileMetadata;
36
+ /**
37
+ * Parses file metadata tags into {@link FileMetadata}
38
+ * @throws
39
+ */
40
+ export declare function parseFileMetadataTags(tags: string[][]): FileMetadata;
41
+ /**
42
+ * Parses a imeta tag into a {@link FileMetadata}
43
+ * @throws
44
+ */
45
+ export declare function getFileMetadataFromImetaTag(tag: string[]): FileMetadata;
46
+ export declare const MediaAttachmentsSymbol: unique symbol;
47
+ /** Gets all the media attachments on an event */
48
+ export declare function getMediaAttachments(event: NostrEvent): FileMetadata[];
49
+ /**
50
+ * Gets {@link FileMetadata} for a NIP-94 kind 1063 event
51
+ * @throws
52
+ */
53
+ export declare function getFileMetadata(file: NostrEvent): FileMetadata;
54
+ /** Returns the last 64 length hex string in a URL */
55
+ export declare function getSha256FromURL(url: string | URL): string | undefined;
@@ -0,0 +1,99 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ /**
3
+ * Parses file metadata tags into {@link FileMetadata}
4
+ * @throws
5
+ */
6
+ export function parseFileMetadataTags(tags) {
7
+ const fields = {};
8
+ let fallback = undefined;
9
+ for (const [name, value] of tags) {
10
+ switch (name) {
11
+ case "fallback":
12
+ fallback = fallback ? [...fallback, value] : [value];
13
+ break;
14
+ default:
15
+ fields[name] = value;
16
+ break;
17
+ }
18
+ }
19
+ if (!fields.url)
20
+ throw new Error("Missing required url in file metadata");
21
+ const metadata = { url: fields.url, fallback };
22
+ // parse size
23
+ if (fields.size)
24
+ metadata.size = parseInt(fields.size);
25
+ // copy optional fields
26
+ if (fields.m)
27
+ metadata.type = fields.m;
28
+ if (fields.x)
29
+ metadata.sha256 = fields.x;
30
+ if (fields.ox)
31
+ metadata.originalSha256 = fields.ox;
32
+ if (fields.dim)
33
+ metadata.dimensions = fields.dim;
34
+ if (fields.magnet)
35
+ metadata.magnet = fields.magnet;
36
+ if (fields.i)
37
+ metadata.infohash = fields.i;
38
+ if (fields.thumb)
39
+ metadata.thumbnail = fields.thumb;
40
+ if (fields.image)
41
+ metadata.image = fields.image;
42
+ if (fields.summary)
43
+ metadata.summary = fields.summary;
44
+ if (fields.alt)
45
+ metadata.alt = fields.alt;
46
+ if (fields.blurhash)
47
+ metadata.blurhash = fields.blurhash;
48
+ return metadata;
49
+ }
50
+ /**
51
+ * Parses a imeta tag into a {@link FileMetadata}
52
+ * @throws
53
+ */
54
+ export function getFileMetadataFromImetaTag(tag) {
55
+ const parts = tag.slice(1);
56
+ const tags = [];
57
+ for (const part of parts) {
58
+ const match = part.match(/^(.+?)\s(.+)$/);
59
+ if (match) {
60
+ const [_, name, value] = match;
61
+ tags.push([name, value]);
62
+ }
63
+ }
64
+ return parseFileMetadataTags(tags);
65
+ }
66
+ export const MediaAttachmentsSymbol = Symbol.for("media-attachments");
67
+ /** Gets all the media attachments on an event */
68
+ export function getMediaAttachments(event) {
69
+ return getOrComputeCachedValue(event, MediaAttachmentsSymbol, () => {
70
+ return event.tags
71
+ .filter((t) => t[0] === "imeta")
72
+ .map((tag) => {
73
+ try {
74
+ return getFileMetadataFromImetaTag(tag);
75
+ }
76
+ catch (error) {
77
+ // ignore invalid attachments
78
+ return undefined;
79
+ }
80
+ })
81
+ .filter((a) => !!a);
82
+ });
83
+ }
84
+ /**
85
+ * Gets {@link FileMetadata} for a NIP-94 kind 1063 event
86
+ * @throws
87
+ */
88
+ export function getFileMetadata(file) {
89
+ return parseFileMetadataTags(file.tags);
90
+ }
91
+ /** Returns the last 64 length hex string in a URL */
92
+ export function getSha256FromURL(url) {
93
+ if (typeof url === "string")
94
+ url = new URL(url);
95
+ const hashes = Array.from(url.pathname.matchAll(/[0-9a-f]{64}/gi));
96
+ if (hashes.length > 0)
97
+ return hashes[hashes.length - 1][0];
98
+ return;
99
+ }
@@ -6,5 +6,9 @@ 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
+ /**
10
+ * Copied from nostr-tools and modified to support undefined
11
+ */
12
+ export declare function mergeFilters(...filters: Filter[]): Filter;
9
13
  /** Check if two filters are equal */
10
14
  export declare function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]): boolean;
@@ -17,7 +17,7 @@ export function matchFilter(filter, event) {
17
17
  for (let f in filter) {
18
18
  if (f[0] === "#") {
19
19
  let tagName = f.slice(1);
20
- let values = filter[`#${tagName}`];
20
+ let values = filter[f];
21
21
  if (values) {
22
22
  const tags = getIndexableTags(event);
23
23
  if (values.some((v) => tags.has(tagName + ":" + v)) === false)
@@ -40,6 +40,39 @@ export function matchFilters(filters, event) {
40
40
  }
41
41
  return false;
42
42
  }
43
+ /**
44
+ * Copied from nostr-tools and modified to support undefined
45
+ */
46
+ export function mergeFilters(...filters) {
47
+ let result = {};
48
+ for (let i = 0; i < filters.length; i++) {
49
+ let filter = filters[i];
50
+ Object.entries(filter).forEach(([property, values]) => {
51
+ // skip undefined
52
+ if (values === undefined)
53
+ return;
54
+ if (property === "kinds" || property === "ids" || property === "authors" || property[0] === "#") {
55
+ // @ts-ignore
56
+ result[property] = result[property] || [];
57
+ // @ts-ignore
58
+ for (let v = 0; v < values.length; v++) {
59
+ // @ts-ignore
60
+ let value = values[v];
61
+ // @ts-ignore
62
+ if (!result[property].includes(value))
63
+ result[property].push(value);
64
+ }
65
+ }
66
+ });
67
+ if (filter.limit && (!result.limit || filter.limit > result.limit))
68
+ result.limit = filter.limit;
69
+ if (filter.until && (!result.until || filter.until > result.until))
70
+ result.until = filter.until;
71
+ if (filter.since && (!result.since || filter.since < result.since))
72
+ result.since = filter.since;
73
+ }
74
+ return result;
75
+ }
43
76
  /** Check if two filters are equal */
44
77
  export function isFilterEqual(a, b) {
45
78
  return equal(a, b);
@@ -0,0 +1,24 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const GROUPS_LIST_KIND = 10009;
3
+ export declare const GROUP_MESSAGE_KIND = 9;
4
+ /** NIP-29 group pointer */
5
+ export type GroupPointer = {
6
+ id: string;
7
+ relay: string;
8
+ name?: string;
9
+ };
10
+ /**
11
+ * decodes a group identifier into a group pointer object
12
+ * @throws
13
+ */
14
+ export declare function decodeGroupPointer(str: string): GroupPointer;
15
+ /** Converts a group pointer into a group identifier */
16
+ export declare function encodeGroupPointer(pointer: GroupPointer): string;
17
+ export declare const GroupsPublicSymbol: unique symbol;
18
+ export declare const GroupsHiddenSymbol: unique symbol;
19
+ /** gets a {@link GroupPointer} from a "group" tag */
20
+ export declare function getGroupPointerFromGroupTag(tag: string[]): GroupPointer;
21
+ /** Returns all the public groups from a k:10009 list */
22
+ export declare function getPublicGroups(bookmark: NostrEvent): GroupPointer[];
23
+ /** Returns all the hidden groups from a k:10009 list */
24
+ export declare function getHiddenGroups(bookmark: NostrEvent): GroupPointer[] | undefined;
@@ -0,0 +1,39 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ import { processTags } from "./tags.js";
3
+ import { getHiddenTags } from "./hidden-tags.js";
4
+ export const GROUPS_LIST_KIND = 10009;
5
+ export const GROUP_MESSAGE_KIND = 9;
6
+ /**
7
+ * decodes a group identifier into a group pointer object
8
+ * @throws
9
+ */
10
+ export function decodeGroupPointer(str) {
11
+ const [relay, id] = str.split("'");
12
+ if (!relay)
13
+ throw new Error("Group pointer missing relay");
14
+ return { relay, id: id || "_" };
15
+ }
16
+ /** Converts a group pointer into a group identifier */
17
+ export function encodeGroupPointer(pointer) {
18
+ const hostname = URL.canParse(pointer.relay) ? new URL(pointer.relay).hostname : pointer.relay;
19
+ return `${hostname}'${pointer.id}`;
20
+ }
21
+ export const GroupsPublicSymbol = Symbol.for("groups-public");
22
+ export const GroupsHiddenSymbol = Symbol.for("groups-hidden");
23
+ /** gets a {@link GroupPointer} from a "group" tag */
24
+ export function getGroupPointerFromGroupTag(tag) {
25
+ const [_, id, relay, name] = tag;
26
+ return { id, relay, name };
27
+ }
28
+ /** Returns all the public groups from a k:10009 list */
29
+ export function getPublicGroups(bookmark) {
30
+ return getOrComputeCachedValue(bookmark, GroupsPublicSymbol, () => processTags(bookmark.tags.filter((t) => t[0] === "group"), getGroupPointerFromGroupTag));
31
+ }
32
+ /** Returns all the hidden groups from a k:10009 list */
33
+ export function getHiddenGroups(bookmark) {
34
+ return getOrComputeCachedValue(bookmark, GroupsHiddenSymbol, () => {
35
+ const tags = getHiddenTags(bookmark);
36
+ return (tags &&
37
+ processTags(bookmark.tags.filter((t) => t[0] === "group"), getGroupPointerFromGroupTag));
38
+ });
39
+ }
@@ -1,5 +1,5 @@
1
- import { EventTemplate, NostrEvent, UnsignedEvent } from "nostr-tools";
2
- import { EventStore } from "applesauce-core";
1
+ import { EventTemplate, NostrEvent } from "nostr-tools";
2
+ import { EventStore } from "../event-store/event-store.js";
3
3
  export type HiddenTagsSigner = {
4
4
  nip04?: {
5
5
  encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
@@ -10,7 +10,6 @@ export type HiddenTagsSigner = {
10
10
  decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
11
11
  };
12
12
  };
13
- export type TagOperation = (tags: string[][]) => string[][];
14
13
  export declare const HiddenTagsSymbol: unique symbol;
15
14
  /** Various event kinds that can have encrypted tags in their content and which encryption method they use */
16
15
  export declare const EventEncryptionMethod: Record<number, "nip04" | "nip44">;
@@ -22,6 +21,14 @@ export declare function hasHiddenTags(event: NostrEvent | EventTemplate): boolea
22
21
  export declare function getHiddenTags(event: NostrEvent | EventTemplate): string[][] | undefined;
23
22
  /** Checks if the hidden tags are locked */
24
23
  export declare function isHiddenTagsLocked(event: NostrEvent): boolean;
24
+ /** Returns either nip04 or nip44 encryption method depending on list kind */
25
+ export declare function getListEncryptionMethods(kind: number, signer: HiddenTagsSigner): {
26
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
27
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
28
+ } | {
29
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
30
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
31
+ };
25
32
  /**
26
33
  * Decrypts the private list
27
34
  * @param event The list event to decrypt
@@ -29,18 +36,11 @@ export declare function isHiddenTagsLocked(event: NostrEvent): boolean;
29
36
  * @param store An optional EventStore to notify about the update
30
37
  * @throws
31
38
  */
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>;
39
+ export declare function unlockHiddenTags<T extends {
40
+ kind: number;
41
+ pubkey: string;
42
+ content: string;
43
+ }>(event: T, signer: HiddenTagsSigner, store?: EventStore): Promise<T>;
44
44
  /**
45
45
  * Override the hidden tags in an event
46
46
  * @throws