mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Refactor publishVideo: Extract NIP-94/71 logic to videoPublisher.js
Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
1d0c9ec6df
commit
74a44ebb36
3 files changed
+246
-209
No files matched your search
+7
-208
@@ -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).
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user