113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import type { EventTemplate } from "nostr-tools/core";
|
|
import type { BlobDescriptor } from "../blossom/client";
|
|
import type { BlossomFrontMatter } from "../blossom/frontmatter";
|
|
|
|
/** NIP-94 file metadata event kind. */
|
|
export const FILE_METADATA_KIND = 1063;
|
|
|
|
/** Input for building a NIP-94 file-metadata (kind 1063) event. */
|
|
export interface FileMetadataInput {
|
|
/** Blob descriptor returned by the Blossom server (BUD-02/BUD-08). */
|
|
descriptor: BlobDescriptor;
|
|
/** Front-matter-derived metadata (title, summary, author, …). */
|
|
meta: BlossomFrontMatter;
|
|
/** First-publish timestamp (unix seconds). Reused on updates. */
|
|
publishedAt: number;
|
|
/**
|
|
* Override for the `m` tag (the file's semantic MIME type).
|
|
*
|
|
* Markdown blobs are uploaded as `text/plain` so browsers render them
|
|
* inline (no mainstream browser displays `text/markdown`, and
|
|
* `X-Content-Type-Options: nosniff` forbids guessing). The server then
|
|
* reports `type: text/plain` in the descriptor. This override keeps the
|
|
* Nostr `m` tag advertising the true semantic type (`text/markdown`) so
|
|
* clients that render markdown can still detect it.
|
|
*/
|
|
mimeType?: string;
|
|
}
|
|
|
|
/**
|
|
* Build an unsigned NIP-94 file-metadata event (kind 1063) from a Blossom blob
|
|
* descriptor and front-matter metadata.
|
|
*
|
|
* Tag precedence:
|
|
* - The server-returned `nip94` tags (BUD-08) provide the canonical `url`,
|
|
* `m`, `x`, and `size` tags. If absent, they are derived from the descriptor.
|
|
* - Descriptive tags (`title`, `summary`, `image`, `author`, `language`,
|
|
* `year`, `published_at`, `t`, `alt`) are added from the front-matter.
|
|
*
|
|
* The `content` field carries the front-matter `summary` (empty string if
|
|
* absent) so clients that only render `content` still show a description.
|
|
*/
|
|
export function buildFileMetadataEvent(input: FileMetadataInput): EventTemplate {
|
|
const { descriptor, meta, publishedAt } = input;
|
|
|
|
// Start from server-returned nip94 tags if present; otherwise synthesize the
|
|
// core tags from the descriptor.
|
|
let tags: string[][];
|
|
if (Array.isArray(descriptor.nip94) && descriptor.nip94.length > 0) {
|
|
tags = descriptor.nip94.map((t) => [...t]);
|
|
} else {
|
|
tags = [
|
|
["url", descriptor.url],
|
|
["m", descriptor.type],
|
|
["x", descriptor.sha256],
|
|
["size", String(descriptor.size)],
|
|
];
|
|
}
|
|
|
|
// Ensure the core tags exist even if nip94 omitted them.
|
|
ensureTag(tags, "url", descriptor.url);
|
|
ensureTag(tags, "m", input.mimeType ?? descriptor.type);
|
|
ensureTag(tags, "x", descriptor.sha256);
|
|
ensureTag(tags, "size", String(descriptor.size));
|
|
|
|
// When an explicit mimeType override is given, replace any server-provided
|
|
// `m` tag (BUD-08 nip94 tags carry the upload Content-Type, which for
|
|
// markdown is deliberately text/plain — see FileMetadataInput.mimeType).
|
|
if (input.mimeType) {
|
|
for (const t of tags) {
|
|
if (t[0] === "m") {
|
|
t[1] = input.mimeType;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Descriptive metadata from front-matter.
|
|
tags.push(["title", meta.title]);
|
|
if (meta.summary) {
|
|
tags.push(["summary", meta.summary]);
|
|
}
|
|
if (meta.cover) {
|
|
tags.push(["image", meta.cover]);
|
|
}
|
|
if (meta.author) {
|
|
tags.push(["author", meta.author]);
|
|
}
|
|
if (meta.language) {
|
|
tags.push(["language", meta.language]);
|
|
}
|
|
if (meta.year) {
|
|
tags.push(["year", meta.year]);
|
|
}
|
|
tags.push(["published_at", String(publishedAt)]);
|
|
for (const t of meta.tags) {
|
|
tags.push(["t", t]);
|
|
}
|
|
tags.push(["alt", `File: ${meta.title}`]);
|
|
|
|
return {
|
|
kind: FILE_METADATA_KIND,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags,
|
|
content: meta.summary ?? "",
|
|
};
|
|
}
|
|
|
|
/** Add a tag only if no tag with that name already exists. */
|
|
function ensureTag(tags: string[][], name: string, value: string): void {
|
|
if (!tags.some((t) => t[0] === name)) {
|
|
tags.push([name, value]);
|
|
}
|
|
}
|