mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Store video file fingerprints alongside series IDs
This commit is contained in:
1 parent
f907f96e1b
commit
82986650ce
8 files changed
+121
-2
No files matched your search
@@ -33,7 +33,12 @@ const event = buildVideoPostEvent({
|
||||
pubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
dTagValue: "debug-video",
|
||||
content: { version: 3, title: "Test", videoRootId: "debug" },
|
||||
content: {
|
||||
version: 3,
|
||||
title: "Test",
|
||||
videoRootId: "debug",
|
||||
infoHash: "0123456789abcdef0123456789abcdef01234567",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -145,7 +150,7 @@ import { updateWatchHistoryListWithDefaultClient } from "./nostrWatchHistoryFaca
|
||||
|
||||
| Note | Kind (default) | Tags | Content format |
|
||||
| --- | --- | --- | --- |
|
||||
| Video post (`NOTE_TYPES.VIDEO_POST`) | `30078` | `['t','video']`, `['d', <stable video identifier>]` plus optional schema append tags | JSON payload using Content Schema v3 (`version`, `title`, optional `url`, `magnet`, `thumbnail`, `description`, `mode`, `videoRootId`, `deleted`, `isPrivate`, `isNsfw`, `isForKids`, `enableComments`, `ws`, `xs`) |
|
||||
| Video post (`NOTE_TYPES.VIDEO_POST`) | `30078` | `['t','video']`, `['d', <stable video identifier>]` plus optional schema append tags | JSON payload using Content Schema v3 (`version`, `title`, optional `url`, `magnet`, `thumbnail`, `description`, `mode`, `videoRootId`, `deleted`, `isPrivate`, `isNsfw`, `isForKids`, `enableComments`, `infoHash`, `fileSha256`, `originalFileSha256`, `ws`, `xs`) |
|
||||
| NIP-94 mirror (`NOTE_TYPES.VIDEO_MIRROR`) | `1063` | Tags forwarded from `publishVideo` (URL, mime type, thumbnail, alt text, magnet) | Plain text alt description |
|
||||
| Repost (`NOTE_TYPES.REPOST`) | `6` | `['e', <event id>, <relay?>]` with optional address pointer `['a', <kind:pubkey:identifier>, <relay?>]`, and `['p', <pubkey>]` when the origin author is known; inherits schema append tags | JSON-serialized event being reposted (or empty if unavailable) |
|
||||
| Generic Repost (`NOTE_TYPES.GENERIC_REPOST`) | `16` | Same as Repost; specific for non-text kinds (e.g. videos). | Same as Repost. |
|
||||
@@ -158,6 +163,8 @@ import { updateWatchHistoryListWithDefaultClient } from "./nostrWatchHistoryFaca
|
||||
|
||||
> **Publishing note:** Session actors only emit passive telemetry (for example, view counters) and must **not** sign video comments. Require a logged-in Nostr signer for comment publishing via [`commentEvents`](../js/nostr/commentEvents.js).
|
||||
|
||||
Video posts should treat `videoRootId` as the stable series identifier that remains unchanged across edits and deletes. File fingerprints such as `infoHash` or `fileSha256` live alongside the content payload so media swaps do not break the version chain.
|
||||
|
||||
| NIP-71 video (`NOTE_TYPES.NIP71_VIDEO`) | `21` | `['title', <title>]`, optional `['published_at', <unix seconds>]`, optional `['alt', <text>]`, repeated `['imeta', ...]` entries describing NIP-92 media variants, optional `['duration', <seconds>]`, repeated `['text-track', <url>, <kind>, <language>]`, optional `['content-warning', <reason>]`, repeated `['segment', <start>, <end>, <title>, <thumbnail>]`, repeated hashtags `['t', <tag>]`, repeated participants `['p', <pubkey>, <relay?>]`, repeated references `['r', <url>]` | Plain text summary carried in the content field. Publishing is gated by the `FEATURE_PUBLISH_NIP71` runtime flag while the rollout stabilizes. |
|
||||
| NIP-71 short video (`NOTE_TYPES.NIP71_SHORT_VIDEO`) | `22` | Same as `NOTE_TYPES.NIP71_VIDEO`; the kind differentiates short-form presentations. | Plain text summary; gated by `FEATURE_PUBLISH_NIP71`. |
|
||||
| Relay list (`NOTE_TYPES.RELAY_LIST`) | `10002` | Repeating `['r', <relay url>]` tags, optionally with a marker of `'read'` or `'write'` to scope the relay; marker omitted for read/write relays | Empty content |
|
||||
|
||||
@@ -21,6 +21,7 @@ import { isDevMode } from "../config.js";
|
||||
import { FEATURE_PUBLISH_NIP71 } from "../constants.js";
|
||||
import { accessControl } from "../accessControl.js";
|
||||
import { bytesToHex, sha256 } from "../../vendor/crypto-helpers.bundle.min.js";
|
||||
import { infoHashFromMagnet } from "../magnets.js";
|
||||
// 🔧 merged conflicting changes from codex/update-video-publishing-and-parsing-logic vs unstable
|
||||
import {
|
||||
buildNip71MetadataTags,
|
||||
@@ -5415,6 +5416,20 @@ export class NostrClient {
|
||||
typeof videoData.ws === "string" ? videoData.ws.trim() : "";
|
||||
const finalXs =
|
||||
typeof videoData.xs === "string" ? videoData.xs.trim() : "";
|
||||
const infoHashCandidates = [videoData.infoHash];
|
||||
let infoHash = "";
|
||||
for (const candidate of infoHashCandidates) {
|
||||
const normalized = infoHashFromMagnet(candidate);
|
||||
if (normalized) {
|
||||
infoHash = normalized;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!infoHash && finalMagnet) {
|
||||
infoHash = infoHashFromMagnet(finalMagnet) || "";
|
||||
}
|
||||
const fileSha256 = normalizeHexHash(videoData.fileSha256);
|
||||
const originalFileSha256 = normalizeHexHash(videoData.originalFileSha256);
|
||||
|
||||
const contentObject = {
|
||||
version: 3,
|
||||
@@ -5432,6 +5447,18 @@ export class NostrClient {
|
||||
enableComments: finalEnableComments,
|
||||
};
|
||||
|
||||
if (infoHash) {
|
||||
contentObject.infoHash = infoHash;
|
||||
}
|
||||
|
||||
if (fileSha256) {
|
||||
contentObject.fileSha256 = fileSha256;
|
||||
}
|
||||
|
||||
if (originalFileSha256) {
|
||||
contentObject.originalFileSha256 = originalFileSha256;
|
||||
}
|
||||
|
||||
if (finalWs) {
|
||||
contentObject.ws = finalWs;
|
||||
}
|
||||
@@ -5805,6 +5832,19 @@ export class NostrClient {
|
||||
? false
|
||||
: true;
|
||||
|
||||
const updatedInfoHash = infoHashFromMagnet(updatedData?.infoHash || "");
|
||||
let infoHash =
|
||||
updatedInfoHash ||
|
||||
infoHashFromMagnet(finalMagnet) ||
|
||||
infoHashFromMagnet(baseEvent.infoHash) ||
|
||||
"";
|
||||
const fileSha256 =
|
||||
normalizeHexHash(updatedData?.fileSha256) ||
|
||||
normalizeHexHash(baseEvent.fileSha256);
|
||||
const originalFileSha256 =
|
||||
normalizeHexHash(updatedData?.originalFileSha256) ||
|
||||
normalizeHexHash(baseEvent.originalFileSha256);
|
||||
|
||||
// Use the existing videoRootId (or fall back to the base event's ID)
|
||||
const oldRootId = baseEvent.videoRootId || baseEvent.id;
|
||||
|
||||
@@ -5837,6 +5877,18 @@ export class NostrClient {
|
||||
enableComments: finalEnableComments,
|
||||
};
|
||||
|
||||
if (infoHash) {
|
||||
contentObject.infoHash = infoHash;
|
||||
}
|
||||
|
||||
if (fileSha256) {
|
||||
contentObject.fileSha256 = fileSha256;
|
||||
}
|
||||
|
||||
if (originalFileSha256) {
|
||||
contentObject.originalFileSha256 = originalFileSha256;
|
||||
}
|
||||
|
||||
if (finalWs) {
|
||||
contentObject.ws = finalWs;
|
||||
}
|
||||
|
||||
@@ -1403,6 +1403,17 @@ export function convertEventToVideo(event = {}) {
|
||||
const enableComments =
|
||||
parsedContent.enableComments === false ? false : true;
|
||||
|
||||
const normalizeSha256 = (candidate) => {
|
||||
if (typeof candidate !== "string") {
|
||||
return "";
|
||||
}
|
||||
const normalized = candidate.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return "";
|
||||
}
|
||||
return /^[0-9a-f]{64}$/.test(normalized) ? normalized : "";
|
||||
};
|
||||
|
||||
let infoHash = "";
|
||||
const pushInfoHash = (candidate) => {
|
||||
if (typeof candidate !== "string") {
|
||||
@@ -1417,6 +1428,8 @@ export function convertEventToVideo(event = {}) {
|
||||
};
|
||||
|
||||
pushInfoHash(parsedContent.infoHash);
|
||||
const fileSha256 = normalizeSha256(parsedContent.fileSha256);
|
||||
const originalFileSha256 = normalizeSha256(parsedContent.originalFileSha256);
|
||||
|
||||
if (!infoHash && magnet) {
|
||||
const match = magnet.match(/xt=urn:btih:([0-9a-z]+)/i);
|
||||
@@ -1476,6 +1489,8 @@ export function convertEventToVideo(event = {}) {
|
||||
magnet,
|
||||
rawMagnet,
|
||||
infoHash,
|
||||
fileSha256,
|
||||
originalFileSha256,
|
||||
thumbnail,
|
||||
description,
|
||||
mode,
|
||||
|
||||
@@ -283,6 +283,9 @@ const BASE_SCHEMAS = {
|
||||
{ key: "isNsfw", type: "boolean", required: false },
|
||||
{ key: "isForKids", type: "boolean", required: false },
|
||||
{ key: "enableComments", type: "boolean", required: false },
|
||||
{ key: "infoHash", type: "string", required: false },
|
||||
{ key: "fileSha256", type: "string", required: false },
|
||||
{ key: "originalFileSha256", type: "string", required: false },
|
||||
{ key: "ws", type: "string", required: false },
|
||||
{ key: "xs", type: "string", required: false },
|
||||
],
|
||||
|
||||
@@ -1018,6 +1018,7 @@ class R2Service {
|
||||
description: metadata?.description ?? "",
|
||||
ws: generatedWs || (metadata?.ws ?? ""),
|
||||
xs: torrentUrl || (metadata?.xs ?? ""),
|
||||
infoHash: hasValidInfoHash ? normalizedInfoHash : "",
|
||||
enableComments: metadata?.enableComments,
|
||||
isNsfw: metadata?.isNsfw,
|
||||
isForKids: metadata?.isForKids,
|
||||
|
||||
@@ -355,6 +355,7 @@ class S3UploadService {
|
||||
description: metadata?.description ?? "",
|
||||
ws: generatedWs || (metadata?.ws ?? ""),
|
||||
xs: torrentUrl || (metadata?.xs ?? ""),
|
||||
infoHash: hasValidInfoHash ? normalizedInfoHash : "",
|
||||
enableComments: metadata?.enableComments,
|
||||
isNsfw: metadata?.isNsfw,
|
||||
isForKids: metadata?.isForKids,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { extractMagnetHints } from "../magnetShared.js";
|
||||
import { normalizeAndAugmentMagnet } from "../magnetUtils.js";
|
||||
import { infoHashFromMagnet } from "../magnets.js";
|
||||
|
||||
export const VIDEO_NOTE_ERROR_CODES = {
|
||||
MISSING_TITLE: "missing_title",
|
||||
@@ -29,6 +30,25 @@ function normalizeString(value) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
function normalizeSha256Hex(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return "";
|
||||
}
|
||||
const normalized = normalizeString(value).toLowerCase();
|
||||
if (!normalized) {
|
||||
return "";
|
||||
}
|
||||
return /^[0-9a-f]{64}$/.test(normalized) ? normalized : "";
|
||||
}
|
||||
|
||||
function normalizeInfoHash(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return "";
|
||||
}
|
||||
const normalized = infoHashFromMagnet(normalizeString(value));
|
||||
return typeof normalized === "string" && normalized ? normalized : "";
|
||||
}
|
||||
|
||||
function parseNumberOrNull(value) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return null;
|
||||
@@ -320,6 +340,11 @@ export function normalizeVideoNotePayload(input) {
|
||||
const description = normalizeString(legacyPayload?.description || "");
|
||||
const ws = normalizeString(legacyPayload?.ws || "");
|
||||
const xs = normalizeString(legacyPayload?.xs || "");
|
||||
const infoHash = normalizeInfoHash(legacyPayload?.infoHash || "");
|
||||
const fileSha256 = normalizeSha256Hex(legacyPayload?.fileSha256 || "");
|
||||
const originalFileSha256 = normalizeSha256Hex(
|
||||
legacyPayload?.originalFileSha256 || ""
|
||||
);
|
||||
const rawMode = normalizeString(legacyPayload?.mode || "");
|
||||
const normalizedMode = rawMode && rawMode.toLowerCase() === "dev" ? "dev" : "live";
|
||||
const enableComments = normalizeBooleanFlag(legacyPayload?.enableComments, true);
|
||||
@@ -401,6 +426,20 @@ export function normalizeVideoNotePayload(input) {
|
||||
legacyFormData.xs = "";
|
||||
}
|
||||
|
||||
const derivedInfoHash =
|
||||
infoHash || normalizeInfoHash(legacyFormData.magnet) || "";
|
||||
if (derivedInfoHash) {
|
||||
legacyFormData.infoHash = derivedInfoHash;
|
||||
}
|
||||
|
||||
if (fileSha256) {
|
||||
legacyFormData.fileSha256 = fileSha256;
|
||||
}
|
||||
|
||||
if (originalFileSha256) {
|
||||
legacyFormData.originalFileSha256 = originalFileSha256;
|
||||
}
|
||||
|
||||
const publishPayload = {
|
||||
legacyFormData,
|
||||
};
|
||||
|
||||
@@ -1017,6 +1017,7 @@ export class UploadModal {
|
||||
magnet: "",
|
||||
ws: this.inputs.ws?.value?.trim() || "",
|
||||
xs: this.inputs.xs?.value?.trim() || "",
|
||||
infoHash: this.torrentState.infoHash || "",
|
||||
};
|
||||
|
||||
// NIP-71 Advanced Data
|
||||
|
||||
Reference in new issue
Block a user