applesauce-core 0.0.0-next-20241113103021 → 0.0.0-next-20241114194041
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/helpers/channel.d.ts +15 -0
- package/dist/helpers/channel.js +27 -0
- package/dist/helpers/mute.d.ts +21 -0
- package/dist/helpers/mute.js +52 -0
- package/dist/helpers/symbols.d.ts +6 -0
- package/dist/helpers/symbols.js +2 -0
- package/dist/helpers/user-status.d.ts +22 -0
- package/dist/helpers/user-status.js +24 -0
- package/dist/observable/stateful.d.ts +1 -1
- package/dist/observable/stateful.js +1 -1
- package/dist/queries/channel.d.ts +11 -0
- package/dist/queries/channel.js +72 -0
- package/dist/queries/mute.d.ts +7 -0
- package/dist/queries/mute.js +16 -0
- package/dist/queries/user-status.d.ts +11 -0
- package/dist/queries/user-status.js +35 -0
- package/dist/query-store/queries/TimelineQuery.d.ts +1 -0
- package/dist/query-store/queries/TimelineQuery.js +1 -0
- 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 +7 -0
- package/dist/query-store/queries/index.js +7 -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/query-store/queries/thread.d.ts +20 -0
- package/dist/query-store/queries/thread.js +60 -0
- package/dist/query-store/queries/timeline.d.ts +3 -0
- package/dist/query-store/queries/timeline.js +3 -0
- package/package.json +7 -2
- package/dist/observable/share-behavior.d.ts +0 -2
- package/dist/observable/share-behavior.js +0 -7
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { nip19, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { ChannelMetadata } from "nostr-tools/nip28";
|
|
3
|
+
export declare const ChannelMetadataSymbol: unique symbol;
|
|
4
|
+
declare module "nostr-tools" {
|
|
5
|
+
interface Event {
|
|
6
|
+
[ChannelMetadataSymbol]?: ChannelMetadataContent;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export type ChannelMetadataContent = ChannelMetadata & {
|
|
10
|
+
relays?: string[];
|
|
11
|
+
};
|
|
12
|
+
/** Gets the parsed metadata on a channel creation or channel metadata event */
|
|
13
|
+
export declare function getChannelMetadataContent(channel: NostrEvent): ChannelMetadataContent;
|
|
14
|
+
/** gets the EventPointer for a channel message or metadata event */
|
|
15
|
+
export declare function getChannelPointer(event: NostrEvent): nip19.EventPointer | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const ChannelMetadataSymbol = Symbol.for("channel-metadata");
|
|
2
|
+
function parseChannelMetadataContent(channel) {
|
|
3
|
+
const metadata = JSON.parse(channel.content);
|
|
4
|
+
if (metadata.name === undefined)
|
|
5
|
+
throw new Error("Missing name");
|
|
6
|
+
if (metadata.about === undefined)
|
|
7
|
+
throw new Error("Missing about");
|
|
8
|
+
if (metadata.picture === undefined)
|
|
9
|
+
throw new Error("Missing picture");
|
|
10
|
+
if (metadata.relays && !Array.isArray(metadata.relays))
|
|
11
|
+
throw new Error("Invalid relays");
|
|
12
|
+
return metadata;
|
|
13
|
+
}
|
|
14
|
+
/** Gets the parsed metadata on a channel creation or channel metadata event */
|
|
15
|
+
export function getChannelMetadataContent(channel) {
|
|
16
|
+
let metadata = channel[ChannelMetadataSymbol];
|
|
17
|
+
if (!metadata)
|
|
18
|
+
metadata = channel[ChannelMetadataSymbol] = parseChannelMetadataContent(channel);
|
|
19
|
+
return metadata;
|
|
20
|
+
}
|
|
21
|
+
/** gets the EventPointer for a channel message or metadata event */
|
|
22
|
+
export function getChannelPointer(event) {
|
|
23
|
+
const tag = event.tags.find((t) => t[0] === "e" && t[1]);
|
|
24
|
+
if (!tag)
|
|
25
|
+
return undefined;
|
|
26
|
+
return tag[2] ? { id: tag[1], relays: [tag[2]] } : { id: tag[1] };
|
|
27
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
3
|
+
export declare const UserStatusPointerSymbol: unique symbol;
|
|
4
|
+
declare module "nostr-tools" {
|
|
5
|
+
interface Event {
|
|
6
|
+
[UserStatusPointerSymbol]?: UserStatusPointer | null;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export type UserStatusPointer = {
|
|
10
|
+
type: "nevent";
|
|
11
|
+
data: EventPointer;
|
|
12
|
+
} | {
|
|
13
|
+
type: "nprofile";
|
|
14
|
+
data: ProfilePointer;
|
|
15
|
+
} | {
|
|
16
|
+
type: "naddr";
|
|
17
|
+
data: AddressPointer;
|
|
18
|
+
} | {
|
|
19
|
+
type: "url";
|
|
20
|
+
data: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function getUserStatusPointer(status: NostrEvent): UserStatusPointer | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getAddressPointerFromTag, getEventPointerFromTag, getProfilePointerFromTag } from "./pointers.js";
|
|
2
|
+
export const UserStatusPointerSymbol = Symbol.for("user-status-pointer");
|
|
3
|
+
function getStatusPointer(status) {
|
|
4
|
+
const pTag = status.tags.find((t) => t[0] === "p" && t[1]);
|
|
5
|
+
if (pTag)
|
|
6
|
+
return { type: "nprofile", data: getProfilePointerFromTag(pTag) };
|
|
7
|
+
const eTag = status.tags.find((t) => t[0] === "e" && t[1]);
|
|
8
|
+
if (eTag)
|
|
9
|
+
return { type: "nevent", data: getEventPointerFromTag(eTag) };
|
|
10
|
+
const aTag = status.tags.find((t) => t[0] === "a" && t[1]);
|
|
11
|
+
if (aTag)
|
|
12
|
+
return { type: "naddr", data: getAddressPointerFromTag(aTag) };
|
|
13
|
+
const rTag = status.tags.find((t) => t[0] === "r" && t[1]);
|
|
14
|
+
if (rTag)
|
|
15
|
+
return { type: "url", data: rTag[1] };
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export function getUserStatusPointer(status) {
|
|
19
|
+
let pointer = status[UserStatusPointerSymbol];
|
|
20
|
+
if (pointer === undefined) {
|
|
21
|
+
pointer = status[UserStatusPointerSymbol] = getStatusPointer(status);
|
|
22
|
+
}
|
|
23
|
+
return pointer;
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Query } from "../query-store/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,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,11 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { UserStatusPointer } from "../helpers/user-status.js";
|
|
3
|
+
import { Query } from "../query-store/index.js";
|
|
4
|
+
export type UserStatus = UserStatusPointer & {
|
|
5
|
+
event: NostrEvent;
|
|
6
|
+
content: string;
|
|
7
|
+
};
|
|
8
|
+
/** Creates a Query that returns a parsed {@link UserStatus} for a certain type */
|
|
9
|
+
export declare function UserStatusQuery(pubkey: string, type?: string): Query<UserStatus | undefined | null>;
|
|
10
|
+
/** Creates a Query that returns a directory of parsed {@link UserStatus} for a pubkey */
|
|
11
|
+
export declare function UserStatusesQuery(pubkey: string): Query<Record<string, UserStatus>>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getUserStatusPointer } from "../helpers/user-status.js";
|
|
3
|
+
import { getTagValue } from "../helpers/event.js";
|
|
4
|
+
/** Creates a Query that returns a parsed {@link UserStatus} for a certain type */
|
|
5
|
+
export function UserStatusQuery(pubkey, type = "general") {
|
|
6
|
+
return {
|
|
7
|
+
key: pubkey,
|
|
8
|
+
run: (events) => events.replaceable(kinds.UserStatuses, pubkey, type).map((event) => {
|
|
9
|
+
if (!event)
|
|
10
|
+
return undefined;
|
|
11
|
+
const pointer = getUserStatusPointer(event);
|
|
12
|
+
if (!pointer)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
...pointer,
|
|
16
|
+
event,
|
|
17
|
+
content: event.content,
|
|
18
|
+
};
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Creates a Query that returns a directory of parsed {@link UserStatus} for a pubkey */
|
|
23
|
+
export function UserStatusesQuery(pubkey) {
|
|
24
|
+
return {
|
|
25
|
+
key: pubkey,
|
|
26
|
+
run: (events) => events.timeline([{ kinds: [kinds.UserStatuses], authors: [pubkey] }]).map((events) => {
|
|
27
|
+
return events.reduce((dir, event) => {
|
|
28
|
+
const d = getTagValue(event, "d");
|
|
29
|
+
if (!d)
|
|
30
|
+
return dir;
|
|
31
|
+
return { ...dir, [d]: { event, ...getUserStatusPointer(event), content: event.content } };
|
|
32
|
+
}, {});
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getEventUID, isReplaceable } from "../../helpers/event.js";
|
|
3
|
+
/** Creates a query that returns all reactions to an event (supports replaceable events) */
|
|
4
|
+
export function ReactionsQuery(event) {
|
|
5
|
+
return {
|
|
6
|
+
key: getEventUID(event),
|
|
7
|
+
run: (events) => events.timeline(isReplaceable(event.kind)
|
|
8
|
+
? [
|
|
9
|
+
{ kinds: [kinds.Reaction], "#e": [event.id] },
|
|
10
|
+
{ kinds: [kinds.Reaction], "#a": [getEventUID(event)] },
|
|
11
|
+
]
|
|
12
|
+
: [
|
|
13
|
+
{
|
|
14
|
+
kinds: [kinds.Reaction],
|
|
15
|
+
"#e": [event.id],
|
|
16
|
+
},
|
|
17
|
+
]),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Query } from "../index.js";
|
|
3
|
+
export declare function SingleEventQuery(uid: string): Query<NostrEvent | undefined>;
|
|
4
|
+
export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
|
|
5
|
+
export declare function TimelineQuery(filters: Filter | Filter[]): Query<NostrEvent[]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import stringify from "json-stringify-deterministic";
|
|
2
|
+
import { getReplaceableUID } from "../../helpers/event.js";
|
|
3
|
+
export function SingleEventQuery(uid) {
|
|
4
|
+
return {
|
|
5
|
+
key: uid,
|
|
6
|
+
run: (events) => events.event(uid),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function ReplaceableQuery(kind, pubkey, d) {
|
|
10
|
+
return {
|
|
11
|
+
key: getReplaceableUID(kind, pubkey, d),
|
|
12
|
+
run: (events) => events.replaceable(kind, pubkey, d),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function TimelineQuery(filters) {
|
|
16
|
+
return {
|
|
17
|
+
key: stringify(filters),
|
|
18
|
+
run: (events) => events.timeline(Array.isArray(filters) ? filters : [filters]),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
import { Query } from "../index.js";
|
|
4
|
+
export type ThreadItem = {
|
|
5
|
+
/** underlying nostr event */
|
|
6
|
+
event: NostrEvent;
|
|
7
|
+
/** the thread root, according to this event */
|
|
8
|
+
root?: ThreadItem;
|
|
9
|
+
/** the parent event this is replying to */
|
|
10
|
+
parent?: ThreadItem;
|
|
11
|
+
/** direct child replies */
|
|
12
|
+
replies: Set<ThreadItem>;
|
|
13
|
+
};
|
|
14
|
+
export type ThreadQueryOptions = {
|
|
15
|
+
kinds?: number[];
|
|
16
|
+
};
|
|
17
|
+
export declare function ThreadQuery(root: string | AddressPointer | EventPointer, opts?: ThreadQueryOptions): Query<{
|
|
18
|
+
root?: ThreadItem;
|
|
19
|
+
all: ThreadItem[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getNip10References } from "../../helpers/threading.js";
|
|
3
|
+
import { getCoordinateFromAddressPointer, isAddressPointer } from "../../helpers/pointers.js";
|
|
4
|
+
import { getEventUID } from "../../helpers/event.js";
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
kinds: [kinds.ShortTextNote],
|
|
7
|
+
};
|
|
8
|
+
export function ThreadQuery(root, opts) {
|
|
9
|
+
const parentReferences = new Map();
|
|
10
|
+
const items = new Map();
|
|
11
|
+
const { kinds } = { ...defaultOptions, ...opts };
|
|
12
|
+
let rootUID = "";
|
|
13
|
+
const rootFilter = {};
|
|
14
|
+
const replyFilter = { kinds };
|
|
15
|
+
if (isAddressPointer(root)) {
|
|
16
|
+
rootUID = getCoordinateFromAddressPointer(root);
|
|
17
|
+
rootFilter.kinds = [root.kind];
|
|
18
|
+
rootFilter.authors = [root.pubkey];
|
|
19
|
+
rootFilter["#d"] = [root.identifier];
|
|
20
|
+
replyFilter["#a"] = [rootUID];
|
|
21
|
+
}
|
|
22
|
+
else if (typeof root === "string") {
|
|
23
|
+
rootUID = root;
|
|
24
|
+
rootFilter.ids = [root];
|
|
25
|
+
replyFilter["#e"] = [root];
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
rootUID = root.id;
|
|
29
|
+
rootFilter.ids = [root.id];
|
|
30
|
+
replyFilter["#e"] = [root.id];
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
key: `${root}-${kinds.join(",")}`,
|
|
34
|
+
run: (events) => events.stream([rootFilter, replyFilter]).map((event) => {
|
|
35
|
+
const refs = getNip10References(event);
|
|
36
|
+
const replies = parentReferences.get(getEventUID(event)) || new Set();
|
|
37
|
+
const item = { event, replies };
|
|
38
|
+
// add item to parent
|
|
39
|
+
if (refs.reply?.e || refs.reply?.a) {
|
|
40
|
+
let uid = refs.reply.e ? refs.reply.e.id : getCoordinateFromAddressPointer(refs.reply.a);
|
|
41
|
+
const parent = items.get(uid);
|
|
42
|
+
if (parent) {
|
|
43
|
+
parent.replies.add(item);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// parent isn't created yet, store ref for later
|
|
47
|
+
let set = parentReferences.get(uid);
|
|
48
|
+
if (!set) {
|
|
49
|
+
set = new Set();
|
|
50
|
+
parentReferences.set(uid, set);
|
|
51
|
+
}
|
|
52
|
+
set.add(item);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// add item to map
|
|
56
|
+
items.set(getEventUID(event), item);
|
|
57
|
+
return { root: items.get(rootUID), all: Array.from(items.values()) };
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-core",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-20241114194041",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
"@types/debug": "^4.1.12",
|
|
65
65
|
"@types/jest": "^29.5.13",
|
|
66
66
|
"jest": "^29.7.0",
|
|
67
|
-
"jest-extended": "^4.0.2"
|
|
67
|
+
"jest-extended": "^4.0.2",
|
|
68
|
+
"typescript": "^5.6.3"
|
|
68
69
|
},
|
|
69
70
|
"jest": {
|
|
70
71
|
"roots": [
|
|
@@ -74,6 +75,10 @@
|
|
|
74
75
|
"jest-extended/all"
|
|
75
76
|
]
|
|
76
77
|
},
|
|
78
|
+
"funding": {
|
|
79
|
+
"type": "lightning",
|
|
80
|
+
"url": "lightning:nostrudel@geyser.fund"
|
|
81
|
+
},
|
|
77
82
|
"scripts": {
|
|
78
83
|
"build": "tsc",
|
|
79
84
|
"watch:build": "tsc --watch > /dev/null",
|