applesauce-core 0.10.0 → 0.12.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 (139) 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 +272 -0
  4. package/dist/event-store/database.d.ts +7 -5
  5. package/dist/event-store/database.js +14 -8
  6. package/dist/event-store/event-store.d.ts +40 -20
  7. package/dist/event-store/event-store.js +269 -314
  8. package/dist/event-store/index.d.ts +1 -0
  9. package/dist/event-store/index.js +1 -0
  10. package/dist/event-store/interface.d.ts +27 -0
  11. package/dist/helpers/__tests__/blossom.test.js +13 -0
  12. package/dist/helpers/__tests__/comment.test.d.ts +1 -0
  13. package/dist/helpers/__tests__/comment.test.js +235 -0
  14. package/dist/helpers/__tests__/emoji.test.d.ts +1 -0
  15. package/dist/helpers/__tests__/emoji.test.js +15 -0
  16. package/dist/helpers/__tests__/event.test.d.ts +1 -0
  17. package/dist/helpers/__tests__/event.test.js +36 -0
  18. package/dist/helpers/__tests__/file-metadata.test.d.ts +1 -0
  19. package/dist/helpers/__tests__/file-metadata.test.js +103 -0
  20. package/dist/helpers/__tests__/hidden-tags.test.d.ts +1 -0
  21. package/dist/helpers/{hidden-tags.test.js → __tests__/hidden-tags.test.js} +2 -1
  22. package/dist/helpers/__tests__/mailboxes.test.d.ts +1 -0
  23. package/dist/helpers/{mailboxes.test.js → __tests__/mailboxes.test.js} +1 -1
  24. package/dist/helpers/__tests__/nip-19.test.d.ts +1 -0
  25. package/dist/helpers/__tests__/nip-19.test.js +42 -0
  26. package/dist/helpers/__tests__/relays.test.d.ts +1 -0
  27. package/dist/helpers/__tests__/relays.test.js +21 -0
  28. package/dist/helpers/__tests__/tags.test.d.ts +1 -0
  29. package/dist/helpers/__tests__/tags.test.js +24 -0
  30. package/dist/helpers/__tests__/threading.test.d.ts +1 -0
  31. package/dist/helpers/{threading.test.js → __tests__/threading.test.js} +1 -1
  32. package/dist/helpers/blossom.d.ts +9 -0
  33. package/dist/helpers/blossom.js +22 -0
  34. package/dist/helpers/bookmarks.d.ts +15 -0
  35. package/dist/helpers/bookmarks.js +27 -0
  36. package/dist/helpers/cache.d.ts +3 -4
  37. package/dist/helpers/cache.js +1 -1
  38. package/dist/helpers/channels.d.ts +10 -0
  39. package/dist/helpers/channels.js +27 -0
  40. package/dist/helpers/comment.d.ts +3 -4
  41. package/dist/helpers/comment.js +20 -16
  42. package/dist/helpers/contacts.d.ts +3 -0
  43. package/dist/helpers/contacts.js +25 -0
  44. package/dist/helpers/direct-messages.d.ts +4 -0
  45. package/dist/helpers/direct-messages.js +5 -0
  46. package/dist/helpers/dns-identity.d.ts +7 -0
  47. package/dist/helpers/dns-identity.js +10 -0
  48. package/dist/helpers/emoji.d.ts +3 -1
  49. package/dist/helpers/emoji.js +2 -2
  50. package/dist/helpers/event.d.ts +15 -1
  51. package/dist/helpers/event.js +34 -11
  52. package/dist/helpers/file-metadata.d.ts +55 -0
  53. package/dist/helpers/file-metadata.js +99 -0
  54. package/dist/helpers/filter.d.ts +4 -0
  55. package/dist/helpers/filter.js +34 -1
  56. package/dist/helpers/gift-wraps.d.ts +12 -0
  57. package/dist/helpers/gift-wraps.js +49 -0
  58. package/dist/helpers/groups.d.ts +24 -0
  59. package/dist/helpers/groups.js +39 -0
  60. package/dist/helpers/hidden-content.d.ts +48 -0
  61. package/dist/helpers/hidden-content.js +88 -0
  62. package/dist/helpers/hidden-tags.d.ts +17 -35
  63. package/dist/helpers/hidden-tags.js +26 -83
  64. package/dist/helpers/index.d.ts +16 -1
  65. package/dist/helpers/index.js +16 -1
  66. package/dist/helpers/lists.d.ts +28 -0
  67. package/dist/helpers/lists.js +65 -0
  68. package/dist/helpers/mailboxes.js +16 -9
  69. package/dist/helpers/mutes.d.ts +15 -0
  70. package/dist/helpers/mutes.js +24 -0
  71. package/dist/helpers/nip-19.d.ts +4 -0
  72. package/dist/helpers/nip-19.js +27 -0
  73. package/dist/helpers/picture-post.d.ts +4 -0
  74. package/dist/helpers/picture-post.js +6 -0
  75. package/dist/helpers/pointers.js +13 -17
  76. package/dist/helpers/profile.d.ts +6 -1
  77. package/dist/helpers/profile.js +4 -0
  78. package/dist/helpers/relays.d.ts +6 -3
  79. package/dist/helpers/relays.js +25 -18
  80. package/dist/helpers/share.d.ts +4 -0
  81. package/dist/helpers/share.js +12 -0
  82. package/dist/helpers/tags.d.ts +17 -0
  83. package/dist/helpers/tags.js +28 -6
  84. package/dist/helpers/threading.js +3 -3
  85. package/dist/helpers/url.d.ts +7 -0
  86. package/dist/helpers/url.js +27 -0
  87. package/dist/helpers/user-status.d.ts +18 -0
  88. package/dist/helpers/user-status.js +21 -0
  89. package/dist/observable/__tests__/claim-events.test.d.ts +1 -0
  90. package/dist/observable/__tests__/claim-events.test.js +23 -0
  91. package/dist/observable/__tests__/claim-latest.test.d.ts +1 -0
  92. package/dist/observable/__tests__/claim-latest.test.js +37 -0
  93. package/dist/observable/__tests__/simple-timeout.test.d.ts +1 -0
  94. package/dist/observable/__tests__/simple-timeout.test.js +34 -0
  95. package/dist/observable/claim-events.d.ts +5 -0
  96. package/dist/observable/claim-events.js +28 -0
  97. package/dist/observable/claim-latest.d.ts +5 -0
  98. package/dist/observable/claim-latest.js +21 -0
  99. package/dist/observable/{get-value.d.ts → get-observable-value.d.ts} +1 -1
  100. package/dist/observable/{get-value.js → get-observable-value.js} +3 -8
  101. package/dist/observable/index.d.ts +2 -1
  102. package/dist/observable/index.js +2 -1
  103. package/dist/observable/share-latest-value.d.ts +2 -4
  104. package/dist/observable/share-latest-value.js +19 -16
  105. package/dist/observable/simple-timeout.d.ts +4 -0
  106. package/dist/observable/simple-timeout.js +6 -0
  107. package/dist/observable/with-immediate-value.d.ts +3 -0
  108. package/dist/observable/with-immediate-value.js +19 -0
  109. package/dist/queries/blossom.d.ts +2 -0
  110. package/dist/queries/blossom.js +10 -0
  111. package/dist/queries/bookmarks.d.ts +8 -0
  112. package/dist/queries/bookmarks.js +23 -0
  113. package/dist/queries/channels.d.ts +11 -0
  114. package/dist/queries/channels.js +73 -0
  115. package/dist/queries/contacts.d.ts +3 -0
  116. package/dist/queries/contacts.js +12 -0
  117. package/dist/queries/index.d.ts +6 -0
  118. package/dist/queries/index.js +6 -0
  119. package/dist/queries/mutes.d.ts +8 -0
  120. package/dist/queries/mutes.js +23 -0
  121. package/dist/queries/pins.d.ts +3 -0
  122. package/dist/queries/pins.js +12 -0
  123. package/dist/queries/simple.d.ts +3 -3
  124. package/dist/queries/simple.js +3 -3
  125. package/dist/queries/thread.js +1 -1
  126. package/dist/queries/user-status.d.ts +11 -0
  127. package/dist/queries/user-status.js +39 -0
  128. package/dist/query-store/__tests__/query-store.test.d.ts +1 -0
  129. package/dist/query-store/__tests__/query-store.test.js +63 -0
  130. package/dist/query-store/index.d.ts +1 -57
  131. package/dist/query-store/index.js +1 -66
  132. package/dist/query-store/query-store.d.ts +53 -0
  133. package/dist/query-store/query-store.js +97 -0
  134. package/package.json +20 -8
  135. package/dist/helpers/media-attachment.d.ts +0 -33
  136. package/dist/helpers/media-attachment.js +0 -60
  137. /package/dist/{helpers/hidden-tags.test.d.ts → event-store/__tests__/event-store.test.d.ts} +0 -0
  138. /package/dist/{helpers/mailboxes.test.d.ts → event-store/interface.js} +0 -0
  139. /package/dist/helpers/{threading.test.d.ts → __tests__/blossom.test.d.ts} +0 -0
@@ -1,2 +1,3 @@
1
1
  export * from "./event-store.js";
2
2
  export * from "./database.js";
3
+ export * from "./interface.js";
@@ -0,0 +1,27 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { Observable } from "rxjs";
3
+ export interface IEventStore {
4
+ updates: Observable<NostrEvent>;
5
+ add(event: NostrEvent, fromRelay?: string): NostrEvent;
6
+ remove(event: string | NostrEvent): boolean;
7
+ update(event: NostrEvent): NostrEvent;
8
+ hasEvent(id: string): boolean;
9
+ hasReplaceable(kind: number, pubkey: string, identifier?: string): boolean;
10
+ getEvent(id: string): NostrEvent | undefined;
11
+ getReplaceable(kind: number, pubkey: string, identifier?: string): NostrEvent | undefined;
12
+ getReplaceableHistory(kind: number, pubkey: string, identifier?: string): NostrEvent[] | undefined;
13
+ getAll(filters: Filter | Filter[]): Set<NostrEvent>;
14
+ getTimeline(filters: Filter | Filter[]): NostrEvent[];
15
+ filters(filters: Filter | Filter[]): Observable<NostrEvent>;
16
+ updated(id: string | NostrEvent): Observable<NostrEvent>;
17
+ removed(id: string): Observable<never>;
18
+ event(id: string): Observable<NostrEvent | undefined>;
19
+ events(ids: string[]): Observable<Record<string, NostrEvent>>;
20
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
21
+ replaceableSet(pointers: {
22
+ kind: number;
23
+ pubkey: string;
24
+ identifier?: string;
25
+ }[]): Observable<Record<string, NostrEvent>>;
26
+ timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
27
+ }
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { areBlossomServersEqual } from "../blossom.js";
3
+ describe("areBlossomServersEqual", () => {
4
+ it("should ignore path", () => {
5
+ expect(areBlossomServersEqual("https://cdn.server.com/pathname", "https://cdn.server.com")).toBe(true);
6
+ });
7
+ it("should not ignore protocol", () => {
8
+ expect(areBlossomServersEqual("http://cdn.server.com", "https://cdn.server.com")).toBe(false);
9
+ });
10
+ it("should not ignore port", () => {
11
+ expect(areBlossomServersEqual("http://cdn.server.com:4658", "https://cdn.server.com")).toBe(false);
12
+ });
13
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -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,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { bytesToHex } from "@noble/hashes/utils";
3
+ import { normalizeToPubkey, normalizeToSecretKey } from "../nip-19.js";
4
+ describe("normalizeToPubkey", () => {
5
+ it("should get pubkey from npub", () => {
6
+ expect(normalizeToPubkey("npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr")).toEqual("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
7
+ });
8
+ it("should get pubkey from nprofile", () => {
9
+ expect(normalizeToPubkey("nprofile1qyw8wumn8ghj7umpw3jkcmrfw3jju6r6wfjrzdpe9e3k7mf0qyf8wumn8ghj7mn0wd68yat99e3k7mf0qqszv6q4uryjzr06xfxxew34wwc5hmjfmfpqn229d72gfegsdn2q3fg5g7lja")).toEqual("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
10
+ });
11
+ it("should return hex pubkey", () => {
12
+ expect(normalizeToPubkey("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")).toEqual("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
13
+ });
14
+ it("should throw on invalid hex pubkey", () => {
15
+ expect(() => {
16
+ normalizeToPubkey("5028372");
17
+ }).toThrow();
18
+ });
19
+ it("should throw on invalid string", () => {
20
+ expect(() => {
21
+ normalizeToPubkey("testing");
22
+ }).toThrow();
23
+ });
24
+ });
25
+ describe("normalizeToSecretKey", () => {
26
+ it("should get secret key from nsec", () => {
27
+ expect(bytesToHex(normalizeToSecretKey("nsec1xe7znq745x5n68566l32ru72aajz3pk2cys9lnf3tuexvkw0dldsj8v2lm"))).toEqual("367c2983d5a1a93d1e9ad7e2a1f3caef642886cac1205fcd315f326659cf6fdb");
28
+ });
29
+ it("should get secret key from raw hex", () => {
30
+ expect(bytesToHex(normalizeToSecretKey("367c2983d5a1a93d1e9ad7e2a1f3caef642886cac1205fcd315f326659cf6fdb"))).toEqual("367c2983d5a1a93d1e9ad7e2a1f3caef642886cac1205fcd315f326659cf6fdb");
31
+ });
32
+ it("should throw on invalid hex key", () => {
33
+ expect(() => {
34
+ normalizeToSecretKey("209573290");
35
+ }).toThrow();
36
+ });
37
+ it("should throw on npub", () => {
38
+ expect(() => {
39
+ normalizeToSecretKey("npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr");
40
+ }).toThrow();
41
+ });
42
+ });
@@ -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;