Merge branch 'vitorpamplona:main' into profiles-list-management

This commit is contained in:
KotlinGeekDev
2025-03-14 23:18:09 +00:00
committed by GitHub
214 changed files with 2620 additions and 948 deletions
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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<Int>,
@@ -30,12 +32,12 @@ class WaveformTag(
companion object {
const val TAG_NAME = "waveform"
const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array<String>): 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<List<Int>>(tag[1]) }.getOrNull()
if (wave.isNullOrEmpty()) return null
return WaveformTag(wave)
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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)
}
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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<String>): 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()
}
@@ -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) }
}
}
}
}
@@ -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<EncryptionKeyCacheKey, ByteArray>(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,
)
@@ -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))
}
}
@@ -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<Array<String>>,
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<EncryptionKeyListEvent>.() -> Unit = {},
) = eventUpdate(currentEvent, createdAt) {
alt(ALT)
key(key)
initializer()
}
fun remove(
key: KeyTag,
currentEvent: EncryptionKeyListEvent,
createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<EncryptionKeyListEvent>.() -> Unit = {},
) = eventUpdate(currentEvent, createdAt) {
alt(ALT)
removeIf(KeyTag::isSameKey, key.toTagArray())
initializer()
}
fun build(
keys: List<KeyTag>,
createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<EncryptionKeyListEvent>.() -> Unit = {},
) = eventTemplate(KIND, "", createdAt) {
alt(ALT)
keys(keys)
initializer()
}
}
}
@@ -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<EncryptionKeyListEvent>.key(
key: HexKey,
nonce: HexKey,
) = add(KeyTag.assemble(key, nonce))
fun TagArrayBuilder<EncryptionKeyListEvent>.key(key: KeyTag) = add(key.toTagArray())
fun TagArrayBuilder<EncryptionKeyListEvent>.keys(keys: List<KeyTag>) = addAll(keys.map { it.toTagArray() })
@@ -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<String>,
tag2: Array<String>,
): 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<String>): 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)
}
}
@@ -47,6 +47,8 @@ class PrivateOutboxRelayListEvent(
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient private var privateTagsCache: Array<Array<String>>? = null
override fun isContentEncoded() = true
override fun countMemory(): Long =
super.countMemory() +
pointerSizeInBytes + (privateTagsCache?.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } ?: 0)
@@ -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<String>): 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(
@@ -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>): 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]
}
@@ -60,7 +60,6 @@ data class RootSceneTag(
companion object {
const val TAG_NAME = "A"
const val TAG_SIZE = 2
fun assembleATagId(
kind: Int,
@@ -35,7 +35,6 @@ class StoryOptionTag(
companion object {
const val TAG_NAME = "option"
const val TAG_SIZE = 3
@JvmStatic
fun parse(tag: Array<String>): StoryOptionTag? {
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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<String>): 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()
}
@@ -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>): 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]
}
@@ -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<String>): 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()
}
@@ -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<String>): 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)
}
@@ -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<String>): 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()
}
@@ -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<String>): 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()
}
@@ -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<String>): 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
@@ -27,64 +27,12 @@ typealias Tag = Array<String>
*/
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<String>): 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<String>,
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
@@ -35,7 +35,7 @@ class TagArrayBuilder<T : IEvent> {
tagName: String,
tagValue: String,
): TagArrayBuilder<T> {
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<T : IEvent> {
predicate: (Tag, Tag) -> Boolean,
toCompare: Tag,
): TagArrayBuilder<T> {
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<T> {
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)
}
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.toHexKey
class KeyPair(
privKey: ByteArray? = null,
pubKey: ByteArray? = null,
forceReplacePubkey: Boolean = true,
) {
val privKey: ByteArray?
val pubKey: ByteArray
@@ -44,7 +45,11 @@ class KeyPair(
} else {
// as private key is provided, ignore the public key and set keys according to private key
this.privKey = privKey
this.pubKey = Nip01.pubKeyCreate(privKey)
if (pubKey == null || forceReplacePubkey) {
this.pubKey = Nip01.pubKeyCreate(privKey)
} else {
this.pubKey = pubKey
}
}
}
@@ -73,6 +73,11 @@ abstract class NostrSigner(
onReady: (LnZapPrivateEvent) -> Unit,
)
abstract fun deriveKey(
nonce: HexKey,
onReady: (HexKey) -> Unit,
)
fun decrypt(
encryptedContent: String,
fromPublicKey: HexKey,
@@ -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) }
}
}
@@ -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()
}
}
@@ -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<String>) = tag.size >= 2 && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun isSameAddress(
tag1: Array<String>,
tag2: Array<String>,
) = 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<String>,
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<String>,
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<String>,
addressIds: Set<String>,
) = 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<String>,
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(
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].length == 64
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].length == 64
@JvmStatic
fun isTagged(
tag: Array<String>,
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<String>): 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>): 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<String>): 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])
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array<String>): 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]
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
fun isTagged(
tag: Array<String>,
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<String>,
kinds: Set<String>,
) = 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<String>): 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()
}
@@ -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<String>,
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<String>): 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<String>): 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])
}
@@ -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<String>,
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<String>,
references: Set<String>,
): 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<String>): 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>): 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]
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array<String>): 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<String>): 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>): 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>): 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<String>): 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])
}
@@ -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<String>): 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])
}
@@ -48,9 +48,9 @@ fun List<String>.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)
}
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun hasTagWithContent(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array<String>): 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<String>): 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()
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun hasTagWithContent(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array<String>): 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]
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array<String>): 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]
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
fun isTag(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array<String>): 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()
}
@@ -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<String>) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
fun isTag(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array<String>): 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()
}
@@ -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<String>,
mimeTypes: Set<String>,
) = 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>): 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]
}
@@ -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<String>): 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)
}
}
@@ -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<String>): 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)
}
}
@@ -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<String>
companion object {
const val TAG_NAME = "q"
const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array<String>): 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<String>): 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<String>): 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])
}
}
@@ -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()
@@ -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<String>): 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<String>) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
fun parseAddressId(tag: Array<String>): 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<String>): 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])
}
@@ -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<String>): 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])
}
@@ -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<String>,
eventId: String,
) = tag.match(TAG_NAME, eventId, TAG_SIZE)
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId
@JvmStatic
fun isIn(
tag: Array<String>,
eventIds: Set<String>,
) = tag.match(TAG_NAME, eventIds, TAG_SIZE)
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds
@JvmStatic
fun parse(tag: Array<String>): 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<String>) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
fun parseKey(tag: Array<String>): 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>): 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<String>): 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])
}
@@ -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(
@@ -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<String>,
) = 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)
@@ -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<String>,
addressId: String,
) = tag.match(TAG_NAME, addressId, TAG_SIZE)
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId
@JvmStatic
fun isIn(
tag: Array<String>,
addressIds: Set<String>,
) = tag.match(TAG_NAME, addressIds, TAG_SIZE)
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
@JvmStatic
fun parse(tag: Array<String>): 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<String>) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
fun parseAddressId(tag: Array<String>): 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<String>): 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])
}
@@ -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<String>): 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,
@@ -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<String>,
eventId: String,
) = tag.match(TAG_NAME, eventId, TAG_SIZE)
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId
@JvmStatic
fun isIn(
tag: Array<String>,
eventIds: Set<String>,
) = tag.match(TAG_NAME, eventIds, TAG_SIZE)
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds
@JvmStatic
fun parse(tag: Array<String>): 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<String>) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
fun parseKey(tag: Array<String>): 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>): 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<String>): 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])
}
@@ -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(
@@ -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)
@@ -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>): 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]
}
@@ -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<String>): 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()
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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>): 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)
}
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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<String>): 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<String>): 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
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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<String>): 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()
}
@@ -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<String>): 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()
}
@@ -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<String>,
keys: Set<HexKey>,
) = 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]
}
@@ -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<String>,
) {
companion object {
const val TAG_NAME = "relays"
const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array<String>): 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
@@ -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<String>): 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()
}
@@ -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>): 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]
}
@@ -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>): 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]
}
@@ -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<String>): 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()
}
@@ -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)
}
},
)
}
}
@@ -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,
@@ -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,36 @@ 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 {
// 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()
if (reportType == null) {
reportType =
tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.uppercase()) }.firstOrNull()
}
if (reportType == null) {
reportType = ReportType.SPAM
}
defaultType?.let { return it }
// 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
}
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 +97,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<Array<String>> = 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,
}
}
@@ -0,0 +1,63 @@
/**
* 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"),
HARASSMENT("Harrassment"),
OTHER("other"),
;
companion object {
fun parseOrNull(
code: String,
tag: Array<String>,
): 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
HARASSMENT.code -> HARASSMENT
OTHER.code -> OTHER
else -> {
Log.w("ReportedEventTag", "Report type not supported: $code ${tag.joinToString(", ")}")
null
}
}
}
}
@@ -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<ReportEvent>.event(
eventId: HexKey,
reportType: ReportType,
) = addUnique(ReportedEventTag.assemble(eventId, reportType))
fun TagArrayBuilder<ReportEvent>.address(
address: Address,
reportType: ReportType,
) = addUnique(ReportedAddressTag.assemble(address, reportType))
fun TagArrayBuilder<ReportEvent>.user(
pubkey: HexKey,
reportType: ReportType,
) = addUnique(ReportedAuthorTag.assemble(pubkey, reportType))
fun TagArrayBuilder<ReportEvent>.hash(
x: String,
reportType: ReportType,
) = addUnique(HashSha256Tag.assemble(x, reportType))
fun TagArrayBuilder<ReportEvent>.server(url: String) = addUnique(ServerTag.assemble(url))
@@ -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.nip56Reports.tags
import com.vitorpamplona.quartz.nip56Reports.ReportType
interface BaseReportTag {
val type: ReportType?
}
@@ -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.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
@Deprecated("Old version had [`report`, `<reason>`] tags")
class DefaultReportTag {
companion object {
const val TAG_NAME = "report"
@JvmStatic
fun parse(tag: Array<String>): 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)
}
}
@@ -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<String>,
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)
}
}
@@ -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<String>,
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)
}
}
@@ -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<String>,
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)
}
}
@@ -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<String>,
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)
}
}
@@ -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>): 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<String>) = urls.map { assemble(it) }
}
}
@@ -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<String>): ZapRaiserTag? {

Some files were not shown because too many files have changed in this diff Show More