Files
boris/node_modules/applesauce-factory/dist/operations/content.js
Gigi 1b8c276529 chore: upgrade applesauce packages to v4.0.0
- Update all applesauce packages from 3.1.0 to 4.0.0
- Add applesauce-factory dependency
- Version 4.0.0 includes app-data helpers needed for NIP-78
2025-10-05 02:35:28 +01:00

135 lines
5.4 KiB
JavaScript

import { Expressions } from "applesauce-content/helpers";
import { EncryptedContentSymbol, getEncryptedContentEncryptionMethods, getPubkeyFromDecodeResult, } from "applesauce-core/helpers";
import { ensureProfilePointerTag } from "../helpers/common-tags.js";
import { getContentPointers } from "../helpers/content.js";
import { eventPipe, skip } from "../helpers/pipeline.js";
import { ensureQuoteEventPointerTag } from "../helpers/quote.js";
import { ensureNamedValueTag } from "../helpers/tag.js";
/** Override the event content */
export function setContent(content) {
return async (draft) => {
draft = { ...draft, content };
Reflect.deleteProperty(draft, EncryptedContentSymbol);
return draft;
};
}
/** Replaces any `@npub` or bare npub mentions with nostr: prefix */
export function repairNostrLinks() {
return (draft) => ({
...draft,
content: draft.content.replaceAll(/(?<=^|\s)(?:@)?((?:npub|note|nprofile|nevent|naddr)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58})/gi, "nostr:$1"),
});
}
/** "p" tag any pubkey mentioned in the content using nostr: links */
export function tagPubkeyMentions() {
return (draft) => {
let tags = Array.from(draft.tags);
const mentions = getContentPointers(draft.content);
for (const mention of mentions) {
const pubkey = getPubkeyFromDecodeResult(mention);
if (pubkey)
tags = ensureProfilePointerTag(tags, mention.type === "nprofile" ? mention.data : { pubkey });
}
return { ...draft, tags };
};
}
/** Sets the NIP-36 content-warning tag */
export function setContentWarning(warning) {
return (draft) => {
let tags = Array.from(draft.tags);
// remove existing content warning
tags = tags.filter((t) => t[0] !== "content-warning");
if (typeof warning === "string")
tags.push(["content-warning", warning]);
else if (warning === true)
tags.push(["content-warning"]);
return { ...draft, tags };
};
}
/** Include "q" quote tags for any nostr event mentioned in the content */
export function includeQuoteTags() {
return (draft) => {
let tags = Array.from(draft.tags);
const mentions = getContentPointers(draft.content);
for (const mention of mentions) {
switch (mention.type) {
case "note":
tags = ensureQuoteEventPointerTag(tags, { id: mention.data });
break;
case "nevent":
tags = ensureQuoteEventPointerTag(tags, mention.data);
break;
}
}
return { ...draft, tags };
};
}
/** Adds "t" tags for every #hashtag in the content */
export function includeContentHashtags() {
return (draft) => {
let tags = Array.from(draft.tags);
// create tags for all occurrences of #hashtag
const matches = draft.content.matchAll(Expressions.hashtag);
for (const [_, hashtag] of matches) {
const lower = hashtag.toLocaleLowerCase();
tags = ensureNamedValueTag(tags, ["t", lower]);
}
return { ...draft, tags };
};
}
/** Adds "emoji" tags for NIP-30 emojis used in the content */
export function includeEmojis(emojis) {
return (draft, ctx) => {
const all = [...(ctx.emojis ?? []), ...(emojis ?? [])];
const tags = Array.from(draft.tags);
// create tags for all occurrences of #hashtag
const matches = draft.content.matchAll(Expressions.emoji);
for (const [_, name] of matches) {
const emoji = all.find((e) => e.shortcode === name);
if (emoji?.url) {
tags.push(["emoji", emoji.shortcode, emoji.url]);
}
}
return { ...draft, tags };
};
}
/** Sets the text for a short text note and include hashtags and mentions */
export function setShortTextContent(content, options) {
return eventPipe(
// set text content
setContent(content),
// fix @ mentions
repairNostrLinks(),
// include "p" tags for pubkeys mentioned
tagPubkeyMentions(),
// include event "q" tags
includeQuoteTags(),
// include "t" tags for hashtags
includeContentHashtags(),
// include "emoji" tags
options?.emojis ? includeEmojis(options.emojis) : skip(),
// set "content-warning" tag
options?.contentWarning !== undefined ? setContentWarning(options.contentWarning) : skip());
}
/** Sets the content to be encrypted to the pubkey with optional override method */
export function setEncryptedContent(pubkey, content, override) {
return async (draft, { signer }) => {
if (!signer)
throw new Error("Signer required for encrypted content");
// Set method based on kind if not provided
const methods = getEncryptedContentEncryptionMethods(draft.kind, signer, override);
// add the plaintext content on the draft so it can be carried forward
const encrypted = await methods.encrypt(pubkey, content);
return { ...draft, content: encrypted, [EncryptedContentSymbol]: content };
};
}
/** Sets the hidden content on an event */
export function setHiddenContent(content, override) {
return async (draft, ctx) => {
if (!ctx.signer)
throw new Error("Signer required for encrypted content");
const pubkey = await ctx.signer.getPublicKey();
return setEncryptedContent(pubkey, content, override)(draft, ctx);
};
}