Merge pull request #2074 from vitorpamplona/claude/add-ephemeral-giftwrap-event-PiIXY
Add support for NIP-59 Ephemeral Gift Wrap events (kind 21059)
This commit is contained in:
+2
-1
@@ -24,6 +24,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ fun filterGiftWrapsToPubkey(
|
|||||||
relay = relay,
|
relay = relay,
|
||||||
filter =
|
filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(GiftWrapEvent.KIND),
|
kinds = listOf(GiftWrapEvent.KIND, EphemeralGiftWrapEvent.KIND),
|
||||||
tags = mapOf("p" to listOf(pubkey)),
|
tags = mapOf("p" to listOf(pubkey)),
|
||||||
since = since?.minus(TimeUtils.twoDays()),
|
since = since?.minus(TimeUtils.twoDays()),
|
||||||
),
|
),
|
||||||
|
|||||||
+3
-1
@@ -238,6 +238,7 @@ import com.vitorpamplona.quartz.nip58Badges.award.BadgeAwardEvent
|
|||||||
import com.vitorpamplona.quartz.nip58Badges.definition.BadgeDefinitionEvent
|
import com.vitorpamplona.quartz.nip58Badges.definition.BadgeDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.nip58Badges.profile.ProfileBadgesEvent
|
import com.vitorpamplona.quartz.nip58Badges.profile.ProfileBadgesEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip62RequestToVanish.RequestToVanishEvent
|
import com.vitorpamplona.quartz.nip62RequestToVanish.RequestToVanishEvent
|
||||||
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
|
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
|
||||||
@@ -615,6 +616,7 @@ fun kindDisplayName(kind: Int): Int =
|
|||||||
GenericRepostEvent.KIND -> R.string.kind_reposts_16
|
GenericRepostEvent.KIND -> R.string.kind_reposts_16
|
||||||
GeohashListEvent.KIND -> R.string.kind_geohash_follows
|
GeohashListEvent.KIND -> R.string.kind_geohash_follows
|
||||||
GiftWrapEvent.KIND -> R.string.kind_gift_wraps
|
GiftWrapEvent.KIND -> R.string.kind_gift_wraps
|
||||||
|
EphemeralGiftWrapEvent.KIND -> R.string.kind_gift_wraps
|
||||||
GitIssueEvent.KIND -> R.string.kind_git_issue
|
GitIssueEvent.KIND -> R.string.kind_git_issue
|
||||||
GitPatchEvent.KIND -> R.string.kind_git_patch
|
GitPatchEvent.KIND -> R.string.kind_git_patch
|
||||||
GitRepositoryEvent.KIND -> R.string.kind_git_repo
|
GitRepositoryEvent.KIND -> R.string.kind_git_repo
|
||||||
@@ -693,7 +695,7 @@ fun kindDisplayName(kind: Int): Int =
|
|||||||
|
|
||||||
val posts = setOf(0, 1, 6, 7, 16, 30023)
|
val posts = setOf(0, 1, 6, 7, 16, 30023)
|
||||||
val settings = setOf(3, 10002, 10000, 10001, 10003, 10004, 30000)
|
val settings = setOf(3, 10002, 10000, 10001, 10003, 10004, 30000)
|
||||||
val dms = setOf(4, 1059, 10050)
|
val dms = setOf(4, GiftWrapEvent.KIND, EphemeralGiftWrapEvent.KIND, 10050)
|
||||||
val zaps = setOf(9734, 9735, 9041, 17375, 23194, 23195)
|
val zaps = setOf(9734, 9735, 9041, 17375, 23194, 23195)
|
||||||
val reports = setOf(ReportEvent.KIND, MuteListEvent.KIND, DeletionEvent.KIND, RequestToVanishEvent.KIND)
|
val reports = setOf(ReportEvent.KIND, MuteListEvent.KIND, DeletionEvent.KIND, RequestToVanishEvent.KIND)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.CountFilter
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@@ -43,7 +44,7 @@ class DMRelayListViewModel : BasicRelaySetupInfoModel() {
|
|||||||
label = R.string.dms,
|
label = R.string.dms,
|
||||||
filter =
|
filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(GiftWrapEvent.KIND, PrivateDmEvent.KIND),
|
kinds = listOf(GiftWrapEvent.KIND, EphemeralGiftWrapEvent.KIND, PrivateDmEvent.KIND),
|
||||||
tags = mapOf("p" to listOf(account.pubKey)),
|
tags = mapOf("p" to listOf(account.pubKey)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+4
-2
@@ -38,6 +38,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -55,7 +57,7 @@ import kotlin.coroutines.cancellation.CancellationException
|
|||||||
* Syncs the user's events across all known relays:
|
* Syncs the user's events across all known relays:
|
||||||
* 1. Downloads all events authored by the user and sends them to their outbox relays.
|
* 1. Downloads all events authored by the user and sends them to their outbox relays.
|
||||||
* 2. Downloads all events that p-tag the user (non-DM) and sends them to their inbox relays.
|
* 2. Downloads all events that p-tag the user (non-DM) and sends them to their inbox relays.
|
||||||
* 3. Downloads kind-4 and kind-1059 events that p-tag the user and sends them to DM relays.
|
* 3. Downloads kind-4, kind-1059 and kind-21059 events that p-tag the user and sends them to DM relays.
|
||||||
*
|
*
|
||||||
* Up to [MAX_CONCURRENT_RELAYS] relays are queried in parallel. As soon as one relay is fully
|
* Up to [MAX_CONCURRENT_RELAYS] relays are queried in parallel. As soon as one relay is fully
|
||||||
* exhausted (all pages retrieved) the next relay from the list starts immediately, keeping the
|
* exhausted (all pages retrieved) the next relay from the list starts immediately, keeping the
|
||||||
@@ -512,7 +514,7 @@ class EventSync(
|
|||||||
onEvent = { event, sourceRelay ->
|
onEvent = { event, sourceRelay ->
|
||||||
val isMyEvent = event.pubKey == myPubKey
|
val isMyEvent = event.pubKey == myPubKey
|
||||||
val mentionsMe = event.tags.isTaggedUser(myPubKey)
|
val mentionsMe = event.tags.isTaggedUser(myPubKey)
|
||||||
val isDmKind = event.kind == 4 || event.kind == 1059
|
val isDmKind = event.kind == 4 || event.kind == GiftWrapEvent.KIND || event.kind == EphemeralGiftWrapEvent.KIND
|
||||||
|
|
||||||
val live = liveActivity.value
|
val live = liveActivity.value
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -21,6 +21,8 @@
|
|||||||
package com.vitorpamplona.amethyst.desktop.subscriptions
|
package com.vitorpamplona.amethyst.desktop.subscriptions
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe builders for common Nostr filter patterns.
|
* Type-safe builders for common Nostr filter patterns.
|
||||||
@@ -540,7 +542,7 @@ object FilterBuilders {
|
|||||||
since: Long? = null,
|
since: Long? = null,
|
||||||
): Filter =
|
): Filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(1059), // GiftWrapEvent.KIND
|
kinds = listOf(GiftWrapEvent.KIND, EphemeralGiftWrapEvent.KIND),
|
||||||
tags = mapOf("p" to listOf(pubKeyHex)),
|
tags = mapOf("p" to listOf(pubKeyHex)),
|
||||||
since = since,
|
since = since,
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-1
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ object FilterDMs {
|
|||||||
since: Long? = null,
|
since: Long? = null,
|
||||||
): Filter =
|
): Filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(GiftWrapEvent.KIND),
|
kinds = listOf(GiftWrapEvent.KIND, EphemeralGiftWrapEvent.KIND),
|
||||||
tags = mapOf("p" to listOf(userPubKeyHex)),
|
tags = mapOf("p" to listOf(userPubKeyHex)),
|
||||||
since = since?.minus(TimeUtils.twoDays()),
|
since = since?.minus(TimeUtils.twoDays()),
|
||||||
)
|
)
|
||||||
|
|||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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.nip59Giftwrap.wraps
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||||
|
import com.vitorpamplona.quartz.nip40Expiration.ExpirationTag
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class EphemeralGiftWrapEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : GiftWrapEvent(id, pubKey, createdAt, tags, content, sig, KIND) {
|
||||||
|
override fun copyNoContent(): EphemeralGiftWrapEvent {
|
||||||
|
val copy =
|
||||||
|
EphemeralGiftWrapEvent(
|
||||||
|
id,
|
||||||
|
pubKey,
|
||||||
|
createdAt,
|
||||||
|
tags,
|
||||||
|
"",
|
||||||
|
sig,
|
||||||
|
)
|
||||||
|
|
||||||
|
copy.innerEventId = innerEventId
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 21059
|
||||||
|
const val ALT = "Ephemeral encrypted event"
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
event: Event,
|
||||||
|
recipientPubKey: HexKey,
|
||||||
|
expirationDelta: Long? = null,
|
||||||
|
createdAt: Long = TimeUtils.randomWithTwoDays(),
|
||||||
|
): EphemeralGiftWrapEvent {
|
||||||
|
val signer = NostrSignerSync(KeyPair())
|
||||||
|
|
||||||
|
val tags =
|
||||||
|
expirationDelta?.let {
|
||||||
|
arrayOf(
|
||||||
|
PTag.assemble(recipientPubKey, null),
|
||||||
|
ExpirationTag.assemble(createdAt + it + TimeUtils.twoDays()),
|
||||||
|
)
|
||||||
|
} ?: arrayOf(
|
||||||
|
PTag.assemble(recipientPubKey, null),
|
||||||
|
)
|
||||||
|
|
||||||
|
return signer.sign(
|
||||||
|
createdAt = createdAt,
|
||||||
|
kind = KIND,
|
||||||
|
tags = tags,
|
||||||
|
content = signer.nip44Encrypt(event.toJson(), recipientPubKey),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-3
@@ -36,19 +36,20 @@ import com.vitorpamplona.quartz.utils.Log
|
|||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class GiftWrapEvent(
|
open class GiftWrapEvent(
|
||||||
id: HexKey,
|
id: HexKey,
|
||||||
pubKey: HexKey,
|
pubKey: HexKey,
|
||||||
createdAt: Long,
|
createdAt: Long,
|
||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
kind: Int = KIND,
|
||||||
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
@kotlinx.serialization.Transient
|
@kotlinx.serialization.Transient
|
||||||
@kotlin.jvm.Transient
|
@kotlin.jvm.Transient
|
||||||
var innerEventId: HexKey? = null
|
var innerEventId: HexKey? = null
|
||||||
|
|
||||||
fun copyNoContent(): GiftWrapEvent {
|
open fun copyNoContent(): GiftWrapEvent {
|
||||||
val copy =
|
val copy =
|
||||||
GiftWrapEvent(
|
GiftWrapEvent(
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ import com.vitorpamplona.quartz.nip58Badges.award.BadgeAwardEvent
|
|||||||
import com.vitorpamplona.quartz.nip58Badges.definition.BadgeDefinitionEvent
|
import com.vitorpamplona.quartz.nip58Badges.definition.BadgeDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.nip58Badges.profile.ProfileBadgesEvent
|
import com.vitorpamplona.quartz.nip58Badges.profile.ProfileBadgesEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.EphemeralGiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip5aStaticWebsites.NamedSiteEvent
|
import com.vitorpamplona.quartz.nip5aStaticWebsites.NamedSiteEvent
|
||||||
import com.vitorpamplona.quartz.nip5aStaticWebsites.RootSiteEvent
|
import com.vitorpamplona.quartz.nip5aStaticWebsites.RootSiteEvent
|
||||||
@@ -384,6 +385,7 @@ class EventFactory {
|
|||||||
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GeohashListEvent.KIND -> GeohashListEvent(id, pubKey, createdAt, tags, content, sig)
|
GeohashListEvent.KIND -> GeohashListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
EphemeralGiftWrapEvent.KIND -> EphemeralGiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GitAuthorListEvent.KIND -> GitAuthorListEvent(id, pubKey, createdAt, tags, content, sig)
|
GitAuthorListEvent.KIND -> GitAuthorListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GitRepositoryListEvent.KIND -> GitRepositoryListEvent(id, pubKey, createdAt, tags, content, sig)
|
GitRepositoryListEvent.KIND -> GitRepositoryListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
Reference in New Issue
Block a user