applesauce-core 0.0.0-next-20250123214405 → 0.0.0-next-20250124150519
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.
- package/dist/event-store/common.d.ts +1 -0
- package/dist/event-store/common.js +2 -0
- package/dist/event-store/database.d.ts +64 -0
- package/dist/event-store/database.js +311 -0
- package/dist/event-store/event-store.d.ts +49 -0
- package/dist/event-store/event-store.js +389 -0
- package/dist/event-store/index.d.ts +2 -0
- package/dist/event-store/index.js +2 -0
- package/dist/helpers/bolt11.d.ts +9 -0
- package/dist/helpers/bolt11.js +15 -0
- package/dist/helpers/cache.d.ts +5 -0
- package/dist/helpers/cache.js +17 -0
- package/dist/helpers/comment.d.ts +47 -0
- package/dist/helpers/comment.js +116 -0
- package/dist/helpers/content.d.ts +3 -0
- package/dist/helpers/content.js +8 -0
- package/dist/helpers/delete.d.ts +3 -0
- package/dist/helpers/delete.js +7 -0
- package/dist/helpers/emoji.d.ts +11 -0
- package/dist/helpers/emoji.js +16 -0
- package/dist/helpers/event.d.ts +48 -0
- package/dist/helpers/event.js +105 -0
- package/dist/helpers/external-id.d.ts +29 -0
- package/dist/helpers/external-id.js +20 -0
- package/dist/helpers/file-metadata.d.ts +55 -0
- package/dist/helpers/file-metadata.js +99 -0
- package/dist/helpers/file-metadata.test.d.ts +1 -0
- package/dist/helpers/file-metadata.test.js +103 -0
- package/dist/helpers/filter.d.ts +10 -0
- package/dist/helpers/filter.js +46 -0
- package/dist/helpers/groups.d.ts +14 -0
- package/dist/helpers/groups.js +16 -0
- package/dist/helpers/hashtag.d.ts +2 -0
- package/dist/helpers/hashtag.js +7 -0
- package/dist/helpers/hidden-tags.d.ts +48 -0
- package/dist/helpers/hidden-tags.js +108 -0
- package/dist/helpers/hidden-tags.test.d.ts +1 -0
- package/dist/helpers/hidden-tags.test.js +28 -0
- package/dist/helpers/index.d.ts +28 -0
- package/dist/helpers/index.js +28 -0
- package/dist/helpers/json.d.ts +2 -0
- package/dist/helpers/json.js +9 -0
- package/dist/helpers/lnurl.d.ts +4 -0
- package/dist/helpers/lnurl.js +40 -0
- package/dist/helpers/lru.d.ts +32 -0
- package/dist/helpers/lru.js +148 -0
- package/dist/helpers/mailboxes.d.ts +11 -0
- package/dist/helpers/mailboxes.js +36 -0
- package/dist/helpers/mailboxes.test.d.ts +1 -0
- package/dist/helpers/mailboxes.test.js +81 -0
- package/dist/helpers/picture-post.d.ts +4 -0
- package/dist/helpers/picture-post.js +6 -0
- package/dist/helpers/pointers.d.ts +55 -0
- package/dist/helpers/pointers.js +205 -0
- package/dist/helpers/profile.d.ts +20 -0
- package/dist/helpers/profile.js +31 -0
- package/dist/helpers/relays.d.ts +12 -0
- package/dist/helpers/relays.js +31 -0
- package/dist/helpers/share.d.ts +4 -0
- package/dist/helpers/share.js +12 -0
- package/dist/helpers/string.d.ts +10 -0
- package/dist/helpers/string.js +15 -0
- package/dist/helpers/tags.d.ts +25 -0
- package/dist/helpers/tags.js +42 -0
- package/dist/helpers/tags.test.d.ts +1 -0
- package/dist/helpers/tags.test.js +16 -0
- package/dist/helpers/threading.d.ts +55 -0
- package/dist/helpers/threading.js +94 -0
- package/dist/helpers/threading.test.d.ts +1 -0
- package/dist/helpers/threading.test.js +41 -0
- package/dist/helpers/time.d.ts +2 -0
- package/dist/helpers/time.js +4 -0
- package/dist/helpers/url.d.ts +14 -0
- package/dist/helpers/url.js +30 -0
- package/dist/helpers/zap.d.ts +39 -0
- package/dist/helpers/zap.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +2 -0
- package/dist/observable/get-value.d.ts +3 -0
- package/dist/observable/get-value.js +14 -0
- package/dist/observable/index.d.ts +2 -0
- package/dist/observable/index.js +2 -0
- package/dist/observable/share-latest-value.d.ts +8 -0
- package/dist/observable/share-latest-value.js +21 -0
- package/dist/promise/deferred.d.ts +6 -0
- package/dist/promise/deferred.js +15 -0
- package/dist/promise/index.d.ts +1 -0
- package/dist/promise/index.js +1 -0
- package/dist/queries/comments.d.ts +4 -0
- package/dist/queries/comments.js +14 -0
- package/dist/queries/index.d.ts +7 -0
- package/dist/queries/index.js +7 -0
- package/dist/queries/mailboxes.d.ts +6 -0
- package/dist/queries/mailboxes.js +13 -0
- package/dist/queries/profile.d.ts +4 -0
- package/dist/queries/profile.js +12 -0
- package/dist/queries/reactions.d.ts +4 -0
- package/dist/queries/reactions.js +19 -0
- package/dist/queries/simple.d.ts +16 -0
- package/dist/queries/simple.js +38 -0
- package/dist/queries/thread.d.ts +25 -0
- package/dist/queries/thread.js +92 -0
- package/dist/queries/zaps.d.ts +5 -0
- package/dist/queries/zaps.js +21 -0
- package/dist/query-store/index.d.ts +57 -0
- package/dist/query-store/index.js +68 -0
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
|
|
2
|
+
import { getPublicKey, kinds } from "nostr-tools";
|
|
3
|
+
import { getReplaceableIdentifier } from "./event.js";
|
|
4
|
+
import { safeRelayUrls } from "./relays.js";
|
|
5
|
+
import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
|
|
6
|
+
export function parseCoordinate(a, requireD = false, silent = true) {
|
|
7
|
+
const parts = a.split(":");
|
|
8
|
+
const kind = parts[0] && parseInt(parts[0]);
|
|
9
|
+
const pubkey = parts[1];
|
|
10
|
+
const d = parts[2];
|
|
11
|
+
if (!kind) {
|
|
12
|
+
if (silent)
|
|
13
|
+
return null;
|
|
14
|
+
else
|
|
15
|
+
throw new Error("Missing kind");
|
|
16
|
+
}
|
|
17
|
+
if (!pubkey) {
|
|
18
|
+
if (silent)
|
|
19
|
+
return null;
|
|
20
|
+
else
|
|
21
|
+
throw new Error("Missing pubkey");
|
|
22
|
+
}
|
|
23
|
+
if (requireD && d === undefined) {
|
|
24
|
+
if (silent)
|
|
25
|
+
return null;
|
|
26
|
+
else
|
|
27
|
+
throw new Error("Missing identifier");
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
kind,
|
|
31
|
+
pubkey,
|
|
32
|
+
identifier: d,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Extra a pubkey from the result of nip19.decode */
|
|
36
|
+
export function getPubkeyFromDecodeResult(result) {
|
|
37
|
+
if (!result)
|
|
38
|
+
return;
|
|
39
|
+
switch (result.type) {
|
|
40
|
+
case "naddr":
|
|
41
|
+
case "nprofile":
|
|
42
|
+
return result.data.pubkey;
|
|
43
|
+
case "npub":
|
|
44
|
+
return result.data;
|
|
45
|
+
case "nsec":
|
|
46
|
+
return getPublicKey(result.data);
|
|
47
|
+
default:
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Encodes the result of nip19.decode */
|
|
52
|
+
export function encodeDecodeResult(result) {
|
|
53
|
+
switch (result.type) {
|
|
54
|
+
case "naddr":
|
|
55
|
+
return naddrEncode(result.data);
|
|
56
|
+
case "nprofile":
|
|
57
|
+
return nprofileEncode(result.data);
|
|
58
|
+
case "nevent":
|
|
59
|
+
return neventEncode(result.data);
|
|
60
|
+
case "nsec":
|
|
61
|
+
return nsecEncode(result.data);
|
|
62
|
+
case "npub":
|
|
63
|
+
return npubEncode(result.data);
|
|
64
|
+
case "note":
|
|
65
|
+
return noteEncode(result.data);
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Gets an EventPointer form a common "e" tag
|
|
71
|
+
* @throws
|
|
72
|
+
*/
|
|
73
|
+
export function getEventPointerFromETag(tag) {
|
|
74
|
+
if (!tag[1])
|
|
75
|
+
throw new Error("Missing event id in tag");
|
|
76
|
+
let pointer = { id: tag[1] };
|
|
77
|
+
if (tag[2])
|
|
78
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
79
|
+
return pointer;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Gets an EventPointer form a "q" tag
|
|
83
|
+
* @throws
|
|
84
|
+
*/
|
|
85
|
+
export function getEventPointerFromQTag(tag) {
|
|
86
|
+
if (!tag[1])
|
|
87
|
+
throw new Error("Missing event id in tag");
|
|
88
|
+
let pointer = { id: tag[1] };
|
|
89
|
+
if (tag[2])
|
|
90
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
91
|
+
if (tag[3] && tag[3].length === 64)
|
|
92
|
+
pointer.author = tag[3];
|
|
93
|
+
return pointer;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get an AddressPointer from an "a" tag
|
|
97
|
+
* @throws
|
|
98
|
+
*/
|
|
99
|
+
export function getAddressPointerFromATag(tag) {
|
|
100
|
+
if (!tag[1])
|
|
101
|
+
throw new Error("Missing coordinate in tag");
|
|
102
|
+
const pointer = parseCoordinate(tag[1], true, false);
|
|
103
|
+
if (tag[2])
|
|
104
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
105
|
+
return pointer;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Gets a ProfilePointer from a "p" tag
|
|
109
|
+
* @throws
|
|
110
|
+
*/
|
|
111
|
+
export function getProfilePointerFromPTag(tag) {
|
|
112
|
+
if (!tag[1])
|
|
113
|
+
throw new Error("Missing pubkey in tag");
|
|
114
|
+
const pointer = { pubkey: tag[1] };
|
|
115
|
+
if (tag[2])
|
|
116
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
117
|
+
return pointer;
|
|
118
|
+
}
|
|
119
|
+
/** Parses "e", "a", "p", and "q" tags into a pointer */
|
|
120
|
+
export function getPointerFromTag(tag) {
|
|
121
|
+
try {
|
|
122
|
+
switch (tag[0]) {
|
|
123
|
+
case "e":
|
|
124
|
+
return { type: "nevent", data: getEventPointerFromETag(tag) };
|
|
125
|
+
case "a":
|
|
126
|
+
return {
|
|
127
|
+
type: "naddr",
|
|
128
|
+
data: getAddressPointerFromATag(tag),
|
|
129
|
+
};
|
|
130
|
+
case "p":
|
|
131
|
+
return { type: "nprofile", data: getProfilePointerFromPTag(tag) };
|
|
132
|
+
// NIP-18 quote tags
|
|
133
|
+
case "q":
|
|
134
|
+
return { type: "nevent", data: getEventPointerFromETag(tag) };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (error) { }
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
export function isAddressPointer(pointer) {
|
|
141
|
+
return (typeof pointer !== "string" &&
|
|
142
|
+
Reflect.has(pointer, "identifier") &&
|
|
143
|
+
Reflect.has(pointer, "pubkey") &&
|
|
144
|
+
Reflect.has(pointer, "kind"));
|
|
145
|
+
}
|
|
146
|
+
export function isEventPointer(pointer) {
|
|
147
|
+
return typeof pointer !== "string" && Reflect.has(pointer, "id");
|
|
148
|
+
}
|
|
149
|
+
/** Returns the coordinate string for an AddressPointer */
|
|
150
|
+
export function getCoordinateFromAddressPointer(pointer) {
|
|
151
|
+
return `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Returns an AddressPointer for a replaceable event
|
|
155
|
+
* @throws
|
|
156
|
+
*/
|
|
157
|
+
export function getAddressPointerForEvent(event, relays) {
|
|
158
|
+
if (!isParameterizedReplaceableKind(event.kind))
|
|
159
|
+
throw new Error("Cant get AddressPointer for non-replaceable event");
|
|
160
|
+
const d = getReplaceableIdentifier(event);
|
|
161
|
+
return {
|
|
162
|
+
identifier: d,
|
|
163
|
+
kind: event.kind,
|
|
164
|
+
pubkey: event.pubkey,
|
|
165
|
+
relays,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Returns an EventPointer for an event
|
|
170
|
+
* @throws
|
|
171
|
+
*/
|
|
172
|
+
export function getEventPointerForEvent(event, relays) {
|
|
173
|
+
return {
|
|
174
|
+
id: event.id,
|
|
175
|
+
kind: event.kind,
|
|
176
|
+
author: event.pubkey,
|
|
177
|
+
relays,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Returns a pointer for a given event */
|
|
181
|
+
export function getPointerForEvent(event, relays) {
|
|
182
|
+
if (kinds.isParameterizedReplaceableKind(event.kind)) {
|
|
183
|
+
const d = getReplaceableIdentifier(event);
|
|
184
|
+
return {
|
|
185
|
+
type: "naddr",
|
|
186
|
+
data: {
|
|
187
|
+
identifier: d,
|
|
188
|
+
kind: event.kind,
|
|
189
|
+
pubkey: event.pubkey,
|
|
190
|
+
relays,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
return {
|
|
196
|
+
type: "nevent",
|
|
197
|
+
data: {
|
|
198
|
+
id: event.id,
|
|
199
|
+
kind: event.kind,
|
|
200
|
+
author: event.pubkey,
|
|
201
|
+
relays,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const ProfileContentSymbol: unique symbol;
|
|
3
|
+
export type ProfileContent = {
|
|
4
|
+
name?: string;
|
|
5
|
+
display_name?: string;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
about?: string;
|
|
8
|
+
/** @deprecated */
|
|
9
|
+
image?: string;
|
|
10
|
+
picture?: string;
|
|
11
|
+
banner?: string;
|
|
12
|
+
website?: string;
|
|
13
|
+
lud16?: string;
|
|
14
|
+
lud06?: string;
|
|
15
|
+
nip05?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Returns the parsed profile content for a kind 0 event */
|
|
18
|
+
export declare function getProfileContent(event: NostrEvent): ProfileContent;
|
|
19
|
+
/** Checks if the content of the kind 0 event is valid JSON */
|
|
20
|
+
export declare function isValidProfile(profile?: NostrEvent): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
+
export const ProfileContentSymbol = Symbol.for("profile-content");
|
|
4
|
+
/** Returns the parsed profile content for a kind 0 event */
|
|
5
|
+
export function getProfileContent(event) {
|
|
6
|
+
return getOrComputeCachedValue(event, ProfileContentSymbol, () => {
|
|
7
|
+
const profile = JSON.parse(event.content);
|
|
8
|
+
// ensure nip05 is a string
|
|
9
|
+
if (profile.nip05 && typeof profile.nip05 !== "string")
|
|
10
|
+
profile.nip05 = String(profile.nip05);
|
|
11
|
+
// add missing protocol to website
|
|
12
|
+
if (profile.website && profile.website?.length > 0 && profile.website?.startsWith("http") === false) {
|
|
13
|
+
profile.website = "https://" + profile.website;
|
|
14
|
+
}
|
|
15
|
+
return profile;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Checks if the content of the kind 0 event is valid JSON */
|
|
19
|
+
export function isValidProfile(profile) {
|
|
20
|
+
if (!profile)
|
|
21
|
+
return false;
|
|
22
|
+
if (profile.kind !== kinds.Metadata)
|
|
23
|
+
return false;
|
|
24
|
+
try {
|
|
25
|
+
getProfileContent(profile);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const SeenRelaysSymbol: unique symbol;
|
|
3
|
+
declare module "nostr-tools" {
|
|
4
|
+
interface Event {
|
|
5
|
+
[SeenRelaysSymbol]?: Set<string>;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
|
|
9
|
+
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[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const SeenRelaysSymbol = Symbol.for("seen-relays");
|
|
2
|
+
// Seen relays
|
|
3
|
+
export function addSeenRelay(event, relay) {
|
|
4
|
+
if (!event[SeenRelaysSymbol])
|
|
5
|
+
event[SeenRelaysSymbol] = new Set();
|
|
6
|
+
event[SeenRelaysSymbol].add(relay);
|
|
7
|
+
return event[SeenRelaysSymbol];
|
|
8
|
+
}
|
|
9
|
+
export function getSeenRelays(event) {
|
|
10
|
+
return event[SeenRelaysSymbol];
|
|
11
|
+
}
|
|
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;
|
|
20
|
+
}
|
|
21
|
+
export function safeRelayUrl(relayUrl) {
|
|
22
|
+
try {
|
|
23
|
+
return validateRelayURL(relayUrl).toString();
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function safeRelayUrls(urls) {
|
|
30
|
+
return Array.from(urls).map(safeRelayUrl).filter(Boolean);
|
|
31
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Tests if a string is hex */
|
|
2
|
+
export declare function isHex(str?: string): boolean;
|
|
3
|
+
/** Tests if a string is a 64 length hex string */
|
|
4
|
+
export declare function isHexKey(key?: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Remove invisible characters from a string
|
|
7
|
+
* @see read more https://www.regular-expressions.info/unicode.html#category
|
|
8
|
+
*/
|
|
9
|
+
export declare function stripInvisibleChar(str: string): string;
|
|
10
|
+
export declare function stripInvisibleChar(str?: string | undefined): string | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Tests if a string is hex */
|
|
2
|
+
export function isHex(str) {
|
|
3
|
+
if (str?.match(/^[0-9a-f]+$/i))
|
|
4
|
+
return true;
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
/** Tests if a string is a 64 length hex string */
|
|
8
|
+
export function isHexKey(key) {
|
|
9
|
+
if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/))
|
|
10
|
+
return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
export function stripInvisibleChar(str) {
|
|
14
|
+
return str && str.replaceAll(/[\p{Cf}\p{Zs}]/gu, "");
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Checks if tag is an "e" tag and has at least one value */
|
|
2
|
+
export declare function isETag(tag: string[]): tag is ["e", string, ...string[]];
|
|
3
|
+
/** Checks if tag is an "p" tag and has at least one value */
|
|
4
|
+
export declare function isPTag(tag: string[]): tag is ["p", string, ...string[]];
|
|
5
|
+
/** Checks if tag is an "r" tag and has at least one value */
|
|
6
|
+
export declare function isRTag(tag: string[]): tag is ["r", string, ...string[]];
|
|
7
|
+
/** Checks if tag is an "d" tag and has at least one value */
|
|
8
|
+
export declare function isDTag(tag: string[]): tag is ["d", string, ...string[]];
|
|
9
|
+
/** Checks if tag is an "a" tag and has at least one value */
|
|
10
|
+
export declare function isATag(tag: string[]): tag is ["a", string, ...string[]];
|
|
11
|
+
/** Checks if tag is an "a" tag and has at least one value */
|
|
12
|
+
export declare function isTTag(tag: string[]): tag is ["t", string, ...string[]];
|
|
13
|
+
/** A pipeline that filters and maps each tag */
|
|
14
|
+
type TagPipe = {
|
|
15
|
+
<A>(tags: string[][], ta: (tag: string[]) => A | undefined): A[];
|
|
16
|
+
<A, B>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined): B[];
|
|
17
|
+
<A, B, C>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined): C[];
|
|
18
|
+
<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[];
|
|
19
|
+
<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[];
|
|
20
|
+
<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[];
|
|
21
|
+
<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[];
|
|
22
|
+
};
|
|
23
|
+
/** Filter and transform tags */
|
|
24
|
+
export declare const processTags: TagPipe;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Checks if tag is an "e" tag and has at least one value */
|
|
2
|
+
export function isETag(tag) {
|
|
3
|
+
return tag[0] === "e" && tag[1] !== undefined;
|
|
4
|
+
}
|
|
5
|
+
/** Checks if tag is an "p" tag and has at least one value */
|
|
6
|
+
export function isPTag(tag) {
|
|
7
|
+
return tag[0] === "p" && tag[1] !== undefined;
|
|
8
|
+
}
|
|
9
|
+
/** Checks if tag is an "r" tag and has at least one value */
|
|
10
|
+
export function isRTag(tag) {
|
|
11
|
+
return tag[0] === "r" && tag[1] !== undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Checks if tag is an "d" tag and has at least one value */
|
|
14
|
+
export function isDTag(tag) {
|
|
15
|
+
return tag[0] === "d" && tag[1] !== undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Checks if tag is an "a" tag and has at least one value */
|
|
18
|
+
export function isATag(tag) {
|
|
19
|
+
return tag[0] === "a" && tag[1] !== undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Checks if tag is an "a" tag and has at least one value */
|
|
22
|
+
export function isTTag(tag) {
|
|
23
|
+
return tag[0] === "a" && tag[1] !== undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Filter and transform tags */
|
|
26
|
+
export const processTags = (tags, ...fns) => {
|
|
27
|
+
return fns.reduce((step, fn) => {
|
|
28
|
+
const next = [];
|
|
29
|
+
for (const value of step) {
|
|
30
|
+
try {
|
|
31
|
+
const result = fn(value);
|
|
32
|
+
if (result === undefined)
|
|
33
|
+
continue; // value is undefined, ignore
|
|
34
|
+
next.push(result);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// failed to process value, ignore
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return next;
|
|
41
|
+
}, tags);
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isATag, processTags } from "./tags.js";
|
|
3
|
+
import { getAddressPointerFromATag } from "./pointers.js";
|
|
4
|
+
describe("tag helpers", () => {
|
|
5
|
+
describe("processTags", () => {
|
|
6
|
+
it("should filter out errors", () => {
|
|
7
|
+
expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], getAddressPointerFromATag)).toEqual([{ identifier: "list", kind: 30000, pubkey: "pubkey" }]);
|
|
8
|
+
});
|
|
9
|
+
it("should filter out undefined", () => {
|
|
10
|
+
expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], (tag) => isATag(tag) ? tag : undefined)).toEqual([
|
|
11
|
+
["a", "bad coordinate"],
|
|
12
|
+
["a", "30000:pubkey:list"],
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { EventTemplate, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
export type ThreadReferences = {
|
|
4
|
+
root?: {
|
|
5
|
+
e: EventPointer;
|
|
6
|
+
a: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
e: undefined;
|
|
9
|
+
a: AddressPointer;
|
|
10
|
+
} | {
|
|
11
|
+
e: EventPointer;
|
|
12
|
+
a: AddressPointer;
|
|
13
|
+
};
|
|
14
|
+
reply?: {
|
|
15
|
+
e: EventPointer;
|
|
16
|
+
a: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
e: undefined;
|
|
19
|
+
a: AddressPointer;
|
|
20
|
+
} | {
|
|
21
|
+
e: EventPointer;
|
|
22
|
+
a: AddressPointer;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare const Nip10ThreadRefsSymbol: unique symbol;
|
|
26
|
+
/**
|
|
27
|
+
* Gets an EventPointer form a NIP-10 threading "e" tag
|
|
28
|
+
* @throws
|
|
29
|
+
*/
|
|
30
|
+
export declare function getEventPointerFromThreadTag(tag: string[]): EventPointer;
|
|
31
|
+
/** Parses NIP-10 tags and handles legacy behavior */
|
|
32
|
+
export declare function interpretThreadTags(tags: string[][]): {
|
|
33
|
+
root?: {
|
|
34
|
+
e: string[];
|
|
35
|
+
a: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
e: undefined;
|
|
38
|
+
a: string[];
|
|
39
|
+
} | {
|
|
40
|
+
e: string[];
|
|
41
|
+
a: string[];
|
|
42
|
+
};
|
|
43
|
+
reply?: {
|
|
44
|
+
e: string[];
|
|
45
|
+
a: undefined;
|
|
46
|
+
} | {
|
|
47
|
+
e: undefined;
|
|
48
|
+
a: string[];
|
|
49
|
+
} | {
|
|
50
|
+
e: string[];
|
|
51
|
+
a: string[];
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/** Returns the parsed NIP-10 tags for an event */
|
|
55
|
+
export declare function getNip10References(event: NostrEvent | EventTemplate): ThreadReferences;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getAddressPointerFromATag } from "./pointers.js";
|
|
2
|
+
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
+
import { safeRelayUrls } from "./relays.js";
|
|
4
|
+
export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
|
|
5
|
+
/**
|
|
6
|
+
* Gets an EventPointer form a NIP-10 threading "e" tag
|
|
7
|
+
* @throws
|
|
8
|
+
*/
|
|
9
|
+
export function getEventPointerFromThreadTag(tag) {
|
|
10
|
+
if (!tag[1])
|
|
11
|
+
throw new Error("Missing event id in tag");
|
|
12
|
+
let pointer = { id: tag[1] };
|
|
13
|
+
if (tag[2])
|
|
14
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
15
|
+
// get author from NIP-18 quote tags, nip-22 comments tags, or nip-10 thread tags
|
|
16
|
+
if (tag[0] === "e" &&
|
|
17
|
+
(tag[3] === "root" || tag[3] === "reply" || tag[3] === "mention") &&
|
|
18
|
+
tag[4] &&
|
|
19
|
+
tag[4].length === 64) {
|
|
20
|
+
// NIP-10 "e" tag
|
|
21
|
+
pointer.author = tag[4];
|
|
22
|
+
}
|
|
23
|
+
return pointer;
|
|
24
|
+
}
|
|
25
|
+
/** Parses NIP-10 tags and handles legacy behavior */
|
|
26
|
+
export function interpretThreadTags(tags) {
|
|
27
|
+
const eTags = tags.filter((t) => t[0] === "e" && t[1]);
|
|
28
|
+
const aTags = tags.filter((t) => t[0] === "a" && t[1]);
|
|
29
|
+
// find the root and reply tags.
|
|
30
|
+
let rootETag = eTags.find((t) => t[3] === "root");
|
|
31
|
+
let replyETag = eTags.find((t) => t[3] === "reply");
|
|
32
|
+
let rootATag = aTags.find((t) => t[3] === "root");
|
|
33
|
+
let replyATag = aTags.find((t) => t[3] === "reply");
|
|
34
|
+
if (!rootETag || !replyETag) {
|
|
35
|
+
// a direct reply does not need a "reply" reference
|
|
36
|
+
// https://github.com/nostr-protocol/nips/blob/master/10.md
|
|
37
|
+
// this is not necessarily to spec. but if there is only one id (root or reply) then assign it to both
|
|
38
|
+
// this handles the cases where a client only set a "reply" tag and no root
|
|
39
|
+
rootETag = replyETag = rootETag || replyETag;
|
|
40
|
+
}
|
|
41
|
+
if (!rootATag || !replyATag) {
|
|
42
|
+
rootATag = replyATag = rootATag || replyATag;
|
|
43
|
+
}
|
|
44
|
+
if (!rootETag && !replyETag) {
|
|
45
|
+
// legacy behavior
|
|
46
|
+
// https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated
|
|
47
|
+
const legacyETags = eTags.filter((t) => {
|
|
48
|
+
// ignore it if there is a marker
|
|
49
|
+
if (t[3])
|
|
50
|
+
return false;
|
|
51
|
+
return true;
|
|
52
|
+
});
|
|
53
|
+
if (legacyETags.length >= 1) {
|
|
54
|
+
// first tag is the root
|
|
55
|
+
rootETag = legacyETags[0];
|
|
56
|
+
// last tag is reply
|
|
57
|
+
replyETag = legacyETags[legacyETags.length - 1] ?? rootETag;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
root: rootETag || rootATag ? { e: rootETag, a: rootATag } : undefined,
|
|
62
|
+
reply: replyETag || replyATag ? { e: replyETag, a: replyATag } : undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Returns the parsed NIP-10 tags for an event */
|
|
66
|
+
export function getNip10References(event) {
|
|
67
|
+
return getOrComputeCachedValue(event, Nip10ThreadRefsSymbol, () => {
|
|
68
|
+
const tags = interpretThreadTags(event.tags);
|
|
69
|
+
let root;
|
|
70
|
+
if (tags.root) {
|
|
71
|
+
try {
|
|
72
|
+
root = {
|
|
73
|
+
e: tags.root.e && getEventPointerFromThreadTag(tags.root.e),
|
|
74
|
+
a: tags.root.a && getAddressPointerFromATag(tags.root.a),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) { }
|
|
78
|
+
}
|
|
79
|
+
let reply;
|
|
80
|
+
if (tags.reply) {
|
|
81
|
+
try {
|
|
82
|
+
reply = {
|
|
83
|
+
e: tags.reply.e && getEventPointerFromThreadTag(tags.reply.e),
|
|
84
|
+
a: tags.reply.a && getAddressPointerFromATag(tags.reply.a),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) { }
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
root,
|
|
91
|
+
reply,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { interpretThreadTags } from "./threading.js";
|
|
3
|
+
describe("threading helpers", () => {
|
|
4
|
+
describe("interpretThreadTags", () => {
|
|
5
|
+
it("should handle legacy tags", () => {
|
|
6
|
+
expect(interpretThreadTags([
|
|
7
|
+
["e", "root-id"],
|
|
8
|
+
["e", "reply-id"],
|
|
9
|
+
])).toEqual({ root: { a: undefined, e: ["e", "root-id"] }, reply: { a: undefined, e: ["e", "reply-id"] } });
|
|
10
|
+
});
|
|
11
|
+
it("should handle nip-10 tags", () => {
|
|
12
|
+
expect(interpretThreadTags([
|
|
13
|
+
["e", "root-id", "relay", "root"],
|
|
14
|
+
["e", "reply-id", "relay", "reply"],
|
|
15
|
+
])).toEqual({
|
|
16
|
+
root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
|
|
17
|
+
reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
it("should ignore mention nip-10 tags", () => {
|
|
21
|
+
expect(interpretThreadTags([
|
|
22
|
+
["e", "root-id", "relay", "root"],
|
|
23
|
+
["e", "mention-id", "relay", "mention"],
|
|
24
|
+
["e", "reply-id", "relay", "reply"],
|
|
25
|
+
])).toEqual({
|
|
26
|
+
root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
|
|
27
|
+
reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
it("should handle single nip-10 tags", () => {
|
|
31
|
+
expect(interpretThreadTags([["e", "root-id", "relay", "root"]])).toEqual({
|
|
32
|
+
root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
|
|
33
|
+
reply: { a: undefined, e: ["e", "root-id", "relay", "root"] },
|
|
34
|
+
});
|
|
35
|
+
expect(interpretThreadTags([["e", "reply-id", "relay", "reply"]])).toEqual({
|
|
36
|
+
root: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
|
|
37
|
+
reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const convertToUrl: (url: string | URL) => URL;
|
|
2
|
+
export declare const getURLFilename: (url: URL) => string | undefined;
|
|
3
|
+
export declare const IMAGE_EXT: string[];
|
|
4
|
+
export declare const VIDEO_EXT: string[];
|
|
5
|
+
export declare const STREAM_EXT: string[];
|
|
6
|
+
export declare const AUDIO_EXT: string[];
|
|
7
|
+
/** Checks if a url is a image URL */
|
|
8
|
+
export declare function isImageURL(url: string | URL): boolean;
|
|
9
|
+
/** Checks if a url is a video URL */
|
|
10
|
+
export declare function isVideoURL(url: string | URL): boolean;
|
|
11
|
+
/** Checks if a url is a stream URL */
|
|
12
|
+
export declare function isStreamURL(url: string | URL): boolean;
|
|
13
|
+
/** Checks if a url is a audio URL */
|
|
14
|
+
export declare function isAudioURL(url: string | URL): boolean;
|