From accc8d4439bff5c9058a361ee243f6b4c7f158d1 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 9 Jul 2025 16:57:26 -0400 Subject: [PATCH] Brute forces the finding of events. --- .../amethyst/model/LocalCache.kt | 3 +- .../event/EventFinderFilterAssembler.kt | 2 + .../EventFinderFilterAssemblerSubscription.kt | 8 +- .../loaders/FilterMissingAddressables.kt | 117 +++++++++++++----- .../event/loaders/FilterMissingEvents.kt | 105 ++++++++++++---- .../user/loaders/FilterUserMetadataForKey.kt | 40 +++--- .../user/loaders/UserLoaderSubAssembler.kt | 7 +- .../subassemblies/FilterByAddress.kt | 35 ++++-- .../subassemblies/FilterByAuthor.kt | 6 +- .../subassemblies/FilterByEvent.kt | 35 +++++- .../SearchWatcherSubAssembler.kt | 18 +-- .../loggedIn/threadview/ThreadScreen.kt | 2 +- .../datasources/ThreadFilterAssembler.kt | 2 + .../ThreadFilterAssemblerSubscription.kt | 15 ++- .../FilterMissingEventsForThread.kt | 64 ++++++---- .../ThreadEventLoaderSubAssembler.kt | 3 +- .../quartz/nip01Core/relay/filters/Filter.kt | 28 ++--- 17 files changed, 345 insertions(+), 145 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 596f66923..632be4fdb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -95,6 +95,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.Entity import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent +import com.vitorpamplona.quartz.nip19Bech32.entities.NNote import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay @@ -2928,7 +2929,7 @@ object LocalCache : ILocalCache { } getOrCreateUser(nip19.hex) } - is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> { + is NNote -> { getOrCreateNote(nip19.hex) } is NEvent -> { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt index beaf7d251..c9a9685cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssembler.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler @@ -29,6 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient // This allows multiple screen to be listening to tags, even the same tag class EventFinderQueryState( val note: Note, + val account: Account, ) class EventFinderFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt index 938c514ea..d0ffe095e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -30,18 +31,19 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel fun EventFinderFilterAssemblerSubscription( note: Note, accountViewModel: AccountViewModel, -) = EventFinderFilterAssemblerSubscription(note, accountViewModel.dataSources().eventFinder) +) = EventFinderFilterAssemblerSubscription(note, accountViewModel.account, accountViewModel.dataSources().eventFinder) @Composable fun EventFinderFilterAssemblerSubscription( note: Note, + account: Account, dataSource: EventFinderFilterAssembler, ) { // different screens get different states // even if they are tracking the same tag. val state = - remember(note) { - EventFinderQueryState(note) + remember(note, account) { + EventFinderQueryState(note, account) } KeyDataSourceSubscription(state, dataSource) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingAddressables.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingAddressables.kt index c29bdaeb4..bcad63005 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingAddressables.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingAddressables.kt @@ -22,64 +22,113 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.LocalCache.relayHints import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState 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.addressables.Address -import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent +import com.vitorpamplona.quartz.utils.mapOfSet -fun filterMissingAddressables(keys: List): List? { - val missingAddressables = mutableSetOf
() +fun potentialRelaysToFindAddress(note: AddressableNote): Set { + val set = mutableSetOf() - keys.forEach { - if (it.note is AddressableNote && it.note.event == null) { - missingAddressables.add(it.note.address()) - } + set.addAll(LocalCache.relayHints.hintsForAddress(note.idHex)) - // loads threading that is event-based - it.note.replyTo?.forEach { - if (it is AddressableNote && it.event == null) { - missingAddressables.add(it.address()) - } + val isInChannel = note.channelHex() + if (isInChannel != null) { + LocalCache.checkGetOrCreateChannel(isInChannel)?.relays()?.forEach { + set.add(it) } } - return filterMissingAddressables(missingAddressables) + note.replyTo?.map { parentNote -> + set.addAll(parentNote.relays) + + parentNote + .channelHex() + ?.let { LocalCache.checkGetOrCreateChannel(it) } + ?.relays() + ?.forEach { set.add(it) } + + parentNote.author?.inboxRelays()?.let { set.addAll(it) } + } + + note.replies.map { childNote -> + set.addAll(childNote.relays) + + childNote + .channelHex() + ?.let { LocalCache.checkGetOrCreateChannel(it) } + ?.relays() + ?.forEach { set.add(it) } + + childNote.author?.outboxRelays()?.let { set.addAll(it) } + } + + note.reactions.map { reactionType -> + reactionType.value.forEach { childNote -> + set.addAll(childNote.relays) + childNote.author?.outboxRelays()?.let { set.addAll(it) } + } + } + + note.boosts.map { childNote -> + set.addAll(childNote.relays) + childNote.author?.outboxRelays()?.let { set.addAll(it) } + } + + return set } -fun filterMissingAddressables(missingAddressables: Set
): List { +fun filterMissingAddressables(keys: List): List? { + val addressesPerRelay = + mapOfSet { + keys.forEach { key -> + val default = key.account.followPlusAllMine.flow.value + if (key.note is AddressableNote && key.note.event == null) { + potentialRelaysToFindAddress(key.note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, key.note.address) + } + } + + // loads threading that is event-based + key.note.replyTo?.forEach { note -> + if (note is AddressableNote && note.event == null) { + potentialRelaysToFindAddress(note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, note.address) + } + } + } + } + } + + return filterMissingAddressables(addressesPerRelay) +} + +fun filterMissingAddressables(missingAddressables: Map>): List { if (missingAddressables.isEmpty()) return emptyList() - return missingAddressables.flatMap { aTag -> - val authorHomeRelayEventAddress = AdvertisedRelayListEvent.createAddressTag(aTag.pubKeyHex) - val authorHomeRelayEvent = (LocalCache.getAddressableNoteIfExists(authorHomeRelayEventAddress)?.event as? AdvertisedRelayListEvent) - - val authorHomeRelays = - authorHomeRelayEvent?.writeRelaysNorm()?.ifEmpty { null } - ?: LocalCache.relayHints.hintsForKey(aTag.pubKeyHex).ifEmpty { null } - ?: listOfNotNull(LocalCache.getUserIfExists(aTag.pubKeyHex)?.latestMetadataRelay) - - val relayHints = LocalCache.relayHints.hintsForAddress(aTag.toValue()) - - (authorHomeRelays + relayHints).toSet().map { - if (aTag.kind < 25000 && aTag.dTag.isBlank()) { + return missingAddressables.flatMap { relayEntry -> + relayEntry.value.map { address -> + if (address.kind < 25000 && address.dTag.isBlank()) { RelayBasedFilter( - relay = it, + relay = relayEntry.key, filter = Filter( - kinds = listOf(aTag.kind), - authors = listOf(aTag.pubKeyHex), + kinds = listOf(address.kind), + authors = listOf(address.pubKeyHex), limit = 1, ), ) } else { RelayBasedFilter( - relay = it, + relay = relayEntry.key, filter = Filter( - kinds = listOf(aTag.kind), - tags = mapOf("d" to listOf(aTag.dTag)), - authors = listOf(aTag.pubKeyHex), + kinds = listOf(address.kind), + tags = mapOf("d" to listOf(address.dTag)), + authors = listOf(address.pubKeyHex), limit = 1, ), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingEvents.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingEvents.kt index ced79d5a0..d526f358b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingEvents.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/loaders/FilterMissingEvents.kt @@ -22,48 +22,101 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.LocalCache.relayHints +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState -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.utils.mapOfSet -fun filterMissingEvents(keys: List): List? { - val missingEvents = mutableSetOf() +fun potentialRelaysToFindEvent(note: Note): Set { + val set = mutableSetOf() - keys.forEach { - if (it.note !is AddressableNote && it.note.event == null) { - missingEvents.add(it.note.idHex) - } + set.addAll(LocalCache.relayHints.hintsForEvent(note.idHex)) - // loads threading that is event-based - it.note.replyTo?.forEach { - if (it !is AddressableNote && it.event == null) { - missingEvents.add(it.idHex) - } + val isInChannel = note.channelHex() + if (isInChannel != null) { + LocalCache.checkGetOrCreateChannel(isInChannel)?.relays()?.forEach { + set.add(it) } } - return filterMissingEvents(missingEvents) + note.replyTo?.map { parentNote -> + set.addAll(parentNote.relays) + + parentNote + .channelHex() + ?.let { LocalCache.checkGetOrCreateChannel(it) } + ?.relays() + ?.forEach { set.add(it) } + + parentNote.author?.inboxRelays()?.let { set.addAll(it) } + } + + note.replies.map { childNote -> + set.addAll(childNote.relays) + + childNote + .channelHex() + ?.let { LocalCache.checkGetOrCreateChannel(it) } + ?.relays() + ?.forEach { set.add(it) } + + childNote.author?.outboxRelays()?.let { set.addAll(it) } + } + + note.reactions.map { reactionType -> + reactionType.value.forEach { childNote -> + set.addAll(childNote.relays) + childNote.author?.outboxRelays()?.let { set.addAll(it) } + } + } + + note.boosts.map { childNote -> + set.addAll(childNote.relays) + childNote.author?.outboxRelays()?.let { set.addAll(it) } + } + + return set } -fun filterMissingEvents(missingEventIds: Set): List { - if (missingEventIds.isEmpty()) return emptyList() - - val relayHints = +fun filterMissingEvents(keys: List): List? { + val eventsPerRelay = mapOfSet { - missingEventIds.forEach { eventId -> - LocalCache.relayHints.hintsForEvent(eventId).forEach { relayUrl -> - add(relayUrl, eventId) + keys.forEach { key -> + val default = key.account.followPlusAllMine.flow.value + + if (key.note !is AddressableNote && key.note.event == null) { + potentialRelaysToFindEvent(key.note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, key.note.idHex) + } + } + + // loads threading that is event-based + key.note.replyTo?.forEach { note -> + if (note !is AddressableNote && note.event == null) { + potentialRelaysToFindEvent(note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, note.idHex) + } + } } } } - return relayHints.map { - RelayBasedFilter( - relay = it.key, - filter = Filter(ids = it.value.sorted()), - ) + return filterMissingEvents(eventsPerRelay) +} + +fun filterMissingEvents(missingEventIds: Map>): List { + if (missingEventIds.isEmpty()) return emptyList() + + return missingEventIds.mapNotNull { + if (it.value.isNotEmpty()) { + RelayBasedFilter( + relay = it.key, + filter = Filter(ids = it.value.sorted()), + ) + } else { + null + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt index 7ce042af7..b2b659438 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/FilterUserMetadataForKey.kt @@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent 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.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.utils.mapOfSet import kotlin.collections.forEach @@ -36,35 +37,44 @@ val MetadataAndRelayListKinds = AdvertisedRelayListEvent.KIND, ) -fun filterFindUserMetadataForKey(author: HexKey): List = +fun filterFindUserMetadataForKey( + author: HexKey, + defaultRelays: Set, +): List = LocalCache.checkGetOrCreateUser(author)?.let { - filterFindUserMetadataForKey(setOf(it)) + filterFindUserMetadataForKey(setOf(it), defaultRelays) } ?: emptyList() -fun filterFindUserMetadataForKey(authors: Set): List { +fun filterFindUserMetadataForKey( + authors: Set, + defaultRelays: Set, +): List { val perRelayKeys = mapOfSet { authors.forEach { key -> val relays = - key.authorRelayList()?.writeRelaysNorm() + key.authorRelayList()?.writeRelaysNorm()?.ifEmpty { null } ?: LocalCache.relayHints.hintsForKey(key.pubkeyHex).ifEmpty { null } - ?: key.relaysBeingUsed.keys + ?: (key.relaysBeingUsed.keys + defaultRelays).toList() relays.forEach { add(it, key.pubkeyHex) } - // TODO, DESPERATION: TRY ALL THE CONNECTED RELAYS. } } - return perRelayKeys.map { - RelayBasedFilter( - relay = it.key, - filter = - Filter( - kinds = MetadataAndRelayListKinds, - authors = it.value.sorted(), - ), - ) + return perRelayKeys.mapNotNull { + if (it.value.isNotEmpty()) { + RelayBasedFilter( + relay = it.key, + filter = + Filter( + kinds = MetadataAndRelayListKinds, + authors = it.value.sorted(), + ), + ) + } else { + null + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt index 019847c5a..83c818126 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/loaders/UserLoaderSubAssembler.kt @@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEo import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl class UserLoaderSubAssembler( client: NostrClient, @@ -42,7 +43,11 @@ class UserLoaderSubAssembler( } } + val defaultRelays = mutableSetOf() + keys.mapTo(mutableSetOf()) { it.account }.forEach { + defaultRelays.addAll(it.followPlusAllMine.flow.value) + it.kind3FollowList.flow.value.authors.forEach { val user = LocalCache.getOrCreateUser(it) if (user.latestMetadata == null) { @@ -55,7 +60,7 @@ class UserLoaderSubAssembler( if (firstTimers.isEmpty()) return null - return filterFindUserMetadataForKey(firstTimers) + return filterFindUserMetadataForKey(firstTimers, defaultRelays) } override fun distinct(key: UserFinderQueryState) = key.user diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAddress.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAddress.kt index db63d07bb..7e7320fcb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAddress.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAddress.kt @@ -20,17 +20,30 @@ */ package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingAddressables -import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindAddress +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress +import com.vitorpamplona.quartz.utils.mapOfSet +import kotlin.collections.forEach +import kotlin.collections.ifEmpty -fun filterByAddress(address: NAddress) = - filterMissingAddressables( - setOf( - Address( - address.kind, - address.author, - address.dTag, - ), - ), - ) +fun filterByAddress( + address: NAddress, + default: Set, +): List { + val note = LocalCache.getOrCreateAddressableNote(address.address()) + + val list = + mapOfSet { + if (note.event == null) { + potentialRelaysToFindAddress(note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, note.address) + } + } + } + + return filterMissingAddressables(list) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt index 4be93be01..c3b2efd28 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByAuthor.kt @@ -22,5 +22,9 @@ package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassembli import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders.filterFindUserMetadataForKey import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -fun filterByAuthor(pubKey: HexKey) = filterFindUserMetadataForKey(pubKey) +fun filterByAuthor( + pubKey: HexKey, + defaultRelays: Set, +) = filterFindUserMetadataForKey(pubKey, defaultRelays) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByEvent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByEvent.kt index 950b392ae..e69b20dad 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByEvent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/FilterByEvent.kt @@ -20,7 +20,40 @@ */ package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies +import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.utils.mapOfSet +import kotlin.collections.forEach +import kotlin.collections.ifEmpty -fun filterByEvent(eventId: HexKey) = filterMissingEvents(setOf(eventId)) +fun filterByEvent( + eventId: HexKey, + default: Set, +): List { + val note = LocalCache.checkGetOrCreateNote(eventId) ?: return emptyList() + + val list = + mapOfSet { + if (note !is AddressableNote && note.event == null) { + potentialRelaysToFindEvent(note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, note.idHex) + } + } + + // loads threading that is event-based + note.replyTo?.forEach { parentNote -> + if (parentNote !is AddressableNote && note.event == null) { + potentialRelaysToFindEvent(note).ifEmpty { default }.forEach { relayUrl -> + add(relayUrl, note.idHex) + } + } + } + } + + return filterMissingEvents(list) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt index 39b58880b..02c29f671 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/subassemblies/SearchWatcherSubAssembler.kt @@ -55,25 +55,27 @@ class SearchWatcherSubAssembler( if (mySearchString.isBlank()) return null + val defaultRelays = key.account.followPlusAllMine.flow.value + val directFilters = runCatching { if (Hex.isHex(mySearchString)) { - val key = Hex.decode(mySearchString).toHexKey() - filterByAuthor(key) + filterByEvent(key) + val hexKey = Hex.decode(mySearchString).toHexKey() + filterByAuthor(hexKey, defaultRelays) + filterByEvent(hexKey, defaultRelays) } else { val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity if (parsed != null) { cache.consume(parsed) when (parsed) { - is NSec -> filterByAuthor(parsed.toPubKeyHex()) - is NPub -> filterByAuthor(parsed.hex) - is NProfile -> filterByAuthor(parsed.hex) - is NNote -> filterByEvent(parsed.hex) - is NEvent -> filterByEvent(parsed.hex) + is NSec -> filterByAuthor(parsed.toPubKeyHex(), defaultRelays) + is NPub -> filterByAuthor(parsed.hex, defaultRelays) + is NProfile -> filterByAuthor(parsed.hex, defaultRelays) + is NNote -> filterByEvent(parsed.hex, defaultRelays) + is NEvent -> filterByEvent(parsed.hex, defaultRelays) is NEmbed -> emptyList() is NRelay -> emptyList() - is NAddress -> filterByAddress(parsed) + is NAddress -> filterByAddress(parsed, defaultRelays) else -> emptyList() } } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt index 5f2158f6c..5ba92fc7a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt @@ -53,7 +53,7 @@ fun ThreadScreen( ) WatchLifecycleAndUpdateModel(feedViewModel) - ThreadFilterAssemblerSubscription(noteId, accountViewModel.dataSources().thread) + ThreadFilterAssemblerSubscription(noteId, accountViewModel) LoadNote(noteId, accountViewModel) { if (it != null) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt index 59659205f..1ac82c64e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssembler.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies.ThreadEventLoaderSubAssembler import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies.ThreadFilterSubAssembler @@ -29,6 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient // This allows multiple screen to be listening to tags, even the same tag class ThreadQueryState( val eventId: HexKey, + val account: Account, ) class ThreadFilterAssembler( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssemblerSubscription.kt index c0407747b..7954bda88 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/ThreadFilterAssemblerSubscription.kt @@ -22,19 +22,32 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.HexKey @Composable fun ThreadFilterAssemblerSubscription( eventId: HexKey, + accountViewModel: AccountViewModel, +) = ThreadFilterAssemblerSubscription( + eventId, + accountViewModel.account, + accountViewModel.dataSources().thread, +) + +@Composable +fun ThreadFilterAssemblerSubscription( + eventId: HexKey, + account: Account, filterAssembler: ThreadFilterAssembler, ) { // different screens get different states // even if they are tracking the same tag. val state = remember(eventId) { - ThreadQueryState(eventId) + ThreadQueryState(eventId, account) } KeyDataSourceSubscription(state, filterAssembler) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/FilterMissingEventsForThread.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/FilterMissingEventsForThread.kt index 2a7745143..791cadcdf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/FilterMissingEventsForThread.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/FilterMissingEventsForThread.kt @@ -24,34 +24,54 @@ import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.ThreadAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingAddressables import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents -import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindAddress +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindEvent import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter -import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.utils.mapOfSet +import kotlin.collections.forEach +import kotlin.collections.ifEmpty -fun filterMissingEventsForThread(threadInfo: ThreadAssembler.ThreadInfo): List { - val missingEvents = mutableSetOf() - val missingAddresses = mutableSetOf
() +fun filterMissingEventsForThread( + threadInfo: ThreadAssembler.ThreadInfo, + defaultRelays: Set, +): List { + val missingEvents = + mapOfSet { + if (threadInfo.root.event == null && threadInfo.root !is AddressableNote) { + potentialRelaysToFindEvent(threadInfo.root).ifEmpty { defaultRelays }.forEach { relayUrl -> + add(relayUrl, threadInfo.root.idHex) + } + } - if (threadInfo.root.event == null) { - if (threadInfo.root is AddressableNote) { - missingAddresses.add(threadInfo.root.address) - } else { - missingEvents.add(threadInfo.root.idHex) - } - } - - threadInfo.allNotes.forEach { - if (it.event == null) { - if (it is AddressableNote) { - missingAddresses.add(it.address) - } else { - missingEvents.add(it.idHex) + threadInfo.allNotes.forEach { + if (it !is AddressableNote && it.event == null) { + potentialRelaysToFindEvent(it).ifEmpty { defaultRelays }.forEach { relayUrl -> + add(relayUrl, it.idHex) + } + } } } - } - val missingEventsFilter = filterMissingEvents(missingEvents) ?: emptyList() - val missingAddressFilter = filterMissingAddressables(missingAddresses) ?: emptyList() + val missingAddresses = + mapOfSet { + if (threadInfo.root.event == null && threadInfo.root is AddressableNote) { + potentialRelaysToFindEvent(threadInfo.root).ifEmpty { defaultRelays }.forEach { relayUrl -> + add(relayUrl, threadInfo.root.address) + } + } + + threadInfo.allNotes.forEach { + if (it is AddressableNote && it.event == null) { + potentialRelaysToFindAddress(it).ifEmpty { defaultRelays }.forEach { relayUrl -> + add(relayUrl, it.address) + } + } + } + } + + val missingEventsFilter = filterMissingEvents(missingEvents) + val missingAddressFilter = filterMissingAddressables(missingAddresses) return missingEventsFilter + missingAddressFilter } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/ThreadEventLoaderSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/ThreadEventLoaderSubAssembler.kt index c6703e69d..5018c949a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/ThreadEventLoaderSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/datasources/subassembies/ThreadEventLoaderSubAssembler.kt @@ -45,7 +45,8 @@ class ThreadEventLoaderSubAssembler( since: SincePerRelayMap?, ): List? { val branches = ThreadAssembler().findThreadFor(key.eventId) ?: return null - return filterMissingEventsForThread(branches) + val defaultRelays = key.account.followPlusAllMine.flow.value + return filterMissingEventsForThread(branches, defaultRelays) } override fun id(key: ThreadQueryState) = key.eventId diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt index 5472bb9c0..90e5d8356 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.quartz.nip01Core.relay.filters import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address class Filter( val ids: List? = null, @@ -48,22 +47,13 @@ class Filter( search: String? = this.search, ) = Filter(ids, authors, kinds, tags, since, until, limit, search) - init { - // TODO: Remove this in production - ids?.forEach { - if (it.length != 64) throw IllegalArgumentException("Invalid id length $it on ${toJson()}") - } - authors?.forEach { - if (it.length != 64) throw IllegalArgumentException("Invalid author length $it on ${toJson()}") - } - tags?.get("#p")?.forEach { - if (it.length != 64) throw IllegalArgumentException("Invalid p-tag length $it on ${toJson()}") - } - tags?.get("#e")?.forEach { - if (it.length != 64) throw IllegalArgumentException("Invalid e-tag length $it on ${toJson()}") - } - tags?.get("#a")?.forEach { - if (Address.parse(it) != null) throw IllegalArgumentException("Invalid a-tag length $it on ${toJson()}") - } - } + fun isFilledFilter() = + (ids != null && ids.isNotEmpty()) || + (authors != null && authors.isNotEmpty()) || + (kinds != null && kinds.isNotEmpty()) || + (tags != null && tags.isNotEmpty()) || + (since != null) || + (until != null) || + (limit != null) || + (search != null && search.isNotEmpty()) }