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
@@ -144,7 +144,7 @@ fun AppNavigation(
composableFromEndArgs<Route.RelayInfo> { RelayInformationScreen(it.url, accountViewModel, nav) } composableFromEndArgs<Route.RelayInfo> { RelayInformationScreen(it.url, accountViewModel, nav) }
composableFromEndArgs<Route.Community> { CommunityScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) } composableFromEndArgs<Route.Community> { CommunityScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
composableFromEndArgs<Route.Room> { ChatroomScreen(it.toKey(), it.message, it.replyId, it.draftId, accountViewModel, nav) } composableFromEndArgs<Route.Room> { ChatroomScreen(it.toKey(), it.message, it.replyId, it.draftId, it.expiresDays, accountViewModel, nav) }
composableFromEndArgs<Route.RoomByAuthor> { ChatroomByAuthorScreen(it.id, null, accountViewModel, nav) } composableFromEndArgs<Route.RoomByAuthor> { ChatroomByAuthorScreen(it.id, null, accountViewModel, nav) }
composableFromEndArgs<Route.PublicChatChannel> { composableFromEndArgs<Route.PublicChatChannel> {
@@ -138,6 +138,7 @@ fun routeToMessage(
draftMessage: String?, draftMessage: String?,
replyId: HexKey? = null, replyId: HexKey? = null,
draftId: HexKey? = null, draftId: HexKey? = null,
expiresDays: Int? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
): Route = ): Route =
routeToMessage( routeToMessage(
@@ -145,6 +146,7 @@ fun routeToMessage(
draftMessage, draftMessage,
replyId, replyId,
draftId, draftId,
expiresDays,
accountViewModel, accountViewModel,
) )
@@ -153,12 +155,14 @@ fun routeToMessage(
draftMessage: String?, draftMessage: String?,
replyId: HexKey? = null, replyId: HexKey? = null,
draftId: HexKey? = null, draftId: HexKey? = null,
expiresDays: Int? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) = routeToMessage( ) = routeToMessage(
ChatroomKey(users), ChatroomKey(users),
draftMessage, draftMessage,
replyId, replyId,
draftId, draftId,
expiresDays,
accountViewModel, accountViewModel,
) )
@@ -167,19 +171,21 @@ fun routeToMessage(
draftMessage: String?, draftMessage: String?,
replyId: HexKey? = null, replyId: HexKey? = null,
draftId: HexKey? = null, draftId: HexKey? = null,
expiresDays: Int? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
): Route = routeToMessage(room, draftMessage, replyId, draftId, accountViewModel.account) ): Route = routeToMessage(room, draftMessage, replyId, draftId, expiresDays, accountViewModel.account)
fun routeToMessage( fun routeToMessage(
room: ChatroomKey, room: ChatroomKey,
draftMessage: String? = null, draftMessage: String? = null,
replyId: HexKey? = null, replyId: HexKey? = null,
draftId: HexKey? = null, draftId: HexKey? = null,
expiresDays: Int? = null,
account: Account, account: Account,
): Route { ): Route {
account.chatroomList.getOrCreatePrivateChatroom(room) account.chatroomList.getOrCreatePrivateChatroom(room)
return Route.Room(room, draftMessage, replyId, draftId) return Route.Room(room, draftMessage, replyId, draftId, expiresDays)
} }
fun routeToMessage( fun routeToMessage(
@@ -187,8 +193,9 @@ fun routeToMessage(
draftMessage: String?, draftMessage: String?,
replyId: HexKey? = null, replyId: HexKey? = null,
draftId: HexKey? = null, draftId: HexKey? = null,
expiresDays: Int? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
): Route = routeToMessage(user.pubkeyHex, draftMessage, replyId, draftId, accountViewModel) ): Route = routeToMessage(user.pubkeyHex, draftMessage, replyId, draftId, expiresDays, accountViewModel)
fun routeFor(note: EphemeralChatChannel): Route = Route.EphemeralChat(note.roomId.id, note.roomId.relayUrl.url) fun routeFor(note: EphemeralChatChannel): Route = Route.EphemeralChat(note.roomId.id, note.roomId.relayUrl.url)
@@ -126,12 +126,14 @@ sealed class Route {
val message: String? = null, val message: String? = null,
val replyId: HexKey? = null, val replyId: HexKey? = null,
val draftId: HexKey? = null, val draftId: HexKey? = null,
val expiresDays: Int? = null,
) : Route() { ) : Route() {
constructor(key: ChatroomKey, message: String? = null, replyId: HexKey? = null, draftId: HexKey? = null) : this( constructor(key: ChatroomKey, message: String? = null, replyId: HexKey? = null, draftId: HexKey? = null, expiresDays: Int? = null) : this(
id = key.users.joinToString(","), id = key.users.joinToString(","),
message = message, message = message,
replyId = replyId, replyId = replyId,
draftId = draftId, draftId = draftId,
expiresDays = expiresDays,
) )
fun toKey(): ChatroomKey = ChatroomKey(id.split(",").toSet()) fun toKey(): ChatroomKey = ChatroomKey(id.split(",").toSet())
@@ -41,6 +41,7 @@ fun ChatroomScreen(
draftMessage: String? = null, draftMessage: String? = null,
replyToNote: HexKey? = null, replyToNote: HexKey? = null,
editFromDraft: HexKey? = null, editFromDraft: HexKey? = null,
expiresDays: Int? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
@@ -57,6 +58,7 @@ fun ChatroomScreen(
draftMessage = draftMessage, draftMessage = draftMessage,
replyToNote = replyToNote, replyToNote = replyToNote,
editFromDraft = editFromDraft, editFromDraft = editFromDraft,
expiresDays = expiresDays,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
@@ -52,6 +52,7 @@ fun ChatroomView(
draftMessage: String?, draftMessage: String?,
replyToNote: HexKey? = null, replyToNote: HexKey? = null,
editFromDraft: HexKey? = null, editFromDraft: HexKey? = null,
expiresDays: Int? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
@@ -79,7 +80,7 @@ fun ChatroomView(
} }
} }
if (editFromDraft != null) { if (editFromDraft != null) {
LaunchedEffect(replyToNote, newPostModel, accountViewModel) { LaunchedEffect(editFromDraft, newPostModel, accountViewModel) {
accountViewModel.checkGetOrCreateNote(editFromDraft) { accountViewModel.checkGetOrCreateNote(editFromDraft) {
if (it != null) { if (it != null) {
newPostModel.editFromDraft(it) newPostModel.editFromDraft(it)
@@ -87,6 +88,11 @@ fun ChatroomView(
} }
} }
} }
if (expiresDays != null) {
LaunchedEffect(expiresDays, newPostModel, accountViewModel) {
newPostModel.loadExpiration(expiresDays)
}
}
if (room.users.size == 1) { if (room.users.size == 1) {
// Activates NIP-17 if the user has DM relays // Activates NIP-17 if the user has DM relays
@@ -80,6 +80,7 @@ import com.vitorpamplona.quartz.nip30CustomEmoji.emojis
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitive
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.nip40Expiration.expiration
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplits import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplits
@@ -88,6 +89,7 @@ import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip92IMeta.imetas import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -168,6 +170,8 @@ class ChatNewMessageViewModel :
var wantsZapraiser by mutableStateOf(false) var wantsZapraiser by mutableStateOf(false)
override var zapRaiserAmount = mutableStateOf<Long?>(null) override var zapRaiserAmount = mutableStateOf<Long?>(null)
var expirationDays by mutableStateOf<Int?>(null)
// NIP17 Wrapped DMs / Group messages // NIP17 Wrapped DMs / Group messages
var nip17 by mutableStateOf(false) var nip17 by mutableStateOf(false)
@@ -267,6 +271,10 @@ class ChatNewMessageViewModel :
} }
} }
fun loadExpiration(expirationDays: Int) {
this.expirationDays = expirationDays
}
private fun loadFromDraft(draft: Note) { private fun loadFromDraft(draft: Note) {
val draftEvent = draft.event ?: return val draftEvent = draft.event ?: return
@@ -341,6 +349,8 @@ class ChatNewMessageViewModel :
} }
urlPreviews.update(message) urlPreviews.update(message)
expirationDays = draftEvent.expiration()?.let { (it / 86_400).toInt() }
iMetaAttachments.addAll(draftEvent.imetas()) iMetaAttachments.addAll(draftEvent.imetas())
requiresNIP17 = draftEvent is NIP17Group requiresNIP17 = draftEvent is NIP17Group
@@ -438,6 +448,8 @@ class ChatNewMessageViewModel :
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null
val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null
val expiration = expirationDays?.let { TimeUtils.now() + it.toLong() * 86_400 }
if (nip17 || room.users.size > 1 || replyTo.value?.event is NIP17Group) { if (nip17 || room.users.size > 1 || replyTo.value?.event is NIP17Group) {
val replyHint = replyTo.value?.toEventHint<BaseDMGroupEvent>() val replyHint = replyTo.value?.toEventHint<BaseDMGroupEvent>()
@@ -452,6 +464,7 @@ class ChatNewMessageViewModel :
localZapRaiserAmount?.let { zapraiser(it) } localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) } zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) } contentWarningReason?.let { contentWarning(it) }
expiration?.let { expiration(it) }
emojis(emojis) emojis(emojis)
imetas(usedAttachments) imetas(usedAttachments)
@@ -466,6 +479,7 @@ class ChatNewMessageViewModel :
localZapRaiserAmount?.let { zapraiser(it) } localZapRaiserAmount?.let { zapraiser(it) }
zapReceiver?.let { zapSplits(it) } zapReceiver?.let { zapSplits(it) }
contentWarningReason?.let { contentWarning(it) } contentWarningReason?.let { contentWarning(it) }
expiration?.let { expiration(it) }
emojis(emojis) emojis(emojis)
imetas(usedAttachments) imetas(usedAttachments)
@@ -487,7 +501,9 @@ class ChatNewMessageViewModel :
imetas = usedAttachments, imetas = usedAttachments,
replyingTo = replyTo.value?.toEId(), replyingTo = replyTo.value?.toEId(),
signer = accountViewModel.account.signer, signer = accountViewModel.account.signer,
) ) {
expiration?.let { expiration(it) }
}
if (draftTag != null) { if (draftTag != null) {
accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag, template) accountViewModel.account.createAndSendDraftIgnoreErrors(draftTag, template)
@@ -518,6 +534,7 @@ class ChatNewMessageViewModel :
subject = TextFieldValue("") subject = TextFieldValue("")
replyTo.value = null replyTo.value = null
expirationDays = null
wantsInvoice = false wantsInvoice = false
wantsZapraiser = false wantsZapraiser = false
@@ -194,7 +194,7 @@ fun NewGroupDMScreen(
accountViewModel.runIOCatching { accountViewModel.runIOCatching {
postViewModel.sendPostSync() postViewModel.sendPostSync()
postViewModel.room?.let { postViewModel.room?.let {
nav.nav(routeToMessage(it, null, null, null, accountViewModel)) nav.nav(routeToMessage(it, null, null, null, null, accountViewModel))
} }
} }
nav.popBack() nav.popBack()
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,6 +33,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -52,6 +55,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
import com.vitorpamplona.amethyst.ui.theme.Font12SP
import com.vitorpamplona.amethyst.ui.theme.PostKeyboard import com.vitorpamplona.amethyst.ui.theme.PostKeyboard
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
@@ -127,6 +131,18 @@ fun PrivateMessageEditFieldRow(
) )
} }
channelScreenModel.expirationDays?.let {
Row(Modifier.fillMaxWidth().padding(vertical = 2.dp), horizontalArrangement = Arrangement.Center) {
Text(
stringResource(R.string.this_message_will_disappear_in_days, it),
fontSize = Font12SP,
color = MaterialTheme.colorScheme.placeholderText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
EditField(channelScreenModel, onSendNewMessage, accountViewModel) EditField(channelScreenModel, onSendNewMessage, accountViewModel)
} }
} }
@@ -110,6 +110,7 @@ fun MessagesTwoPane(
draftMessage = it.message, draftMessage = it.message,
replyToNote = it.replyId, replyToNote = it.replyId,
editFromDraft = it.draftId, editFromDraft = it.draftId,
expiresDays = it.expiresDays,
nav = nav, nav = nav,
) )
} }
+1
View File
@@ -1249,4 +1249,5 @@
<string name="crashreport_found">Crash Report found</string> <string name="crashreport_found">Crash Report found</string>
<string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Would you like to send the recent crash report to Amethyst in a DM? No personal information will be shared</string> <string name="would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared">Would you like to send the recent crash report to Amethyst in a DM? No personal information will be shared</string>
<string name="crashreport_found_send">Send it</string> <string name="crashreport_found_send">Send it</string>
<string name="this_message_will_disappear_in_days">This message will disappear in %1$d days</string>
</resources> </resources>
@@ -29,9 +29,11 @@ import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEven
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
import com.vitorpamplona.quartz.nip40Expiration.expiration
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.utils.mapNotNullAsync import com.vitorpamplona.quartz.utils.mapNotNullAsync
import java.time.temporal.TemporalAdjusters.next
class NIP17Factory { class NIP17Factory {
data class Result( data class Result(
@@ -43,8 +45,17 @@ class NIP17Factory {
event: Event, event: Event,
to: Set<HexKey>, to: Set<HexKey>,
signer: NostrSigner, signer: NostrSigner,
): List<GiftWrapEvent> = ): List<GiftWrapEvent> {
mapNotNullAsync( val innerExpDelta =
event.expiration()?.let {
if (it > event.createdAt) {
it - event.createdAt
} else {
null
}
}
return mapNotNullAsync(
to.toList(), to.toList(),
) { next -> ) { next ->
GiftWrapEvent.create( GiftWrapEvent.create(
@@ -52,11 +63,14 @@ class NIP17Factory {
SealedRumorEvent.create( SealedRumorEvent.create(
event = event, event = event,
encryptTo = next, encryptTo = next,
expirationDelta = innerExpDelta,
signer = signer, signer = signer,
), ),
recipientPubKey = next, recipientPubKey = next,
expirationDelta = innerExpDelta,
) )
} }
}
suspend fun createMessageNIP17( suspend fun createMessageNIP17(
template: EventTemplate<ChatMessageEvent>, 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.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip40Expiration.ExpirationTag
import com.vitorpamplona.quartz.nip59Giftwrap.HostStub import com.vitorpamplona.quartz.nip59Giftwrap.HostStub
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
@@ -99,23 +100,33 @@ class SealedRumorEvent(
event: Event, event: Event,
encryptTo: HexKey, encryptTo: HexKey,
signer: NostrSigner, signer: NostrSigner,
expirationDelta: Long? = null,
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
): SealedRumorEvent { ): SealedRumorEvent {
val rumor = Rumor.create(event) val rumor = Rumor.create(event)
return create(rumor, encryptTo, signer, createdAt) return create(rumor, encryptTo, signer, expirationDelta, createdAt)
} }
suspend fun create( suspend fun create(
rumor: Rumor, rumor: Rumor,
encryptTo: HexKey, encryptTo: HexKey,
signer: NostrSigner, signer: NostrSigner,
expirationDelta: Long? = null,
createdAt: Long = TimeUtils.randomWithTwoDays(), createdAt: Long = TimeUtils.randomWithTwoDays(),
): SealedRumorEvent { ): SealedRumorEvent {
val msg = Rumor.toJson(rumor) 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( return signer.sign(
createdAt = createdAt, createdAt = createdAt,
kind = KIND, kind = KIND,
tags = emptyArray(), tags = tags,
content = signer.nip44Encrypt(msg, encryptTo), 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.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal 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.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip40Expiration.ExpirationTag
import com.vitorpamplona.quartz.nip59Giftwrap.HostStub import com.vitorpamplona.quartz.nip59Giftwrap.HostStub
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
@@ -103,13 +105,27 @@ class GiftWrapEvent(
suspend fun create( suspend fun create(
event: Event, event: Event,
recipientPubKey: HexKey, recipientPubKey: HexKey,
expirationDelta: Long? = null,
createdAt: Long = TimeUtils.randomWithTwoDays(), createdAt: Long = TimeUtils.randomWithTwoDays(),
): GiftWrapEvent { ): GiftWrapEvent {
val signer = NostrSignerInternal(KeyPair()) // GiftWrap is always a random key 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( return signer.sign(
createdAt = createdAt, createdAt = createdAt,
kind = KIND, kind = KIND,
tags = arrayOf(arrayOf("p", recipientPubKey)), tags = tags,
content = signer.nip44Encrypt(event.toJson(), recipientPubKey), content = signer.nip44Encrypt(event.toJson(), recipientPubKey),
) )
} }