Adds support for expirations in DMs

This commit is contained in:
Vitor Pamplona
2025-08-29 09:34:26 -04:00
parent ecf530d512
commit 458a7c7377
13 changed files with 106 additions and 13 deletions
@@ -29,9 +29,11 @@ import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEven
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
import com.vitorpamplona.quartz.nip40Expiration.expiration
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.utils.mapNotNullAsync
import java.time.temporal.TemporalAdjusters.next
class NIP17Factory {
data class Result(
@@ -43,8 +45,17 @@ class NIP17Factory {
event: Event,
to: Set<HexKey>,
signer: NostrSigner,
): List<GiftWrapEvent> =
mapNotNullAsync(
): List<GiftWrapEvent> {
val innerExpDelta =
event.expiration()?.let {
if (it > event.createdAt) {
it - event.createdAt
} else {
null
}
}
return mapNotNullAsync(
to.toList(),
) { next ->
GiftWrapEvent.create(
@@ -52,11 +63,14 @@ class NIP17Factory {
SealedRumorEvent.create(
event = event,
encryptTo = next,
expirationDelta = innerExpDelta,
signer = signer,
),
recipientPubKey = next,
expirationDelta = innerExpDelta,
)
}
}
suspend fun createMessageNIP17(
template: EventTemplate<ChatMessageEvent>,
@@ -25,6 +25,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip40Expiration.ExpirationTag
import com.vitorpamplona.quartz.nip59Giftwrap.HostStub
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
@@ -99,23 +100,33 @@ class SealedRumorEvent(
event: Event,
encryptTo: HexKey,
signer: NostrSigner,
expirationDelta: Long? = null,
createdAt: Long = TimeUtils.now(),
): SealedRumorEvent {
val rumor = Rumor.create(event)
return create(rumor, encryptTo, signer, createdAt)
return create(rumor, encryptTo, signer, expirationDelta, createdAt)
}
suspend fun create(
rumor: Rumor,
encryptTo: HexKey,
signer: NostrSigner,
expirationDelta: Long? = null,
createdAt: Long = TimeUtils.randomWithTwoDays(),
): SealedRumorEvent {
val msg = Rumor.toJson(rumor)
val tags =
expirationDelta?.let {
// minimum expiration is two days in the future due to the random created at
// this will make sure the even arrives and is not deleted because of the 2 days.
arrayOf(ExpirationTag.assemble(createdAt + it + TimeUtils.twoDays()))
} ?: emptyArray()
return signer.sign(
createdAt = createdAt,
kind = KIND,
tags = emptyArray(),
tags = tags,
content = signer.nip44Encrypt(msg, encryptTo),
)
}
@@ -28,7 +28,9 @@ import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip40Expiration.ExpirationTag
import com.vitorpamplona.quartz.nip59Giftwrap.HostStub
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -103,13 +105,27 @@ class GiftWrapEvent(
suspend fun create(
event: Event,
recipientPubKey: HexKey,
expirationDelta: Long? = null,
createdAt: Long = TimeUtils.randomWithTwoDays(),
): GiftWrapEvent {
val signer = NostrSignerInternal(KeyPair()) // GiftWrap is always a random key
val tags =
expirationDelta?.let {
// minimum expiration is two days in the future due to the random created at
// this will make sure the even arrives and is not deleted because of the 2 days.
arrayOf(
PTag.assemble(recipientPubKey, null),
ExpirationTag.assemble(createdAt + it + TimeUtils.twoDays()),
)
} ?: arrayOf(
PTag.assemble(recipientPubKey, null),
)
return signer.sign(
createdAt = createdAt,
kind = KIND,
tags = arrayOf(arrayOf("p", recipientPubKey)),
tags = tags,
content = signer.nip44Encrypt(event.toJson(), recipientPubKey),
)
}