Refactors the old NIP-05 code on Quartz
New Caching system for User metadata New Caching system for NIP-05 verifications
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.commons.richtext
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
package com.vitorpamplona.amethyst.commons.richtext
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.nip01Core.core.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
class ImmutableListOfLists<T>(
|
||||
val lists: Array<Array<T>>,
|
||||
)
|
||||
|
||||
val EmptyTagList = ImmutableListOfLists<String>(emptyArray())
|
||||
|
||||
fun Array<Array<String>>.toImmutableListOfLists(): ImmutableListOfLists<String> = ImmutableListOfLists(this)
|
||||
@@ -33,7 +33,6 @@ import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
@@ -231,7 +230,7 @@ open class Note(
|
||||
|
||||
return relays.firstOrNull()
|
||||
} else {
|
||||
currentOutbox?.firstOrNull() ?: author?.latestMetadataRelay
|
||||
currentOutbox?.firstOrNull() ?: author?.metadataOrNull()?.relay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -973,9 +972,8 @@ class NoteFlowSet(
|
||||
fun author() =
|
||||
metadata.stateFlow.flatMapLatest {
|
||||
it.note.author
|
||||
?.flow()
|
||||
?.metadata
|
||||
?.stateFlow ?: MutableStateFlow(null)
|
||||
?.metadata()
|
||||
?.flow ?: MutableStateFlow(null)
|
||||
}
|
||||
|
||||
fun isInUse(): Boolean =
|
||||
|
||||
@@ -22,12 +22,13 @@ package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.nip01Core.UserMetadataCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.UserNip05Cache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip56Reports.UserReportCache
|
||||
import com.vitorpamplona.amethyst.commons.model.trustedAssertions.UserCardsCache
|
||||
import com.vitorpamplona.amethyst.commons.util.toShortDisplay
|
||||
import com.vitorpamplona.quartz.lightning.Lud06
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -52,15 +53,11 @@ class User(
|
||||
val nip65RelayListNote: Note,
|
||||
val dmRelayListNote: Note,
|
||||
) {
|
||||
private var metadata: UserMetadataCache? = null
|
||||
private var reports: UserReportCache? = null
|
||||
private var cards: UserCardsCache? = null
|
||||
private var nip05: UserNip05Cache? = null
|
||||
|
||||
// private var deps = ScatterMap<KClass<out UserDependencies>, UserDependencies>()
|
||||
|
||||
var info: UserMetadata? = null
|
||||
|
||||
var latestMetadata: MetadataEvent? = null
|
||||
var latestMetadataRelay: NormalizedRelayUrl? = null
|
||||
var latestContactList: ContactListEvent? = null
|
||||
|
||||
var zaps = mapOf<Note, Note?>()
|
||||
@@ -85,39 +82,45 @@ class User(
|
||||
|
||||
fun outboxRelays() = authorRelayList()?.writeRelaysNorm()
|
||||
|
||||
fun relayHints() = authorRelayList()?.writeRelaysNorm()?.take(3) ?: listOfNotNull(latestMetadataRelay)
|
||||
fun relayHints() = authorRelayList()?.writeRelaysNorm()?.take(3) ?: listOfNotNull(metadataOrNull()?.relay)
|
||||
|
||||
fun inboxRelays() = authorRelayList()?.readRelaysNorm()
|
||||
|
||||
fun dmInboxRelays() = dmInboxRelayList()?.relays()?.ifEmpty { null } ?: inboxRelays()
|
||||
|
||||
fun bestRelayHint() = authorRelayList()?.writeRelaysNorm()?.firstOrNull() ?: latestMetadataRelay
|
||||
fun bestRelayHint() = authorRelayList()?.writeRelaysNorm()?.firstOrNull() ?: metadataOrNull()?.relay
|
||||
|
||||
fun toPTag() = PTag(pubkeyHex, bestRelayHint())
|
||||
|
||||
fun toNostrUri() = "nostr:${toNProfile()}"
|
||||
|
||||
fun toBestShortFirstName(): String {
|
||||
val fullName = toBestDisplayName()
|
||||
fun toBestDisplayName(): String =
|
||||
metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.bestName() ?: pubkeyDisplayHex()
|
||||
|
||||
val names = fullName.split(' ')
|
||||
fun nip05(): String? =
|
||||
metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.nip05
|
||||
|
||||
val firstName =
|
||||
if (names[0].length <= 3) {
|
||||
// too short. Remove Dr.
|
||||
"${names[0]} ${names.getOrNull(1) ?: ""}"
|
||||
} else {
|
||||
names[0]
|
||||
}
|
||||
fun profilePicture(): String? =
|
||||
metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.picture
|
||||
|
||||
return firstName
|
||||
}
|
||||
|
||||
fun toBestDisplayName(): String = info?.bestName() ?: pubkeyDisplayHex()
|
||||
|
||||
fun nip05(): String? = info?.nip05
|
||||
|
||||
fun profilePicture(): String? = info?.picture
|
||||
fun lnAddress(): String? =
|
||||
metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.lnAddress()
|
||||
|
||||
fun updateContactList(event: ContactListEvent): Set<HexKey> {
|
||||
if (event.id == latestContactList?.id) return emptySet()
|
||||
@@ -184,21 +187,30 @@ class User(
|
||||
|
||||
fun updateUserInfo(
|
||||
newUserInfo: UserMetadata,
|
||||
latestMetadata: MetadataEvent,
|
||||
metaEvent: MetadataEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) {
|
||||
info = newUserInfo
|
||||
info?.tags = latestMetadata.tags.toImmutableListOfLists()
|
||||
info?.cleanBlankNames()
|
||||
newUserInfo.cleanBlankNames()
|
||||
|
||||
// converts lud06 to lud16
|
||||
if (newUserInfo.lud16.isNullOrBlank()) {
|
||||
info?.lud06?.let {
|
||||
newUserInfo.lud06?.let {
|
||||
if (it.lowercase().startsWith("lnurl")) {
|
||||
info?.lud16 = Lud06().toLud16(it)
|
||||
newUserInfo.lud16 = Lud06().toLud16(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flowSet?.metadata?.invalidateData()
|
||||
val metadata = metadata()
|
||||
|
||||
metadata.newMetadata(newUserInfo, metaEvent)
|
||||
|
||||
if (relay != null) {
|
||||
metadata.relay = relay
|
||||
}
|
||||
|
||||
// doesn't create Nip05 unless needed.
|
||||
nip05StateOrNull()?.newMetadata(newUserInfo.nip05, metaEvent.pubKey)
|
||||
}
|
||||
|
||||
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||
@@ -209,49 +221,73 @@ class User(
|
||||
|
||||
fun reports(): UserReportCache = reports ?: UserReportCache().also { reports = it }
|
||||
|
||||
// fun reportsOrNull(): UserReports? = deps[UserReports::class] as? UserReports
|
||||
|
||||
// fun reports(): UserReports = deps.getOrPut(UserReports::class) { UserReports() } as UserReports
|
||||
|
||||
fun cardsOrNull(): UserCardsCache? = cards
|
||||
|
||||
fun cards(): UserCardsCache = cards ?: UserCardsCache().also { cards = it }
|
||||
|
||||
fun metadataOrNull(): UserMetadataCache? = metadata
|
||||
|
||||
fun metadata(): UserMetadataCache = metadata ?: UserMetadataCache().also { metadata = it }
|
||||
|
||||
fun nip05StateOrNull(): UserNip05Cache? = nip05
|
||||
|
||||
fun nip05State(): UserNip05Cache =
|
||||
nip05 ?: UserNip05Cache().also {
|
||||
nip05 = it
|
||||
val meta = metadata().flow.value
|
||||
if (meta != null) {
|
||||
it.newMetadata(meta.info.nip05, pubkeyHex)
|
||||
}
|
||||
}
|
||||
|
||||
fun containsAny(hiddenWordsCase: List<DualCase>): Boolean {
|
||||
if (hiddenWordsCase.isEmpty()) return false
|
||||
|
||||
if (toBestDisplayName().containsAny(hiddenWordsCase)) {
|
||||
return true
|
||||
}
|
||||
metadata?.flow?.value?.let { userInfo ->
|
||||
val info = userInfo.info
|
||||
|
||||
if (profilePicture()?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
if (info.name?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info?.banner?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
if (info.displayName?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info?.about?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
if (info.picture?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info?.lud06?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
if (info.banner?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info?.lud16?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
if (info.about?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info?.nip05?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
if (info.lud06?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.lud16?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.nip05?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
||||
fun anyNameStartsWith(username: String): Boolean =
|
||||
metadata
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.anyNameStartsWith(username) ?: false
|
||||
|
||||
@Synchronized
|
||||
fun createOrDestroyFlowSync(create: Boolean) {
|
||||
@@ -285,7 +321,6 @@ class UserFlowSet(
|
||||
u: User,
|
||||
) {
|
||||
// Observers line up here.
|
||||
val metadata = UserBundledRefresherFlow(u)
|
||||
val follows = UserBundledRefresherFlow(u)
|
||||
val followers = UserBundledRefresherFlow(u)
|
||||
val usedRelays = UserBundledRefresherFlow(u)
|
||||
@@ -293,8 +328,7 @@ class UserFlowSet(
|
||||
val statuses = UserBundledRefresherFlow(u)
|
||||
|
||||
fun isInUse(): Boolean =
|
||||
metadata.hasObservers() ||
|
||||
follows.hasObservers() ||
|
||||
follows.hasObservers() ||
|
||||
followers.hasObservers() ||
|
||||
usedRelays.hasObservers() ||
|
||||
zaps.hasObservers() ||
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.model.nip01Core
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.IdentityClaimTag
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.identityClaims
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@Stable
|
||||
class UserInfo(
|
||||
val info: UserMetadata,
|
||||
val tags: ImmutableListOfLists<String>,
|
||||
val identities: List<IdentityClaimTag>,
|
||||
val createdAt: Long,
|
||||
)
|
||||
|
||||
@Stable
|
||||
class UserMetadataCache {
|
||||
val flow: MutableStateFlow<UserInfo?> = MutableStateFlow(null)
|
||||
var relay: NormalizedRelayUrl? = null
|
||||
|
||||
fun newMetadata(
|
||||
userInfo: UserMetadata,
|
||||
metaEvent: MetadataEvent,
|
||||
) {
|
||||
flow.update {
|
||||
UserInfo(
|
||||
info = userInfo,
|
||||
tags = metaEvent.tags.toImmutableListOfLists(),
|
||||
identities = metaEvent.identityClaims(),
|
||||
createdAt = metaEvent.createdAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldUpdateWith(event: MetadataEvent) = event.createdAt > (flow.value?.createdAt ?: 0)
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.model.nip05DnsIdentifiers
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
@Stable
|
||||
sealed interface Nip05State {
|
||||
object NotFound : Nip05State
|
||||
|
||||
@Stable
|
||||
class Exists(
|
||||
val nip05: Nip05Id,
|
||||
val hexKey: HexKey,
|
||||
) : Nip05State {
|
||||
val verificationState = MutableStateFlow<Nip05VerifState>(Nip05VerifState.NotStarted)
|
||||
|
||||
fun markAsVerified() = verificationState.tryEmit(Nip05VerifState.Verified(TimeUtils.oneHourAhead()))
|
||||
|
||||
fun markAsInvalid() = verificationState.tryEmit(Nip05VerifState.Failed(TimeUtils.oneHourAhead()))
|
||||
|
||||
fun markAsVerifying() = verificationState.tryEmit(Nip05VerifState.Verifying(TimeUtils.fiveMinutesAhead()))
|
||||
|
||||
fun markAsError() = verificationState.tryEmit(Nip05VerifState.Verified(TimeUtils.fiveMinutesAhead()))
|
||||
|
||||
fun reset() = verificationState.tryEmit(Nip05VerifState.NotStarted)
|
||||
|
||||
suspend fun checkAndUpdate(nip05Client: Nip05Client) {
|
||||
println("AABBCC checkAndUpdate $nip05")
|
||||
if (verificationState.value.isExpired()) {
|
||||
println("AABBCC Veryfing $nip05")
|
||||
markAsVerifying()
|
||||
try {
|
||||
if (nip05Client.verify(nip05, hexKey)) {
|
||||
markAsVerified()
|
||||
} else {
|
||||
markAsInvalid()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
markAsError()
|
||||
if (e is CancellationException) throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.model.nip05DnsIdentifiers
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Stable
|
||||
sealed class Nip05VerifState(
|
||||
val nextTimeToCheck: Long,
|
||||
) {
|
||||
@Stable
|
||||
class Verified(
|
||||
nextTimeToCheck: Long,
|
||||
) : Nip05VerifState(nextTimeToCheck)
|
||||
|
||||
@Stable
|
||||
class Failed(
|
||||
nextTimeToCheck: Long,
|
||||
) : Nip05VerifState(nextTimeToCheck)
|
||||
|
||||
@Stable
|
||||
class Verifying(
|
||||
nextTimeToCheck: Long,
|
||||
) : Nip05VerifState(nextTimeToCheck)
|
||||
|
||||
@Stable
|
||||
class Error(
|
||||
nextTimeToCheck: Long,
|
||||
) : Nip05VerifState(nextTimeToCheck)
|
||||
|
||||
@Stable
|
||||
object NotStarted : Nip05VerifState(0)
|
||||
|
||||
fun isExpired() = TimeUtils.now() > nextTimeToCheck
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.model.nip05DnsIdentifiers
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@Stable
|
||||
class UserNip05Cache {
|
||||
val flow: MutableStateFlow<Nip05State> = MutableStateFlow(Nip05State.NotFound)
|
||||
|
||||
fun newMetadata(
|
||||
nip05: String?,
|
||||
ownerPubKey: HexKey,
|
||||
) {
|
||||
if (nip05.isNullOrBlank()) {
|
||||
flow.tryEmit(Nip05State.NotFound)
|
||||
} else {
|
||||
val current = flow.value
|
||||
val parsedNip05 = Nip05Id.parse(nip05)
|
||||
|
||||
if (parsedNip05 == null) {
|
||||
flow.tryEmit(Nip05State.NotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if (current is Nip05State.Exists && current.nip05 == parsedNip05) return
|
||||
|
||||
flow.update { current ->
|
||||
if (current is Nip05State.Exists && current.nip05 == parsedNip05) {
|
||||
current
|
||||
} else {
|
||||
Nip05State.Exists(parsedNip05, ownerPubKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-4
@@ -22,11 +22,11 @@ package com.vitorpamplona.amethyst.commons.model.nip28PublicChats
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.util.toShortDisplay
|
||||
import com.vitorpamplona.quartz.nip01Core.core.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHintOptional
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
@@ -99,8 +99,20 @@ class PublicChatChannel(
|
||||
fun summary(): String? = info.about
|
||||
|
||||
fun profilePicture(): String? {
|
||||
if (info.picture.isNullOrBlank()) return creator?.info?.banner
|
||||
return info.picture ?: creator?.info?.banner
|
||||
if (info.picture.isNullOrBlank()) {
|
||||
return creator
|
||||
?.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.banner
|
||||
}
|
||||
return info.picture ?: creator
|
||||
?.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.banner
|
||||
}
|
||||
|
||||
fun anyNameStartsWith(prefix: String): Boolean =
|
||||
|
||||
+1
-1
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.commons.richtext
|
||||
import com.linkedin.urls.detection.UrlDetector
|
||||
import com.linkedin.urls.detection.UrlDetectorOptions
|
||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.experimental.inlineMetadata.Nip54InlineMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.ContentWarningTag
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.commons.richtext
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.commons.util
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
|
||||
fun String.isUTF16Char(pos: Int): Boolean = Character.charCount(this.codePointAt(pos)) == 2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user