applesauce-core 0.1.0 → 0.3.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 +39 -0
- package/dist/event-store/database.d.ts +57 -13
- package/dist/event-store/database.js +52 -15
- package/dist/event-store/event-store.d.ts +4 -1
- package/dist/event-store/event-store.js +122 -31
- package/dist/event-store/index.d.ts +2 -1
- package/dist/event-store/index.js +2 -1
- package/dist/helpers/channel.d.ts +15 -0
- package/dist/helpers/channel.js +27 -0
- package/dist/helpers/event.d.ts +12 -0
- package/dist/helpers/event.js +13 -8
- package/dist/helpers/filter.d.ts +5 -0
- package/dist/helpers/filter.js +5 -2
- package/dist/helpers/index.d.ts +1 -1
- package/dist/helpers/index.js +1 -1
- package/dist/helpers/json.d.ts +1 -0
- package/dist/helpers/json.js +8 -0
- package/dist/helpers/mailboxes.d.ts +14 -0
- package/dist/helpers/mailboxes.js +14 -7
- package/dist/helpers/mailboxes.test.d.ts +1 -0
- package/dist/helpers/mailboxes.test.js +80 -0
- package/dist/helpers/mute.d.ts +21 -0
- package/dist/helpers/mute.js +52 -0
- package/dist/helpers/profile.d.ts +6 -0
- package/dist/helpers/profile.js +4 -4
- package/dist/helpers/relays.d.ts +6 -0
- package/dist/helpers/relays.js +7 -6
- package/dist/promise/index.d.ts +1 -1
- package/dist/promise/index.js +1 -1
- package/dist/query-store/index.d.ts +35 -17
- package/dist/query-store/index.js +40 -58
- package/dist/query-store/queries/channel.d.ts +11 -0
- package/dist/query-store/queries/channel.js +72 -0
- package/dist/query-store/queries/index.d.ts +6 -0
- package/dist/query-store/queries/index.js +6 -0
- package/dist/query-store/queries/mailboxes.d.ts +5 -0
- package/dist/query-store/queries/mailboxes.js +11 -0
- package/dist/query-store/queries/mute.d.ts +7 -0
- package/dist/query-store/queries/mute.js +16 -0
- package/dist/query-store/queries/profile.d.ts +3 -0
- package/dist/query-store/queries/profile.js +10 -0
- package/dist/query-store/queries/reactions.d.ts +4 -0
- package/dist/query-store/queries/reactions.js +19 -0
- package/dist/query-store/queries/simple.d.ts +5 -0
- package/dist/query-store/queries/simple.js +20 -0
- package/dist/utils/lru.d.ts +32 -0
- package/dist/utils/lru.js +148 -0
- package/package.json +19 -3
- package/dist/helpers/symbols.d.ts +0 -17
- package/dist/helpers/symbols.js +0 -10
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const MailboxesInboxesSymbol: unique symbol;
|
|
3
|
+
export declare const MailboxesOutboxesSymbol: unique symbol;
|
|
4
|
+
declare module "nostr-tools" {
|
|
5
|
+
interface Event {
|
|
6
|
+
[MailboxesInboxesSymbol]?: Set<string>;
|
|
7
|
+
[MailboxesOutboxesSymbol]?: Set<string>;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
|
|
12
|
+
*/
|
|
2
13
|
export declare function getInboxes(event: NostrEvent): Set<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
|
|
16
|
+
*/
|
|
3
17
|
export declare function getOutboxes(event: NostrEvent): Set<string>;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { safeRelayUrl } from "./relays.js";
|
|
2
|
-
|
|
2
|
+
export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
|
|
3
|
+
export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
|
|
4
|
+
/**
|
|
5
|
+
* Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
|
|
6
|
+
*/
|
|
3
7
|
export function getInboxes(event) {
|
|
4
|
-
if (!event[
|
|
8
|
+
if (!event[MailboxesInboxesSymbol]) {
|
|
5
9
|
const inboxes = new Set();
|
|
6
10
|
for (const tag of event.tags) {
|
|
7
11
|
if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
|
|
@@ -10,12 +14,15 @@ export function getInboxes(event) {
|
|
|
10
14
|
inboxes.add(url);
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
|
-
event[
|
|
17
|
+
event[MailboxesInboxesSymbol] = inboxes;
|
|
14
18
|
}
|
|
15
|
-
return event[
|
|
19
|
+
return event[MailboxesInboxesSymbol];
|
|
16
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
|
|
23
|
+
*/
|
|
17
24
|
export function getOutboxes(event) {
|
|
18
|
-
if (!event[
|
|
25
|
+
if (!event[MailboxesOutboxesSymbol]) {
|
|
19
26
|
const outboxes = new Set();
|
|
20
27
|
for (const tag of event.tags) {
|
|
21
28
|
if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
|
|
@@ -24,7 +31,7 @@ export function getOutboxes(event) {
|
|
|
24
31
|
outboxes.add(url);
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
|
-
event[
|
|
34
|
+
event[MailboxesOutboxesSymbol] = outboxes;
|
|
28
35
|
}
|
|
29
|
-
return event[
|
|
36
|
+
return event[MailboxesOutboxesSymbol];
|
|
30
37
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getInboxes, getOutboxes } from "./mailboxes.js";
|
|
2
|
+
const emptyEvent = {
|
|
3
|
+
kind: 10002,
|
|
4
|
+
content: "",
|
|
5
|
+
tags: [],
|
|
6
|
+
created_at: 0,
|
|
7
|
+
sig: "",
|
|
8
|
+
id: "",
|
|
9
|
+
pubkey: "",
|
|
10
|
+
};
|
|
11
|
+
describe("Mailboxes", () => {
|
|
12
|
+
describe("getInboxes", () => {
|
|
13
|
+
test("should transform urls", () => {
|
|
14
|
+
expect(Array.from(getInboxes({
|
|
15
|
+
...emptyEvent,
|
|
16
|
+
tags: [["r", "wss://inbox.com"]],
|
|
17
|
+
}))).toIncludeAllMembers(["wss://inbox.com/"]);
|
|
18
|
+
});
|
|
19
|
+
test("should remove bad urls", () => {
|
|
20
|
+
expect(Array.from(getInboxes({
|
|
21
|
+
...emptyEvent,
|
|
22
|
+
tags: [["r", "bad://inbox.com"]],
|
|
23
|
+
}))).toBeArrayOfSize(0);
|
|
24
|
+
expect(Array.from(getInboxes({
|
|
25
|
+
...emptyEvent,
|
|
26
|
+
tags: [["r", "something that is not a url"]],
|
|
27
|
+
}))).toBeArrayOfSize(0);
|
|
28
|
+
expect(Array.from(getInboxes({
|
|
29
|
+
...emptyEvent,
|
|
30
|
+
tags: [["r", "wss://inbox.com,wss://inbox.org"]],
|
|
31
|
+
}))).toBeArrayOfSize(0);
|
|
32
|
+
});
|
|
33
|
+
test("without marker", () => {
|
|
34
|
+
expect(Array.from(getInboxes({
|
|
35
|
+
...emptyEvent,
|
|
36
|
+
tags: [["r", "wss://inbox.com/"]],
|
|
37
|
+
}))).toIncludeAllMembers(["wss://inbox.com/"]);
|
|
38
|
+
});
|
|
39
|
+
test("with marker", () => {
|
|
40
|
+
expect(Array.from(getInboxes({
|
|
41
|
+
...emptyEvent,
|
|
42
|
+
tags: [["r", "wss://inbox.com/", "read"]],
|
|
43
|
+
}))).toIncludeAllMembers(["wss://inbox.com/"]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("getOutboxes", () => {
|
|
47
|
+
test("should transform urls", () => {
|
|
48
|
+
expect(Array.from(getOutboxes({
|
|
49
|
+
...emptyEvent,
|
|
50
|
+
tags: [["r", "wss://outbox.com"]],
|
|
51
|
+
}))).toIncludeAllMembers(["wss://outbox.com/"]);
|
|
52
|
+
});
|
|
53
|
+
test("should remove bad urls", () => {
|
|
54
|
+
expect(Array.from(getOutboxes({
|
|
55
|
+
...emptyEvent,
|
|
56
|
+
tags: [["r", "bad://inbox.com"]],
|
|
57
|
+
}))).toBeArrayOfSize(0);
|
|
58
|
+
expect(Array.from(getOutboxes({
|
|
59
|
+
...emptyEvent,
|
|
60
|
+
tags: [["r", "something that is not a url"]],
|
|
61
|
+
}))).toBeArrayOfSize(0);
|
|
62
|
+
expect(Array.from(getOutboxes({
|
|
63
|
+
...emptyEvent,
|
|
64
|
+
tags: [["r", "wss://outbox.com,wss://inbox.org"]],
|
|
65
|
+
}))).toBeArrayOfSize(0);
|
|
66
|
+
});
|
|
67
|
+
test("without marker", () => {
|
|
68
|
+
expect(Array.from(getOutboxes({
|
|
69
|
+
...emptyEvent,
|
|
70
|
+
tags: [["r", "wss://outbox.com/"]],
|
|
71
|
+
}))).toIncludeAllMembers(["wss://outbox.com/"]);
|
|
72
|
+
});
|
|
73
|
+
test("with marker", () => {
|
|
74
|
+
expect(Array.from(getOutboxes({
|
|
75
|
+
...emptyEvent,
|
|
76
|
+
tags: [["r", "wss://outbox.com/", "write"]],
|
|
77
|
+
}))).toIncludeAllMembers(["wss://outbox.com/"]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const MutePubkeysSymbol: unique symbol;
|
|
3
|
+
export declare const MuteThreadsSymbol: unique symbol;
|
|
4
|
+
export declare const MuteHashtagsSymbol: unique symbol;
|
|
5
|
+
export declare const MuteWordsSymbol: unique symbol;
|
|
6
|
+
declare module "nostr-tools" {
|
|
7
|
+
interface Event {
|
|
8
|
+
[MutePubkeysSymbol]?: Set<string>;
|
|
9
|
+
[MuteThreadsSymbol]?: Set<string>;
|
|
10
|
+
[MuteHashtagsSymbol]?: Set<string>;
|
|
11
|
+
[MuteWordsSymbol]?: Set<string>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Returns a set of muted pubkeys */
|
|
15
|
+
export declare function getMutedPubkeys(mute: NostrEvent): Set<string>;
|
|
16
|
+
/** Returns a set of muted threads */
|
|
17
|
+
export declare function getMutedThreads(mute: NostrEvent): Set<string>;
|
|
18
|
+
/** Returns a set of muted words ( lowercase ) */
|
|
19
|
+
export declare function getMutedWords(mute: NostrEvent): Set<string>;
|
|
20
|
+
/** Returns a set of muted hashtags ( lowercase ) */
|
|
21
|
+
export declare function getMutedHashtags(mute: NostrEvent): Set<string>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const MutePubkeysSymbol = Symbol.for("mute-pubkeys");
|
|
2
|
+
export const MuteThreadsSymbol = Symbol.for("mute-threads");
|
|
3
|
+
export const MuteHashtagsSymbol = Symbol.for("mute-hashtags");
|
|
4
|
+
export const MuteWordsSymbol = Symbol.for("mute-words");
|
|
5
|
+
/** Returns a set of muted pubkeys */
|
|
6
|
+
export function getMutedPubkeys(mute) {
|
|
7
|
+
let pubkeys = mute[MutePubkeysSymbol];
|
|
8
|
+
if (!pubkeys) {
|
|
9
|
+
pubkeys = mute[MutePubkeysSymbol] = new Set();
|
|
10
|
+
for (const tag of mute.tags) {
|
|
11
|
+
if (tag[0] === "p" && tag[1])
|
|
12
|
+
pubkeys.add(tag[1]);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return pubkeys;
|
|
16
|
+
}
|
|
17
|
+
/** Returns a set of muted threads */
|
|
18
|
+
export function getMutedThreads(mute) {
|
|
19
|
+
let threads = mute[MuteThreadsSymbol];
|
|
20
|
+
if (!threads) {
|
|
21
|
+
threads = mute[MuteThreadsSymbol] = new Set();
|
|
22
|
+
for (const tag of mute.tags) {
|
|
23
|
+
if (tag[0] === "e" && tag[1])
|
|
24
|
+
threads.add(tag[1]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return threads;
|
|
28
|
+
}
|
|
29
|
+
/** Returns a set of muted words ( lowercase ) */
|
|
30
|
+
export function getMutedWords(mute) {
|
|
31
|
+
let words = mute[MuteWordsSymbol];
|
|
32
|
+
if (!words) {
|
|
33
|
+
words = mute[MuteWordsSymbol] = new Set();
|
|
34
|
+
for (const tag of mute.tags) {
|
|
35
|
+
if (tag[0] === "word" && tag[1])
|
|
36
|
+
words.add(tag[1].toLocaleLowerCase());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return words;
|
|
40
|
+
}
|
|
41
|
+
/** Returns a set of muted hashtags ( lowercase ) */
|
|
42
|
+
export function getMutedHashtags(mute) {
|
|
43
|
+
let hashtags = mute[MuteHashtagsSymbol];
|
|
44
|
+
if (!hashtags) {
|
|
45
|
+
hashtags = mute[MuteHashtagsSymbol] = new Set();
|
|
46
|
+
for (const tag of mute.tags) {
|
|
47
|
+
if (tag[0] === "t" && tag[1])
|
|
48
|
+
hashtags.add(tag[1].toLocaleLowerCase());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return hashtags;
|
|
52
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const ProfileContentSymbol: unique symbol;
|
|
3
|
+
declare module "nostr-tools" {
|
|
4
|
+
interface Event {
|
|
5
|
+
[ProfileContentSymbol]?: ProfileContent | Error;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
2
8
|
export type ProfileContent = {
|
|
3
9
|
name?: string;
|
|
4
10
|
display_name?: string;
|
package/dist/helpers/profile.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
export const ProfileContentSymbol = Symbol.for("profile-content");
|
|
2
2
|
export function getProfileContent(event, quite = false) {
|
|
3
|
-
let cached = event[
|
|
3
|
+
let cached = event[ProfileContentSymbol];
|
|
4
4
|
if (!cached) {
|
|
5
5
|
try {
|
|
6
6
|
const profile = JSON.parse(event.content);
|
|
7
7
|
// ensure nip05 is a string
|
|
8
8
|
if (profile.nip05 && typeof profile.nip05 !== "string")
|
|
9
9
|
profile.nip05 = String(profile.nip05);
|
|
10
|
-
cached = event[
|
|
10
|
+
cached = event[ProfileContentSymbol] = profile;
|
|
11
11
|
}
|
|
12
12
|
catch (e) {
|
|
13
13
|
if (e instanceof Error)
|
|
14
|
-
cached = event[
|
|
14
|
+
cached = event[ProfileContentSymbol] = e;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
if (cached === undefined) {
|
package/dist/helpers/relays.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
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
|
+
}
|
|
2
8
|
export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
|
|
3
9
|
export declare function getSeenRelays(event: NostrEvent): Set<string> | undefined;
|
|
4
10
|
export declare function validateRelayURL(relay: string | URL): URL;
|
package/dist/helpers/relays.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
export const SeenRelaysSymbol = Symbol.for("seen-relays");
|
|
2
|
+
// Seen relays
|
|
2
3
|
export function addSeenRelay(event, relay) {
|
|
3
|
-
if (!event[
|
|
4
|
-
event[
|
|
5
|
-
event[
|
|
6
|
-
return event[
|
|
4
|
+
if (!event[SeenRelaysSymbol])
|
|
5
|
+
event[SeenRelaysSymbol] = new Set();
|
|
6
|
+
event[SeenRelaysSymbol].add(relay);
|
|
7
|
+
return event[SeenRelaysSymbol];
|
|
7
8
|
}
|
|
8
9
|
export function getSeenRelays(event) {
|
|
9
|
-
return event[
|
|
10
|
+
return event[SeenRelaysSymbol];
|
|
10
11
|
}
|
|
11
12
|
// Relay URLs
|
|
12
13
|
export function validateRelayURL(relay) {
|
package/dist/promise/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./deferred.js";
|
package/dist/promise/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./deferred.js";
|
|
@@ -1,26 +1,44 @@
|
|
|
1
1
|
import Observable from "zen-observable";
|
|
2
|
-
import { LRU } from "tiny-lru";
|
|
3
2
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
4
3
|
import { EventStore } from "../event-store/event-store.js";
|
|
5
|
-
import {
|
|
4
|
+
import { LRU } from "../utils/lru.js";
|
|
5
|
+
import * as Queries from "./queries/index.js";
|
|
6
|
+
export type Query<T extends unknown> = {
|
|
7
|
+
key: string;
|
|
8
|
+
run: (events: EventStore, store: QueryStore) => Observable<T>;
|
|
9
|
+
};
|
|
10
|
+
export type QueryConstructor<T extends unknown, Args extends Array<any>> = (...args: Args) => Query<T>;
|
|
6
11
|
export declare class QueryStore {
|
|
12
|
+
static Queries: typeof Queries;
|
|
7
13
|
store: EventStore;
|
|
8
14
|
constructor(store: EventStore);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
queries: LRU<Observable<any>>;
|
|
16
|
+
/** Creates a cached query */
|
|
17
|
+
runQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
|
|
18
|
+
key: string;
|
|
19
|
+
run: (events: EventStore, store: QueryStore) => Observable<T>;
|
|
20
|
+
}): (...args: Args) => Observable<T>;
|
|
21
|
+
/** Returns a single event */
|
|
22
|
+
event(id: string): Observable<import("nostr-tools").Event | undefined>;
|
|
23
|
+
/** Returns the latest version of a replaceable event */
|
|
24
|
+
replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
|
|
25
|
+
/** Returns an array of events that match the filter */
|
|
26
|
+
timeline(filters: Filter | Filter[]): Observable<import("nostr-tools").Event[]>;
|
|
27
|
+
/** Returns the parsed profile (0) for a pubkey */
|
|
28
|
+
profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
|
|
29
|
+
/** Returns all reactions for an event (supports replaceable events) */
|
|
30
|
+
reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
31
|
+
/** Returns the parsed relay list (10002) for the pubkey */
|
|
32
|
+
mailboxes(pubkey: string): Observable<{
|
|
19
33
|
inboxes: Set<string>;
|
|
20
34
|
outboxes: Set<string>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
} | undefined>;
|
|
36
|
+
/** Returns the parsed mute list for the pubkey */
|
|
37
|
+
mute(pubkey: string): Observable<{
|
|
38
|
+
words: Set<string>;
|
|
39
|
+
pubkeys: Set<string>;
|
|
40
|
+
threads: Set<string>;
|
|
41
|
+
hashtags: Set<string>;
|
|
42
|
+
} | undefined>;
|
|
26
43
|
}
|
|
44
|
+
export { Queries };
|
|
@@ -1,71 +1,53 @@
|
|
|
1
|
-
import { LRU } from "tiny-lru";
|
|
2
|
-
import { kinds } from "nostr-tools";
|
|
3
|
-
import stringify from "json-stringify-deterministic";
|
|
4
1
|
import { stateful } from "../observable/stateful.js";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
|
|
2
|
+
import { LRU } from "../utils/lru.js";
|
|
3
|
+
import * as Queries from "./queries/index.js";
|
|
8
4
|
export class QueryStore {
|
|
5
|
+
static Queries = Queries;
|
|
9
6
|
store;
|
|
10
7
|
constructor(store) {
|
|
11
8
|
this.store = store;
|
|
12
9
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
queries = new LRU();
|
|
11
|
+
/** Creates a cached query */
|
|
12
|
+
runQuery(queryConstructor) {
|
|
13
|
+
return (...args) => {
|
|
14
|
+
const query = queryConstructor(...args);
|
|
15
|
+
const key = `${queryConstructor.name}|${query.key}`;
|
|
16
|
+
if (!this.queries.has(key)) {
|
|
17
|
+
const observable = stateful(query.run(this.store, this));
|
|
18
|
+
this.queries.set(key, observable);
|
|
19
|
+
return observable;
|
|
20
|
+
}
|
|
21
|
+
return this.queries.get(key);
|
|
22
|
+
};
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
/** Returns a single event */
|
|
25
|
+
event(id) {
|
|
26
|
+
return this.runQuery(Queries.SingleEventQuery)(id);
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!this.singleEvents.has(key)) {
|
|
28
|
-
const observable = stateful(this.store.timeline(filters));
|
|
29
|
-
this.timelines.set(key, observable);
|
|
30
|
-
}
|
|
31
|
-
return this.timelines.get(key);
|
|
28
|
+
/** Returns the latest version of a replaceable event */
|
|
29
|
+
replaceable(kind, pubkey, d) {
|
|
30
|
+
return this.runQuery(Queries.ReplaceableQuery)(kind, pubkey, d);
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const observable = stateful(this.getReplaceable(kinds.Metadata, pubkey).map((event) => getProfileContent(event)));
|
|
37
|
-
this.profiles.set(pubkey, observable);
|
|
38
|
-
}
|
|
39
|
-
return this.profiles.get(pubkey);
|
|
32
|
+
/** Returns an array of events that match the filter */
|
|
33
|
+
timeline(filters) {
|
|
34
|
+
return this.runQuery(Queries.TimelineQuery)(filters);
|
|
40
35
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!this.reactions.has(uid)) {
|
|
45
|
-
const observable = this.getTimeline(isReplaceable(event.kind)
|
|
46
|
-
? [
|
|
47
|
-
{ kinds: [kinds.Reaction], "#e": [event.id] },
|
|
48
|
-
{ kinds: [kinds.Reaction], "#a": [uid] },
|
|
49
|
-
]
|
|
50
|
-
: [
|
|
51
|
-
{
|
|
52
|
-
kinds: [kinds.Reaction],
|
|
53
|
-
"#e": [event.id],
|
|
54
|
-
},
|
|
55
|
-
]);
|
|
56
|
-
this.reactions.set(uid, observable);
|
|
57
|
-
}
|
|
58
|
-
return this.reactions.get(uid);
|
|
36
|
+
/** Returns the parsed profile (0) for a pubkey */
|
|
37
|
+
profile(pubkey) {
|
|
38
|
+
return this.runQuery(Queries.ProfileQuery)(pubkey);
|
|
59
39
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
/** Returns all reactions for an event (supports replaceable events) */
|
|
41
|
+
reactions(event) {
|
|
42
|
+
return this.runQuery(Queries.ReactionsQuery)(event);
|
|
43
|
+
}
|
|
44
|
+
/** Returns the parsed relay list (10002) for the pubkey */
|
|
45
|
+
mailboxes(pubkey) {
|
|
46
|
+
return this.runQuery(Queries.MailboxesQuery)(pubkey);
|
|
47
|
+
}
|
|
48
|
+
/** Returns the parsed mute list for the pubkey */
|
|
49
|
+
mute(pubkey) {
|
|
50
|
+
return this.runQuery(Queries.UserMuteQuery)(pubkey);
|
|
70
51
|
}
|
|
71
52
|
}
|
|
53
|
+
export { Queries };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Query } from "../index.js";
|
|
3
|
+
import { ChannelMetadataContent } from "../../helpers/channel.js";
|
|
4
|
+
/** Creates a query that returns the latest parsed metadata */
|
|
5
|
+
export declare function ChannelMetadataQuery(channel: NostrEvent): Query<ChannelMetadataContent | undefined>;
|
|
6
|
+
/** Creates a query that returns a map of hidden messages Map<id, reason> */
|
|
7
|
+
export declare function ChannelHiddenQuery(channel: NostrEvent, authors?: string[]): Query<Map<string, string>>;
|
|
8
|
+
/** Creates a query that returns a map of muted users Map<pubkey, reason> */
|
|
9
|
+
export declare function ChannelMutedQuery(channel: NostrEvent, authors?: string[]): Query<Map<string, string>>;
|
|
10
|
+
/** Creates a query that returns all messages in a channel */
|
|
11
|
+
export declare function ChannelMessagesQuery(channel: NostrEvent): Query<NostrEvent[]>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getChannelMetadataContent } from "../../helpers/channel.js";
|
|
3
|
+
import { safeParse } from "../../helpers/json.js";
|
|
4
|
+
/** Creates a query that returns the latest parsed metadata */
|
|
5
|
+
export function ChannelMetadataQuery(channel) {
|
|
6
|
+
return {
|
|
7
|
+
key: channel.id,
|
|
8
|
+
run: (events) => {
|
|
9
|
+
const filters = [
|
|
10
|
+
{ ids: [channel.id] },
|
|
11
|
+
{ kinds: [kinds.ChannelMetadata], "#e": [channel.id], authors: [channel.pubkey] },
|
|
12
|
+
];
|
|
13
|
+
let latest = channel;
|
|
14
|
+
return events.stream(filters).map((event) => {
|
|
15
|
+
try {
|
|
16
|
+
if (event.pubkey === latest.pubkey && event.created_at > latest.created_at) {
|
|
17
|
+
latest = event;
|
|
18
|
+
}
|
|
19
|
+
return getChannelMetadataContent(latest);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Creates a query that returns a map of hidden messages Map<id, reason> */
|
|
29
|
+
export function ChannelHiddenQuery(channel, authors = []) {
|
|
30
|
+
return {
|
|
31
|
+
key: channel.id,
|
|
32
|
+
run: (events) => {
|
|
33
|
+
const hidden = new Map();
|
|
34
|
+
return events
|
|
35
|
+
.stream([{ kinds: [kinds.ChannelHideMessage], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
|
|
36
|
+
.map((event) => {
|
|
37
|
+
const reason = safeParse(event.content)?.reason;
|
|
38
|
+
for (const tag of event.tags) {
|
|
39
|
+
if (tag[0] === "e" && tag[1])
|
|
40
|
+
hidden.set(tag[1], reason ?? "");
|
|
41
|
+
}
|
|
42
|
+
return hidden;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Creates a query that returns a map of muted users Map<pubkey, reason> */
|
|
48
|
+
export function ChannelMutedQuery(channel, authors = []) {
|
|
49
|
+
return {
|
|
50
|
+
key: channel.id + authors.join(","),
|
|
51
|
+
run: (events) => {
|
|
52
|
+
const muted = new Map();
|
|
53
|
+
return events
|
|
54
|
+
.stream([{ kinds: [kinds.ChannelMuteUser], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
|
|
55
|
+
.map((event) => {
|
|
56
|
+
const reason = safeParse(event.content)?.reason;
|
|
57
|
+
for (const tag of event.tags) {
|
|
58
|
+
if (tag[0] === "p" && tag[1])
|
|
59
|
+
muted.set(tag[1], reason ?? "");
|
|
60
|
+
}
|
|
61
|
+
return muted;
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Creates a query that returns all messages in a channel */
|
|
67
|
+
export function ChannelMessagesQuery(channel) {
|
|
68
|
+
return {
|
|
69
|
+
key: channel.id,
|
|
70
|
+
run: (events) => events.timeline([{ kinds: [kinds.ChannelMessage], "#e": [channel.id] }]),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getInboxes, getOutboxes } from "../../helpers/mailboxes.js";
|
|
3
|
+
export function MailboxesQuery(pubkey) {
|
|
4
|
+
return {
|
|
5
|
+
key: pubkey,
|
|
6
|
+
run: (events) => events.replaceable(kinds.RelayList, pubkey).map((event) => event && {
|
|
7
|
+
inboxes: getInboxes(event),
|
|
8
|
+
outboxes: getOutboxes(event),
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getMutedHashtags, getMutedPubkeys, getMutedThreads, getMutedWords } from "../../helpers/mute.js";
|
|
3
|
+
export function UserMuteQuery(pubkey) {
|
|
4
|
+
return {
|
|
5
|
+
key: pubkey,
|
|
6
|
+
run: (store) => store.replaceable(kinds.Mutelist, pubkey).map((event) => {
|
|
7
|
+
if (!event)
|
|
8
|
+
return;
|
|
9
|
+
const pubkeys = getMutedPubkeys(event);
|
|
10
|
+
const threads = getMutedThreads(event);
|
|
11
|
+
const hashtags = getMutedHashtags(event);
|
|
12
|
+
const words = getMutedWords(event);
|
|
13
|
+
return { pubkeys, threads, hashtags, words };
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getProfileContent } from "../../helpers/profile.js";
|
|
3
|
+
export function ProfileQuery(pubkey) {
|
|
4
|
+
return {
|
|
5
|
+
key: pubkey,
|
|
6
|
+
run: (events) => {
|
|
7
|
+
return events.replaceable(kinds.Metadata, pubkey).map((event) => event && getProfileContent(event));
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|