applesauce-content 5.0.0 → 6.2.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.
@@ -0,0 +1,9 @@
1
+ import { ParsedBlossomURI } from "applesauce-common/helpers/blossom";
2
+ import { Link, Nodes } from "mdast";
3
+ import { Transformer } from "unified";
4
+ export interface BlossomMdastLink extends Link {
5
+ type: "link";
6
+ data: ParsedBlossomURI;
7
+ }
8
+ /** Finds and creates BUD-10 `blossom:` URI links in a mdast tree */
9
+ export declare function remarkBlossomURIs(): Transformer<Nodes>;
@@ -0,0 +1,22 @@
1
+ import { parseBlossomURI } from "applesauce-common/helpers/blossom";
2
+ import { Tokens } from "applesauce-core/helpers/regexp";
3
+ import { findAndReplace } from "mdast-util-find-and-replace";
4
+ /** Finds and creates BUD-10 `blossom:` URI links in a mdast tree */
5
+ export function remarkBlossomURIs() {
6
+ return (tree) => {
7
+ findAndReplace(tree, [
8
+ Tokens.blossom,
9
+ (raw) => {
10
+ const parsed = parseBlossomURI(raw);
11
+ if (!parsed)
12
+ return false;
13
+ return {
14
+ type: "link",
15
+ data: parsed,
16
+ url: raw,
17
+ children: [{ type: "text", value: raw }],
18
+ };
19
+ },
20
+ ]);
21
+ };
22
+ }
@@ -0,0 +1,12 @@
1
+ import { type Token } from "@cashu/cashu-ts";
2
+ import { Link, Nodes } from "mdast";
3
+ import { Transformer } from "unified";
4
+ export interface CashuMdastLink extends Link {
5
+ type: "link";
6
+ data: {
7
+ token: Token;
8
+ raw: string;
9
+ };
10
+ }
11
+ /** Finds cashu tokens in a mdast tree and replaces them with link nodes carrying the decoded token */
12
+ export declare function remarkCashuTokens(): Transformer<Nodes>;
@@ -0,0 +1,24 @@
1
+ import { getDecodedToken } from "@cashu/cashu-ts";
2
+ import { Tokens } from "applesauce-core/helpers/regexp";
3
+ import { findAndReplace } from "mdast-util-find-and-replace";
4
+ /** Finds cashu tokens in a mdast tree and replaces them with link nodes carrying the decoded token */
5
+ export function remarkCashuTokens() {
6
+ return (tree) => {
7
+ findAndReplace(tree, [
8
+ Tokens.cashu,
9
+ (_, $1) => {
10
+ try {
11
+ const token = getDecodedToken($1, []);
12
+ return {
13
+ type: "link",
14
+ data: { token, raw: $1 },
15
+ url: "cashu:" + $1,
16
+ children: [{ type: "text", value: $1 }],
17
+ };
18
+ }
19
+ catch (error) { }
20
+ return false;
21
+ },
22
+ ]);
23
+ };
24
+ }
@@ -1 +1,2 @@
1
1
  export * from "./mentions.js";
2
+ export * from "./blossom.js";
@@ -1 +1,2 @@
1
1
  export * from "./mentions.js";
2
+ export * from "./blossom.js";
@@ -13,6 +13,9 @@ export function truncateContent(tree, maxLength = 256) {
13
13
  case "cashu":
14
14
  length += node.raw.length;
15
15
  break;
16
+ case "blossom":
17
+ length += node.raw.length;
18
+ break;
16
19
  case "gallery":
17
20
  length += node.links.reduce((t, l) => t + l.length, 0);
18
21
  break;
@@ -1,5 +1,3 @@
1
- import { type Token } from "@cashu/cashu-ts";
2
- import { type ParsedInvoice } from "applesauce-common/helpers/bolt11";
3
1
  import { type EventTemplate, type NostrEvent } from "applesauce-core/helpers/event";
4
2
  import { type DecodeResult } from "applesauce-core/helpers/pointers";
5
3
  import { type Parent, type Node as UnistNode } from "unist";
@@ -27,16 +25,6 @@ export interface Mention extends Node {
27
25
  decoded: DecodeResult;
28
26
  encoded: string;
29
27
  }
30
- export interface CashuToken extends Node {
31
- type: "cashu";
32
- token: Token;
33
- raw: string;
34
- }
35
- export interface LightningInvoice extends Node {
36
- type: "lightning";
37
- invoice: string;
38
- parsed: ParsedInvoice;
39
- }
40
28
  export interface Hashtag extends Node {
41
29
  type: "hashtag";
42
30
  /** The name as it was written in the event */
@@ -53,15 +41,29 @@ export interface Emoji extends Node {
53
41
  url: string;
54
42
  tag: ["emoji", ...string[]];
55
43
  }
44
+ export interface BlossomURI extends Node {
45
+ type: "blossom";
46
+ /** The original matched text, e.g. `blossom:<hash>.pdf?xs=...` */
47
+ raw: string;
48
+ /** 64 character lowercase hex sha256 hash of the blob */
49
+ sha256: string;
50
+ /** File extension without the leading dot */
51
+ ext: string;
52
+ /** Optional exact blob size in bytes */
53
+ size?: number;
54
+ /** Server hints from repeated `xs` query parameters */
55
+ servers: string[];
56
+ /** Author hex pubkeys from repeated `as` query parameters */
57
+ authors: string[];
58
+ }
56
59
  export interface ContentMap {
57
60
  text: Text;
58
61
  link: Link;
59
62
  mention: Mention;
60
- cashu: CashuToken;
61
- lightning: LightningInvoice;
62
63
  hashtag: Hashtag;
63
64
  emoji: Emoji;
64
65
  gallery: Gallery;
66
+ blossom: BlossomURI;
65
67
  }
66
68
  export type Content = ContentMap[keyof ContentMap];
67
69
  export interface Root extends Parent {
@@ -0,0 +1,4 @@
1
+ import { Transformer } from "unified";
2
+ import { Root } from "../nast/types.js";
3
+ /** Finds and creates BUD-10 blossom URI nodes in the tree */
4
+ export declare function blossomURIs(): Transformer<Root>;
@@ -0,0 +1,23 @@
1
+ import { parseBlossomURI } from "applesauce-common/helpers/blossom";
2
+ import { Tokens } from "applesauce-core/helpers/regexp";
3
+ import { findAndReplace } from "../nast/find-and-replace.js";
4
+ /** Finds and creates BUD-10 blossom URI nodes in the tree */
5
+ export function blossomURIs() {
6
+ return (tree) => {
7
+ findAndReplace(tree, [
8
+ [
9
+ Tokens.blossom,
10
+ (raw) => {
11
+ const parsed = parseBlossomURI(raw);
12
+ if (!parsed)
13
+ return false;
14
+ return {
15
+ type: "blossom",
16
+ raw,
17
+ ...parsed,
18
+ };
19
+ },
20
+ ],
21
+ ]);
22
+ };
23
+ }
@@ -1,4 +1,15 @@
1
+ import { type Token } from "@cashu/cashu-ts";
1
2
  import { Transformer } from "unified";
2
- import { Root } from "../nast/types.js";
3
+ import { Node, Root } from "../nast/types.js";
4
+ export interface CashuToken extends Node {
5
+ type: "cashu";
6
+ token: Token;
7
+ raw: string;
8
+ }
9
+ declare module "../nast/types.js" {
10
+ interface ContentMap {
11
+ cashu: CashuToken;
12
+ }
13
+ }
3
14
  /** Parse cashu tokens from an ATS tree */
4
15
  export declare function cashuTokens(): Transformer<Root>;
@@ -1,6 +1,7 @@
1
1
  import { getDecodedToken } from "@cashu/cashu-ts";
2
2
  import { Tokens } from "applesauce-core/helpers/regexp";
3
3
  import { findAndReplace } from "../nast/find-and-replace.js";
4
+ import { textNoteTransformers } from "./content.js";
4
5
  /** Parse cashu tokens from an ATS tree */
5
6
  export function cashuTokens() {
6
7
  return (tree) => {
@@ -9,7 +10,7 @@ export function cashuTokens() {
9
10
  Tokens.cashu,
10
11
  (_, $1) => {
11
12
  try {
12
- const token = getDecodedToken($1);
13
+ const token = getDecodedToken($1, []);
13
14
  return {
14
15
  type: "cashu",
15
16
  token,
@@ -23,3 +24,9 @@ export function cashuTokens() {
23
24
  ]);
24
25
  };
25
26
  }
27
+ // Register the cashu transformer in the default text-note pipeline as a side
28
+ // effect of importing this module. Consumers opt-in to cashu support by
29
+ // importing `applesauce-content/text/cashu`.
30
+ if (!textNoteTransformers.includes(cashuTokens)) {
31
+ textNoteTransformers.push(cashuTokens);
32
+ }
@@ -1,9 +1,8 @@
1
1
  import { Transformer } from "unified";
2
2
  import { EventTemplate, NostrEvent } from "applesauce-core/helpers/event";
3
3
  import { Root } from "../nast/types.js";
4
- import { galleries } from "./gallery.js";
5
4
  export declare const TextNoteContentSymbol: unique symbol;
6
- export declare const textNoteTransformers: (typeof galleries)[];
5
+ export declare const textNoteTransformers: (() => Transformer<Root>)[];
7
6
  /** Parsed and process a note with custom transformers */
8
7
  export declare function getParsedContent(event: NostrEvent | EventTemplate | string, content?: string, transformers?: (() => Transformer<Root>)[], cacheKey?: symbol | null | undefined): Root;
9
8
  export declare function removeParsedTextContent(event: NostrEvent | EventTemplate): void;
@@ -1,24 +1,25 @@
1
1
  import { unified } from "unified";
2
2
  import { getOrComputeCachedValue } from "applesauce-core/helpers/cache";
3
3
  import { nostrMentions } from "./mentions.js";
4
- import { cashuTokens } from "./cashu.js";
5
4
  import { emojis } from "./emoji.js";
6
5
  import { createEventContentTree } from "./parser.js";
7
6
  import { hashtags } from "./hashtag.js";
8
7
  import { galleries } from "./gallery.js";
9
- import { lightningInvoices } from "./lightning.js";
10
8
  import { eolMetadata } from "../nast/eol-metadata.js";
11
9
  import { links } from "./links.js";
10
+ import { blossomURIs } from "./blossom.js";
12
11
  export const TextNoteContentSymbol = Symbol.for("text-note-content");
13
- // default kind 1 transformers
12
+ // default kind 1 transformers. Optional transformers (like `cashuTokens` and
13
+ // `lightningInvoices`) opt in by appending to this array when their module is
14
+ // imported — see `applesauce-content/text/cashu` and
15
+ // `applesauce-content/text/lightning`.
14
16
  export const textNoteTransformers = [
17
+ blossomURIs,
15
18
  links,
16
19
  nostrMentions,
17
20
  galleries,
18
21
  emojis,
19
22
  hashtags,
20
- lightningInvoices,
21
- cashuTokens,
22
23
  eolMetadata,
23
24
  ];
24
25
  /** Parsed and process a note with custom transformers */
@@ -1,4 +1,8 @@
1
1
  import { Transformer } from "unified";
2
2
  import { Root } from "../nast/types.js";
3
+ export interface GalleriesOptions {
4
+ /** When true, adjacent `blossom:` image URIs are clustered alongside HTTP image links. Defaults to false. */
5
+ includeBlossom?: boolean;
6
+ }
3
7
  /** Group images into galleries in an ATS tree */
4
- export declare function galleries(types?: string[]): Transformer<Root>;
8
+ export declare function galleries(types?: string[], options?: GalleriesOptions): Transformer<Root>;
@@ -1,21 +1,23 @@
1
1
  import { convertToUrl, getURLFilename, IMAGE_EXT } from "applesauce-core/helpers/url";
2
2
  /** Group images into galleries in an ATS tree */
3
- export function galleries(types = IMAGE_EXT) {
3
+ export function galleries(types = IMAGE_EXT, options = {}) {
4
+ const { includeBlossom = false } = options;
4
5
  return (tree) => {
5
- let links = [];
6
+ let items = [];
7
+ const getItemHref = (item) => (item.type === "link" ? item.href : item.raw);
6
8
  const commit = (index) => {
7
9
  // only create a gallery if there are more than a single image
8
- if (links.length > 1) {
9
- const start = tree.children.indexOf(links[0]);
10
- const end = tree.children.indexOf(links[links.length - 1]);
10
+ if (items.length > 1) {
11
+ const start = tree.children.indexOf(items[0]);
12
+ const end = tree.children.indexOf(items[items.length - 1]);
11
13
  // replace all nodes with a gallery
12
- tree.children.splice(start, 1 + end - start, { type: "gallery", links: links.map((l) => l.href) });
13
- links = [];
14
+ tree.children.splice(start, 1 + end - start, { type: "gallery", links: items.map(getItemHref) });
15
+ items = [];
14
16
  // return new cursor
15
17
  return end - 1;
16
18
  }
17
19
  else {
18
- links = [];
20
+ items = [];
19
21
  return index;
20
22
  }
21
23
  };
@@ -26,13 +28,21 @@ export function galleries(types = IMAGE_EXT) {
26
28
  const url = convertToUrl(node.href);
27
29
  const filename = getURLFilename(url);
28
30
  if (filename && types.some((ext) => filename.endsWith(ext))) {
29
- links.push(node);
31
+ items.push(node);
30
32
  }
31
33
  else {
32
34
  i = commit(i);
33
35
  }
34
36
  }
35
- else if (node.type === "text" && links.length > 0) {
37
+ else if (node.type === "blossom" && includeBlossom) {
38
+ if (types.some((ext) => ext === `.${node.ext.toLowerCase()}`)) {
39
+ items.push(node);
40
+ }
41
+ else {
42
+ i = commit(i);
43
+ }
44
+ }
45
+ else if (node.type === "text" && items.length > 0) {
36
46
  const isEmpty = node.value === "\n" || !node.value.match(/[^\s]/g);
37
47
  if (!isEmpty)
38
48
  i = commit(i);
@@ -0,0 +1,11 @@
1
+ import { type FileMetadataFields } from "applesauce-common/helpers/file-metadata";
2
+ import { type Transformer } from "unified";
3
+ import { Root } from "../nast/types.js";
4
+ declare module "../nast/types.js" {
5
+ interface Link {
6
+ /** File metadata from a matching NIP-92 `imeta` tag on the event */
7
+ metadata?: FileMetadataFields;
8
+ }
9
+ }
10
+ /** Hydrates link nodes with NIP-92 file metadata from matching `imeta` tags on the event */
11
+ export declare function imetaLinks(): Transformer<Root>;
@@ -0,0 +1,40 @@
1
+ import { getMediaAttachments } from "applesauce-common/helpers/file-metadata";
2
+ import { textNoteTransformers } from "./content.js";
3
+ /** Hydrates link nodes with NIP-92 file metadata from matching `imeta` tags on the event */
4
+ export function imetaLinks() {
5
+ return (tree) => {
6
+ const event = tree.event;
7
+ if (!event || !event.tags || event.tags.length === 0)
8
+ return;
9
+ const attachments = getMediaAttachments(event);
10
+ if (attachments.length === 0)
11
+ return;
12
+ // Build a normalized URL -> metadata map so lookups match `link.href` exactly
13
+ const byUrl = new Map();
14
+ for (const attachment of attachments) {
15
+ if (!attachment.url)
16
+ continue;
17
+ try {
18
+ byUrl.set(new URL(attachment.url).toString(), attachment);
19
+ }
20
+ catch {
21
+ // ignore invalid URLs
22
+ }
23
+ }
24
+ if (byUrl.size === 0)
25
+ return;
26
+ for (const node of tree.children) {
27
+ if (node.type !== "link")
28
+ continue;
29
+ const metadata = byUrl.get(node.href);
30
+ if (metadata)
31
+ node.metadata = metadata;
32
+ }
33
+ };
34
+ }
35
+ // Register the imeta transformer in the default text-note pipeline as a side
36
+ // effect of importing this module. Consumers opt-in to imeta link hydration
37
+ // by importing `applesauce-content/text/imeta`.
38
+ if (!textNoteTransformers.includes(imetaLinks)) {
39
+ textNoteTransformers.push(imetaLinks);
40
+ }
@@ -1,9 +1,9 @@
1
+ export * from "./blossom.js";
1
2
  export * from "./content.js";
3
+ export * from "./emoji.js";
4
+ export * from "./gallery.js";
5
+ export * from "./hashtag.js";
6
+ export * from "./imeta.js";
2
7
  export * from "./links.js";
3
8
  export * from "./mentions.js";
4
- export * from "./cashu.js";
5
- export * from "./emoji.js";
6
9
  export * from "./parser.js";
7
- export * from "./hashtag.js";
8
- export * from "./gallery.js";
9
- export * from "./lightning.js";
@@ -1,9 +1,9 @@
1
+ export * from "./blossom.js";
1
2
  export * from "./content.js";
3
+ export * from "./emoji.js";
4
+ export * from "./gallery.js";
5
+ export * from "./hashtag.js";
6
+ export * from "./imeta.js";
2
7
  export * from "./links.js";
3
8
  export * from "./mentions.js";
4
- export * from "./cashu.js";
5
- export * from "./emoji.js";
6
9
  export * from "./parser.js";
7
- export * from "./hashtag.js";
8
- export * from "./gallery.js";
9
- export * from "./lightning.js";
@@ -1,4 +1,15 @@
1
+ import { type ParsedInvoice } from "applesauce-common/helpers/bolt11";
1
2
  import { type Transformer } from "unified";
2
- import { Root } from "../nast/types.js";
3
+ import { Node, Root } from "../nast/types.js";
4
+ export interface LightningInvoice extends Node {
5
+ type: "lightning";
6
+ invoice: string;
7
+ parsed: ParsedInvoice;
8
+ }
9
+ declare module "../nast/types.js" {
10
+ interface ContentMap {
11
+ lightning: LightningInvoice;
12
+ }
13
+ }
3
14
  /** Finds and creates lightning invoice nodes in the tree */
4
15
  export declare function lightningInvoices(): Transformer<Root>;
@@ -1,6 +1,7 @@
1
1
  import { parseBolt11 } from "applesauce-common/helpers/bolt11";
2
2
  import { Tokens } from "applesauce-core/helpers/regexp";
3
3
  import { findAndReplace } from "../nast/find-and-replace.js";
4
+ import { textNoteTransformers } from "./content.js";
4
5
  /** Finds and creates lightning invoice nodes in the tree */
5
6
  export function lightningInvoices() {
6
7
  return (tree) => {
@@ -24,3 +25,9 @@ export function lightningInvoices() {
24
25
  ]);
25
26
  };
26
27
  }
28
+ // Register the lightning transformer in the default text-note pipeline as a
29
+ // side effect of importing this module. Consumers opt-in to lightning invoice
30
+ // parsing by importing `applesauce-content/text/lightning`.
31
+ if (!textNoteTransformers.includes(lightningInvoices)) {
32
+ textNoteTransformers.push(lightningInvoices);
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-content",
3
- "version": "5.0.0",
3
+ "version": "6.2.0",
4
4
  "description": "Unified plugins for processing event content",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,19 +39,28 @@
39
39
  "require": "./dist/markdown/index.js",
40
40
  "types": "./dist/markdown/index.d.ts"
41
41
  },
42
+ "./markdown/*": {
43
+ "import": "./dist/markdown/*.js",
44
+ "require": "./dist/markdown/*.js",
45
+ "types": "./dist/markdown/*.d.ts"
46
+ },
42
47
  "./text": {
43
48
  "import": "./dist/text/index.js",
44
49
  "require": "./dist/text/index.js",
45
50
  "types": "./dist/text/index.d.ts"
51
+ },
52
+ "./text/*": {
53
+ "import": "./dist/text/*.js",
54
+ "require": "./dist/text/*.js",
55
+ "types": "./dist/text/*.d.ts"
46
56
  }
47
57
  },
48
58
  "dependencies": {
49
- "@cashu/cashu-ts": "^3.1.1",
50
59
  "@types/hast": "^3.0.4",
51
60
  "@types/mdast": "^4.0.4",
52
61
  "@types/unist": "^3.0.3",
53
- "applesauce-common": "^5.0.0",
54
- "applesauce-core": "^5.0.0",
62
+ "applesauce-common": "^6.2.0",
63
+ "applesauce-core": "^6.2.0",
55
64
  "mdast-util-find-and-replace": "^3.0.2",
56
65
  "remark": "^15.0.1",
57
66
  "remark-parse": "^11.0.0",
@@ -59,11 +68,20 @@
59
68
  "unist-util-visit-parents": "^6.0.1"
60
69
  },
61
70
  "devDependencies": {
62
- "applesauce-signers": "^5.0.0",
71
+ "@cashu/cashu-ts": "^4.5.1",
72
+ "applesauce-signers": "^6.2.0",
63
73
  "rimraf": "^6.0.1",
64
74
  "typescript": "^5.8.3",
65
75
  "vitest": "^4.0.15"
66
76
  },
77
+ "peerDependencies": {
78
+ "@cashu/cashu-ts": "^4.5.1"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "@cashu/cashu-ts": {
82
+ "optional": true
83
+ }
84
+ },
67
85
  "funding": {
68
86
  "type": "lightning",
69
87
  "url": "lightning:nostrudel@geyser.fund"