diff --git a/config/instance-config.js b/config/instance-config.js index ba450e93..6a22e0ef 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -246,14 +246,8 @@ export const ALLOW_NSFW_CONTENT = false; * * Set to `false` to disable all inbound NIP-71 ingest with a single switch if it * ever causes problems (spam, low-quality content, performance). - * - * NOTE: currently OFF by default. Ingest's data pipeline works, but surfacing a - * burst of ingested authors pushes bitvid's moderation/WoT stage to hydrate - * trust + report data for many cold authors at once, which storms relays and - * stalls the feed render. Re-enable once that hydration is bounded/non-blocking - * (or whitelisted authors are treated as pre-trusted). See the NIP-71 plan doc. */ -export const FEATURE_NIP71_INGEST = false; +export const FEATURE_NIP71_INGEST = true; /** * Emergency fallback accounts for seeding the moderation graph. diff --git a/js/nostr/nip71IngestAdapter.js b/js/nostr/nip71IngestAdapter.js index fe308d79..d5e1c337 100644 --- a/js/nostr/nip71IngestAdapter.js +++ b/js/nostr/nip71IngestAdapter.js @@ -153,6 +153,18 @@ export function buildVideoFromNip71Event(event = {}) { const dTag = dTagFromEvent(event); const videoRootId = dTag || trimString(event.id); + // The feed's resolve-posted-at stage fetches per-video kind-30078 history when + // a timestamp isn't already known. Foreign NIP-71 videos have no such history, + // so without a posted-at the feed would fire a blocking history fetch per + // ingested video (a relay storm that stalls the render). Surface the NIP-71 + // published_at (falling back to created_at) so the feed short-circuits. + const createdAt = Number.isFinite(event.created_at) ? Math.floor(event.created_at) : 0; + const publishedAtNum = Number(metadata.publishedAt); + const publishedAt = + Number.isFinite(publishedAtNum) && publishedAtNum > 0 + ? Math.floor(publishedAtNum) + : createdAt; + return { id: event.id, videoRootId, @@ -181,7 +193,10 @@ export function buildVideoFromNip71Event(event = {}) { infoJsonUrl: "", enableComments: true, pubkey: trimString(event.pubkey), - created_at: Number.isFinite(event.created_at) ? event.created_at : 0, + created_at: createdAt, + // Lets the feed's resolve-posted-at stage short-circuit instead of fetching + // non-existent kind-30078 history for foreign videos. + nip71: { publishedAt }, tags: Array.isArray(event.tags) ? event.tags : [], // Provenance markers so the rest of the app can distinguish ingested videos // from native bitvid uploads (e.g. to hide WebTorrent-only affordances). diff --git a/tests/nip71-ingest-adapter.test.mjs b/tests/nip71-ingest-adapter.test.mjs index c1aaa68f..de9cde86 100644 --- a/tests/nip71-ingest-adapter.test.mjs +++ b/tests/nip71-ingest-adapter.test.mjs @@ -56,6 +56,25 @@ test("maps a foreign NIP-71 event to a playable bitvid video", () => { assert.equal(v.isPrivate, false); }); +test("surfaces nip71.publishedAt so the feed skips per-video history fetches", () => { + // The published_at tag becomes nip71.publishedAt (feed resolve-posted-at uses + // it to avoid a blocking kind-30078 history fetch per ingested video). + const withPublished = buildVideoFromNip71Event( + foreignEvent({ + tags: [ + ["title", "Has published_at"], + ["published_at", "1700000123"], + ["imeta", "url https://example.com/v.mp4", "m video/mp4"], + ], + }), + ); + assert.equal(withPublished.nip71.publishedAt, 1700000123); + + // Falls back to created_at when there's no published_at tag. + const noPublished = buildVideoFromNip71Event(foreignEvent({ created_at: 1699999999 })); + assert.equal(noPublished.nip71.publishedAt, 1699999999); +}); + test("content-warning maps to isNsfw so the NSFW gate applies", () => { const clean = buildVideoFromNip71Event(foreignEvent()); assert.equal(clean.isNsfw, false);