applesauce-core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +38 -0
  2. package/dist/event-store/event-store.d.ts +3 -1
  3. package/dist/event-store/event-store.js +6 -2
  4. package/dist/helpers/channel.d.ts +15 -0
  5. package/dist/helpers/channel.js +27 -0
  6. package/dist/helpers/event.d.ts +17 -1
  7. package/dist/helpers/event.js +22 -9
  8. package/dist/helpers/index.d.ts +2 -1
  9. package/dist/helpers/index.js +2 -1
  10. package/dist/helpers/json.d.ts +1 -0
  11. package/dist/helpers/json.js +8 -0
  12. package/dist/helpers/mailboxes.d.ts +10 -2
  13. package/dist/helpers/mailboxes.js +10 -9
  14. package/dist/helpers/mailboxes.test.d.ts +1 -0
  15. package/dist/helpers/mailboxes.test.js +80 -0
  16. package/dist/helpers/mute.d.ts +21 -0
  17. package/dist/helpers/mute.js +52 -0
  18. package/dist/helpers/pointers.d.ts +22 -0
  19. package/dist/helpers/pointers.js +127 -0
  20. package/dist/helpers/profile.d.ts +7 -0
  21. package/dist/helpers/profile.js +4 -4
  22. package/dist/helpers/relays.d.ts +6 -0
  23. package/dist/helpers/relays.js +7 -6
  24. package/dist/helpers/threading.d.ts +55 -0
  25. package/dist/helpers/threading.js +61 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +1 -0
  28. package/dist/observable/stateful.d.ts +1 -1
  29. package/dist/observable/stateful.js +1 -1
  30. package/dist/observable/throttle.d.ts +1 -0
  31. package/dist/observable/throttle.js +1 -0
  32. package/dist/promise/index.d.ts +1 -1
  33. package/dist/promise/index.js +1 -1
  34. package/dist/queries/channel.d.ts +11 -0
  35. package/dist/queries/channel.js +72 -0
  36. package/dist/queries/index.d.ts +7 -0
  37. package/dist/queries/index.js +7 -0
  38. package/dist/queries/mailboxes.d.ts +5 -0
  39. package/dist/queries/mailboxes.js +11 -0
  40. package/dist/queries/mute.d.ts +7 -0
  41. package/dist/queries/mute.js +16 -0
  42. package/dist/queries/profile.d.ts +3 -0
  43. package/dist/queries/profile.js +10 -0
  44. package/dist/queries/reactions.d.ts +4 -0
  45. package/dist/queries/reactions.js +19 -0
  46. package/dist/queries/simple.d.ts +5 -0
  47. package/dist/queries/simple.js +20 -0
  48. package/dist/queries/thread.d.ts +23 -0
  49. package/dist/queries/thread.js +65 -0
  50. package/dist/query-store/index.d.ts +35 -15
  51. package/dist/query-store/index.js +42 -57
  52. package/package.json +21 -2
  53. package/dist/helpers/symbols.d.ts +0 -17
  54. package/dist/helpers/symbols.js +0 -10
package/README.md CHANGED
@@ -1 +1,39 @@
1
1
  # applesauce-core
2
+
3
+ AppleSauce Core is an interpretation layer for nostr clients, Push events into the in-memory [database](https://hzrd149.github.io/applesauce/classes/Database.html) and get nicely formatted data out with [queries](https://hzrd149.github.io/applesauce/modules/Queries)
4
+
5
+ # Example
6
+
7
+ ```js
8
+ import { EventStore, QueryStore } from "applesauce-core";
9
+ import { Relay } from "nostr-tools/relay";
10
+
11
+ // The EventStore handles all the events
12
+ const eventStore = new EventStore();
13
+
14
+ // The QueryStore handles queries and makes sure not to run multiple of the same query
15
+ const queryStore = new QueryStore(eventStore);
16
+
17
+ // Use nostr-tools or anything else to talk to relays
18
+ const relay = await Relay.connect("wss://relay.example.com");
19
+
20
+ const sub = relay.subscribe([{ authors: ["266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5"] }], {
21
+ onevent(event) {
22
+ eventStore.add(event);
23
+ },
24
+ });
25
+
26
+ // This will return an Observable<ProfileContent | undefined> of the parsed metadata
27
+ const profile = queryStore.profile("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5");
28
+
29
+ profile.subscribe((parsed) => {
30
+ if (parsed) console.log(parsed);
31
+ });
32
+
33
+ // This will return an Observable<NostrEvent[]> of all kind 1 events sorted by created_at
34
+ const timeline = queryStore.timeline({ kinds: [1] });
35
+
36
+ timeline.subscribe((events) => {
37
+ console.log(events);
38
+ });
39
+ ```
@@ -14,7 +14,9 @@ export declare class EventStore {
14
14
  hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
15
15
  getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
16
16
  /** Creates an observable that updates a single event */
17
- single(uid: string): Observable<import("nostr-tools").Event | undefined>;
17
+ event(uid: string): Observable<import("nostr-tools").Event | undefined>;
18
+ /** Creates an observable that updates a single replaceable event */
19
+ replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
18
20
  /** Creates an observable that streams all events that match the filter */
19
21
  stream(filters: Filter[]): Observable<import("nostr-tools").Event>;
20
22
  /** Creates an observable that updates with an array of sorted events */
@@ -1,7 +1,7 @@
1
1
  import { insertEventIntoDescendingList } from "nostr-tools/utils";
2
2
  import Observable from "zen-observable";
3
3
  import { Database } from "./database.js";
4
- import { getEventUID } from "../helpers/event.js";
4
+ import { getEventUID, getReplaceableUID } from "../helpers/event.js";
5
5
  import { matchFilters } from "../helpers/filter.js";
6
6
  import { addSeenRelay } from "../helpers/relays.js";
7
7
  export class EventStore {
@@ -34,7 +34,7 @@ export class EventStore {
34
34
  return this.events.getReplaceable(kind, pubkey, d);
35
35
  }
36
36
  /** Creates an observable that updates a single event */
37
- single(uid) {
37
+ event(uid) {
38
38
  return new Observable((observer) => {
39
39
  let current = this.events.getEvent(uid);
40
40
  if (current) {
@@ -71,6 +71,10 @@ export class EventStore {
71
71
  };
72
72
  });
73
73
  }
74
+ /** Creates an observable that updates a single replaceable event */
75
+ replaceable(kind, pubkey, d) {
76
+ return this.event(getReplaceableUID(kind, pubkey, d));
77
+ }
74
78
  /** Creates an observable that streams all events that match the filter */
75
79
  stream(filters) {
76
80
  return new Observable((observer) => {
@@ -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
+ }
@@ -1,10 +1,26 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
+ export declare const EventUIDSymbol: unique symbol;
3
+ export declare const EventIndexableTagsSymbol: unique symbol;
4
+ declare module "nostr-tools" {
5
+ interface Event {
6
+ [EventUIDSymbol]?: string;
7
+ [EventIndexableTagsSymbol]?: Set<string>;
8
+ }
9
+ }
2
10
  /**
3
11
  * Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )
4
12
  * or parameterized replaceable ( 30000 <= n < 40000 )
5
13
  */
6
14
  export declare function isReplaceable(kind: number): boolean;
7
- /** returns the events Unique ID */
15
+ /**
16
+ * Returns the events Unique ID
17
+ * For normal or ephemeral events this is ( event.id )
18
+ * For replaceable events this is ( event.kind + ":" + event.pubkey )
19
+ * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
20
+ */
8
21
  export declare function getEventUID(event: NostrEvent): string;
9
22
  export declare function getReplaceableUID(kind: number, pubkey: string, d?: string): string;
23
+ /** Returns a Set of tag names and values that are indexable */
10
24
  export declare function getIndexableTags(event: NostrEvent): Set<string>;
25
+ /** Returns the second index ( tag[1] ) of the first tag that matches the name */
26
+ export declare function getTagValue(event: NostrEvent, name: string): string | undefined;
@@ -1,6 +1,7 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { INDEXABLE_TAGS } from "../event-store/common.js";
3
- import { EventIndexableTags, EventUID } from "./symbols.js";
3
+ export const EventUIDSymbol = Symbol.for("event-uid");
4
+ export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
4
5
  /**
5
6
  * Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )
6
7
  * or parameterized replaceable ( 30000 <= n < 40000 )
@@ -8,31 +9,43 @@ import { EventIndexableTags, EventUID } from "./symbols.js";
8
9
  export function isReplaceable(kind) {
9
10
  return kinds.isReplaceableKind(kind) || kinds.isParameterizedReplaceableKind(kind);
10
11
  }
11
- /** returns the events Unique ID */
12
+ /**
13
+ * Returns the events Unique ID
14
+ * For normal or ephemeral events this is ( event.id )
15
+ * For replaceable events this is ( event.kind + ":" + event.pubkey )
16
+ * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
17
+ */
12
18
  export function getEventUID(event) {
13
- if (!event[EventUID]) {
19
+ let id = event[EventUIDSymbol];
20
+ if (!id) {
14
21
  if (isReplaceable(event.kind)) {
15
22
  const d = event.tags.find((t) => t[0] === "d")?.[1];
16
- event[EventUID] = getReplaceableUID(event.kind, event.pubkey, d);
23
+ id = getReplaceableUID(event.kind, event.pubkey, d);
17
24
  }
18
25
  else {
19
- event[EventUID] = event.id;
26
+ id = event.id;
20
27
  }
21
28
  }
22
- return event[EventUID];
29
+ return id;
23
30
  }
24
31
  export function getReplaceableUID(kind, pubkey, d) {
25
32
  return d ? `${kind}:${pubkey}:${d}` : `${kind}:${pubkey}`;
26
33
  }
34
+ /** Returns a Set of tag names and values that are indexable */
27
35
  export function getIndexableTags(event) {
28
- if (!event[EventIndexableTags]) {
36
+ let indexable = event[EventIndexableTagsSymbol];
37
+ if (!indexable) {
29
38
  const tags = new Set();
30
39
  for (const tag of event.tags) {
31
40
  if (tag[0] && INDEXABLE_TAGS.has(tag[0]) && tag[1]) {
32
41
  tags.add(tag[0] + ":" + tag[1]);
33
42
  }
34
43
  }
35
- event[EventIndexableTags] = tags;
44
+ indexable = event[EventIndexableTagsSymbol] = tags;
36
45
  }
37
- return event[EventIndexableTags];
46
+ return indexable;
47
+ }
48
+ /** Returns the second index ( tag[1] ) of the first tag that matches the name */
49
+ export function getTagValue(event, name) {
50
+ return event.tags.find((t) => t[0] === name)?.[1];
38
51
  }
@@ -3,4 +3,5 @@ export * from "./relays.js";
3
3
  export * from "./event.js";
4
4
  export * from "./filter.js";
5
5
  export * from "./mailboxes.js";
6
- export * as symbols from "./symbols.js";
6
+ export * from "./threading.js";
7
+ export * from "./pointers.js";
@@ -3,4 +3,5 @@ export * from "./relays.js";
3
3
  export * from "./event.js";
4
4
  export * from "./filter.js";
5
5
  export * from "./mailboxes.js";
6
- export * as symbols from "./symbols.js";
6
+ export * from "./threading.js";
7
+ export * from "./pointers.js";
@@ -0,0 +1 @@
1
+ export declare function safeParse<T extends unknown = any>(str: string): T | undefined;
@@ -0,0 +1,8 @@
1
+ export function safeParse(str) {
2
+ try {
3
+ return JSON.parse(str);
4
+ }
5
+ catch (error) {
6
+ return undefined;
7
+ }
8
+ }
@@ -1,9 +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
+ }
2
10
  /**
3
- * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxes} symbol
11
+ * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
4
12
  */
5
13
  export declare function getInboxes(event: NostrEvent): Set<string>;
6
14
  /**
7
- * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxes} symbol
15
+ * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
8
16
  */
9
17
  export declare function getOutboxes(event: NostrEvent): Set<string>;
@@ -1,10 +1,11 @@
1
1
  import { safeRelayUrl } from "./relays.js";
2
- import { MailboxesInboxes, MailboxesOutboxes } from "./symbols.js";
2
+ export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
3
+ export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
3
4
  /**
4
- * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxes} symbol
5
+ * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
5
6
  */
6
7
  export function getInboxes(event) {
7
- if (!event[MailboxesInboxes]) {
8
+ if (!event[MailboxesInboxesSymbol]) {
8
9
  const inboxes = new Set();
9
10
  for (const tag of event.tags) {
10
11
  if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
@@ -13,15 +14,15 @@ export function getInboxes(event) {
13
14
  inboxes.add(url);
14
15
  }
15
16
  }
16
- event[MailboxesInboxes] = inboxes;
17
+ event[MailboxesInboxesSymbol] = inboxes;
17
18
  }
18
- return event[MailboxesInboxes];
19
+ return event[MailboxesInboxesSymbol];
19
20
  }
20
21
  /**
21
- * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxes} symbol
22
+ * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
22
23
  */
23
24
  export function getOutboxes(event) {
24
- if (!event[MailboxesOutboxes]) {
25
+ if (!event[MailboxesOutboxesSymbol]) {
25
26
  const outboxes = new Set();
26
27
  for (const tag of event.tags) {
27
28
  if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
@@ -30,7 +31,7 @@ export function getOutboxes(event) {
30
31
  outboxes.add(url);
31
32
  }
32
33
  }
33
- event[MailboxesOutboxes] = outboxes;
34
+ event[MailboxesOutboxesSymbol] = outboxes;
34
35
  }
35
- return event[MailboxesOutboxes];
36
+ return event[MailboxesOutboxesSymbol];
36
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
+ }
@@ -0,0 +1,22 @@
1
+ import { AddressPointer, DecodeResult, EventPointer, ProfilePointer } from "nostr-tools/nip19";
2
+ export type AddressPointerWithoutD = Omit<AddressPointer, "identifier"> & {
3
+ identifier?: string;
4
+ };
5
+ export declare function parseCoordinate(a: string): AddressPointerWithoutD | null;
6
+ export declare function parseCoordinate(a: string, requireD: false): AddressPointerWithoutD | null;
7
+ export declare function parseCoordinate(a: string, requireD: true): AddressPointer | null;
8
+ export declare function parseCoordinate(a: string, requireD: false, silent: false): AddressPointerWithoutD;
9
+ export declare function parseCoordinate(a: string, requireD: true, silent: false): AddressPointer;
10
+ export declare function parseCoordinate(a: string, requireD: true, silent: true): AddressPointer | null;
11
+ export declare function parseCoordinate(a: string, requireD: false, silent: true): AddressPointerWithoutD | null;
12
+ export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
13
+ export declare function encodeDecodeResult(result: DecodeResult): "" | `nsec1${string}` | `npub1${string}` | `note1${string}` | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nrelay1${string}`;
14
+ export declare function getEventPointerFromTag(tag: string[]): EventPointer;
15
+ export declare function getAddressPointerFromTag(tag: string[]): AddressPointer;
16
+ export declare function getProfilePointerFromTag(tag: string[]): ProfilePointer;
17
+ export declare function getPointerFromTag(tag: string[]): DecodeResult | null;
18
+ export declare function isAddressPointer(pointer: DecodeResult["data"]): pointer is AddressPointer;
19
+ export declare function isEventPointer(pointer: DecodeResult["data"]): pointer is EventPointer;
20
+ export declare function getCoordinateFromAddressPointer(pointer: AddressPointer): string;
21
+ export declare function getATagFromAddressPointer(pointer: AddressPointer): ["a", ...string[]];
22
+ export declare function getETagFromEventPointer(pointer: EventPointer): ["e", ...string[]];
@@ -0,0 +1,127 @@
1
+ import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nrelayEncode, nsecEncode, } from "nostr-tools/nip19";
2
+ import { getPublicKey } from "nostr-tools";
3
+ import { safeRelayUrls } from "./relays.js";
4
+ export function parseCoordinate(a, requireD = false, silent = true) {
5
+ const parts = a.split(":");
6
+ const kind = parts[0] && parseInt(parts[0]);
7
+ const pubkey = parts[1];
8
+ const d = parts[2];
9
+ if (!kind) {
10
+ if (silent)
11
+ return null;
12
+ else
13
+ throw new Error("Missing kind");
14
+ }
15
+ if (!pubkey) {
16
+ if (silent)
17
+ return null;
18
+ else
19
+ throw new Error("Missing pubkey");
20
+ }
21
+ if (requireD && d === undefined) {
22
+ if (silent)
23
+ return null;
24
+ else
25
+ throw new Error("Missing identifier");
26
+ }
27
+ return {
28
+ kind,
29
+ pubkey,
30
+ identifier: d,
31
+ };
32
+ }
33
+ export function getPubkeyFromDecodeResult(result) {
34
+ if (!result)
35
+ return;
36
+ switch (result.type) {
37
+ case "naddr":
38
+ case "nprofile":
39
+ return result.data.pubkey;
40
+ case "npub":
41
+ return result.data;
42
+ case "nsec":
43
+ return getPublicKey(result.data);
44
+ default:
45
+ return undefined;
46
+ }
47
+ }
48
+ export function encodeDecodeResult(result) {
49
+ switch (result.type) {
50
+ case "naddr":
51
+ return naddrEncode(result.data);
52
+ case "nprofile":
53
+ return nprofileEncode(result.data);
54
+ case "nevent":
55
+ return neventEncode(result.data);
56
+ case "nrelay":
57
+ return nrelayEncode(result.data);
58
+ case "nsec":
59
+ return nsecEncode(result.data);
60
+ case "npub":
61
+ return npubEncode(result.data);
62
+ case "note":
63
+ return noteEncode(result.data);
64
+ }
65
+ return "";
66
+ }
67
+ export function getEventPointerFromTag(tag) {
68
+ if (!tag[1])
69
+ throw new Error("Missing event id in tag");
70
+ let pointer = { id: tag[1] };
71
+ if (tag[2])
72
+ pointer.relays = safeRelayUrls([tag[2]]);
73
+ return pointer;
74
+ }
75
+ export function getAddressPointerFromTag(tag) {
76
+ if (!tag[1])
77
+ throw new Error("Missing coordinate in tag");
78
+ const pointer = parseCoordinate(tag[1], true, false);
79
+ if (tag[2])
80
+ pointer.relays = safeRelayUrls([tag[2]]);
81
+ return pointer;
82
+ }
83
+ export function getProfilePointerFromTag(tag) {
84
+ if (!tag[1])
85
+ throw new Error("Missing pubkey in tag");
86
+ const pointer = { pubkey: tag[1] };
87
+ if (tag[2])
88
+ pointer.relays = safeRelayUrls([tag[2]]);
89
+ return pointer;
90
+ }
91
+ export function getPointerFromTag(tag) {
92
+ try {
93
+ switch (tag[0]) {
94
+ case "e":
95
+ return { type: "nevent", data: getEventPointerFromTag(tag) };
96
+ case "a":
97
+ return {
98
+ type: "naddr",
99
+ data: getAddressPointerFromTag(tag),
100
+ };
101
+ case "p":
102
+ return { type: "nprofile", data: getProfilePointerFromTag(tag) };
103
+ }
104
+ }
105
+ catch (error) { }
106
+ return null;
107
+ }
108
+ export function isAddressPointer(pointer) {
109
+ return (typeof pointer !== "string" &&
110
+ Object.hasOwn(pointer, "identifier") &&
111
+ Object.hasOwn(pointer, "pubkey") &&
112
+ Object.hasOwn(pointer, "kind"));
113
+ }
114
+ export function isEventPointer(pointer) {
115
+ return typeof pointer !== "string" && Object.hasOwn(pointer, "id");
116
+ }
117
+ export function getCoordinateFromAddressPointer(pointer) {
118
+ return `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
119
+ }
120
+ export function getATagFromAddressPointer(pointer) {
121
+ const relay = pointer.relays?.[0];
122
+ const coordinate = getCoordinateFromAddressPointer(pointer);
123
+ return relay ? ["a", coordinate, relay] : ["a", coordinate];
124
+ }
125
+ export function getETagFromEventPointer(pointer) {
126
+ return pointer.relays?.length ? ["e", pointer.id, pointer.relays[0]] : ["e", pointer.id];
127
+ }
@@ -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;
@@ -13,6 +19,7 @@ export type ProfileContent = {
13
19
  lud06?: string;
14
20
  nip05?: string;
15
21
  };
22
+ /** Returns the parsed profile content for a kind 0 event */
16
23
  export declare function getProfileContent(event: NostrEvent): ProfileContent;
17
24
  export declare function getProfileContent(event: NostrEvent, quite: false): ProfileContent;
18
25
  export declare function getProfileContent(event: NostrEvent, quite: true): ProfileContent | Error;
@@ -1,17 +1,17 @@
1
- import { ProfileContent } from "./symbols.js";
1
+ export const ProfileContentSymbol = Symbol.for("profile-content");
2
2
  export function getProfileContent(event, quite = false) {
3
- let cached = event[ProfileContent];
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[ProfileContent] = profile;
10
+ cached = event[ProfileContentSymbol] = profile;
11
11
  }
12
12
  catch (e) {
13
13
  if (e instanceof Error)
14
- cached = event[ProfileContent] = e;
14
+ cached = event[ProfileContentSymbol] = e;
15
15
  }
16
16
  }
17
17
  if (cached === undefined) {