Massive refactoring of quartz to prepare for nip-based packages.
This commit is contained in:
+4
-3
@@ -18,11 +18,12 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.blossom
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+5
-4
@@ -18,12 +18,13 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.blossom
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -20,13 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.nip01.Nip01
|
||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||
import com.vitorpamplona.quartz.crypto.nip06.Nip06
|
||||
import com.vitorpamplona.quartz.crypto.nip44.Nip44
|
||||
import com.vitorpamplona.quartz.crypto.nip44.Nip44v2
|
||||
import com.vitorpamplona.quartz.crypto.nip49.Nip49
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.Nip01
|
||||
import com.vitorpamplona.quartz.nip04Dm.Nip04
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
|
||||
import com.vitorpamplona.quartz.nip44Encryption.Nip44
|
||||
import com.vitorpamplona.quartz.nip44Encryption.Nip44v2
|
||||
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
import java.security.SecureRandom
|
||||
|
||||
@@ -34,11 +34,11 @@ object CryptoUtils {
|
||||
private val secp256k1 = Secp256k1.get()
|
||||
private val random = SecureRandom()
|
||||
|
||||
public val nip01 = Nip01(secp256k1, random)
|
||||
public val nip06 = Nip06(secp256k1)
|
||||
public val nip04 = Nip04(secp256k1, random)
|
||||
public val nip44 = Nip44(secp256k1, random, nip04)
|
||||
public val nip49 = Nip49(secp256k1, random)
|
||||
val nip01 = Nip01(secp256k1, random)
|
||||
val nip06 = Nip06(secp256k1)
|
||||
val nip04 = Nip04(secp256k1, random)
|
||||
val nip44 = Nip44(secp256k1, random, nip04)
|
||||
val nip49 = Nip49(secp256k1, random)
|
||||
|
||||
fun clearCache() {
|
||||
nip04.clearCache()
|
||||
|
||||
+15
-23
@@ -18,31 +18,25 @@
|
||||
* 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.encoders
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
/** Makes the distinction between String and Hex * */
|
||||
typealias HexKey = String
|
||||
object Hex {
|
||||
private val lowerCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
private val upperCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
|
||||
fun ByteArray.toHexKey(): HexKey = Hex.encode(this)
|
||||
private val hexToByte: IntArray =
|
||||
IntArray(256) { -1 }.apply {
|
||||
lowerCaseHex.forEachIndexed { index, char -> this[char.code] = index }
|
||||
upperCaseHex.forEachIndexed { index, char -> this[char.code] = index }
|
||||
}
|
||||
|
||||
fun HexKey.hexToByteArray(): ByteArray = Hex.decode(this)
|
||||
// Encodes both chars in a single Int variable
|
||||
private val byteToHex =
|
||||
IntArray(256) {
|
||||
(lowerCaseHex[(it shr 4)].code shl 8) or lowerCaseHex[(it and 0xF)].code
|
||||
}
|
||||
|
||||
val lowerCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
val upperCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
|
||||
|
||||
val hexToByte: IntArray =
|
||||
IntArray(256) { -1 }.apply {
|
||||
lowerCaseHex.forEachIndexed { index, char -> this[char.code] = index }
|
||||
upperCaseHex.forEachIndexed { index, char -> this[char.code] = index }
|
||||
}
|
||||
|
||||
// Encodes both chars in a single Int variable
|
||||
val byteToHex =
|
||||
IntArray(256) {
|
||||
(lowerCaseHex[(it shr 4)].code shl 8) or lowerCaseHex[(it and 0xF)].code
|
||||
}
|
||||
|
||||
object HexValidator {
|
||||
@JvmStatic
|
||||
fun isHex(hex: String?): Boolean {
|
||||
if (hex == null) return false
|
||||
if (hex.isEmpty()) return false
|
||||
@@ -54,9 +48,7 @@ object HexValidator {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
object Hex {
|
||||
@JvmStatic
|
||||
fun decode(hex: String): ByteArray {
|
||||
// faster version of hex decoder
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.crypto
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
|
||||
class KeyPair(
|
||||
privKey: ByteArray? = null,
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* 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.encoders
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
import com.vitorpamplona.quartz.utils.removeTrailingNullsAndEmptyOthers
|
||||
|
||||
@Immutable
|
||||
data class ATag(
|
||||
val kind: Int,
|
||||
val pubKeyHex: String,
|
||||
val dTag: String,
|
||||
) {
|
||||
var relay: String? = null
|
||||
|
||||
constructor(
|
||||
kind: Int,
|
||||
pubKeyHex: String,
|
||||
dTag: String,
|
||||
relayHint: String?,
|
||||
) : this(kind, pubKeyHex, dTag) {
|
||||
this.relay = relayHint
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
8L + // kind
|
||||
pubKeyHex.bytesUsedInMemory() +
|
||||
dTag.bytesUsedInMemory() +
|
||||
(relay?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = assembleATag(kind, pubKeyHex, dTag)
|
||||
|
||||
fun toATagArray() = removeTrailingNullsAndEmptyOthers("a", toTag(), relay)
|
||||
|
||||
fun toQTagArray() = removeTrailingNullsAndEmptyOthers("q", toTag(), relay)
|
||||
|
||||
fun toNAddr(overrideRelay: String? = relay): String =
|
||||
TlvBuilder()
|
||||
.apply {
|
||||
addString(Nip19Bech32.TlvTypes.SPECIAL, dTag)
|
||||
addStringIfNotNull(Nip19Bech32.TlvTypes.RELAY, overrideRelay ?: relay)
|
||||
addHex(Nip19Bech32.TlvTypes.AUTHOR, pubKeyHex)
|
||||
addInt(Nip19Bech32.TlvTypes.KIND, kind)
|
||||
}.build()
|
||||
.toNAddress()
|
||||
|
||||
companion object {
|
||||
fun assembleATag(
|
||||
kind: Int,
|
||||
pubKeyHex: String,
|
||||
dTag: String,
|
||||
) = "$kind:$pubKeyHex:$dTag"
|
||||
|
||||
fun isATag(key: String): Boolean = key.startsWith("naddr1") || key.contains(":")
|
||||
|
||||
fun parse(
|
||||
address: String,
|
||||
relay: String?,
|
||||
): ATag? =
|
||||
if (address.startsWith("naddr") || address.startsWith("nostr:naddr")) {
|
||||
parseNAddr(address)
|
||||
} else {
|
||||
parseAtag(address, relay)
|
||||
}
|
||||
|
||||
fun parseAtag(
|
||||
atag: String,
|
||||
relay: String?,
|
||||
): ATag? =
|
||||
try {
|
||||
val parts = atag.split(":", limit = 3)
|
||||
Hex.decode(parts[1])
|
||||
ATag(parts[0].toInt(), parts[1], parts[2], relay)
|
||||
} catch (t: Throwable) {
|
||||
Log.w("ATag", "Error parsing A Tag: $atag: ${t.message}")
|
||||
null
|
||||
}
|
||||
|
||||
fun parseAtagUnckecked(atag: String): ATag? =
|
||||
try {
|
||||
val parts = atag.split(":")
|
||||
ATag(parts[0].toInt(), parts[1], parts[2], null)
|
||||
} catch (t: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
fun parseNAddr(naddr: String): ATag? {
|
||||
try {
|
||||
val key = naddr.removePrefix("nostr:")
|
||||
|
||||
if (key.startsWith("naddr")) {
|
||||
val tlv = Tlv.parse(key.bechToBytes())
|
||||
|
||||
val d = tlv.firstAsString(Nip19Bech32.TlvTypes.SPECIAL) ?: ""
|
||||
val relay = tlv.firstAsString(Nip19Bech32.TlvTypes.RELAY)
|
||||
val author = tlv.firstAsHex(Nip19Bech32.TlvTypes.AUTHOR)
|
||||
val kind = tlv.firstAsInt(Nip19Bech32.TlvTypes.KIND)
|
||||
|
||||
if (kind != null && author != null) {
|
||||
return ATag(kind, author, d, relay)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.w("ATag", "Issue trying to Decode NIP19 $this: ${e.message}")
|
||||
// e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,418 +0,0 @@
|
||||
/**
|
||||
* 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.encoders
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.regex.Pattern
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
object Nip19Bech32 {
|
||||
enum class Type {
|
||||
USER,
|
||||
NOTE,
|
||||
EVENT,
|
||||
RELAY,
|
||||
ADDRESS,
|
||||
}
|
||||
|
||||
enum class TlvTypes(
|
||||
val id: Byte,
|
||||
) {
|
||||
SPECIAL(0),
|
||||
RELAY(1),
|
||||
AUTHOR(2),
|
||||
KIND(3),
|
||||
}
|
||||
|
||||
val nip19PlusNip46regex =
|
||||
Pattern.compile(
|
||||
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1|nembed1|ncryptsec1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
)
|
||||
|
||||
val nip19regex =
|
||||
Pattern.compile(
|
||||
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1|nembed1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class ParseReturn(
|
||||
val entity: Entity,
|
||||
val nip19raw: String,
|
||||
val additionalChars: String? = null,
|
||||
)
|
||||
|
||||
interface Entity
|
||||
|
||||
@Immutable
|
||||
data class NSec(
|
||||
val hex: String,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NPub(
|
||||
val hex: String,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class Note(
|
||||
val hex: String,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NProfile(
|
||||
val hex: String,
|
||||
val relay: List<String>,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NEvent(
|
||||
val hex: String,
|
||||
val relay: List<String>,
|
||||
val author: String?,
|
||||
val kind: Int?,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NAddress(
|
||||
val atag: String,
|
||||
val relay: List<String>,
|
||||
val author: String,
|
||||
val kind: Int,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NRelay(
|
||||
val relay: List<String>,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NEmbed(
|
||||
val event: Event,
|
||||
) : Entity
|
||||
|
||||
fun tryParseAndClean(uri: String?): String? {
|
||||
if (uri == null) return null
|
||||
|
||||
try {
|
||||
val matcher = nip19PlusNip46regex.matcher(uri)
|
||||
if (!matcher.find()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = matcher.group(2) // npub1
|
||||
val key = matcher.group(3) // bech32
|
||||
|
||||
return type + key
|
||||
} catch (e: Throwable) {
|
||||
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun uriToRoute(uri: String?): ParseReturn? {
|
||||
if (uri == null) return null
|
||||
|
||||
try {
|
||||
val matcher = nip19regex.matcher(uri)
|
||||
if (!matcher.find()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = matcher.group(2) // npub1
|
||||
val key = matcher.group(3) // bech32
|
||||
val additionalChars = matcher.group(4) // additional chars
|
||||
|
||||
if (type == null) return null
|
||||
|
||||
return parseComponents(type, key, additionalChars.ifEmpty { null })
|
||||
} catch (e: Throwable) {
|
||||
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun parseComponents(
|
||||
type: String,
|
||||
key: String?,
|
||||
additionalChars: String?,
|
||||
): ParseReturn? =
|
||||
try {
|
||||
val nip19 = (type + key)
|
||||
val bytes = nip19.bechToBytes()
|
||||
|
||||
when (type.lowercase()) {
|
||||
"nsec1" -> nsec(bytes)
|
||||
"npub1" -> npub(bytes)
|
||||
"note1" -> note(bytes)
|
||||
"nprofile1" -> nprofile(bytes)
|
||||
"nevent1" -> nevent(bytes)
|
||||
"nrelay1" -> nrelay(bytes)
|
||||
"naddr1" -> naddr(bytes)
|
||||
"nembed1" -> nembed(bytes)
|
||||
else -> null
|
||||
}?.let {
|
||||
ParseReturn(it, nip19, additionalChars)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.w("NIP19 Parser", "Issue trying to Decode NIP19 $key: ${e.message}", e)
|
||||
null
|
||||
}
|
||||
|
||||
private fun nembed(bytes: ByteArray): NEmbed? {
|
||||
if (bytes.isEmpty()) return null
|
||||
return NEmbed(Event.fromJson(ungzip(bytes)))
|
||||
}
|
||||
|
||||
private fun nsec(bytes: ByteArray): NSec? {
|
||||
if (bytes.isEmpty()) return null
|
||||
return NSec(bytes.toHexKey())
|
||||
}
|
||||
|
||||
private fun npub(bytes: ByteArray): NPub? {
|
||||
if (bytes.isEmpty()) return null
|
||||
return NPub(bytes.toHexKey())
|
||||
}
|
||||
|
||||
private fun note(bytes: ByteArray): Note? {
|
||||
if (bytes.isEmpty()) return null
|
||||
return Note(bytes.toHexKey())
|
||||
}
|
||||
|
||||
private fun nprofile(bytes: ByteArray): NProfile? {
|
||||
if (bytes.isEmpty()) return null
|
||||
|
||||
val tlv = Tlv.parse(bytes)
|
||||
|
||||
val hex = tlv.firstAsHex(TlvTypes.SPECIAL) ?: return null
|
||||
val relay = tlv.asStringList(TlvTypes.RELAY) ?: emptyList()
|
||||
|
||||
if (hex.isBlank()) return null
|
||||
|
||||
return NProfile(hex, relay)
|
||||
}
|
||||
|
||||
private fun nevent(bytes: ByteArray): NEvent? {
|
||||
if (bytes.isEmpty()) return null
|
||||
|
||||
val tlv = Tlv.parse(bytes)
|
||||
|
||||
val hex = tlv.firstAsHex(TlvTypes.SPECIAL) ?: return null
|
||||
val relay = tlv.asStringList(TlvTypes.RELAY) ?: emptyList()
|
||||
val author = tlv.firstAsHex(TlvTypes.AUTHOR)
|
||||
val kind = tlv.firstAsInt(TlvTypes.KIND.id)
|
||||
|
||||
if (hex.isBlank()) return null
|
||||
|
||||
return NEvent(hex, relay, author, kind)
|
||||
}
|
||||
|
||||
private fun nrelay(bytes: ByteArray): NRelay? {
|
||||
if (bytes.isEmpty()) return null
|
||||
|
||||
val relayUrl = Tlv.parse(bytes).asStringList(TlvTypes.SPECIAL.id) ?: return null
|
||||
|
||||
return NRelay(relayUrl)
|
||||
}
|
||||
|
||||
private fun naddr(bytes: ByteArray): NAddress? {
|
||||
if (bytes.isEmpty()) return null
|
||||
|
||||
val tlv = Tlv.parse(bytes)
|
||||
|
||||
val d = tlv.firstAsString(TlvTypes.SPECIAL.id) ?: ""
|
||||
val relay = tlv.asStringList(TlvTypes.RELAY.id) ?: emptyList()
|
||||
val author = tlv.firstAsHex(TlvTypes.AUTHOR.id) ?: return null
|
||||
val kind = tlv.firstAsInt(TlvTypes.KIND.id) ?: return null
|
||||
|
||||
return NAddress("$kind:$author:$d", relay, author, kind)
|
||||
}
|
||||
|
||||
fun createNEvent(
|
||||
idHex: String,
|
||||
author: String?,
|
||||
kind: Int?,
|
||||
relay: String?,
|
||||
): String =
|
||||
TlvBuilder()
|
||||
.apply {
|
||||
addHex(TlvTypes.SPECIAL, idHex)
|
||||
addStringIfNotNull(TlvTypes.RELAY, relay)
|
||||
addHexIfNotNull(TlvTypes.AUTHOR, author)
|
||||
addIntIfNotNull(TlvTypes.KIND, kind)
|
||||
}.build()
|
||||
.toNEvent()
|
||||
|
||||
@Deprecated("Use nevent instead")
|
||||
fun createNote(eventId: HexKey): String = eventId.hexToByteArray().toNote()
|
||||
|
||||
fun createNPub(authorPubKeyHex: HexKey): String = authorPubKeyHex.hexToByteArray().toNpub()
|
||||
|
||||
fun createNProfile(
|
||||
authorPubKeyHex: String,
|
||||
relay: List<String>,
|
||||
): String =
|
||||
TlvBuilder()
|
||||
.apply {
|
||||
addHex(TlvTypes.SPECIAL, authorPubKeyHex)
|
||||
relay.forEach {
|
||||
addStringIfNotNull(TlvTypes.RELAY, it)
|
||||
}
|
||||
}.build()
|
||||
.toNProfile()
|
||||
|
||||
fun createNEmbed(event: Event): String = gzip(event.toJson()).toNEmbed()
|
||||
|
||||
fun gzip(content: String): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
GZIPOutputStream(bos).bufferedWriter(Charsets.UTF_8).use { it.write(content) }
|
||||
val array = bos.toByteArray()
|
||||
return array
|
||||
}
|
||||
|
||||
fun ungzip(content: ByteArray): String = GZIPInputStream(content.inputStream()).bufferedReader(Charsets.UTF_8).use { it.readText() }
|
||||
}
|
||||
|
||||
fun ByteArray.toNsec() = Bech32.encodeBytes(hrp = "nsec", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNpub() = Bech32.encodeBytes(hrp = "npub", this, Bech32.Encoding.Bech32)
|
||||
|
||||
@Deprecated("Prefer nevent1 instead")
|
||||
fun ByteArray.toNote() = Bech32.encodeBytes(hrp = "note", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNEvent() = Bech32.encodeBytes(hrp = "nevent", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNProfile() = Bech32.encodeBytes(hrp = "nprofile", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNAddress() = Bech32.encodeBytes(hrp = "naddr", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toLnUrl() = Bech32.encodeBytes(hrp = "lnurl", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNEmbed() = Bech32.encodeBytes(hrp = "nembed", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun decodePublicKey(key: String): ByteArray =
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> KeyPair(privKey = key.bechToBytes()).pubKey
|
||||
is Nip19Bech32.NPub -> parsed.hex.hexToByteArray()
|
||||
is Nip19Bech32.NProfile -> parsed.hex.hexToByteArray()
|
||||
else -> Hex.decode(key) // crashes on purpose
|
||||
}
|
||||
|
||||
fun decodePrivateKeyAsHexOrNull(key: String): HexKey? =
|
||||
try {
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> parsed.hex
|
||||
is Nip19Bech32.NPub -> null
|
||||
is Nip19Bech32.NProfile -> null
|
||||
is Nip19Bech32.Note -> null
|
||||
is Nip19Bech32.NEvent -> null
|
||||
is Nip19Bech32.NEmbed -> null
|
||||
is Nip19Bech32.NRelay -> null
|
||||
is Nip19Bech32.NAddress -> null
|
||||
else -> Hex.decode(key).toHexKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
|
||||
fun decodePublicKeyAsHexOrNull(key: String): HexKey? =
|
||||
try {
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> KeyPair(privKey = key.bechToBytes()).pubKey.toHexKey()
|
||||
is Nip19Bech32.NPub -> parsed.hex
|
||||
is Nip19Bech32.NProfile -> parsed.hex
|
||||
is Nip19Bech32.Note -> null
|
||||
is Nip19Bech32.NEvent -> null
|
||||
is Nip19Bech32.NEmbed -> null
|
||||
is Nip19Bech32.NRelay -> null
|
||||
is Nip19Bech32.NAddress -> null
|
||||
else -> Hex.decode(key).toHexKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
|
||||
fun decodeEventIdAsHexOrNull(key: String): HexKey? =
|
||||
try {
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> null
|
||||
is Nip19Bech32.NPub -> null
|
||||
is Nip19Bech32.NProfile -> null
|
||||
is Nip19Bech32.Note -> parsed.hex
|
||||
is Nip19Bech32.NEvent -> parsed.hex
|
||||
is Nip19Bech32.NAddress -> parsed.atag
|
||||
is Nip19Bech32.NEmbed -> null
|
||||
is Nip19Bech32.NRelay -> null
|
||||
else -> Hex.decode(key).toHexKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
|
||||
fun TlvBuilder.addString(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
string: String,
|
||||
) = addString(type.id, string)
|
||||
|
||||
fun TlvBuilder.addHex(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
key: HexKey,
|
||||
) = addHex(type.id, key)
|
||||
|
||||
fun TlvBuilder.addInt(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
data: Int,
|
||||
) = addInt(type.id, data)
|
||||
|
||||
fun TlvBuilder.addStringIfNotNull(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
data: String?,
|
||||
) = addStringIfNotNull(type.id, data)
|
||||
|
||||
fun TlvBuilder.addHexIfNotNull(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
data: HexKey?,
|
||||
) = addHexIfNotNull(type.id, data)
|
||||
|
||||
fun TlvBuilder.addIntIfNotNull(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
data: Int?,
|
||||
) = addIntIfNotNull(type.id, data)
|
||||
|
||||
fun Tlv.firstAsInt(type: Nip19Bech32.TlvTypes) = firstAsInt(type.id)
|
||||
|
||||
fun Tlv.firstAsHex(type: Nip19Bech32.TlvTypes) = firstAsHex(type.id)
|
||||
|
||||
fun Tlv.firstAsString(type: Nip19Bech32.TlvTypes) = firstAsString(type.id)
|
||||
|
||||
fun Tlv.asStringList(type: Nip19Bech32.TlvTypes) = asStringList(type.id)
|
||||
@@ -1,559 +0,0 @@
|
||||
/**
|
||||
* 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.events
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.nip01.EventHasher
|
||||
import com.vitorpamplona.quartz.crypto.nip01.EventHasher.Companion.hashId
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||
import com.vitorpamplona.quartz.encoders.PoWRank
|
||||
import com.vitorpamplona.quartz.events.nip46.BunkerMessage
|
||||
import com.vitorpamplona.quartz.events.nip46.BunkerRequest
|
||||
import com.vitorpamplona.quartz.events.nip46.BunkerResponse
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
import com.vitorpamplona.quartz.utils.remove
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
open class Event(
|
||||
val id: HexKey,
|
||||
@JsonProperty("pubkey") val pubKey: HexKey,
|
||||
@JsonProperty("created_at") val createdAt: Long,
|
||||
val kind: Int,
|
||||
val tags: Array<Array<String>>,
|
||||
val content: String,
|
||||
val sig: HexKey,
|
||||
) : EventInterface {
|
||||
override fun isContentEncoded() = false
|
||||
|
||||
override fun countMemory(): Long =
|
||||
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
12L + // createdAt + kind
|
||||
id.bytesUsedInMemory() +
|
||||
pubKey.bytesUsedInMemory() +
|
||||
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
|
||||
content.bytesUsedInMemory() +
|
||||
sig.bytesUsedInMemory()
|
||||
|
||||
override fun id(): HexKey = id
|
||||
|
||||
override fun pubKey(): HexKey = pubKey
|
||||
|
||||
override fun createdAt(): Long = createdAt
|
||||
|
||||
override fun kind(): Int = kind
|
||||
|
||||
override fun tags(): Array<Array<String>> = tags
|
||||
|
||||
override fun content(): String = content
|
||||
|
||||
override fun sig(): HexKey = sig
|
||||
|
||||
override fun toJson(): String = mapper.writeValueAsString(toJsonObject())
|
||||
|
||||
override fun hasAnyTaggedUser() = hasTagWithContent("p")
|
||||
|
||||
override fun hasTagWithContent(tagName: String) = tags.any { it.size > 1 && it[0] == tagName }
|
||||
|
||||
override fun forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = forEachTagged("e", onEach)
|
||||
|
||||
override fun forEachHashTag(onEach: (eventId: HexKey) -> Unit) = forEachTagged("t", onEach)
|
||||
|
||||
private fun forEachTagged(
|
||||
tagName: String,
|
||||
onEach: (eventId: HexKey) -> Unit,
|
||||
) = tags.forEach {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
onEach(it[1])
|
||||
}
|
||||
}
|
||||
|
||||
override fun anyHashTag(onEach: (str: String) -> Boolean) = anyTagged("t", onEach)
|
||||
|
||||
private fun anyTagged(
|
||||
tagName: String,
|
||||
onEach: (str: String) -> Boolean,
|
||||
) = tags.any {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
onEach(it[1])
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R) = mapTagged("e", map)
|
||||
|
||||
override fun <R> mapTaggedAddress(map: (address: String) -> R) = mapTagged("a", map)
|
||||
|
||||
private fun <R> mapTagged(
|
||||
tagName: String,
|
||||
map: (eventId: HexKey) -> R,
|
||||
) = tags.mapNotNull {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
map(it[1])
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||
|
||||
override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||
|
||||
override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
|
||||
|
||||
override fun firstTag(key: String) = tags.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1] }
|
||||
|
||||
override fun firstTagFor(vararg key: String) = tags.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] }
|
||||
|
||||
override fun firstTaggedUser() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.let { it[1] }
|
||||
|
||||
override fun firstTaggedEvent() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.let { it[1] }
|
||||
|
||||
override fun firstTaggedUrl() = tags.firstOrNull { it.size > 1 && it[0] == "r" }?.let { it[1] }
|
||||
|
||||
override fun firstTaggedK() = tags.firstOrNull { it.size > 1 && it[0] == "k" }?.let { it[1].toIntOrNull() }
|
||||
|
||||
override fun firstTaggedAddress() =
|
||||
tags
|
||||
.firstOrNull { it.size > 1 && it[0] == "a" }
|
||||
?.let {
|
||||
val aTagValue = it[1]
|
||||
val relay = it.getOrNull(2)
|
||||
|
||||
ATag.parse(aTagValue, relay)
|
||||
}
|
||||
|
||||
override fun taggedEmojis() = tags.filter { it.size > 2 && it[0] == "emoji" }.mapNotNull { EmojiUrl.parse(it) }
|
||||
|
||||
override fun isSensitive() =
|
||||
tags.any {
|
||||
(it.size > 0 && it[0] == "content-warning") ||
|
||||
(it.size > 1 && it[0] == "t" && (it[1].equals("nsfw", true) || it[1].equals("nude", true)))
|
||||
}
|
||||
|
||||
override fun subject() = tags.firstOrNull { it.size > 1 && it[0] == "subject" }?.get(1)
|
||||
|
||||
override fun zapraiserAmount() = tags.firstOrNull { (it.size > 1 && it[0] == "zapraiser") }?.get(1)?.toLongOrNull()
|
||||
|
||||
override fun hasZapSplitSetup() = tags.any { it.size > 1 && it[0] == "zap" }
|
||||
|
||||
override fun zapSplitSetup(): List<ZapSplitSetup> =
|
||||
tags
|
||||
.filter { it.size > 1 && it[0] == "zap" }
|
||||
.mapNotNull {
|
||||
val isLnAddress = it[0].contains("@") || it[0].startsWith("LNURL", true)
|
||||
val weight = if (isLnAddress) 1.0 else (it.getOrNull(3)?.toDoubleOrNull() ?: 0.0)
|
||||
|
||||
if (weight > 0) {
|
||||
ZapSplitSetup(
|
||||
it[1],
|
||||
it.getOrNull(2),
|
||||
weight,
|
||||
isLnAddress,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun taggedAddresses() =
|
||||
tags
|
||||
.filter { it.size > 1 && it[0] == "a" }
|
||||
.mapNotNull {
|
||||
val aTagValue = it[1]
|
||||
val relay = it.getOrNull(2)
|
||||
|
||||
ATag.parse(aTagValue, relay)
|
||||
}
|
||||
|
||||
override fun hasHashtags() = tags.any { it.size > 1 && it[0] == "t" }
|
||||
|
||||
override fun hasGeohashes() = tags.any { it.size > 1 && it[0] == "g" }
|
||||
|
||||
override fun hashtags() = tags.filter { it.size > 1 && it[0] == "t" }.map { it[1] }
|
||||
|
||||
override fun geohashes() = tags.filter { it.size > 1 && it[0] == "g" }.map { it[1] }
|
||||
|
||||
override fun matchTag1With(text: String) = tags.any { it.size > 1 && it[1].contains(text, true) }
|
||||
|
||||
override fun isTagged(
|
||||
key: String,
|
||||
tag: String,
|
||||
) = tags.any { it.size > 1 && it[0] == key && it[1] == tag }
|
||||
|
||||
override fun isAnyTagged(
|
||||
key: String,
|
||||
tags: Set<String>,
|
||||
) = this.tags.any { it.size > 1 && it[0] == key && it[1] in tags }
|
||||
|
||||
override fun isTaggedWord(word: String) = isTagged("word", word)
|
||||
|
||||
override fun isTaggedUser(idHex: String) = isTagged("p", idHex)
|
||||
|
||||
override fun isTaggedUsers(idHexes: Set<String>) = isAnyTagged("p", idHexes)
|
||||
|
||||
override fun isTaggedEvent(idHex: String) = isTagged("e", idHex)
|
||||
|
||||
override fun isTaggedAddressableNote(idHex: String) = isTagged("a", idHex)
|
||||
|
||||
override fun isTaggedAddressableNotes(idHexes: Set<String>) = isAnyTagged("a", idHexes)
|
||||
|
||||
override fun isTaggedHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
|
||||
|
||||
override fun isTaggedGeoHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "g" && it[1].startsWith(hashtag, true) }
|
||||
|
||||
override fun isTaggedHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }
|
||||
|
||||
override fun isTaggedGeoHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "g" && it[1].lowercase() in hashtags }
|
||||
|
||||
override fun firstIsTaggedHashes(hashtags: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }?.getOrNull(1)
|
||||
|
||||
override fun firstIsTaggedAddressableNote(addressableNotes: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "a" && it[1] in addressableNotes }?.getOrNull(1)
|
||||
|
||||
override fun isTaggedAddressableKind(kind: Int): Boolean {
|
||||
val kindStr = kind.toString()
|
||||
return tags.any { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }
|
||||
}
|
||||
|
||||
override fun expiration() =
|
||||
try {
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "expiration" }?.get(1)?.toLongOrNull()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
override fun isExpired() = (expiration() ?: Long.MAX_VALUE) < TimeUtils.now()
|
||||
|
||||
override fun isExpirationBefore(time: Long) = (expiration() ?: Long.MAX_VALUE) < time
|
||||
|
||||
override fun getTagOfAddressableKind(kind: Int): ATag? {
|
||||
val kindStr = kind.toString()
|
||||
val aTag =
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }?.getOrNull(1)
|
||||
?: return null
|
||||
|
||||
return ATag.parse(aTag, null)
|
||||
}
|
||||
|
||||
override fun getPoWRank(): Int {
|
||||
val commitedPoW = tags.firstOrNull { it.size > 2 && it[0] == "nonce" }?.get(2)?.toIntOrNull()
|
||||
return PoWRank.getCommited(id, commitedPoW)
|
||||
}
|
||||
|
||||
override fun getGeoHash(): String? =
|
||||
tags
|
||||
.filter { it.size > 1 && it[0] == "g" }
|
||||
.maxByOrNull {
|
||||
it[1].length
|
||||
}?.get(1)
|
||||
?.ifBlank { null }
|
||||
|
||||
override fun getReward(): BigDecimal? =
|
||||
try {
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "reward" }?.get(1)?.let { BigDecimal(it) }
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
fun filterTags(startsWith: Array<String>) = tags.remove(startsWith)
|
||||
|
||||
open fun toNIP19(): String =
|
||||
if (this is AddressableEvent) {
|
||||
ATag(kind, pubKey, dTag(), null).toNAddr()
|
||||
} else {
|
||||
Nip19Bech32.createNEvent(id, pubKey, kind, null)
|
||||
}
|
||||
|
||||
fun toNostrUri(): String = "nostr:${toNIP19()}"
|
||||
|
||||
fun hasCorrectIDHash(): Boolean {
|
||||
if (id.isEmpty()) return false
|
||||
return id.equals(generateId())
|
||||
}
|
||||
|
||||
fun hasVerifiedSignature(): Boolean {
|
||||
if (id.isEmpty() || sig.isEmpty()) return false
|
||||
return CryptoUtils.verifySignature(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))
|
||||
}
|
||||
|
||||
/** Checks if the ID is correct and then if the pubKey's secret key signed the event. */
|
||||
override fun checkSignature() {
|
||||
if (!hasCorrectIDHash()) {
|
||||
throw Exception(
|
||||
"""
|
||||
|Unexpected ID.
|
||||
| Event: ${toJson()}
|
||||
| Actual ID: $id
|
||||
| Generated: ${generateId()}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
if (!hasVerifiedSignature()) {
|
||||
throw Exception("""Bad signature!""")
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasValidSignature(): Boolean =
|
||||
try {
|
||||
hasCorrectIDHash() && hasVerifiedSignature()
|
||||
} catch (e: Exception) {
|
||||
Log.w("Event", "Event $id does not have a valid signature: ${toJson()}", e)
|
||||
false
|
||||
}
|
||||
|
||||
fun generateId(): String = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
|
||||
|
||||
private class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Event = fromJson(jp.codec.readTree<JsonNode>(jp))
|
||||
}
|
||||
|
||||
private class GossipDeserializer : StdDeserializer<Gossip>(Gossip::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Gossip {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
return Gossip(
|
||||
id = jsonObject.get("id")?.asText()?.intern(),
|
||||
pubKey = jsonObject.get("pubkey")?.asText()?.intern(),
|
||||
createdAt = jsonObject.get("created_at")?.asLong(),
|
||||
kind = jsonObject.get("kind")?.asInt(),
|
||||
tags =
|
||||
jsonObject.get("tags").toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
},
|
||||
content = jsonObject.get("content")?.asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class EventSerializer : StdSerializer<Event>(Event::class.java) {
|
||||
override fun serialize(
|
||||
event: Event,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
gen.writeStringField("id", event.id)
|
||||
gen.writeStringField("pubkey", event.pubKey)
|
||||
gen.writeNumberField("created_at", event.createdAt)
|
||||
gen.writeNumberField("kind", event.kind)
|
||||
gen.writeArrayFieldStart("tags")
|
||||
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
|
||||
gen.writeEndArray()
|
||||
gen.writeStringField("content", event.content)
|
||||
gen.writeStringField("sig", event.sig)
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
|
||||
private class GossipSerializer : StdSerializer<Gossip>(Gossip::class.java) {
|
||||
override fun serialize(
|
||||
event: Gossip,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
event.id?.let { gen.writeStringField("id", it) }
|
||||
event.pubKey?.let { gen.writeStringField("pubkey", it) }
|
||||
event.createdAt?.let { gen.writeNumberField("created_at", it) }
|
||||
event.kind?.let { gen.writeNumberField("kind", it) }
|
||||
event.tags?.let {
|
||||
gen.writeArrayFieldStart("tags")
|
||||
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
|
||||
gen.writeEndArray()
|
||||
}
|
||||
event.content?.let { gen.writeStringField("content", it) }
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
|
||||
fun toJsonObject(): JsonNode {
|
||||
val factory = mapper.nodeFactory
|
||||
|
||||
return factory.objectNode().apply {
|
||||
put("id", id)
|
||||
put("pubkey", pubKey)
|
||||
put("created_at", createdAt)
|
||||
put("kind", kind)
|
||||
replace(
|
||||
"tags",
|
||||
factory.arrayNode(tags.size).apply {
|
||||
tags.forEach { tag ->
|
||||
add(
|
||||
factory.arrayNode(tag.size).apply { tag.forEach { add(it) } },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
put("content", content)
|
||||
put("sig", sig)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val mapper =
|
||||
jacksonObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||
.registerModule(
|
||||
SimpleModule()
|
||||
.addSerializer(Event::class.java, EventSerializer())
|
||||
.addDeserializer(Event::class.java, EventDeserializer())
|
||||
.addSerializer(Gossip::class.java, GossipSerializer())
|
||||
.addDeserializer(Gossip::class.java, GossipDeserializer())
|
||||
.addDeserializer(Response::class.java, ResponseDeserializer())
|
||||
.addDeserializer(Request::class.java, RequestDeserializer())
|
||||
.addDeserializer(BunkerMessage::class.java, BunkerMessage.BunkerMessageDeserializer())
|
||||
.addSerializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestSerializer())
|
||||
.addDeserializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestDeserializer())
|
||||
.addSerializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseSerializer())
|
||||
.addDeserializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseDeserializer()),
|
||||
)
|
||||
|
||||
fun fromJson(jsonObject: JsonNode): Event =
|
||||
EventFactory.create(
|
||||
id = jsonObject.get("id").asText().intern(),
|
||||
pubKey = jsonObject.get("pubkey").asText().intern(),
|
||||
createdAt = jsonObject.get("created_at").asLong(),
|
||||
kind = jsonObject.get("kind").asInt(),
|
||||
tags =
|
||||
jsonObject.get("tags").toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
},
|
||||
content = jsonObject.get("content").asText(),
|
||||
sig = jsonObject.get("sig").asText(),
|
||||
)
|
||||
|
||||
inline fun <reified R> JsonNode.toTypedArray(transform: (JsonNode) -> R): Array<R> = Array(size()) { transform(get(it)) }
|
||||
|
||||
fun fromJson(json: String): Event = mapper.readValue(json, Event::class.java)
|
||||
|
||||
fun toJson(event: Event): String = mapper.writeValueAsString(event)
|
||||
|
||||
fun generateId(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): HexKey = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
|
||||
|
||||
fun generateIdBytes(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): ByteArray = EventHasher.hashIdBytes(pubKey, createdAt, kind, tags, content)
|
||||
|
||||
fun create(
|
||||
signer: NostrSigner,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>> = emptyArray(),
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (Event) -> Unit,
|
||||
) = signer.sign(createdAt, kind, tags, content, onReady)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
open class WrappedEvent(
|
||||
id: HexKey,
|
||||
@JsonProperty("pubkey") pubKey: HexKey,
|
||||
@JsonProperty("created_at") createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
@Transient var host: HostStub? = null // host event to broadcast when needed
|
||||
}
|
||||
|
||||
class HostStub(
|
||||
val id: HexKey,
|
||||
val pubKey: HexKey,
|
||||
val kind: Int,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
interface AddressableEvent {
|
||||
fun dTag(): String
|
||||
|
||||
fun address(relayHint: String? = null): ATag
|
||||
|
||||
fun addressTag(): String
|
||||
}
|
||||
|
||||
@Immutable
|
||||
open class BaseAddressableEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig),
|
||||
AddressableEvent {
|
||||
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
|
||||
|
||||
override fun address(relayHint: String?) = ATag(kind, pubKey, dTag(), relayHint)
|
||||
|
||||
/**
|
||||
* Creates the tag in a memory effecient way (without creating the ATag class
|
||||
*/
|
||||
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
|
||||
}
|
||||
|
||||
data class ZapSplitSetup(
|
||||
val lnAddressOrPubKeyHex: String,
|
||||
val relay: String?,
|
||||
val weight: Double,
|
||||
val isLnAddress: Boolean,
|
||||
)
|
||||
|
||||
interface RootScope
|
||||
@@ -1,169 +0,0 @@
|
||||
/**
|
||||
* 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.events
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.nip46.NostrConnectEvent
|
||||
|
||||
class EventFactory {
|
||||
companion object {
|
||||
val factories: MutableMap<Int, (HexKey, HexKey, Long, Array<Array<String>>, String, HexKey) -> Event> = mutableMapOf()
|
||||
|
||||
fun create(
|
||||
id: String,
|
||||
pubKey: String,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: String,
|
||||
) = when (kind) {
|
||||
AdvertisedRelayListEvent.KIND -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppDefinitionEvent.KIND -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppRecommendationEvent.KIND -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppSpecificDataEvent.KIND -> AppSpecificDataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AudioHeaderEvent.KIND -> AudioHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AudioTrackEvent.KIND -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BadgeAwardEvent.KIND -> BadgeAwardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BadgeDefinitionEvent.KIND -> BadgeDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BadgeProfilesEvent.KIND -> BadgeProfilesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BlossomServersEvent.KIND -> BlossomServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BlossomAuthorizationEvent.KIND -> BlossomAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BookmarkListEvent.KIND -> BookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarDateSlotEvent.KIND -> CalendarDateSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelHideMessageEvent.KIND -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChatMessageEncryptedFileHeaderEvent.KIND -> {
|
||||
if (id.isBlank()) {
|
||||
ChatMessageEncryptedFileHeaderEvent(
|
||||
Event.generateId(pubKey, createdAt, kind, tags, content),
|
||||
pubKey,
|
||||
createdAt,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
)
|
||||
} else {
|
||||
ChatMessageEncryptedFileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
}
|
||||
ChatMessageEvent.KIND -> {
|
||||
if (id.isBlank()) {
|
||||
ChatMessageEvent(
|
||||
Event.generateId(pubKey, createdAt, kind, tags, content),
|
||||
pubKey,
|
||||
createdAt,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
)
|
||||
} else {
|
||||
ChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
}
|
||||
ChatMessageRelayListEvent.KIND -> ChatMessageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommentEvent.KIND -> CommentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityDefinitionEvent.KIND -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityPostApprovalEvent.KIND -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
DraftEvent.KIND -> DraftEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitReplyEvent.KIND -> GitReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
InteractiveStoryPrologueEvent.KIND -> InteractiveStoryPrologueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
InteractiveStorySceneEvent.KIND -> InteractiveStorySceneEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
InteractiveStoryReadingStateEvent.KIND -> InteractiveStoryReadingStateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LiveActivitiesChatMessageEvent.KIND -> LiveActivitiesChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LiveActivitiesEvent.KIND -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NostrConnectEvent.KIND -> NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PictureEvent.KIND -> PictureEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PollNoteEvent.KIND -> PollNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RecommendRelayEvent.KIND -> RecommendRelayEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelationshipStatusEvent.KIND -> RelationshipStatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SealedGossipEvent.KIND -> SealedGossipEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TextNoteModificationEvent.KIND -> TextNoteModificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TorrentEvent.KIND -> TorrentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TorrentCommentEvent.KIND -> TorrentCommentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoViewEvent.KIND -> VideoViewEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
else -> {
|
||||
factories[kind]?.let {
|
||||
return it(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
|
||||
Event(id, pubKey, createdAt, kind, tags, content, sig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* 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.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
interface EventInterface {
|
||||
fun isContentEncoded(): Boolean
|
||||
|
||||
fun countMemory(): Long
|
||||
|
||||
fun id(): HexKey
|
||||
|
||||
fun pubKey(): HexKey
|
||||
|
||||
fun createdAt(): Long
|
||||
|
||||
fun kind(): Int
|
||||
|
||||
fun tags(): Array<Array<String>>
|
||||
|
||||
fun content(): String
|
||||
|
||||
fun sig(): HexKey
|
||||
|
||||
fun toJson(): String
|
||||
|
||||
fun checkSignature()
|
||||
|
||||
fun hasValidSignature(): Boolean
|
||||
|
||||
fun isTagged(
|
||||
key: String,
|
||||
tag: String,
|
||||
): Boolean
|
||||
|
||||
fun isAnyTagged(
|
||||
key: String,
|
||||
tags: Set<String>,
|
||||
): Boolean
|
||||
|
||||
fun isTaggedWord(word: String): Boolean
|
||||
|
||||
fun isTaggedUser(idHex: String): Boolean
|
||||
|
||||
fun isTaggedUsers(idHexes: Set<String>): Boolean
|
||||
|
||||
fun isTaggedEvent(idHex: String): Boolean
|
||||
|
||||
fun isTaggedAddressableNote(idHex: String): Boolean
|
||||
|
||||
fun isTaggedAddressableNotes(idHexes: Set<String>): Boolean
|
||||
|
||||
fun isTaggedHash(hashtag: String): Boolean
|
||||
|
||||
fun isTaggedGeoHash(hashtag: String): Boolean
|
||||
|
||||
fun isTaggedHashes(hashtags: Set<String>): Boolean
|
||||
|
||||
fun isTaggedGeoHashes(hashtags: Set<String>): Boolean
|
||||
|
||||
fun firstIsTaggedHashes(hashtags: Set<String>): String?
|
||||
|
||||
fun firstIsTaggedAddressableNote(addressableNotes: Set<String>): String?
|
||||
|
||||
fun isTaggedAddressableKind(kind: Int): Boolean
|
||||
|
||||
fun getTagOfAddressableKind(kind: Int): ATag?
|
||||
|
||||
fun expiration(): Long?
|
||||
|
||||
fun hasHashtags(): Boolean
|
||||
|
||||
fun hasGeohashes(): Boolean
|
||||
|
||||
fun hashtags(): List<String>
|
||||
|
||||
fun geohashes(): List<String>
|
||||
|
||||
fun getReward(): BigDecimal?
|
||||
|
||||
fun getPoWRank(): Int
|
||||
|
||||
fun getGeoHash(): String?
|
||||
|
||||
fun zapSplitSetup(): List<ZapSplitSetup>
|
||||
|
||||
fun isSensitive(): Boolean
|
||||
|
||||
fun subject(): String?
|
||||
|
||||
fun zapraiserAmount(): Long?
|
||||
|
||||
fun hasAnyTaggedUser(): Boolean
|
||||
|
||||
fun hasTagWithContent(tagName: String): Boolean
|
||||
|
||||
fun forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit)
|
||||
|
||||
fun forEachHashTag(onEach: (eventId: HexKey) -> Unit)
|
||||
|
||||
fun anyHashTag(onEach: (str: String) -> Boolean): Boolean
|
||||
|
||||
fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R): List<R>
|
||||
|
||||
fun <R> mapTaggedAddress(map: (address: String) -> R): List<R>
|
||||
|
||||
fun taggedAddresses(): List<ATag>
|
||||
|
||||
fun taggedUsers(): List<HexKey>
|
||||
|
||||
fun taggedEvents(): List<HexKey>
|
||||
|
||||
fun taggedUrls(): List<String>
|
||||
|
||||
fun firstTag(key: String): String?
|
||||
|
||||
fun firstTagFor(vararg key: String): String?
|
||||
|
||||
fun firstTaggedAddress(): ATag?
|
||||
|
||||
fun firstTaggedUser(): HexKey?
|
||||
|
||||
fun firstTaggedEvent(): HexKey?
|
||||
|
||||
fun firstTaggedUrl(): String?
|
||||
|
||||
fun firstTaggedK(): Int?
|
||||
|
||||
fun taggedEmojis(): List<EmojiUrl>
|
||||
|
||||
fun matchTag1With(text: String): Boolean
|
||||
|
||||
fun isExpired(): Boolean
|
||||
|
||||
fun isExpirationBefore(time: Long): Boolean
|
||||
|
||||
fun hasZapSplitSetup(): Boolean
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/**
|
||||
* 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.events
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
|
||||
@Immutable
|
||||
class LnZapPaymentResponseEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
// Once one of an app user decrypts the payment, all users else can see it.
|
||||
@Transient private var response: Response? = null
|
||||
|
||||
override fun countMemory(): Long = super.countMemory() + pointerSizeInBytes + (response?.countMemory() ?: 0)
|
||||
|
||||
fun requestAuthor() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.get(1)
|
||||
|
||||
fun requestId() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
|
||||
|
||||
fun talkingWith(oneSideHex: String): HexKey = if (pubKey == oneSideHex) requestAuthor() ?: pubKey else pubKey
|
||||
|
||||
private fun plainContent(
|
||||
signer: NostrSigner,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
try {
|
||||
signer.decrypt(content, talkingWith(signer.pubKey)) { content -> onReady(content) }
|
||||
} catch (e: Exception) {
|
||||
Log.w("PrivateDM", "Error decrypting the message ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun response(
|
||||
signer: NostrSigner,
|
||||
onReady: (Response) -> Unit,
|
||||
) {
|
||||
response?.let {
|
||||
onReady(it)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (content.isNotEmpty()) {
|
||||
plainContent(signer) {
|
||||
mapper.readValue(it, Response::class.java)?.let {
|
||||
response = it
|
||||
onReady(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 23195
|
||||
const val ALT = "Zap payment response"
|
||||
}
|
||||
}
|
||||
|
||||
// RESPONSE OBJECTS
|
||||
abstract class Response(
|
||||
@JsonProperty("result_type") val resultType: String,
|
||||
) {
|
||||
abstract fun countMemory(): Long
|
||||
}
|
||||
|
||||
// PayInvoice Call
|
||||
|
||||
class PayInvoiceSuccessResponse(
|
||||
val result: PayInvoiceResultParams? = null,
|
||||
) : Response("pay_invoice") {
|
||||
class PayInvoiceResultParams(
|
||||
val preimage: String,
|
||||
) {
|
||||
fun countMemory(): Long = pointerSizeInBytes + preimage.bytesUsedInMemory()
|
||||
}
|
||||
|
||||
override fun countMemory(): Long = pointerSizeInBytes + (result?.countMemory() ?: 0)
|
||||
}
|
||||
|
||||
class PayInvoiceErrorResponse(
|
||||
val error: PayInvoiceErrorParams? = null,
|
||||
) : Response("pay_invoice") {
|
||||
class PayInvoiceErrorParams(
|
||||
val code: ErrorType?,
|
||||
val message: String?,
|
||||
) {
|
||||
fun countMemory(): Long = pointerSizeInBytes + pointerSizeInBytes + (message?.bytesUsedInMemory() ?: 0)
|
||||
}
|
||||
|
||||
override fun countMemory(): Long = pointerSizeInBytes + (error?.countMemory() ?: 0)
|
||||
|
||||
enum class ErrorType {
|
||||
@JsonProperty(value = "RATE_LIMITED")
|
||||
RATE_LIMITED,
|
||||
|
||||
// The client is sending commands too fast. It should retry in a few seconds.
|
||||
@JsonProperty(value = "NOT_IMPLEMENTED")
|
||||
NOT_IMPLEMENTED,
|
||||
|
||||
// The command is not known or is intentionally not implemented.
|
||||
@JsonProperty(value = "INSUFFICIENT_BALANCE")
|
||||
INSUFFICIENT_BALANCE,
|
||||
|
||||
// The wallet does not have enough funds to cover a fee reserve or the payment amount.
|
||||
@JsonProperty(value = "QUOTA_EXCEEDED")
|
||||
QUOTA_EXCEEDED,
|
||||
|
||||
// The wallet has exceeded its spending quota.
|
||||
@JsonProperty(value = "RESTRICTED")
|
||||
RESTRICTED,
|
||||
|
||||
// This public key is not allowed to do this operation.
|
||||
@JsonProperty(value = "UNAUTHORIZED")
|
||||
UNAUTHORIZED,
|
||||
|
||||
// This public key has no wallet connected.
|
||||
@JsonProperty(value = "INTERNAL")
|
||||
INTERNAL,
|
||||
|
||||
// An internal error.
|
||||
@JsonProperty(value = "OTHER")
|
||||
OTHER, // Other error.
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseDeserializer : StdDeserializer<Response>(Response::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Response? {
|
||||
val jsonObject: JsonNode = jp.codec.readTree(jp)
|
||||
val resultType = jsonObject.get("result_type")?.asText()
|
||||
|
||||
if (resultType == "pay_invoice") {
|
||||
val result = jsonObject.get("result")
|
||||
val error = jsonObject.get("error")
|
||||
if (result != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
||||
}
|
||||
if (error != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
||||
}
|
||||
} else {
|
||||
// tries to guess
|
||||
if (jsonObject.get("result")?.get("preimage") != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
|
||||
}
|
||||
if (jsonObject.get("error")?.get("code") != null) {
|
||||
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
/**
|
||||
* 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.events
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.StringWriter
|
||||
|
||||
@Stable
|
||||
abstract class IdentityClaim(
|
||||
val identity: String,
|
||||
val proof: String,
|
||||
) {
|
||||
abstract fun toProofUrl(): String
|
||||
|
||||
abstract fun platform(): String
|
||||
|
||||
fun platformIdentity() = "${platform()}:$identity"
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
platformIdentity: String,
|
||||
proof: String,
|
||||
): IdentityClaim {
|
||||
val (platform, identity) = platformIdentity.split(':')
|
||||
|
||||
return when (platform.lowercase()) {
|
||||
GitHubIdentity.platform -> GitHubIdentity(identity, proof)
|
||||
TwitterIdentity.platform -> TwitterIdentity(identity, proof)
|
||||
TelegramIdentity.platform -> TelegramIdentity(identity, proof)
|
||||
MastodonIdentity.platform -> MastodonIdentity(identity, proof)
|
||||
else -> throw IllegalArgumentException("Platform $platform not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GitHubIdentity(
|
||||
identity: String,
|
||||
proof: String,
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://gist.github.com/$identity/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
|
||||
companion object {
|
||||
val platform = "github"
|
||||
|
||||
fun parseProofUrl(proofUrl: String): GitHubIdentity? {
|
||||
return try {
|
||||
if (proofUrl.isBlank()) return null
|
||||
val path = proofUrl.removePrefix("https://gist.github.com/").split("?")[0].split("/")
|
||||
|
||||
GitHubIdentity(path[0], path[1])
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TwitterIdentity(
|
||||
identity: String,
|
||||
proof: String,
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://x.com/$identity/status/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
|
||||
companion object {
|
||||
val platform = "twitter"
|
||||
|
||||
fun parseProofUrl(proofUrl: String): TwitterIdentity? {
|
||||
return try {
|
||||
if (proofUrl.isBlank()) return null
|
||||
val path = proofUrl.removePrefix("https://x.com/").split("?")[0].split("/")
|
||||
|
||||
TwitterIdentity(path[0], path[2])
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TelegramIdentity(
|
||||
identity: String,
|
||||
proof: String,
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://t.me/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
|
||||
companion object {
|
||||
val platform = "telegram"
|
||||
}
|
||||
}
|
||||
|
||||
class MastodonIdentity(
|
||||
identity: String,
|
||||
proof: String,
|
||||
) : IdentityClaim(identity, proof) {
|
||||
override fun toProofUrl() = "https://$identity/$proof"
|
||||
|
||||
override fun platform() = platform
|
||||
|
||||
companion object {
|
||||
val platform = "mastodon"
|
||||
|
||||
fun parseProofUrl(proofUrl: String): MastodonIdentity? {
|
||||
return try {
|
||||
if (proofUrl.isBlank()) return null
|
||||
val path = proofUrl.removePrefix("https://").split("?")[0].split("/")
|
||||
|
||||
return MastodonIdentity("${path[0]}/${path[1]}", path[2])
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MetadataEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun contactMetaData() =
|
||||
try {
|
||||
mapper.readValue(content, UserMetadata::class.java)
|
||||
} catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
Log.w("MetadataEvent", "Content Parse Error: ${toNostrUri()} ${e.localizedMessage}")
|
||||
null
|
||||
}
|
||||
|
||||
fun identityClaims() =
|
||||
tags
|
||||
.filter { it.firstOrNull() == "i" }
|
||||
.mapNotNull {
|
||||
try {
|
||||
IdentityClaim.create(it[1], it[2])
|
||||
} catch (e: Exception) {
|
||||
Log.e("MetadataEvent", "Can't parse identity [${it.joinToString { "," }}]", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 0
|
||||
|
||||
fun newUser(
|
||||
name: String?,
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): MetadataEvent? {
|
||||
// Tries to not delete any existing attribute that we do not work with.
|
||||
val currentJson = ObjectMapper().createObjectNode()
|
||||
|
||||
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
|
||||
val writer = StringWriter()
|
||||
ObjectMapper().writeValue(writer, currentJson)
|
||||
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
|
||||
tags.add(
|
||||
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
|
||||
)
|
||||
|
||||
return signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString())
|
||||
}
|
||||
|
||||
fun updateFromPast(
|
||||
latest: MetadataEvent?,
|
||||
name: String?,
|
||||
picture: String?,
|
||||
banner: String?,
|
||||
website: String?,
|
||||
about: String?,
|
||||
nip05: String?,
|
||||
lnAddress: String?,
|
||||
lnURL: String?,
|
||||
pronouns: String?,
|
||||
twitter: String?,
|
||||
mastodon: String?,
|
||||
github: String?,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (MetadataEvent) -> Unit,
|
||||
) {
|
||||
// Tries to not delete any existing attribute that we do not work with.
|
||||
val currentJson =
|
||||
if (latest != null) {
|
||||
ObjectMapper()
|
||||
.readTree(
|
||||
ByteArrayInputStream(latest.content.toByteArray(Charsets.UTF_8)),
|
||||
) as ObjectNode
|
||||
} else {
|
||||
ObjectMapper().createObjectNode()
|
||||
}
|
||||
|
||||
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
|
||||
name?.let { addIfNotBlank(currentJson, "display_name", it.trim()) }
|
||||
picture?.let { addIfNotBlank(currentJson, "picture", it.trim()) }
|
||||
banner?.let { addIfNotBlank(currentJson, "banner", it.trim()) }
|
||||
website?.let { addIfNotBlank(currentJson, "website", it.trim()) }
|
||||
pronouns?.let { addIfNotBlank(currentJson, "pronouns", it.trim()) }
|
||||
about?.let { addIfNotBlank(currentJson, "about", it.trim()) }
|
||||
nip05?.let { addIfNotBlank(currentJson, "nip05", it.trim()) }
|
||||
lnAddress?.let { addIfNotBlank(currentJson, "lud16", it.trim()) }
|
||||
lnURL?.let { addIfNotBlank(currentJson, "lud06", it.trim()) }
|
||||
|
||||
var claims = latest?.identityClaims() ?: emptyList()
|
||||
|
||||
if (twitter?.isBlank() == true) {
|
||||
// delete twitter
|
||||
claims = claims.filter { it !is TwitterIdentity }
|
||||
}
|
||||
|
||||
if (github?.isBlank() == true) {
|
||||
// delete github
|
||||
claims = claims.filter { it !is GitHubIdentity }
|
||||
}
|
||||
|
||||
if (mastodon?.isBlank() == true) {
|
||||
// delete mastodon
|
||||
claims = claims.filter { it !is MastodonIdentity }
|
||||
}
|
||||
|
||||
// Updates while keeping other identities intact
|
||||
val newClaims =
|
||||
listOfNotNull(
|
||||
twitter?.let { TwitterIdentity.parseProofUrl(it) },
|
||||
github?.let { GitHubIdentity.parseProofUrl(it) },
|
||||
mastodon?.let { MastodonIdentity.parseProofUrl(it) },
|
||||
) +
|
||||
claims.filter { it !is TwitterIdentity && it !is GitHubIdentity && it !is MastodonIdentity }
|
||||
|
||||
val writer = StringWriter()
|
||||
ObjectMapper().writeValue(writer, currentJson)
|
||||
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
|
||||
tags.add(
|
||||
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
|
||||
)
|
||||
|
||||
newClaims.forEach { tags.add(arrayOf("i", it.platformIdentity(), it.proof)) }
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString(), onReady)
|
||||
}
|
||||
|
||||
private fun addIfNotBlank(
|
||||
currentJson: ObjectNode,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
if (value.isBlank() || value == "null") {
|
||||
currentJson.remove(key)
|
||||
} else {
|
||||
currentJson.put(key, value.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -18,12 +18,14 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.audio
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -43,7 +45,7 @@ class AudioHeaderEvent(
|
||||
tags
|
||||
.firstOrNull { it.size > 1 && it[0] == WAVEFORM }
|
||||
?.get(1)
|
||||
?.let { mapper.readValue<List<Int>>(it) }
|
||||
?.let { EventMapper.mapper.readValue<List<Int>>(it) }
|
||||
|
||||
companion object {
|
||||
const val KIND = 1808
|
||||
+4
-3
@@ -18,11 +18,12 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.audio
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -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.experimental.bounties
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstMapTagged
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun Event.getReward(): BigDecimal? = tags.firstMapTagged("reward") { runCatching { BigDecimal(it[1]) }.getOrNull() }
|
||||
+9
-6
@@ -18,14 +18,17 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.edits
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
@@ -84,7 +87,7 @@ class PrivateOutboxRelayListEvent(
|
||||
try {
|
||||
signer.nip44Decrypt(content, pubKey) {
|
||||
try {
|
||||
privateTagsCache = mapper.readValue<Array<Array<String>>>(it)
|
||||
privateTagsCache = EventMapper.mapper.readValue<Array<Array<String>>>(it)
|
||||
privateTagsCache?.let { onReady(it) }
|
||||
} catch (e: Throwable) {
|
||||
Log.w("PrivateOutboxRelayListEvent", "Error parsing the JSON: ${e.message}. Json `$it` from event `${toNostrUri()}`")
|
||||
@@ -109,7 +112,7 @@ class PrivateOutboxRelayListEvent(
|
||||
signer: NostrSigner,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
val msg = mapper.writeValueAsString(privateTags)
|
||||
val msg = EventMapper.mapper.writeValueAsString(privateTags)
|
||||
|
||||
signer.nip44Encrypt(
|
||||
msg,
|
||||
+5
-3
@@ -18,11 +18,13 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.edits
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.events.firstTaggedEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+2
-1
@@ -18,8 +18,9 @@
|
||||
* 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.encoders
|
||||
package com.vitorpamplona.quartz.experimental.inlineMetadata
|
||||
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import java.net.URI
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
+16
-8
@@ -18,12 +18,20 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.interactiveStories
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.IMetaTag
|
||||
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
|
||||
import com.vitorpamplona.quartz.nip01Core.geohash.geohashMipMap
|
||||
import com.vitorpamplona.quartz.nip10Notes.findHashtags
|
||||
import com.vitorpamplona.quartz.nip10Notes.findURLs
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrl
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import com.vitorpamplona.quartz.nip92IMeta.Nip92MediaAttachments
|
||||
|
||||
open class InteractiveStoryBaseEvent(
|
||||
id: HexKey,
|
||||
@@ -34,11 +42,11 @@ open class InteractiveStoryBaseEvent(
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
fun title() = firstTag("title")
|
||||
fun title() = tags.firstTagValue("title")
|
||||
|
||||
fun summary() = firstTag("summary")
|
||||
fun summary() = tags.firstTagValue("summary")
|
||||
|
||||
fun image() = firstTag("image")
|
||||
fun image() = tags.firstTagValue("image")
|
||||
|
||||
fun options() =
|
||||
tags
|
||||
+7
-5
@@ -18,12 +18,14 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.interactiveStories
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.IMetaTag
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class InteractiveStoryPrologueEvent(
|
||||
+14
-16
@@ -18,12 +18,16 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.interactiveStories
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.removeTrailingNullsAndEmptyOthers
|
||||
|
||||
@@ -36,23 +40,17 @@ class InteractiveStoryReadingStateEvent(
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun title() = firstTag("title")
|
||||
fun title() = tags.firstTagValue("title")
|
||||
|
||||
fun summary() = firstTag("summary")
|
||||
fun summary() = tags.firstTagValue("summary")
|
||||
|
||||
fun image() = firstTag("image")
|
||||
fun image() = tags.firstTagValue("image")
|
||||
|
||||
fun status() = firstTag("status")
|
||||
fun status() = tags.firstTagValue("status")
|
||||
|
||||
fun root() =
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "A" }?.let {
|
||||
ATag.parse(it[1], it.getOrNull(2))
|
||||
}
|
||||
fun root() = tags.firstTag("A")?.let { ATag.parse(it[1], it.getOrNull(2)) }
|
||||
|
||||
fun currentScene() =
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "a" }?.let {
|
||||
ATag.parse(it[1], it.getOrNull(2))
|
||||
}
|
||||
fun currentScene() = tags.firstTag("a")?.let { ATag.parse(it[1], it.getOrNull(2)) }
|
||||
|
||||
companion object {
|
||||
const val KIND = 30298
|
||||
+7
-5
@@ -18,12 +18,14 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.interactiveStories
|
||||
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.IMetaTag
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class InteractiveStorySceneEvent(
|
||||
+4
-3
@@ -18,11 +18,12 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.medical
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+4
-3
@@ -18,12 +18,13 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.nip95
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.util.Base64
|
||||
|
||||
+6
-4
@@ -18,12 +18,14 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.nip95
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.Dimension
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+4
-3
@@ -18,11 +18,12 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.nns
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+5
-4
@@ -18,12 +18,13 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.profileGallery
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+5
-4
@@ -18,12 +18,13 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.profileGallery
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.Dimension
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+4
-3
@@ -18,11 +18,12 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.relationshipStatus
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
+10
-6
@@ -18,14 +18,18 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.experimental.zapPolls
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.IMetaTag
|
||||
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.geohash.geohashMipMap
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrl
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import com.vitorpamplona.quartz.nip92IMeta.Nip92MediaAttachments
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
const val POLL_OPTION = "poll_option"
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.encoders
|
||||
package com.vitorpamplona.quartz.lightning
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.util.Locale
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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.encoders
|
||||
package com.vitorpamplona.quartz.lightning
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
+2
-1
@@ -18,9 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.encoders
|
||||
package com.vitorpamplona.quartz.lightning
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.bech32.Bech32
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class Lud06 {
|
||||
@@ -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.nip01Core
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.Hex
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
fun Event.generateId(): String = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
|
||||
|
||||
fun Event.hasCorrectIDHash(): Boolean {
|
||||
if (id.isEmpty()) return false
|
||||
return id == generateId()
|
||||
}
|
||||
|
||||
fun Event.hasVerifiedSignature(): Boolean {
|
||||
if (id.isEmpty() || sig.isEmpty()) return false
|
||||
return CryptoUtils.verifySignature(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))
|
||||
}
|
||||
|
||||
/** Checks if the ID is correct and then if the pubKey's secret key signed the event. */
|
||||
fun Event.checkSignature() {
|
||||
if (!hasCorrectIDHash()) {
|
||||
throw Exception(
|
||||
"""
|
||||
|Unexpected ID.
|
||||
| Event: ${toJson()}
|
||||
| Actual ID: $id
|
||||
| Generated: ${generateId()}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
if (!hasVerifiedSignature()) {
|
||||
throw Exception("""Bad signature!""")
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.hasValidSignature(): Boolean =
|
||||
try {
|
||||
hasCorrectIDHash() && hasVerifiedSignature()
|
||||
} catch (e: Exception) {
|
||||
Log.w("Event", "Event $id does not have a valid signature: ${toJson()}", e)
|
||||
false
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* 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.nip01Core
|
||||
|
||||
import com.vitorpamplona.quartz.blossom.BlossomAuthorizationEvent
|
||||
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelHideMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMuteUserEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitRepositoryEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.SealedGossipEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoViewEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityListEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
|
||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryRequestEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.FileServersEvent
|
||||
import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
|
||||
class EventFactory {
|
||||
companion object {
|
||||
val factories: MutableMap<Int, (HexKey, HexKey, Long, Array<Array<String>>, String, HexKey) -> Event> = mutableMapOf()
|
||||
|
||||
fun create(
|
||||
id: String,
|
||||
pubKey: String,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: String,
|
||||
): Event =
|
||||
when (kind) {
|
||||
AdvertisedRelayListEvent.KIND -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppDefinitionEvent.KIND -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppRecommendationEvent.KIND -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppSpecificDataEvent.KIND -> AppSpecificDataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AudioHeaderEvent.KIND -> AudioHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AudioTrackEvent.KIND -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BadgeAwardEvent.KIND -> BadgeAwardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BadgeDefinitionEvent.KIND -> BadgeDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BadgeProfilesEvent.KIND -> BadgeProfilesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BlossomServersEvent.KIND -> BlossomServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BlossomAuthorizationEvent.KIND -> BlossomAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
BookmarkListEvent.KIND -> BookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarDateSlotEvent.KIND -> CalendarDateSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelHideMessageEvent.KIND -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChatMessageEncryptedFileHeaderEvent.KIND -> {
|
||||
if (id.isBlank()) {
|
||||
ChatMessageEncryptedFileHeaderEvent(
|
||||
Event.generateId(pubKey, createdAt, kind, tags, content),
|
||||
pubKey,
|
||||
createdAt,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
)
|
||||
} else {
|
||||
ChatMessageEncryptedFileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
}
|
||||
ChatMessageEvent.KIND -> {
|
||||
if (id.isBlank()) {
|
||||
ChatMessageEvent(
|
||||
Event.generateId(pubKey, createdAt, kind, tags, content),
|
||||
pubKey,
|
||||
createdAt,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
)
|
||||
} else {
|
||||
ChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
}
|
||||
ChatMessageRelayListEvent.KIND -> ChatMessageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommentEvent.KIND -> CommentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityDefinitionEvent.KIND -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityPostApprovalEvent.KIND -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
DraftEvent.KIND -> DraftEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitReplyEvent.KIND -> GitReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
InteractiveStoryPrologueEvent.KIND -> InteractiveStoryPrologueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
InteractiveStorySceneEvent.KIND -> InteractiveStorySceneEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
InteractiveStoryReadingStateEvent.KIND -> InteractiveStoryReadingStateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LiveActivitiesChatMessageEvent.KIND -> LiveActivitiesChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LiveActivitiesEvent.KIND -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent.KIND ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner
|
||||
.NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PictureEvent.KIND -> PictureEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PollNoteEvent.KIND -> PollNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelationshipStatusEvent.KIND -> RelationshipStatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SealedGossipEvent.KIND -> SealedGossipEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TextNoteModificationEvent.KIND -> TextNoteModificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TorrentEvent.KIND -> TorrentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TorrentCommentEvent.KIND -> TorrentCommentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
VideoViewEvent.KIND -> VideoViewEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
else -> {
|
||||
factories[kind]?.let {
|
||||
return it(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
|
||||
Event(id, pubKey, createdAt, kind, tags, content, sig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -18,13 +18,11 @@
|
||||
* 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.crypto.nip01
|
||||
package com.vitorpamplona.quartz.nip01Core
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory
|
||||
import com.vitorpamplona.quartz.crypto.sha256Hash
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event.Companion.mapper
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
|
||||
class EventHasher {
|
||||
companion object {
|
||||
@@ -54,7 +52,7 @@ class EventHasher {
|
||||
add(content)
|
||||
}
|
||||
|
||||
return mapper.writeValueAsString(rawEvent)
|
||||
return EventMapper.toJson(rawEvent)
|
||||
}
|
||||
|
||||
fun hashIdBytes(
|
||||
+4
-5
@@ -18,10 +18,11 @@
|
||||
* 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.encoders
|
||||
package com.vitorpamplona.quartz.nip01Core
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.entities.NEvent
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
|
||||
@@ -40,9 +41,7 @@ data class EventHint<T : Event>(
|
||||
event.countMemory() +
|
||||
(relay?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toNEvent(): String = Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay)
|
||||
|
||||
fun toNPub(): String = Nip19Bech32.createNPub(event.id)
|
||||
fun toNEvent(): String = NEvent.create(event.id, event.pubKey, event.kind, relay)
|
||||
|
||||
fun toTagArray(tag: String) = listOfNotNull(tag, event.id, relay, event.pubKey).toTypedArray()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.nip01Core
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.Hex
|
||||
|
||||
/** Makes the distinction between String and Hex * */
|
||||
typealias HexKey = String
|
||||
|
||||
fun ByteArray.toHexKey(): HexKey = Hex.encode(this)
|
||||
|
||||
fun HexKey.hexToByteArray(): ByteArray = Hex.decode(this)
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.nip01Core
|
||||
|
||||
import android.util.Log
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.updateClaims
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.StringWriter
|
||||
|
||||
class MetadataEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun contactMetaData() =
|
||||
try {
|
||||
EventMapper.mapper.readValue(content, UserMetadata::class.java)
|
||||
} catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
Log.w("MetadataEvent", "Content Parse Error: ${toNostrUri()} ${e.localizedMessage}")
|
||||
null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 0
|
||||
|
||||
fun newUser(
|
||||
name: String?,
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): MetadataEvent? {
|
||||
// Tries to not delete any existing attribute that we do not work with.
|
||||
val currentJson = ObjectMapper().createObjectNode()
|
||||
|
||||
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
|
||||
val writer = StringWriter()
|
||||
ObjectMapper().writeValue(writer, currentJson)
|
||||
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
|
||||
tags.add(
|
||||
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
|
||||
)
|
||||
|
||||
return signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString())
|
||||
}
|
||||
|
||||
fun updateFromPast(
|
||||
latest: MetadataEvent?,
|
||||
name: String?,
|
||||
picture: String?,
|
||||
banner: String?,
|
||||
website: String?,
|
||||
about: String?,
|
||||
nip05: String?,
|
||||
lnAddress: String?,
|
||||
lnURL: String?,
|
||||
pronouns: String?,
|
||||
twitter: String?,
|
||||
mastodon: String?,
|
||||
github: String?,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (MetadataEvent) -> Unit,
|
||||
) {
|
||||
// Tries to not delete any existing attribute that we do not work with.
|
||||
val currentJson =
|
||||
if (latest != null) {
|
||||
ObjectMapper()
|
||||
.readTree(
|
||||
ByteArrayInputStream(latest.content.toByteArray(Charsets.UTF_8)),
|
||||
) as ObjectNode
|
||||
} else {
|
||||
ObjectMapper().createObjectNode()
|
||||
}
|
||||
|
||||
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
|
||||
name?.let { addIfNotBlank(currentJson, "display_name", it.trim()) }
|
||||
picture?.let { addIfNotBlank(currentJson, "picture", it.trim()) }
|
||||
banner?.let { addIfNotBlank(currentJson, "banner", it.trim()) }
|
||||
website?.let { addIfNotBlank(currentJson, "website", it.trim()) }
|
||||
pronouns?.let { addIfNotBlank(currentJson, "pronouns", it.trim()) }
|
||||
about?.let { addIfNotBlank(currentJson, "about", it.trim()) }
|
||||
nip05?.let { addIfNotBlank(currentJson, "nip05", it.trim()) }
|
||||
lnAddress?.let { addIfNotBlank(currentJson, "lud16", it.trim()) }
|
||||
lnURL?.let { addIfNotBlank(currentJson, "lud06", it.trim()) }
|
||||
|
||||
val writer = StringWriter()
|
||||
ObjectMapper().writeValue(writer, currentJson)
|
||||
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
tags.add(arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"))
|
||||
|
||||
latest?.updateClaims(twitter, github, mastodon)?.forEach {
|
||||
tags.add(it)
|
||||
}
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString(), onReady)
|
||||
}
|
||||
|
||||
private fun addIfNotBlank(
|
||||
currentJson: ObjectNode,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
if (value.isBlank() || value == "null") {
|
||||
currentJson.remove(key)
|
||||
} else {
|
||||
currentJson.put(key, value.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -18,7 +18,7 @@
|
||||
* 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.crypto.nip01
|
||||
package com.vitorpamplona.quartz.nip01Core
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.nextBytes
|
||||
@@ -40,8 +40,8 @@ class Nip01(
|
||||
fun sign(
|
||||
data: ByteArray,
|
||||
privKey: ByteArray,
|
||||
auxrand32: ByteArray? = random.nextBytes(32),
|
||||
): ByteArray = secp256k1.signSchnorr(data, privKey, auxrand32)
|
||||
nonce: ByteArray? = random.nextBytes(32),
|
||||
): ByteArray = secp256k1.signSchnorr(data, privKey, nonce)
|
||||
|
||||
fun signDeterministic(
|
||||
data: ByteArray,
|
||||
@@ -59,6 +59,6 @@ class Nip01(
|
||||
fun signString(
|
||||
message: String,
|
||||
privKey: ByteArray,
|
||||
auxrand32: ByteArray = random.nextBytes(32),
|
||||
): ByteArray = sign(CryptoUtils.sha256(message.toByteArray()), privKey, auxrand32)
|
||||
nonce: ByteArray = random.nextBytes(32),
|
||||
): ByteArray = sign(CryptoUtils.sha256(message.toByteArray()), privKey, nonce)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.nip01Core
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
|
||||
@Stable
|
||||
class UserMetadata {
|
||||
var name: String? = null
|
||||
|
||||
@Deprecated("Use name instead", replaceWith = ReplaceWith("name"))
|
||||
var username: String? = null
|
||||
|
||||
@JsonProperty("display_name")
|
||||
var displayName: String? = null
|
||||
var picture: String? = null
|
||||
var banner: String? = null
|
||||
var website: String? = null
|
||||
var about: String? = null
|
||||
var bot: Boolean? = null
|
||||
var pronouns: String? = null
|
||||
|
||||
var nip05: String? = null
|
||||
var nip05Verified: Boolean = false
|
||||
var nip05LastVerificationTime: Long? = 0
|
||||
|
||||
var domain: String? = null
|
||||
var lud06: String? = null
|
||||
var lud16: String? = null
|
||||
|
||||
var twitter: String? = null
|
||||
|
||||
@Transient
|
||||
var tags: ImmutableListOfLists<String>? = null
|
||||
|
||||
fun anyName(): String? = displayName ?: name ?: username
|
||||
|
||||
fun anyNameStartsWith(prefix: String): Boolean =
|
||||
listOfNotNull(name, username, displayName, nip05, lud06, lud16).any {
|
||||
it.contains(prefix, true)
|
||||
}
|
||||
|
||||
fun lnAddress(): String? = lud16 ?: lud06
|
||||
|
||||
fun bestName(): String? = displayName ?: name ?: username
|
||||
|
||||
fun nip05(): String? = nip05
|
||||
|
||||
fun profilePicture(): String? = picture
|
||||
|
||||
fun cleanBlankNames() {
|
||||
if (pronouns == "null") pronouns = null
|
||||
|
||||
if (picture?.isNotEmpty() == true) picture = picture?.trim()
|
||||
if (nip05?.isNotEmpty() == true) nip05 = nip05?.trim()
|
||||
if (displayName?.isNotEmpty() == true) displayName = displayName?.trim()
|
||||
if (name?.isNotEmpty() == true) name = name?.trim()
|
||||
if (username?.isNotEmpty() == true) username = username?.trim()
|
||||
if (lud06?.isNotEmpty() == true) lud06 = lud06?.trim()
|
||||
if (lud16?.isNotEmpty() == true) lud16 = lud16?.trim()
|
||||
if (pronouns?.isNotEmpty() == true) pronouns = pronouns?.trim()
|
||||
|
||||
if (banner?.isNotEmpty() == true) banner = banner?.trim()
|
||||
if (website?.isNotEmpty() == true) website = website?.trim()
|
||||
if (domain?.isNotEmpty() == true) domain = domain?.trim()
|
||||
|
||||
if (picture?.isBlank() == true) picture = null
|
||||
if (nip05?.isBlank() == true) nip05 = null
|
||||
if (displayName?.isBlank() == true) displayName = null
|
||||
if (name?.isBlank() == true) name = null
|
||||
if (username?.isBlank() == true) username = null
|
||||
if (lud06?.isBlank() == true) lud06 = null
|
||||
if (lud16?.isBlank() == true) lud16 = null
|
||||
|
||||
if (banner?.isBlank() == true) banner = null
|
||||
if (website?.isBlank() == true) website = null
|
||||
if (domain?.isBlank() == true) domain = null
|
||||
if (pronouns?.isBlank() == true) pronouns = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.nip01Core.addressables
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
import com.vitorpamplona.quartz.utils.removeTrailingNullsAndEmptyOthers
|
||||
|
||||
@Immutable
|
||||
data class ATag(
|
||||
val kind: Int,
|
||||
val pubKeyHex: String,
|
||||
val dTag: String,
|
||||
) {
|
||||
var relay: String? = null
|
||||
|
||||
constructor(
|
||||
kind: Int,
|
||||
pubKeyHex: String,
|
||||
dTag: String,
|
||||
relayHint: String?,
|
||||
) : this(kind, pubKeyHex, dTag) {
|
||||
this.relay = relayHint
|
||||
}
|
||||
|
||||
fun countMemory(): Long =
|
||||
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
8L + // kind
|
||||
pubKeyHex.bytesUsedInMemory() +
|
||||
dTag.bytesUsedInMemory() +
|
||||
(relay?.bytesUsedInMemory() ?: 0)
|
||||
|
||||
fun toTag() = assembleATag(kind, pubKeyHex, dTag)
|
||||
|
||||
fun toATagArray() = removeTrailingNullsAndEmptyOthers("a", toTag(), relay)
|
||||
|
||||
fun toQTagArray() = removeTrailingNullsAndEmptyOthers("q", toTag(), relay)
|
||||
|
||||
companion object {
|
||||
fun assembleATag(
|
||||
kind: Int,
|
||||
pubKeyHex: String,
|
||||
dTag: String,
|
||||
) = "$kind:$pubKeyHex:$dTag"
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.nip01Core.addressables
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
interface AddressableEvent {
|
||||
fun dTag(): String
|
||||
|
||||
fun address(relayHint: String? = null): ATag
|
||||
|
||||
fun addressTag(): String
|
||||
}
|
||||
+13
-20
@@ -18,36 +18,29 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.nip01Core.addressables
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.net.URI
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@Immutable
|
||||
class RecommendRelayEvent(
|
||||
open class BaseAddressableEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
fun relay() = URI.create(content.trim())
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig),
|
||||
AddressableEvent {
|
||||
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
|
||||
|
||||
companion object {
|
||||
const val KIND = 2
|
||||
override fun address(relayHint: String?) = ATag(kind, pubKey, dTag(), relayHint)
|
||||
|
||||
fun create(
|
||||
relay: URI,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (RecommendRelayEvent) -> Unit,
|
||||
) {
|
||||
val content = relay.toString()
|
||||
signer.sign(createdAt, KIND, emptyArray(), content, onReady)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates the tag in a memory efficient way (without creating the ATag class
|
||||
*/
|
||||
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.addressables
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
fun <R> Event.mapTaggedAddress(map: (address: String) -> R) = tags.mapTaggedAddress(map)
|
||||
|
||||
fun Event.firstIsTaggedAddressableNote(addressableNotes: Set<String>) = tags.firstIsTaggedAddressableNote(addressableNotes)
|
||||
|
||||
fun Event.isTaggedAddressableNote(idHex: String) = tags.isTaggedAddressableNote(idHex)
|
||||
|
||||
fun Event.isTaggedAddressableNotes(idHexes: Set<String>) = tags.isTaggedAddressableNotes(idHexes)
|
||||
|
||||
fun Event.isTaggedAddressableKind(kind: Int) = tags.isTaggedAddressableKind(kind)
|
||||
|
||||
fun Event.getTagOfAddressableKind(kind: Int) = tags.getTagOfAddressableKind(kind)
|
||||
|
||||
fun Event.taggedAddresses() = tags.taggedAddresses()
|
||||
|
||||
fun Event.firstTaggedAddress() = tags.firstTaggedAddress()
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.nip01Core.addressables
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstMapTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValueTagged
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
|
||||
|
||||
fun <R> TagArray.mapTaggedAddress(map: (address: String) -> R) = this.mapValueTagged("a", map)
|
||||
|
||||
fun TagArray.firstIsTaggedAddressableNote(addressableNotes: Set<String>) =
|
||||
this
|
||||
.firstOrNull { it.size > 1 && it[0] == "a" && it[1] in addressableNotes }
|
||||
?.getOrNull(1)
|
||||
|
||||
fun TagArray.isTaggedAddressableNote(idHex: String) = this.isTagged("a", idHex)
|
||||
|
||||
fun TagArray.isTaggedAddressableNotes(idHexes: Set<String>) = this.isAnyTagged("a", idHexes)
|
||||
|
||||
fun TagArray.isTaggedAddressableKind(kind: Int): Boolean {
|
||||
val kindStr = kind.toString()
|
||||
return this.any { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }
|
||||
}
|
||||
|
||||
fun TagArray.getTagOfAddressableKind(kind: Int): ATag? {
|
||||
val kindStr = kind.toString()
|
||||
val aTag =
|
||||
this
|
||||
.firstOrNull { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }
|
||||
?.getOrNull(1)
|
||||
?: return null
|
||||
|
||||
return ATag.parse(aTag, null)
|
||||
}
|
||||
|
||||
fun TagArray.taggedAddresses() = this.mapTagged("a") { ATag.parse(it[1], it.getOrNull(2)) }
|
||||
|
||||
fun TagArray.firstTaggedAddress() = this.firstMapTagged("a") { ATag.parse(it[1], it.getOrNull(2)) }
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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.nip01Core.core
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.vitorpamplona.quartz.nip01Core.EventHasher
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventManualSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
|
||||
@Immutable
|
||||
open class Event(
|
||||
val id: HexKey,
|
||||
@JsonProperty("pubkey") val pubKey: HexKey,
|
||||
@JsonProperty("created_at") val createdAt: Long,
|
||||
val kind: Int,
|
||||
val tags: TagArray,
|
||||
val content: String,
|
||||
val sig: HexKey,
|
||||
) {
|
||||
open fun isContentEncoded() = false
|
||||
|
||||
open fun countMemory(): Long =
|
||||
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||
12L + // createdAt + kind
|
||||
id.bytesUsedInMemory() +
|
||||
pubKey.bytesUsedInMemory() +
|
||||
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
|
||||
content.bytesUsedInMemory() +
|
||||
sig.bytesUsedInMemory()
|
||||
|
||||
fun toJson(): String = EventManualSerializer.toJson(id, pubKey, createdAt, kind, tags, content, sig)
|
||||
|
||||
companion object {
|
||||
fun fromJson(json: String): Event = EventMapper.fromJson(json)
|
||||
|
||||
fun toJson(event: Event): String = EventMapper.toJson(event)
|
||||
|
||||
fun generateId(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): HexKey = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
|
||||
|
||||
fun generateIdBytes(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): ByteArray = EventHasher.hashIdBytes(pubKey, createdAt, kind, tags, content)
|
||||
|
||||
fun create(
|
||||
signer: NostrSigner,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>> = emptyArray(),
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (Event) -> Unit,
|
||||
) = signer.sign(createdAt, kind, tags, content, onReady)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.nip01Core.core
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
|
||||
typealias TagArray = Array<Array<String>>
|
||||
|
||||
fun TagArray.forEachTagged(
|
||||
tagName: String,
|
||||
onEach: (eventId: HexKey) -> Unit,
|
||||
) = this.forEach {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
onEach(it[1])
|
||||
}
|
||||
}
|
||||
|
||||
fun TagArray.anyTagged(
|
||||
tagName: String,
|
||||
onEach: (tagValue: String) -> Boolean,
|
||||
) = this.any {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
onEach(it[1])
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun TagArray.anyTagged(tagName: String) = this.any { it.size > 0 && it[0] == tagName }
|
||||
|
||||
fun TagArray.anyTagWithValueStartingWithIgnoreCase(
|
||||
tagName: String,
|
||||
valuePrefix: String,
|
||||
): Boolean = this.any { it.size > 1 && it[0] == tagName && it[1].startsWith(valuePrefix, true) }
|
||||
|
||||
fun TagArray.hasTagWithContent(tagName: String) = this.any { it.size > 1 && it[0] == tagName }
|
||||
|
||||
fun <R> TagArray.mapValueTagged(
|
||||
tagName: String,
|
||||
map: (tagValue: String) -> R,
|
||||
) = this.mapNotNull {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
map(it[1])
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun <R> TagArray.mapTagged(
|
||||
tagName: String,
|
||||
map: (tagValue: Array<String>) -> R,
|
||||
) = this.mapNotNull {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
map(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun TagArray.mapValues(tagName: String) =
|
||||
this.mapNotNull {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
it[1]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun <R> TagArray.firstMapTagged(
|
||||
tagName: String,
|
||||
map: (tagValue: Array<String>) -> R,
|
||||
) = this.firstNotNullOfOrNull {
|
||||
if (it.size > 1 && it[0] == tagName) {
|
||||
map(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun TagArray.filterByTag(tagName: String) = this.filter { it.size > 0 && it[0] == tagName }
|
||||
|
||||
fun TagArray.filterByTagWithValue(tagName: String) = this.filter { it.size > 1 && it[0] == tagName }
|
||||
|
||||
fun TagArray.firstTag(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }
|
||||
|
||||
fun TagArray.firstTagValue(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1] }
|
||||
|
||||
fun TagArray.firstTagValueAsInt(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1].toIntOrNull() }
|
||||
|
||||
fun TagArray.firstTagValueAsLong(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1].toLongOrNull() }
|
||||
|
||||
fun TagArray.firstTagValueFor(vararg key: String) = this.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] }
|
||||
|
||||
fun TagArray.isTagged(
|
||||
key: String,
|
||||
tag: String,
|
||||
) = this.any { it.size > 1 && it[0] == key && it[1] == tag }
|
||||
|
||||
fun TagArray.isAnyTagged(
|
||||
key: String,
|
||||
tags: Set<String>,
|
||||
) = this.any { it.size > 1 && it[0] == key && it[1] in tags }
|
||||
|
||||
fun TagArray.matchTag1With(text: String) = this.any { it.size > 1 && it[1].contains(text, true) }
|
||||
@@ -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.nip01Core.events
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
fun Event.forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = tags.forEachTaggedEvent(onEach)
|
||||
|
||||
fun <R> Event.mapTaggedEvent(map: (eventId: HexKey) -> R) = tags.mapTaggedEvent(map)
|
||||
|
||||
fun Event.taggedEvents() = tags.taggedEvents()
|
||||
|
||||
fun Event.firstTaggedEvent() = tags.firstTaggedEvent()
|
||||
|
||||
fun Event.isTaggedEvent(idHex: String) = tags.isTaggedEvent(idHex)
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.events
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
|
||||
import com.vitorpamplona.quartz.nip01Core.core.forEachTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValueTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValues
|
||||
|
||||
fun TagArray.forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = this.forEachTagged("e", onEach)
|
||||
|
||||
fun <R> TagArray.mapTaggedEvent(map: (eventId: HexKey) -> R) = this.mapValueTagged("e", map)
|
||||
|
||||
fun TagArray.taggedEvents() = this.mapValues("e")
|
||||
|
||||
fun TagArray.firstTaggedEvent() = this.firstTagValue("e")
|
||||
|
||||
fun TagArray.isTaggedEvent(idHex: String) = this.isTagged("e", idHex)
|
||||
+2
-2
@@ -18,9 +18,9 @@
|
||||
* 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.encoders
|
||||
package com.vitorpamplona.quartz.nip01Core.experimental
|
||||
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.CharBuffer
|
||||
import java.nio.charset.CodingErrorAction
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.nip01Core.geohash
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
|
||||
fun Event.hasGeohashes() =
|
||||
if (this is CommentEvent) {
|
||||
this.hasGeohashes()
|
||||
} else {
|
||||
tags.hasGeohashes()
|
||||
}
|
||||
|
||||
fun Event.isTaggedGeoHashes(hashtags: Set<String>) =
|
||||
if (this is CommentEvent) {
|
||||
this.isTaggedGeoHashes(hashtags)
|
||||
} else {
|
||||
tags.isTaggedGeoHashes(hashtags)
|
||||
}
|
||||
|
||||
fun Event.isTaggedGeoHash(hashtag: String) =
|
||||
if (this is CommentEvent) {
|
||||
this.isTaggedGeoHash(hashtag)
|
||||
} else {
|
||||
tags.isTaggedGeoHash(hashtag)
|
||||
}
|
||||
|
||||
fun Event.geohashes() =
|
||||
if (this is CommentEvent) {
|
||||
geohashes()
|
||||
} else {
|
||||
tags.geohashes()
|
||||
}
|
||||
|
||||
fun Event.getGeoHash(): String? =
|
||||
if (this is CommentEvent) {
|
||||
getGeoHash()
|
||||
} else {
|
||||
tags.getGeoHash()
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.nip01Core.geohash
|
||||
|
||||
fun geohashMipMap(geohash: String): Array<Array<String>> =
|
||||
geohash.indices
|
||||
.asSequence()
|
||||
.map { arrayOf("g", geohash.substring(0, it + 1)) }
|
||||
.toList()
|
||||
.reversed()
|
||||
.toTypedArray()
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.nip01Core.geohash
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.anyTagWithValueStartingWithIgnoreCase
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValues
|
||||
|
||||
fun TagArray.hasGeohashes() = this.hasTagWithContent("g")
|
||||
|
||||
fun TagArray.isTaggedGeoHashes(hashtags: Set<String>) = this.isAnyTagged("g", hashtags)
|
||||
|
||||
fun TagArray.isTaggedGeoHash(hashtag: String) = this.anyTagWithValueStartingWithIgnoreCase("g", hashtag)
|
||||
|
||||
fun TagArray.geohashes() = this.mapValues("g")
|
||||
|
||||
fun TagArray.getGeoHash(): String? = geohashes().maxByOrNull { it.length }?.ifBlank { null }
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.nip01Core.hashtags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
fun Event.forEachHashTag(onEach: (eventId: HexKey) -> Unit) = tags.forEachHashTag(onEach)
|
||||
|
||||
fun Event.anyHashTag(onEach: (str: String) -> Boolean) = tags.anyHashTag(onEach)
|
||||
|
||||
fun Event.hasHashtags() = tags.hasHashtags()
|
||||
|
||||
fun Event.hashtags() = tags.hashtags()
|
||||
|
||||
fun Event.isTaggedHash(hashtag: String) = tags.isTaggedHash(hashtag)
|
||||
|
||||
fun Event.isTaggedHashes(hashtags: Set<String>) = tags.isTaggedHashes(hashtags)
|
||||
|
||||
fun Event.firstIsTaggedHashes(hashtags: Set<String>) = tags.firstIsTaggedHashes(hashtags)
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.nip01Core.hashtags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.anyTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.forEachTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValues
|
||||
|
||||
fun TagArray.forEachHashTag(onEach: (eventId: HexKey) -> Unit) = this.forEachTagged("t", onEach)
|
||||
|
||||
fun TagArray.anyHashTag(onEach: (str: String) -> Boolean) = this.anyTagged("t", onEach)
|
||||
|
||||
fun TagArray.hasHashtags() = this.hasTagWithContent("t")
|
||||
|
||||
fun TagArray.hashtags() = this.mapValues("t")
|
||||
|
||||
fun TagArray.isTaggedHash(hashtag: String) = this.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
|
||||
|
||||
fun TagArray.isTaggedHashes(hashtags: Set<String>) = this.any { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }
|
||||
|
||||
fun TagArray.firstIsTaggedHashes(hashtags: Set<String>) = this.firstOrNull { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }?.getOrNull(1)
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
|
||||
override fun deserialize(
|
||||
jp: JsonParser,
|
||||
ctxt: DeserializationContext,
|
||||
): Event = EventManualDeserializer.fromJson(jp.codec.readTree(jp))
|
||||
}
|
||||
+43
@@ -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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.vitorpamplona.quartz.nip01Core.EventFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
class EventManualDeserializer {
|
||||
companion object {
|
||||
fun fromJson(jsonObject: JsonNode): Event =
|
||||
EventFactory.create(
|
||||
id = jsonObject.get("id").asText().intern(),
|
||||
pubKey = jsonObject.get("pubkey").asText().intern(),
|
||||
createdAt = jsonObject.get("created_at").asLong(),
|
||||
kind = jsonObject.get("kind").asInt(),
|
||||
tags =
|
||||
jsonObject.get("tags").toTypedArray {
|
||||
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
|
||||
},
|
||||
content = jsonObject.get("content").asText(),
|
||||
sig = jsonObject.get("sig").asText(),
|
||||
)
|
||||
}
|
||||
}
|
||||
+62
@@ -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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
|
||||
class EventManualSerializer {
|
||||
companion object {
|
||||
fun toJson(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: String,
|
||||
): String {
|
||||
val factory = JsonNodeFactory.instance
|
||||
|
||||
val obj =
|
||||
factory.objectNode().apply {
|
||||
put("id", id)
|
||||
put("pubkey", pubKey)
|
||||
put("created_at", createdAt)
|
||||
put("kind", kind)
|
||||
replace(
|
||||
"tags",
|
||||
factory.arrayNode(tags.size).apply {
|
||||
tags.forEach { tag ->
|
||||
add(
|
||||
factory.arrayNode(tag.size).apply { tag.forEach { add(it) } },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
put("content", content)
|
||||
put("sig", sig)
|
||||
}
|
||||
|
||||
return EventMapper.mapper.writeValueAsString(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerMessage
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.RequestDeserializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.ResponseDeserializer
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.Gossip
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GossipDeserializer
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GossipSerializer
|
||||
|
||||
class EventMapper {
|
||||
companion object {
|
||||
val mapper =
|
||||
jacksonObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||
.registerModule(
|
||||
SimpleModule()
|
||||
.addSerializer(Event::class.java, EventSerializer())
|
||||
.addDeserializer(Event::class.java, EventDeserializer())
|
||||
.addSerializer(Gossip::class.java, GossipSerializer())
|
||||
.addDeserializer(Gossip::class.java, GossipDeserializer())
|
||||
.addDeserializer(Response::class.java, ResponseDeserializer())
|
||||
.addDeserializer(Request::class.java, RequestDeserializer())
|
||||
.addDeserializer(BunkerMessage::class.java, BunkerMessage.BunkerMessageDeserializer())
|
||||
.addSerializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestSerializer())
|
||||
.addDeserializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestDeserializer())
|
||||
.addSerializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseSerializer())
|
||||
.addDeserializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseDeserializer()),
|
||||
)
|
||||
|
||||
fun fromJson(json: String): Event = mapper.readValue(json, Event::class.java)
|
||||
|
||||
fun fromJson(json: JsonNode): Event = EventManualDeserializer.fromJson(json)
|
||||
|
||||
fun toJson(event: Event): String = mapper.writeValueAsString(event)
|
||||
|
||||
fun toJson(event: ArrayNode?): String = mapper.writeValueAsString(event)
|
||||
|
||||
fun toJson(event: ObjectNode?): String = mapper.writeValueAsString(event)
|
||||
}
|
||||
}
|
||||
@@ -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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
class EventSerializer : StdSerializer<Event>(Event::class.java) {
|
||||
override fun serialize(
|
||||
event: Event,
|
||||
gen: JsonGenerator,
|
||||
provider: SerializerProvider,
|
||||
) {
|
||||
gen.writeStartObject()
|
||||
gen.writeStringField("id", event.id)
|
||||
gen.writeStringField("pubkey", event.pubKey)
|
||||
gen.writeNumberField("created_at", event.createdAt)
|
||||
gen.writeNumberField("kind", event.kind)
|
||||
gen.writeArrayFieldStart("tags")
|
||||
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
|
||||
gen.writeEndArray()
|
||||
gen.writeStringField("content", event.content)
|
||||
gen.writeStringField("sig", event.sig)
|
||||
gen.writeEndObject()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.nip01Core.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
|
||||
inline fun <reified R> JsonNode.toTypedArray(transform: (JsonNode) -> R): Array<R> = Array(size()) { transform(get(it)) }
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.nip01Core.people
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValues
|
||||
|
||||
fun Event.isTaggedUser(idHex: String) = tags.isTagged("p", idHex)
|
||||
|
||||
fun Event.isTaggedUsers(idHexes: Set<String>) = tags.isAnyTagged("p", idHexes)
|
||||
|
||||
fun Event.taggedUsers() = tags.mapValues("p")
|
||||
|
||||
fun Event.firstTaggedUser() = tags.firstTagValue("p")
|
||||
|
||||
fun Event.hasAnyTaggedUser() = tags.hasTagWithContent("p")
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.nip01Core.people
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isTagged
|
||||
import com.vitorpamplona.quartz.nip01Core.core.mapValues
|
||||
|
||||
fun TagArray.isTaggedUser(idHex: String) = this.isTagged("p", idHex)
|
||||
|
||||
fun TagArray.isTaggedUsers(idHexes: Set<String>) = this.isAnyTagged("p", idHexes)
|
||||
|
||||
fun TagArray.taggedUsers() = this.mapValues("p")
|
||||
|
||||
fun TagArray.firstTaggedUser() = this.firstTagValue("p")
|
||||
|
||||
fun TagArray.hasAnyTaggedUser() = this.hasTagWithContent("p")
|
||||
+7
-7
@@ -18,14 +18,14 @@
|
||||
* 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.signers
|
||||
package com.vitorpamplona.quartz.nip01Core.signers
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.nip04.Nip04
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventFactory
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.EventFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip04Dm.Nip04
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
|
||||
abstract class NostrSigner(
|
||||
val pubKey: HexKey,
|
||||
+6
-6
@@ -18,14 +18,14 @@
|
||||
* 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.signers
|
||||
package com.vitorpamplona.quartz.nip01Core.signers
|
||||
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
|
||||
class NostrSignerInternal(
|
||||
val keyPair: KeyPair,
|
||||
+13
-12
@@ -18,18 +18,19 @@
|
||||
* 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.signers
|
||||
package com.vitorpamplona.quartz.nip01Core.signers
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventFactory
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.EventFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapEncryption
|
||||
|
||||
class NostrSignerSync(
|
||||
val keyPair: KeyPair,
|
||||
@@ -153,7 +154,7 @@ class NostrSignerSync(
|
||||
val idToGeneratePrivateKey = zappedEvent ?: userHex
|
||||
|
||||
val encryptionPrivateKey =
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
PrivateZapEncryption.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
idToGeneratePrivateKey,
|
||||
createdAt,
|
||||
@@ -165,7 +166,7 @@ class NostrSignerSync(
|
||||
|
||||
val noteJson = privateEvent.toJson()
|
||||
val encryptedContent =
|
||||
LnZapRequestEvent.encryptPrivateZapMessage(
|
||||
PrivateZapEncryption.encryptPrivateZapMessage(
|
||||
noteJson,
|
||||
encryptionPrivateKey,
|
||||
userHex.hexToByteArray(),
|
||||
@@ -195,13 +196,13 @@ class NostrSignerSync(
|
||||
val altPubkeyToUse = recipientPK
|
||||
val altPrivateKeyToUse =
|
||||
if (recipientPost != null) {
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
PrivateZapEncryption.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
recipientPost,
|
||||
event.createdAt,
|
||||
)
|
||||
} else if (recipientPK != null) {
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
PrivateZapEncryption.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
recipientPK,
|
||||
event.createdAt,
|
||||
+20
-89
@@ -18,19 +18,27 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.nip02FollowList
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.decodePublicKey
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.addressables.isTaggedAddressableNote
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.events.isTaggedEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.geohash.isTaggedGeoHash
|
||||
import com.vitorpamplona.quartz.nip01Core.hashtags.hashtags
|
||||
import com.vitorpamplona.quartz.nip01Core.hashtags.isTaggedHash
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.decodePublicKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable data class Contact(
|
||||
@@ -83,8 +91,6 @@ class ContactListEvent(
|
||||
|
||||
fun countFollowTags() = tags.count { it.size > 1 && it[0] == "t" }
|
||||
|
||||
fun unverifiedFollowGeohashSet() = tags.filter { it.size > 1 && it[0] == "g" }.mapNotNull { it.getOrNull(1) }
|
||||
|
||||
fun follows() =
|
||||
tags.mapNotNull {
|
||||
try {
|
||||
@@ -104,7 +110,7 @@ class ContactListEvent(
|
||||
fun relays(): Map<String, ReadWrite>? =
|
||||
try {
|
||||
if (content.isNotEmpty()) {
|
||||
mapper.readValue<Map<String, ReadWrite>>(content)
|
||||
EventMapper.mapper.readValue<Map<String, ReadWrite>>(content)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -131,7 +137,7 @@ class ContactListEvent(
|
||||
): ContactListEvent? {
|
||||
val content =
|
||||
if (relayUse != null) {
|
||||
mapper.writeValueAsString(relayUse)
|
||||
EventMapper.mapper.writeValueAsString(relayUse)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -162,7 +168,7 @@ class ContactListEvent(
|
||||
) {
|
||||
val content =
|
||||
if (relayUse != null) {
|
||||
mapper.writeValueAsString(relayUse)
|
||||
EventMapper.mapper.writeValueAsString(relayUse)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -382,7 +388,7 @@ class ContactListEvent(
|
||||
) {
|
||||
val content =
|
||||
if (relayUse != null) {
|
||||
mapper.writeValueAsString(relayUse)
|
||||
EventMapper.mapper.writeValueAsString(relayUse)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -420,81 +426,6 @@ class ContactListEvent(
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
class UserMetadata {
|
||||
var name: String? = null
|
||||
|
||||
@Deprecated("Use name instead", replaceWith = ReplaceWith("name"))
|
||||
var username: String? = null
|
||||
|
||||
@JsonProperty("display_name")
|
||||
var displayName: String? = null
|
||||
var picture: String? = null
|
||||
var banner: String? = null
|
||||
var website: String? = null
|
||||
var about: String? = null
|
||||
var bot: Boolean? = null
|
||||
var pronouns: String? = null
|
||||
|
||||
var nip05: String? = null
|
||||
var nip05Verified: Boolean = false
|
||||
var nip05LastVerificationTime: Long? = 0
|
||||
|
||||
var domain: String? = null
|
||||
var lud06: String? = null
|
||||
var lud16: String? = null
|
||||
|
||||
var twitter: String? = null
|
||||
|
||||
@Transient
|
||||
var tags: ImmutableListOfLists<String>? = null
|
||||
|
||||
fun anyName(): String? = displayName ?: name ?: username
|
||||
|
||||
fun anyNameStartsWith(prefix: String): Boolean =
|
||||
listOfNotNull(name, username, displayName, nip05, lud06, lud16).any {
|
||||
it.contains(prefix, true)
|
||||
}
|
||||
|
||||
fun lnAddress(): String? = lud16 ?: lud06
|
||||
|
||||
fun bestName(): String? = displayName ?: name ?: username
|
||||
|
||||
fun nip05(): String? = nip05
|
||||
|
||||
fun profilePicture(): String? = picture
|
||||
|
||||
fun cleanBlankNames() {
|
||||
if (pronouns == "null") pronouns = null
|
||||
|
||||
if (picture?.isNotEmpty() == true) picture = picture?.trim()
|
||||
if (nip05?.isNotEmpty() == true) nip05 = nip05?.trim()
|
||||
if (displayName?.isNotEmpty() == true) displayName = displayName?.trim()
|
||||
if (name?.isNotEmpty() == true) name = name?.trim()
|
||||
if (username?.isNotEmpty() == true) username = username?.trim()
|
||||
if (lud06?.isNotEmpty() == true) lud06 = lud06?.trim()
|
||||
if (lud16?.isNotEmpty() == true) lud16 = lud16?.trim()
|
||||
if (pronouns?.isNotEmpty() == true) pronouns = pronouns?.trim()
|
||||
|
||||
if (banner?.isNotEmpty() == true) banner = banner?.trim()
|
||||
if (website?.isNotEmpty() == true) website = website?.trim()
|
||||
if (domain?.isNotEmpty() == true) domain = domain?.trim()
|
||||
|
||||
if (picture?.isBlank() == true) picture = null
|
||||
if (nip05?.isBlank() == true) nip05 = null
|
||||
if (displayName?.isBlank() == true) displayName = null
|
||||
if (name?.isBlank() == true) name = null
|
||||
if (username?.isBlank() == true) username = null
|
||||
if (lud06?.isBlank() == true) lud06 = null
|
||||
if (lud16?.isBlank() == true) lud16 = null
|
||||
|
||||
if (banner?.isBlank() == true) banner = null
|
||||
if (website?.isBlank() == true) website = null
|
||||
if (domain?.isBlank() == true) domain = null
|
||||
if (pronouns?.isBlank() == true) pronouns = null
|
||||
}
|
||||
}
|
||||
|
||||
@Stable class ImmutableListOfLists<T>(
|
||||
val lists: Array<Array<T>>,
|
||||
)
|
||||
+28
-35
@@ -18,46 +18,27 @@
|
||||
* 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.events
|
||||
package com.vitorpamplona.quartz.nip03Timestamp
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.ots.BlockstreamExplorer
|
||||
import com.vitorpamplona.quartz.ots.CalendarPureJavaBuilder
|
||||
import com.vitorpamplona.quartz.ots.DetachedTimestampFile
|
||||
import com.vitorpamplona.quartz.ots.Hash
|
||||
import com.vitorpamplona.quartz.ots.OpenTimestamps
|
||||
import com.vitorpamplona.quartz.ots.VerifyResult
|
||||
import com.vitorpamplona.quartz.ots.exceptions.UrlException
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockstreamExplorer
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.CalendarPureJavaBuilder
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.DetachedTimestampFile
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Hash
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.VerifyResult
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import java.util.Base64
|
||||
|
||||
@Immutable
|
||||
sealed class VerificationState {
|
||||
@Immutable object NotStarted : VerificationState()
|
||||
|
||||
@Stable
|
||||
class Verified(
|
||||
val verifiedTime: Long,
|
||||
) : VerificationState()
|
||||
|
||||
@Immutable class Error(
|
||||
val errorMessage: String,
|
||||
) : VerificationState()
|
||||
|
||||
@Immutable class NetworkError(
|
||||
val errorMessage: String,
|
||||
val time: Long = TimeUtils.now(),
|
||||
) : VerificationState()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class OtsEvent(
|
||||
id: HexKey,
|
||||
@@ -110,13 +91,25 @@ class OtsEvent(
|
||||
const val KIND = 1040
|
||||
const val ALT = "Opentimestamps Attestation"
|
||||
|
||||
var otsInstance = OpenTimestamps(BlockstreamExplorer(), CalendarPureJavaBuilder())
|
||||
var otsInstance =
|
||||
OpenTimestamps(
|
||||
BlockstreamExplorer(),
|
||||
CalendarPureJavaBuilder(),
|
||||
)
|
||||
|
||||
fun stamp(eventId: HexKey): String {
|
||||
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
|
||||
val hash =
|
||||
Hash(
|
||||
eventId.hexToByteArray(),
|
||||
OpSHA256._TAG,
|
||||
)
|
||||
val file = DetachedTimestampFile.from(hash)
|
||||
val timestamp = otsInstance.stamp(file)
|
||||
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
|
||||
val detachedToSerialize =
|
||||
DetachedTimestampFile(
|
||||
hash.getOp(),
|
||||
timestamp,
|
||||
)
|
||||
return Base64.getEncoder().encodeToString(detachedToSerialize.serialize())
|
||||
}
|
||||
|
||||
@@ -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.nip03Timestamp
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
sealed class VerificationState {
|
||||
@Immutable
|
||||
object NotStarted : VerificationState()
|
||||
|
||||
@Stable
|
||||
class Verified(
|
||||
val verifiedTime: Long,
|
||||
) : VerificationState()
|
||||
|
||||
@Immutable
|
||||
class Error(
|
||||
val errorMessage: String,
|
||||
) : VerificationState()
|
||||
|
||||
@Immutable
|
||||
class NetworkError(
|
||||
val errorMessage: String,
|
||||
val time: Long = TimeUtils.now(),
|
||||
) : VerificationState()
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
public interface BitcoinExplorer {
|
||||
/**
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
public class BlockHeader {
|
||||
|
||||
+6
-4
@@ -1,13 +1,15 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.vitorpamplona.quartz.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.ots.http.Response;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class BlockstreamExplorer implements BitcoinExplorer {
|
||||
private static final String esploraUrl = "https://blockstream.info/api";
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.CommitmentNotFoundException;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.ExceededSizeException;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.UrlException;
|
||||
import com.vitorpamplona.quartz.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.ots.http.Response;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
+3
-5
@@ -1,9 +1,7 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.ots.http.Response;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
public interface CalendarBuilder {
|
||||
public ICalendar newSyncCalendar(String url);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
public class CalendarPureJavaBuilder implements CalendarBuilder {
|
||||
public ICalendar newSyncCalendar(String url) {
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.ots.op.Op;
|
||||
import com.vitorpamplona.quartz.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.ots.op.OpKECCAK256;
|
||||
import com.vitorpamplona.quartz.ots.op.OpRIPEMD160;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA1;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
import com.vitorpamplona.quartz.crypto.Hex;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpKECCAK256;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpRIPEMD160;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA1;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException;
|
||||
|
||||
public interface ICalendar {
|
||||
Timestamp submit(byte[] digest)
|
||||
throws ExceededSizeException, UrlException, DeserializationException;
|
||||
|
||||
Timestamp getTimestamp(byte[] commitment) throws DeserializationException, ExceededSizeException, CommitmentNotFoundException, UrlException;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.op.OpAppend;
|
||||
import com.vitorpamplona.quartz.ots.op.OpPrepend;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpPrepend;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
+11
-11
@@ -1,17 +1,17 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.attestation.BitcoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.EthereumBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.LitecoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.PendingAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.TimeAttestation;
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
|
||||
import com.vitorpamplona.quartz.ots.op.OpAppend;
|
||||
import com.vitorpamplona.quartz.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
import com.vitorpamplona.quartz.crypto.Hex;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.EthereumBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.LitecoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.PendingAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StreamDeserializationContext {
|
||||
|
||||
+1
-2
@@ -1,9 +1,8 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StreamSerializationContext {
|
||||
|
||||
+18
-9
@@ -1,15 +1,24 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.attestation.BitcoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.ots.attestation.TimeAttestation;
|
||||
import com.vitorpamplona.quartz.encoders.Hex;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.ots.op.Op;
|
||||
import com.vitorpamplona.quartz.ots.op.OpBinary;
|
||||
import com.vitorpamplona.quartz.ots.op.OpSHA256;
|
||||
import com.vitorpamplona.quartz.crypto.Hex;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpBinary;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Proof that one or more attestations commit to a message.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots;
|
||||
|
||||
import java.text.DateFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
+6
-7
@@ -1,13 +1,12 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.BlockHeader;
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bitcoin Block Header Attestation.
|
||||
+3
-5
@@ -1,11 +1,9 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Ethereum Block Header Attestation.
|
||||
+6
-7
@@ -1,13 +1,12 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.BlockHeader;
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Litecoin Block Header Attestation.
|
||||
+5
-6
@@ -1,15 +1,14 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Pending attestations.
|
||||
+7
-7
@@ -1,15 +1,15 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Class representing {@link com.vitorpamplona.quartz.ots.Timestamp} signature verification
|
||||
* Class representing {@link Timestamp} signature verification
|
||||
*/
|
||||
public abstract class TimeAttestation implements Comparable<TimeAttestation> {
|
||||
|
||||
+5
-6
@@ -1,12 +1,11 @@
|
||||
package com.vitorpamplona.quartz.ots.attestation;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Placeholder for attestations that don't support
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
|
||||
|
||||
/**
|
||||
* Message digest interface
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
|
||||
|
||||
public interface ExtendedDigest extends Digest {
|
||||
/**
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
|
||||
|
||||
/**
|
||||
* Base implementation of MD4 family style digest as outlined in
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
|
||||
|
||||
import com.vitorpamplona.quartz.ots.Utils;
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
|
||||
|
||||
/**
|
||||
* Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vitorpamplona.quartz.ots.crypto;
|
||||
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
|
||||
|
||||
/**
|
||||
* Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user