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
@@ -0,0 +1,235 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getCommentAddressPointer, getCommentEventPointer, getCommentExternalPointer, getCommentReplyPointer, getCommentRootPointer, } from "../comment.js";
3
+ import { FakeUser } from "../../__tests__/fixtures.js";
4
+ const user = new FakeUser();
5
+ describe("getCommentRootPointer", () => {
6
+ it("should throw if event is not a comment", () => {
7
+ expect(() => {
8
+ getCommentRootPointer(user.note("testing"));
9
+ }).toThrow("Event is not a comment");
10
+ });
11
+ });
12
+ describe("getCommentReplyPointer", () => {
13
+ it("should throw if event is not a comment", () => {
14
+ expect(() => {
15
+ getCommentReplyPointer(user.note("testing"));
16
+ }).toThrow("Event is not a comment");
17
+ });
18
+ });
19
+ describe("getCommentEventPointer", () => {
20
+ it("should get pubkey from P tag when root=true", () => {
21
+ const tags = [
22
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
23
+ ["K", "1621"],
24
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
25
+ ["k", "1621"],
26
+ ["p", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
27
+ ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
28
+ ];
29
+ expect(getCommentEventPointer(tags, true)).toEqual({
30
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
31
+ kind: 1621,
32
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
33
+ relay: undefined,
34
+ });
35
+ });
36
+ it("should default to pubkey in E tag when root pubkey do not match", () => {
37
+ const tags = [
38
+ [
39
+ "E",
40
+ "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
41
+ "",
42
+ "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
43
+ ],
44
+ ["K", "1621"],
45
+ ["P", "bad-pubkey"],
46
+ ];
47
+ expect(getCommentEventPointer(tags, true)).toEqual({
48
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
49
+ kind: 1621,
50
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
51
+ relay: undefined,
52
+ });
53
+ });
54
+ it("should get pubkey from E tag", () => {
55
+ const tags = [
56
+ [
57
+ "E",
58
+ "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
59
+ "",
60
+ "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
61
+ ],
62
+ ["K", "1621"],
63
+ ];
64
+ expect(getCommentEventPointer(tags, true)).toEqual({
65
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
66
+ kind: 1621,
67
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
68
+ relay: undefined,
69
+ });
70
+ });
71
+ it("should get relay from E tag", () => {
72
+ const tags = [
73
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
74
+ ["K", "1621"],
75
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
76
+ ];
77
+ expect(getCommentEventPointer(tags, true)).toEqual({
78
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
79
+ kind: 1621,
80
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
81
+ relay: "wss://relay.io/",
82
+ });
83
+ });
84
+ it("should throw if K tag is missing", () => {
85
+ const tags = [
86
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
87
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
88
+ ];
89
+ expect(() => {
90
+ getCommentEventPointer(tags, true);
91
+ }).toThrow("Missing kind tag");
92
+ });
93
+ it("should return null if missing E tag", () => {
94
+ const tags = [
95
+ ["K", "1621"],
96
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
97
+ ];
98
+ expect(getCommentEventPointer(tags, true)).toBe(null);
99
+ expect(getCommentEventPointer(tags)).toBe(null);
100
+ });
101
+ });
102
+ describe("getCommentAddressPointer", () => {
103
+ it("should get event id from E tag", () => {
104
+ // root
105
+ expect(getCommentAddressPointer([
106
+ ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
107
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
108
+ ["K", "30000"],
109
+ ], true)).toEqual({
110
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
111
+ kind: 30000,
112
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
113
+ identifier: "list",
114
+ });
115
+ // reply
116
+ expect(getCommentAddressPointer([
117
+ ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
118
+ ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
119
+ ["k", "30000"],
120
+ ])).toEqual({
121
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
122
+ kind: 30000,
123
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
124
+ identifier: "list",
125
+ });
126
+ });
127
+ it("should get relay from A tag", () => {
128
+ // root
129
+ expect(getCommentAddressPointer([
130
+ ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list", "wss://relay.io/"],
131
+ ["K", "30000"],
132
+ ], true)).toEqual({
133
+ kind: 30000,
134
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
135
+ identifier: "list",
136
+ relay: "wss://relay.io/",
137
+ });
138
+ // reply
139
+ expect(getCommentAddressPointer([
140
+ ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list", "wss://relay.io/"],
141
+ ["k", "30000"],
142
+ ])).toEqual({
143
+ kind: 30000,
144
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
145
+ identifier: "list",
146
+ relay: "wss://relay.io/",
147
+ });
148
+ });
149
+ it("should get relay from E tag", () => {
150
+ // root
151
+ expect(getCommentAddressPointer([
152
+ ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
153
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
154
+ ["K", "30000"],
155
+ ], true)).toEqual({
156
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
157
+ kind: 30000,
158
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
159
+ identifier: "list",
160
+ relay: "wss://relay.io/",
161
+ });
162
+ // reply
163
+ expect(getCommentAddressPointer([
164
+ ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
165
+ ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
166
+ ["k", "30000"],
167
+ ])).toEqual({
168
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
169
+ kind: 30000,
170
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
171
+ identifier: "list",
172
+ relay: "wss://relay.io/",
173
+ });
174
+ });
175
+ it("should return A tag kind over K tag", () => {
176
+ // root
177
+ expect(getCommentAddressPointer([
178
+ ["A", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
179
+ ["K", "30000"],
180
+ ], true)).toEqual({
181
+ kind: 30010,
182
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
183
+ identifier: "list",
184
+ });
185
+ // reply
186
+ expect(getCommentAddressPointer([
187
+ ["a", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
188
+ ["k", "30000"],
189
+ ])).toEqual({
190
+ kind: 30010,
191
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
192
+ identifier: "list",
193
+ });
194
+ });
195
+ it("should throw if missing K tag", () => {
196
+ // root
197
+ expect(() => getCommentAddressPointer([["A", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"]], true)).toThrow("Missing kind tag");
198
+ // reply
199
+ expect(() => getCommentAddressPointer([["a", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"]])).toThrow("Missing kind tag");
200
+ });
201
+ it("should return null if missing A tag", () => {
202
+ const tags = [
203
+ ["K", "1621"],
204
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
205
+ ];
206
+ expect(getCommentEventPointer(tags, true)).toBe(null);
207
+ expect(getCommentEventPointer(tags)).toBe(null);
208
+ });
209
+ });
210
+ describe("getCommentExternalPointer", () => {
211
+ it("should get kind prefix from I tag", () => {
212
+ // root
213
+ expect(getCommentExternalPointer([
214
+ ["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"],
215
+ ["K", "podcast:item:guid"],
216
+ ], true)).toEqual({
217
+ identifier: "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
218
+ kind: "podcast:item:guid",
219
+ });
220
+ // reply
221
+ expect(getCommentExternalPointer([
222
+ ["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"],
223
+ ["k", "podcast:item:guid"],
224
+ ])).toEqual({
225
+ identifier: "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
226
+ kind: "podcast:item:guid",
227
+ });
228
+ });
229
+ it("should throw if missing K tag", () => {
230
+ // root
231
+ expect(() => getCommentExternalPointer([["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"]], true)).toThrow("Missing kind tag");
232
+ // reply
233
+ expect(() => getCommentExternalPointer([["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"]])).toThrow("Missing kind tag");
234
+ });
235
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getEmojiTag } from "../emoji.js";
3
+ import { FakeUser } from "../../__tests__/fixtures.js";
4
+ const user = new FakeUser();
5
+ describe("getEmojiTag", () => {
6
+ it("Should find emoji tag", () => {
7
+ expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), "custom")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
8
+ });
9
+ it("Should custom leading and trailing :", () => {
10
+ expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), ":custom:")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
11
+ });
12
+ it("Should convert to lowercase", () => {
13
+ expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), "CustoM")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
14
+ });
15
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { EventIndexableTagsSymbol, getIndexableTags, getTagValue } from "../event.js";
3
+ const event = {
4
+ content: "",
5
+ created_at: 1732889913,
6
+ id: "2d53511f321cc82dd13eedfb597c9fe834d12d271c10d8068e9d8cfb8f58d1b4",
7
+ kind: 30000,
8
+ pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
9
+ sig: "e6a442487ef44a8a00ec1e0a852e547991fcd5cbf19aa1a4219fa65d6f41022675e0745207649f4b16fe9a6c5c7c3693dc3e13966ffa5b2891634867c874cf22",
10
+ tags: [
11
+ ["d", "qRxLhBbTfRlxsvKSu0iUl"],
12
+ ["title", "Musicians"],
13
+ ["client", "noStrudel", "31990:266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5:1686066542546"],
14
+ ["p", "2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493"],
15
+ ["p", "28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc"],
16
+ ["p", "f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199"],
17
+ ],
18
+ };
19
+ describe("getIndexableTags", () => {
20
+ it("should return a set of indexable tags for event", () => {
21
+ expect(Array.from(getIndexableTags(event))).toEqual(expect.arrayContaining([
22
+ "p:2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493",
23
+ "p:28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc",
24
+ "p:f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199",
25
+ ]));
26
+ });
27
+ it("should cache value on EventIndexableTagsSymbol", () => {
28
+ getIndexableTags(event);
29
+ expect(Reflect.has(event, EventIndexableTagsSymbol)).toBe(true);
30
+ });
31
+ });
32
+ describe("getTagValue", () => {
33
+ it("should return value of tag if present", () => {
34
+ expect(getTagValue(event, "title")).toBe("Musicians");
35
+ });
36
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getFileMetadataFromImetaTag, parseFileMetadataTags } from "../file-metadata.js";
3
+ describe("file metadata helpers", () => {
4
+ describe("parseFileMetadataTags", () => {
5
+ it("should parse a simple 1060 event", () => {
6
+ const tags = [
7
+ ["url", "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif"],
8
+ ["ox", "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae"],
9
+ ["fallback", "https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
10
+ ["x", "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5"],
11
+ ["m", "image/gif"],
12
+ ["dim", "360x306"],
13
+ ["bh", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
14
+ ["blurhash", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
15
+ [
16
+ "thumb",
17
+ "https://image.nostr.build/thumb/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
18
+ ],
19
+ ["t", "gifbuddy"],
20
+ ["summary", "Khaleesi call dragons Daenerys Targaryen"],
21
+ ["alt", "a woman with blonde hair and a brooch on her shoulder"],
22
+ [
23
+ "thumb",
24
+ "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
25
+ "5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
26
+ ],
27
+ [
28
+ "image",
29
+ "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
30
+ "5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
31
+ ],
32
+ ];
33
+ expect(parseFileMetadataTags(tags)).toEqual({
34
+ url: "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
35
+ type: "image/gif",
36
+ dimensions: "360x306",
37
+ blurhash: "L38zleNL00~W^kRj0L-p0KM_^kx]",
38
+ sha256: "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5",
39
+ originalSha256: "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae",
40
+ thumbnail: "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
41
+ image: "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
42
+ summary: "Khaleesi call dragons Daenerys Targaryen",
43
+ fallback: ["https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
44
+ alt: "a woman with blonde hair and a brooch on her shoulder",
45
+ });
46
+ });
47
+ });
48
+ describe("getFileMetadataFromImetaTag", () => {
49
+ it("should parse simple imeta tag", () => {
50
+ expect(getFileMetadataFromImetaTag([
51
+ "imeta",
52
+ "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
53
+ "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
54
+ "dim 1024x1024",
55
+ "m image/jpeg",
56
+ "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
57
+ ])).toEqual({
58
+ url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
59
+ sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
60
+ dimensions: "1024x1024",
61
+ type: "image/jpeg",
62
+ blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
63
+ });
64
+ });
65
+ it("should parse thumbnail url", () => {
66
+ expect(getFileMetadataFromImetaTag([
67
+ "imeta",
68
+ "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
69
+ "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
70
+ "dim 1024x1024",
71
+ "m image/jpeg",
72
+ "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
73
+ "thumb https://exmaple.com/thumb.jpg",
74
+ ])).toEqual({
75
+ url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
76
+ sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
77
+ dimensions: "1024x1024",
78
+ type: "image/jpeg",
79
+ blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
80
+ thumbnail: "https://exmaple.com/thumb.jpg",
81
+ });
82
+ });
83
+ it("should parse multiple fallback urls", () => {
84
+ expect(getFileMetadataFromImetaTag([
85
+ "imeta",
86
+ "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
87
+ "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
88
+ "dim 1024x1024",
89
+ "m image/jpeg",
90
+ "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
91
+ "fallback https://exmaple.com/image2.jpg",
92
+ "fallback https://exmaple.com/image3.jpg",
93
+ ])).toEqual({
94
+ url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
95
+ sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
96
+ dimensions: "1024x1024",
97
+ type: "image/jpeg",
98
+ blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
99
+ fallback: ["https://exmaple.com/image2.jpg", "https://exmaple.com/image3.jpg"],
100
+ });
101
+ });
102
+ });
103
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import { describe, beforeEach, it, expect } from "vitest";
2
- import { getHiddenTags, unixNow, unlockHiddenTags } from "applesauce-core/helpers";
3
2
  import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools";
3
+ import { getHiddenTags, unlockHiddenTags } from "../hidden-tags.js";
4
+ import { unixNow } from "../time.js";
4
5
  const key = generateSecretKey();
5
6
  const pubkey = getPublicKey(key);
6
7
  const signer = {
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "vitest";
2
- import { getInboxes, getOutboxes } from "./mailboxes.js";
2
+ import { getInboxes, getOutboxes } from "../mailboxes.js";
3
3
  const emptyEvent = {
4
4
  kind: 10002,
5
5
  content: "",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { isSafeRelayURL } from "../relays.js";
3
+ describe("isSafeRelayURL", () => {
4
+ it("should correctly filter URLs", () => {
5
+ // safe URLs
6
+ expect(isSafeRelayURL("wss://relay.damus.io/")).toBe(true);
7
+ expect(isSafeRelayURL("wss://nostrue.com")).toBe(true);
8
+ expect(isSafeRelayURL("ws://192.168.0.194:8080")).toBe(true);
9
+ expect(isSafeRelayURL("ws://localhost:4869/ws")).toBe(true);
10
+ expect(isSafeRelayURL("ws://localhost/testing")).toBe(true);
11
+ expect(isSafeRelayURL("ws://437fqnfqtcaquzvs5sd43ugznw7dsoatvtskoowgnpn6q5vqkljcrsyd.onion")).toBe(true);
12
+ expect(isSafeRelayURL("ws://hypr1fk4trjnhjf62r6hhkpettmvxhxx2uvkkg4u4ea44va2fvxvfkl4s82m6dy.hyper")).toBe(true);
13
+ // bad URLs
14
+ expect(isSafeRelayURL("")).toBe(false);
15
+ expect(isSafeRelayURL("bad")).toBe(false);
16
+ expect(isSafeRelayURL("bad wss://nostr.wine")).toBe(false);
17
+ expect(isSafeRelayURL("http://nostr.wine")).toBe(false);
18
+ expect(isSafeRelayURL("http://cache-relay.com")).toBe(false);
19
+ expect(isSafeRelayURL("wss://nostr.wine,wss://relayable.com")).toBe(false);
20
+ });
21
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { isATag, isNameValueTag, processTags } from "../tags.js";
3
+ import { getAddressPointerFromATag } from "../pointers.js";
4
+ describe("isNameValueTag", () => {
5
+ it("should return true if tag has at least two indexes", () => {
6
+ expect(isNameValueTag(["a", "30000:pubkey:list"])).toBe(true);
7
+ expect(isNameValueTag(["title", "article", "other-value"])).toBe(true);
8
+ });
9
+ it("should ignore tags without values", () => {
10
+ expect(isNameValueTag(["a"])).toBe(false);
11
+ expect(isNameValueTag(["title"])).toBe(false);
12
+ });
13
+ });
14
+ describe("processTags", () => {
15
+ it("should filter out errors", () => {
16
+ expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], getAddressPointerFromATag)).toEqual([{ identifier: "list", kind: 30000, pubkey: "pubkey" }]);
17
+ });
18
+ it("should filter out undefined", () => {
19
+ expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], (tag) => isATag(tag) ? tag : undefined)).toEqual([
20
+ ["a", "bad coordinate"],
21
+ ["a", "30000:pubkey:list"],
22
+ ]);
23
+ });
24
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { interpretThreadTags } from "./threading.js";
2
+ import { interpretThreadTags } from "../threading.js";
3
3
  describe("threading helpers", () => {
4
4
  describe("interpretThreadTags", () => {
5
5
  it("should handle legacy tags", () => {
@@ -0,0 +1,9 @@
1
+ export declare const BLOSSOM_SERVER_LIST_KIND = 10063;
2
+ /** Check if two servers are the same */
3
+ export declare function areBlossomServersEqual(a: string | URL, b: string | URL): boolean;
4
+ /** Checks if a string is a sha256 hash */
5
+ export declare function isSha256(str: string): boolean;
6
+ /** Returns an ordered array of servers found in a server list event (10063) */
7
+ export declare function getBlossomServersFromList(event: {
8
+ tags: string[][];
9
+ } | string[][]): URL[];
@@ -0,0 +1,22 @@
1
+ import { isNameValueTag, processTags } from "./tags.js";
2
+ export const BLOSSOM_SERVER_LIST_KIND = 10063;
3
+ /** Check if two servers are the same */
4
+ export function areBlossomServersEqual(a, b) {
5
+ const hostnameA = new URL("/", a).toString();
6
+ const hostnameB = new URL("/", b).toString();
7
+ return hostnameA === hostnameB;
8
+ }
9
+ /** Checks if a string is a sha256 hash */
10
+ export function isSha256(str) {
11
+ return !!str.match(/^[0-9a-f]{64}$/);
12
+ }
13
+ /** Returns an ordered array of servers found in a server list event (10063) */
14
+ export function getBlossomServersFromList(event) {
15
+ const tags = Array.isArray(event) ? event : event.tags;
16
+ return processTags(tags, (tag) => {
17
+ if (isNameValueTag(tag, "server") && URL.canParse(tag[1]))
18
+ return new URL("/", tag[1]);
19
+ else
20
+ return undefined;
21
+ });
22
+ }
@@ -0,0 +1,15 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
+ export declare const BookmarkPublicSymbol: unique symbol;
4
+ export declare const BookmarkHiddenSymbol: unique symbol;
5
+ export type Bookmarks = {
6
+ notes: EventPointer[];
7
+ articles: AddressPointer[];
8
+ hashtags: string[];
9
+ urls: string[];
10
+ };
11
+ export declare function parseBookmarkTags(tags: string[][]): Bookmarks;
12
+ /** Returns the public bookmarks of the event */
13
+ export declare function getBookmarks(bookmark: NostrEvent): Bookmarks;
14
+ /** Returns the bookmarks of the event if its unlocked */
15
+ export declare function getHiddenBookmarks(bookmark: NostrEvent): Bookmarks | undefined;
@@ -0,0 +1,27 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
3
+ import { getOrComputeCachedValue } from "./cache.js";
4
+ import { getHiddenTags } from "./index.js";
5
+ export const BookmarkPublicSymbol = Symbol.for("bookmark-public");
6
+ export const BookmarkHiddenSymbol = Symbol.for("bookmark-hidden");
7
+ export function parseBookmarkTags(tags) {
8
+ const notes = tags.filter((t) => t[0] === "e" && t[1]).map(getEventPointerFromETag);
9
+ const articles = tags
10
+ .filter((t) => t[0] === "a" && t[1])
11
+ .map(getAddressPointerFromATag)
12
+ .filter((addr) => addr.kind === kinds.LongFormArticle);
13
+ const hashtags = tags.filter((t) => t[0] === "t" && t[1]).map((t) => t[1]);
14
+ const urls = tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]);
15
+ return { notes, articles, hashtags, urls };
16
+ }
17
+ /** Returns the public bookmarks of the event */
18
+ export function getBookmarks(bookmark) {
19
+ return getOrComputeCachedValue(bookmark, BookmarkPublicSymbol, () => parseBookmarkTags(bookmark.tags));
20
+ }
21
+ /** Returns the bookmarks of the event if its unlocked */
22
+ export function getHiddenBookmarks(bookmark) {
23
+ return getOrComputeCachedValue(bookmark, BookmarkHiddenSymbol, () => {
24
+ const tags = getHiddenTags(bookmark);
25
+ return tags && parseBookmarkTags(tags);
26
+ });
27
+ }
@@ -0,0 +1,10 @@
1
+ import { nip19, NostrEvent } from "nostr-tools";
2
+ import { ChannelMetadata } from "nostr-tools/nip28";
3
+ export declare const ChannelMetadataSymbol: unique symbol;
4
+ export type ChannelMetadataContent = ChannelMetadata & {
5
+ relays?: string[];
6
+ };
7
+ /** Gets the parsed metadata on a channel creation or channel metadata event */
8
+ export declare function getChannelMetadataContent(channel: NostrEvent): ChannelMetadataContent;
9
+ /** gets the EventPointer for a channel message or metadata event */
10
+ export declare function getChannelPointer(event: NostrEvent): nip19.EventPointer | undefined;
@@ -0,0 +1,27 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ export const ChannelMetadataSymbol = Symbol.for("channel-metadata");
3
+ function parseChannelMetadataContent(channel) {
4
+ const metadata = JSON.parse(channel.content);
5
+ if (metadata.name === undefined)
6
+ throw new Error("Missing name");
7
+ if (metadata.about === undefined)
8
+ throw new Error("Missing about");
9
+ if (metadata.picture === undefined)
10
+ throw new Error("Missing picture");
11
+ if (metadata.relays && !Array.isArray(metadata.relays))
12
+ throw new Error("Invalid relays");
13
+ return metadata;
14
+ }
15
+ /** Gets the parsed metadata on a channel creation or channel metadata event */
16
+ export function getChannelMetadataContent(channel) {
17
+ return getOrComputeCachedValue(channel, ChannelMetadataSymbol, () => {
18
+ return parseChannelMetadataContent(channel);
19
+ });
20
+ }
21
+ /** gets the EventPointer for a channel message or metadata event */
22
+ export function getChannelPointer(event) {
23
+ const tag = event.tags.find((t) => t[0] === "e" && t[1]);
24
+ if (!tag)
25
+ return undefined;
26
+ return tag[2] ? { id: tag[1], relays: [tag[2]] } : { id: tag[1] };
27
+ }
@@ -1,20 +1,20 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  import { ExternalPointer, ExternalIdentifiers } from "./external-id.js";
3
3
  export declare const COMMENT_KIND = 1111;
4
- type CommentEventPointer = {
4
+ export type CommentEventPointer = {
5
5
  id: string;
6
6
  kind: number;
7
7
  pubkey?: string;
8
8
  relay?: string;
9
9
  };
10
- type CommentAddressPointer = {
10
+ export type CommentAddressPointer = {
11
11
  id?: string;
12
12
  kind: number;
13
13
  pubkey: string;
14
14
  identifier: string;
15
15
  relay?: string;
16
16
  };
17
- type CommentExternalPointer = ExternalPointer<keyof ExternalIdentifiers>;
17
+ export type CommentExternalPointer = ExternalPointer<keyof ExternalIdentifiers>;
18
18
  export type CommentPointer = CommentEventPointer | CommentAddressPointer | CommentExternalPointer;
19
19
  export declare const CommentRootPointerSymbol: unique symbol;
20
20
  export declare const CommentReplyPointerSymbol: unique symbol;
@@ -45,4 +45,3 @@ export declare function getCommentRootPointer(comment: NostrEvent): CommentPoint
45
45
  export declare function getCommentReplyPointer(comment: NostrEvent): CommentPointer | null;
46
46
  export declare function isCommentEventPointer(pointer: any): pointer is CommentEventPointer;
47
47
  export declare function isCommentAddressPointer(pointer: any): pointer is CommentAddressPointer;
48
- export {};