diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 59e413f8d..0664e2bb5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -220,6 +220,26 @@ object LocalCache { } } + private fun consume(event: AdvertisedRelayListEvent) { + val version = getOrCreateNote(event.id) + val note = getOrCreateAddressableNote(event.address()) + val author = getOrCreateUser(event.pubKey) + + if (version.event == null) { + version.loadEvent(event, author, emptyList()) + version.moveAllReferencesTo(note) + } + + // Already processed this event. + if (note.event?.id() == event.id()) return + + if (event.createdAt > (note.createdAt() ?: 0)) { + note.loadEvent(event, author, emptyList()) + + refreshObservers(note) + } + } + fun formattedDateTime(timestamp: Long): String { return Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a")) @@ -1497,6 +1517,7 @@ object LocalCache { try { when (event) { + is AdvertisedRelayListEvent -> consume(event) is AppDefinitionEvent -> consume(event) is AppRecommendationEvent -> consume(event) is AudioTrackEvent -> consume(event) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 9ce1400ac..4ed3941bc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -48,6 +48,17 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { ) } + fun createAccountRelayListFilter(): TypedFilter { + return TypedFilter( + types = COMMON_FEED_TYPES, + filter = JsonFilter( + kinds = listOf(AdvertisedRelayListEvent.kind), + authors = listOf(account.userProfile().pubkeyHex), + limit = 1 + ) + ) + } + fun createAccountAcceptedAwardsFilter(): TypedFilter { return TypedFilter( types = COMMON_FEED_TYPES, @@ -155,6 +166,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { accountChannel.typedFilters = listOf( createAccountMetadataFilter(), createAccountContactListFilter(), + createAccountRelayListFilter(), createNotificationFilter(), createGiftWrapsToMeFilter(), createAccountReportsFilter(), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/AdvertisedRelayListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/AdvertisedRelayListEvent.kt new file mode 100644 index 000000000..5403809fc --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/AdvertisedRelayListEvent.kt @@ -0,0 +1,70 @@ +package com.vitorpamplona.amethyst.service.model + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.amethyst.model.HexKey +import com.vitorpamplona.amethyst.model.TimeUtils +import com.vitorpamplona.amethyst.model.toHexKey +import com.vitorpamplona.amethyst.service.CryptoUtils + +@Immutable +class AdvertisedRelayListEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: List>, + content: String, + sig: HexKey +) : Event(id, pubKey, createdAt, kind, tags, content, sig), AddressableEvent { + override fun dTag() = fixedDTag + override fun address() = ATag(kind, pubKey, dTag(), null) + + fun relays(): List { + return tags.mapNotNull { + if (it.size > 1 && it[0] == "r") { + val type = when (it.getOrNull(2)) { + "read" -> AdvertisedRelayType.READ + "write" -> AdvertisedRelayType.WRITE + else -> AdvertisedRelayType.BOTH + } + + AdvertisedRelayInfo(it[1], type) + } else { + null + } + } + } + + companion object { + const val kind = 10002 + const val fixedDTag = "" + + fun create( + list: List, + privateKey: ByteArray, + createdAt: Long = TimeUtils.now() + ): AdvertisedRelayListEvent { + val tags = list.map { + if (it.type == AdvertisedRelayType.BOTH) { + listOf(it.relayUrl) + } else { + listOf(it.relayUrl, it.type.code) + } + } + val msg = "" + val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey() + val id = generateId(pubKey, createdAt, kind, tags, msg) + val sig = CryptoUtils.sign(id, privateKey) + return AdvertisedRelayListEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey()) + } + } + + @Immutable + data class AdvertisedRelayInfo(val relayUrl: String, val type: AdvertisedRelayType) + + @Immutable + enum class AdvertisedRelayType(val code: String) { + BOTH(""), + READ("read"), + WRITE("write") + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt index fcb5301c6..0e8d6d69b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt @@ -14,6 +14,7 @@ class EventFactory { sig: String, lenient: Boolean ) = when (kind) { + AdvertisedRelayListEvent.kind -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig) AppDefinitionEvent.kind -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig) AppRecommendationEvent.kind -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig) AudioTrackEvent.kind -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)