From a0cb2a7db6f53537ed2674d521944cda27b4a649 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 20 Oct 2025 20:22:20 -0400 Subject: [PATCH] Creates a default relay list for indexers in new accounts --- .../amethyst/LocalPreferences.kt | 5 +++ .../amethyst/model/AccountSettings.kt | 12 +++++ .../indexerRelays/IndexerRelayListState.kt | 45 +++++++++++++++++-- .../ui/screen/AccountStateViewModel.kt | 4 ++ .../indexer/IndexerRelayListViewModel.kt | 2 +- .../relayLists/IndexerRelayListEvent.kt | 2 +- 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 77abdf80a..9328f7d39 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -52,6 +52,7 @@ import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent +import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent @@ -99,6 +100,7 @@ private object PrefKeys { const val LATEST_DM_RELAY_LIST = "latestDMRelayList" const val LATEST_NIP65_RELAY_LIST = "latestNIP65RelayList" const val LATEST_SEARCH_RELAY_LIST = "latestSearchRelayList" + const val LATEST_INDEX_RELAY_LIST = "latestIndexRelayList" const val LATEST_BLOCKED_RELAY_LIST = "latestBlockedRelayList" const val LATEST_TRUSTED_RELAY_LIST = "latestTrustedRelayList" const val LATEST_MUTE_LIST = "latestMuteList" @@ -334,6 +336,7 @@ object LocalPreferences { putOrRemove(PrefKeys.LATEST_DM_RELAY_LIST, settings.backupDMRelayList) putOrRemove(PrefKeys.LATEST_NIP65_RELAY_LIST, settings.backupNIP65RelayList) putOrRemove(PrefKeys.LATEST_SEARCH_RELAY_LIST, settings.backupSearchRelayList) + putOrRemove(PrefKeys.LATEST_INDEX_RELAY_LIST, settings.backupIndexRelayList) putOrRemove(PrefKeys.LATEST_BLOCKED_RELAY_LIST, settings.backupBlockedRelayList) putOrRemove(PrefKeys.LATEST_TRUSTED_RELAY_LIST, settings.backupTrustedRelayList) @@ -469,6 +472,7 @@ object LocalPreferences { val latestDmRelayList = parseEventOrNull(PrefKeys.LATEST_DM_RELAY_LIST) val latestNip65RelayList = parseEventOrNull(PrefKeys.LATEST_NIP65_RELAY_LIST) val latestSearchRelayList = parseEventOrNull(PrefKeys.LATEST_SEARCH_RELAY_LIST) + val latestIndexRelayList = parseEventOrNull(PrefKeys.LATEST_INDEX_RELAY_LIST) val latestBlockedRelayList = parseEventOrNull(PrefKeys.LATEST_BLOCKED_RELAY_LIST) val latestTrustedRelayList = parseEventOrNull(PrefKeys.LATEST_TRUSTED_RELAY_LIST) val latestMuteList = parseEventOrNull(PrefKeys.LATEST_MUTE_LIST) @@ -511,6 +515,7 @@ object LocalPreferences { backupNIP65RelayList = latestNip65RelayList, backupDMRelayList = latestDmRelayList, backupSearchRelayList = latestSearchRelayList, + backupIndexRelayList = latestIndexRelayList, backupBlockedRelayList = latestBlockedRelayList, backupTrustedRelayList = latestTrustedRelayList, backupPrivateHomeRelayList = latestPrivateHomeRelayList, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index c23628065..e81151655 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -41,6 +41,7 @@ import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent +import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission @@ -123,6 +124,7 @@ class AccountSettings( var backupDMRelayList: ChatMessageRelayListEvent? = null, var backupNIP65RelayList: AdvertisedRelayListEvent? = null, var backupSearchRelayList: SearchRelayListEvent? = null, + var backupIndexRelayList: IndexerRelayListEvent? = null, var backupBlockedRelayList: BlockedRelayListEvent? = null, var backupTrustedRelayList: TrustedRelayListEvent? = null, var backupMuteList: MuteListEvent? = null, @@ -342,6 +344,16 @@ class AccountSettings( } } + fun updateIndexRelayList(newIndexRelayList: IndexerRelayListEvent?) { + if (newIndexRelayList == null || newIndexRelayList.tags.isEmpty()) return + + // Events might be different objects, we have to compare their ids. + if (backupIndexRelayList?.id != newIndexRelayList.id) { + backupIndexRelayList = newIndexRelayList + saveAccountSettings() + } + } + fun updateBlockedRelayList(newBlockedRelayList: BlockedRelayListEvent?) { if (newBlockedRelayList == null || newBlockedRelayList.tags.isEmpty()) return diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/indexerRelays/IndexerRelayListState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/indexerRelays/IndexerRelayListState.kt index 924dcd9f1..e9913bf36 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/indexerRelays/IndexerRelayListState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/indexerRelays/IndexerRelayListState.kt @@ -21,20 +21,27 @@ package com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays import com.vitorpamplona.amethyst.model.AccountSettings +import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList +import com.vitorpamplona.amethyst.model.DefaultSearchRelayList import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent +import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch class IndexerRelayListState( val signer: NostrSigner, @@ -52,10 +59,11 @@ class IndexerRelayListState( fun getIndexerRelayList(): IndexerRelayListEvent? = indexerListNote.event as? IndexerRelayListEvent - suspend fun normalizeIndexerRelayListWithBackup(note: Note): Set { - val event = note.event as? IndexerRelayListEvent - return event?.let { decryptionCache.relays(it) } ?: emptySet() - } + fun indexListEvent(note: Note) = note.event as? IndexerRelayListEvent ?: settings.backupIndexRelayList + + suspend fun normalizeIndexerRelayListWithBackup(note: Note): Set = indexListEvent(note)?.let { decryptionCache.relays(it) }?.ifEmpty { null } ?: DefaultIndexerRelayList + + suspend fun normalizeIndexerRelayListWithBackupNoDefaults(note: Note): Set = indexListEvent(note)?.let { decryptionCache.relays(it) } ?: emptySet() val flow = getIndexerRelayListFlow() @@ -68,6 +76,17 @@ class IndexerRelayListState( emptySet(), ) + val flowNoDefaults = + getIndexerRelayListFlow() + .map { normalizeIndexerRelayListWithBackupNoDefaults(it.note) } + .onStart { emit(normalizeIndexerRelayListWithBackupNoDefaults(indexerListNote)) } + .flowOn(Dispatchers.Default) + .stateIn( + scope, + SharingStarted.Eagerly, + emptySet(), + ) + suspend fun saveRelayList(indexerRelays: List): IndexerRelayListEvent { val relayListForIndexer = getIndexerRelayList() @@ -84,4 +103,22 @@ class IndexerRelayListState( ) } } + + init { + settings.backupIndexRelayList?.let { + Log.d("AccountRegisterObservers", "Loading saved index relay list ${it.toJson()}") + @OptIn(DelicateCoroutinesApi::class) + GlobalScope.launch(Dispatchers.IO) { LocalCache.justConsumeMyOwnEvent(it) } + } + + scope.launch(Dispatchers.Default) { + Log.d("AccountRegisterObservers", "Index Relay List Collector Start") + getIndexerRelayListFlow().collect { + Log.d("AccountRegisterObservers", "Updating Index Relay List for ${signer.pubKey}") + (it.note.event as? IndexerRelayListEvent)?.let { + settings.updateIndexRelayList(it) + } + } + } + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 54ca707d7..abc12cdbc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.DefaultChannels import com.vitorpamplona.amethyst.model.DefaultDMRelayList +import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList import com.vitorpamplona.amethyst.model.DefaultNIP65List import com.vitorpamplona.amethyst.model.DefaultNIP65RelaySet import com.vitorpamplona.amethyst.model.DefaultSearchRelayList @@ -57,6 +58,7 @@ import com.vitorpamplona.quartz.nip19Bech32.toNpub import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49 import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent +import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.Log @@ -288,6 +290,7 @@ class AccountStateViewModel : ViewModel() { accountSettings.backupNIP65RelayList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupDMRelayList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupSearchRelayList?.let { Amethyst.instance.client.send(it, toPost) } + accountSettings.backupIndexRelayList?.let { Amethyst.instance.client.send(it, toPost) } } } } @@ -309,6 +312,7 @@ class AccountStateViewModel : ViewModel() { backupNIP65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, tempSigner), backupDMRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, tempSigner), backupSearchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), tempSigner), + backupIndexRelayList = IndexerRelayListEvent.create(DefaultIndexerRelayList.toList(), tempSigner), backupChannelList = ChannelListEvent.create(emptyList(), DefaultChannels, tempSigner), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt index 8bbb81fa2..c40255bd0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListViewModel.kt @@ -25,7 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl class IndexerRelayListViewModel : BasicRelaySetupInfoModel() { override fun getRelayList(): List? = - account.indexerRelayList.flow.value + account.indexerRelayList.flowNoDefaults.value .toList() override suspend fun saveRelayList(urlList: List) { diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip51Lists/relayLists/IndexerRelayListEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip51Lists/relayLists/IndexerRelayListEvent.kt index 3a20129fc..bc6dce8c1 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip51Lists/relayLists/IndexerRelayListEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip51Lists/relayLists/IndexerRelayListEvent.kt @@ -91,7 +91,7 @@ class IndexerRelayListEvent( return signer.signNip51List(createdAt, KIND, TAGS, privateTagArray) } - suspend fun create( + fun create( relays: List, signer: NostrSignerSync, createdAt: Long = TimeUtils.now(),