Merge pull request #1943 from dadofsambonzuki/attestation-fix

Align attestation events with latest NIP format
This commit is contained in:
Vitor Pamplona
2026-03-25 07:49:07 -04:00
committed by GitHub
13 changed files with 19 additions and 180 deletions
@@ -84,7 +84,6 @@ import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
@@ -106,8 +105,7 @@ fun RenderAttestationPreview() {
arrayOf( arrayOf(
arrayOf("d", "af5aa898:fe108febb997:1773941524"), arrayOf("d", "af5aa898:fe108febb997:1773941524"),
arrayOf("e", "fe108febb99796c4091775e00aa1fc3ffc489ad22fdf1f8c559b2472815c09c7"), arrayOf("e", "fe108febb99796c4091775e00aa1fc3ffc489ad22fdf1f8c559b2472815c09c7"),
arrayOf("s", "verified"), arrayOf("s", "valid"),
arrayOf("v", "valid"),
arrayOf("client", "attestr.xyz"), arrayOf("client", "attestr.xyz"),
), ),
) )
@@ -161,19 +159,17 @@ fun RenderAttestation(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val validity = remember(noteEvent) { noteEvent.validity() }
val status = remember(noteEvent) { noteEvent.status() } val status = remember(noteEvent) { noteEvent.status() }
val validFrom = remember(noteEvent) { noteEvent.validFrom() } val validFrom = remember(noteEvent) { noteEvent.validFrom() }
val validTo = remember(noteEvent) { noteEvent.validTo() } val validTo = remember(noteEvent) { noteEvent.validTo() }
val content = remember(noteEvent) { noteEvent.content.ifBlank { null } } val content = remember(noteEvent) { noteEvent.content.ifBlank { null } }
val statusColor = remember(status, validity) { attestationColor(status, validity) } val statusColor = remember(status) { attestationColor(status) }
val statusIcon = remember(status, validity) { attestationIcon(status, validity) } val statusIcon = remember(status) { attestationIcon(status) }
val statusLabel = attestationStatusLabel(status, validity) val statusLabel = attestationStatusLabel(status)
val aboutAddress = remember(noteEvent) { noteEvent.assertionAddress() } val aboutAddress = remember(noteEvent) { noteEvent.assertionAddress() }
val aboutEvent = remember(noteEvent) { noteEvent.assertionEventId() } val aboutEvent = remember(noteEvent) { noteEvent.assertionEventId() }
val aboutPubkey = remember(noteEvent) { noteEvent.assertionPubkey() }
Column( Column(
modifier = modifier =
@@ -257,13 +253,6 @@ fun RenderAttestation(
) )
} }
} }
} else if (aboutPubkey != null) {
LoadUser(aboutPubkey, accountViewModel) {
if (it != null) {
Spacer(modifier = DoubleVertSpacer)
UserCompose(it, accountViewModel = accountViewModel, nav = nav)
}
}
} }
} }
@@ -592,47 +581,35 @@ fun RenderAttestorProficiency(
private fun attestationColor( private fun attestationColor(
status: AttestationStatus?, status: AttestationStatus?,
validity: Validity?,
): Color = ): Color =
when { when {
validity == Validity.INVALID -> Color(0xFFB71C1C) status == AttestationStatus.INVALID -> Color(0xFFB71C1C)
validity == Validity.VALID -> Color(0xFF2E7D32) status == AttestationStatus.VALID -> Color(0xFF2E7D32)
status == AttestationStatus.REVOKED -> Color(0xFFB21CB7) status == AttestationStatus.REVOKED -> Color(0xFFB21CB7)
status == AttestationStatus.REJECTED -> Color(0xFFB71C1C)
status == AttestationStatus.VERIFIED -> Color(0xFF2E7D32)
status == AttestationStatus.VERIFYING -> Color(0xFF173CF5) status == AttestationStatus.VERIFYING -> Color(0xFF173CF5)
status == AttestationStatus.ACCEPTED -> Color(0xFF15C0C0)
else -> Color(0xFF757575) else -> Color(0xFF757575)
} }
private fun attestationIcon( private fun attestationIcon(
status: AttestationStatus?, status: AttestationStatus?,
validity: Validity?,
): ImageVector = ): ImageVector =
when { when {
validity == Validity.INVALID -> Icons.Default.Close status == AttestationStatus.INVALID -> Icons.Default.Close
validity == Validity.VALID -> Icons.Default.CheckCircle status == AttestationStatus.VALID -> Icons.Default.CheckCircle
status == AttestationStatus.REVOKED -> Icons.Default.RemoveDone status == AttestationStatus.REVOKED -> Icons.Default.RemoveDone
status == AttestationStatus.REJECTED -> Icons.Default.Delete
status == AttestationStatus.VERIFIED -> Icons.Default.Approval
status == AttestationStatus.VERIFYING -> Icons.Default.HourglassTop status == AttestationStatus.VERIFYING -> Icons.Default.HourglassTop
status == AttestationStatus.ACCEPTED -> Icons.Default.HourglassEmpty
else -> Icons.Default.ErrorOutline else -> Icons.Default.ErrorOutline
} }
@Composable @Composable
private fun attestationStatusLabel( private fun attestationStatusLabel(
status: AttestationStatus?, status: AttestationStatus?,
validity: Validity?,
): String = ): String =
when { when {
validity == Validity.INVALID -> stringRes(R.string.attestation_invalid) status == AttestationStatus.INVALID -> stringRes(R.string.attestation_invalid)
validity == Validity.VALID -> stringRes(R.string.attestation_valid) status == AttestationStatus.VALID -> stringRes(R.string.attestation_valid)
status == AttestationStatus.REVOKED -> stringRes(R.string.attestation_status_revoked) status == AttestationStatus.REVOKED -> stringRes(R.string.attestation_status_revoked)
status == AttestationStatus.REJECTED -> stringRes(R.string.attestation_status_rejected)
status == AttestationStatus.VERIFIED -> stringRes(R.string.attestation_status_verified)
status == AttestationStatus.VERIFYING -> stringRes(R.string.attestation_status_verifying) status == AttestationStatus.VERIFYING -> stringRes(R.string.attestation_status_verifying)
status == AttestationStatus.ACCEPTED -> stringRes(R.string.attestation_status_accepted)
else -> stringRes(R.string.attestation) else -> stringRes(R.string.attestation)
} }
@@ -22,7 +22,6 @@ package com.vitorpamplona.quartz.experimental.attestations.attestation
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
@@ -32,17 +31,14 @@ import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.serialization.json.JsonNull.content
@Immutable @Immutable
class AttestationEvent( class AttestationEvent(
@@ -54,8 +50,7 @@ class AttestationEvent(
sig: HexKey, sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig), ) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
EventHintProvider, EventHintProvider,
AddressHintProvider, AddressHintProvider {
PubKeyHintProvider {
override fun eventHints(): List<EventIdHint> = tags.mapNotNull(ETag::parseAsHint) override fun eventHints(): List<EventIdHint> = tags.mapNotNull(ETag::parseAsHint)
override fun linkedEventIds(): List<HexKey> = tags.mapNotNull(ETag::parseId) override fun linkedEventIds(): List<HexKey> = tags.mapNotNull(ETag::parseId)
@@ -64,12 +59,6 @@ class AttestationEvent(
override fun linkedAddressIds(): List<String> = tags.mapNotNull(ATag::parseAddressId) override fun linkedAddressIds(): List<String> = tags.mapNotNull(ATag::parseAddressId)
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
fun validity() = tags.validity()
fun status() = tags.status() fun status() = tags.status()
fun validFrom() = tags.validFrom() fun validFrom() = tags.validFrom()
@@ -94,10 +83,6 @@ class AttestationEvent(
fun assertionETag() = tags.firstNotNullOfOrNull(ETag::parse) fun assertionETag() = tags.firstNotNullOfOrNull(ETag::parse)
fun assertionPubkey() = tags.firstNotNullOfOrNull(PTag::parseKey)
fun assertionPTag() = tags.firstNotNullOfOrNull(PTag::parse)
companion object { companion object {
const val KIND = 31871 const val KIND = 31871
const val ALT_DESCRIPTION = "Attestation" const val ALT_DESCRIPTION = "Attestation"
@@ -106,7 +91,6 @@ class AttestationEvent(
dTagId: String, dTagId: String,
about: EventHintBundle<Event>, about: EventHintBundle<Event>,
content: String = "", content: String = "",
validity: Validity? = null,
status: AttestationStatus? = null, status: AttestationStatus? = null,
validFrom: Long? = null, validFrom: Long? = null,
validTo: Long? = null, validTo: Long? = null,
@@ -117,7 +101,6 @@ class AttestationEvent(
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
dTag(dTagId) dTag(dTagId)
about(about) about(about)
validity?.let { validity(it) }
status?.let { status(it) } status?.let { status(it) }
validFrom?.let { validFrom(it) } validFrom?.let { validFrom(it) }
validTo?.let { validTo(it) } validTo?.let { validTo(it) }
@@ -129,7 +112,6 @@ class AttestationEvent(
dTagId: String, dTagId: String,
about: EventHintBundle<BaseReplaceableEvent>, about: EventHintBundle<BaseReplaceableEvent>,
content: String = "", content: String = "",
validity: Validity? = null,
status: AttestationStatus? = null, status: AttestationStatus? = null,
validFrom: Long? = null, validFrom: Long? = null,
validTo: Long? = null, validTo: Long? = null,
@@ -140,7 +122,6 @@ class AttestationEvent(
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
dTag(dTagId) dTag(dTagId)
aboutReplaceable(about) aboutReplaceable(about)
validity?.let { validity(it) }
status?.let { status(it) } status?.let { status(it) }
validFrom?.let { validFrom(it) } validFrom?.let { validFrom(it) }
validTo?.let { validTo(it) } validTo?.let { validTo(it) }
@@ -152,7 +133,6 @@ class AttestationEvent(
dTagId: String, dTagId: String,
about: EventHintBundle<BaseAddressableEvent>, about: EventHintBundle<BaseAddressableEvent>,
content: String = "", content: String = "",
validity: Validity? = null,
status: AttestationStatus? = null, status: AttestationStatus? = null,
validFrom: Long? = null, validFrom: Long? = null,
validTo: Long? = null, validTo: Long? = null,
@@ -163,7 +143,6 @@ class AttestationEvent(
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
dTag(dTagId) dTag(dTagId)
aboutAddressable(about) aboutAddressable(about)
validity?.let { validity(it) }
status?.let { status(it) } status?.let { status(it) }
validFrom?.let { validFrom(it) } validFrom?.let { validFrom(it) }
validTo?.let { validTo(it) } validTo?.let { validTo(it) }
@@ -25,8 +25,6 @@ import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Reque
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.StatusTag import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.StatusTag
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidFromTag import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidFromTag
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidToTag import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidToTag
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidityTag
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
@@ -36,8 +34,6 @@ import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
fun TagArrayBuilder<AttestationEvent>.validity(validity: Validity) = addUnique(ValidityTag.assemble(validity))
fun TagArrayBuilder<AttestationEvent>.status(status: AttestationStatus) = addUnique(StatusTag.assemble(status)) fun TagArrayBuilder<AttestationEvent>.status(status: AttestationStatus) = addUnique(StatusTag.assemble(status))
fun TagArrayBuilder<AttestationEvent>.validFrom(timestamp: Long) = addUnique(ValidFromTag.assemble(timestamp)) fun TagArrayBuilder<AttestationEvent>.validFrom(timestamp: Long) = addUnique(ValidFromTag.assemble(timestamp))
@@ -24,11 +24,8 @@ import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Reque
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.StatusTag import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.StatusTag
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidFromTag import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidFromTag
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidToTag import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidToTag
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidityTag
import com.vitorpamplona.quartz.nip01Core.core.TagArray import com.vitorpamplona.quartz.nip01Core.core.TagArray
fun TagArray.validity() = firstNotNullOfOrNull(ValidityTag::parse)
fun TagArray.status() = firstNotNullOfOrNull(StatusTag::parse) fun TagArray.status() = firstNotNullOfOrNull(StatusTag::parse)
fun TagArray.validFrom() = firstNotNullOfOrNull(ValidFromTag::parse) fun TagArray.validFrom() = firstNotNullOfOrNull(ValidFromTag::parse)
@@ -26,10 +26,9 @@ import com.vitorpamplona.quartz.utils.ensure
enum class AttestationStatus( enum class AttestationStatus(
val code: String, val code: String,
) { ) {
ACCEPTED("accepted"),
REJECTED("rejected"),
VERIFYING("verifying"), VERIFYING("verifying"),
VERIFIED("verified"), VALID("valid"),
INVALID("invalid"),
REVOKED("revoked"), REVOKED("revoked"),
} }
@@ -45,10 +44,9 @@ class StatusTag {
ensure(tag[1].isNotEmpty()) { return null } ensure(tag[1].isNotEmpty()) { return null }
return when (tag[1]) { return when (tag[1]) {
AttestationStatus.ACCEPTED.code -> AttestationStatus.ACCEPTED
AttestationStatus.REJECTED.code -> AttestationStatus.REJECTED
AttestationStatus.VERIFYING.code -> AttestationStatus.VERIFYING AttestationStatus.VERIFYING.code -> AttestationStatus.VERIFYING
AttestationStatus.VERIFIED.code -> AttestationStatus.VERIFIED AttestationStatus.VALID.code -> AttestationStatus.VALID
AttestationStatus.INVALID.code -> AttestationStatus.INVALID
AttestationStatus.REVOKED.code -> AttestationStatus.REVOKED AttestationStatus.REVOKED.code -> AttestationStatus.REVOKED
else -> null else -> null
} }
@@ -1,53 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.experimental.attestations.attestation.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.ensure
enum class Validity(
val code: String,
) {
VALID("valid"),
INVALID("invalid"),
}
class ValidityTag {
companion object {
const val TAG_NAME = "v"
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun parse(tag: Array<String>): Validity? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].isNotEmpty()) { return null }
return when (tag[1]) {
Validity.VALID.code -> Validity.VALID
Validity.INVALID.code -> Validity.INVALID
else -> null
}
}
fun assemble(validity: Validity) = arrayOf(TAG_NAME, validity.code)
}
}
@@ -40,7 +40,7 @@ class AttestorProficiencyEvent(
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { ) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
fun kinds() = tags.kinds() fun kinds() = tags.kinds()
fun description() = tags.description() fun description() = content.ifBlank { null }
companion object { companion object {
const val KIND = 11871 const val KIND = 11871
@@ -51,10 +51,9 @@ class AttestorProficiencyEvent(
description: String? = null, description: String? = null,
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<AttestorProficiencyEvent>.() -> Unit = {}, initializer: TagArrayBuilder<AttestorProficiencyEvent>.() -> Unit = {},
) = eventTemplate(KIND, "", createdAt) { ) = eventTemplate(KIND, description ?: "", createdAt) {
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
kinds(kinds) kinds(kinds)
description?.let { desc(it) }
initializer() initializer()
} }
} }
@@ -20,11 +20,8 @@
*/ */
package com.vitorpamplona.quartz.experimental.attestations.proficiency package com.vitorpamplona.quartz.experimental.attestations.proficiency
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.DescriptionTag
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag
import com.vitorpamplona.quartz.nip01Core.core.Kind import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
fun TagArrayBuilder<AttestorProficiencyEvent>.kinds(kinds: List<Kind>) = addAll(KindTag.assemble(kinds)) fun TagArrayBuilder<AttestorProficiencyEvent>.kinds(kinds: List<Kind>) = addAll(KindTag.assemble(kinds))
fun TagArrayBuilder<AttestorProficiencyEvent>.desc(description: String) = addUnique(DescriptionTag.assemble(description))
@@ -20,10 +20,7 @@
*/ */
package com.vitorpamplona.quartz.experimental.attestations.proficiency package com.vitorpamplona.quartz.experimental.attestations.proficiency
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.DescriptionTag
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag
import com.vitorpamplona.quartz.nip01Core.core.TagArray import com.vitorpamplona.quartz.nip01Core.core.TagArray
fun TagArray.kinds() = mapNotNull(KindTag::parse) fun TagArray.kinds() = mapNotNull(KindTag::parse)
fun TagArray.description() = firstNotNullOfOrNull(DescriptionTag::parse)
@@ -41,7 +41,7 @@ class AttestorRecommendationEvent(
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { ) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
fun kinds() = tags.kinds() fun kinds() = tags.kinds()
fun description() = tags.description() fun description() = content.ifBlank { null }
companion object { companion object {
const val KIND = 31873 const val KIND = 31873
@@ -53,11 +53,10 @@ class AttestorRecommendationEvent(
description: String? = null, description: String? = null,
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<AttestorRecommendationEvent>.() -> Unit = {}, initializer: TagArrayBuilder<AttestorRecommendationEvent>.() -> Unit = {},
) = eventTemplate(KIND, "", createdAt) { ) = eventTemplate(KIND, description ?: "", createdAt) {
alt(ALT_DESCRIPTION) alt(ALT_DESCRIPTION)
dTag(attestorPubKey) dTag(attestorPubKey)
kinds(kinds) kinds(kinds)
description?.let { desc(it) }
initializer() initializer()
} }
} }
@@ -20,11 +20,8 @@
*/ */
package com.vitorpamplona.quartz.experimental.attestations.recommendation package com.vitorpamplona.quartz.experimental.attestations.recommendation
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.DescriptionTag
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag
import com.vitorpamplona.quartz.nip01Core.core.Kind import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
fun TagArrayBuilder<AttestorRecommendationEvent>.kinds(kinds: List<Kind>) = addAll(KindTag.assemble(kinds)) fun TagArrayBuilder<AttestorRecommendationEvent>.kinds(kinds: List<Kind>) = addAll(KindTag.assemble(kinds))
fun TagArrayBuilder<AttestorRecommendationEvent>.desc(description: String) = addUnique(DescriptionTag.assemble(description))
@@ -20,10 +20,7 @@
*/ */
package com.vitorpamplona.quartz.experimental.attestations.recommendation package com.vitorpamplona.quartz.experimental.attestations.recommendation
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.DescriptionTag
import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag import com.vitorpamplona.quartz.experimental.attestations.recommendation.tags.KindTag
import com.vitorpamplona.quartz.nip01Core.core.TagArray import com.vitorpamplona.quartz.nip01Core.core.TagArray
fun TagArray.kinds() = mapNotNull(KindTag::parse) fun TagArray.kinds() = mapNotNull(KindTag::parse)
fun TagArray.description() = firstNotNullOfOrNull(DescriptionTag::parse)
@@ -1,41 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.experimental.attestations.recommendation.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.ensure
class DescriptionTag {
companion object {
const val TAG_NAME = "desc"
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun parse(tag: Array<String>): String? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
fun assemble(description: String) = arrayOf(TAG_NAME, description)
}
}