mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add NSFW and kids flags to video schema
This commit is contained in:
1 parent
f33f56c923
commit
259b493a20
6 files changed
+127
-10
No files matched your search
@@ -72,7 +72,7 @@ timestamps.
|
||||
|
||||
| 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`, `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`, `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 | Empty content |
|
||||
| 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. |
|
||||
|
||||
+38
-8
@@ -800,7 +800,13 @@ function extractVideoPublishPayload(rawPayload) {
|
||||
videoData = {};
|
||||
}
|
||||
|
||||
return { videoData, nip71Metadata };
|
||||
const normalizedVideoData = { ...videoData };
|
||||
const isNsfw = videoData.isNsfw === true;
|
||||
normalizedVideoData.isNsfw = isNsfw;
|
||||
normalizedVideoData.isForKids =
|
||||
videoData.isForKids === true && !isNsfw;
|
||||
|
||||
return { videoData: normalizedVideoData, nip71Metadata };
|
||||
}
|
||||
|
||||
function buildVideoPointerValue(pubkey, videoRootId) {
|
||||
@@ -2916,6 +2922,8 @@ function convertEventToVideo(event = {}) {
|
||||
const mode = rawMode || "live";
|
||||
const deleted = parsedContent.deleted === true;
|
||||
const isPrivate = parsedContent.isPrivate === true;
|
||||
const isNsfw = parsedContent.isNsfw === true;
|
||||
const isForKids = parsedContent.isForKids === true && !isNsfw;
|
||||
const videoRootId = safeTrim(parsedContent.videoRootId) || event.id;
|
||||
const wsField = safeTrim(parsedContent.ws);
|
||||
const xsField = safeTrim(parsedContent.xs);
|
||||
@@ -3018,6 +3026,8 @@ function convertEventToVideo(event = {}) {
|
||||
videoRootId,
|
||||
version,
|
||||
isPrivate,
|
||||
isNsfw,
|
||||
isForKids,
|
||||
title,
|
||||
url,
|
||||
magnet,
|
||||
@@ -6367,6 +6377,9 @@ export class NostrClient {
|
||||
|
||||
const finalEnableComments =
|
||||
videoData.enableComments === false ? false : true;
|
||||
const finalIsNsfw = videoData.isNsfw === true;
|
||||
const finalIsForKids =
|
||||
videoData.isForKids === true && !finalIsNsfw;
|
||||
const finalWs =
|
||||
typeof videoData.ws === "string" ? videoData.ws.trim() : "";
|
||||
const finalXs =
|
||||
@@ -6383,6 +6396,8 @@ export class NostrClient {
|
||||
videoRootId,
|
||||
deleted: false,
|
||||
isPrivate: videoData.isPrivate ?? false,
|
||||
isNsfw: finalIsNsfw,
|
||||
isForKids: finalIsForKids,
|
||||
enableComments: finalEnableComments,
|
||||
};
|
||||
|
||||
@@ -6493,6 +6508,8 @@ export class NostrClient {
|
||||
thumbnail: contentObject.thumbnail,
|
||||
mode: contentObject.mode,
|
||||
isPrivate: contentObject.isPrivate,
|
||||
isNsfw: contentObject.isNsfw,
|
||||
isForKids: contentObject.isForKids,
|
||||
};
|
||||
|
||||
if (contentObject.ws) {
|
||||
@@ -6666,6 +6683,10 @@ export class NostrClient {
|
||||
|
||||
// Determine if the updated note should be private
|
||||
const wantPrivate = updatedData.isPrivate ?? baseEvent.isPrivate ?? false;
|
||||
const wantNsfw = updatedData.isNsfw ?? baseEvent.isNsfw ?? false;
|
||||
const wantForKids = updatedData.isForKids ?? baseEvent.isForKids ?? false;
|
||||
const finalIsNsfw = wantNsfw === true;
|
||||
const finalIsForKids = finalIsNsfw ? false : wantForKids === true;
|
||||
|
||||
// Use the new magnet if provided; otherwise, fall back to the decrypted old magnet
|
||||
const magnetEdited = updatedData.magnetEdited === true;
|
||||
@@ -6709,6 +6730,8 @@ export class NostrClient {
|
||||
version: updatedData.version ?? baseEvent.version ?? 2,
|
||||
deleted: false,
|
||||
isPrivate: wantPrivate,
|
||||
isNsfw: finalIsNsfw,
|
||||
isForKids: finalIsForKids,
|
||||
title: updatedData.title ?? baseEvent.title,
|
||||
url: finalUrl,
|
||||
magnet: finalMagnet,
|
||||
@@ -6833,13 +6856,15 @@ export class NostrClient {
|
||||
baseEvent = {
|
||||
id: fetched.id,
|
||||
pubkey: fetched.pubkey,
|
||||
content: JSON.stringify({
|
||||
version: fetched.version,
|
||||
deleted: fetched.deleted,
|
||||
isPrivate: fetched.isPrivate,
|
||||
title: fetched.title,
|
||||
url: fetched.url,
|
||||
magnet: fetched.magnet,
|
||||
content: JSON.stringify({
|
||||
version: fetched.version,
|
||||
deleted: fetched.deleted,
|
||||
isPrivate: fetched.isPrivate,
|
||||
isNsfw: fetched.isNsfw,
|
||||
isForKids: fetched.isForKids,
|
||||
title: fetched.title,
|
||||
url: fetched.url,
|
||||
magnet: fetched.magnet,
|
||||
thumbnail: fetched.thumbnail,
|
||||
description: fetched.description,
|
||||
mode: fetched.mode,
|
||||
@@ -6869,11 +6894,16 @@ export class NostrClient {
|
||||
? `LEGACY:${baseEvent.pubkey}:${existingD}`
|
||||
: baseEvent.id);
|
||||
|
||||
const oldIsNsfw = oldContent.isNsfw === true;
|
||||
const oldIsForKids = oldContent.isForKids === true && !oldIsNsfw;
|
||||
|
||||
const contentObject = {
|
||||
videoRootId: finalRootId,
|
||||
version: oldVersion,
|
||||
deleted: true,
|
||||
isPrivate: oldContent.isPrivate ?? false,
|
||||
isNsfw: oldIsNsfw,
|
||||
isForKids: oldIsForKids,
|
||||
title: oldContent.title || "",
|
||||
url: "",
|
||||
magnet: "",
|
||||
|
||||
@@ -118,6 +118,8 @@ const BASE_SCHEMAS = {
|
||||
{ key: "videoRootId", type: "string", required: true },
|
||||
{ key: "deleted", type: "boolean", required: false },
|
||||
{ key: "isPrivate", type: "boolean", required: false },
|
||||
{ key: "isNsfw", type: "boolean", required: false },
|
||||
{ key: "isForKids", type: "boolean", required: false },
|
||||
{ key: "enableComments", type: "boolean", required: false },
|
||||
{ key: "ws", type: "string", required: false },
|
||||
{ key: "xs", type: "string", required: false },
|
||||
|
||||
@@ -302,6 +302,8 @@ export class VideoListView {
|
||||
: Number(video?.created_at) || 0,
|
||||
deleted: Boolean(video?.deleted),
|
||||
isPrivate: Boolean(video?.isPrivate),
|
||||
isNsfw: Boolean(video?.isNsfw),
|
||||
isForKids: Boolean(video?.isForKids),
|
||||
thumbnail: typeof video?.thumbnail === "string" ? video.thumbnail : "",
|
||||
url: typeof video?.url === "string" ? video.url : "",
|
||||
magnet: typeof video?.magnet === "string" ? video.magnet : "",
|
||||
|
||||
@@ -33,11 +33,17 @@ test("30078 events carry nip71 metadata tags and hydrate fallback metadata", ()
|
||||
videoRootId: "root-123",
|
||||
deleted: false,
|
||||
isPrivate: false,
|
||||
isNsfw: false,
|
||||
isForKids: true,
|
||||
enableComments: true,
|
||||
},
|
||||
additionalTags,
|
||||
});
|
||||
|
||||
const parsedContent = JSON.parse(event.content);
|
||||
assert.equal(parsedContent.isNsfw, false);
|
||||
assert.equal(parsedContent.isForKids, true);
|
||||
|
||||
const hashtagValues = event.tags
|
||||
.filter((tag) => tag[0] === "t")
|
||||
.map((tag) => tag[1])
|
||||
@@ -58,7 +64,6 @@ test("30078 events carry nip71 metadata tags and hydrate fallback metadata", ()
|
||||
assert.deepEqual(referenceTag, ["r", "https://example.com/info"]);
|
||||
|
||||
const client = new NostrClient();
|
||||
const parsedContent = JSON.parse(event.content);
|
||||
const video = {
|
||||
id: "event-123",
|
||||
videoRootId: parsedContent.videoRootId,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
NOTE_TYPES,
|
||||
getNostrEventSchema,
|
||||
} from "../js/nostrEventSchemas.js";
|
||||
import { convertEventToVideo } from "../js/nostr.js";
|
||||
|
||||
test("video post schema documents nsfw and kids flags", () => {
|
||||
const schema = getNostrEventSchema(NOTE_TYPES.VIDEO_POST);
|
||||
const fieldKeys = Array.isArray(schema?.content?.fields)
|
||||
? schema.content.fields.map((field) => field?.key)
|
||||
: [];
|
||||
|
||||
assert.ok(
|
||||
fieldKeys.includes("isNsfw"),
|
||||
"schema should include isNsfw field"
|
||||
);
|
||||
assert.ok(
|
||||
fieldKeys.includes("isForKids"),
|
||||
"schema should include isForKids field"
|
||||
);
|
||||
});
|
||||
|
||||
test("convertEventToVideo normalizes nsfw and kids booleans", () => {
|
||||
const nsfwEvent = {
|
||||
id: "evt-nsfw",
|
||||
content: JSON.stringify({
|
||||
version: 3,
|
||||
title: "Flagged clip",
|
||||
url: "https://cdn.example/nsfw.mp4",
|
||||
videoRootId: "root-nsfw",
|
||||
isNsfw: true,
|
||||
isForKids: true,
|
||||
}),
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const nsfwParsed = convertEventToVideo(nsfwEvent);
|
||||
assert.equal(nsfwParsed.isNsfw, true);
|
||||
assert.equal(
|
||||
nsfwParsed.isForKids,
|
||||
false,
|
||||
"isForKids should be suppressed when nsfw is true"
|
||||
);
|
||||
|
||||
const kidsEvent = {
|
||||
id: "evt-kids",
|
||||
content: JSON.stringify({
|
||||
version: 3,
|
||||
title: "Kids clip",
|
||||
url: "https://cdn.example/kids.mp4",
|
||||
videoRootId: "root-kids",
|
||||
isForKids: true,
|
||||
}),
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const kidsParsed = convertEventToVideo(kidsEvent);
|
||||
assert.equal(kidsParsed.isNsfw, false);
|
||||
assert.equal(kidsParsed.isForKids, true);
|
||||
|
||||
const defaultEvent = {
|
||||
id: "evt-default",
|
||||
content: JSON.stringify({
|
||||
version: 3,
|
||||
title: "Default clip",
|
||||
url: "https://cdn.example/default.mp4",
|
||||
videoRootId: "root-default",
|
||||
}),
|
||||
tags: [],
|
||||
};
|
||||
|
||||
const defaultParsed = convertEventToVideo(defaultEvent);
|
||||
assert.equal(defaultParsed.isNsfw, false);
|
||||
assert.equal(defaultParsed.isForKids, false);
|
||||
});
|
||||
Reference in new issue
Block a user