From 74a44ebb362245b49aac1843b1c3c194f8a7b5ca Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:16:58 +0000 Subject: [PATCH] Refactor publishVideo: Extract NIP-94/71 logic to videoPublisher.js Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/nostr/client.js | 215 ++------------------------------ js/nostr/videoPublisher.js | 239 ++++++++++++++++++++++++++++++++++++ tests/nostr/client.test.mjs | 1 - 3 files changed, 246 insertions(+), 209 deletions(-) create mode 100644 js/nostr/videoPublisher.js diff --git a/js/nostr/client.js b/js/nostr/client.js index baa090f2..5577d542 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -18,12 +18,10 @@ */ import { isDevMode } from "../config.js"; -import { FEATURE_PUBLISH_NIP71 } from "../constants.js"; import { infoHashFromMagnet } from "../magnets.js"; // 🔧 merged conflicting changes from codex/update-video-publishing-and-parsing-logic vs unstable import { buildNip71MetadataTags, - buildNip71VideoEvent, collectNip71PointerRequests, convertEventToVideo, extractNip71MetadataFromTags, @@ -31,9 +29,11 @@ import { mergeNip71MetadataIntoVideo as mergeNip71MetadataIntoVideoHelper, populateNip71MetadataForVideos as populateNip71MetadataForVideosHelper, processNip71Events as processNip71EventsHelper, - buildVideoPointerValue, - stringFromInput, } from "./nip71.js"; +import { + handlePublishNip94, + handlePublishNip71, +} from "./videoPublisher.js"; import { buildStoragePointerValue, deriveStoragePointerFromUrl, @@ -222,7 +222,6 @@ import { } from "./managers/SignerManager.js"; import { prepareVideoPublishPayload, - prepareVideoMirrorOptions, } from "./videoPayloadBuilder.js"; import { inferMimeTypeFromUrl } from "../utils/mime.js"; @@ -414,7 +413,6 @@ export { convertEventToVideo, Nip46RpcClient, buildNip71MetadataTags, - buildNip71VideoEvent, extractNip71MetadataFromTags, }; @@ -2406,7 +2404,7 @@ export class NostrClient { }); // 2. Publish NIP-94 Mirror (Kind 1063) if a hosted URL is present. - await this._handlePublishNip94(signedEvent, finalUrl, { + await handlePublishNip94(this, signedEvent, finalUrl, { videoData, videoPayload, finalMagnet, @@ -2422,7 +2420,8 @@ export class NostrClient { }); // 3. Publish NIP-71 Metadata (Kind 22) if categories/tags were added. - await this._handlePublishNip71( + await handlePublishNip71( + this, signedEvent, videoPayload, nip71Metadata, @@ -2440,206 +2439,6 @@ export class NostrClient { } } - async _handlePublishNip94(signedEvent, finalUrl, mirrorParams) { - if (!finalUrl) { - devLogger.log("Skipping NIP-94 mirror: no hosted URL provided."); - return; - } - - const { - videoData, - videoPayload, - finalMagnet, - finalThumbnail, - finalDescription, - finalTitle, - mimeType, - fileSha256, - originalFileSha256, - pubkey, - createdAt, - isPrivate, - } = mirrorParams; - - const mirrorOptions = await prepareVideoMirrorOptions({ - videoData, - videoPayload, - finalUrl, - finalMagnet, - finalThumbnail, - finalDescription, - finalTitle, - mimeType, - fileSha256, - originalFileSha256, - pubkey, - createdAt, - isPrivate, - }); - - try { - const mirrorResult = await this.mirrorVideoEvent( - signedEvent.id, - mirrorOptions, - ); - - if (mirrorResult?.ok) { - devLogger.log("Prepared NIP-94 mirror event:", mirrorResult.event); - devLogger.log("NIP-94 mirror dispatched for hosted URL:", finalUrl); - } else if (mirrorResult) { - devLogger.warn( - "[nostr] NIP-94 mirror rejected:", - mirrorResult.error || "mirror-failed", - mirrorResult.details || null, - ); - } - } catch (mirrorError) { - devLogger.warn( - "[nostr] Failed to publish NIP-94 mirror:", - mirrorError, - ); - } - } - - async _handlePublishNip71( - signedEvent, - videoPayload, - nip71Metadata, - contentObject, - wantPrivate, - userPubkeyLower, - videoRootId, - dTagValue, - ) { - const nip71EditedFlag = - videoPayload && typeof videoPayload === "object" - ? videoPayload.nip71Edited - : null; - const hasMetadataObject = - nip71Metadata && typeof nip71Metadata === "object"; - const metadataWasEdited = - nip71EditedFlag === true || - (nip71EditedFlag == null && hasMetadataObject); - const shouldAttemptNip71 = !wantPrivate && metadataWasEdited; - - if (shouldAttemptNip71) { - const metadataLegacyFormData = { - title: contentObject.title, - description: contentObject.description, - url: contentObject.url, - magnet: contentObject.magnet, - thumbnail: contentObject.thumbnail, - mode: contentObject.mode, - isPrivate: wantPrivate, - isNsfw: contentObject.isNsfw, - isForKids: contentObject.isForKids, - }; - - if (contentObject.ws) { - metadataLegacyFormData.ws = contentObject.ws; - } - - if (contentObject.xs) { - metadataLegacyFormData.xs = contentObject.xs; - } - - try { - await this.publishNip71Video( - { - nip71: nip71Metadata, - legacyFormData: metadataLegacyFormData, - }, - userPubkeyLower, - { - videoRootId, - dTag: dTagValue, - eventId: signedEvent.id, - }, - ); - } catch (nip71Error) { - userLogger.warn( - "[nostr] Failed to publish NIP-71 metadata for edit:", - nip71Error, - ); - } - } - } - - async publishNip71Video(videoPayload, pubkey, pointerOptions = {}) { - if (!FEATURE_PUBLISH_NIP71) { - return null; - } - - if (!pubkey) { - throw new Error("Not logged in to publish video."); - } - - const { videoData, nip71Metadata } = extractVideoPublishPayload(videoPayload); - - if (!nip71Metadata || typeof nip71Metadata !== "object") { - devLogger.log("[nostr] Skipping NIP-71 publish: metadata missing."); - return null; - } - - const title = stringFromInput(videoData?.title); - const description = stringFromInput(videoData?.description); - - const pointerIdentifiers = - pointerOptions && typeof pointerOptions === "object" - ? pointerOptions - : {}; - - const event = buildNip71VideoEvent({ - metadata: nip71Metadata, - pubkey, - title, - summaryFallback: description, - pointerIdentifiers: { - videoRootId: pointerIdentifiers.videoRootId, - dTag: pointerIdentifiers.dTag, - eventId: pointerIdentifiers.eventId, - }, - createdAt: Math.floor(Date.now() / 1000), - }); - - if (!event) { - devLogger.warn("[nostr] Skipping NIP-71 publish: builder produced no event."); - return null; - } - - devLogger.log("Prepared NIP-71 video event:", event); - - const { signedEvent } = await this.signAndPublishEvent(event, { - context: "NIP-71 video", - logName: "NIP-71 video", - devLogLabel: "NIP-71 video", - rejectionLogLevel: "warn", - resolveActiveSigner: (p) => this.signerManager.resolveActiveSigner(p), - }); - - const pointerMap = new Map(); - if (pointerIdentifiers.videoRootId) { - const pointerValue = buildVideoPointerValue( - pubkey, - pointerIdentifiers.videoRootId - ); - if (pointerValue) { - pointerMap.set(pointerValue, { - videoRootId: pointerIdentifiers.videoRootId, - pointerValue, - videoEventIds: new Set( - pointerIdentifiers.eventId ? [pointerIdentifiers.eventId] : [] - ), - dTags: new Set(pointerIdentifiers.dTag ? [pointerIdentifiers.dTag] : []), - }); - } - } - - this.processNip71Events([signedEvent], pointerMap); - - return signedEvent; - } - /** * Edits an existing video by publishing a new version (Kind 30078). * diff --git a/js/nostr/videoPublisher.js b/js/nostr/videoPublisher.js new file mode 100644 index 00000000..e9cf4e54 --- /dev/null +++ b/js/nostr/videoPublisher.js @@ -0,0 +1,239 @@ +import { devLogger, userLogger } from "../utils/logger.js"; +import { FEATURE_PUBLISH_NIP71 } from "../constants.js"; +import { prepareVideoMirrorOptions, extractVideoPublishPayload } from "./videoPayloadBuilder.js"; +import { + buildNip71VideoEvent, + buildVideoPointerValue, + stringFromInput +} from "./nip71.js"; + +/** + * Handles NIP-94 mirroring for a published video. + * + * @param {import("./client.js").NostrClient} client - The client instance. + * @param {import("nostr-tools").Event} signedEvent - The signed video event. + * @param {string} finalUrl - The final URL of the video. + * @param {object} mirrorParams - Parameters for the mirror. + */ +export async function handlePublishNip94(client, signedEvent, finalUrl, mirrorParams) { + if (!finalUrl) { + devLogger.log("Skipping NIP-94 mirror: no hosted URL provided."); + return; + } + + const { + videoData, + videoPayload, + finalMagnet, + finalThumbnail, + finalDescription, + finalTitle, + mimeType, + fileSha256, + originalFileSha256, + pubkey, + createdAt, + isPrivate, + } = mirrorParams; + + const mirrorOptions = await prepareVideoMirrorOptions({ + videoData, + videoPayload, + finalUrl, + finalMagnet, + finalThumbnail, + finalDescription, + finalTitle, + mimeType, + fileSha256, + originalFileSha256, + pubkey, + createdAt, + isPrivate, + }); + + try { + const mirrorResult = await client.mirrorVideoEvent( + signedEvent.id, + mirrorOptions, + ); + + if (mirrorResult?.ok) { + devLogger.log("Prepared NIP-94 mirror event:", mirrorResult.event); + devLogger.log("NIP-94 mirror dispatched for hosted URL:", finalUrl); + } else if (mirrorResult) { + devLogger.warn( + "[nostr] NIP-94 mirror rejected:", + mirrorResult.error || "mirror-failed", + mirrorResult.details || null, + ); + } + } catch (mirrorError) { + devLogger.warn( + "[nostr] Failed to publish NIP-94 mirror:", + mirrorError, + ); + } +} + +/** + * Handles NIP-71 metadata publishing. + * + * @param {import("./client.js").NostrClient} client - The client instance. + * @param {import("nostr-tools").Event} signedEvent - The signed video event. + * @param {object} videoPayload - The video payload. + * @param {object} nip71Metadata - The NIP-71 metadata. + * @param {object} contentObject - The content object. + * @param {boolean} wantPrivate - Whether the video is private. + * @param {string} userPubkeyLower - The lowercased user pubkey. + * @param {string} videoRootId - The video root ID. + * @param {string} dTagValue - The d tag value. + */ +export async function handlePublishNip71( + client, + signedEvent, + videoPayload, + nip71Metadata, + contentObject, + wantPrivate, + userPubkeyLower, + videoRootId, + dTagValue, + ) { + const nip71EditedFlag = + videoPayload && typeof videoPayload === "object" + ? videoPayload.nip71Edited + : null; + const hasMetadataObject = + nip71Metadata && typeof nip71Metadata === "object"; + const metadataWasEdited = + nip71EditedFlag === true || + (nip71EditedFlag == null && hasMetadataObject); + const shouldAttemptNip71 = !wantPrivate && metadataWasEdited; + + if (shouldAttemptNip71) { + const metadataLegacyFormData = { + title: contentObject.title, + description: contentObject.description, + url: contentObject.url, + magnet: contentObject.magnet, + thumbnail: contentObject.thumbnail, + mode: contentObject.mode, + isPrivate: wantPrivate, + isNsfw: contentObject.isNsfw, + isForKids: contentObject.isForKids, + }; + + if (contentObject.ws) { + metadataLegacyFormData.ws = contentObject.ws; + } + + if (contentObject.xs) { + metadataLegacyFormData.xs = contentObject.xs; + } + + try { + await publishNip71Video( + client, + { + nip71: nip71Metadata, + legacyFormData: metadataLegacyFormData, + }, + userPubkeyLower, + { + videoRootId, + dTag: dTagValue, + eventId: signedEvent.id, + }, + ); + } catch (nip71Error) { + userLogger.warn( + "[nostr] Failed to publish NIP-71 metadata for edit:", + nip71Error, + ); + } + } +} + +/** + * Publishes a NIP-71 video event. + * + * @param {import("./client.js").NostrClient} client - The client instance. + * @param {object} videoPayload - The video payload. + * @param {string} pubkey - The publisher's pubkey. + * @param {object} pointerOptions - The pointer options. + */ +export async function publishNip71Video(client, videoPayload, pubkey, pointerOptions = {}) { + if (!FEATURE_PUBLISH_NIP71) { + return null; + } + + if (!pubkey) { + throw new Error("Not logged in to publish video."); + } + + const { videoData, nip71Metadata } = extractVideoPublishPayload(videoPayload); + + if (!nip71Metadata || typeof nip71Metadata !== "object") { + devLogger.log("[nostr] Skipping NIP-71 publish: metadata missing."); + return null; + } + + const title = stringFromInput(videoData?.title); + const description = stringFromInput(videoData?.description); + + const pointerIdentifiers = + pointerOptions && typeof pointerOptions === "object" + ? pointerOptions + : {}; + + const event = buildNip71VideoEvent({ + metadata: nip71Metadata, + pubkey, + title, + summaryFallback: description, + pointerIdentifiers: { + videoRootId: pointerIdentifiers.videoRootId, + dTag: pointerIdentifiers.dTag, + eventId: pointerIdentifiers.eventId, + }, + createdAt: Math.floor(Date.now() / 1000), + }); + + if (!event) { + devLogger.warn("[nostr] Skipping NIP-71 publish: builder produced no event."); + return null; + } + + devLogger.log("Prepared NIP-71 video event:", event); + + const { signedEvent } = await client.signAndPublishEvent(event, { + context: "NIP-71 video", + logName: "NIP-71 video", + devLogLabel: "NIP-71 video", + rejectionLogLevel: "warn", + resolveActiveSigner: (p) => client.signerManager.resolveActiveSigner(p), + }); + + const pointerMap = new Map(); + if (pointerIdentifiers.videoRootId) { + const pointerValue = buildVideoPointerValue( + pubkey, + pointerIdentifiers.videoRootId + ); + if (pointerValue) { + pointerMap.set(pointerValue, { + videoRootId: pointerIdentifiers.videoRootId, + pointerValue, + videoEventIds: new Set( + pointerIdentifiers.eventId ? [pointerIdentifiers.eventId] : [] + ), + dTags: new Set(pointerIdentifiers.dTag ? [pointerIdentifiers.dTag] : []), + }); + } + } + + client.processNip71Events([signedEvent], pointerMap); + + return signedEvent; +} diff --git a/tests/nostr/client.test.mjs b/tests/nostr/client.test.mjs index 008a7094..e8787cbc 100644 --- a/tests/nostr/client.test.mjs +++ b/tests/nostr/client.test.mjs @@ -230,7 +230,6 @@ describe("NostrClient", () => { const signedEvent = { id: "evt1", pubkey, sig: "sig" }; client.signAndPublishEvent = mock.fn(async () => ({ signedEvent })); client.mirrorVideoEvent = mock.fn(async () => ({ ok: true })); - client.publishNip71Video = mock.fn(async () => null); const videoPayload = { title: "My Video",