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,5 +1,7 @@
1
- import { unixNow } from "applesauce-core/helpers";
2
1
  import { kinds } from "nostr-tools";
2
+ import { GROUPS_LIST_KIND } from "./groups.js";
3
+ import { unixNow } from "./time.js";
4
+ import { isEvent } from "./event.js";
3
5
  export const HiddenTagsSymbol = Symbol.for("hidden-tags");
4
6
  /** Various event kinds that can have encrypted tags in their content and which encryption method they use */
5
7
  export const EventEncryptionMethod = {
@@ -12,6 +14,7 @@ export const EventEncryptionMethod = {
12
14
  [kinds.CommunitiesList]: "nip04",
13
15
  [kinds.PublicChatsList]: "nip04",
14
16
  [kinds.SearchRelaysList]: "nip04",
17
+ [GROUPS_LIST_KIND]: "nip04",
15
18
  // NIP-51 sets
16
19
  [kinds.Bookmarksets]: "nip04",
17
20
  [kinds.Relaysets]: "nip04",
@@ -35,7 +38,8 @@ export function getHiddenTags(event) {
35
38
  export function isHiddenTagsLocked(event) {
36
39
  return hasHiddenTags(event) && getHiddenTags(event) === undefined;
37
40
  }
38
- function getEventEncryption(kind, signer) {
41
+ /** Returns either nip04 or nip44 encryption method depending on list kind */
42
+ export function getListEncryptionMethods(kind, signer) {
39
43
  const method = EventEncryptionMethod[kind];
40
44
  const encryption = signer[method];
41
45
  if (!encryption)
@@ -52,7 +56,7 @@ function getEventEncryption(kind, signer) {
52
56
  export async function unlockHiddenTags(event, signer, store) {
53
57
  if (!canHaveHiddenTags(event.kind))
54
58
  throw new Error("Event kind does not support hidden tags");
55
- const encryption = getEventEncryption(event.kind, signer);
59
+ const encryption = getListEncryptionMethods(event.kind, signer);
56
60
  const plaintext = await encryption.decrypt(event.pubkey, event.content);
57
61
  const parsed = JSON.parse(plaintext);
58
62
  if (!Array.isArray(parsed))
@@ -60,36 +64,10 @@ export async function unlockHiddenTags(event, signer, store) {
60
64
  // Convert array to tags array string[][]
61
65
  const tags = parsed.filter((t) => Array.isArray(t)).map((t) => t.map((v) => String(v)));
62
66
  Reflect.set(event, HiddenTagsSymbol, tags);
63
- if (store)
67
+ if (store && isEvent(event))
64
68
  store.update(event);
65
69
  return event;
66
70
  }
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
71
  /**
94
72
  * Override the hidden tags in an event
95
73
  * @throws
@@ -97,7 +75,7 @@ export async function modifyEventTags(event, operations, signer) {
97
75
  export async function overrideHiddenTags(event, hidden, signer) {
98
76
  if (!canHaveHiddenTags(event.kind))
99
77
  throw new Error("Event kind does not support hidden tags");
100
- const encryption = getEventEncryption(event.kind, signer);
78
+ const encryption = getListEncryptionMethods(event.kind, signer);
101
79
  const ciphertext = await encryption.encrypt(event.pubkey, JSON.stringify(hidden));
102
80
  return {
103
81
  kind: event.kind,
@@ -1,24 +1,36 @@
1
+ export * from "./blossom.js";
1
2
  export * from "./bolt11.js";
3
+ export * from "./bookmarks.js";
2
4
  export * from "./cache.js";
5
+ export * from "./channels.js";
3
6
  export * from "./comment.js";
7
+ export * from "./contacts.js";
4
8
  export * from "./content.js";
5
9
  export * from "./delete.js";
10
+ export * from "./dns-identity.js";
6
11
  export * from "./emoji.js";
7
12
  export * from "./event.js";
8
13
  export * from "./external-id.js";
14
+ export * from "./file-metadata.js";
9
15
  export * from "./filter.js";
16
+ export * from "./groups.js";
10
17
  export * from "./hashtag.js";
11
18
  export * from "./hidden-tags.js";
19
+ export * from "./json.js";
20
+ export * from "./lists.js";
12
21
  export * from "./lnurl.js";
13
22
  export * from "./lru.js";
14
23
  export * from "./mailboxes.js";
15
- export * from "./media-attachment.js";
24
+ export * from "./mutes.js";
25
+ export * from "./picture-post.js";
16
26
  export * from "./pointers.js";
17
27
  export * from "./profile.js";
18
28
  export * from "./relays.js";
29
+ export * from "./share.js";
19
30
  export * from "./string.js";
20
31
  export * from "./tags.js";
21
32
  export * from "./threading.js";
22
33
  export * from "./time.js";
23
34
  export * from "./url.js";
35
+ export * from "./user-status.js";
24
36
  export * from "./zap.js";
@@ -1,24 +1,36 @@
1
+ export * from "./blossom.js";
1
2
  export * from "./bolt11.js";
3
+ export * from "./bookmarks.js";
2
4
  export * from "./cache.js";
5
+ export * from "./channels.js";
3
6
  export * from "./comment.js";
7
+ export * from "./contacts.js";
4
8
  export * from "./content.js";
5
9
  export * from "./delete.js";
10
+ export * from "./dns-identity.js";
6
11
  export * from "./emoji.js";
7
12
  export * from "./event.js";
8
13
  export * from "./external-id.js";
14
+ export * from "./file-metadata.js";
9
15
  export * from "./filter.js";
16
+ export * from "./groups.js";
10
17
  export * from "./hashtag.js";
11
18
  export * from "./hidden-tags.js";
19
+ export * from "./json.js";
20
+ export * from "./lists.js";
12
21
  export * from "./lnurl.js";
13
22
  export * from "./lru.js";
14
23
  export * from "./mailboxes.js";
15
- export * from "./media-attachment.js";
24
+ export * from "./mutes.js";
25
+ export * from "./picture-post.js";
16
26
  export * from "./pointers.js";
17
27
  export * from "./profile.js";
18
28
  export * from "./relays.js";
29
+ export * from "./share.js";
19
30
  export * from "./string.js";
20
31
  export * from "./tags.js";
21
32
  export * from "./threading.js";
22
33
  export * from "./time.js";
23
34
  export * from "./url.js";
35
+ export * from "./user-status.js";
24
36
  export * from "./zap.js";
@@ -0,0 +1,28 @@
1
+ import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
2
+ import { NostrEvent } from "nostr-tools";
3
+ /**
4
+ * Checks if an event pointer is anywhere in a list or set
5
+ * NOTE: Ignores the `relay` field in EventPointer
6
+ * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
7
+ */
8
+ export declare function isEventPointerInList(list: NostrEvent, pointer: string | EventPointer): boolean;
9
+ /**
10
+ * Checks if an address pointer is anywhere in a list or set
11
+ * NOTE: Ignores the `relay` field in AddressPointer
12
+ * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
13
+ */
14
+ export declare function isAddressPointerInList(list: NostrEvent, pointer: string | AddressPointer): boolean;
15
+ /**
16
+ * Checks if an profile pointer is anywhere in a list or set
17
+ * NOTE: Ignores the `relay` field in ProfilePointer
18
+ * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
19
+ */
20
+ export declare function isProfilePointerInList(list: NostrEvent, pointer: string | ProfilePointer): boolean;
21
+ /** Returns all the EventPointer in a list or set */
22
+ export declare function getEventPointersFromList(list: NostrEvent): EventPointer[];
23
+ /** Returns all the AddressPointer in a list or set */
24
+ export declare function getAddressPointersFromList(list: NostrEvent): AddressPointer[];
25
+ /** Returns all the ProfilePointer in a list or set */
26
+ export declare function getProfilePointersFromList(list: NostrEvent): ProfilePointer[];
27
+ /** Returns if an event is a valid list or set */
28
+ export declare function isValidList(event: NostrEvent): boolean;
@@ -0,0 +1,65 @@
1
+ import { isParameterizedReplaceableKind, isReplaceableKind } from "nostr-tools/kinds";
2
+ import { getHiddenTags } from "./hidden-tags.js";
3
+ import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag, getProfilePointerFromPTag, } from "./pointers.js";
4
+ import { isATag, isETag, isPTag, processTags } from "./tags.js";
5
+ import { getReplaceableIdentifier } from "./event.js";
6
+ function listGetAllTags(list) {
7
+ const hidden = getHiddenTags(list);
8
+ return hidden ? [...hidden, ...list.tags] : list.tags;
9
+ }
10
+ /**
11
+ * Checks if an event pointer is anywhere in a list or set
12
+ * NOTE: Ignores the `relay` field in EventPointer
13
+ * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
14
+ */
15
+ export function isEventPointerInList(list, pointer) {
16
+ const id = typeof pointer === "string" ? pointer : pointer.id;
17
+ return listGetAllTags(list).some((t) => t[0] === "e" && t[1] === id);
18
+ }
19
+ /**
20
+ * Checks if an address pointer is anywhere in a list or set
21
+ * NOTE: Ignores the `relay` field in AddressPointer
22
+ * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
23
+ */
24
+ export function isAddressPointerInList(list, pointer) {
25
+ const cord = typeof pointer === "string" ? pointer : getCoordinateFromAddressPointer(pointer);
26
+ return listGetAllTags(list).some((t) => t[0] === "a" && t[1] === cord);
27
+ }
28
+ /**
29
+ * Checks if an profile pointer is anywhere in a list or set
30
+ * NOTE: Ignores the `relay` field in ProfilePointer
31
+ * NOTE: This will check the hidden tags if the list has hidden tags and they are unlocked
32
+ */
33
+ export function isProfilePointerInList(list, pointer) {
34
+ const pubkey = typeof pointer === "string" ? pointer : pointer.pubkey;
35
+ return listGetAllTags(list).some((t) => t[0] === "p" && t[1] === pubkey);
36
+ }
37
+ /** Returns all the EventPointer in a list or set */
38
+ export function getEventPointersFromList(list) {
39
+ return processTags(listGetAllTags(list), (tag) => (isETag(tag) ? tag : undefined), getEventPointerFromETag);
40
+ }
41
+ /** Returns all the AddressPointer in a list or set */
42
+ export function getAddressPointersFromList(list) {
43
+ return processTags(listGetAllTags(list), (t) => (isATag(t) ? t : undefined), getAddressPointerFromATag);
44
+ }
45
+ /** Returns all the ProfilePointer in a list or set */
46
+ export function getProfilePointersFromList(list) {
47
+ return processTags(listGetAllTags(list), (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag);
48
+ }
49
+ /** Returns if an event is a valid list or set */
50
+ export function isValidList(event) {
51
+ try {
52
+ if (isParameterizedReplaceableKind(event.kind)) {
53
+ // event is a set
54
+ // ensure the set has an identifier
55
+ getReplaceableIdentifier(event);
56
+ return true;
57
+ }
58
+ else if (isReplaceableKind(event.kind) && event.kind >= 10000 && event.kind < 20000) {
59
+ // event is a list
60
+ return true;
61
+ }
62
+ }
63
+ catch (error) { }
64
+ return false;
65
+ }
@@ -1,5 +1,6 @@
1
- import { safeRelayUrl } from "./relays.js";
2
1
  import { getOrComputeCachedValue } from "./cache.js";
2
+ import { isSafeRelayURL } from "./relays.js";
3
+ import { normalizeURL } from "./url.js";
3
4
  export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
4
5
  export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
5
6
  /**
@@ -9,10 +10,13 @@ export function getInboxes(event) {
9
10
  return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
10
11
  const inboxes = [];
11
12
  for (const tag of event.tags) {
12
- if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
13
- const url = safeRelayUrl(tag[1]);
14
- if (url && !inboxes.includes(url))
15
- inboxes.push(url);
13
+ const [name, url, mode] = tag;
14
+ if (name === "r" &&
15
+ url &&
16
+ isSafeRelayURL(url) &&
17
+ !inboxes.includes(url) &&
18
+ (mode === "read" || mode === undefined)) {
19
+ inboxes.push(normalizeURL(url));
16
20
  }
17
21
  }
18
22
  return inboxes;
@@ -25,10 +29,13 @@ export function getOutboxes(event) {
25
29
  return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
26
30
  const outboxes = [];
27
31
  for (const tag of event.tags) {
28
- if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
29
- const url = safeRelayUrl(tag[1]);
30
- if (url && !outboxes.includes(url))
31
- outboxes.push(url);
32
+ const [name, url, mode] = tag;
33
+ if (name === "r" &&
34
+ url &&
35
+ isSafeRelayURL(url) &&
36
+ !outboxes.includes(url) &&
37
+ (mode === "write" || mode === undefined)) {
38
+ outboxes.push(normalizeURL(url));
32
39
  }
33
40
  }
34
41
  return outboxes;
@@ -0,0 +1,14 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const MutePublicSymbol: unique symbol;
3
+ export declare const MuteHiddenSymbol: unique symbol;
4
+ export type Mutes = {
5
+ pubkeys: Set<string>;
6
+ threads: Set<string>;
7
+ hashtags: Set<string>;
8
+ words: Set<string>;
9
+ };
10
+ export declare function parseMutedTags(tags: string[][]): Mutes;
11
+ /** Returns muted things */
12
+ export declare function getMutedThings(mute: NostrEvent): Mutes;
13
+ /** Returns the hidden muted content if the event is unlocked */
14
+ export declare function getHiddenMutedThings(mute: NostrEvent): Mutes | undefined;
@@ -0,0 +1,23 @@
1
+ import { isETag, isPTag, isTTag } from "./tags.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ import { getHiddenTags } from "./hidden-tags.js";
4
+ export const MutePublicSymbol = Symbol.for("mute-public");
5
+ export const MuteHiddenSymbol = Symbol.for("mute-hidden");
6
+ export function parseMutedTags(tags) {
7
+ const pubkeys = new Set(tags.filter(isPTag).map((t) => t[1]));
8
+ const threads = new Set(tags.filter(isETag).map((t) => t[1]));
9
+ const hashtags = new Set(tags.filter(isTTag).map((t) => t[1].toLocaleLowerCase()));
10
+ const words = new Set(tags.filter((t) => t[0] === "word" && t[1]).map((t) => t[1].toLocaleLowerCase()));
11
+ return { pubkeys, threads, hashtags, words };
12
+ }
13
+ /** Returns muted things */
14
+ export function getMutedThings(mute) {
15
+ return getOrComputeCachedValue(mute, MutePublicSymbol, (e) => parseMutedTags(e.tags));
16
+ }
17
+ /** Returns the hidden muted content if the event is unlocked */
18
+ export function getHiddenMutedThings(mute) {
19
+ return getOrComputeCachedValue(mute, MuteHiddenSymbol, () => {
20
+ const tags = getHiddenTags(mute);
21
+ return tags && parseMutedTags(tags);
22
+ });
23
+ }
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const PICTURE_POST_KIND = 20;
3
+ /** Return the media attachments from a kind 20 media post */
4
+ export declare function getPicturePostAttachments(post: NostrEvent): import("./file-metadata.js").FileMetadata[];
@@ -0,0 +1,6 @@
1
+ import { getMediaAttachments } from "./file-metadata.js";
2
+ export const PICTURE_POST_KIND = 20;
3
+ /** Return the media attachments from a kind 20 media post */
4
+ export function getPicturePostAttachments(post) {
5
+ return getMediaAttachments(post);
6
+ }
@@ -1,8 +1,8 @@
1
1
  import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
2
2
  import { getPublicKey, kinds } from "nostr-tools";
3
- import { safeRelayUrls } from "./relays.js";
4
- import { getTagValue } from "./index.js";
3
+ import { getReplaceableIdentifier } from "./event.js";
5
4
  import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
5
+ import { isSafeRelayURL } from "./relays.js";
6
6
  export function parseCoordinate(a, requireD = false, silent = true) {
7
7
  const parts = a.split(":");
8
8
  const kind = parts[0] && parseInt(parts[0]);
@@ -74,8 +74,8 @@ export function getEventPointerFromETag(tag) {
74
74
  if (!tag[1])
75
75
  throw new Error("Missing event id in tag");
76
76
  let pointer = { id: tag[1] };
77
- if (tag[2])
78
- pointer.relays = safeRelayUrls([tag[2]]);
77
+ if (tag[2] && isSafeRelayURL(tag[2]))
78
+ pointer.relays = [tag[2]];
79
79
  return pointer;
80
80
  }
81
81
  /**
@@ -86,8 +86,8 @@ export function getEventPointerFromQTag(tag) {
86
86
  if (!tag[1])
87
87
  throw new Error("Missing event id in tag");
88
88
  let pointer = { id: tag[1] };
89
- if (tag[2])
90
- pointer.relays = safeRelayUrls([tag[2]]);
89
+ if (tag[2] && isSafeRelayURL(tag[2]))
90
+ pointer.relays = [tag[2]];
91
91
  if (tag[3] && tag[3].length === 64)
92
92
  pointer.author = tag[3];
93
93
  return pointer;
@@ -100,8 +100,8 @@ export function getAddressPointerFromATag(tag) {
100
100
  if (!tag[1])
101
101
  throw new Error("Missing coordinate in tag");
102
102
  const pointer = parseCoordinate(tag[1], true, false);
103
- if (tag[2])
104
- pointer.relays = safeRelayUrls([tag[2]]);
103
+ if (tag[2] && isSafeRelayURL(tag[2]))
104
+ pointer.relays = [tag[2]];
105
105
  return pointer;
106
106
  }
107
107
  /**
@@ -112,8 +112,8 @@ export function getProfilePointerFromPTag(tag) {
112
112
  if (!tag[1])
113
113
  throw new Error("Missing pubkey in tag");
114
114
  const pointer = { pubkey: tag[1] };
115
- if (tag[2])
116
- pointer.relays = safeRelayUrls([tag[2]]);
115
+ if (tag[2] && isSafeRelayURL(tag[2]))
116
+ pointer.relays = [tag[2]];
117
117
  return pointer;
118
118
  }
119
119
  /** Parses "e", "a", "p", and "q" tags into a pointer */
@@ -148,7 +148,7 @@ export function isEventPointer(pointer) {
148
148
  }
149
149
  /** Returns the coordinate string for an AddressPointer */
150
150
  export function getCoordinateFromAddressPointer(pointer) {
151
- return `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
151
+ return pointer.kind + ":" + pointer.pubkey + ":" + pointer.identifier;
152
152
  }
153
153
  /**
154
154
  * Returns an AddressPointer for a replaceable event
@@ -157,9 +157,7 @@ export function getCoordinateFromAddressPointer(pointer) {
157
157
  export function getAddressPointerForEvent(event, relays) {
158
158
  if (!isParameterizedReplaceableKind(event.kind))
159
159
  throw new Error("Cant get AddressPointer for non-replaceable event");
160
- const d = getTagValue(event, "d");
161
- if (!d)
162
- throw new Error("Event missing identifier");
160
+ const d = getReplaceableIdentifier(event);
163
161
  return {
164
162
  identifier: d,
165
163
  kind: event.kind,
@@ -182,9 +180,7 @@ export function getEventPointerForEvent(event, relays) {
182
180
  /** Returns a pointer for a given event */
183
181
  export function getPointerForEvent(event, relays) {
184
182
  if (kinds.isParameterizedReplaceableKind(event.kind)) {
185
- const d = getTagValue(event, "d");
186
- if (!d)
187
- throw new Error("Event missing identifier");
183
+ const d = getReplaceableIdentifier(event);
188
184
  return {
189
185
  type: "naddr",
190
186
  data: {
@@ -2,10 +2,13 @@ import { NostrEvent } from "nostr-tools";
2
2
  export declare const ProfileContentSymbol: unique symbol;
3
3
  export type ProfileContent = {
4
4
  name?: string;
5
+ /** @deprecated use name instead */
6
+ username?: string;
5
7
  display_name?: string;
8
+ /** @deprecated use display_name instead */
6
9
  displayName?: string;
7
10
  about?: string;
8
- /** @deprecated */
11
+ /** @deprecated use picture instead */
9
12
  image?: string;
10
13
  picture?: string;
11
14
  banner?: string;
@@ -18,3 +21,5 @@ export type ProfileContent = {
18
21
  export declare function getProfileContent(event: NostrEvent): ProfileContent;
19
22
  /** Checks if the content of the kind 0 event is valid JSON */
20
23
  export declare function isValidProfile(profile?: NostrEvent): boolean;
24
+ /** Gets the display name from a profile with fallbacks */
25
+ export declare function getDisplayName(metadata?: ProfileContent): string | undefined;
@@ -29,3 +29,7 @@ export function isValidProfile(profile) {
29
29
  return false;
30
30
  }
31
31
  }
32
+ /** Gets the display name from a profile with fallbacks */
33
+ export function getDisplayName(metadata) {
34
+ return metadata?.display_name || metadata?.displayName || metadata?.name;
35
+ }
@@ -5,8 +5,11 @@ declare module "nostr-tools" {
5
5
  [SeenRelaysSymbol]?: Set<string>;
6
6
  }
7
7
  }
8
+ /** Marks an event as being seen on a relay */
8
9
  export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
10
+ /** Returns the set of relays this event was seen on */
9
11
  export declare function getSeenRelays(event: NostrEvent): Set<string> | undefined;
10
- export declare function validateRelayURL(relay: string | URL): URL;
11
- export declare function safeRelayUrl(relayUrl: string | URL): string | null;
12
- export declare function safeRelayUrls(urls: Iterable<string>): string[];
12
+ /** A fast check to make sure relay hints are safe to connect to */
13
+ export declare function isSafeRelayURL(relay: string): boolean;
14
+ /** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
15
+ export declare function mergeRelaySets(...sources: (Iterable<string> | undefined)[]): string[];
@@ -1,31 +1,38 @@
1
+ import { normalizeURL } from "./url.js";
1
2
  export const SeenRelaysSymbol = Symbol.for("seen-relays");
2
- // Seen relays
3
+ /** Marks an event as being seen on a relay */
3
4
  export function addSeenRelay(event, relay) {
4
5
  if (!event[SeenRelaysSymbol])
5
6
  event[SeenRelaysSymbol] = new Set();
6
7
  event[SeenRelaysSymbol].add(relay);
7
8
  return event[SeenRelaysSymbol];
8
9
  }
10
+ /** Returns the set of relays this event was seen on */
9
11
  export function getSeenRelays(event) {
10
12
  return event[SeenRelaysSymbol];
11
13
  }
12
- // Relay URLs
13
- export function validateRelayURL(relay) {
14
- if (typeof relay === "string" && relay.includes(",ws"))
15
- throw new Error("Can not have multiple relays in one string");
16
- const url = typeof relay === "string" ? new URL(relay) : relay;
17
- if (url.protocol !== "wss:" && url.protocol !== "ws:")
18
- throw new Error("Incorrect protocol");
19
- return url;
14
+ const WEBSOCKET_URL_CHECK = /^wss?:\/\/([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}|localhost)\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)$/;
15
+ /** A fast check to make sure relay hints are safe to connect to */
16
+ export function isSafeRelayURL(relay) {
17
+ // anything smaller than 8 is not a URL
18
+ return relay.length >= 8 && WEBSOCKET_URL_CHECK.test(relay);
20
19
  }
21
- export function safeRelayUrl(relayUrl) {
22
- try {
23
- return validateRelayURL(relayUrl).toString();
20
+ /** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
21
+ export function mergeRelaySets(...sources) {
22
+ const set = new Set();
23
+ for (const src of sources) {
24
+ if (!src)
25
+ continue;
26
+ for (const url of src) {
27
+ try {
28
+ const safe = normalizeURL(url).toString();
29
+ if (safe)
30
+ set.add(safe);
31
+ }
32
+ catch (error) {
33
+ // failed to parse URL, ignore
34
+ }
35
+ }
24
36
  }
25
- catch (e) {
26
- return null;
27
- }
28
- }
29
- export function safeRelayUrls(urls) {
30
- return Array.from(urls).map(safeRelayUrl).filter(Boolean);
37
+ return Array.from(set);
31
38
  }
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const SharedEventSymbol: unique symbol;
3
+ /** Returns the stringified event in the content of a kind 6 or 16 share event */
4
+ export declare function parseSharedEvent(event: NostrEvent): NostrEvent | undefined;
@@ -0,0 +1,12 @@
1
+ import { safeParse } from "./json.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ export const SharedEventSymbol = Symbol.for("shared-event");
4
+ /** Returns the stringified event in the content of a kind 6 or 16 share event */
5
+ export function parseSharedEvent(event) {
6
+ return getOrComputeCachedValue(event, SharedEventSymbol, () => {
7
+ const json = safeParse(event.content);
8
+ if (!json)
9
+ return;
10
+ return json;
11
+ });
12
+ }
@@ -1,3 +1,7 @@
1
+ /** A tag with at least two indexes, the first being the name, the second the value */
2
+ export type NameValueTag<Name extends string = string> = [Name, string, ...string[]];
3
+ /** Tests if a tag has at least two indexes, and optionally the value of the first */
4
+ export declare function isNameValueTag<Name extends string>(tag: string[], name?: Name): tag is NameValueTag<Name>;
1
5
  /** Checks if tag is an "e" tag and has at least one value */
2
6
  export declare function isETag(tag: string[]): tag is ["e", string, ...string[]];
3
7
  /** Checks if tag is an "p" tag and has at least one value */
@@ -10,3 +14,16 @@ export declare function isDTag(tag: string[]): tag is ["d", string, ...string[]]
10
14
  export declare function isATag(tag: string[]): tag is ["a", string, ...string[]];
11
15
  /** Checks if tag is an "a" tag and has at least one value */
12
16
  export declare function isTTag(tag: string[]): tag is ["t", string, ...string[]];
17
+ /** A pipeline that filters and maps each tag */
18
+ type TagPipe = {
19
+ <A>(tags: string[][], ta: (tag: string[]) => A | undefined): A[];
20
+ <A, B>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined): B[];
21
+ <A, B, C>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined): C[];
22
+ <A, B, C, D>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined): D[];
23
+ <A, B, C, D, E>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined, de: (d: D) => E | undefined): E[];
24
+ <A, B, C, D, E, F>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined, de: (d: D) => E | undefined, ef: (e: E) => F | undefined): F[];
25
+ <A, B, C, D, E, F, G>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined, de: (d: D) => E | undefined, ef: (e: E) => F | undefined, fg: (f: F) => G | undefined): G[];
26
+ };
27
+ /** Filter and transform tags */
28
+ export declare const processTags: TagPipe;
29
+ export {};
@@ -1,24 +1,46 @@
1
+ /** Tests if a tag has at least two indexes, and optionally the value of the first */
2
+ export function isNameValueTag(tag, name) {
3
+ return tag[0] !== undefined && tag[1] !== undefined && (name ? tag[0] === name : true);
4
+ }
1
5
  /** Checks if tag is an "e" tag and has at least one value */
2
6
  export function isETag(tag) {
3
- return tag[0] === "e" && tag[1] !== undefined;
7
+ return isNameValueTag(tag, "e");
4
8
  }
5
9
  /** Checks if tag is an "p" tag and has at least one value */
6
10
  export function isPTag(tag) {
7
- return tag[0] === "p" && tag[1] !== undefined;
11
+ return isNameValueTag(tag, "p");
8
12
  }
9
13
  /** Checks if tag is an "r" tag and has at least one value */
10
14
  export function isRTag(tag) {
11
- return tag[0] === "r" && tag[1] !== undefined;
15
+ return isNameValueTag(tag, "r");
12
16
  }
13
17
  /** Checks if tag is an "d" tag and has at least one value */
14
18
  export function isDTag(tag) {
15
- return tag[0] === "d" && tag[1] !== undefined;
19
+ return isNameValueTag(tag, "d");
16
20
  }
17
21
  /** Checks if tag is an "a" tag and has at least one value */
18
22
  export function isATag(tag) {
19
- return tag[0] === "a" && tag[1] !== undefined;
23
+ return isNameValueTag(tag, "a");
20
24
  }
21
25
  /** Checks if tag is an "a" tag and has at least one value */
22
26
  export function isTTag(tag) {
23
- return tag[0] === "a" && tag[1] !== undefined;
27
+ return isNameValueTag(tag, "t");
24
28
  }
29
+ /** Filter and transform tags */
30
+ export const processTags = (tags, ...fns) => {
31
+ return fns.reduce((step, fn) => {
32
+ const next = [];
33
+ for (const value of step) {
34
+ try {
35
+ const result = fn(value);
36
+ if (result === undefined)
37
+ continue; // value is undefined, ignore
38
+ next.push(result);
39
+ }
40
+ catch (error) {
41
+ // failed to process value, ignore
42
+ }
43
+ }
44
+ return next;
45
+ }, tags);
46
+ };