From 890516263f00b329ca552d4c4d23136fc7f8cc4d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 01:17:05 +0000 Subject: [PATCH] feat: query zapstore's relay in the global Apps feed and keep its filter specific MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global Apps feed (NIP-82 software apps) was sending a kinds-only REQ filter with limit=200. zapstore's relay scores each REQ filter for specificity and rejects anything that scores below 3, treating a kinds-only filter with a limit >= 100 as "too vague" — so the whole subscription was refused. - Drop the global Apps filter limit from 200 to 99 so a kinds-only filter clears the bar (kinds +1, limit < 100 +2 = 3). - Always include wss://relay.zapstore.dev in the global Apps feed relays, since it indexes the full app catalog, unless the user has added it to their blocked relay list (NIP-51 kind 10006). - Re-evaluate the subscription when the blocked relay list changes so blocking/unblocking zapstore takes effect without an app restart. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01US2aK3igzfeo4xtcvg3mKc --- .../datasource/SoftwareAppsSubAssembler.kt | 14 +++++++++- .../datasource/SubAssemblyHelper.kt | 4 ++- .../subassemblies/FilterSoftwareAppsGlobal.kt | 28 +++++++++++++++---- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsSubAssembler.kt index b1eeed1aad..88710a4665 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SoftwareAppsSubAssembler.kt @@ -44,7 +44,12 @@ class SoftwareAppsSubAssembler( ): List { val feedSettings = key.followsPerRelay() - return makeSoftwareAppsFilter(feedSettings, since, key.feedStates.softwareAppsFeed.lastNoteCreatedAtIfFilled()) + return makeSoftwareAppsFilter( + feedSettings, + since, + key.feedStates.softwareAppsFeed.lastNoteCreatedAtIfFilled(), + key.account.blockedRelayList.flow.value, + ) } override fun user(key: SoftwareAppsQueryState) = key.account.userProfile() @@ -77,6 +82,13 @@ class SoftwareAppsSubAssembler( invalidateFilters() } }, + key.scope.launch(Dispatchers.IO) { + // Re-evaluate when the user blocks/unblocks zapstore's relay so the + // global Apps feed stops/starts querying it without a restart. + key.account.blockedRelayList.flow.collectLatest { + invalidateFilters() + } + }, key.account.scope.launch(Dispatchers.IO) { key.feedStates.softwareAppsFeed.lastNoteCreatedAtWhenFullyLoaded.sample(5000).collectLatest { invalidateFilters() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SubAssemblyHelper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SubAssemblyHelper.kt index be86fd0cdc..6dbfbf2fc2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SubAssemblyHelper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/SubAssemblyHelper.kt @@ -33,16 +33,18 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.datasource.sub import com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.datasource.subassemblies.filterSoftwareAppsByMutedAuthors import com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.datasource.subassemblies.filterSoftwareAppsGlobal import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl fun makeSoftwareAppsFilter( feedSettings: IFeedTopNavPerRelayFilterSet, since: SincePerRelayMap?, defaultSince: Long? = null, + blockedRelays: Set = emptySet(), ): List = when (feedSettings) { is AllFollowsTopNavPerRelayFilterSet -> filterSoftwareAppsByFollows(feedSettings, since, defaultSince) is AuthorsTopNavPerRelayFilterSet -> filterSoftwareAppsByAuthors(feedSettings, since, defaultSince) - is GlobalTopNavPerRelayFilterSet -> filterSoftwareAppsGlobal(feedSettings, since, defaultSince) + is GlobalTopNavPerRelayFilterSet -> filterSoftwareAppsGlobal(feedSettings, since, defaultSince, blockedRelays) is HashtagTopNavPerRelayFilterSet -> filterSoftwareAppsByHashtag(feedSettings, since, defaultSince) is MutedAuthorsTopNavPerRelayFilterSet -> filterSoftwareAppsByMutedAuthors(feedSettings, since, defaultSince) else -> emptyList() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/subassemblies/FilterSoftwareAppsGlobal.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/subassemblies/FilterSoftwareAppsGlobal.kt index a37c99ba02..056c3a067f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/subassemblies/FilterSoftwareAppsGlobal.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/datasource/subassemblies/FilterSoftwareAppsGlobal.kt @@ -26,22 +26,40 @@ import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.Softw import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.SoftwareReleaseEvent import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl + +// zapstore's relay indexes the entire software-app catalog (NIP-82), so the +// global Apps feed always queries it in addition to the user's own relays — +// unless the user has explicitly added it to their blocked relay list. +val ZAPSTORE_APP_RELAY: NormalizedRelayUrl = "wss://relay.zapstore.dev".normalizeRelayUrl() fun filterSoftwareAppsGlobal( relays: GlobalTopNavPerRelayFilterSet, since: SincePerRelayMap?, defaultSince: Long? = null, + blockedRelays: Set = emptySet(), ): List { - if (relays.set.isEmpty()) return emptyList() + val relayUrls = + if (ZAPSTORE_APP_RELAY in blockedRelays) { + relays.set.keys + } else { + relays.set.keys + ZAPSTORE_APP_RELAY + } - return relays.set.map { - val sinceForRelay = since?.get(it.key)?.time ?: defaultSince + if (relayUrls.isEmpty()) return emptyList() + + return relayUrls.map { relay -> + val sinceForRelay = since?.get(relay)?.time ?: defaultSince RelayBasedFilter( - relay = it.key, + relay = relay, filter = Filter( kinds = listOf(SoftwareApplicationEvent.KIND, SoftwareReleaseEvent.KIND), - limit = 200, + // Keep the limit < 100. Relays such as zapstore's score filter + // specificity and treat a limit >= 100 (or none) as too vague, + // rejecting the whole REQ with "filters are too vague". + limit = 99, since = sinceForRelay, ), )