Merge pull request #4133 from vitorpamplona/claude/hopeful-allen-1a54ff

Add picture and video kinds to all home feed legs
This commit is contained in:
Vitor Pamplona
2026-09-17 15:59:43 -04:00
committed by GitHub
6 changed files with 108 additions and 17 deletions
@@ -186,7 +186,7 @@ class NostrSignerPermissionLedger(
* delete afterwards, in the same risk class as the original kind 1/6/7 set (notes, reposts,
* reactions, pictures, videos, voice, public/live/relay chat, threads, polls, comments,
* highlights, code snippets, file metadata, reports, torrents, long-form articles, wiki, status).
* Some are *addressable* (long-form 30023, wiki 30818, legacy video 34235/34236): re-signing
* Some are *addressable* (long-form 30023, wiki 30818, video 34235/34236): re-signing
* with the same `d` tag replaces the app's own prior version at that address — an accepted
* trade-off, since an app that can already post arbitrary notes could do equal reputational harm.
* The set also includes two harmless non-content signatures:
@@ -247,8 +247,8 @@ class NostrSignerPermissionLedger(
LongTextNoteEvent.KIND, // 30023 — NIP-23 long-form articles (addressable content)
StatusEvent.KIND, // 30315 — ephemeral user status / presence
WikiNoteEvent.KIND, // 30818 — NIP-54 wiki articles (addressable content)
VideoHorizontalEvent.KIND, // 34235 — legacy addressable horizontal video (NIP-71)
VideoVerticalEvent.KIND, // 34236 — legacy addressable vertical video (NIP-71)
VideoHorizontalEvent.KIND, // 34235 — addressable horizontal video (NIP-71)
VideoVerticalEvent.KIND, // 34236 — addressable vertical video (NIP-71)
PublicationIndexEvent.KIND, // 30040 — NKBIP-01 publication index (addressable content)
LearningResourceEvent.KIND, // 30142 — learning resources (addressable content)
BlossomPieceIndexEvent.KIND, // 32176 — Blossom piece indexes (addressable content)
@@ -35,6 +35,11 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
import com.vitorpamplona.quartz.nip71Video.VideoShortEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
@@ -52,6 +57,11 @@ val HomePostsByGeohashKinds =
VoiceEvent.KIND,
AttestationEvent.KIND,
InteractiveStoryPrologueEvent.KIND,
PictureEvent.KIND,
VideoNormalEvent.KIND,
VideoShortEvent.KIND,
VideoHorizontalEvent.KIND,
VideoVerticalEvent.KIND,
)
fun filterHomePostsByGeohashes(
@@ -38,6 +38,11 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
import com.vitorpamplona.quartz.nip71Video.VideoShortEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
@@ -57,6 +62,11 @@ val HomePostsBuHashtagsKinds =
VoiceEvent.KIND,
AttestationEvent.KIND,
InteractiveStoryPrologueEvent.KIND,
PictureEvent.KIND,
VideoNormalEvent.KIND,
VideoShortEvent.KIND,
VideoHorizontalEvent.KIND,
VideoVerticalEvent.KIND,
)
fun filterHomePostsByHashtags(
@@ -32,6 +32,11 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
import com.vitorpamplona.quartz.nip71Video.VideoShortEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
@@ -49,21 +54,15 @@ val HomePostsFromCommunityKinds =
CommunityPostApprovalEvent.KIND,
CommentEvent.KIND,
InteractiveStoryPrologueEvent.KIND,
PictureEvent.KIND,
VideoNormalEvent.KIND,
VideoShortEvent.KIND,
VideoHorizontalEvent.KIND,
VideoVerticalEvent.KIND,
)
val HomePostsFromCommunityKindsStr =
listOf(
TextNoteEvent.KIND.toString(),
LongTextNoteEvent.KIND.toString(),
ClassifiedsEvent.KIND.toString(),
HighlightEvent.KIND.toString(),
PollEvent.KIND.toString(),
WikiNoteEvent.KIND.toString(),
NipTextEvent.KIND.toString(),
CommunityPostApprovalEvent.KIND.toString(),
CommentEvent.KIND.toString(),
InteractiveStoryPrologueEvent.KIND.toString(),
)
/** The same set, as the `k` tag values a NIP-72 post approval carries. */
val HomePostsFromCommunityKindsStr = HomePostsFromCommunityKinds.map { it.toString() }
fun filterHomePostsFromCommunity(
relay: NormalizedRelayUrl,
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.relayClient.home
import com.vitorpamplona.amethyst.commons.relayClient.home.nip01Core.HomePostsBuHashtagsKinds
import com.vitorpamplona.amethyst.commons.relayClient.home.nip01Core.HomePostsByGeohashKinds
import com.vitorpamplona.amethyst.commons.relayClient.home.nip65Follows.HomePostsNewThreadKinds1
import com.vitorpamplona.amethyst.commons.relayClient.home.nip72Communities.HomePostsFromCommunityKinds
import com.vitorpamplona.amethyst.commons.relayClient.home.nip72Communities.HomePostsFromCommunityKindsStr
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
import com.vitorpamplona.quartz.nip71Video.VideoShortEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* The "All Follows" home feed fans out to four REQ builders: authors, hashtags, geohashes and
* communities. The home DAL renders pictures and every NIP-71 video kind from any of those legs,
* so a leg that omits a media kind silently hides content the feed would happily show — a
* kind-22 short tagged `#bitcoin` never arriving while a kind-1 with the same tag does.
*/
class HomeMediaKindCoverageTest {
private val mediaKinds =
listOf(
PictureEvent.KIND,
VideoNormalEvent.KIND,
VideoShortEvent.KIND,
VideoHorizontalEvent.KIND,
VideoVerticalEvent.KIND,
)
private fun assertCarriesMedia(
legName: String,
kinds: List<Int>,
) = mediaKinds.forEach {
assertTrue(it in kinds, "$legName must request kind $it")
}
@Test
fun everyHomeLegRequestsPicturesAndAllVideoKinds() {
assertCarriesMedia("authors", HomePostsNewThreadKinds1)
assertCarriesMedia("hashtags", HomePostsBuHashtagsKinds)
assertCarriesMedia("geohashes", HomePostsByGeohashKinds)
assertCarriesMedia("communities", HomePostsFromCommunityKinds)
}
@Test
fun communityApprovalKTagsMirrorTheCommunityKinds() {
assertEquals(HomePostsFromCommunityKinds.map { it.toString() }, HomePostsFromCommunityKindsStr)
}
}
@@ -25,7 +25,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals
/**
* Kinds 34235/34236 (legacy NIP-71 videos) are parameterized replaceable
* Kinds 34235/34236 (the addressable NIP-71 videos) are parameterized replaceable
* events: their address MUST include the `d` tag. A wrong (empty-dTag)
* address makes LocalCache consume the event into a different
* AddressableNote than the one `a` tags point to, so quotes/reposts of