diff --git a/js/nostr/client.js b/js/nostr/client.js index e48f4b92..05572104 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -25,6 +25,7 @@ import { buildNip71MetadataTags, collectNip71PointerRequests, convertEventToVideo, + isAudioOnlyVideoObject, extractNip71MetadataFromTags, getDTagValueFromTags, mergeNip71MetadataIntoVideo as mergeNip71MetadataIntoVideoHelper, @@ -4469,9 +4470,13 @@ export class NostrClient { } getActiveVideos() { - return Array.from(this.activeMap.values()).sort( - (a, b) => b.created_at - a.created_at - ); + // Filter audio-only notes (podcasts/music) here too, not just at ingest: + // video objects rehydrated from the persisted cache bypass + // convertEventToVideo, so a note cached before the audio guard existed would + // otherwise still reach the feed. See TODO #60. + return Array.from(this.activeMap.values()) + .filter((video) => !isAudioOnlyVideoObject(video)) + .sort((a, b) => b.created_at - a.created_at); } // Optimistically add a just-published video event to the local caches so it diff --git a/js/nostr/index.js b/js/nostr/index.js index a2c6714d..90f2da15 100644 --- a/js/nostr/index.js +++ b/js/nostr/index.js @@ -18,6 +18,7 @@ export { buildNip71VideoEvent, collectNip71PointerRequests, convertEventToVideo, + isAudioOnlyVideoObject, extractNip71MetadataFromTags, getDTagValueFromTags, mergeNip71MetadataIntoVideo, diff --git a/js/nostr/managers/PersistenceManager.js b/js/nostr/managers/PersistenceManager.js index 9f96c759..16b0a9fa 100644 --- a/js/nostr/managers/PersistenceManager.js +++ b/js/nostr/managers/PersistenceManager.js @@ -1,4 +1,5 @@ import { EventsCacheStore } from "./EventsCacheStore.js"; +import { isAudioOnlyVideoObject } from "../nip71.js"; import { devLogger, userLogger } from "../../utils/logger.js"; import { isDevMode } from "../../config.js"; import { NOTE_TYPES } from "../../nostrEventSchemas.js"; @@ -154,6 +155,12 @@ export class PersistenceManager { continue; } + // Drop audio-only notes (podcasts/music) cached before the audio guard + // existed so they don't get rehydrated into the feed. See TODO #60. + if (isAudioOnlyVideoObject(video)) { + continue; + } + this.client.applyRootCreatedAt(video); const activeKey = this.client.getActiveKey(video); diff --git a/js/nostr/nip71.js b/js/nostr/nip71.js index 4e4706f3..928010ae 100644 --- a/js/nostr/nip71.js +++ b/js/nostr/nip71.js @@ -1497,6 +1497,31 @@ function detectImetaMediaKinds(tags = []) { // treated as audio when an `imeta m audio/*` tag says so. const AUDIO_ONLY_URL_PATTERN = /\.(mp3|m4a|aac|wav|flac|opus|oga|weba)(?:[?#]|$)/i; +// Whether a (converted) video object is an audio-only note — a podcast/music +// note published as a native kind-30078. bitvid has no