applesauce-core 0.0.0-next-20241207164640 → 0.0.0-next-20241210191750

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.
@@ -0,0 +1,48 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { ExternalPointer, ExternalIdentifiers } from "./external-id.js";
3
+ export declare const COMMENT_KIND = 1111;
4
+ type CommentEventPointer = {
5
+ id: string;
6
+ kind: number;
7
+ pubkey?: string;
8
+ relay?: string;
9
+ };
10
+ type CommentAddressPointer = {
11
+ id?: string;
12
+ kind: number;
13
+ pubkey: string;
14
+ identifier: string;
15
+ relay?: string;
16
+ };
17
+ type CommentExternalPointer = ExternalPointer<keyof ExternalIdentifiers>;
18
+ export type CommentPointer = CommentEventPointer | CommentAddressPointer | CommentExternalPointer;
19
+ export declare const CommentRootPointerSymbol: unique symbol;
20
+ export declare const CommentReplyPointerSymbol: unique symbol;
21
+ /**
22
+ * Gets the EventPointer from an array of tags
23
+ * @throws
24
+ */
25
+ export declare function getCommentEventPointer(tags: string[][], root?: boolean): CommentEventPointer | null;
26
+ /**
27
+ * Gets the AddressPointer from an array of tags
28
+ * @throws
29
+ */
30
+ export declare function getCommentAddressPointer(tags: string[][], root?: boolean): CommentAddressPointer | null;
31
+ /**
32
+ * Gets the ExternalPointer from an array of tags
33
+ * @throws
34
+ */
35
+ export declare function getCommentExternalPointer(tags: string[][], root?: boolean): CommentExternalPointer | null;
36
+ /**
37
+ * Returns the root pointer for a comment
38
+ * @throws
39
+ */
40
+ export declare function getCommentRootPointer(comment: NostrEvent): CommentPointer | null;
41
+ /**
42
+ * Returns the reply pointer for a comment
43
+ * @throws
44
+ */
45
+ export declare function getCommentReplyPointer(comment: NostrEvent): CommentPointer | null;
46
+ export declare function isCommentEventPointer(pointer: any): pointer is CommentEventPointer;
47
+ export declare function isCommentAddressPointer(pointer: any): pointer is CommentAddressPointer;
48
+ export {};
@@ -0,0 +1,115 @@
1
+ import { getExternalPointerFromTag } from "./external-id.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
4
+ export const COMMENT_KIND = 1111;
5
+ export const CommentRootPointerSymbol = Symbol.for("comment-root-pointer");
6
+ export const CommentReplyPointerSymbol = Symbol.for("comment-reply-pointer");
7
+ /**
8
+ * Gets the EventPointer from an array of tags
9
+ * @throws
10
+ */
11
+ export function getCommentEventPointer(tags, root = false) {
12
+ const tag = tags.find((t) => t[0] === (root ? "E" : "e"));
13
+ const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
14
+ if (tag) {
15
+ if (!kind)
16
+ throw new Error("Missing kind tag");
17
+ const eventPointer = getEventPointerFromTag(tag);
18
+ return {
19
+ id: eventPointer.id,
20
+ kind: parseInt(kind),
21
+ pubkey: eventPointer.author,
22
+ relay: eventPointer.relays?.[0],
23
+ };
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Gets the AddressPointer from an array of tags
29
+ * @throws
30
+ */
31
+ export function getCommentAddressPointer(tags, root = false) {
32
+ const tag = tags.find((t) => t[0] === (root ? "A" : "a"));
33
+ const id = tags.find((t) => t[0] === (root ? "E" : "e"))?.[1];
34
+ const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
35
+ if (tag) {
36
+ if (!kind)
37
+ throw new Error("Missing kind tag");
38
+ const pointer = {
39
+ id,
40
+ ...getAddressPointerFromTag(tag),
41
+ kind: parseInt(kind),
42
+ };
43
+ return pointer;
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Gets the ExternalPointer from an array of tags
49
+ * @throws
50
+ */
51
+ export function getCommentExternalPointer(tags, root = false) {
52
+ const tag = tags.find((t) => t[0] === (root ? "I" : "i"));
53
+ const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
54
+ if (tag) {
55
+ if (!kind)
56
+ throw new Error("Missing kind tag");
57
+ const pointer = getExternalPointerFromTag(tag);
58
+ return pointer;
59
+ }
60
+ return null;
61
+ }
62
+ /**
63
+ * Returns the root pointer for a comment
64
+ * @throws
65
+ */
66
+ export function getCommentRootPointer(comment) {
67
+ if (comment.kind !== COMMENT_KIND)
68
+ throw new Error("Event is not a comment");
69
+ return getOrComputeCachedValue(comment, CommentRootPointerSymbol, () => {
70
+ // check for address pointer first since it can also have E tags
71
+ const A = getCommentAddressPointer(comment.tags, true);
72
+ if (A)
73
+ return A;
74
+ const E = getCommentEventPointer(comment.tags, true);
75
+ if (E)
76
+ return E;
77
+ const I = getCommentExternalPointer(comment.tags, true);
78
+ if (I)
79
+ return I;
80
+ return null;
81
+ });
82
+ }
83
+ /**
84
+ * Returns the reply pointer for a comment
85
+ * @throws
86
+ */
87
+ export function getCommentReplyPointer(comment) {
88
+ if (comment.kind !== COMMENT_KIND)
89
+ throw new Error("Event is not a comment");
90
+ return getOrComputeCachedValue(comment, CommentReplyPointerSymbol, () => {
91
+ // check for address pointer first since it can also have E tags
92
+ const A = getCommentAddressPointer(comment.tags, false);
93
+ if (A)
94
+ return A;
95
+ const E = getCommentEventPointer(comment.tags, false);
96
+ if (E)
97
+ return E;
98
+ const I = getCommentExternalPointer(comment.tags, false);
99
+ if (I)
100
+ return I;
101
+ return null;
102
+ });
103
+ }
104
+ export function isCommentEventPointer(pointer) {
105
+ return (Reflect.has(pointer, "id") &&
106
+ Reflect.has(pointer, "kind") &&
107
+ !Reflect.has(pointer, "identifier") &&
108
+ typeof pointer.kind === "number");
109
+ }
110
+ export function isCommentAddressPointer(pointer) {
111
+ return (Reflect.has(pointer, "identifier") &&
112
+ Reflect.has(pointer, "pubkey") &&
113
+ Reflect.has(pointer, "kind") &&
114
+ typeof pointer.kind === "number");
115
+ }
@@ -3,8 +3,9 @@ import { EventTemplate, NostrEvent } from "nostr-tools";
3
3
  export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string] | undefined;
4
4
  /** Returns the name of a NIP-30 emoji pack */
5
5
  export declare function getPackName(pack: NostrEvent): string | undefined;
6
- /** Returns an array of emojis from a NIP-30 emoji pack */
7
- export declare function getEmojis(pack: NostrEvent): {
6
+ export type Emoji = {
8
7
  name: string;
9
8
  url: string;
10
- }[];
9
+ };
10
+ /** Returns an array of emojis from a NIP-30 emoji pack */
11
+ export declare function getEmojis(pack: NostrEvent): Emoji[];
@@ -0,0 +1,29 @@
1
+ export type ExternalIdentifiers = {
2
+ "#": `#${string}`;
3
+ geo: `geo:${string}`;
4
+ isbn: `isbn:${string}`;
5
+ "podcast:guid": `podcast:guid:${string}`;
6
+ "podcast:item:guid": `podcast:item:guid:${string}`;
7
+ "podcast:publisher:guid": `podcast:publisher:guid:${string}`;
8
+ isan: `isan:${string}`;
9
+ doi: `doi:${string}`;
10
+ };
11
+ export type ExternalPointer<Prefix extends keyof ExternalIdentifiers> = {
12
+ kind: Prefix;
13
+ identifier: ExternalIdentifiers[Prefix];
14
+ };
15
+ export type ParseResult = {
16
+ [P in keyof ExternalIdentifiers]: ExternalPointer<P>;
17
+ }[keyof ExternalIdentifiers];
18
+ /**
19
+ * Parses a NIP-73 external identifier
20
+ * @throws
21
+ */
22
+ export declare function parseExternalPointer<Prefix extends keyof ExternalIdentifiers>(identifier: `${Prefix}1${string}`): ExternalPointer<Prefix>;
23
+ export declare function parseExternalPointer(identifier: string): ParseResult;
24
+ /**
25
+ * Gets an ExternalPointer for a "i" tag
26
+ * @throws
27
+ */
28
+ export declare function getExternalPointerFromTag<Prefix extends keyof ExternalIdentifiers>(tag: string[]): ExternalPointer<Prefix>;
29
+ export declare function getExternalPointerFromTag(tag: string[]): ParseResult;
@@ -0,0 +1,20 @@
1
+ export function parseExternalPointer(identifier) {
2
+ if (identifier.startsWith("#"))
3
+ return { kind: "#", identifier: identifier };
4
+ if (identifier.startsWith("geo:"))
5
+ return { kind: "geo", identifier: identifier };
6
+ if (identifier.startsWith("podcast:guid:"))
7
+ return { kind: "podcast:guid", identifier: identifier };
8
+ if (identifier.startsWith("podcast:item:guid:"))
9
+ return { kind: "podcast:item:guid", identifier: identifier };
10
+ if (identifier.startsWith("podcast:publisher:guid:"))
11
+ return { kind: "podcast:publisher:guid", identifier: identifier };
12
+ if (identifier.startsWith("isan:"))
13
+ return { kind: "isan", identifier: identifier };
14
+ if (identifier.startsWith("doi:"))
15
+ return { kind: "doi", identifier: identifier };
16
+ throw new Error("Failed to parse external identifier");
17
+ }
18
+ export function getExternalPointerFromTag(tag) {
19
+ return parseExternalPointer(tag[1]);
20
+ }
@@ -1,19 +1,23 @@
1
- export * from "./profile.js";
2
- export * from "./relays.js";
1
+ export * from "./bolt11.js";
2
+ export * from "./cache.js";
3
+ export * from "./comment.js";
4
+ export * from "./delete.js";
5
+ export * from "./emoji.js";
3
6
  export * from "./event.js";
7
+ export * from "./external-id.js";
4
8
  export * from "./filter.js";
9
+ export * from "./hashtag.js";
10
+ export * from "./hidden-tags.js";
11
+ export * from "./lnurl.js";
12
+ export * from "./lru.js";
5
13
  export * from "./mailboxes.js";
6
- export * from "./threading.js";
14
+ export * from "./media-attachment.js";
7
15
  export * from "./pointers.js";
16
+ export * from "./profile.js";
17
+ export * from "./relays.js";
8
18
  export * from "./string.js";
9
- export * from "./time.js";
10
19
  export * from "./tags.js";
11
- export * from "./emoji.js";
12
- export * from "./lru.js";
13
- export * from "./hashtag.js";
20
+ export * from "./threading.js";
21
+ export * from "./time.js";
14
22
  export * from "./url.js";
15
23
  export * from "./zap.js";
16
- export * from "./hidden-tags.js";
17
- export * from "./bolt11.js";
18
- export * from "./lnurl.js";
19
- export * from "./delete.js";
@@ -1,19 +1,23 @@
1
- export * from "./profile.js";
2
- export * from "./relays.js";
1
+ export * from "./bolt11.js";
2
+ export * from "./cache.js";
3
+ export * from "./comment.js";
4
+ export * from "./delete.js";
5
+ export * from "./emoji.js";
3
6
  export * from "./event.js";
7
+ export * from "./external-id.js";
4
8
  export * from "./filter.js";
9
+ export * from "./hashtag.js";
10
+ export * from "./hidden-tags.js";
11
+ export * from "./lnurl.js";
12
+ export * from "./lru.js";
5
13
  export * from "./mailboxes.js";
6
- export * from "./threading.js";
14
+ export * from "./media-attachment.js";
7
15
  export * from "./pointers.js";
16
+ export * from "./profile.js";
17
+ export * from "./relays.js";
8
18
  export * from "./string.js";
9
- export * from "./time.js";
10
19
  export * from "./tags.js";
11
- export * from "./emoji.js";
12
- export * from "./lru.js";
13
- export * from "./hashtag.js";
20
+ export * from "./threading.js";
21
+ export * from "./time.js";
14
22
  export * from "./url.js";
15
23
  export * from "./zap.js";
16
- export * from "./hidden-tags.js";
17
- export * from "./bolt11.js";
18
- export * from "./lnurl.js";
19
- export * from "./delete.js";
@@ -0,0 +1,33 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export type MediaAttachment = {
3
+ /** URL of the file */
4
+ url: string;
5
+ /** mime type */
6
+ type?: string;
7
+ /** sha256 hash of the file */
8
+ sha256?: string;
9
+ /** size of the file in bytes */
10
+ size?: number;
11
+ /** size of file in pixels in the form <width>x<height> */
12
+ dimensions?: string;
13
+ /** magnet */
14
+ magnet?: string;
15
+ /** torrent infohash */
16
+ infohash?: string;
17
+ /** URL to a thumbnail */
18
+ thumb?: string;
19
+ /** URL to a preview image with the same dimensions */
20
+ image?: string;
21
+ /** summary */
22
+ summary?: string;
23
+ /** description for accessability */
24
+ alt?: string;
25
+ };
26
+ /**
27
+ * Parses a imeta tag into a {@link MediaAttachment}
28
+ * @throws
29
+ */
30
+ export declare function parseMediaAttachmentTag(tag: string[]): MediaAttachment;
31
+ export declare const MediaAttachmentsSymbol: unique symbol;
32
+ /** Gets all the media attachments on an event */
33
+ export declare function getMediaAttachments(event: NostrEvent): MediaAttachment[];
@@ -0,0 +1,60 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ /**
3
+ * Parses a imeta tag into a {@link MediaAttachment}
4
+ * @throws
5
+ */
6
+ export function parseMediaAttachmentTag(tag) {
7
+ const parts = tag.slice(1);
8
+ const fields = {};
9
+ for (const part of parts) {
10
+ const match = part.match(/^(.+?)\s(.+)$/);
11
+ if (match) {
12
+ const [_, name, value] = match;
13
+ fields[name] = value;
14
+ }
15
+ }
16
+ if (!fields.url)
17
+ throw new Error("Missing required url in attachment");
18
+ const attachment = { url: fields.url };
19
+ // parse size
20
+ if (fields.size)
21
+ attachment.size = parseInt(fields.size);
22
+ // copy optional fields
23
+ if (fields.m)
24
+ attachment.type = fields.m;
25
+ if (fields.x)
26
+ attachment.sha256 = fields.x;
27
+ if (fields.dim)
28
+ attachment.dimensions = fields.dim;
29
+ if (fields.magnet)
30
+ attachment.magnet = fields.magnet;
31
+ if (fields.i)
32
+ attachment.infohash = fields.i;
33
+ if (fields.thumb)
34
+ attachment.thumb = fields.thumb;
35
+ if (fields.image)
36
+ attachment.image = fields.image;
37
+ if (fields.summary)
38
+ attachment.summary = fields.summary;
39
+ if (fields.alt)
40
+ attachment.alt = fields.alt;
41
+ return attachment;
42
+ }
43
+ export const MediaAttachmentsSymbol = Symbol.for("media-attachments");
44
+ /** Gets all the media attachments on an event */
45
+ export function getMediaAttachments(event) {
46
+ return getOrComputeCachedValue(event, MediaAttachmentsSymbol, () => {
47
+ return event.tags
48
+ .filter((t) => t[0] === "imeta")
49
+ .map((tag) => {
50
+ try {
51
+ return parseMediaAttachmentTag(tag);
52
+ }
53
+ catch (error) {
54
+ // ignore invalid attachments
55
+ return undefined;
56
+ }
57
+ })
58
+ .filter((a) => !!a);
59
+ });
60
+ }
@@ -72,9 +72,10 @@ export function getEventPointerFromTag(tag) {
72
72
  let pointer = { id: tag[1] };
73
73
  if (tag[2])
74
74
  pointer.relays = safeRelayUrls([tag[2]]);
75
- // get author from NIP-18 quote tags
76
- if (tag[0] === "q" && tag[3] && tag[3].length === 64)
75
+ // get author from NIP-18 quote tags and nip-22 comments
76
+ if ((tag[0] === "q" || tag[0] === "e" || tag[0] === "E") && tag[3] && tag[3].length === 64) {
77
77
  pointer.author = tag[3];
78
+ }
78
79
  return pointer;
79
80
  }
80
81
  /** @throws */
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Query } from "../query-store/index.js";
3
+ /** Returns all NIP-22 comment replies for the event */
4
+ export declare function CommentsQuery(parent: NostrEvent): Query<NostrEvent[]>;
@@ -0,0 +1,14 @@
1
+ import { COMMENT_KIND, getEventUID } from "../helpers/index.js";
2
+ import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
3
+ /** Returns all NIP-22 comment replies for the event */
4
+ export function CommentsQuery(parent) {
5
+ return {
6
+ key: `${getEventUID(parent)}-comments`,
7
+ run: (events) => {
8
+ const filter = { kinds: [COMMENT_KIND], "#e": [parent.id] };
9
+ if (isParameterizedReplaceableKind(parent.kind))
10
+ filter["#a"] = [getEventUID(parent)];
11
+ return events.timeline(filter);
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Query } from "../query-store/index.js";
3
+ /** Returns all NIP-22 comment replies for the event */
4
+ export declare function CommentsQuery(parent: NostrEvent): Query<NostrEvent[]>;
@@ -0,0 +1,14 @@
1
+ import { COMMENT_KIND, getEventUID } from "../helpers/index.js";
2
+ import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
3
+ /** Returns all NIP-22 comment replies for the event */
4
+ export function CommentsQuery(parent) {
5
+ return {
6
+ key: `${getEventUID(parent)}-comments`,
7
+ run: (events) => {
8
+ const filter = { kinds: [COMMENT_KIND], "#e": [parent.id] };
9
+ if (isParameterizedReplaceableKind(parent.kind))
10
+ filter["#a"] = [getEventUID(parent)];
11
+ return events.timeline(filter);
12
+ },
13
+ };
14
+ }
@@ -1,6 +1,7 @@
1
- export * from "./simple.js";
2
- export * from "./profile.js";
1
+ export * from "./comments.js";
3
2
  export * from "./mailboxes.js";
3
+ export * from "./profile.js";
4
4
  export * from "./reactions.js";
5
+ export * from "./simple.js";
5
6
  export * from "./thread.js";
6
7
  export * from "./zaps.js";
@@ -1,6 +1,7 @@
1
- export * from "./simple.js";
2
- export * from "./profile.js";
1
+ export * from "./comments.js";
3
2
  export * from "./mailboxes.js";
3
+ export * from "./profile.js";
4
4
  export * from "./reactions.js";
5
+ export * from "./simple.js";
5
6
  export * from "./thread.js";
6
7
  export * from "./zaps.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.0.0-next-20241207164640",
3
+ "version": "0.0.0-next-20241210191750",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",