diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt index d70ee68a8..44ea6d2a9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowListOutboxOrProxyRelays.kt @@ -20,18 +20,14 @@ */ package com.vitorpamplona.amethyst.model.nip02FollowLists -import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState -import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -52,37 +48,16 @@ class FollowListOutboxOrProxyRelays( val cache: LocalCache, scope: CoroutineScope, ) { - fun getNIP65RelayListAddress(pubkey: HexKey) = AdvertisedRelayListEvent.createAddress(pubkey) - - fun getNIP65RelayListNote(pubkey: HexKey): AddressableNote = cache.getOrCreateAddressableNote(getNIP65RelayListAddress(pubkey)) - - fun getNIP65RelayListFlow(pubkey: HexKey): StateFlow = getNIP65RelayListNote(pubkey).flow().metadata.stateFlow - - fun getNIP65RelayList(pubkey: HexKey): AdvertisedRelayListEvent? = getNIP65RelayListNote(pubkey).event as? AdvertisedRelayListEvent - - fun allRelayListFlows(followList: Set): List> = followList.map { getNIP65RelayListFlow(it) } - - fun combineAllFlows(flows: List>): Flow> = - combine(flows) { relayListNotes: Array -> - relayListNotes - .mapNotNull { - (it.note.event as? AdvertisedRelayListEvent)?.writeRelaysNorm() - }.flatten() - .toSet() - } - @OptIn(ExperimentalCoroutinesApi::class) val outboxRelayFlow: StateFlow> = kind3Follows.flow - .transformLatest { - emitAll(combineAllFlows(allRelayListFlows(it.authors))) + .transformLatest { follows -> + emitAll( + OutboxRelayLoader(true).toAuthorsPerRelayFlow(follows.authors, cache) { it.keys }, + ) }.onStart { emit( - kind3Follows.flow.value.authors - .mapNotNull { - getNIP65RelayList(it)?.writeRelaysNorm() - }.flatten() - .toSet(), + OutboxRelayLoader(true).authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { it.keys }, ) }.distinctUntilChanged() .flowOn(Dispatchers.Default) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt index a00bd2bcc..b81e95124 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip02FollowLists/FollowsPerOutboxRelay.kt @@ -20,20 +20,15 @@ */ package com.vitorpamplona.amethyst.model.nip02FollowLists -import com.vitorpamplona.amethyst.model.AddressableNote -import com.vitorpamplona.amethyst.model.Constants import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState +import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent -import com.vitorpamplona.quartz.utils.mapOfSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -53,51 +48,16 @@ class FollowsPerOutboxRelay( val cache: LocalCache, scope: CoroutineScope, ) { - fun getNIP65RelayListAddress(pubkey: HexKey) = AdvertisedRelayListEvent.createAddress(pubkey) - - fun getNIP65RelayListNote(pubkey: HexKey): AddressableNote = cache.getOrCreateAddressableNote(getNIP65RelayListAddress(pubkey)) - - fun getNIP65RelayListFlow(pubkey: HexKey): StateFlow = getNIP65RelayListNote(pubkey).flow().metadata.stateFlow - - fun getNIP65RelayList(pubkey: HexKey): AdvertisedRelayListEvent? = getNIP65RelayListNote(pubkey).event as? AdvertisedRelayListEvent - - fun allRelayListFlows(followList: Set): List> = followList.map { getNIP65RelayListFlow(it) } - - fun combineAllRelayListFlows(flows: List>): Flow>> = - combine(flows) { relayListNotes: Array -> - mapOfSet { - relayListNotes.forEach { noteState -> - noteState.note.author?.pubkeyHex?.let { authorHex -> - val outboxRelayList = - getNIP65RelayList(authorHex)?.writeRelaysNorm() - ?: LocalCache.relayHints.hintsForKey(authorHex).ifEmpty { null } - ?: Constants.eventFinderRelays - outboxRelayList.forEach { relay -> - add(relay, authorHex) - } - } - } - } - } - @OptIn(ExperimentalCoroutinesApi::class) val outboxPerRelayFlow: StateFlow>> = kind3Follows.flow .transformLatest { - emitAll(combineAllRelayListFlows(allRelayListFlows(it.authors))) + emitAll( + OutboxRelayLoader().toAuthorsPerRelayFlow(it.authors, cache) { it }, + ) }.onStart { emit( - mapOfSet { - kind3Follows.flow.value.authors.map { authorHex -> - val outboxRelayList = - getNIP65RelayList(authorHex)?.writeRelaysNorm() - ?: LocalCache.relayHints.hintsForKey(authorHex).ifEmpty { null } - ?: Constants.eventFinderRelays - outboxRelayList.forEach { relay -> - add(relay, authorHex) - } - } - }, + OutboxRelayLoader().authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { it }, ) }.distinctUntilChanged() .flowOn(Dispatchers.Default) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/OutboxRelayLoader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/OutboxRelayLoader.kt index f33da000a..5b1b6f843 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/OutboxRelayLoader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/OutboxRelayLoader.kt @@ -32,75 +32,79 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine -class OutboxRelayLoader { - companion object { - private fun authorsPerRelay( - outboxRelayNotes: Array, - cache: LocalCache, - ): Map> = - mapOfSet { - outboxRelayNotes.forEach { outboxNote -> - val note = outboxNote.note +class OutboxRelayLoader( + val rawOutboxRelays: Boolean = false, +) { + fun authorsPerRelay( + outboxRelayNotes: Array, + cache: LocalCache, + ): Map> = + mapOfSet { + outboxRelayNotes.forEach { outboxNote -> + val note = outboxNote.note - val authorHex = - if (note is AddressableNote) { - note.address.pubKeyHex + val authorHex = + if (note is AddressableNote) { + note.address.pubKeyHex + } else { + note.author?.pubkeyHex + } + + if (authorHex != null) { + val relays = + if (rawOutboxRelays) { + (outboxNote.note.event as? AdvertisedRelayListEvent)?.writeRelaysNorm() ?: emptySet() } else { - note.author?.pubkeyHex - } - - if (authorHex != null) { - val relays = (outboxNote.note.event as? AdvertisedRelayListEvent)?.writeRelaysNorm() ?: cache.relayHints.hintsForKey(authorHex).ifEmpty { null } ?: Constants.eventFinderRelays + } - relays.forEach { - if (!it.url.startsWith("wss://feeds.nostr.band") && - !it.url.startsWith("wss://filter.nostr.wine") && - !it.url.startsWith("wss://nwc.primal.net") && - !it.url.startsWith("wss://relay.getalby.com") - ) { - add(it, authorHex) - } + relays.forEach { + if (!it.url.startsWith("wss://feeds.nostr.band") && + !it.url.startsWith("wss://filter.nostr.wine") && + !it.url.startsWith("wss://nwc.primal.net") && + !it.url.startsWith("wss://relay.getalby.com") + ) { + add(it, authorHex) } } } } - - fun authorsPerRelaySnapshot( - authors: Set, - cache: LocalCache, - transformation: (Map>) -> T, - ): T { - val noteMetadata = - authors - .map { pubkeyHex -> - cache - .getOrCreateAddressableNote(AdvertisedRelayListEvent.createAddress(pubkeyHex)) - .flow() - .metadata.stateFlow.value - }.toTypedArray() - return transformation(authorsPerRelay(noteMetadata, cache)) } - fun toAuthorsPerRelayFlow( - authors: Set, - cache: LocalCache, - transformation: (Map>) -> T, - ): Flow { - val noteMetadataFlows = - authors.map { pubkeyHex -> - val note = cache.getOrCreateAddressableNote(AdvertisedRelayListEvent.createAddress(pubkeyHex)) - note.flow().metadata.stateFlow - } + fun authorsPerRelaySnapshot( + authors: Set, + cache: LocalCache, + transformation: (Map>) -> T, + ): T { + val noteMetadata = + authors + .map { pubkeyHex -> + cache + .getOrCreateAddressableNote(AdvertisedRelayListEvent.createAddress(pubkeyHex)) + .flow() + .metadata.stateFlow.value + }.toTypedArray() + return transformation(authorsPerRelay(noteMetadata, cache)) + } - return if (noteMetadataFlows.isEmpty()) { - MutableStateFlow(transformation(emptyMap())) - } else { - combine(noteMetadataFlows) { outboxRelays -> - transformation(authorsPerRelay(outboxRelays, cache)) - } + fun toAuthorsPerRelayFlow( + authors: Set, + cache: LocalCache, + transformation: (Map>) -> T, + ): Flow { + val noteMetadataFlows = + authors.map { pubkeyHex -> + val note = cache.getOrCreateAddressableNote(AdvertisedRelayListEvent.createAddress(pubkeyHex)) + note.flow().metadata.stateFlow + } + + return if (noteMetadataFlows.isEmpty()) { + MutableStateFlow(transformation(emptyMap())) + } else { + combine(noteMetadataFlows) { outboxRelays -> + transformation(authorsPerRelay(outboxRelays, cache)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsByOutboxTopNavFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsByOutboxTopNavFilter.kt index b4f9bdbf6..e917a47fa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsByOutboxTopNavFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/allFollows/AllFollowsByOutboxTopNavFilter.kt @@ -81,7 +81,7 @@ class AllFollowsByOutboxTopNavFilter( override fun toPerRelayFlow(cache: LocalCache): Flow { val authorsPerRelay = if (authors != null) { - OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it } + OutboxRelayLoader().toAuthorsPerRelayFlow(authors, cache) { it } } else { MutableStateFlow(emptyMap()) } @@ -112,7 +112,7 @@ class AllFollowsByOutboxTopNavFilter( override fun startValue(cache: LocalCache): AllFollowsTopNavPerRelayFilterSet { val authorsPerRelay = if (authors != null) { - OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it } + OutboxRelayLoader().authorsPerRelaySnapshot(authors, cache) { it } } else { emptyMap() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/author/AuthorsByOutboxTopNavFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/author/AuthorsByOutboxTopNavFilter.kt index 63dc77fdb..96ae9d520 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/author/AuthorsByOutboxTopNavFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/author/AuthorsByOutboxTopNavFilter.kt @@ -52,7 +52,7 @@ class AuthorsByOutboxTopNavFilter( ) override fun toPerRelayFlow(cache: LocalCache): Flow { - val authorsPerRelay = OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it } + val authorsPerRelay = OutboxRelayLoader().toAuthorsPerRelayFlow(authors, cache) { it } return combine(authorsPerRelay, blockedRelays) { authors, blocked -> convert(authors.minus(blocked)) @@ -60,7 +60,7 @@ class AuthorsByOutboxTopNavFilter( } override fun startValue(cache: LocalCache): AuthorsTopNavPerRelayFilterSet { - val authorsPerRelay = OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it } + val authorsPerRelay = OutboxRelayLoader().authorsPerRelaySnapshot(authors, cache) { it } return convert(authorsPerRelay.minus(blockedRelays.value)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/community/SingleCommunityTopNavFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/community/SingleCommunityTopNavFilter.kt index 7b090f141..706b75fc9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/community/SingleCommunityTopNavFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/community/SingleCommunityTopNavFilter.kt @@ -67,7 +67,7 @@ class SingleCommunityTopNavFilter( if (authors != null) { // go by authors - val authorsPerRelay = OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it } + val authorsPerRelay = OutboxRelayLoader().toAuthorsPerRelayFlow(authors, cache) { it } return combine(authorsPerRelay, blockedRelays) { authorsPerRelay, blocked -> SingleCommunityTopNavPerRelayFilterSet( @@ -100,7 +100,7 @@ class SingleCommunityTopNavFilter( if (authors != null) { // go by authors - val authorsPerRelay = OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it } + val authorsPerRelay = OutboxRelayLoader().authorsPerRelaySnapshot(authors, cache) { it } return SingleCommunityTopNavPerRelayFilterSet( authorsPerRelay.minus(blockedRelays.value).mapValues { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/muted/MutedAuthorsByOutboxTopNavFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/muted/MutedAuthorsByOutboxTopNavFilter.kt index b2a5b68ea..76028ddbe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/muted/MutedAuthorsByOutboxTopNavFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/topNavFeeds/noteBased/muted/MutedAuthorsByOutboxTopNavFilter.kt @@ -52,7 +52,7 @@ class MutedAuthorsByOutboxTopNavFilter( ) override fun toPerRelayFlow(cache: LocalCache): Flow { - val authorsPerRelay = OutboxRelayLoader.toAuthorsPerRelayFlow(authors, cache) { it } + val authorsPerRelay = OutboxRelayLoader().toAuthorsPerRelayFlow(authors, cache) { it } return combine(authorsPerRelay, blockedRelays) { authors, blocked -> convert(authors.minus(blocked)) @@ -60,7 +60,7 @@ class MutedAuthorsByOutboxTopNavFilter( } override fun startValue(cache: LocalCache): MutedAuthorsTopNavPerRelayFilterSet { - val authorsPerRelay = OutboxRelayLoader.authorsPerRelaySnapshot(authors, cache) { it } + val authorsPerRelay = OutboxRelayLoader().authorsPerRelaySnapshot(authors, cache) { it } return convert(authorsPerRelay.minus(blockedRelays.value)) }