Merge pull request #3376 from vitorpamplona/claude/keen-franklin-h4kah5

feat: query zapstore's relay in the global Apps feed and keep its filter specific
This commit is contained in:
Vitor Pamplona
2026-06-25 21:32:20 -04:00
committed by GitHub
3 changed files with 39 additions and 7 deletions
@@ -44,7 +44,12 @@ class SoftwareAppsSubAssembler(
): List<RelayBasedFilter> {
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()
@@ -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<NormalizedRelayUrl> = emptySet(),
): List<RelayBasedFilter> =
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()
@@ -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<NormalizedRelayUrl> = emptySet(),
): List<RelayBasedFilter> {
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,
),
)