mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
"Share on Nostr" published a kind-1 note whose body ended with a bare thumbnail
URL. Many clients only inline such a URL when a NIP-92 `imeta` entry describes
it, so the viewer often saw a plain link instead of a preview image.
- buildShareEvent now emits an `imeta` entry for the thumbnail. The media type
is inferred from the extension and OMITTED when unknown -- a wrong `m` is
worse than a missing one, since clients use it to decide whether to render
inline at all, so extensionless Blossom URLs ship url + alt only.
- inferImageMimeTypeFromUrl lives in a SEPARATE map from EXTENSION_MIME_MAP:
that map gates video upload/publish paths, and folding image types into it
would let an image pass a video check.
- shareNostrController was passing only { id, pubkey } to buildShareEvent, so
the tag could never be built; it now forwards title and thumbnail.
When a video has a NIP-71 mirror, the note instead quotes it with
`nostr:naddr1...`, which nostr clients render as a native video quote card --
the closest thing to a rich preview that does not depend on any client fetching
OpenGraph tags (most never do).
- The mirror is addressable (kind 34235/34236, author = video pubkey,
d = videoRootId), so js/nostr/mirrorPointer.js derives the coordinate with no
relay lookup, and the pointer survives the author editing the video where a
plain `nevent` would rot on the next replace.
- The pointer is only embedded after nip71MirrorService.findMirror() confirms
the mirror exists. A pointer to a missing event renders as a broken/empty
quote box, which is worse than the plain-link fallback, so a disabled
service, a throwing lookup, a 2.5s timeout or a missing videoRootId all fall
back to the thumbnail note.
- resolveMirrorKind prefers the kinds actually observed on relays and only then
falls back to the same portrait rule the mirror builder uses, so a derived
pointer matches what would be published rather than guessing independently.
- While quoting, the raw thumbnail URL is dropped from the body and the imeta
tag is skipped: the quote card already shows the thumbnail, and emitting both
rendered the same image twice. An `a` tag links the note to the coordinate.
Known limitation: a client rendering the quote card still has to play the
video, so mirrors whose imeta url is an .m3u8 hit the same HLS wall -- title and
thumbnail render, playback depends on the client.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
80 lines
3.1 KiB
JavaScript
80 lines
3.1 KiB
JavaScript
// SCN-share-body: what the viewer actually reads in the composed note.
|
|
//
|
|
// Two shapes, chosen by whether a NIP-71 mirror was confirmed on relays:
|
|
// mirror -> quote card via `nostr:naddr1…`, NO bare thumbnail URL
|
|
// no mirror -> bare thumbnail URL (described by the note's NIP-92 imeta)
|
|
//
|
|
// The "no bare thumbnail while quoting" rule is the one that matters visually:
|
|
// keeping both rendered the same image twice (once loose, once in the card).
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { ShareNostrModal } from "../js/ui/components/ShareNostrModal.js";
|
|
|
|
// buildShareContent is a pure string builder; construct without touching the
|
|
// DOM-dependent parts of the modal.
|
|
const modal = Object.create(ShareNostrModal.prototype);
|
|
const build = (payload) => modal.buildShareContent(payload);
|
|
|
|
const BASE = {
|
|
title: "Why I'm (sort of) not worried about AI",
|
|
shareUrl: "https://bitvid.network/?v=nevent1abc",
|
|
thumbnail: "https://cdn.example/thumb.jpg",
|
|
};
|
|
|
|
test("without a mirror the body ends with the thumbnail URL", () => {
|
|
const body = build(BASE);
|
|
assert.equal(
|
|
body,
|
|
"Why I'm (sort of) not worried about AI — Check out this video on bitvid 👇\n\n" +
|
|
"https://bitvid.network/?v=nevent1abc\n\n" +
|
|
"https://cdn.example/thumb.jpg"
|
|
);
|
|
});
|
|
|
|
test("with a mirror the body quotes it and omits the thumbnail URL", () => {
|
|
const body = build({ ...BASE, mirrorNaddr: "naddr1qqxnzd3e" });
|
|
|
|
assert.match(body, /nostr:naddr1qqxnzd3e$/);
|
|
assert.equal(
|
|
body.includes("https://cdn.example/thumb.jpg"),
|
|
false,
|
|
"quote card already shows the thumbnail; a loose URL duplicates it"
|
|
);
|
|
// The bitvid link stays — attribution and traffic still matter.
|
|
assert.match(body, /https:\/\/bitvid\.network\/\?v=nevent1abc/);
|
|
});
|
|
|
|
test("the quote reference uses the nostr: URI scheme clients resolve", () => {
|
|
const body = build({ ...BASE, mirrorNaddr: "naddr1qqxnzd3e" });
|
|
assert.equal(body.includes("nostr:naddr1qqxnzd3e"), true);
|
|
// A bare naddr without the scheme is rendered as plain text by most clients.
|
|
assert.equal(/(^|\s)naddr1qqxnzd3e/.test(body), false);
|
|
});
|
|
|
|
test("an empty or whitespace mirror pointer falls back to the thumbnail", () => {
|
|
for (const mirrorNaddr of ["", " ", null, undefined]) {
|
|
const body = build({ ...BASE, mirrorNaddr });
|
|
assert.match(body, /https:\/\/cdn\.example\/thumb\.jpg$/);
|
|
assert.equal(body.includes("nostr:"), false);
|
|
}
|
|
});
|
|
|
|
test("missing pieces never leave dangling blank sections", () => {
|
|
const noThumb = build({ title: "T", shareUrl: "https://bitvid.network/?v=x" });
|
|
assert.equal(noThumb.endsWith("\n"), false);
|
|
assert.equal(/\n{3,}/.test(noThumb), false);
|
|
|
|
const titleOnly = build({ title: "T" });
|
|
assert.equal(titleOnly, "T — Check out this video on bitvid 👇");
|
|
|
|
const empty = build({});
|
|
assert.equal(empty, "Untitled video — Check out this video on bitvid 👇");
|
|
});
|
|
|
|
test("a missing title degrades to a placeholder rather than 'undefined'", () => {
|
|
const body = build({ ...BASE, title: " " });
|
|
assert.match(body, /^Untitled video — /);
|
|
assert.equal(body.includes("undefined"), false);
|
|
});
|