Brute forces the finding of events.
This commit is contained in:
@@ -95,6 +95,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.Entity
|
|||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
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.NProfile
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
|
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
|
||||||
@@ -2928,7 +2929,7 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
getOrCreateUser(nip19.hex)
|
getOrCreateUser(nip19.hex)
|
||||||
}
|
}
|
||||||
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> {
|
is NNote -> {
|
||||||
getOrCreateNote(nip19.hex)
|
getOrCreateNote(nip19.hex)
|
||||||
}
|
}
|
||||||
is NEvent -> {
|
is NEvent -> {
|
||||||
|
|||||||
+2
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.NoteEventLoaderSubAssembler
|
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
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class EventFinderQueryState(
|
class EventFinderQueryState(
|
||||||
val note: Note,
|
val note: Note,
|
||||||
|
val account: Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
class EventFinderFilterAssembler(
|
class EventFinderFilterAssembler(
|
||||||
|
|||||||
+5
-3
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -30,18 +31,19 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
fun EventFinderFilterAssemblerSubscription(
|
fun EventFinderFilterAssemblerSubscription(
|
||||||
note: Note,
|
note: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) = EventFinderFilterAssemblerSubscription(note, accountViewModel.dataSources().eventFinder)
|
) = EventFinderFilterAssemblerSubscription(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EventFinderFilterAssemblerSubscription(
|
fun EventFinderFilterAssemblerSubscription(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
account: Account,
|
||||||
dataSource: EventFinderFilterAssembler,
|
dataSource: EventFinderFilterAssembler,
|
||||||
) {
|
) {
|
||||||
// different screens get different states
|
// different screens get different states
|
||||||
// even if they are tracking the same tag.
|
// even if they are tracking the same tag.
|
||||||
val state =
|
val state =
|
||||||
remember(note) {
|
remember(note, account) {
|
||||||
EventFinderQueryState(note)
|
EventFinderQueryState(note, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyDataSourceSubscription(state, dataSource)
|
KeyDataSourceSubscription(state, dataSource)
|
||||||
|
|||||||
+83
-34
@@ -22,64 +22,113 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
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.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
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.nip01Core.tags.addressables.Address
|
||||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||||
|
|
||||||
fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBasedFilter>? {
|
fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl> {
|
||||||
val missingAddressables = mutableSetOf<Address>()
|
val set = mutableSetOf<NormalizedRelayUrl>()
|
||||||
|
|
||||||
keys.forEach {
|
set.addAll(LocalCache.relayHints.hintsForAddress(note.idHex))
|
||||||
if (it.note is AddressableNote && it.note.event == null) {
|
|
||||||
missingAddressables.add(it.note.address())
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads threading that is event-based
|
val isInChannel = note.channelHex()
|
||||||
it.note.replyTo?.forEach {
|
if (isInChannel != null) {
|
||||||
if (it is AddressableNote && it.event == null) {
|
LocalCache.checkGetOrCreateChannel(isInChannel)?.relays()?.forEach {
|
||||||
missingAddressables.add(it.address())
|
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<Address>): List<RelayBasedFilter> {
|
fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBasedFilter>? {
|
||||||
|
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<NormalizedRelayUrl, Set<Address>>): List<RelayBasedFilter> {
|
||||||
if (missingAddressables.isEmpty()) return emptyList()
|
if (missingAddressables.isEmpty()) return emptyList()
|
||||||
|
|
||||||
return missingAddressables.flatMap { aTag ->
|
return missingAddressables.flatMap { relayEntry ->
|
||||||
val authorHomeRelayEventAddress = AdvertisedRelayListEvent.createAddressTag(aTag.pubKeyHex)
|
relayEntry.value.map { address ->
|
||||||
val authorHomeRelayEvent = (LocalCache.getAddressableNoteIfExists(authorHomeRelayEventAddress)?.event as? AdvertisedRelayListEvent)
|
if (address.kind < 25000 && address.dTag.isBlank()) {
|
||||||
|
|
||||||
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()) {
|
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
relay = it,
|
relay = relayEntry.key,
|
||||||
filter =
|
filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(aTag.kind),
|
kinds = listOf(address.kind),
|
||||||
authors = listOf(aTag.pubKeyHex),
|
authors = listOf(address.pubKeyHex),
|
||||||
limit = 1,
|
limit = 1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
relay = it,
|
relay = relayEntry.key,
|
||||||
filter =
|
filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(aTag.kind),
|
kinds = listOf(address.kind),
|
||||||
tags = mapOf("d" to listOf(aTag.dTag)),
|
tags = mapOf("d" to listOf(address.dTag)),
|
||||||
authors = listOf(aTag.pubKeyHex),
|
authors = listOf(address.pubKeyHex),
|
||||||
limit = 1,
|
limit = 1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
+79
-26
@@ -22,48 +22,101 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
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.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.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.utils.mapOfSet
|
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||||
|
|
||||||
fun filterMissingEvents(keys: List<EventFinderQueryState>): List<RelayBasedFilter>? {
|
fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
||||||
val missingEvents = mutableSetOf<String>()
|
val set = mutableSetOf<NormalizedRelayUrl>()
|
||||||
|
|
||||||
keys.forEach {
|
set.addAll(LocalCache.relayHints.hintsForEvent(note.idHex))
|
||||||
if (it.note !is AddressableNote && it.note.event == null) {
|
|
||||||
missingEvents.add(it.note.idHex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads threading that is event-based
|
val isInChannel = note.channelHex()
|
||||||
it.note.replyTo?.forEach {
|
if (isInChannel != null) {
|
||||||
if (it !is AddressableNote && it.event == null) {
|
LocalCache.checkGetOrCreateChannel(isInChannel)?.relays()?.forEach {
|
||||||
missingEvents.add(it.idHex)
|
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<HexKey>): List<RelayBasedFilter> {
|
fun filterMissingEvents(keys: List<EventFinderQueryState>): List<RelayBasedFilter>? {
|
||||||
if (missingEventIds.isEmpty()) return emptyList()
|
val eventsPerRelay =
|
||||||
|
|
||||||
val relayHints =
|
|
||||||
mapOfSet {
|
mapOfSet {
|
||||||
missingEventIds.forEach { eventId ->
|
keys.forEach { key ->
|
||||||
LocalCache.relayHints.hintsForEvent(eventId).forEach { relayUrl ->
|
val default = key.account.followPlusAllMine.flow.value
|
||||||
add(relayUrl, eventId)
|
|
||||||
|
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 {
|
return filterMissingEvents(eventsPerRelay)
|
||||||
RelayBasedFilter(
|
}
|
||||||
relay = it.key,
|
|
||||||
filter = Filter(ids = it.value.sorted()),
|
fun filterMissingEvents(missingEventIds: Map<NormalizedRelayUrl, Set<String>>): List<RelayBasedFilter> {
|
||||||
)
|
if (missingEventIds.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return missingEventIds.mapNotNull {
|
||||||
|
if (it.value.isNotEmpty()) {
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = it.key,
|
||||||
|
filter = Filter(ids = it.value.sorted()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-15
@@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
|||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
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.nip65RelayList.AdvertisedRelayListEvent
|
||||||
import com.vitorpamplona.quartz.utils.mapOfSet
|
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||||
import kotlin.collections.forEach
|
import kotlin.collections.forEach
|
||||||
@@ -36,35 +37,44 @@ val MetadataAndRelayListKinds =
|
|||||||
AdvertisedRelayListEvent.KIND,
|
AdvertisedRelayListEvent.KIND,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun filterFindUserMetadataForKey(author: HexKey): List<RelayBasedFilter> =
|
fun filterFindUserMetadataForKey(
|
||||||
|
author: HexKey,
|
||||||
|
defaultRelays: Set<NormalizedRelayUrl>,
|
||||||
|
): List<RelayBasedFilter> =
|
||||||
LocalCache.checkGetOrCreateUser(author)?.let {
|
LocalCache.checkGetOrCreateUser(author)?.let {
|
||||||
filterFindUserMetadataForKey(setOf(it))
|
filterFindUserMetadataForKey(setOf(it), defaultRelays)
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
fun filterFindUserMetadataForKey(authors: Set<User>): List<RelayBasedFilter> {
|
fun filterFindUserMetadataForKey(
|
||||||
|
authors: Set<User>,
|
||||||
|
defaultRelays: Set<NormalizedRelayUrl>,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
val perRelayKeys =
|
val perRelayKeys =
|
||||||
mapOfSet {
|
mapOfSet {
|
||||||
authors.forEach { key ->
|
authors.forEach { key ->
|
||||||
val relays =
|
val relays =
|
||||||
key.authorRelayList()?.writeRelaysNorm()
|
key.authorRelayList()?.writeRelaysNorm()?.ifEmpty { null }
|
||||||
?: LocalCache.relayHints.hintsForKey(key.pubkeyHex).ifEmpty { null }
|
?: LocalCache.relayHints.hintsForKey(key.pubkeyHex).ifEmpty { null }
|
||||||
?: key.relaysBeingUsed.keys
|
?: (key.relaysBeingUsed.keys + defaultRelays).toList()
|
||||||
|
|
||||||
relays.forEach {
|
relays.forEach {
|
||||||
add(it, key.pubkeyHex)
|
add(it, key.pubkeyHex)
|
||||||
}
|
}
|
||||||
// TODO, DESPERATION: TRY ALL THE CONNECTED RELAYS.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return perRelayKeys.map {
|
return perRelayKeys.mapNotNull {
|
||||||
RelayBasedFilter(
|
if (it.value.isNotEmpty()) {
|
||||||
relay = it.key,
|
RelayBasedFilter(
|
||||||
filter =
|
relay = it.key,
|
||||||
Filter(
|
filter =
|
||||||
kinds = MetadataAndRelayListKinds,
|
Filter(
|
||||||
authors = it.value.sorted(),
|
kinds = MetadataAndRelayListKinds,
|
||||||
),
|
authors = it.value.sorted(),
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEo
|
|||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|
||||||
class UserLoaderSubAssembler(
|
class UserLoaderSubAssembler(
|
||||||
client: NostrClient,
|
client: NostrClient,
|
||||||
@@ -42,7 +43,11 @@ class UserLoaderSubAssembler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val defaultRelays = mutableSetOf<NormalizedRelayUrl>()
|
||||||
|
|
||||||
keys.mapTo(mutableSetOf()) { it.account }.forEach {
|
keys.mapTo(mutableSetOf()) { it.account }.forEach {
|
||||||
|
defaultRelays.addAll(it.followPlusAllMine.flow.value)
|
||||||
|
|
||||||
it.kind3FollowList.flow.value.authors.forEach {
|
it.kind3FollowList.flow.value.authors.forEach {
|
||||||
val user = LocalCache.getOrCreateUser(it)
|
val user = LocalCache.getOrCreateUser(it)
|
||||||
if (user.latestMetadata == null) {
|
if (user.latestMetadata == null) {
|
||||||
@@ -55,7 +60,7 @@ class UserLoaderSubAssembler(
|
|||||||
|
|
||||||
if (firstTimers.isEmpty()) return null
|
if (firstTimers.isEmpty()) return null
|
||||||
|
|
||||||
return filterFindUserMetadataForKey(firstTimers)
|
return filterFindUserMetadataForKey(firstTimers, defaultRelays)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun distinct(key: UserFinderQueryState) = key.user
|
override fun distinct(key: UserFinderQueryState) = key.user
|
||||||
|
|||||||
+24
-11
@@ -20,17 +20,30 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies
|
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.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.nip19Bech32.entities.NAddress
|
||||||
|
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.ifEmpty
|
||||||
|
|
||||||
fun filterByAddress(address: NAddress) =
|
fun filterByAddress(
|
||||||
filterMissingAddressables(
|
address: NAddress,
|
||||||
setOf(
|
default: Set<NormalizedRelayUrl>,
|
||||||
Address(
|
): List<RelayBasedFilter> {
|
||||||
address.kind,
|
val note = LocalCache.getOrCreateAddressableNote(address.address())
|
||||||
address.author,
|
|
||||||
address.dTag,
|
val list =
|
||||||
),
|
mapOfSet {
|
||||||
),
|
if (note.event == null) {
|
||||||
)
|
potentialRelaysToFindAddress(note).ifEmpty { default }.forEach { relayUrl ->
|
||||||
|
add(relayUrl, note.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterMissingAddressables(list)
|
||||||
|
}
|
||||||
|
|||||||
+5
-1
@@ -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.amethyst.service.relayClient.reqCommand.user.loaders.filterFindUserMetadataForKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
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<NormalizedRelayUrl>,
|
||||||
|
) = filterFindUserMetadataForKey(pubKey, defaultRelays)
|
||||||
|
|||||||
+34
-1
@@ -20,7 +20,40 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies
|
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.filterMissingEvents
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.potentialRelaysToFindEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
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<NormalizedRelayUrl>,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|||||||
+10
-8
@@ -55,25 +55,27 @@ class SearchWatcherSubAssembler(
|
|||||||
|
|
||||||
if (mySearchString.isBlank()) return null
|
if (mySearchString.isBlank()) return null
|
||||||
|
|
||||||
|
val defaultRelays = key.account.followPlusAllMine.flow.value
|
||||||
|
|
||||||
val directFilters =
|
val directFilters =
|
||||||
runCatching {
|
runCatching {
|
||||||
if (Hex.isHex(mySearchString)) {
|
if (Hex.isHex(mySearchString)) {
|
||||||
val key = Hex.decode(mySearchString).toHexKey()
|
val hexKey = Hex.decode(mySearchString).toHexKey()
|
||||||
filterByAuthor(key) + filterByEvent(key)
|
filterByAuthor(hexKey, defaultRelays) + filterByEvent(hexKey, defaultRelays)
|
||||||
} else {
|
} else {
|
||||||
val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity
|
val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity
|
||||||
if (parsed != null) {
|
if (parsed != null) {
|
||||||
cache.consume(parsed)
|
cache.consume(parsed)
|
||||||
|
|
||||||
when (parsed) {
|
when (parsed) {
|
||||||
is NSec -> filterByAuthor(parsed.toPubKeyHex())
|
is NSec -> filterByAuthor(parsed.toPubKeyHex(), defaultRelays)
|
||||||
is NPub -> filterByAuthor(parsed.hex)
|
is NPub -> filterByAuthor(parsed.hex, defaultRelays)
|
||||||
is NProfile -> filterByAuthor(parsed.hex)
|
is NProfile -> filterByAuthor(parsed.hex, defaultRelays)
|
||||||
is NNote -> filterByEvent(parsed.hex)
|
is NNote -> filterByEvent(parsed.hex, defaultRelays)
|
||||||
is NEvent -> filterByEvent(parsed.hex)
|
is NEvent -> filterByEvent(parsed.hex, defaultRelays)
|
||||||
is NEmbed -> emptyList()
|
is NEmbed -> emptyList()
|
||||||
is NRelay -> emptyList()
|
is NRelay -> emptyList()
|
||||||
is NAddress -> filterByAddress(parsed)
|
is NAddress -> filterByAddress(parsed, defaultRelays)
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ fun ThreadScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
WatchLifecycleAndUpdateModel(feedViewModel)
|
WatchLifecycleAndUpdateModel(feedViewModel)
|
||||||
ThreadFilterAssemblerSubscription(noteId, accountViewModel.dataSources().thread)
|
ThreadFilterAssemblerSubscription(noteId, accountViewModel)
|
||||||
|
|
||||||
LoadNote(noteId, accountViewModel) {
|
LoadNote(noteId, accountViewModel) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
|
|||||||
+2
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources
|
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.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.ThreadEventLoaderSubAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.subassembies.ThreadFilterSubAssembler
|
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
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class ThreadQueryState(
|
class ThreadQueryState(
|
||||||
val eventId: HexKey,
|
val eventId: HexKey,
|
||||||
|
val account: Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
class ThreadFilterAssembler(
|
class ThreadFilterAssembler(
|
||||||
|
|||||||
+14
-1
@@ -22,19 +22,32 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThreadFilterAssemblerSubscription(
|
fun ThreadFilterAssemblerSubscription(
|
||||||
eventId: HexKey,
|
eventId: HexKey,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) = ThreadFilterAssemblerSubscription(
|
||||||
|
eventId,
|
||||||
|
accountViewModel.account,
|
||||||
|
accountViewModel.dataSources().thread,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThreadFilterAssemblerSubscription(
|
||||||
|
eventId: HexKey,
|
||||||
|
account: Account,
|
||||||
filterAssembler: ThreadFilterAssembler,
|
filterAssembler: ThreadFilterAssembler,
|
||||||
) {
|
) {
|
||||||
// different screens get different states
|
// different screens get different states
|
||||||
// even if they are tracking the same tag.
|
// even if they are tracking the same tag.
|
||||||
val state =
|
val state =
|
||||||
remember(eventId) {
|
remember(eventId) {
|
||||||
ThreadQueryState(eventId)
|
ThreadQueryState(eventId, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyDataSourceSubscription(state, filterAssembler)
|
KeyDataSourceSubscription(state, filterAssembler)
|
||||||
|
|||||||
+42
-22
@@ -24,34 +24,54 @@ import com.vitorpamplona.amethyst.model.AddressableNote
|
|||||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
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.filterMissingAddressables
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.loaders.filterMissingEvents
|
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.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<RelayBasedFilter> {
|
fun filterMissingEventsForThread(
|
||||||
val missingEvents = mutableSetOf<HexKey>()
|
threadInfo: ThreadAssembler.ThreadInfo,
|
||||||
val missingAddresses = mutableSetOf<Address>()
|
defaultRelays: Set<NormalizedRelayUrl>,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
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) {
|
threadInfo.allNotes.forEach {
|
||||||
if (threadInfo.root is AddressableNote) {
|
if (it !is AddressableNote && it.event == null) {
|
||||||
missingAddresses.add(threadInfo.root.address)
|
potentialRelaysToFindEvent(it).ifEmpty { defaultRelays }.forEach { relayUrl ->
|
||||||
} else {
|
add(relayUrl, it.idHex)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val missingEventsFilter = filterMissingEvents(missingEvents) ?: emptyList()
|
val missingAddresses =
|
||||||
val missingAddressFilter = filterMissingAddressables(missingAddresses) ?: emptyList()
|
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
|
return missingEventsFilter + missingAddressFilter
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -45,7 +45,8 @@ class ThreadEventLoaderSubAssembler(
|
|||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? {
|
): List<RelayBasedFilter>? {
|
||||||
val branches = ThreadAssembler().findThreadFor(key.eventId) ?: return null
|
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
|
override fun id(key: ThreadQueryState) = key.eventId
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
package com.vitorpamplona.quartz.nip01Core.relay.filters
|
package com.vitorpamplona.quartz.nip01Core.relay.filters
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
|
||||||
|
|
||||||
class Filter(
|
class Filter(
|
||||||
val ids: List<String>? = null,
|
val ids: List<String>? = null,
|
||||||
@@ -48,22 +47,13 @@ class Filter(
|
|||||||
search: String? = this.search,
|
search: String? = this.search,
|
||||||
) = Filter(ids, authors, kinds, tags, since, until, limit, search)
|
) = Filter(ids, authors, kinds, tags, since, until, limit, search)
|
||||||
|
|
||||||
init {
|
fun isFilledFilter() =
|
||||||
// TODO: Remove this in production
|
(ids != null && ids.isNotEmpty()) ||
|
||||||
ids?.forEach {
|
(authors != null && authors.isNotEmpty()) ||
|
||||||
if (it.length != 64) throw IllegalArgumentException("Invalid id length $it on ${toJson()}")
|
(kinds != null && kinds.isNotEmpty()) ||
|
||||||
}
|
(tags != null && tags.isNotEmpty()) ||
|
||||||
authors?.forEach {
|
(since != null) ||
|
||||||
if (it.length != 64) throw IllegalArgumentException("Invalid author length $it on ${toJson()}")
|
(until != null) ||
|
||||||
}
|
(limit != null) ||
|
||||||
tags?.get("#p")?.forEach {
|
(search != null && search.isNotEmpty())
|
||||||
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()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user