mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
fix: narrow relay-group follows/authors REQ instead of broad directory pull
A follows/authors filter no longer pulls every group on the relay and filters
client-side. It now emits, per relay, the three narrowed REQs a relay-signed
group is discoverable by:
1. {kinds:[39000], authors:<follows>} — the relay signing-key is a follow
2. {kinds:[39001,39002], #p:<follows>} — a follow is an admin/member
3. {kinds:[39000], #d:<roster group-ids>} — metadata backfill for (2)
(3) reads the group-ids of cached 39001/39002 rosters that already mention a
follow (empty on the first pass, filled once (2)'s events land and the
sub-assembler re-invalidates), since a #p roster hit doesn't carry the 39000.
ByFollows routes through the shared per-relay author builder (mirrors Git);
muted-authors likewise. Global / communities keep the broad directory pull,
since they carry no author dimension to narrow on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5MLY4hq5LXJ2D5WeLRyXj
This commit is contained in:
+78
-5
@@ -20,14 +20,85 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
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.tags.dTag.DTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupAdminsEvent
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMembersEvent
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
|
||||
|
||||
// Authors can't be pushed into the REQ (a group's 39000 is signed by its relay, never a follow),
|
||||
// so an author filter contributes only its RELAY set; the directory is pulled per relay and the
|
||||
// dal keeps the groups whose relay-key/admins/members intersect the selected authors.
|
||||
/**
|
||||
* A group's kind-39000 is relay-signed, so a follow shows up in three distinct ways, and each is a
|
||||
* real relay-side REQ (not a broad "pull everything" pass):
|
||||
* 1. the follow IS the relay signing key → `{kinds:[39000], authors:<follows>}` (metadata included);
|
||||
* 2. a follow is an admin/member → `{kinds:[39001,39002], #p:<follows>}` (standard p-tag filter);
|
||||
* 3. metadata backfill for the groups (2) surfaced → `{kinds:[39000], #d:<their group-ids>}`, read
|
||||
* from the rosters already in cache (empty on the first pass, filled once (2)'s events land and
|
||||
* the sub-assembler re-invalidates).
|
||||
*/
|
||||
fun filterRelayGroupsByAuthors(
|
||||
relay: NormalizedRelayUrl,
|
||||
authors: Set<HexKey>,
|
||||
since: Long? = null,
|
||||
): List<RelayBasedFilter> {
|
||||
if (authors.isEmpty()) return emptyList()
|
||||
val authorList = authors.sorted()
|
||||
|
||||
val filters =
|
||||
mutableListOf(
|
||||
// (1) groups whose relay signing-key is a follow
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
authors = authorList,
|
||||
kinds = listOf(GroupMetadataEvent.KIND),
|
||||
limit = 200,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
// (2) groups where a follow is an admin (39001) or member (39002)
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(GroupAdminsEvent.KIND, GroupMembersEvent.KIND),
|
||||
tags = mapOf(PTag.TAG_NAME to authorList),
|
||||
limit = 200,
|
||||
since = since,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// (3) backfill 39000 metadata for the roster-discovered groups already known on this relay.
|
||||
val rosterGroupIds =
|
||||
LocalCache
|
||||
.getRelayGroupChannelsOnRelay(relay)
|
||||
.filter { channel -> channel.admins.any { it.pubKey in authors } || channel.members.any { it in authors } }
|
||||
.map { it.groupId.id }
|
||||
if (rosterGroupIds.isNotEmpty()) {
|
||||
filters +=
|
||||
RelayBasedFilter(
|
||||
relay = relay,
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(GroupMetadataEvent.KIND),
|
||||
tags = mapOf(DTag.TAG_NAME to rosterGroupIds),
|
||||
limit = 200,
|
||||
since = since,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
fun filterRelayGroupsByAuthors(
|
||||
authorSet: AuthorsTopNavPerRelayFilterSet,
|
||||
@@ -37,8 +108,9 @@ fun filterRelayGroupsByAuthors(
|
||||
if (authorSet.set.isEmpty()) return emptyList()
|
||||
|
||||
return authorSet.set.flatMap {
|
||||
filterRelayGroupsDirectory(
|
||||
filterRelayGroupsByAuthors(
|
||||
relay = it.key,
|
||||
authors = it.value.authors,
|
||||
since = since?.get(it.key)?.time ?: defaultSince,
|
||||
)
|
||||
}
|
||||
@@ -52,8 +124,9 @@ fun filterRelayGroupsByMutedAuthors(
|
||||
if (authorSet.set.isEmpty()) return emptyList()
|
||||
|
||||
return authorSet.set.flatMap {
|
||||
filterRelayGroupsDirectory(
|
||||
filterRelayGroupsByAuthors(
|
||||
relay = it.key,
|
||||
authors = it.value.authors,
|
||||
since = since?.get(it.key)?.time ?: defaultSince,
|
||||
)
|
||||
}
|
||||
|
||||
+7
-7
@@ -24,9 +24,9 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavP
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
|
||||
// A follows filter pulls the whole directory (metadata + rosters) from each relay the follow set
|
||||
// routes to; the dal keeps groups where a follow is the relay key, an admin, or a member, and any
|
||||
// topic/geo groups the AllFollows big-OR also selects (all present in the directory pull).
|
||||
// A follows filter routes to each relay the follow set uses and asks, per relay, for the three
|
||||
// ways a follow surfaces in a relay-signed group: the relay signing-key is a follow, or a follow
|
||||
// is an admin (39001) or a member (39002). See [filterRelayGroupsByAuthors].
|
||||
fun filterRelayGroupsByFollows(
|
||||
followsSet: AllFollowsTopNavPerRelayFilterSet,
|
||||
since: SincePerRelayMap?,
|
||||
@@ -35,9 +35,9 @@ fun filterRelayGroupsByFollows(
|
||||
if (followsSet.set.isEmpty()) return emptyList()
|
||||
|
||||
return followsSet.set.flatMap {
|
||||
filterRelayGroupsDirectory(
|
||||
relay = it.key,
|
||||
since = since?.get(it.key)?.time ?: defaultSince,
|
||||
)
|
||||
val relaySince = since?.get(it.key)?.time ?: defaultSince
|
||||
it.value.authors?.let { authors ->
|
||||
filterRelayGroupsByAuthors(it.key, authors, relaySince)
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user