mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
refactor: move per-note event finder to commons on the new upstream model (Phase 3)
Move EventFinderFilterAssembler(+QueryState), the loaders (NoteEventLoader, FilterMissingEvents, FilterMissingAddressables, AddressableAuthorRelayLoader) and watchers (EventWatcher, FilterRepliesAndReactionsToNotes/Addresses) from amethyst/reqCommand/event/ into commons/relayClient/event/, mirroring the Phase 2 user-finder move. - EventFinderQueryState carries the narrow UserFinderAccount; DROPS AccountScopedQuery. Attribution stays inline via userFinderPubkeyHex; ExplainedFilter/SubPurpose tags (REFERENCED_EVENTS / ENGAGEMENT) preserved. - cache: LocalCache -> ICacheProvider threaded into NoteEventLoaderSubAssembler + the FilterMissing* functions (which called the LocalCache singleton statically); getOrCreateUser is now nullable at these sites. Added ICacheProvider.checkGetOrCreateUser default; LocalCache.checkGetOrCreateUser now overrides it. - SingleSubNoEoseCacheEoseManager moved to commons (account-agnostic accountPubKeyOf); its two amethyst callers keep attribution via a new amethyst subclass AccountScopedSingleSubNoEoseCacheEoseManager (ChannelLoader) or the plain commons base (NWCPaymentWatcher, which never attributed). Android unchanged via EventFinderShims.kt (typealiases + AccountViewModel overload); EventObservers.kt stays in amethyst. New commons EventFinderFilterAssemblerSubscription (LocalEventFinder + (note) overload reusing LocalUserFinderAccount) for Desktop. Updated the direct loader-function callers (search/hashtag/thread sub-assemblers + the moved test) to import from commons and pass LocalCache. Green: commons JVM + iOS purity, :amethyst compilePlayDebugKotlin, :desktopApp compile, spotless. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
983fc1aab2
commit
4d31cd7d25
@@ -679,7 +679,7 @@ object LocalCache : ILocalCache, ICacheProvider, Dao {
|
||||
|
||||
fun observeLatestNote(filter: Filter) = observeNotes(filter).map { it.firstOrNull() }
|
||||
|
||||
fun checkGetOrCreateUser(key: String): User? = runCatching { getOrCreateUser(key) }.getOrNull()
|
||||
override fun checkGetOrCreateUser(key: String): User? = runCatching { getOrCreateUser(key) }.getOrNull()
|
||||
|
||||
fun load(keys: List<String>): List<User> = keys.mapNotNull(::checkGetOrCreateUser)
|
||||
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
|
||||
/**
|
||||
* Amethyst variant of [SingleSubNoEoseCacheEoseManager] that restores single-account
|
||||
* attribution for [AccountScopedQuery] keys.
|
||||
*
|
||||
* The commons base is account-agnostic (attribution defaults to null) so it can live in
|
||||
* commonMain. Query states that carry an [Account] (home feed, channels, notifications, …)
|
||||
* subclass this so their single-account REQs still show up attributed in "Active Relay
|
||||
* Subscriptions".
|
||||
*
|
||||
* Keyed on [AccountScopedQuery] rather than a concrete query-state type: the home feed uses
|
||||
* HomeQueryState, notifications use AccountQueryState, and checking one concrete class filed the
|
||||
* other under "not attributed" despite both being built from a single account's data.
|
||||
*/
|
||||
abstract class AccountScopedSingleSubNoEoseCacheEoseManager<T>(
|
||||
client: INostrClient,
|
||||
allKeys: () -> Set<T>,
|
||||
invalidateAfterEose: Boolean = false,
|
||||
) : SingleSubNoEoseCacheEoseManager<T>(client, allKeys, invalidateAfterEose) {
|
||||
override fun accountPubKeyOf(key: Any?): String? = (key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex
|
||||
}
|
||||
+2
-2
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.AccountScopedSingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
@@ -37,7 +37,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
class ChannelLoaderSubAssembler(
|
||||
client: INostrClient,
|
||||
allKeys: () -> Set<ChannelFinderQueryState>,
|
||||
) : SingleSubNoEoseCacheEoseManager<ChannelFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||
) : AccountScopedSingleSubNoEoseCacheEoseManager<ChannelFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||
override fun updateFilter(keys: List<ChannelFinderQueryState>): List<RelayBasedFilter> = filterMissingChannelsById(keys)
|
||||
|
||||
override fun distinct(key: ChannelFinderQueryState) = key.channel
|
||||
|
||||
+20
-20
@@ -21,30 +21,30 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
/**
|
||||
* Back-compat aliases: the per-note event finder moved to commons
|
||||
* (`com.vitorpamplona.amethyst.commons.relayClient.event`). Existing Android call
|
||||
* sites that reference these by their old names resolve here.
|
||||
*/
|
||||
typealias EventFinderFilterAssembler = com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderFilterAssembler
|
||||
|
||||
typealias EventFinderQueryState = com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
|
||||
/**
|
||||
* Android convenience overload: pulls the account + shared event-finder data source
|
||||
* out of [accountViewModel] and delegates to the commons subscription. `Account`
|
||||
* is-a `UserFinderAccount`, so no adaptation is needed.
|
||||
*/
|
||||
@Composable
|
||||
fun EventFinderFilterAssemblerSubscription(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = 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, account) {
|
||||
EventFinderQueryState(note, account)
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
}
|
||||
) = EventFinderFilterAssemblerSubscription(
|
||||
note,
|
||||
accountViewModel.account,
|
||||
accountViewModel.dataSources().eventFinder,
|
||||
)
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
|
||||
|
||||
+3
-3
@@ -20,9 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.filterMissingAddressables
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.potentialRelaysToFindAddress
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingAddressables
|
||||
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
|
||||
@@ -37,7 +37,7 @@ fun filterByAddress(
|
||||
val list =
|
||||
mapOfSet {
|
||||
if (note.event == null) {
|
||||
potentialRelaysToFindAddress(note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindAddress(LocalCache, note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, note.address)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -20,10 +20,10 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.filterMissingEvents
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.potentialRelaysToFindEvent
|
||||
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
|
||||
@@ -38,7 +38,7 @@ fun filterByEvent(
|
||||
val list =
|
||||
mapOfSet {
|
||||
if (note !is AddressableNote && note.event == null) {
|
||||
potentialRelaysToFindEvent(note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(LocalCache, note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, note.idHex)
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ fun filterByEvent(
|
||||
// loads threading that is event-based
|
||||
note.replyTo?.forEach { parentNote ->
|
||||
if (parentNote !is AddressableNote && note.event == null) {
|
||||
potentialRelaysToFindEvent(note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(LocalCache, note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, note.idHex)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,12 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.filterMissingEvents
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.potentialRelaysToFindEvent
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
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.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -93,7 +93,7 @@ fun filterHashtagLabels(
|
||||
val target = LocalCache.getNoteIfExists(targetId)
|
||||
if (target?.event == null) {
|
||||
val targetNote = LocalCache.getOrCreateNote(targetId)
|
||||
potentialRelaysToFindEvent(targetNote).ifEmpty { relays }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(LocalCache, targetNote).ifEmpty { relays }.forEach { relayUrl ->
|
||||
add(relayUrl, targetId)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -21,11 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.ThreadAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.filterMissingAddressables
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.filterMissingEvents
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.potentialRelaysToFindAddress
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.potentialRelaysToFindEvent
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingAddressables
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindAddress
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindEvent
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||
@@ -37,14 +38,14 @@ fun filterMissingEventsForThread(
|
||||
val missingEvents =
|
||||
mapOfSet {
|
||||
if (threadInfo.root.event == null && threadInfo.root !is AddressableNote) {
|
||||
potentialRelaysToFindEvent(threadInfo.root).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(LocalCache, threadInfo.root).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
add(relayUrl, threadInfo.root.idHex)
|
||||
}
|
||||
}
|
||||
|
||||
threadInfo.allNotes.forEach {
|
||||
if (it !is AddressableNote && it.event == null) {
|
||||
potentialRelaysToFindEvent(it).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(LocalCache, it).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
add(relayUrl, it.idHex)
|
||||
}
|
||||
}
|
||||
@@ -59,14 +60,14 @@ fun filterMissingEventsForThread(
|
||||
// note's aTag idHex into the hex-keyed event-hint index, which throws
|
||||
// on the non-hex string and kills the whole filter build — leaving a
|
||||
// thread opened on an uncached naddr permanently unfetched.
|
||||
potentialRelaysToFindAddress(rootNote).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
potentialRelaysToFindAddress(LocalCache, rootNote).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
add(relayUrl, rootNote.address)
|
||||
}
|
||||
}
|
||||
|
||||
threadInfo.allNotes.forEach {
|
||||
if (it is AddressableNote && it.event == null) {
|
||||
potentialRelaysToFindAddress(it).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
potentialRelaysToFindAddress(LocalCache, it).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||
add(relayUrl, it.address)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -20,11 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.AddressableAuthorRelayLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
Vendored
+10
@@ -142,5 +142,15 @@ interface ICacheProvider {
|
||||
*/
|
||||
fun getOrCreateUser(pubkey: HexKey): User?
|
||||
|
||||
/**
|
||||
* Gets or creates a User by public key hex, swallowing any failure.
|
||||
* Used by the event-finder relay-hint scan, which touches many potentially
|
||||
* malformed pubkeys and must never throw mid-scan.
|
||||
*
|
||||
* @param key The user's public key in hex format
|
||||
* @return The User (existing or newly created), or null on failure
|
||||
*/
|
||||
fun checkGetOrCreateUser(key: HexKey): User? = runCatching { getOrCreateUser(key) }.getOrNull()
|
||||
|
||||
fun justConsumeMyOwnEvent(event: Event): Boolean
|
||||
}
|
||||
|
||||
+6
-7
@@ -18,11 +18,9 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.eoseManagers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.eoseManagers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.attributedTo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
@@ -85,9 +83,10 @@ abstract class SingleSubNoEoseCacheEoseManager<T>(
|
||||
/**
|
||||
* The account behind [key], when the key is account-scoped. Null for keys about other users.
|
||||
*
|
||||
* Keyed on [AccountScopedQuery] rather than a concrete query-state type: the home feed uses
|
||||
* HomeQueryState, notifications use AccountQueryState, and checking one concrete class filed the
|
||||
* other under "not attributed" despite both being built from a single account's data.
|
||||
* Account-agnostic in commons: front ends that want single-account attribution override this
|
||||
* (see the amethyst `AccountScopedSingleSubNoEoseCacheEoseManager`, which reads
|
||||
* `(key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex`). The default returns null,
|
||||
* so pooled / cross-account subscriptions are filed as "not attributed".
|
||||
*/
|
||||
private fun accountPubKeyOf(key: Any?): String? = (key as? AccountScopedQuery)?.account?.userProfile()?.pubkeyHex
|
||||
open fun accountPubKeyOf(key: Any?): String? = null
|
||||
}
|
||||
+12
-13
@@ -18,36 +18,35 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.AccountScopedQuery
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.AddressableAuthorRelayLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers.EventWatcherSubAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.AddressableAuthorRelayLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.loaders.NoteEventLoaderSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.watchers.EventWatcherSubAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderAccount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
|
||||
// This allows multiple screen to be listening to tags, even the same tag
|
||||
@Stable
|
||||
class EventFinderQueryState(
|
||||
val note: Note,
|
||||
override val account: Account,
|
||||
) : AccountScopedQuery
|
||||
val account: UserFinderAccount,
|
||||
)
|
||||
|
||||
@Stable
|
||||
class EventFinderFilterAssembler(
|
||||
client: INostrClient,
|
||||
cache: LocalCache,
|
||||
cache: ICacheProvider,
|
||||
userFinder: UserFinderFilterAssembler,
|
||||
) : ComposeSubscriptionManager<EventFinderQueryState>() {
|
||||
val group =
|
||||
listOf(
|
||||
NoteEventLoaderSubAssembler(client, ::allKeys),
|
||||
NoteEventLoaderSubAssembler(client, cache, ::allKeys),
|
||||
EventWatcherSubAssembler(client, ::allKeys),
|
||||
AddressableAuthorRelayLoaderSubAssembler(cache, ::allKeys, userFinder),
|
||||
)
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.LocalUserFinderAccount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderAccount
|
||||
|
||||
/**
|
||||
* The shared per-note event data source (reactions / zaps / reposts / replies /
|
||||
* OTS / references) for the current front end. Provided once near the composition
|
||||
* root (Android via AppModules, Desktop via its subscriptions coordinator).
|
||||
* Reading it without a provider is a programming error — the per-note observers
|
||||
* must never be reachable from a composition that has no relay client (e.g. the
|
||||
* Android `:napplet` sandbox process).
|
||||
*
|
||||
* The *account* half is reused from the user-finder: [LocalUserFinderAccount]
|
||||
* already carries the narrow relay-hint seam the event loaders need.
|
||||
*/
|
||||
val LocalEventFinder =
|
||||
staticCompositionLocalOf<EventFinderFilterAssembler> {
|
||||
error("LocalEventFinder not provided")
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to relay updates for [note]'s interactions (reactions, zaps,
|
||||
* reposts, replies, …) for as long as this composable is in composition,
|
||||
* coalesced with every other on-screen note into batched REQs by [dataSource].
|
||||
*
|
||||
* Like the user-finder, because a `LazyColumn` composes only the visible window
|
||||
* (+ a small prefetch buffer) this means "load interactions only for notes
|
||||
* currently on screen" — [LifecycleAwareKeyDataSourceSubscription] unsubscribes
|
||||
* ~30s after the row leaves composition or the app is backgrounded.
|
||||
*/
|
||||
@Composable
|
||||
fun EventFinderFilterAssemblerSubscription(
|
||||
note: Note,
|
||||
account: UserFinderAccount,
|
||||
dataSource: EventFinderFilterAssembler,
|
||||
) {
|
||||
// Different screens get their own query-state instance even when tracking
|
||||
// the same note; the assembler dedups to one REQ per note.
|
||||
val state =
|
||||
remember(note, account) {
|
||||
EventFinderQueryState(note, account)
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience overload that reads the front end's [LocalEventFinder] and
|
||||
* [LocalUserFinderAccount] from the composition.
|
||||
*/
|
||||
@Composable
|
||||
fun EventFinderFilterAssemblerSubscription(note: Note) {
|
||||
EventFinderFilterAssemblerSubscription(
|
||||
note = note,
|
||||
account = LocalUserFinderAccount.current,
|
||||
dataSource = LocalEventFinder.current,
|
||||
)
|
||||
}
|
||||
+8
-8
@@ -18,15 +18,15 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.IEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.service.BundledUpdate
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
|
||||
@@ -41,7 +41,7 @@ import kotlinx.coroutines.IO
|
||||
* relay list arrives, [EventFinderFilterAssembler] is invalidated and can query the correct relay.
|
||||
*/
|
||||
class AddressableAuthorRelayLoaderSubAssembler(
|
||||
val cache: LocalCache,
|
||||
val cache: ICacheProvider,
|
||||
val allKeys: () -> Set<EventFinderQueryState>,
|
||||
val userFinder: UserFinderFilterAssembler,
|
||||
) : IEoseManager {
|
||||
@@ -69,7 +69,7 @@ class AddressableAuthorRelayLoaderSubAssembler(
|
||||
val note = key.note
|
||||
if (note is AddressableNote && note.event == null) {
|
||||
val author = cache.getOrCreateUser(note.address.pubKeyHex)
|
||||
if (author.authorRelayList() == null) {
|
||||
if (author != null && author.authorRelayList() == null) {
|
||||
needed.add(UserFinderQueryState(author, key.account))
|
||||
}
|
||||
}
|
||||
+20
-14
@@ -18,33 +18,36 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||
|
||||
fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl> {
|
||||
fun potentialRelaysToFindAddress(
|
||||
cache: ICacheProvider,
|
||||
note: AddressableNote,
|
||||
): Set<NormalizedRelayUrl> {
|
||||
val set = mutableSetOf<NormalizedRelayUrl>()
|
||||
|
||||
LocalCache.getOrCreateUser(note.address.pubKeyHex).outboxRelays()?.let {
|
||||
cache.getOrCreateUser(note.address.pubKeyHex)?.outboxRelays()?.let {
|
||||
set.addAll(it)
|
||||
}
|
||||
|
||||
set.addAll(LocalCache.relayHints.hintsForAddress(note.idHex))
|
||||
set.addAll(cache.relayHints.hintsForAddress(note.idHex))
|
||||
|
||||
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
||||
cache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
||||
|
||||
note.replyTo?.forEach { parentNote ->
|
||||
set.addAll(parentNote.relays)
|
||||
|
||||
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
||||
cache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
||||
|
||||
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
|
||||
}
|
||||
@@ -52,7 +55,7 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
|
||||
note.replies.forEach { childNote ->
|
||||
set.addAll(childNote.relays)
|
||||
|
||||
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
||||
cache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
||||
|
||||
childNote.author?.outboxRelays()?.let { set.addAll(it) }
|
||||
}
|
||||
@@ -72,13 +75,16 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
|
||||
return set
|
||||
}
|
||||
|
||||
fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBasedFilter> {
|
||||
fun filterMissingAddressables(
|
||||
cache: ICacheProvider,
|
||||
keys: List<EventFinderQueryState>,
|
||||
): List<RelayBasedFilter> {
|
||||
val addressesPerRelay =
|
||||
mapOfSet {
|
||||
keys.forEach { key ->
|
||||
val default = key.account.followPlusAllMineWithSearch.flow.value
|
||||
val default = key.account.followPlusAllMineWithSearchRelays()
|
||||
if (key.note is AddressableNote && key.note.event == null) {
|
||||
potentialRelaysToFindAddress(key.note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindAddress(cache, key.note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, key.note.address)
|
||||
}
|
||||
}
|
||||
@@ -86,7 +92,7 @@ fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBase
|
||||
// loads threading that is event-based
|
||||
key.note.replyTo?.forEach { note ->
|
||||
if (note is AddressableNote && note.event == null) {
|
||||
potentialRelaysToFindAddress(note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindAddress(cache, note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, note.address)
|
||||
}
|
||||
}
|
||||
+22
-16
@@ -18,33 +18,36 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||
|
||||
fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
||||
fun potentialRelaysToFindEvent(
|
||||
cache: ICacheProvider,
|
||||
note: Note,
|
||||
): Set<NormalizedRelayUrl> {
|
||||
val set = mutableSetOf<NormalizedRelayUrl>()
|
||||
|
||||
set.addAll(LocalCache.relayHints.hintsForEvent(note.idHex))
|
||||
set.addAll(cache.relayHints.hintsForEvent(note.idHex))
|
||||
|
||||
note.author?.outboxRelays()?.let { set.addAll(it) }
|
||||
|
||||
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
||||
cache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
||||
|
||||
note.replyTo?.forEach { parentNote ->
|
||||
set.addAll(parentNote.relays)
|
||||
|
||||
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
||||
cache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
||||
|
||||
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
|
||||
}
|
||||
@@ -52,7 +55,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
||||
note.replies.forEach { childNote ->
|
||||
set.addAll(childNote.relays)
|
||||
|
||||
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
||||
cache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
||||
|
||||
childNote.author?.outboxRelays()?.let { set.addAll(it) }
|
||||
}
|
||||
@@ -81,7 +84,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
||||
val noteEvent = parent.event
|
||||
if (noteEvent is PubKeyHintProvider) {
|
||||
noteEvent.linkedPubKeys().forEach { potentialAuthor ->
|
||||
LocalCache.checkGetOrCreateUser(potentialAuthor)?.let { potentialAuthor ->
|
||||
cache.checkGetOrCreateUser(potentialAuthor)?.let { potentialAuthor ->
|
||||
potentialAuthor.outboxRelays()?.let { set.addAll(it) }
|
||||
potentialAuthor.inboxRelays()?.let { set.addAll(it) }
|
||||
}
|
||||
@@ -98,18 +101,21 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
||||
return set
|
||||
}
|
||||
|
||||
fun filterMissingEvents(keys: List<EventFinderQueryState>): List<RelayBasedFilter> {
|
||||
fun filterMissingEvents(
|
||||
cache: ICacheProvider,
|
||||
keys: List<EventFinderQueryState>,
|
||||
): List<RelayBasedFilter> {
|
||||
val eventsPerRelay =
|
||||
mapOfSet {
|
||||
keys.forEach { key ->
|
||||
val default = key.account.followPlusAllMineWithSearch.flow.value
|
||||
val default = key.account.followPlusAllMineWithSearchRelays()
|
||||
|
||||
if (key.note !is AddressableNote && key.note.event == null) {
|
||||
potentialRelaysToFindEvent(key.note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(cache, key.note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, key.note.idHex)
|
||||
}
|
||||
|
||||
key.account.searchRelayList.flow.value.forEach { relayUrl ->
|
||||
key.account.searchRelays().forEach { relayUrl ->
|
||||
add(relayUrl, key.note.idHex)
|
||||
}
|
||||
}
|
||||
@@ -117,7 +123,7 @@ fun filterMissingEvents(keys: List<EventFinderQueryState>): List<RelayBasedFilte
|
||||
// loads threading that is event-based
|
||||
key.note.replyTo?.forEach { note ->
|
||||
if (note !is AddressableNote && note.event == null) {
|
||||
potentialRelaysToFindEvent(note).ifEmpty { default }.forEach { relayUrl ->
|
||||
potentialRelaysToFindEvent(cache, note).ifEmpty { default }.forEach { relayUrl ->
|
||||
add(relayUrl, note.idHex)
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -18,21 +18,28 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.loaders
|
||||
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
|
||||
class NoteEventLoaderSubAssembler(
|
||||
client: INostrClient,
|
||||
val cache: ICacheProvider,
|
||||
allKeys: () -> Set<EventFinderQueryState>,
|
||||
) : SingleSubNoEoseCacheEoseManager<EventFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||
override fun updateFilter(keys: List<EventFinderQueryState>) =
|
||||
listOfNotNull(
|
||||
filterMissingEvents(keys),
|
||||
filterMissingAddressables(keys),
|
||||
filterMissingEvents(cache, keys),
|
||||
filterMissingAddressables(cache, keys),
|
||||
).flatten()
|
||||
|
||||
override fun distinct(key: EventFinderQueryState) = key.note
|
||||
|
||||
// Attribute to the account that owns this subscription, when a single account is watching.
|
||||
// Deduped by pubkey hex, not by account identity, so two objects for the same logged-in user
|
||||
// don't look like two accounts and suppress attribution.
|
||||
override fun accountPubKeyOf(key: Any?): String? = (key as? EventFinderQueryState)?.account?.userFinderPubkeyHex
|
||||
}
|
||||
+8
-8
@@ -18,15 +18,15 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubEoseManager
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.amethyst.service.relays.MutableTime
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.EOSEAccountFast
|
||||
import com.vitorpamplona.amethyst.commons.relays.MutableTime
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
@@ -67,7 +67,7 @@ class EventWatcherSubAssembler(
|
||||
// the same logged-in user would look like two accounts and suppress attribution entirely.
|
||||
val soleAccountPubKey =
|
||||
keys
|
||||
.mapTo(mutableSetOf()) { it.account.userProfile().pubkeyHex }
|
||||
.mapTo(mutableSetOf()) { it.account.userFinderPubkeyHex }
|
||||
.singleOrNull()
|
||||
|
||||
return groupByRelayPresence(lastNotesOnFilter, latestEOSEs)
|
||||
+3
-3
@@ -18,12 +18,12 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
+3
-3
@@ -20,12 +20,12 @@
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
Reference in New Issue
Block a user