From 5766bb1887009b97a897093d683e9b6f7ac375e0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 12 Mar 2025 09:41:18 -0400 Subject: [PATCH 01/18] First idea without changing NIP-44 --- .../decoupling/DecoupledCipher.kt | 112 ++++++++++++++++++ .../decoupling/EncryptionKeyCache.kt | 68 +++++++++++ .../decoupling/EncryptionKeyDerivation.kt | 46 +++++++ .../setup/EncryptionKeyListEvent.kt | 80 +++++++++++++ .../decoupling/setup/TagArrayBuilderExt.kt | 34 ++++++ .../decoupling/setup/tags/KeyTag.kt | 62 ++++++++++ .../quartz/nip01Core/signers/NostrSigner.kt | 5 + .../nip01Core/signers/NostrSignerInternal.kt | 7 ++ .../nip01Core/signers/NostrSignerSync.kt | 7 ++ .../ExternalSignerLauncher.kt | 34 ++++++ .../nip55AndroidSigner/NostrSignerExternal.kt | 11 ++ 11 files changed, 466 insertions(+) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt new file mode 100644 index 000000000..8d7ca91f2 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2024 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.quartz.experimental.decoupling + +import com.vitorpamplona.quartz.experimental.decoupling.setup.EncryptionKeyListEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray +import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip44Encryption.Nip44 + +class DecoupledCipher { + fun innerEncrypt( + content: String, + privKey: ByteArray, + toPublicKey: HexKey, + ) = Nip44 + .encrypt(content, privKey, toPublicKey.hexToByteArray()) + .encodePayload() + + fun innerDecrypt( + ciphertext: String, + privKey: ByteArray, + fromPublicKey: HexKey, + ) = Nip44.decrypt( + payload = ciphertext, + privateKey = privKey, + pubKey = fromPublicKey.hexToByteArray(), + ) + + fun encrypt( + decryptedContent: String, + toPublicKey: HexKey, + fromKeyList: EncryptionKeyListEvent, + toKeyList: EncryptionKeyListEvent, + signer: NostrSigner, + onReady: (String) -> Unit, + ) { + val toKeys = toKeyList.keys() + val sendToKey = if (toKeys.isEmpty()) toKeyList.pubKey else toKeys.random().pubkey + + val fromKeys = fromKeyList.keys() + + // uses the main key + if (fromKeys.isEmpty()) { + signer.nip44Encrypt(decryptedContent, sendToKey, onReady) + } else { + val keyToUse = fromKeys.random() + + EncryptionKeyCache.getOrLoad( + deriveFromPubKey = signer.pubKey, + nonce = keyToUse.nonce, + load = { onLoaded -> + signer.deriveKey(keyToUse.nonce) { newPrivKey -> + onLoaded(newPrivKey.hexToByteArray()) + } + }, + ) { derivedPrivKey -> + onReady(innerEncrypt(decryptedContent, derivedPrivKey, sendToKey)) + } + } + } + + fun decrypt( + encryptedContent: String, + fromPublicKey: HexKey, + toPublicKey: HexKey, + fromKeyList: EncryptionKeyListEvent, + toEncryptedKeyList: EncryptionKeyListEvent, + signer: NostrSigner, + onReady: (String) -> Unit, + ) { + val fromKeys = fromKeyList.keys() + val sentFromKey = if (fromKeys.isEmpty()) fromKeyList.pubKey else fromKeys.random().pubkey + + val keyToUse = toEncryptedKeyList.keys().firstOrNull { it.pubkey == toPublicKey } + + // uses the main key + if (signer.pubKey == toPublicKey) { + signer.nip44Decrypt(encryptedContent, sentFromKey, onReady) + } else if (keyToUse != null) { + EncryptionKeyCache.getOrLoad( + deriveFromPubKey = signer.pubKey, + nonce = keyToUse.nonce, + load = { onLoaded -> + signer.deriveKey(keyToUse.nonce) { newPrivKey -> + onLoaded(newPrivKey.hexToByteArray()) + } + }, + ) { derivedPrivKey -> + innerDecrypt(encryptedContent, derivedPrivKey, sentFromKey)?.let { onReady(it) } + } + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt new file mode 100644 index 000000000..0cddc52af --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2024 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.quartz.experimental.decoupling + +import android.util.LruCache +import com.vitorpamplona.quartz.nip01Core.core.HexKey + +object EncryptionKeyCache { + private val sharedNonceKeyCache = LruCache(50) + + private fun idx( + key: HexKey, + nonce: HexKey, + ) = EncryptionKeyCacheKey(key, nonce) + + fun put( + deriveFromPubKey: HexKey, + nonce: HexKey, + privKey: ByteArray, + ) = sharedNonceKeyCache.put(idx(deriveFromPubKey, nonce), privKey) + + fun get( + deriveFromPubKey: HexKey, + nonce: HexKey, + ) = sharedNonceKeyCache.get(idx(deriveFromPubKey, nonce)) + + inline fun getOrLoad( + deriveFromPubKey: HexKey, + nonce: HexKey, + load: (onLoaded: (privKey: ByteArray) -> Unit) -> Unit, + crossinline whenReady: (privKey: ByteArray) -> Unit, + ) { + val cachedPrivKey = get(deriveFromPubKey, nonce) + if (cachedPrivKey != null) { + whenReady(cachedPrivKey) + return + } + + load { newPrivKey -> + put(deriveFromPubKey, nonce, newPrivKey) + whenReady(newPrivKey) + } + } +} + +// keeps a cache per signed-in user +private data class EncryptionKeyCacheKey( + val pubkey: HexKey, + val nonce: HexKey, +) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt new file mode 100644 index 000000000..15fbbad7a --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2024 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.quartz.experimental.decoupling + +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair +import com.vitorpamplona.quartz.nip44Encryption.crypto.Hkdf +import com.vitorpamplona.quartz.utils.sha256.sha256 + +class EncryptionKeyDerivation { + companion object { + private val saltPrefix = "nip44kd".toByteArray(Charsets.UTF_8) + + fun derivePrivateKey( + privKey: ByteArray, + nonce: ByteArray?, + ): ByteArray { + if (nonce == null) { + return privKey + } + return sha256(Hkdf().extract(privKey, saltPrefix + nonce)) + } + + fun deriveKeyPair( + privKey: ByteArray, + nonce: ByteArray?, + ): KeyPair = KeyPair(privKey = derivePrivateKey(privKey, nonce)) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt new file mode 100644 index 000000000..e55232c0c --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2024 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.quartz.experimental.decoupling.setup + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.experimental.decoupling.setup.tags.KeyTag +import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.signers.eventUpdate +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class EncryptionKeyListEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + fun keys() = tags.mapNotNull(KeyTag::parse) + + companion object { + const val KIND = 10044 + const val ALT = "Encryption keys" + + fun add( + key: KeyTag, + currentEvent: EncryptionKeyListEvent, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventUpdate(currentEvent, createdAt) { + alt(ALT) + key(key) + initializer() + } + + fun remove( + key: KeyTag, + currentEvent: EncryptionKeyListEvent, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventUpdate(currentEvent, createdAt) { + alt(ALT) + removeIf(KeyTag::isSameKey, key.toTagArray()) + initializer() + } + + fun build( + keys: List, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, "", createdAt) { + alt(ALT) + keys(keys) + initializer() + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt new file mode 100644 index 000000000..a2a761236 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024 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.quartz.experimental.decoupling.setup + +import com.vitorpamplona.quartz.experimental.decoupling.setup.tags.KeyTag +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder + +fun TagArrayBuilder.key( + key: HexKey, + nonce: HexKey, +) = add(KeyTag.assemble(key, nonce)) + +fun TagArrayBuilder.key(key: KeyTag) = add(key.toTagArray()) + +fun TagArrayBuilder.keys(keys: List) = addAll(keys.map { it.toTagArray() }) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt new file mode 100644 index 000000000..4cc4a1707 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2024 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.quartz.experimental.decoupling.setup.tags + +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class KeyTag( + val pubkey: HexKey, + val nonce: HexKey, +) { + fun toTagArray() = assemble(pubkey, nonce) + + companion object { + const val TAG_NAME = "n" + + fun isSameKey( + tag1: Array, + tag2: Array, + ): Boolean { + ensure(tag1.has(1)) { return false } + ensure(tag2.has(1)) { return false } + ensure(tag1[0] == tag2[0]) { return false } + ensure(tag1[1] == tag2[1]) { return false } + return true + } + + @JvmStatic + fun parse(tag: Array): KeyTag? { + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + ensure(tag[2].isNotEmpty()) { return null } + return KeyTag(tag[1], tag[2]) + } + + @JvmStatic + fun assemble( + key: HexKey, + nonce: HexKey, + ) = arrayOf(TAG_NAME, key, nonce) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt index cb9e70b98..d5f401c9b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt @@ -73,6 +73,11 @@ abstract class NostrSigner( onReady: (LnZapPrivateEvent) -> Unit, ) + abstract fun deriveKey( + nonce: HexKey, + onReady: (HexKey) -> Unit, + ) + fun decrypt( encryptedContent: String, fromPublicKey: HexKey, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt index c49e52bc2..a2d568d6e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt @@ -80,4 +80,11 @@ class NostrSignerInternal( ) { signerSync.decryptZapEvent(event)?.let { onReady(it) } } + + override fun deriveKey( + nonce: HexKey, + onReady: (HexKey) -> Unit, + ) { + signerSync.deriveKey(nonce)?.let { onReady(it) } + } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt index 112e0eae4..3376d70fc 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.quartz.nip01Core.signers import android.util.Log +import com.vitorpamplona.quartz.experimental.decoupling.EncryptionKeyDerivation import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray @@ -128,4 +129,10 @@ class NostrSignerSync( } fun decryptZapEvent(event: LnZapRequestEvent): LnZapPrivateEvent? = PrivateZapRequestBuilder().decryptZapEvent(event, this) + + fun deriveKey(nonce: HexKey): HexKey? { + if (keyPair.privKey == null) return null + + return EncryptionKeyDerivation.derivePrivateKey(keyPair.privKey, nonce.hexToByteArray()).toHexKey() + } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt index f375dad42..fd59617c0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt @@ -46,6 +46,7 @@ enum class SignerType { NIP44_DECRYPT, GET_PUBLIC_KEY, DECRYPT_ZAP_EVENT, + DERIVE_KEY, } class Permission( @@ -219,6 +220,7 @@ class ExternalSignerLauncher( SignerType.NIP44_DECRYPT -> "nip44_decrypt" SignerType.GET_PUBLIC_KEY -> "get_public_key" SignerType.DECRYPT_ZAP_EVENT -> "decrypt_zap_event" + SignerType.DERIVE_KEY -> "derive_key" } intent.putExtra("type", signerType) intent.putExtra("pubKey", pubKey) @@ -318,6 +320,15 @@ class ExternalSignerLauncher( return kotlin.Result.success(null) } + fun hashCodeFields( + str1: String, + onReady: (String) -> Unit, + ): Int { + var result = str1.hashCode() + result = 31 * result + onReady.hashCode() + return result + } + fun hashCodeFields( str1: String, str2: String, @@ -398,4 +409,27 @@ class ExternalSignerLauncher( }, ) } + + fun deriveKey( + nonce: HexKey, + signerType: SignerType = SignerType.DERIVE_KEY, + onReady: (String) -> Unit, + ) { + getDataFromResolver(signerType, arrayOf(nonce)).fold( + onFailure = { }, + onSuccess = { + if (it == null) { + openSignerApp( + nonce, + signerType, + "", + hashCodeFields(nonce, onReady).toString(), + onReady, + ) + } else { + onReady(it) + } + }, + ) + } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt index b1e5bf32e..bf4c2164d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt @@ -133,6 +133,17 @@ class NostrSignerExternal( ) } + override fun deriveKey( + nonce: HexKey, + onReady: (HexKey) -> Unit, + ) { + launcher.deriveKey( + nonce, + SignerType.DERIVE_KEY, + onReady, + ) + } + override fun decryptZapEvent( event: LnZapRequestEvent, onReady: (LnZapPrivateEvent) -> Unit, From b017e03728d3c822259e1072a7f0c934f35ee508 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 12 Mar 2025 14:51:23 -0400 Subject: [PATCH 02/18] Updates NIP-56 to the newest tag structure for Quartz --- .../vitorpamplona/amethyst/model/Account.kt | 14 +-- .../amethyst/model/LocalCache.kt | 8 +- .../com/vitorpamplona/amethyst/model/Note.kt | 11 +++ .../com/vitorpamplona/amethyst/model/User.kt | 5 +- .../amethyst/ui/note/types/Report.kt | 22 +++-- .../ui/screen/loggedIn/AccountViewModel.kt | 6 +- .../ui/screen/loggedIn/ReportNoteDialog.kt | 16 ++-- .../profile/header/UserProfileDropDownMenu.kt | 14 +-- .../quartz/nip56Reports/ReportEvent.kt | 93 ++++++++----------- .../quartz/nip56Reports/ReportType.kt | 61 ++++++++++++ .../quartz/nip56Reports/TagArrayBuilderExt.kt | 52 +++++++++++ .../tags/BaseReportTag.kt} | 10 +- .../nip56Reports/tags/DefaultReportTag.kt | 46 +++++++++ .../quartz/nip56Reports/tags/HashSha256Tag.kt | 55 +++++++++++ .../nip56Reports/tags/ReportedAddressTag.kt | 71 ++++++++++++++ .../nip56Reports/tags/ReportedAuthorTag.kt | 67 +++++++++++++ .../nip56Reports/tags/ReportedEventTag.kt | 67 +++++++++++++ .../quartz/nip56Reports/tags/ServerTag.kt | 43 +++++++++ 18 files changed, 561 insertions(+), 100 deletions(-) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt rename quartz/src/main/java/com/vitorpamplona/quartz/{nip70ProtectedEvts/ProtectedTagSerializer.kt => nip56Reports/tags/BaseReportTag.kt} (86%) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index d73db1e3c..f6cfb9f61 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -149,6 +149,7 @@ import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent import com.vitorpamplona.quartz.nip51Lists.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup @@ -1616,18 +1617,18 @@ class Account( suspend fun report( note: Note, - type: ReportEvent.ReportType, + type: ReportType, content: String = "", ) { if (!isWriteable()) return - if (note.hasReacted(userProfile(), "⚠️")) { - // has already liked this note + if (note.hasReport(userProfile(), type)) { + // has already reported this note return } note.event?.let { - ReportEvent.create(it, type, signer, content) { + signer.sign(ReportEvent.build(it, type)) { Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } @@ -1636,7 +1637,7 @@ class Account( suspend fun report( user: User, - type: ReportEvent.ReportType, + type: ReportType, ) { if (!isWriteable()) return @@ -1645,7 +1646,8 @@ class Account( return } - ReportEvent.create(user.pubkeyHex, type, signer) { + val template = ReportEvent.build(user.pubkeyHex, type) + signer.sign(template) { Amethyst.instance.client.send(it) LocalCache.justConsume(it, null) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 1c9c97fa0..be5342272 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -695,8 +695,8 @@ object LocalCache { event.originalPost().mapNotNull { checkGetOrCreateNote(it) } + event.taggedAddresses().map { getOrCreateAddressableNote(it) } is ReportEvent -> - event.reportedPost().mapNotNull { checkGetOrCreateNote(it.key) } + - event.taggedAddresses().map { getOrCreateAddressableNote(it) } + event.reportedPost().mapNotNull { checkGetOrCreateNote(it.eventId) } + + event.reportedAddresses().map { getOrCreateAddressableNote(it.address) } is ChannelMessageEvent -> event .tagsWithoutCitations() @@ -1342,7 +1342,7 @@ object LocalCache { // Already processed this event. if (note.event != null) return - val mentions = event.reportedAuthor().mapNotNull { checkGetOrCreateUser(it.key) } + val mentions = event.reportedAuthor().mapNotNull { checkGetOrCreateUser(it.pubkey) } val repliesTo = computeReplyTo(event) note.loadEvent(event, author, repliesTo) @@ -2218,7 +2218,7 @@ object LocalCache { } if (noteEvent is ReportEvent) { noteEvent.reportedAuthor().mapNotNull { - val author = getUserIfExists(it.key) + val author = getUserIfExists(it.pubkey) author?.removeReport(note) author?.clearEOSE() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 9868922ca..984781e74 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -68,6 +68,8 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent +import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent @@ -717,6 +719,15 @@ open class Note( ) } + fun hasReport( + loggedIn: User, + type: ReportType, + ): Boolean = + reports[loggedIn]?.firstOrNull { + it.event is ReportEvent && + (it.event as ReportEvent).reportedAuthor().any { it.type == type } + } != null + fun hasPledgeBy(user: User): Boolean = replies .filter { it.event?.hasAdditionalReward() ?: false } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 712a7f4f9..08c8a7c41 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -46,6 +46,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile import com.vitorpamplona.quartz.nip19Bech32.toNpub import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.utils.DualCase @@ -357,11 +358,11 @@ class User( fun hasReport( loggedIn: User, - type: ReportEvent.ReportType, + type: ReportType, ): Boolean = reports[loggedIn]?.firstOrNull { it.event is ReportEvent && - (it.event as ReportEvent).reportedAuthor().any { it.reportType == type } + (it.event as ReportEvent).reportedAuthor().any { it.type == type } } != null fun containsAny(hiddenWordsCase: List): Boolean { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt index 4c3aaa019..a2a2a5135 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt @@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType @Composable fun RenderReport( @@ -52,16 +53,17 @@ fun RenderReport( val reportType = base .map { - when (it.reportType) { - ReportEvent.ReportType.EXPLICIT -> stringRes(R.string.explicit_content) - ReportEvent.ReportType.NUDITY -> stringRes(R.string.nudity) - ReportEvent.ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech) - ReportEvent.ReportType.SPAM -> stringRes(R.string.spam) - ReportEvent.ReportType.IMPERSONATION -> stringRes(R.string.impersonation) - ReportEvent.ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior) - ReportEvent.ReportType.MALWARE -> stringRes(R.string.malware) - ReportEvent.ReportType.MOD -> stringRes(R.string.mod) - ReportEvent.ReportType.OTHER -> stringRes(R.string.other) + when (it.type) { + ReportType.EXPLICIT -> stringRes(R.string.explicit_content) + ReportType.NUDITY -> stringRes(R.string.nudity) + ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech) + ReportType.SPAM -> stringRes(R.string.spam) + ReportType.IMPERSONATION -> stringRes(R.string.impersonation) + ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior) + ReportType.MALWARE -> stringRes(R.string.malware) + ReportType.MOD -> stringRes(R.string.mod) + ReportType.OTHER -> stringRes(R.string.other) + null -> stringRes(R.string.other) } }.toSet() .joinToString(", ") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 6b131680e..14d7e73ab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -102,7 +102,7 @@ import com.vitorpamplona.quartz.nip37Drafts.DraftEvent import com.vitorpamplona.quartz.nip47WalletConnect.Response import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent -import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount @@ -741,7 +741,7 @@ class AccountViewModel( fun report( note: Note, - type: ReportEvent.ReportType, + type: ReportType, content: String = "", ) { viewModelScope.launch(Dispatchers.IO) { account.report(note, type, content) } @@ -749,7 +749,7 @@ class AccountViewModel( fun report( user: User, - type: ReportEvent.ReportType, + type: ReportType, ) { viewModelScope.launch(Dispatchers.IO) { account.report(user, type) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt index 1fb25ddfa..3ae9a888c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt @@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.WarningColor -import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) @@ -75,13 +75,13 @@ fun ReportNoteDialog( ) { val reportTypes = listOf( - Pair(ReportEvent.ReportType.SPAM, stringRes(R.string.report_dialog_spam)), - Pair(ReportEvent.ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)), - Pair(ReportEvent.ReportType.IMPERSONATION, stringRes(R.string.report_dialog_impersonation)), - Pair(ReportEvent.ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)), - Pair(ReportEvent.ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)), - Pair(ReportEvent.ReportType.MALWARE, stringRes(R.string.report_malware)), - Pair(ReportEvent.ReportType.MOD, stringRes(R.string.report_mod)), + Pair(ReportType.SPAM, stringRes(R.string.report_dialog_spam)), + Pair(ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)), + Pair(ReportType.IMPERSONATION, stringRes(R.string.report_dialog_impersonation)), + Pair(ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)), + Pair(ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)), + Pair(ReportType.MALWARE, stringRes(R.string.report_malware)), + Pair(ReportType.MOD, stringRes(R.string.report_mod)), ) val reasonOptions = remember { reportTypes.map { TitleExplainer(it.second) }.toImmutableList() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt index 79c05f7ef..2a09eb206 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt @@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.note.externalLinkForUser import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness -import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip56Reports.ReportType @Composable fun UserProfileDropDownMenu( @@ -108,42 +108,42 @@ fun UserProfileDropDownMenu( DropdownMenuItem( text = { Text(stringRes(id = R.string.report_spam_scam)) }, onClick = { - accountViewModel.report(user, ReportEvent.ReportType.SPAM) + accountViewModel.report(user, ReportType.SPAM) onDismiss() }, ) DropdownMenuItem( text = { Text(stringRes(R.string.report_hateful_speech)) }, onClick = { - accountViewModel.report(user, ReportEvent.ReportType.PROFANITY) + accountViewModel.report(user, ReportType.PROFANITY) onDismiss() }, ) DropdownMenuItem( text = { Text(stringRes(id = R.string.report_impersonation)) }, onClick = { - accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION) + accountViewModel.report(user, ReportType.IMPERSONATION) onDismiss() }, ) DropdownMenuItem( text = { Text(stringRes(R.string.report_nudity_porn)) }, onClick = { - accountViewModel.report(user, ReportEvent.ReportType.NUDITY) + accountViewModel.report(user, ReportType.NUDITY) onDismiss() }, ) DropdownMenuItem( text = { Text(stringRes(id = R.string.report_illegal_behaviour)) }, onClick = { - accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL) + accountViewModel.report(user, ReportType.ILLEGAL) onDismiss() }, ) DropdownMenuItem( text = { Text(stringRes(id = R.string.report_malware)) }, onClick = { - accountViewModel.report(user, ReportEvent.ReportType.MALWARE) + accountViewModel.report(user, ReportType.MALWARE) onDismiss() }, ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt index 3e222faab..45281e3aa 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt @@ -25,14 +25,15 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate import com.vitorpamplona.quartz.nip31Alts.AltTag +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip56Reports.tags.DefaultReportTag +import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAddressTag +import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAuthorTag +import com.vitorpamplona.quartz.nip56Reports.tags.ReportedEventTag import com.vitorpamplona.quartz.utils.TimeUtils -@Immutable data class ReportedKey( - val key: String, - val reportType: ReportEvent.ReportType, -) - // NIP 56 event. @Immutable class ReportEvent( @@ -43,48 +44,35 @@ class ReportEvent( content: String, sig: HexKey, ) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + @Transient + private var defaultType: ReportType? = null + + private fun defaultReportTypes() = tags.mapNotNull(DefaultReportTag::parse) + private fun defaultReportType(): ReportType { + defaultType?.let { return it } + // Works with old and new structures for report. - var reportType = - tags - .filter { it.firstOrNull() == "report" } - .mapNotNull { it.getOrNull(1) } - .map { ReportType.valueOf(it.uppercase()) } - .firstOrNull() + var reportType = defaultReportTypes().firstOrNull() if (reportType == null) { - reportType = - tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.uppercase()) }.firstOrNull() + reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.parseOrNull(it, emptyArray()) }.firstOrNull() } if (reportType == null) { reportType = ReportType.SPAM } + defaultType = reportType return reportType } - fun reportedPost() = - tags - .filter { it.size > 1 && it[0] == "e" } - .map { - ReportedKey( - it[1], - it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) } - ?: defaultReportType(), - ) - } + fun reportedPost() = tags.mapNotNull { ReportedEventTag.parse(it, defaultReportType()) } - fun reportedAuthor() = - tags - .filter { it.size > 1 && it[0] == "p" } - .map { - ReportedKey( - it[1], - it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) } - ?: defaultReportType(), - ) - } + fun reportedAddresses() = tags.mapNotNull { ReportedAddressTag.parse(it, defaultReportType()) } + + fun reportedAuthor() = tags.mapNotNull { ReportedAuthorTag.parse(it, defaultReportType()) } companion object { const val KIND = 1984 + const val ALT_PREFIX = "Report for " fun create( reportedPost: Event, @@ -108,32 +96,27 @@ class ReportEvent( signer.sign(createdAt, KIND, tags, content, onReady) } - fun create( - reportedUser: String, + fun build( + reportedPost: Event, type: ReportType, - signer: NostrSigner, createdAt: Long = TimeUtils.now(), - onReady: (ReportEvent) -> Unit, - ) { - val content = "" + ) = eventTemplate(KIND, "", createdAt) { + alt(ALT_PREFIX + type.code) + event(reportedPost.id, type) + user(reportedPost.pubKey, type) - val reportAuthorTag = arrayOf("p", reportedUser, type.name.lowercase()) - val alt = AltTag.assemble("Report for ${type.name}") + if (reportedPost is AddressableEvent) { + address(reportedPost.address(), type) + } + } - val tags: Array> = arrayOf(reportAuthorTag, alt) - signer.sign(createdAt, KIND, tags, content, onReady) + fun build( + reportedUser: HexKey, + type: ReportType, + createdAt: Long = TimeUtils.now(), + ) = eventTemplate(KIND, "", createdAt) { + alt(ALT_PREFIX + type.code) + user(reportedUser, type) } } - - enum class ReportType { - EXPLICIT, // Not used anymore. - ILLEGAL, - SPAM, - IMPERSONATION, - NUDITY, - PROFANITY, - MALWARE, - MOD, - OTHER, - } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt new file mode 100644 index 000000000..125926808 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports + +import android.util.Log + +enum class ReportType( + val code: String, +) { + EXPLICIT("explicit"), // Not used anymore. + ILLEGAL("illegal"), + SPAM("spam"), + IMPERSONATION("impersonation"), + NUDITY("nudity"), + PROFANITY("profanity"), + MALWARE("malware"), + MOD("mod"), + OTHER("other"), + ; + + companion object { + fun parseOrNull( + code: String, + tag: Array, + ): ReportType? = + when (code) { + EXPLICIT.code -> EXPLICIT + ILLEGAL.code -> ILLEGAL + SPAM.code -> SPAM + IMPERSONATION.code -> IMPERSONATION + NUDITY.code -> NUDITY + PROFANITY.code -> PROFANITY + MALWARE.code -> MALWARE + MOD.code -> MOD + "MOD" -> MOD + OTHER.code -> OTHER + else -> { + Log.w("ReportedEventTag", "Report type not supported: $code ${tag.joinToString(", ")}") + null + } + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt new file mode 100644 index 000000000..b663a3d13 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports + +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip56Reports.tags.HashSha256Tag +import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAddressTag +import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAuthorTag +import com.vitorpamplona.quartz.nip56Reports.tags.ReportedEventTag +import com.vitorpamplona.quartz.nip56Reports.tags.ServerTag + +fun TagArrayBuilder.event( + eventId: HexKey, + reportType: ReportType, +) = addUnique(ReportedEventTag.assemble(eventId, reportType)) + +fun TagArrayBuilder.address( + address: Address, + reportType: ReportType, +) = addUnique(ReportedAddressTag.assemble(address, reportType)) + +fun TagArrayBuilder.user( + pubkey: HexKey, + reportType: ReportType, +) = addUnique(ReportedAuthorTag.assemble(pubkey, reportType)) + +fun TagArrayBuilder.hash( + x: String, + reportType: ReportType, +) = addUnique(HashSha256Tag.assemble(x, reportType)) + +fun TagArrayBuilder.server(url: String) = addUnique(ServerTag.assemble(url)) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/ProtectedTagSerializer.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/BaseReportTag.kt similarity index 86% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/ProtectedTagSerializer.kt rename to quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/BaseReportTag.kt index 4245aefd7..0ccff5b2c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/ProtectedTagSerializer.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/BaseReportTag.kt @@ -18,10 +18,10 @@ * 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.quartz.nip70ProtectedEvts +package com.vitorpamplona.quartz.nip56Reports.tags -class ProtectedTagSerializer { - companion object { - fun toTagArray(reason: String = "") = arrayOf("-", reason) - } +import com.vitorpamplona.quartz.nip56Reports.ReportType + +interface BaseReportTag { + val type: ReportType? } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt new file mode 100644 index 000000000..24551aa95 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip56Reports.ReportType +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +@Immutable +class DefaultReportTag { + companion object { + const val TAG_NAME = "report" + + @JvmStatic + fun parse(tag: Array): ReportType? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + + return ReportType.parseOrNull(tag[1], tag) + } + + @JvmStatic + fun assemble(type: ReportType? = null) = arrayOfNotNull(TAG_NAME, type?.code) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt new file mode 100644 index 000000000..172e94c13 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports.tags + +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip56Reports.ReportType +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +class HashSha256Tag( + val hash: HexKey, + override val type: ReportType? = null, +) : BaseReportTag { + companion object { + const val TAG_NAME = "x" + + @JvmStatic + fun parse( + tag: Array, + defaultReportType: ReportType? = null, + ): HashSha256Tag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + + val type = tag.getOrNull(2)?.let { ReportType.parseOrNull(it, tag) } ?: defaultReportType + return HashSha256Tag(tag[1], type) + } + + @JvmStatic + fun assemble( + hash: String, + type: ReportType? = null, + ) = arrayOfNotNull(TAG_NAME, hash, type?.code) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt new file mode 100644 index 000000000..8910a5fe2 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip56Reports.ReportType +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +@Immutable +class ReportedAddressTag( + val address: Address, + override val type: ReportType? = null, +) : BaseReportTag { + fun toTagArray() = assemble(address, type) + + companion object { + const val TAG_NAME = "a" + + @JvmStatic + fun parse( + tag: Array, + defaultReportType: ReportType? = null, + ): ReportedAddressTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + + val address = Address.parse(tag[1]) + + ensure(address != null) { return null } + + val type = + if (tag.size == 2) { + defaultReportType + } else if (tag.size == 3) { + ReportType.parseOrNull(tag[2], tag) ?: defaultReportType + } else { + ReportType.parseOrNull(tag[3], tag) ?: defaultReportType + } + + return ReportedAddressTag(address, type) + } + + @JvmStatic + fun assemble( + address: Address, + type: ReportType? = null, + ) = arrayOfNotNull(TAG_NAME, address.toValue(), type?.code) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt new file mode 100644 index 000000000..a0d155dcd --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip56Reports.ReportType +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +@Immutable +class ReportedAuthorTag( + val pubkey: HexKey, + override val type: ReportType? = null, +) : BaseReportTag { + fun toTagArray() = assemble(pubkey, type) + + companion object { + const val TAG_NAME = "p" + + @JvmStatic + fun parse( + tag: Array, + defaultReportType: ReportType? = null, + ): ReportedAuthorTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + + val type = + if (tag.size == 2) { + defaultReportType + } else if (tag.size == 3) { + ReportType.parseOrNull(tag[2], tag) ?: defaultReportType + } else { + ReportType.parseOrNull(tag[3], tag) ?: defaultReportType + } + + return ReportedAuthorTag(tag[1], type) + } + + @JvmStatic + fun assemble( + pubkey: HexKey, + type: ReportType? = null, + ) = arrayOfNotNull(TAG_NAME, pubkey, type?.code) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt new file mode 100644 index 000000000..a939f0c7d --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip56Reports.ReportType +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +@Immutable +class ReportedEventTag( + val eventId: HexKey, + override val type: ReportType? = null, +) : BaseReportTag { + fun toTagArray() = assemble(eventId, type) + + companion object { + const val TAG_NAME = "e" + + @JvmStatic + fun parse( + tag: Array, + defaultReportType: ReportType? = null, + ): ReportedEventTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + + val type = + if (tag.size == 2) { + defaultReportType + } else if (tag.size == 3) { + ReportType.parseOrNull(tag[2], tag) ?: defaultReportType + } else { + ReportType.parseOrNull(tag[3], tag) ?: defaultReportType + } + + return ReportedEventTag(tag[1], type) + } + + @JvmStatic + fun assemble( + eventId: HexKey, + type: ReportType? = null, + ) = arrayOfNotNull(TAG_NAME, eventId, type?.code) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt new file mode 100644 index 000000000..9d42dc08d --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024 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.quartz.nip56Reports.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class ServerTag { + companion object { + const val TAG_NAME = "server" + + @JvmStatic + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + return tag[1] + } + + @JvmStatic + fun assemble(url: String) = arrayOf(TAG_NAME, url) + + @JvmStatic + fun assemble(urls: List) = urls.map { assemble(it) } + } +} From ba6786cdcfc43aadbedfd7bfc8d23c7ff2d6bb8d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 12 Mar 2025 14:51:47 -0400 Subject: [PATCH 03/18] Updates nip70 Protected Tags to the new structure --- .../quartz/nip70ProtectedEvts/EventExt.kt | 4 +- .../nip70ProtectedEvts/TagArrayBuilderExt.kt | 27 +++++++++++ .../quartz/nip70ProtectedEvts/TagArrayExt.kt | 26 ++++++++++ .../nip70ProtectedEvts/tags/ProtectedTag.kt | 47 +++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt index 11ceb0b5a..62fe0ab62 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt @@ -22,6 +22,4 @@ package com.vitorpamplona.quartz.nip70ProtectedEvts import com.vitorpamplona.quartz.nip01Core.core.Event -const val PROTECTED_TAG = "-" - -fun Event.isProtected() = tags.any { (it.size > 0 && it[0] == PROTECTED_TAG) } +fun Event.isProtected() = tags.isProtected() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt new file mode 100644 index 000000000..3f820f553 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2024 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.quartz.nip70ProtectedEvts + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip70ProtectedEvts.tags.ProtectedTag + +fun TagArrayBuilder.protect() = addUnique(ProtectedTag.assemble()) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt new file mode 100644 index 000000000..27260e8b1 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2024 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.quartz.nip70ProtectedEvts + +import com.vitorpamplona.quartz.nip01Core.core.TagArray +import com.vitorpamplona.quartz.nip70ProtectedEvts.tags.ProtectedTag + +fun TagArray.isProtected() = any(ProtectedTag::match) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt new file mode 100644 index 000000000..c9027d9dc --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2024 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.quartz.nip70ProtectedEvts.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class ProtectedTag { + companion object { + const val TAG_NAME = "-" + + @JvmStatic + fun match(tag: Array): Boolean { + ensure(tag.has(0)) { return false } + ensure(tag[0] == TAG_NAME) { return false } + return true + } + + @JvmStatic + fun parse(tag: Array): Boolean? { + ensure(tag.has(0)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + return true + } + + @JvmStatic + fun assemble() = arrayOf(TAG_NAME) + } +} From 1603a7473a457598d5cf92d1eccf9005fda94472 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 12 Mar 2025 15:51:17 -0400 Subject: [PATCH 04/18] Adds support for Harassment reports. --- .../amethyst/ui/note/types/Report.kt | 1 + amethyst/src/main/res/values/strings.xml | 1 + .../quartz/nip56Reports/ReportEvent.kt | 17 +++++++++-------- .../quartz/nip56Reports/ReportType.kt | 2 ++ .../nip56Reports/tags/DefaultReportTag.kt | 1 + 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt index a2a2a5135..3f400a768 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt @@ -63,6 +63,7 @@ fun RenderReport( ReportType.MALWARE -> stringRes(R.string.malware) ReportType.MOD -> stringRes(R.string.mod) ReportType.OTHER -> stringRes(R.string.other) + ReportType.HARASSMENT -> stringRes(R.string.harassment) null -> stringRes(R.string.other) } }.toSet() diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 3d638607c..8463b5bbb 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -21,6 +21,7 @@ Impersonation Illegal Behavior Other + Harassment Unknown Relay Icon Unknown Author diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt index 45281e3aa..158d75557 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt @@ -52,14 +52,15 @@ class ReportEvent( private fun defaultReportType(): ReportType { defaultType?.let { return it } - // Works with old and new structures for report. - var reportType = defaultReportTypes().firstOrNull() - if (reportType == null) { - reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.parseOrNull(it, emptyArray()) }.firstOrNull() - } - if (reportType == null) { - reportType = ReportType.SPAM - } + // search for any type in any tag. + val reportType = + defaultReportTypes().firstOrNull() + ?: tags.firstNotNullOfOrNull { + ReportedAuthorTag.parse(it)?.type + ?: ReportedEventTag.parse(it)?.type + ?: ReportedAddressTag.parse(it)?.type + } ?: ReportType.SPAM + defaultType = reportType return reportType } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt index 125926808..6ff598773 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt @@ -33,6 +33,7 @@ enum class ReportType( PROFANITY("profanity"), MALWARE("malware"), MOD("mod"), + HARASSMENT("Harrassment"), OTHER("other"), ; @@ -51,6 +52,7 @@ enum class ReportType( MALWARE.code -> MALWARE MOD.code -> MOD "MOD" -> MOD + HARASSMENT.code -> HARASSMENT OTHER.code -> OTHER else -> { Log.w("ReportedEventTag", "Report type not supported: $code ${tag.joinToString(", ")}") diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt index 24551aa95..adc260a1b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.ensure @Immutable +@Deprecated("Old version had [`report`, ``] tags") class DefaultReportTag { companion object { const val TAG_NAME = "report" From 258a550e1573923812272064401d6315a0f5cb50 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 09:00:44 -0400 Subject: [PATCH 05/18] updating android studio --- .idea/AndroidProjectSystem.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .idea/AndroidProjectSystem.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 000000000..4a53bee8c --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file From 8afe1ae537bf986af50066111e3ff48500e17300 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 09:04:39 -0400 Subject: [PATCH 06/18] Marks PrivateOutbox contents as encrypted --- .../quartz/experimental/edits/PrivateOutboxRelayListEvent.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt index bd418eec3..624e101e6 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt @@ -47,6 +47,8 @@ class PrivateOutboxRelayListEvent( ) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { @Transient private var privateTagsCache: Array>? = null + override fun isContentEncoded() = true + override fun countMemory(): Long = super.countMemory() + pointerSizeInBytes + (privateTagsCache?.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } ?: 0) From 64b42ede7c938e7e79fcc6cc36e8098577cb25c2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 09:04:54 -0400 Subject: [PATCH 07/18] Updates compose, firebase, navigation --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 322b3ba5a..0226c5187 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,10 +13,10 @@ benchmark = "1.3.3" benchmarkJunit4 = "1.3.3" biometricKtx = "1.2.0-alpha05" coil = "3.1.0" -composeBom = "2025.02.00" +composeBom = "2025.03.00" coreKtx = "1.15.0" espressoCore = "3.6.1" -firebaseBom = "33.9.0" +firebaseBom = "33.10.0" fragmentKtx = "1.8.6" gms = "4.4.2" jacksonModuleKotlin = "2.18.2" @@ -35,7 +35,7 @@ markdown = "077a2cde64" media3 = "1.5.1" mockk = "1.13.16" kotlinx-coroutines-test = "1.10.1" -navigationCompose = "2.8.8" +navigationCompose = "2.8.9" okhttp = "5.0.0-alpha.14" runner = "1.6.2" rfc3986 = "0.1.2" From ba7bd0bd8a20e42c0f2841982614f2af0cc222f6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 09:21:50 -0400 Subject: [PATCH 08/18] Migrates to AGP 8.9 --- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0226c5187..e82747fa7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanistAdaptive = "0.37.2" activityCompose = "1.10.1" -agp = "8.8.2" +agp = "8.9.0" android-compileSdk = "35" android-minSdk = "26" android-targetSdk = "35" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4209e0f1..17098b30b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Jan 04 09:23:50 EST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 682dc627031e372d9431d49626958cbeb41e496d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 10:47:31 -0400 Subject: [PATCH 09/18] Updates zoomable, vico and mockk --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e82747fa7..efeb84b50 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ lifecycleRuntimeKtx = "2.8.7" lightcompressor = "1.3.2" markdown = "077a2cde64" media3 = "1.5.1" -mockk = "1.13.16" +mockk = "1.13.17" kotlinx-coroutines-test = "1.10.1" navigationCompose = "2.8.9" okhttp = "5.0.0-alpha.14" @@ -46,9 +46,9 @@ torAndroid = "0.4.8.12" translate = "17.0.3" unifiedpush = "2.3.1" urlDetector = "0.1.23" -vico-charts = "2.0.1" +vico-charts = "2.0.3" zelory = "3.0.1" -zoomable = "2.1.0" +zoomable = "2.3.0" zxing = "3.5.3" zxingAndroidEmbedded = "4.3.0" windowCoreAndroid = "1.3.0" From 66535e11cef5dfb3d2d444bc2678616a5d1e619e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 17:24:20 -0400 Subject: [PATCH 10/18] - Removes the TAG_SIZE constant on all tags because each parser might use a different size. There is no minimum size for tags. - Moves the tag parsing checks to use ensure. - Explicitly writes matching methods to avoid hiding complexity beyond utility functions. --- .../audio/header/tags/DownloadUrlTag.kt | 8 ++- .../audio/header/tags/StreamUrlTag.kt | 8 ++- .../audio/header/tags/WaveformTag.kt | 8 ++- .../experimental/audio/track/tags/CoverTag.kt | 8 ++- .../experimental/audio/track/tags/MediaTag.kt | 8 ++- .../audio/track/tags/ParticipantTag.kt | 16 ++--- .../experimental/audio/track/tags/PriceTag.kt | 8 ++- .../experimental/audio/track/tags/TypeTag.kt | 8 ++- .../quartz/experimental/bounties/RewardTag.kt | 7 ++- .../experimental/forks/MarkedETagExt.kt | 2 +- .../interactiveStories/tags/ReadStatusTag.kt | 8 ++- .../interactiveStories/tags/RootSceneTag.kt | 1 - .../interactiveStories/tags/StoryOptionTag.kt | 1 - .../quartz/experimental/nns/tags/IPv4Tag.kt | 8 ++- .../quartz/experimental/nns/tags/IPv6Tag.kt | 8 ++- .../experimental/nns/tags/VersionTag.kt | 8 ++- .../relationshipStatus/tags/PetnameTag.kt | 8 ++- .../relationshipStatus/tags/RankTag.kt | 8 ++- .../relationshipStatus/tags/SummaryTag.kt | 8 ++- .../experimental/zapPolls/tags/ClosedAtTag.kt | 8 ++- .../zapPolls/tags/ConsensusThresholdTag.kt | 7 ++- .../experimental/zapPolls/tags/MaximumTag.kt | 8 ++- .../experimental/zapPolls/tags/MinimumTag.kt | 8 ++- .../zapPolls/tags/PollOptionTag.kt | 9 ++- .../quartz/nip01Core/core/Tag.kt | 58 +------------------ .../quartz/nip01Core/core/TagArrayBuilder.kt | 16 +---- .../nip01Core/tags/addressables/ATag.kt | 23 ++++---- .../quartz/nip01Core/tags/events/ETag.kt | 20 +++++-- .../nip01Core/tags/hashtags/HashtagTag.kt | 10 +++- .../quartz/nip01Core/tags/kinds/KindTag.kt | 14 +++-- .../quartz/nip01Core/tags/people/PTag.kt | 20 ++++--- .../nip01Core/tags/references/ReferenceTag.kt | 14 +++-- .../quartz/nip02FollowList/tags/ContactTag.kt | 26 ++++++--- .../quartz/nip10Notes/tags/MarkedETag.kt | 9 ++- .../quartz/nip10Notes/tags/Positional.kt | 6 +- .../quartz/nip13Pow/tags/PoWTag.kt | 13 +++-- .../quartz/nip14Subject/SubjectTag.kt | 10 +++- .../nip17Dm/files/tags/EncryptionAlgo.kt | 10 +++- .../nip17Dm/files/tags/EncryptionKey.kt | 9 ++- .../nip17Dm/files/tags/EncryptionNonce.kt | 9 ++- .../quartz/nip17Dm/files/tags/FileTypeTag.kt | 10 +++- .../nip18Reposts/quotes/QAddressableTag.kt | 10 ++-- .../quartz/nip18Reposts/quotes/QEventTag.kt | 10 +++- .../quartz/nip18Reposts/quotes/QTag.kt | 23 ++++++-- .../quartz/nip22Comments/CommentEvent.kt | 4 +- .../nip22Comments/tags/ReplyAddressTag.kt | 23 +++++--- .../nip22Comments/tags/ReplyAuthorTag.kt | 21 ++++--- .../nip22Comments/tags/ReplyEventTag.kt | 34 +++++++---- .../nip22Comments/tags/ReplyIdentifierTag.kt | 14 +++-- .../quartz/nip22Comments/tags/ReplyKindTag.kt | 22 +++---- .../nip22Comments/tags/RootAddressTag.kt | 25 +++++--- .../nip22Comments/tags/RootAuthorTag.kt | 20 +++---- .../quartz/nip22Comments/tags/RootEventTag.kt | 34 +++++++---- .../nip22Comments/tags/RootIdentifierTag.kt | 14 +++-- .../quartz/nip22Comments/tags/RootKindTag.kt | 14 +++-- .../quartz/nip23LongContent/tags/ImageTag.kt | 8 ++- .../nip23LongContent/tags/PublishedAtTag.kt | 8 ++- .../nip23LongContent/tags/SummaryTag.kt | 8 ++- .../quartz/nip23LongContent/tags/TitleTag.kt | 8 ++- .../vitorpamplona/quartz/nip31Alts/AltTag.kt | 10 +++- .../nip34Git/repository/tags/CloneTag.kt | 8 ++- .../repository/tags/DescriptionTag.kt | 8 ++- .../nip34Git/repository/tags/NameTag.kt | 8 ++- .../quartz/nip34Git/repository/tags/WebTag.kt | 8 ++- .../quartz/nip35Torrents/tags/BtihTag.kt | 8 ++- .../quartz/nip35Torrents/tags/FileTag.kt | 13 +++-- .../quartz/nip35Torrents/tags/InfoHashTag.kt | 8 ++- .../quartz/nip35Torrents/tags/TrackerTag.kt | 8 ++- .../streaming/tags/CurrentParticipantsTag.kt | 8 ++- .../streaming/tags/EndsTag.kt | 8 ++- .../streaming/tags/ParticipantTag.kt | 34 ++++++----- .../streaming/tags/RelayListTag.kt | 8 ++- .../streaming/tags/StartsTag.kt | 8 ++- .../streaming/tags/StatusTag.kt | 8 ++- .../streaming/tags/StreamingTag.kt | 8 ++- .../streaming/tags/TotalParticipantsTag.kt | 8 ++- .../nip57Zaps/zapraiser/ZapRaiserTag.kt | 2 +- .../quartz/nip71Video/tags/DurationTag.kt | 8 ++- .../quartz/nip71Video/tags/SegmentTag.kt | 9 ++- .../quartz/nip71Video/tags/TextTrackTag.kt | 7 ++- .../definition/tags/DescriptionTag.kt | 8 ++- .../definition/tags/ImageTag.kt | 8 ++- .../definition/tags/ModeratorTag.kt | 14 +++-- .../definition/tags/NameTag.kt | 8 ++- .../definition/tags/RelayTag.kt | 7 ++- .../definition/tags/RulesTag.kt | 8 ++- .../quartz/nip75ZapGoals/tags/AmountTag.kt | 8 ++- .../quartz/nip75ZapGoals/tags/ClosedAtTag.kt | 8 ++- .../quartz/nip75ZapGoals/tags/RelayListTag.kt | 8 ++- .../quartz/nip84Highlights/tags/ContextTag.kt | 8 ++- .../definition/tags/PlatformLinkTag.kt | 9 +-- .../recommendation/AppRecommendationEvent.kt | 2 +- .../recommendation/tags/RecommendationTag.kt | 12 ++-- .../quartz/nip92IMeta/IMetaTag.kt | 8 ++- .../nip94FileMetadata/tags/BlurhashTag.kt | 8 ++- .../nip94FileMetadata/tags/DimensionTag.kt | 8 ++- .../nip94FileMetadata/tags/FallbackTag.kt | 8 ++- .../nip94FileMetadata/tags/HashSha256Tag.kt | 8 ++- .../quartz/nip94FileMetadata/tags/ImageTag.kt | 7 ++- .../nip94FileMetadata/tags/MagnetTag.kt | 8 ++- .../nip94FileMetadata/tags/MimeTypeTag.kt | 10 +++- .../nip94FileMetadata/tags/OriginalHashTag.kt | 8 ++- .../nip94FileMetadata/tags/ServiceTag.kt | 10 +++- .../quartz/nip94FileMetadata/tags/SizeTag.kt | 8 ++- .../nip94FileMetadata/tags/SummaryTag.kt | 10 +++- .../quartz/nip94FileMetadata/tags/ThumbTag.kt | 7 ++- .../nip94FileMetadata/tags/TorrentInfoHash.kt | 8 ++- .../quartz/nip94FileMetadata/tags/UrlTag.kt | 10 +++- .../nip96FileStorage/config/tags/ServerTag.kt | 8 ++- .../quartz/nip98HttpAuth/tags/MethodTag.kt | 8 ++- .../nip98HttpAuth/tags/PayloadHashTag.kt | 7 ++- .../quartz/nip98HttpAuth/tags/UrlTag.kt | 8 ++- .../nip99Classifieds/tags/ConditionTag.kt | 8 ++- .../nip99Classifieds/tags/LocationTag.kt | 8 ++- .../quartz/nip99Classifieds/tags/PriceTag.kt | 7 ++- .../quartz/nip99Classifieds/tags/StatusTag.kt | 8 ++- 116 files changed, 807 insertions(+), 440 deletions(-) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt index 5f335b67b..6e69131b7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.audio.header.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class DownloadUrlTag { companion object { const val TAG_NAME = "download_url" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt index d3bc35de7..2e896aa98 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.audio.header.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class StreamUrlTag { companion object { const val TAG_NAME = "stream_url" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt index 0942299f2..491653632 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.quartz.experimental.audio.header.tags import com.fasterxml.jackson.module.kotlin.readValue +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper +import com.vitorpamplona.quartz.utils.ensure class WaveformTag( val wave: List, @@ -30,12 +32,12 @@ class WaveformTag( companion object { const val TAG_NAME = "waveform" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): WaveformTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].isEmpty()) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } val wave = runCatching { EventMapper.mapper.readValue>(tag[1]) }.getOrNull() if (wave.isNullOrEmpty()) return null return WaveformTag(wave) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt index e52b81d2e..56488bb95 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.audio.track.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class CoverTag { companion object { const val TAG_NAME = "cover" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt index d620dbe45..7ebaacc27 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.audio.track.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class MediaTag { companion object { const val TAG_NAME = "media" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt index 1701e908f..ebc074a71 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt @@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.experimental.audio.track.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.isNotName +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable data class ParticipantTag( @@ -36,26 +37,27 @@ data class ParticipantTag( companion object { const val TAG_NAME = "p" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Tag): ParticipantTag? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ParticipantTag(tag[1], tag.getOrNull(2)) } @JvmStatic fun parseKey(tag: Tag): String? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return tag[1] } @JvmStatic fun assemble( pubkey: HexKey, - relayHint: String?, + relayHint: String? = null, ) = arrayOfNotNull(TAG_NAME, pubkey, relayHint) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt index c0439e8df..dde2c19ae 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.audio.track.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class PriceTag { companion object { const val TAG_NAME = "price" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt index fd3a227c6..34bf125a3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.audio.track.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class TypeTag { companion object { const val TAG_NAME = "c" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt index e8f3acd97..cb514b1d7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt @@ -20,16 +20,19 @@ */ package com.vitorpamplona.quartz.experimental.bounties +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure import java.math.BigDecimal class RewardTag { companion object { const val TAG_NAME = "reward" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): BigDecimal? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return runCatching { BigDecimal(tag[1]) }.getOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt index 9a3217b0e..fa1a7b02e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt @@ -24,7 +24,7 @@ import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag.MARKER fun MarkedETag.Companion.parseFork(tag: Array): MarkedETag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + if (tag.size < 4 || tag[0] != "e") return null if (tag[ORDER_MARKER] != MARKER.FORK.code) return null // ["e", id hex, relay hint, marker, pubkey] return MarkedETag( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt index c09d4e768..28a5eaf8d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.experimental.interactiveStories.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ReadStatusTag { enum class STATUS( val value: String, @@ -34,11 +37,12 @@ class ReadStatusTag { companion object { const val TAG_NAME = "status" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt index 01062321e..ae354c2d0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt @@ -60,7 +60,6 @@ data class RootSceneTag( companion object { const val TAG_NAME = "A" - const val TAG_SIZE = 2 fun assembleATagId( kind: Int, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt index 652e68129..a17697642 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt @@ -35,7 +35,6 @@ class StoryOptionTag( companion object { const val TAG_NAME = "option" - const val TAG_SIZE = 3 @JvmStatic fun parse(tag: Array): StoryOptionTag? { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt index c8f7ded13..f6b2024f3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.nns.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class IPv4Tag { companion object { const val TAG_NAME = "ip4" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt index 07086b274..db673d7db 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.nns.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class IPv6Tag { companion object { const val TAG_NAME = "ip6" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt index 2eca2f76c..7de43e61d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.nns.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class VersionTag { companion object { const val TAG_NAME = "version" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt index df1ccfbfc..ea9b4981a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.relationshipStatus.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class PetnameTag { companion object { const val TAG_NAME = "petname" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt index 3525445d0..b39bce3ae 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.relationshipStatus.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class RankTag { companion object { const val TAG_NAME = "rank" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toIntOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt index 8a7696c29..058c58a74 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.relationshipStatus.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class SummaryTag { companion object { const val TAG_NAME = "summary" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt index 5b07019a8..21883f052 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.zapPolls.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ClosedAtTag { companion object { const val TAG_NAME = "closed_at" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt index 87e3967ec..b1d2fce00 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt @@ -20,16 +20,19 @@ */ package com.vitorpamplona.quartz.experimental.zapPolls.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure import kotlin.math.round class ConsensusThresholdTag { companion object { const val TAG_NAME = "consensus_threshold" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Double? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull()?.toDouble()?.div(100) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt index bf6381dee..286c9876d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.zapPolls.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class MaximumTag { companion object { const val TAG_NAME = "value_maximum" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt index 7f1b8cfe8..5a1fb5ed8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.experimental.zapPolls.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class MinimumTag { companion object { const val TAG_NAME = "value_minimum" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt index f5a74b06d..1ab922f5f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.experimental.zapPolls.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class PollOptionTag( val index: Int, val descriptor: String, @@ -28,11 +31,13 @@ class PollOptionTag( companion object { const val TAG_NAME = "poll_option" - const val TAG_SIZE = 3 @JvmStatic fun parse(tag: Array): PollOptionTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + ensure(tag[2].isNotEmpty()) { return null } val index = tag[1].toIntOrNull() ?: return null diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt index d97acc9c1..d9273988a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt @@ -27,64 +27,12 @@ typealias Tag = Array */ fun Tag.has(index: Int) = size > index -/** - * Returns if the Tag has less than $index elements. - */ -fun Tag.lacks(index: Int) = size <= index - fun Tag.name() = this[0] fun Tag.value() = this[1] -fun Tag.hasValue() = this[1].isNotEmpty() +fun Tag.hasValue() = if (has(1)) this[1].isNotEmpty() else false -fun Tag.nameOrNull() = if (size > 0) name() else null +fun Tag.nameOrNull() = if (has(0)) this[0] else null -fun Tag.valueOrNull() = if (size > 1) value() else null - -fun Tag.isNameUnsafe(name: String): Boolean = name() == name - -fun Tag.isValueUnsafe(value: String): Boolean = value() == value - -fun Tag.isValueInUnsafe(values: Set): Boolean = value() in values - -fun Tag.match(name: String): Boolean = if (size > 0) isNameUnsafe(name) else false - -fun Tag.isValue(value: String): Boolean = if (size > 1) isValueUnsafe(value) else false - -fun Tag.match( - name: String, - value: String, - minSize: Int, -): Boolean = if (size >= minSize) isNameUnsafe(name) && isValueUnsafe(value) else false - -fun Tag.match( - name: String, - values: Set, - minSize: Int, -): Boolean = if (size >= minSize) isNameUnsafe(name) && isValueInUnsafe(values) else false - -fun Tag.match( - name: String, - minSize: Int, -): Boolean = if (size >= minSize) isNameUnsafe(name) else false - -fun Tag.isNotName( - name: String, - minSize: Int, -): Boolean = !match(name, minSize) - -fun Tag.matchAndHasValue( - name: String, - minSize: Int, -): Boolean = if (size >= minSize) isNameUnsafe(name) && hasValue() else false - -fun Tag.valueIfMatches( - name: String, - minSize: Int, -): String? = if (match(name, minSize)) value() else null - -fun Tag.valueToIntIfMatches( - name: String, - minSize: Int, -): Int? = if (match(name, minSize)) value().toIntOrNull() else null +fun Tag.valueOrNull() = if (has(1)) this[1] else null diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt index 3d03f667b..aab802bed 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt @@ -35,7 +35,7 @@ class TagArrayBuilder { tagName: String, tagValue: String, ): TagArrayBuilder { - tagList[tagName]?.removeIf { it.value() == tagValue } + tagList[tagName]?.removeIf { it.valueOrNull() == tagValue } if (tagList[tagName]?.isEmpty() == true) { tagList.remove(tagName) } @@ -46,18 +46,8 @@ class TagArrayBuilder { predicate: (Tag, Tag) -> Boolean, toCompare: Tag, ): TagArrayBuilder { - tagList[toCompare.name()]?.removeIf { predicate(it, toCompare) } - if (tagList[toCompare.name()]?.isEmpty() == true) { - tagList.remove(toCompare.name()) - } - return this - } - - fun removeIf( - tagName: String, - tagValue: String, - ): TagArrayBuilder { - tagList[tagName]?.removeIf { it.value() == tagValue } + val tagName = toCompare.nameOrNull() ?: return this + tagList[tagName]?.removeIf { predicate(it, toCompare) } if (tagList[tagName]?.isEmpty() == true) { tagList.remove(tagName) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt index 2f8e464d1..07ba6ac59 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt @@ -23,9 +23,6 @@ package com.vitorpamplona.quartz.nip01Core.tags.addressables import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.has -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.name -import com.vitorpamplona.quartz.nip01Core.core.value import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory @@ -57,40 +54,44 @@ data class ATag( companion object { const val TAG_NAME = "a" - const val TAG_SIZE = 2 @JvmStatic - fun isTagged(tag: Array) = tag.size >= 2 && tag[0] == TAG_NAME && tag[1].isNotEmpty() + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() - @JvmStatic fun isSameAddress( tag1: Array, tag2: Array, - ) = tag1.match(tag2.name(), tag2.value(), TAG_SIZE) + ): Boolean { + ensure(tag1.has(1)) { return false } + ensure(tag2.has(1)) { return false } + ensure(tag1[0] == tag2[0]) { return false } + ensure(tag1[1] == tag2[1]) { return false } + return true + } @JvmStatic fun isTagged( tag: Array, addressId: String, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == addressId + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId @JvmStatic fun isTagged( tag: Array, address: ATag, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == address.toTag() + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag() @JvmStatic fun isIn( tag: Array, addressIds: Set, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in addressIds + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds @JvmStatic fun isTaggedWithKind( tag: Array, kind: String, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && Address.isOfKind(tag[1], kind) + ) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], kind) @JvmStatic fun parseIfOfKind( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt index 0f59c7897..de7942709 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt @@ -22,10 +22,12 @@ package com.vitorpamplona.quartz.nip01Core.tags.events import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -56,32 +58,38 @@ data class ETag( companion object { const val TAG_NAME = "e" - const val TAG_SIZE = 2 @JvmStatic - fun isTagged(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].length == 64 + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].length == 64 @JvmStatic fun isTagged( tag: Array, eventId: HexKey, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == eventId + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId @JvmStatic fun parse(tag: Array): ETag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ETag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } @JvmStatic fun parseId(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return tag[1] } @JvmStatic fun parseAsHint(tag: Array): EventIdHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return EventIdHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt index 773b14db2..ac11b4d46 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt @@ -20,17 +20,21 @@ */ package com.vitorpamplona.quartz.nip01Core.tags.hashtags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class HashtagTag { companion object { const val TAG_NAME = "t" - const val TAG_SIZE = 2 @JvmStatic - fun isTagged(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty() + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].isEmpty()) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt index 051199464..c076c4dee 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt @@ -20,26 +20,30 @@ */ package com.vitorpamplona.quartz.nip01Core.tags.kinds +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class KindTag { companion object { const val TAG_NAME = "k" - const val TAG_SIZE = 2 - fun match(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME fun isTagged( tag: Array, kind: String, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == kind + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == kind fun isIn( tag: Array, kinds: Set, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in kinds + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in kinds @JvmStatic fun parse(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toInt() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt index 55a2270ac..53e941df9 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt @@ -24,12 +24,14 @@ import android.util.Log import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile import com.vitorpamplona.quartz.nip19Bech32.toNpub import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -50,24 +52,25 @@ data class PTag( companion object { const val TAG_NAME = "p" - const val TAG_SIZE = 2 fun isTagged( tag: Array, key: HexKey, - ): Boolean = tag.size >= 2 && tag[0] == TAG_NAME && tag[1] == key + ): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] == key @JvmStatic fun parse(tag: Tag): PTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return PTag(tag[1], tag.getOrNull(2)) } @JvmStatic fun parseKey(tag: Array): HexKey? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].length != 64) { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { Log.w("PTag", "Invalid `$TAG_NAME` value ${tag.joinToString(", ")}") return null } @@ -76,7 +79,10 @@ data class PTag( @JvmStatic fun parseAsHint(tag: Array): PubKeyHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return PubKeyHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt index 8c07af9ff..1ad8b8f29 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt @@ -20,34 +20,38 @@ */ package com.vitorpamplona.quartz.nip01Core.tags.references +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip96FileStorage.HttpUrlFormatter +import com.vitorpamplona.quartz.utils.ensure class ReferenceTag { companion object { const val TAG_NAME = "r" - const val TAG_SIZE = 2 @JvmStatic fun isTagged( tag: Array, reference: String, - ): Boolean = tag.size >= 2 && tag[0] == TAG_NAME && tag[1] == reference + ): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] == reference @JvmStatic fun isIn( tag: Array, references: Set, - ): Boolean = tag.size >= 2 && tag[0] == TAG_NAME && tag[1] in references + ): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] in references @JvmStatic fun hasReference(tag: Array): Boolean { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return false + ensure(tag.has(1)) { return false } + ensure(tag[0] == TAG_NAME) { return false } return tag[1].isNotEmpty() } @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt index eab66cef3..d40a858c7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt @@ -23,11 +23,13 @@ package com.vitorpamplona.quartz.nip02FollowList.tags import android.util.Log import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -56,20 +58,23 @@ data class ContactTag( companion object { const val TAG_NAME = "p" - const val TAG_SIZE = 2 @JvmStatic - fun isTagged(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty() + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): ContactTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ContactTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } @JvmStatic fun parseValid(tag: Array): ContactTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return try { ContactTag(decodePublicKey(tag[1]).toHexKey(), tag.getOrNull(2), tag.getOrNull(3)) } catch (e: Exception) { @@ -80,13 +85,17 @@ data class ContactTag( @JvmStatic fun parseKey(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return tag[1] } @JvmStatic fun parseValidKey(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return try { decodePublicKey(tag[1]).toHexKey() } catch (e: Exception) { @@ -97,7 +106,10 @@ data class ContactTag( @JvmStatic fun parseAsHint(tag: Array): PubKeyHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return PubKeyHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt index dc150479c..15ecd3494 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt @@ -22,11 +22,13 @@ package com.vitorpamplona.quartz.nip10Notes.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint import com.vitorpamplona.quartz.nip01Core.tags.events.GenericETag import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -71,7 +73,7 @@ data class MarkedETag( companion object { const val TAG_NAME = "e" - const val TAG_SIZE = 4 + private const val TAG_SIZE = 4 const val ORDER_NAME = 0 const val ORDER_EVT_ID = 1 @@ -183,7 +185,10 @@ data class MarkedETag( @JvmStatic fun parseAsHint(tag: Array): EventIdHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return EventIdHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt index 7b0947b38..d21e0d341 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt @@ -48,9 +48,9 @@ fun List.positionalMarkedTags( } }.map { when (it) { - root -> arrayOf(tagName, it, "", "root") - replyingTo -> arrayOf(tagName, it, "", "reply") - forkedFrom -> arrayOf(tagName, it, "", "fork") + root -> arrayOf(tagName, it, "", MarkedETag.MARKER.ROOT) + replyingTo -> arrayOf(tagName, it, "", MarkedETag.MARKER.REPLY) + forkedFrom -> arrayOf(tagName, it, "", MarkedETag.MARKER.FORK) else -> arrayOf(tagName, it) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt index 8c2fd5510..8b2674324 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt @@ -20,8 +20,10 @@ */ package com.vitorpamplona.quartz.nip13Pow.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes class PoWTag( @@ -34,20 +36,23 @@ class PoWTag( companion object { const val TAG_NAME = "nonce" - const val TAG_SIZE = 2 @JvmStatic - fun hasTagWithContent(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty() + fun hasTagWithContent(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): PoWTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return PoWTag(tag[1], tag.getOrNull(2)?.toIntOrNull()) } @JvmStatic fun parseCommitment(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag.getOrNull(2)?.toIntOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt index 8489b1669..4d8becbb8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt @@ -20,17 +20,21 @@ */ package com.vitorpamplona.quartz.nip14Subject +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class SubjectTag { companion object { const val TAG_NAME = "subject" - const val TAG_SIZE = 2 @JvmStatic - fun hasTagWithContent(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty() + fun hasTagWithContent(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt index a44727038..8fe35ea2d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt @@ -20,17 +20,21 @@ */ package com.vitorpamplona.quartz.nip17Dm.files.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class EncryptionAlgo { companion object { const val TAG_NAME = "encryption-algorithm" - const val TAG_SIZE = 2 @JvmStatic - fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun isTag(tag: Array) = tag.has(1) && tag[0] == TAG_NAME @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt index d3ed47c41..6dbfdf91b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt @@ -20,20 +20,23 @@ */ package com.vitorpamplona.quartz.nip17Dm.files.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.utils.ensure class EncryptionKey { companion object { const val TAG_NAME = "decryption-key" - const val TAG_SIZE = 2 @JvmStatic - fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun isTag(tag: Array) = tag.has(0) && tag[0] == TAG_NAME @JvmStatic fun parse(tag: Array): ByteArray? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return runCatching { tag[1].hexToByteArray() }.getOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt index b34eb627e..1a792a3df 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt @@ -20,20 +20,23 @@ */ package com.vitorpamplona.quartz.nip17Dm.files.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.utils.ensure class EncryptionNonce { companion object { const val TAG_NAME = "decryption-nonce" - const val TAG_SIZE = 2 @JvmStatic - fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun isTag(tag: Array) = tag.has(0) && tag[0] == TAG_NAME @JvmStatic fun parse(tag: Array): ByteArray? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return runCatching { tag[1].hexToByteArray() }.getOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt index 38620fd2d..3cae4c9d3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt @@ -20,19 +20,23 @@ */ package com.vitorpamplona.quartz.nip17Dm.files.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class FileTypeTag { companion object { const val TAG_NAME = "file-type" - const val TAG_SIZE = 2 fun isIn( tag: Array, mimeTypes: Set, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in mimeTypes + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in mimeTypes @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt index a0e4ac3a8..cc78b6f5d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt @@ -59,13 +59,13 @@ data class QAddressableTag( override fun toTagArray() = assemble(address, relay) companion object { - const val TAG_SIZE = 2 + const val TAG_NAME = "q" @JvmStatic fun parse(tag: Array): QAddressableTag? { ensure(tag.has(1)) { return null } - ensure(tag[0] == QTag.TAG_NAME) { return null } - ensure(tag[1].isNotEmpty()) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length != 64) { return null } val address = Address.parse(tag[1]) ?: return null return QAddressableTag(address, tag.getOrNull(2)) } @@ -76,12 +76,12 @@ data class QAddressableTag( pubKeyHex: HexKey, dTag: String, relay: String?, - ) = arrayOfNotNull(QTag.TAG_NAME, Address.assemble(kind, pubKeyHex, dTag), relay) + ) = arrayOfNotNull(TAG_NAME, Address.assemble(kind, pubKeyHex, dTag), relay) @JvmStatic fun assemble( address: Address, relay: String?, - ) = arrayOfNotNull(QTag.TAG_NAME, address.toValue(), relay) + ) = arrayOfNotNull(TAG_NAME, address.toValue(), relay) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt index 511bb12d0..995890fd0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt @@ -22,8 +22,10 @@ package com.vitorpamplona.quartz.nip18Reposts.quotes import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -47,11 +49,13 @@ data class QEventTag( override fun toTagArray() = assemble(eventId, relay, author) companion object { - const val TAG_SIZE = 2 + const val TAG_NAME = "q" @JvmStatic fun parse(tag: Array): QEventTag? { - if (tag.size < TAG_SIZE || tag[0] != QTag.TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return QEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } @@ -60,6 +64,6 @@ data class QEventTag( eventId: HexKey, relay: String?, author: HexKey?, - ) = arrayOfNotNull(QTag.TAG_NAME, eventId, relay, author) + ) = arrayOfNotNull(TAG_NAME, eventId, relay, author) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt index 52e6d1490..5feeab571 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt @@ -20,35 +20,46 @@ */ package com.vitorpamplona.quartz.nip18Reposts.quotes +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint +import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.utils.ensure interface QTag { fun toTagArray(): Array companion object { const val TAG_NAME = "q" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): QTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } return if (tag[1].length == 64) { - QEventTag.parse(tag) + QEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } else { - QAddressableTag.parse(tag) + val address = Address.parse(tag[1]) ?: return null + QAddressableTag(address, tag.getOrNull(2)) } } @JvmStatic fun parseEventAsHint(tag: Array): EventIdHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return EventIdHint(tag[1], tag[2]) } @JvmStatic fun parseAddressAsHint(tag: Array): AddressHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length == 64 || !tag[1].contains(':') || tag[2].isEmpty()) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length != 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } + ensure(!tag[1].contains(':')) { return null } return AddressHint(tag[1], tag[2]) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt index 83e94ac49..14410a207 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt @@ -107,8 +107,8 @@ class CommentEvent( ?: tags.lastNotNullOfOrNull(RootEventTag::parseKey) fun replyingToAddressId(): String? = - tags.lastNotNullOfOrNull(RootAddressTag::parseAddress) - ?: tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddress) + tags.lastNotNullOfOrNull(RootAddressTag::parseAddressId) + ?: tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddressId) override fun replyingToAddressOrEvent(): HexKey? = replyingToAddressId() ?: replyingTo() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt index 9e930bbf3..43933ee44 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt @@ -23,11 +23,11 @@ package com.vitorpamplona.quartz.nip22Comments.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable class ReplyAddressTag( @@ -38,23 +38,32 @@ class ReplyAddressTag( companion object { const val TAG_NAME = "a" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): ReplyAddressTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ReplyAddressTag(tag[1], tag.getOrNull(2)) } @JvmStatic - fun parseAddress(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parseAddressId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun parseAsHint(tag: Array): AddressHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length == 64 || !tag[1].contains(':') || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return AddressHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt index 1f647c2d5..c6e8598d7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt @@ -23,10 +23,11 @@ package com.vitorpamplona.quartz.nip22Comments.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable data class ReplyAuthorTag( @@ -37,28 +38,32 @@ data class ReplyAuthorTag( companion object { const val TAG_NAME = "p" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Tag): ReplyAuthorTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ReplyAuthorTag(tag[1], tag.getOrNull(2)) } @JvmStatic fun parseKey(tag: Tag): HexKey? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return tag[1] } @JvmStatic fun parseAsHint(tag: Array): PubKeyHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return PubKeyHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt index 77f68fbc4..337e6f35b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt @@ -23,11 +23,12 @@ package com.vitorpamplona.quartz.nip22Comments.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference +import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable class ReplyEventTag( @@ -39,42 +40,53 @@ class ReplyEventTag( companion object { const val TAG_NAME = "e" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun isTagged( tag: Array, eventId: String, - ) = tag.match(TAG_NAME, eventId, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId @JvmStatic fun isIn( tag: Array, eventIds: Set, - ) = tag.match(TAG_NAME, eventIds, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds @JvmStatic fun parse(tag: Array): ReplyEventTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ReplyEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } @JvmStatic - fun parseKey(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parseKey(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + return tag[1] + } @JvmStatic fun parseValidKey(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(Hex.isHex(tag[1])) { return null } return tag[1] } @JvmStatic fun parseAsHint(tag: Array): EventIdHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return EventIdHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt index 9926fa8ff..f0e2299e8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt @@ -22,24 +22,28 @@ package com.vitorpamplona.quartz.nip22Comments.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId import com.vitorpamplona.quartz.nip73ExternalIds.GeohashId import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable class ReplyIdentifierTag { companion object { const val TAG_NAME = "i" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun parse(tag: Tag) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parse(tag: Tag): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun assemble( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt index f3a0028ed..43647bcb2 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt @@ -21,36 +21,36 @@ package com.vitorpamplona.quartz.nip22Comments.tags import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.matchAndHasValue -import com.vitorpamplona.quartz.nip01Core.core.valueToIntIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId +import com.vitorpamplona.quartz.utils.ensure class ReplyKindTag { companion object { const val TAG_NAME = "k" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) - - @JvmStatic - fun isTagged(tag: Tag) = tag.matchAndHasValue(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun isTagged( tag: Tag, kind: String, - ) = tag.match(TAG_NAME, kind, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == kind @JvmStatic fun isIn( tag: Tag, kinds: Set, - ) = tag.match(TAG_NAME, kinds, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in kinds @JvmStatic - fun parse(tag: Tag) = tag.valueToIntIfMatches(TAG_NAME, TAG_SIZE) + fun parse(tag: Tag): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun assemble(kind: String) = arrayOf(TAG_NAME, kind) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt index 3241fbfc6..46263abe2 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt @@ -24,8 +24,6 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag import com.vitorpamplona.quartz.nip01Core.core.has -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address import com.vitorpamplona.quartz.utils.arrayOfNotNull @@ -40,26 +38,27 @@ class RootAddressTag( companion object { const val TAG_NAME = "A" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun isTagged( tag: Array, addressId: String, - ) = tag.match(TAG_NAME, addressId, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId @JvmStatic fun isIn( tag: Array, addressIds: Set, - ) = tag.match(TAG_NAME, addressIds, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds @JvmStatic fun parse(tag: Array): RootAddressTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return RootAddressTag(tag[1], tag.getOrNull(2)) } @@ -72,11 +71,19 @@ class RootAddressTag( } @JvmStatic - fun parseAddress(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parseAddressId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun parseAsHint(tag: Array): AddressHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length == 64 || !tag[1].contains(':') || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return AddressHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt index dcae0d9f9..e548f60bc 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt @@ -24,11 +24,11 @@ import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.PUBKEY_LENGTH import com.vitorpamplona.quartz.nip01Core.core.Tag +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag import com.vitorpamplona.quartz.utils.arrayOfNotNull -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract +import com.vitorpamplona.quartz.utils.ensure @Immutable data class RootAuthorTag( @@ -41,7 +41,7 @@ data class RootAuthorTag( const val TAG_NAME = "P" @JvmStatic - fun match(tag: Tag) = tag.size > 1 && tag[0] == TAG_NAME + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Tag): ReplyAuthorTag? { @@ -65,19 +65,13 @@ data class RootAuthorTag( @JvmStatic fun parseAsHint(tag: Array): PubKeyHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != PUBKEY_LENGTH || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return PubKeyHint(tag[1], tag[2]) } - @OptIn(ExperimentalContracts::class) - inline fun ensure( - condition: Boolean, - exit: () -> Nothing, - ) { - contract { returns() implies condition } - if (!condition) exit() - } - @JvmStatic fun assemble( pubkey: HexKey, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt index c0d546f92..9796e5ec6 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt @@ -23,11 +23,12 @@ package com.vitorpamplona.quartz.nip22Comments.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference +import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable class RootEventTag( @@ -39,42 +40,53 @@ class RootEventTag( companion object { const val TAG_NAME = "E" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun isTagged( tag: Array, eventId: String, - ) = tag.match(TAG_NAME, eventId, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId @JvmStatic fun isIn( tag: Array, eventIds: Set, - ) = tag.match(TAG_NAME, eventIds, TAG_SIZE) + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds @JvmStatic fun parse(tag: Array): RootEventTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return RootEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } @JvmStatic - fun parseKey(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parseKey(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + return tag[1] + } @JvmStatic fun parseValidKey(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(Hex.isHex(tag[1])) { return null } return tag[1] } @JvmStatic fun parseAsHint(tag: Array): EventIdHint? { - if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } return EventIdHint(tag[1], tag[2]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt index 34aa37a9d..d896b3e66 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt @@ -22,24 +22,28 @@ package com.vitorpamplona.quartz.nip22Comments.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId import com.vitorpamplona.quartz.nip73ExternalIds.GeohashId import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable class RootIdentifierTag { companion object { const val TAG_NAME = "I" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun parse(tag: Tag) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parse(tag: Tag): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun assemble( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt index bbbe5861d..7a1a31767 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt @@ -21,20 +21,24 @@ package com.vitorpamplona.quartz.nip22Comments.tags import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId +import com.vitorpamplona.quartz.utils.ensure class RootKindTag { companion object { const val TAG_NAME = "K" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun parse(tag: Tag) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parse(tag: Tag): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun assemble(kind: String): Tag = arrayOf(TAG_NAME, kind) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt index d7e74286e..0ab6c6714 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip23LongContent.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ImageTag { companion object { const val TAG_NAME = "image" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt index 671348f27..21f33adb4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip23LongContent.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class PublishedAtTag { companion object { const val TAG_NAME = "published_at" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt index 1e04d3b56..6acbac79d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip23LongContent.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class SummaryTag { companion object { const val TAG_NAME = "summary" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt index 409c4eb1d..77b6ab6d6 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip23LongContent.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class TitleTag { companion object { const val TAG_NAME = "title" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt index b28d37465..8a7df1c4a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt @@ -20,18 +20,22 @@ */ package com.vitorpamplona.quartz.nip31Alts +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class AltTag { companion object { const val TAG_NAME = "alt" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } @JvmStatic - fun assemble(altDescriptor: String = "") = arrayOf(TAG_NAME, altDescriptor) + fun assemble(altDescriptor: String) = arrayOf(TAG_NAME, altDescriptor) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt index 47b1e1f18..b4a95634e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip34Git.repository.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class CloneTag { companion object { const val TAG_NAME = "clone" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt index 4256d7efe..faea7e0d8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip34Git.repository.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class DescriptionTag { companion object { const val TAG_NAME = "description" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt index 8fc6aff04..147b52d35 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip34Git.repository.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class NameTag { companion object { const val TAG_NAME = "name" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt index 5c5445856..3dae53fa3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip34Git.repository.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class WebTag { companion object { const val TAG_NAME = "web" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt index 85697ae11..47feee58c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip35Torrents.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class BtihTag { companion object { const val TAG_NAME = "btih" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt index 895eb423c..0639907fe 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.quartz.nip35Torrents.tags import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip46RemoteSigner.getOrNull +import com.vitorpamplona.quartz.utils.ensure @Immutable class FileTag( @@ -32,18 +34,21 @@ class FileTag( companion object { const val TAG_NAME = "file" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): FileTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return FileTag(tag[1], tag.getOrNull(2)?.toLongOrNull()) } @JvmStatic fun parseBytes(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - return tag.getOrNull(2)?.toLongOrNull() + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[2].isNotEmpty()) { return null } + return tag[2].toLongOrNull() } @JvmStatic diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt index 744aad94d..1e0786ebf 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip35Torrents.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class InfoHashTag { companion object { const val TAG_NAME = "x" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt index 8282c3670..4e154418e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip35Torrents.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class TrackerTag { companion object { const val TAG_NAME = "tracker" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt index 934a4637b..fb1b44148 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class CurrentParticipantsTag { companion object { const val TAG_NAME = "current_participants" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toIntOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt index 0597b1d54..26d212547 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class EndsTag { companion object { const val TAG_NAME = "ends" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt index a77348d0d..c48c004c3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt @@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.isNotName +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure enum class ROLE( val code: String, @@ -43,40 +44,43 @@ data class ParticipantTag( ) : PubKeyReferenceTag { companion object { const val TAG_NAME = "p" - const val TAG_SIZE = 2 fun isIn( tag: Array, keys: Set, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in keys + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in keys @JvmStatic fun isHost(tag: Tag): Boolean { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return false - if (tag[1].length != 64) return false - if (tag.getOrNull(3).equals(ROLE.HOST.code)) return true - return false + ensure(tag.has(3)) { return false } + ensure(tag[0] == TAG_NAME) { return false } + ensure(tag[1].length == 64) { return false } + ensure(tag[3] == ROLE.HOST.code) { return false } + return true } @JvmStatic fun parse(tag: Tag): ParticipantTag? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ParticipantTag(tag[1], tag.getOrNull(2), tag.getOrNull(3), tag.getOrNull(4)) } @JvmStatic fun parseHost(tag: Tag): ParticipantTag? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null - if (!tag.getOrNull(3).equals(ROLE.HOST.code)) return null - return ParticipantTag(tag[1], tag.getOrNull(2), tag.getOrNull(3), tag.getOrNull(4)) + ensure(tag.has(3)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[3] == ROLE.HOST.code) { return null } + return ParticipantTag(tag[1], tag[2], tag[3], tag.getOrNull(4)) } @JvmStatic fun parseKey(tag: Tag): String? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt index fda64e467..70627f607 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt @@ -20,16 +20,20 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class RelayListTag( val relayUrls: List, ) { companion object { const val TAG_NAME = "relays" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): RelayListTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } val relays = tag.mapIndexedNotNull { index, s -> if (index == 0) null else s diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt index 8c6cb4488..7a094d643 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class StartsTag { companion object { const val TAG_NAME = "starts" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt index d6e651b9e..f41dcfc05 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class StatusTag { enum class STATUS( val code: String, @@ -34,11 +37,12 @@ class StatusTag { companion object { const val TAG_NAME = "status" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt index 7303e1a44..667cf742e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class StreamingTag { companion object { const val TAG_NAME = "streaming" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt index 52bbeac76..ac34c4bf6 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class TotalParticipantsTag { companion object { const val TAG_NAME = "total_participants" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toIntOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt index 192ade51d..66feb1396 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt @@ -31,7 +31,7 @@ class ZapRaiserTag( fun toTagArray() = assemble(amountInSats) companion object { - val TAG_NAME = "zapraiser" + const val TAG_NAME = "zapraiser" @JvmStatic fun parse(tags: Array): ZapRaiserTag? { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt index 4e4fed010..fc378cc8a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip71Video.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class DurationTag { companion object { const val TAG_NAME = "duration" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toIntOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt index 6feb0fea0..2d4a09bd4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt @@ -20,7 +20,9 @@ */ package com.vitorpamplona.quartz.nip71Video.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure class SegmentTag( val start: String, // HH:MM:SS.sss @@ -32,11 +34,14 @@ class SegmentTag( companion object { const val TAG_NAME = "segment" - const val TAG_SIZE = 4 @JvmStatic fun parse(tag: Array): SegmentTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(3)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + ensure(tag[2].isNotEmpty()) { return null } + ensure(tag[3].isNotEmpty()) { return null } return SegmentTag(tag[1], tag[2], tag[3], tag.getOrNull(4)) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt index 1f0680f46..a624e0c09 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt @@ -21,8 +21,10 @@ package com.vitorpamplona.quartz.nip71Video.tags import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.pointerSizeInBytes data class TextTrackTag( @@ -38,11 +40,12 @@ data class TextTrackTag( companion object { const val TAG_NAME = "text-track" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): TextTrackTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return TextTrackTag(tag[1], tag.getOrNull(2)) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt index 84a2edb9d..5e76b7ed9 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class DescriptionTag { companion object { const val TAG_NAME = "description" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt index 9bae1d46a..2f3a7dbe3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt @@ -20,8 +20,10 @@ */ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure class ImageTag( val imageUrl: String, @@ -29,12 +31,12 @@ class ImageTag( ) { companion object { const val TAG_NAME = "image" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): ImageTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null - if (tag[1].isEmpty()) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } val dims = tag.getOrNull(2)?.let { DimensionTag.parse(it) } return ImageTag(tag[1], dims) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt index 08d901cc2..ddd2b46a2 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt @@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Tag -import com.vitorpamplona.quartz.nip01Core.core.isNotName +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure @Immutable data class ModeratorTag( @@ -37,19 +38,20 @@ data class ModeratorTag( companion object { const val TAG_NAME = "p" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Tag): ModeratorTag? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return ModeratorTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } @JvmStatic fun parseKey(tag: Tag): String? { - if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null - if (tag[1].length != 64) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt index 0c66da66d..5f23b0f82 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class NameTag { companion object { const val TAG_NAME = "name" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt index a70f2f7d9..6e5c4dd14 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt @@ -20,7 +20,9 @@ */ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure class RelayTag( val url: String, @@ -30,11 +32,12 @@ class RelayTag( companion object { const val TAG_NAME = "relay" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): RelayTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return RelayTag(tag[1], tag.getOrNull(2)) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt index 467216f8f..3908eca08 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class RulesTag { companion object { const val TAG_NAME = "rules" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt index e5c24cae3..177c985ab 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip75ZapGoals.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class AmountTag { companion object { const val TAG_NAME = "amount" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt index 2fbd2a885..2af7ec8d6 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip75ZapGoals.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ClosedAtTag { companion object { const val TAG_NAME = "closed_at" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Long? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toLongOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt index bb5c0811a..9f49ce761 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt @@ -20,16 +20,20 @@ */ package com.vitorpamplona.quartz.nip75ZapGoals.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class RelayListTag( val relayUrls: List, ) { companion object { const val TAG_NAME = "relays" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): RelayListTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } val relays = tag.mapIndexedNotNull { index, s -> if (index == 0) null else s diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt index 6e46a32e7..80daa368f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip84Highlights.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ContextTag { companion object { const val TAG_NAME = "context" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt index 94f5f798d..8c18bf480 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.quartz.nip89AppHandlers.definition.tags import com.vitorpamplona.quartz.nip01Core.core.Tag +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip89AppHandlers.PlatformType import com.vitorpamplona.quartz.utils.arrayOfNotNull @@ -32,14 +33,10 @@ class PlatformLinkTag( fun toTagArray() = assemble(platform, uri, entityType) companion object { - const val TAG_SIZE = 3 - @JvmStatic fun match(tag: Tag): Boolean = - if (tag.size >= TAG_SIZE) { - tag[0] == PlatformType.IOS.code || - tag[0] == PlatformType.WEB.code || - tag[0] == PlatformType.ANDROID.code + if (tag.has(2)) { + tag[0] == PlatformType.IOS.code || tag[0] == PlatformType.WEB.code || tag[0] == PlatformType.ANDROID.code } else { false } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt index 0b3db29b5..4b98181e7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt @@ -43,7 +43,7 @@ class AppRecommendationEvent( ) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { fun recommendations() = tags.mapNotNull(RecommendationTag::parse) - fun recommendationAddresses() = tags.mapNotNull(RecommendationTag::parseAddress) + fun recommendationAddresses() = tags.mapNotNull(RecommendationTag::parseAddressId) companion object { const val KIND = 31989 diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt index 045510e69..22c8fea76 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt @@ -23,8 +23,6 @@ package com.vitorpamplona.quartz.nip89AppHandlers.recommendation.tags import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip01Core.core.Tag import com.vitorpamplona.quartz.nip01Core.core.has -import com.vitorpamplona.quartz.nip01Core.core.match -import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.ensure @@ -39,10 +37,9 @@ class RecommendationTag( companion object { const val TAG_NAME = "a" - const val TAG_SIZE = 2 @JvmStatic - fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE) + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): RecommendationTag? { @@ -54,7 +51,12 @@ class RecommendationTag( } @JvmStatic - fun parseAddress(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE) + fun parseAddressId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } @JvmStatic fun assemble( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt index 7f8c1c8d1..1c7d89153 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.nip92IMeta +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class IMetaTag( val url: String, val properties: Map>, @@ -38,10 +41,11 @@ class IMetaTag( companion object { const val TAG_NAME = "imeta" const val ANCHOR_PROPERTY = "url" - const val TAG_SIZE = 2 fun parse(tag: Array): IMetaTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } val allTags = parseIMeta(tag) val url = allTags.get(ANCHOR_PROPERTY)?.firstOrNull() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt index 329314d84..57c646976 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class BlurhashTag { companion object { const val TAG_NAME = "blurhash" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt index dcfd011ca..515b7c916 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class DimensionTag( val width: Int, val height: Int, @@ -34,11 +37,12 @@ class DimensionTag( companion object { const val TAG_NAME = "dim" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): DimensionTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return parse(tag[1]) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt index 4eca6fd06..b00bfc43c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class FallbackTag { companion object { const val TAG_NAME = "fallback" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt index 9880e9162..adf394c3a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class HashSha256Tag { companion object { const val TAG_NAME = "x" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].isEmpty()) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt index 7aa7274a2..f8423bd3e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.quartz.nip94FileMetadata.tags import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure class ImageTag( val imageUrl: String, @@ -29,11 +31,12 @@ class ImageTag( ) { companion object { const val TAG_NAME = "image" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): ImageTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return ImageTag(tag[1], tag.getOrNull(2)) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt index 9bd916cd7..d608a6f64 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class MagnetTag { companion object { const val TAG_NAME = "magnet" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt index 416b3f0a1..c0555f089 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt @@ -20,19 +20,23 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class MimeTypeTag { companion object { const val TAG_NAME = "m" - const val TAG_SIZE = 2 fun isIn( tag: Array, mimeTypes: Set, - ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in mimeTypes + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in mimeTypes @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt index 6cc31f954..f17d01e75 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class OriginalHashTag { companion object { const val TAG_NAME = "ox" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt index ca4703c05..18c6a9315 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt @@ -20,17 +20,21 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ServiceTag { companion object { const val TAG_NAME = "service" - const val TAG_SIZE = 2 @JvmStatic - fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun isTag(tag: Array) = tag.has(0) && tag[0] == TAG_NAME @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt index 4548ea5f5..645a77d1f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class SizeTag { companion object { const val TAG_NAME = "size" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): Int? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1].toIntOrNull() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt index b854ab7a6..b0a405fca 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt @@ -20,17 +20,21 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class SummaryTag { companion object { const val TAG_NAME = "summary" - const val TAG_SIZE = 2 @JvmStatic - fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun isTag(tag: Array) = tag.has(1) && tag[0] == TAG_NAME @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt index e11faa1be..52e53e202 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.quartz.nip94FileMetadata.tags import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure class ThumbTag( val imageUrl: String, @@ -29,11 +31,12 @@ class ThumbTag( ) { companion object { const val TAG_NAME = "thumb" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): ThumbTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return ThumbTag(tag[1], tag.getOrNull(2)) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt index 93b87b03e..4ea1a7d4b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class TorrentInfoHash { companion object { const val TAG_NAME = "i" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt index 4be128be8..05bcd0a91 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt @@ -20,17 +20,21 @@ */ package com.vitorpamplona.quartz.nip94FileMetadata.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class UrlTag { companion object { const val TAG_NAME = "url" - const val TAG_SIZE = 2 @JvmStatic - fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME + fun isTag(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt index 4e326bb9d..2b926fb6e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt @@ -20,16 +20,20 @@ */ package com.vitorpamplona.quartz.nip96FileStorage.config.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ServerTag { companion object { const val TAG_NAME = "server" - const val TAG_SIZE = 2 fun isTag(tag: Array) = tag[0] == TAG_NAME @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt index 5459cfdf5..bd127fd4f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip98HttpAuth.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class MethodTag { companion object { const val TAG_NAME = "method" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt index f8ae6d583..1c342f818 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt @@ -21,17 +21,20 @@ package com.vitorpamplona.quartz.nip98HttpAuth.tags import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.utils.ensure import com.vitorpamplona.quartz.utils.sha256.sha256 class PayloadHashTag { companion object { const val TAG_NAME = "payload" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): HexKey? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt index a8f1afd40..1895606d7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip98HttpAuth.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class UrlTag { companion object { const val TAG_NAME = "u" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt index 658a694c6..2071cf817 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.nip99Classifieds.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class ConditionTag { enum class CONDITION( val value: String, @@ -35,11 +38,12 @@ class ConditionTag { companion object { const val TAG_NAME = "condition" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt index 1d8966ef1..1a7efcd9e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt @@ -20,14 +20,18 @@ */ package com.vitorpamplona.quartz.nip99Classifieds.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class LocationTag { companion object { const val TAG_NAME = "location" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt index 48c2e4801..e432aef23 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt @@ -20,7 +20,9 @@ */ package com.vitorpamplona.quartz.nip99Classifieds.tags +import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure data class PriceTag( val amount: String, @@ -31,11 +33,12 @@ data class PriceTag( companion object { const val TAG_NAME = "price" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): PriceTag? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return PriceTag(tag[1], tag.getOrNull(2), tag.getOrNull(3)) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt index d4365b82f..149cc9c74 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt @@ -20,6 +20,9 @@ */ package com.vitorpamplona.quartz.nip99Classifieds.tags +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + class StatusTag { enum class STATUS( val value: String, @@ -33,11 +36,12 @@ class StatusTag { companion object { const val TAG_NAME = "status" - const val TAG_SIZE = 2 @JvmStatic fun parse(tag: Array): String? { - if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } return tag[1] } From 4d4c7d5b721f46a307b107d957434b79e8086bf5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 17:38:05 -0400 Subject: [PATCH 11/18] Moves DVM's last announcement restriction from 90 days to 365 days --- .../vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt index da10b3858..6fc4cf22c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt @@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils open class DiscoverNIP89FeedFilter( val account: Account, ) : AdditiveFeedFilter() { - val lastAnnounced = 90 * 24 * 60 * 60 // 90 Days ago + val lastAnnounced = 365 * 24 * 60 * 60 // 365 Days ago // TODO better than announced would be last active, as this requires the DVM provider to regularly update the NIP89 announcement override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList() @@ -43,8 +43,6 @@ open class DiscoverNIP89FeedFilter( followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex) override fun feed(): List { - val filterParams = buildFilterParams(account) - val notes = LocalCache.addressables.filterIntoSet { _, it -> acceptDVM(it) From 311964ce336becd3f41895f111adc571145209ce Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 13 Mar 2025 19:30:57 -0400 Subject: [PATCH 12/18] Caches video and image aspect ratios to recompose with the correct size --- .../amethyst/model/MediaAspectRatioCache.kt | 39 +++++++++++++++++++ .../playback/MultiPlayerPlaybackManager.kt | 6 +++ .../amethyst/ui/components/VideoView.kt | 6 ++- .../ui/components/ZoomableContentView.kt | 23 +++++++---- 4 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt new file mode 100644 index 000000000..6790b244a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024 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.model + +import android.util.LruCache + +object MediaAspectRatioCache { + val mediaAspectRatioCacheByUrl = LruCache(1000) + + fun get(url: String) = mediaAspectRatioCacheByUrl.get(url) + + fun add( + url: String, + width: Int, + height: Int, + ) { + if (height > 1) { + mediaAspectRatioCacheByUrl.put(url, width.toFloat() / height.toFloat()) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt index f04170dc1..80989981a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt @@ -30,9 +30,11 @@ import androidx.media3.common.Player import androidx.media3.common.Player.PositionInfo import androidx.media3.common.Player.STATE_IDLE import androidx.media3.common.Player.STATE_READY +import androidx.media3.common.VideoSize import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaSession +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.ui.MainActivity import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -113,6 +115,10 @@ class MultiPlayerPlaybackManager( // avoids saving positions for live streams otherwise caching goes crazy val mustCachePositions = !uri.contains(".m3u8", true) + override fun onVideoSizeChanged(videoSize: VideoSize) { + MediaAspectRatioCache.add(uri, videoSize.width, videoSize.height) + } + override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { player.setWakeMode(C.WAKE_MODE_NETWORK) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 0fbc34067..e9cacf55c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -98,6 +98,7 @@ import com.linc.audiowaveform.infiniteLinearGradient import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache import com.vitorpamplona.amethyst.commons.compose.produceCachedState +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager import com.vitorpamplona.amethyst.service.playback.PlaybackClientController import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk @@ -261,7 +262,8 @@ fun VideoView( } if (blurhash == null) { - val ratio = dimensions?.aspectRatio() + val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri) + val modifier = if (ratio != null && automaticallyStartPlayback.value) { Modifier.aspectRatio(ratio) @@ -295,7 +297,7 @@ fun VideoView( } } } else { - val ratio = dimensions?.aspectRatio() + val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri) val modifier = if (ratio != null) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 378a2dddc..e9de960dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -43,6 +43,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -77,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaPreloadedContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo +import com.vitorpamplona.amethyst.model.MediaAspectRatioCache import com.vitorpamplona.amethyst.service.Blurhash import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.InformationDialog @@ -87,6 +89,7 @@ import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon import com.vitorpamplona.amethyst.ui.note.HashCheckFailedIcon import com.vitorpamplona.amethyst.ui.note.HashCheckIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageView import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size24dp @@ -235,7 +238,7 @@ fun LocalImageView( ) } - val ratio = remember(content) { aspectRatio(content.dim) } + val ratio = remember(content) { content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.localFile.toString()) } CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) { if (it) { SubcomposeAsyncImage( @@ -276,6 +279,11 @@ fun LocalImageView( is AsyncImagePainter.State.Success -> { SubcomposeAsyncImageContent(loadedImageModifier) + SideEffect { + val drawable = (state as AsyncImagePainter.State.Success).result.image + MediaAspectRatioCache.add(content.localFile.toString(), drawable.width, drawable.height) + } + content.isVerified?.let { AnimatedVisibility( visible = controllerVisible.value, @@ -328,7 +336,7 @@ fun UrlImageView( accountViewModel: AccountViewModel, alwayShowImage: Boolean = false, ) { - val ratio = content.dim?.aspectRatio() + val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url) val showImage = remember { @@ -385,6 +393,11 @@ fun UrlImageView( SubcomposeAsyncImageContent(loadedImageModifier) ShowHashAnimated(content, controllerVisible, Modifier.align(Alignment.TopEnd)) + + SideEffect { + val drawable = (state as AsyncImagePainter.State.Success).result.image + MediaAspectRatioCache.add(content.url, drawable.width, drawable.height) + } } else -> {} } @@ -519,12 +532,6 @@ fun ShowHash(content: MediaUrlContent) { verifiedHash?.let { HashVerificationSymbol(it) } } -fun aspectRatio(dim: DimensionTag?): Float? { - if (dim == null) return null - - return dim.width.toFloat() / dim.height.toFloat() -} - @Composable fun WaitAndDisplay( content: From de2b1a6c95adf34052cef40aefdaf46910a1b5ee Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 14 Mar 2025 10:24:03 -0400 Subject: [PATCH 13/18] Optimizes caching for hidden chars on emoji --- .../amethyst/service/CachedRichTextParser.kt | 6 +++ .../amethyst/ui/components/RichTextViewer.kt | 18 ++++--- .../amethyst/ui/note/MultiSetCompose.kt | 21 ++++---- .../ui/note/UpdateReactionTypeDialog.kt | 51 ------------------- 4 files changed, 27 insertions(+), 69 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index 55a13f3b6..cc5b30f6b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -41,6 +41,12 @@ object CachedRichTextParser { return result } + fun cachedText( + content: String, + tags: ImmutableListOfLists, + callbackUri: String? = null, + ): RichTextViewerState? = richTextCache[hashCodeCache(content, tags, callbackUri)] + fun parseText( content: String, tags: ImmutableListOfLists, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 1a5d99cb7..0320f5086 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -530,20 +530,22 @@ fun DisplaySecretEmoji( ) { if (canPreview && quotesLeft > 0) { var secretContent by remember { - mutableStateOf(null) + mutableStateOf(CachedRichTextParser.cachedText(EmojiCoder.decode(segment.segmentText), state.tags)) } var showPopup by remember { mutableStateOf(false) } - LaunchedEffect(segment) { - launch(Dispatchers.Default) { - secretContent = - CachedRichTextParser.parseText( - EmojiCoder.decode(segment.segmentText), - state.tags, - ) + if (secretContent == null) { + LaunchedEffect(segment) { + launch(Dispatchers.Default) { + secretContent = + CachedRichTextParser.parseText( + EmojiCoder.decode(segment.segmentText), + state.tags, + ) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 1f0a9252e..685f72bff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -64,7 +64,6 @@ import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder -import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.NoteState @@ -251,21 +250,23 @@ fun DisplaySecretEmojiAsReaction( accountViewModel: AccountViewModel, nav: INav, ) { - var secretContent by remember { - mutableStateOf(null) + var secretContent by remember(reaction) { + mutableStateOf(CachedRichTextParser.cachedText(EmojiCoder.decode(reaction), EmptyTagList)) } var showPopup by remember { mutableStateOf(false) } - LaunchedEffect(reaction) { - launch(Dispatchers.Default) { - secretContent = - CachedRichTextParser.parseText( - EmojiCoder.decode(reaction), - EmptyTagList, - ) + if (secretContent == null) { + LaunchedEffect(reaction) { + launch(Dispatchers.Default) { + secretContent = + CachedRichTextParser.parseText( + EmojiCoder.decode(reaction), + EmptyTagList, + ) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index d1e2162b0..93cbd2be3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -53,7 +53,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -79,13 +78,10 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder -import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote -import com.vitorpamplona.amethyst.service.CachedRichTextParser import com.vitorpamplona.amethyst.service.firstFullChar import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius -import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.navigation.INav @@ -380,53 +376,6 @@ private fun RenderReactionOption( } } -@Composable -fun DisplaySecretEmoji( - text: String, - state: RichTextViewerState, - callbackUri: String?, - canPreview: Boolean, - quotesLeft: Int, - backgroundColor: MutableState, - accountViewModel: AccountViewModel, - nav: INav, -) { - if (canPreview && quotesLeft > 0) { - var secretContent by remember { - mutableStateOf(null) - } - - var showPopup by remember { - mutableStateOf(false) - } - - LaunchedEffect(text) { - launch(Dispatchers.Default) { - secretContent = - CachedRichTextParser.parseText( - EmojiCoder.decode(text), - state.tags, - ) - } - } - - val localSecretContent = secretContent - - AnimatedBorderTextCornerRadius( - text, - Modifier.clickable { - showPopup = !showPopup - }, - ) - - if (localSecretContent != null && showPopup) { - CoreSecretMessage(localSecretContent, callbackUri, quotesLeft, backgroundColor, accountViewModel, nav) - } - } else { - Text(text) - } -} - @Composable private fun EmojiSelector( accountViewModel: AccountViewModel, From d76de0123a1aefeb58428c41a185f53258665560 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 14 Mar 2025 10:27:13 -0400 Subject: [PATCH 14/18] Forces aspect ratio of when loading an image to minimize resizing of the UI after the image is loaded. --- .../ui/components/LoadingAnimation.kt | 3 ++- .../ui/components/ZoomableContentView.kt | 23 +++++++++++++++---- .../ui/note/ShowEmojiSuggestionList.kt | 5 ++-- .../chats/rooms/feed/ChatroomListTabs.kt | 4 ++-- .../loggedIn/profile/gallery/GalleryThumb.kt | 7 +++--- .../ui/screen/loggedOff/LoginScreen.kt | 19 ++++++++------- .../vitorpamplona/amethyst/ui/theme/Shape.kt | 1 + 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt index c1245cbff..1e4f2176c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt @@ -59,6 +59,7 @@ val DefaultAnimationColors = @Composable fun LoadingAnimation( indicatorSize: Dp = 20.dp, + circleWidth: Dp = 4.dp, circleColors: ImmutableList = DefaultAnimationColors, animationDuration: Int = 1000, ) { @@ -86,7 +87,7 @@ fun LoadingAnimation( .size(size = indicatorSize) .rotate(degrees = rotateAnimation) .border( - width = 4.dp, + width = circleWidth, brush = Brush.sweepGradient(circleColors), shape = CircleShape, ), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index e9de960dd..0db17f94b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -94,6 +94,8 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size24dp import com.vitorpamplona.amethyst.ui.theme.Size30dp +import com.vitorpamplona.amethyst.ui.theme.Size40dp +import com.vitorpamplona.amethyst.ui.theme.Size6dp import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.hashVerifierMark import com.vitorpamplona.amethyst.ui.theme.imageModifier @@ -135,7 +137,6 @@ fun ZoomableContentView( .fillMaxWidth() .clickable { dialogOpen = true } val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth() - UrlImageView(content, contentScale, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel) } } @@ -268,8 +269,14 @@ fun LocalImageView( ) } } else { - WaitAndDisplay { - DisplayUrlWithLoadingSymbol(content) + if (ratio != null) { + Box(loadedImageModifier.aspectRatio(ratio), contentAlignment = Alignment.Center) { + LoadingAnimation(Size40dp, Size6dp) + } + } else { + WaitAndDisplay { + DisplayUrlWithLoadingSymbol(content) + } } } } @@ -381,8 +388,14 @@ fun UrlImageView( ) } } else { - WaitAndDisplay { - DisplayUrlWithLoadingSymbol(content) + if (ratio != null) { + Box(loadedImageModifier.aspectRatio(ratio), contentAlignment = Alignment.Center) { + LoadingAnimation(Size40dp, Size6dp) + } + } else { + WaitAndDisplay { + DisplayUrlWithLoadingSymbol(content) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt index fa5a6bfb5..c2ae42a05 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt @@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageVie import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size40Modifier import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent import kotlinx.collections.immutable.toImmutableList @@ -108,11 +109,11 @@ fun ShowEmojiSuggestionList( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = spacedBy(Size10dp), ) { - Box(Modifier.size(40.dp)) { + Box(Size40Modifier) { UrlImageView(it.url, accountViewModel) } Text(it.code, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) - Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { + Box(Size40Modifier, contentAlignment = Alignment.Center) { IconButton( onClick = { onFullSize(it) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt index 0ce4facea..d11c2d114 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt @@ -47,12 +47,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size40dp import com.vitorpamplona.amethyst.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.launch @@ -93,7 +93,7 @@ fun MessagesTabHeader( IconButton( modifier = Modifier - .size(40.dp) + .size(Size40dp) .align(Alignment.CenterEnd), onClick = { moreActionsExpanded = true }, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt index 71d3b9651..8aee3d211 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt @@ -56,12 +56,11 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.DisplayBlurHash -import com.vitorpamplona.amethyst.ui.components.DisplayUrlWithLoadingSymbol import com.vitorpamplona.amethyst.ui.components.GetMediaItem import com.vitorpamplona.amethyst.ui.components.GetVideoController import com.vitorpamplona.amethyst.ui.components.ImageUrlWithDownloadButton +import com.vitorpamplona.amethyst.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.ui.components.SensitivityWarning -import com.vitorpamplona.amethyst.ui.components.WaitAndDisplay import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon import com.vitorpamplona.amethyst.ui.note.WatchAuthor @@ -217,8 +216,8 @@ fun UrlImageView( defaultModifier, ) } else { - WaitAndDisplay { - DisplayUrlWithLoadingSymbol(content) + Box(defaultModifier, contentAlignment = Alignment.Center) { + LoadingAnimation() } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index b200a8b8c..3b00f12c8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -99,9 +99,12 @@ import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.Size0dp +import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.Size40dp +import com.vitorpamplona.amethyst.ui.theme.Size50dp import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.tor.TorSettings @@ -200,7 +203,7 @@ fun LoginPage( contentScale = ContentScale.Inside, ) - Spacer(modifier = Modifier.height(40.dp)) + Spacer(modifier = Modifier.height(Size40dp)) KeyTextField( value = key.value, @@ -334,9 +337,9 @@ fun LoginPage( } } - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(Size10dp)) - Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) { + Box(modifier = Modifier.padding(Size40dp, 0.dp, Size40dp, 0.dp)) { LoginButton( enabled = acceptedTerms.value, processingLogin = processingLogin, @@ -371,7 +374,7 @@ fun LoginPage( } if (PackageUtils.isExternalSignerInstalled(context)) { - Box(modifier = Modifier.padding(40.dp, 20.dp, 40.dp, 0.dp)) { + Box(modifier = Modifier.padding(Size40dp, Size20dp, Size40dp, Size0dp)) { LoginWithAmberButton( enabled = acceptedTerms.value, onClick = { @@ -777,11 +780,11 @@ fun LoginWithAmberButton( enabled = enabled, onClick = onClick, shape = RoundedCornerShape(Size35dp), - modifier = Modifier.height(50.dp), + modifier = Modifier.height(Size50dp), ) { Text( text = stringRes(R.string.login_with_external_signer), - modifier = Modifier.padding(horizontal = 40.dp), + modifier = Modifier.padding(horizontal = Size40dp), ) } } @@ -796,9 +799,9 @@ fun LoginButton( enabled = enabled, onClick = onClick, shape = RoundedCornerShape(Size35dp), - modifier = Modifier.height(50.dp), + modifier = Modifier.height(Size50dp), ) { - Row(modifier = Modifier.padding(horizontal = 40.dp)) { + Row(modifier = Modifier.padding(horizontal = Size40dp)) { if (processingLogin) { LoadingAnimation() Spacer(modifier = DoubleHorzSpacer) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 5527262b1..f1d701362 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -84,6 +84,7 @@ val TopBarSize = 50.dp - 64.dp val Size0dp = 0.dp val Size5dp = 5.dp +val Size6dp = 6.dp val Size10dp = 10.dp val Size12dp = 12.dp val Size13dp = 13.dp From fd538ebde169fbd40fa62d317c19fb1bfc712fe8 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 14 Mar 2025 13:57:33 -0400 Subject: [PATCH 15/18] Improves multiple error dialogs appearing when zapping from the reactions bar in the feed. --- .idea/inspectionProfiles/Project_Default.xml | 4 + .../amethyst/ui/actions/EditPostView.kt | 4 +- .../amethyst/ui/actions/NewMediaView.kt | 2 +- .../ui/actions/NewUserMetadataScreen.kt | 4 +- .../ui/actions/RelaySelectionDialog.kt | 2 +- .../relays/BasicRelaySetupInfoDialog.kt | 2 +- .../ui/actions/relays/Kind3RelayListView.kt | 2 +- .../Kind3RelaySetupInfoProposalDialog.kt | 2 +- .../ui/actions/relays/RelayStatusRow.kt | 8 +- .../amethyst/ui/components/CashuRedeem.kt | 2 +- .../amethyst/ui/components/VideoView.kt | 4 +- .../ui/components/ZoomableContentDialog.kt | 8 +- .../ui/components/ZoomableContentView.kt | 2 +- .../{ => toasts}/DisplayErrorMessages.kt | 26 ++-- .../ui/components/toasts/ResourceToastMsg.kt | 30 +++++ .../ui/components/toasts/StringToastMsg.kt | 29 +++++ .../ui/components/toasts/ThrowableToastMsg.kt | 30 +++++ .../ui/components/toasts/ToastManager.kt | 89 +++++++++++++ .../amethyst/ui/components/toasts/ToastMsg.kt | 26 ++++ .../toasts/multiline/ErrorList.kt} | 117 +++--------------- .../toasts/multiline/MultiErrorToastMsg.kt | 52 ++++++++ .../multiline/MultiUserErrorMessageDialog.kt | 107 ++++++++++++++++ .../amethyst/ui/navigation/AppNavigation.kt | 8 +- .../amethyst/ui/navigation/DrawerContent.kt | 2 +- .../amethyst/ui/note/PollNote.kt | 10 +- .../amethyst/ui/note/ReactionsRow.kt | 36 +++--- .../amethyst/ui/note/RelayListRow.kt | 2 +- .../amethyst/ui/note/UpdateZapAmountDialog.kt | 10 +- .../amethyst/ui/note/ZapCustomDialog.kt | 1 + .../amethyst/ui/note/ZapNoteCompose.kt | 4 +- .../amethyst/ui/note/elements/DisplayOts.kt | 2 +- .../ui/note/elements/ZapTheDevsCard.kt | 25 ++-- .../amethyst/ui/note/types/Torrent.kt | 2 +- .../amethyst/ui/screen/AccountScreen.kt | 6 +- .../ui/screen/loggedIn/AccountBackupDialog.kt | 6 +- .../ui/screen/loggedIn/AccountViewModel.kt | 63 +--------- .../ui/screen/loggedIn/NewPostScreen.kt | 4 +- .../send/upload/ChatFileUploadDialog.kt | 2 +- .../send/ChannelFileUploadDialog.kt | 2 +- .../dvms/DvmContentDiscoveryScreen.kt | 18 +-- .../screen/loggedIn/geohash/GeoHashScreen.kt | 4 +- .../screen/loggedIn/hashtag/HashtagScreen.kt | 4 +- .../header/DisplayFollowUnfollowButton.kt | 6 +- .../profile/header/DisplayLNAddress.kt | 2 +- .../settings/SecurityFiltersScreen.kt | 6 +- .../quartz/nip01Core/crypto/KeyPair.kt | 7 +- 46 files changed, 502 insertions(+), 282 deletions(-) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/{ => toasts}/DisplayErrorMessages.kt (75%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ResourceToastMsg.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/StringToastMsg.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ThrowableToastMsg.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastManager.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastMsg.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{note/MultiUserMessageDialog.kt => components/toasts/multiline/ErrorList.kt} (65%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiErrorToastMsg.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiUserErrorMessageDialog.kt diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index e0da3ee56..bb270b80a 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -65,6 +65,10 @@