applesauce-core 0.12.0 → 1.0.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.
- package/README.md +28 -10
- package/dist/event-store/__tests__/event-store.test.js +83 -1
- package/dist/event-store/database.d.ts +1 -0
- package/dist/event-store/database.js +7 -8
- package/dist/event-store/event-store.d.ts +4 -0
- package/dist/event-store/event-store.js +61 -22
- package/dist/event-store/interface.d.ts +11 -5
- package/dist/helpers/__tests__/bookmarks.test.d.ts +1 -0
- package/dist/helpers/__tests__/bookmarks.test.js +88 -0
- package/dist/helpers/__tests__/comment.test.js +14 -0
- package/dist/helpers/__tests__/contacts.test.d.ts +1 -0
- package/dist/helpers/__tests__/contacts.test.js +34 -0
- package/dist/helpers/__tests__/events.test.d.ts +1 -0
- package/dist/helpers/__tests__/events.test.js +32 -0
- package/dist/helpers/__tests__/mutes.test.d.ts +1 -0
- package/dist/helpers/__tests__/mutes.test.js +55 -0
- package/dist/helpers/bookmarks.d.ts +6 -1
- package/dist/helpers/bookmarks.js +52 -7
- package/dist/helpers/comment.d.ts +7 -3
- package/dist/helpers/comment.js +6 -1
- package/dist/helpers/contacts.d.ts +11 -0
- package/dist/helpers/contacts.js +34 -0
- package/dist/helpers/event.d.ts +8 -3
- package/dist/helpers/event.js +21 -13
- package/dist/helpers/lists.d.ts +40 -12
- package/dist/helpers/lists.js +62 -23
- package/dist/helpers/mutes.d.ts +8 -0
- package/dist/helpers/mutes.js +66 -5
- package/dist/helpers/nip-19.d.ts +14 -0
- package/dist/helpers/nip-19.js +29 -0
- package/dist/helpers/pointers.js +6 -6
- package/dist/observable/__tests__/listen-latest-updates.test.d.ts +1 -0
- package/dist/observable/__tests__/listen-latest-updates.test.js +55 -0
- package/dist/observable/defined.d.ts +3 -0
- package/dist/observable/defined.js +5 -0
- package/dist/observable/get-observable-value.d.ts +4 -1
- package/dist/observable/get-observable-value.js +4 -1
- package/dist/observable/index.d.ts +3 -1
- package/dist/observable/index.js +3 -1
- package/dist/observable/listen-latest-updates.d.ts +5 -0
- package/dist/observable/listen-latest-updates.js +12 -0
- package/dist/queries/blossom.js +1 -6
- package/dist/queries/bookmarks.d.ts +5 -5
- package/dist/queries/bookmarks.js +18 -17
- package/dist/queries/channels.js +41 -53
- package/dist/queries/comments.js +6 -9
- package/dist/queries/contacts.d.ts +6 -1
- package/dist/queries/contacts.js +21 -9
- package/dist/queries/index.d.ts +1 -0
- package/dist/queries/index.js +1 -0
- package/dist/queries/mailboxes.js +4 -7
- package/dist/queries/mutes.d.ts +6 -6
- package/dist/queries/mutes.js +20 -19
- package/dist/queries/pins.d.ts +1 -0
- package/dist/queries/pins.js +4 -6
- package/dist/queries/profile.js +1 -6
- package/dist/queries/reactions.js +11 -14
- package/dist/queries/relays.d.ts +27 -0
- package/dist/queries/relays.js +44 -0
- package/dist/queries/simple.js +5 -22
- package/dist/queries/thread.js +45 -51
- package/dist/queries/user-status.js +23 -29
- package/dist/queries/zaps.js +10 -13
- package/dist/query-store/query-store.d.ts +7 -6
- package/dist/query-store/query-store.js +13 -8
- package/package.json +3 -3
- package/dist/observable/share-latest-value.d.ts +0 -6
- package/dist/observable/share-latest-value.js +0 -24
package/dist/helpers/lists.js
CHANGED
|
@@ -1,55 +1,94 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAddressableKind, isReplaceableKind } from "nostr-tools/kinds";
|
|
2
2
|
import { getHiddenTags } from "./hidden-tags.js";
|
|
3
3
|
import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag, getProfilePointerFromPTag, } from "./pointers.js";
|
|
4
4
|
import { isATag, isETag, isPTag, processTags } from "./tags.js";
|
|
5
5
|
import { getReplaceableIdentifier } from "./event.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { mergeRelaySets } from "./relays.js";
|
|
7
|
+
export const FAVORITE_RELAYS_KIND = 10012;
|
|
8
|
+
/** Returns all the tags of a list or set */
|
|
9
|
+
export function getListTags(list, type) {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case "public":
|
|
12
|
+
return list.tags;
|
|
13
|
+
case "hidden":
|
|
14
|
+
return getHiddenTags(list) ?? [];
|
|
15
|
+
default:
|
|
16
|
+
case "all":
|
|
17
|
+
return [...(getHiddenTags(list) ?? []), ...list.tags];
|
|
18
|
+
}
|
|
9
19
|
}
|
|
10
20
|
/**
|
|
11
21
|
* Checks if an event pointer is anywhere in a list or set
|
|
12
22
|
* NOTE: Ignores the `relay` field in EventPointer
|
|
13
|
-
*
|
|
23
|
+
* @param list - The list or set to check
|
|
24
|
+
* @param pointer - The event pointer to check
|
|
25
|
+
* @param type - Which types of tags to check
|
|
14
26
|
*/
|
|
15
|
-
export function isEventPointerInList(list, pointer) {
|
|
27
|
+
export function isEventPointerInList(list, pointer, type) {
|
|
16
28
|
const id = typeof pointer === "string" ? pointer : pointer.id;
|
|
17
|
-
|
|
29
|
+
const tags = getListTags(list, type);
|
|
30
|
+
return tags.some((t) => t[0] === "e" && t[1] === id);
|
|
18
31
|
}
|
|
19
32
|
/**
|
|
20
33
|
* Checks if an address pointer is anywhere in a list or set
|
|
21
34
|
* NOTE: Ignores the `relay` field in AddressPointer
|
|
22
|
-
*
|
|
35
|
+
* @param list - The list or set to check
|
|
36
|
+
* @param pointer - The address pointer to check
|
|
37
|
+
* @param type - Which types of tags to check
|
|
23
38
|
*/
|
|
24
|
-
export function isAddressPointerInList(list, pointer) {
|
|
39
|
+
export function isAddressPointerInList(list, pointer, type) {
|
|
25
40
|
const cord = typeof pointer === "string" ? pointer : getCoordinateFromAddressPointer(pointer);
|
|
26
|
-
|
|
41
|
+
const tags = getListTags(list, type);
|
|
42
|
+
return tags.some((t) => t[0] === "a" && t[1] === cord);
|
|
27
43
|
}
|
|
28
44
|
/**
|
|
29
45
|
* Checks if an profile pointer is anywhere in a list or set
|
|
30
46
|
* NOTE: Ignores the `relay` field in ProfilePointer
|
|
31
|
-
*
|
|
47
|
+
* @param list - The list or set to check
|
|
48
|
+
* @param pointer - The profile pointer to check
|
|
49
|
+
* @param type - Which types of tags to check
|
|
32
50
|
*/
|
|
33
|
-
export function isProfilePointerInList(list, pointer) {
|
|
51
|
+
export function isProfilePointerInList(list, pointer, type) {
|
|
34
52
|
const pubkey = typeof pointer === "string" ? pointer : pointer.pubkey;
|
|
35
|
-
|
|
53
|
+
const tags = getListTags(list, type);
|
|
54
|
+
return tags.some((t) => t[0] === "p" && t[1] === pubkey);
|
|
36
55
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Returns all the EventPointer in a list or set
|
|
58
|
+
* @param list - The list or set to get the event pointers from
|
|
59
|
+
* @param type - Which types of tags to read
|
|
60
|
+
*/
|
|
61
|
+
export function getEventPointersFromList(list, type) {
|
|
62
|
+
return processTags(getListTags(list, type), (tag) => (isETag(tag) ? tag : undefined), getEventPointerFromETag);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns all the AddressPointer in a list or set
|
|
66
|
+
* @param list - The list or set to get the address pointers from
|
|
67
|
+
* @param type - Which types of tags to read
|
|
68
|
+
*/
|
|
69
|
+
export function getAddressPointersFromList(list, type) {
|
|
70
|
+
return processTags(getListTags(list, type), (t) => (isATag(t) ? t : undefined), getAddressPointerFromATag);
|
|
40
71
|
}
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Returns all the ProfilePointer in a list or set
|
|
74
|
+
* @param list - The list or set to get the profile pointers from
|
|
75
|
+
* @param type - Which types of tags to read
|
|
76
|
+
*/
|
|
77
|
+
export function getProfilePointersFromList(list, type) {
|
|
78
|
+
return processTags(getListTags(list, type), (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag);
|
|
44
79
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Returns a deduplicated array of all 'relay' tags in a list or set
|
|
82
|
+
* @param list - The list or set to get the relays from
|
|
83
|
+
* @param type - Which types of tags to read
|
|
84
|
+
*/
|
|
85
|
+
export function getRelaysFromList(list, type) {
|
|
86
|
+
return mergeRelaySets(processTags(getListTags(list, type), (t) => (t[0] === "relay" ? t[1] : undefined)));
|
|
48
87
|
}
|
|
49
88
|
/** Returns if an event is a valid list or set */
|
|
50
89
|
export function isValidList(event) {
|
|
51
90
|
try {
|
|
52
|
-
if (
|
|
91
|
+
if (isAddressableKind(event.kind)) {
|
|
53
92
|
// event is a set
|
|
54
93
|
// ensure the set has an identifier
|
|
55
94
|
getReplaceableIdentifier(event);
|
package/dist/helpers/mutes.d.ts
CHANGED
|
@@ -7,9 +7,17 @@ export type Mutes = {
|
|
|
7
7
|
hashtags: Set<string>;
|
|
8
8
|
words: Set<string>;
|
|
9
9
|
};
|
|
10
|
+
/** Merges any number of mute sets */
|
|
11
|
+
export declare function mergeMutes(...mutes: Mutes[]): Mutes;
|
|
10
12
|
/** Parses mute tags */
|
|
11
13
|
export declare function parseMutedTags(tags: string[][]): Mutes;
|
|
12
14
|
/** Returns muted things */
|
|
13
15
|
export declare function getMutedThings(mute: NostrEvent): Mutes;
|
|
16
|
+
/** Returns only the public muted things from a mute event */
|
|
17
|
+
export declare function getPublicMutedThings(mute: NostrEvent): Mutes;
|
|
14
18
|
/** Returns the hidden muted content if the event is unlocked */
|
|
15
19
|
export declare function getHiddenMutedThings(mute: NostrEvent): Mutes | undefined;
|
|
20
|
+
/** Creates a RegExp for matching muted words */
|
|
21
|
+
export declare function createMutedWordsRegExp(mutedWords: string[]): RegExp;
|
|
22
|
+
/** Returns true if the event matches the mutes */
|
|
23
|
+
export declare function matchMutes(mutes: Mutes, event: NostrEvent): boolean;
|
package/dist/helpers/mutes.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
1
2
|
import { isETag, isPTag, isTTag } from "./tags.js";
|
|
2
3
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
-
import { getHiddenTags } from "./hidden-tags.js";
|
|
4
|
+
import { getHiddenTags, isHiddenTagsLocked } from "./hidden-tags.js";
|
|
5
|
+
import { getIndexableTags, getNip10References } from "./index.js";
|
|
4
6
|
export const MutePublicSymbol = Symbol.for("mute-public");
|
|
5
7
|
export const MuteHiddenSymbol = Symbol.for("mute-hidden");
|
|
8
|
+
/** Merges any number of mute sets */
|
|
9
|
+
export function mergeMutes(...mutes) {
|
|
10
|
+
const mute = { pubkeys: new Set(), threads: new Set(), hashtags: new Set(), words: new Set() };
|
|
11
|
+
for (const m of mutes) {
|
|
12
|
+
for (const pubkey of m.pubkeys)
|
|
13
|
+
mute.pubkeys.add(pubkey);
|
|
14
|
+
for (const thread of m.threads)
|
|
15
|
+
mute.threads.add(thread);
|
|
16
|
+
for (const hashtag of m.hashtags)
|
|
17
|
+
mute.hashtags.add(hashtag);
|
|
18
|
+
for (const word of m.words)
|
|
19
|
+
mute.words.add(word);
|
|
20
|
+
}
|
|
21
|
+
return mute;
|
|
22
|
+
}
|
|
6
23
|
/** Parses mute tags */
|
|
7
24
|
export function parseMutedTags(tags) {
|
|
8
25
|
const pubkeys = new Set(tags.filter(isPTag).map((t) => t[1]));
|
|
@@ -13,12 +30,56 @@ export function parseMutedTags(tags) {
|
|
|
13
30
|
}
|
|
14
31
|
/** Returns muted things */
|
|
15
32
|
export function getMutedThings(mute) {
|
|
33
|
+
const hidden = getHiddenMutedThings(mute);
|
|
34
|
+
const mutes = getPublicMutedThings(mute);
|
|
35
|
+
if (hidden)
|
|
36
|
+
return mergeMutes(hidden, mutes);
|
|
37
|
+
return mutes;
|
|
38
|
+
}
|
|
39
|
+
/** Returns only the public muted things from a mute event */
|
|
40
|
+
export function getPublicMutedThings(mute) {
|
|
16
41
|
return getOrComputeCachedValue(mute, MutePublicSymbol, () => parseMutedTags(mute.tags));
|
|
17
42
|
}
|
|
18
43
|
/** Returns the hidden muted content if the event is unlocked */
|
|
19
44
|
export function getHiddenMutedThings(mute) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
if (isHiddenTagsLocked(mute))
|
|
46
|
+
return undefined;
|
|
47
|
+
return getOrComputeCachedValue(mute, MuteHiddenSymbol, () => parseMutedTags(getHiddenTags(mute)));
|
|
48
|
+
}
|
|
49
|
+
/** Creates a RegExp for matching muted words */
|
|
50
|
+
export function createMutedWordsRegExp(mutedWords) {
|
|
51
|
+
// Escape special characters and join with |
|
|
52
|
+
const escapedWords = mutedWords.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
53
|
+
// Create the RegExp with word boundaries and case insensitive flag
|
|
54
|
+
return new RegExp(`\\b(${escapedWords.join("|")})\\b`, "gi");
|
|
55
|
+
}
|
|
56
|
+
/** Returns true if the event matches the mutes */
|
|
57
|
+
export function matchMutes(mutes, event) {
|
|
58
|
+
// Filter on muted pubkeys
|
|
59
|
+
if (mutes.pubkeys.size > 0) {
|
|
60
|
+
if (mutes.pubkeys.has(event.pubkey))
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Filter on muted hashtags`
|
|
64
|
+
if (mutes.hashtags.size > 0) {
|
|
65
|
+
const tags = getIndexableTags(event);
|
|
66
|
+
for (let tag of mutes.hashtags) {
|
|
67
|
+
if (tags.has("t:" + tag))
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Filter on muted threads
|
|
72
|
+
if (mutes.threads.size > 0 && event.kind === kinds.ShortTextNote) {
|
|
73
|
+
const refs = getNip10References(event);
|
|
74
|
+
if (refs.root?.e && mutes.threads.has(refs.root.e.id))
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
// Filter on muted words
|
|
78
|
+
if (mutes.words.size > 0) {
|
|
79
|
+
const regExp = createMutedWordsRegExp(Array.from(mutes.words));
|
|
80
|
+
if (regExp.test(event.content))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
// Event does not match any mutes
|
|
84
|
+
return false;
|
|
24
85
|
}
|
package/dist/helpers/nip-19.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
1
2
|
/** Gets the hex pubkey from any nip-19 encoded string */
|
|
2
3
|
export declare function normalizeToPubkey(str: string): string;
|
|
3
4
|
/** Converts hex to nsec strings into Uint8 secret keys */
|
|
4
5
|
export declare function normalizeToSecretKey(str: string): Uint8Array;
|
|
6
|
+
/**
|
|
7
|
+
* Merges two event points and keeps all relays
|
|
8
|
+
* @throws if the ids are different
|
|
9
|
+
*/
|
|
10
|
+
export declare function mergeEventPointers(a: EventPointer, b: EventPointer): EventPointer;
|
|
11
|
+
/** Merges two address pointers and keeps all relays
|
|
12
|
+
* @throws if the kinds, pubkeys, or identifiers are different
|
|
13
|
+
*/
|
|
14
|
+
export declare function mergeAddressPointers(a: AddressPointer, b: AddressPointer): AddressPointer;
|
|
15
|
+
/** Merges two profile pointers and keeps all relays
|
|
16
|
+
* @throws if the pubkeys are different
|
|
17
|
+
*/
|
|
18
|
+
export declare function mergeProfilePointers(a: ProfilePointer, b: ProfilePointer): ProfilePointer;
|
package/dist/helpers/nip-19.js
CHANGED
|
@@ -2,6 +2,7 @@ import { nip19 } from "nostr-tools";
|
|
|
2
2
|
import { hexToBytes } from "@noble/hashes/utils";
|
|
3
3
|
import { isHexKey } from "./string.js";
|
|
4
4
|
import { getPubkeyFromDecodeResult } from "./pointers.js";
|
|
5
|
+
import { mergeRelaySets } from "./relays.js";
|
|
5
6
|
/** Gets the hex pubkey from any nip-19 encoded string */
|
|
6
7
|
export function normalizeToPubkey(str) {
|
|
7
8
|
if (isHexKey(str))
|
|
@@ -25,3 +26,31 @@ export function normalizeToSecretKey(str) {
|
|
|
25
26
|
return decode.data;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Merges two event points and keeps all relays
|
|
31
|
+
* @throws if the ids are different
|
|
32
|
+
*/
|
|
33
|
+
export function mergeEventPointers(a, b) {
|
|
34
|
+
if (a.id !== b.id)
|
|
35
|
+
throw new Error("Cant merge event pointers with different ids");
|
|
36
|
+
const relays = mergeRelaySets(a.relays, b.relays);
|
|
37
|
+
return { id: a.id, kind: a.kind ?? b.kind, author: a.author ?? b.author, relays };
|
|
38
|
+
}
|
|
39
|
+
/** Merges two address pointers and keeps all relays
|
|
40
|
+
* @throws if the kinds, pubkeys, or identifiers are different
|
|
41
|
+
*/
|
|
42
|
+
export function mergeAddressPointers(a, b) {
|
|
43
|
+
if (a.kind !== b.kind || a.pubkey !== b.pubkey || a.identifier !== b.identifier)
|
|
44
|
+
throw new Error("Cant merge address pointers with different kinds, pubkeys, or identifiers");
|
|
45
|
+
const relays = mergeRelaySets(a.relays, b.relays);
|
|
46
|
+
return { ...a, relays };
|
|
47
|
+
}
|
|
48
|
+
/** Merges two profile pointers and keeps all relays
|
|
49
|
+
* @throws if the pubkeys are different
|
|
50
|
+
*/
|
|
51
|
+
export function mergeProfilePointers(a, b) {
|
|
52
|
+
if (a.pubkey !== b.pubkey)
|
|
53
|
+
throw new Error("Cant merge profile pointers with different pubkeys");
|
|
54
|
+
const relays = mergeRelaySets(a.relays, b.relays);
|
|
55
|
+
return { ...a, relays };
|
|
56
|
+
}
|
package/dist/helpers/pointers.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
|
|
2
2
|
import { getPublicKey, kinds } from "nostr-tools";
|
|
3
3
|
import { getReplaceableIdentifier } from "./event.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isAddressableKind } from "nostr-tools/kinds";
|
|
5
5
|
import { isSafeRelayURL } from "./relays.js";
|
|
6
6
|
export function parseCoordinate(a, requireD = false, silent = true) {
|
|
7
7
|
const parts = a.split(":");
|
|
8
|
-
const kind = parts[0]
|
|
8
|
+
const kind = parts[0] ? parseInt(parts[0]) : undefined;
|
|
9
9
|
const pubkey = parts[1];
|
|
10
10
|
const d = parts[2];
|
|
11
|
-
if (
|
|
11
|
+
if (kind === undefined) {
|
|
12
12
|
if (silent)
|
|
13
13
|
return null;
|
|
14
14
|
else
|
|
15
15
|
throw new Error("Missing kind");
|
|
16
16
|
}
|
|
17
|
-
if (
|
|
17
|
+
if (pubkey === undefined || pubkey === "") {
|
|
18
18
|
if (silent)
|
|
19
19
|
return null;
|
|
20
20
|
else
|
|
@@ -155,7 +155,7 @@ export function getCoordinateFromAddressPointer(pointer) {
|
|
|
155
155
|
* @throws
|
|
156
156
|
*/
|
|
157
157
|
export function getAddressPointerForEvent(event, relays) {
|
|
158
|
-
if (!
|
|
158
|
+
if (!isAddressableKind(event.kind))
|
|
159
159
|
throw new Error("Cant get AddressPointer for non-replaceable event");
|
|
160
160
|
const d = getReplaceableIdentifier(event);
|
|
161
161
|
return {
|
|
@@ -179,7 +179,7 @@ export function getEventPointerForEvent(event, relays) {
|
|
|
179
179
|
}
|
|
180
180
|
/** Returns a pointer for a given event */
|
|
181
181
|
export function getPointerForEvent(event, relays) {
|
|
182
|
-
if (kinds.
|
|
182
|
+
if (kinds.isAddressableKind(event.kind)) {
|
|
183
183
|
const d = getReplaceableIdentifier(event);
|
|
184
184
|
return {
|
|
185
185
|
type: "naddr",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
3
|
+
import { of } from "rxjs";
|
|
4
|
+
import { EventStore } from "../../event-store/event-store.js";
|
|
5
|
+
import { listenLatestUpdates } from "../listen-latest-updates.js";
|
|
6
|
+
import { FakeUser } from "../../__tests__/fixtures.js";
|
|
7
|
+
let eventStore;
|
|
8
|
+
let user;
|
|
9
|
+
let event;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
eventStore = new EventStore();
|
|
12
|
+
user = new FakeUser();
|
|
13
|
+
event = user.note("original content");
|
|
14
|
+
});
|
|
15
|
+
describe("listenLatestUpdates", () => {
|
|
16
|
+
it("should emit the initial event", () => {
|
|
17
|
+
const source = of(event);
|
|
18
|
+
const spy = subscribeSpyTo(source.pipe(listenLatestUpdates(eventStore)));
|
|
19
|
+
expect(spy.getValues()).toEqual([event]);
|
|
20
|
+
});
|
|
21
|
+
it("should emit the event again when it's updated in the event store", () => {
|
|
22
|
+
// Add the event to the store first
|
|
23
|
+
eventStore.add(event);
|
|
24
|
+
// Create a source that emits the event
|
|
25
|
+
const source = of(event);
|
|
26
|
+
const spy = subscribeSpyTo(source.pipe(listenLatestUpdates(eventStore)));
|
|
27
|
+
// Create an updated version of the event
|
|
28
|
+
Reflect.set(event, Symbol.for("new-prop"), "testing");
|
|
29
|
+
// Update the event in the store
|
|
30
|
+
eventStore.update(event);
|
|
31
|
+
// Should have received both the original and updated event
|
|
32
|
+
expect(spy.getValues()).toEqual([event, event]);
|
|
33
|
+
});
|
|
34
|
+
it("should not emit updates for other events", () => {
|
|
35
|
+
// Add the event to the store
|
|
36
|
+
eventStore.add(event);
|
|
37
|
+
// Create a source that emits the event
|
|
38
|
+
const source = of(event);
|
|
39
|
+
const spy = subscribeSpyTo(source.pipe(listenLatestUpdates(eventStore)));
|
|
40
|
+
// Create a different event
|
|
41
|
+
const otherEvent = user.note("other content");
|
|
42
|
+
// Add the other event to the store
|
|
43
|
+
eventStore.add(otherEvent);
|
|
44
|
+
// Should only have received the original event
|
|
45
|
+
expect(spy.getValues()).toEqual([event]);
|
|
46
|
+
});
|
|
47
|
+
it("should handle undefined initial event", () => {
|
|
48
|
+
const source = of(undefined);
|
|
49
|
+
const spy = subscribeSpyTo(source.pipe(listenLatestUpdates(eventStore)));
|
|
50
|
+
expect(spy.getValues()).toEqual([undefined]);
|
|
51
|
+
// Adding events to the store should not trigger emissions
|
|
52
|
+
eventStore.add(event);
|
|
53
|
+
expect(spy.getValues()).toEqual([undefined]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Subscribes and returns the observables current or next value
|
|
4
|
+
* @deprecated use `firstValueFrom` instead
|
|
5
|
+
*/
|
|
3
6
|
export declare function getObservableValue<T>(observable: Observable<T>): T | Promise<T>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Subscribes and returns the observables current or next value
|
|
4
|
+
* @deprecated use `firstValueFrom` instead
|
|
5
|
+
*/
|
|
3
6
|
export function getObservableValue(observable) {
|
|
4
7
|
if (observable instanceof BehaviorSubject)
|
|
5
8
|
return observable.value;
|
package/dist/observable/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { MonoTypeOperatorFunction } from "rxjs";
|
|
3
|
+
import { IStreamEventStore } from "../event-store/interface.js";
|
|
4
|
+
/** Lists for any updates to the latest event and remits it */
|
|
5
|
+
export declare function listenLatestUpdates(eventStore: IStreamEventStore): MonoTypeOperatorFunction<NostrEvent | undefined>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { filter, merge, tap } from "rxjs";
|
|
2
|
+
/** Lists for any updates to the latest event and remits it */
|
|
3
|
+
export function listenLatestUpdates(eventStore) {
|
|
4
|
+
return (source) => {
|
|
5
|
+
let latest;
|
|
6
|
+
return merge(
|
|
7
|
+
// Get the latest event
|
|
8
|
+
source.pipe(tap((value) => (latest = value))),
|
|
9
|
+
// listen for updates
|
|
10
|
+
eventStore.updates.pipe(filter((e) => e.id === latest?.id)));
|
|
11
|
+
};
|
|
12
|
+
}
|
package/dist/queries/blossom.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { map } from "rxjs/operators";
|
|
2
2
|
import { BLOSSOM_SERVER_LIST_KIND, getBlossomServersFromList } from "../helpers/blossom.js";
|
|
3
3
|
export function UserBlossomServersQuery(pubkey) {
|
|
4
|
-
return
|
|
5
|
-
key: pubkey,
|
|
6
|
-
run: (store) => store
|
|
7
|
-
.replaceable(BLOSSOM_SERVER_LIST_KIND, pubkey)
|
|
8
|
-
.pipe(map((event) => event && getBlossomServersFromList(event))),
|
|
9
|
-
};
|
|
4
|
+
return (store) => store.replaceable(BLOSSOM_SERVER_LIST_KIND, pubkey).pipe(map((event) => event && getBlossomServersFromList(event)));
|
|
10
5
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Bookmarks } from "../helpers/bookmarks.js";
|
|
2
2
|
import { Query } from "../query-store/index.js";
|
|
3
|
+
/** A query that returns all the bookmarks of a user */
|
|
3
4
|
export declare function UserBookmarkQuery(pubkey: string): Query<Bookmarks | undefined>;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} | undefined>;
|
|
5
|
+
/** A query that returns all the public bookmarks of a user */
|
|
6
|
+
export declare function UserPublicBookmarkQuery(pubkey: string): Query<Bookmarks | undefined>;
|
|
7
|
+
/** A query that returns all the hidden bookmarks of a user */
|
|
8
|
+
export declare function UserHiddenBookmarkQuery(pubkey: string): Query<Bookmarks | null | undefined>;
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
2
|
import { map } from "rxjs/operators";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { getBookmarks, getHiddenBookmarks, getPublicBookmarks } from "../helpers/bookmarks.js";
|
|
4
|
+
import { listenLatestUpdates } from "../observable/index.js";
|
|
5
|
+
/** A query that returns all the bookmarks of a user */
|
|
5
6
|
export function UserBookmarkQuery(pubkey) {
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(
|
|
8
|
+
// listen for event updates (hidden tags unlocked)
|
|
9
|
+
listenLatestUpdates(events),
|
|
10
|
+
// Get all bookmarks
|
|
11
|
+
map((event) => event && getBookmarks(event)));
|
|
10
12
|
}
|
|
13
|
+
/** A query that returns all the public bookmarks of a user */
|
|
14
|
+
export function UserPublicBookmarkQuery(pubkey) {
|
|
15
|
+
return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(map((event) => event && getPublicBookmarks(event)));
|
|
16
|
+
}
|
|
17
|
+
/** A query that returns all the hidden bookmarks of a user */
|
|
11
18
|
export function UserHiddenBookmarkQuery(pubkey) {
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const bookmarks = getHiddenBookmarks(event);
|
|
18
|
-
if (isHiddenTagsLocked(event) || !bookmarks)
|
|
19
|
-
return { locked: true };
|
|
20
|
-
return { locked: false, ...bookmarks };
|
|
21
|
-
})),
|
|
22
|
-
};
|
|
19
|
+
return (events) => events.replaceable(kinds.Mutelist, pubkey).pipe(
|
|
20
|
+
// listen for event updates (hidden tags unlocked)
|
|
21
|
+
listenLatestUpdates(events),
|
|
22
|
+
// Get hidden bookmarks
|
|
23
|
+
map((event) => event && (getHiddenBookmarks(event) ?? null)));
|
|
23
24
|
}
|
package/dist/queries/channels.js
CHANGED
|
@@ -4,70 +4,58 @@ import { map } from "rxjs";
|
|
|
4
4
|
import { getChannelMetadataContent } from "../helpers/channels.js";
|
|
5
5
|
/** A query that returns a map of hidden messages Map<id, reason> */
|
|
6
6
|
export function ChannelHiddenQuery(channel, authors = []) {
|
|
7
|
-
return {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return hidden;
|
|
20
|
-
}));
|
|
21
|
-
},
|
|
7
|
+
return (events) => {
|
|
8
|
+
const hidden = new Map();
|
|
9
|
+
return events
|
|
10
|
+
.filters([{ kinds: [kinds.ChannelHideMessage], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
|
|
11
|
+
.pipe(map((event) => {
|
|
12
|
+
const reason = safeParse(event.content)?.reason;
|
|
13
|
+
for (const tag of event.tags) {
|
|
14
|
+
if (tag[0] === "e" && tag[1])
|
|
15
|
+
hidden.set(tag[1], reason ?? "");
|
|
16
|
+
}
|
|
17
|
+
return hidden;
|
|
18
|
+
}));
|
|
22
19
|
};
|
|
23
20
|
}
|
|
24
21
|
/** A query that returns all messages in a channel */
|
|
25
22
|
export function ChannelMessagesQuery(channel) {
|
|
26
|
-
return {
|
|
27
|
-
key: channel.id,
|
|
28
|
-
run: (events) => events.timeline([{ kinds: [kinds.ChannelMessage], "#e": [channel.id] }]),
|
|
29
|
-
};
|
|
23
|
+
return (events) => events.timeline([{ kinds: [kinds.ChannelMessage], "#e": [channel.id] }]);
|
|
30
24
|
}
|
|
31
25
|
/** A query that returns the latest parsed metadata */
|
|
32
26
|
export function ChannelMetadataQuery(channel) {
|
|
33
|
-
return {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (event.pubkey === latest.pubkey && event.created_at > latest.created_at) {
|
|
44
|
-
latest = event;
|
|
45
|
-
}
|
|
46
|
-
return getChannelMetadataContent(latest);
|
|
27
|
+
return (events) => {
|
|
28
|
+
const filters = [
|
|
29
|
+
{ ids: [channel.id] },
|
|
30
|
+
{ kinds: [kinds.ChannelMetadata], "#e": [channel.id], authors: [channel.pubkey] },
|
|
31
|
+
];
|
|
32
|
+
let latest = channel;
|
|
33
|
+
return events.filters(filters).pipe(map((event) => {
|
|
34
|
+
try {
|
|
35
|
+
if (event.pubkey === latest.pubkey && event.created_at > latest.created_at) {
|
|
36
|
+
latest = event;
|
|
47
37
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
return getChannelMetadataContent(latest);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
53
44
|
};
|
|
54
45
|
}
|
|
55
46
|
/** A query that returns a map of muted users Map<pubkey, reason> */
|
|
56
47
|
export function ChannelMutedQuery(channel, authors = []) {
|
|
57
|
-
return {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return muted;
|
|
70
|
-
}));
|
|
71
|
-
},
|
|
48
|
+
return (events) => {
|
|
49
|
+
const muted = new Map();
|
|
50
|
+
return events
|
|
51
|
+
.filters([{ kinds: [kinds.ChannelMuteUser], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
|
|
52
|
+
.pipe(map((event) => {
|
|
53
|
+
const reason = safeParse(event.content)?.reason;
|
|
54
|
+
for (const tag of event.tags) {
|
|
55
|
+
if (tag[0] === "p" && tag[1])
|
|
56
|
+
muted.set(tag[1], reason ?? "");
|
|
57
|
+
}
|
|
58
|
+
return muted;
|
|
59
|
+
}));
|
|
72
60
|
};
|
|
73
61
|
}
|