diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index ffcfd11db..667a7507c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -56,6 +56,7 @@ import com.vitorpamplona.quartz.events.ClassifiedsEvent import com.vitorpamplona.quartz.events.Contact import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.DeletionEvent +import com.vitorpamplona.quartz.events.DraftEvent import com.vitorpamplona.quartz.events.EmojiPackEvent import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent import com.vitorpamplona.quartz.events.EmojiUrl @@ -1422,6 +1423,7 @@ class Account( relayList: List? = null, geohash: String? = null, nip94attachments: List? = null, + draftTag: String?, ) { if (!isWriteable()) return @@ -1445,20 +1447,28 @@ class Account( nip94attachments = nip94attachments, forkedFrom = forkedFrom, signer = signer, + isDraft = draftTag != null, ) { - Client.send(it, relayList = relayList) - LocalCache.justConsume(it, null) - - // broadcast replied notes - replyingTo?.let { - LocalCache.getNoteIfExists(replyingTo)?.event?.let { - Client.send(it, relayList = relayList) + if (draftTag != null) { + DraftEvent.create(draftTag, it, signer) { draftEvent -> + Client.send(draftEvent, relayList = relayList) + LocalCache.justConsume(draftEvent, null) } - } - replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } - addresses?.forEach { - LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { - Client.send(it, relayList = relayList) + } else { + Client.send(it, relayList = relayList) + LocalCache.justConsume(it, null) + + // broadcast replied notes + replyingTo?.let { + LocalCache.getNoteIfExists(replyingTo)?.event?.let { + Client.send(it, relayList = relayList) + } + } + replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } } + addresses?.forEach { + LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let { + Client.send(it, relayList = relayList) + } } } } @@ -2210,6 +2220,7 @@ class Account( fun cachedDecryptContent(note: Note): String? { val event = note.event + return if (event is PrivateDmEvent && isWriteable()) { event.cachedContentFor(signer) } else if (event is LnZapRequestEvent && event.isPrivateZap() && isWriteable()) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index f211e30f4..ee68d4585 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -61,6 +61,7 @@ import com.vitorpamplona.quartz.events.CommunityListEvent import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.DeletionEvent +import com.vitorpamplona.quartz.events.DraftEvent import com.vitorpamplona.quartz.events.EmojiPackEvent import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent import com.vitorpamplona.quartz.events.Event @@ -2042,6 +2043,13 @@ object LocalCache { } } + private fun consume( + event: DraftEvent, + relay: Relay?, + ) { + consumeBaseReplaceable(event, relay) + } + fun justConsume( event: Event, relay: Relay?, @@ -2079,6 +2087,7 @@ object LocalCache { } is ContactListEvent -> consume(event) is DeletionEvent -> consume(event) + is DraftEvent -> consume(event, relay) is EmojiPackEvent -> consume(event, relay) is EmojiPackSelectionEvent -> consume(event, relay) is SealedGossipEvent -> consume(event, relay) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 77f380b3c..b4523c9c6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -106,6 +106,7 @@ open class Note(val idHex: String) { var event: EventInterface? = null var author: User? = null var replyTo: List? = null + var draft: String? = null // These fields are updated every time an event related to this note is received. var replies = listOf() @@ -183,6 +184,17 @@ open class Note(val idHex: String) { open fun createdAt() = event?.createdAt() + fun updateDraft(id: String) { + draft = id + } + + fun isDraft(): Boolean { + draft?.let { + return it.isNotBlank() + } + return false + } + fun loadEvent( event: Event, author: User, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 0e49e30bf..77288c2d9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.quartz.events.CalendarRSVPEvent import com.vitorpamplona.quartz.events.CalendarTimeSlotEvent import com.vitorpamplona.quartz.events.ChannelMessageEvent import com.vitorpamplona.quartz.events.ContactListEvent +import com.vitorpamplona.quartz.events.DraftEvent import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.EventInterface @@ -229,6 +230,16 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { ) } + fun createDraftsFilter() = + TypedFilter( + types = COMMON_FEED_TYPES, + filter = + JsonFilter( + kinds = listOf(DraftEvent.KIND), + authors = listOf(account.userProfile().pubkeyHex), + ), + ) + fun createGiftWrapsToMeFilter() = TypedFilter( types = COMMON_FEED_TYPES, @@ -262,22 +273,38 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { checkNotInMainThread() if (LocalCache.justVerify(event)) { - if (event is GiftWrapEvent) { - // Avoid decrypting over and over again if the event already exist. - val note = LocalCache.getNoteIfExists(event.id) - if (note != null && relay.brief in note.relays) return + when (event) { + is DraftEvent -> { + // Avoid decrypting over and over again if the event already exist. + val note = LocalCache.getNoteIfExists(event.id) + if (note != null && relay.brief in note.relays) return - event.cachedGift(account.signer) { this.consume(it, relay) } - } + event.plainContent(account.signer) { + LocalCache.justConsume(it, relay) + val draftNote = LocalCache.getNoteIfExists(it.id) + draftNote?.updateDraft(event.id) + } + } - if (event is SealedGossipEvent) { - // Avoid decrypting over and over again if the event already exist. - val note = LocalCache.getNoteIfExists(event.id) - if (note != null && relay.brief in note.relays) return + is GiftWrapEvent -> { + // Avoid decrypting over and over again if the event already exist. + val note = LocalCache.getNoteIfExists(event.id) + if (note != null && relay.brief in note.relays) return - event.cachedGossip(account.signer) { LocalCache.justConsume(it, relay) } - } else { - LocalCache.justConsume(event, relay) + event.cachedGift(account.signer) { this.consume(it, relay) } + } + + is SealedGossipEvent -> { + // Avoid decrypting over and over again if the event already exist. + val note = LocalCache.getNoteIfExists(event.id) + if (note != null && relay.brief in note.relays) return + + event.cachedGossip(account.signer) { LocalCache.justConsume(it, relay) } + } + + else -> { + LocalCache.justConsume(event, relay) + } } } } @@ -328,6 +355,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { createAccountSettingsFilter(), createAccountLastPostsListFilter(), createOtherAccountsBaseFilter(), + createDraftsFilter(), ) .ifEmpty { null } } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 04a6aaaa2..747473bcf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -263,7 +263,7 @@ class Relay( val subscriptionId = msgArray.get(1).asText() val event = Event.fromJson(msgArray.get(2)) - // Log.w("Relay", "Relay onEVENT ${event.kind} $url, $subscriptionId ${msgArray.get(2)}") + Log.w("Relay", "Relay onEVENT ${event.kind} $url, $subscriptionId ${msgArray.get(2)}") listeners.forEach { it.onEvent( this@Relay, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index 285a4139c..fee300398 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -74,6 +74,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch +import java.util.UUID enum class UserSuggestionAnchor { MAIN_MESSAGE, @@ -83,6 +84,7 @@ enum class UserSuggestionAnchor { @Stable open class NewPostViewModel() : ViewModel() { + var draftTag: String = UUID.randomUUID().toString() var accountViewModel: AccountViewModel? = null var account: Account? = null var requiresNIP24: Boolean = false @@ -306,11 +308,17 @@ open class NewPostViewModel() : ViewModel() { } } - fun sendPost(relayList: List? = null) { - viewModelScope.launch(Dispatchers.IO) { innerSendPost(relayList) } + fun sendPost( + relayList: List? = null, + localDraft: String? = null, + ) { + viewModelScope.launch(Dispatchers.IO) { innerSendPost(relayList, localDraft) } } - suspend fun innerSendPost(relayList: List? = null) { + private suspend fun innerSendPost( + relayList: List? = null, + localDraft: String?, + ) { if (accountViewModel == null) { cancel() return @@ -555,11 +563,13 @@ open class NewPostViewModel() : ViewModel() { relayList = relayList, geohash = geoHash, nip94attachments = usedAttachments, + draftTag = draftTag, ) } } - - cancel() + if (localDraft == null) { + cancel() + } } fun upload( @@ -693,8 +703,8 @@ open class NewPostViewModel() : ViewModel() { pTags = pTags?.filter { it != userToRemove } } - open fun saveDraft(message: String) { - account?.let { LocalPreferences.saveDraft(message, originalNote?.idHex, it) } + open fun saveDraft() { + sendPost(localDraft = draftTag) } open fun loadDraft(): String? { @@ -708,9 +718,6 @@ open class NewPostViewModel() : ViewModel() { } open fun updateMessage(it: TextFieldValue) { - viewModelScope.launch(Dispatchers.IO) { - saveDraft(it.text) - } message = it urlPreview = findUrlInMessage() @@ -732,6 +739,10 @@ open class NewPostViewModel() : ViewModel() { userSuggestions = emptyList() } } + + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } open fun updateToUsers(it: TextFieldValue) { @@ -755,10 +766,16 @@ open class NewPostViewModel() : ViewModel() { userSuggestions = emptyList() } } + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } open fun updateSubject(it: TextFieldValue) { subject = it + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } open fun updateZapForwardTo(it: TextFieldValue) { @@ -785,6 +802,9 @@ open class NewPostViewModel() : ViewModel() { userSuggestions = emptyList() } } + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } open fun autocompleteWithUser(item: User) { @@ -800,9 +820,6 @@ open class NewPostViewModel() : ViewModel() { message.text.replaceRange(lastWordStart, it.end, wordToInsert), TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length), ) - viewModelScope.launch(Dispatchers.IO) { - saveDraft(message.text) - } } else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) { forwardZapTo.addItem(item) forwardZapToEditting = TextFieldValue("") @@ -833,6 +850,10 @@ open class NewPostViewModel() : ViewModel() { userSuggestionsMainMessage = null userSuggestions = emptyList() } + + viewModelScope.launch(Dispatchers.IO) { + saveDraft() + } } private fun newStateMapPollOptions(): SnapshotStateMap { @@ -902,8 +923,8 @@ open class NewPostViewModel() : ViewModel() { nip94attachments = nip94attachments + event message = message.insertUrlAtCursor(imageUrl) - saveDraft(message.text) urlPreview = findUrlInMessage() + saveDraft() } }, onError = { @@ -945,10 +966,10 @@ open class NewPostViewModel() : ViewModel() { note?.let { message = message.insertUrlAtCursor("nostr:" + it.toNEvent()) - saveDraft(message.text) } urlPreview = findUrlInMessage() + saveDraft() } }, onError = { @@ -969,6 +990,7 @@ open class NewPostViewModel() : ViewModel() { locUtil?.let { location = it.locationStateFlow.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() } + saveDraft() } viewModelScope.launch(Dispatchers.IO) { locUtil?.start() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt index 15d977db7..b66750632 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.quartz.events.ChannelMessageEvent +import com.vitorpamplona.quartz.events.DraftEvent import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.PeopleListEvent @@ -69,7 +70,8 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter>, content: String, sig: HexKey, -) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { - companion object { - const val KIND = 31234 +) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + @Transient private var decryptedContent: Map = mapOf() + + @Transient private var citedNotesCache: Set? = null + + fun replyTos(): List { + val oldStylePositional = tags.filter { it.size > 1 && it.size <= 3 && it[0] == "e" }.map { it[1] } + val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1) + val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1) + + val newStyleReplyTos = listOfNotNull(newStyleReply, newStyleRoot) + + return if (newStyleReplyTos.isNotEmpty()) { + newStyleReplyTos + } else { + oldStylePositional + } } - fun create( - dTag: String, - originalNote: EventInterface, - signer: NostrSigner, - createdAt: Long = TimeUtils.now(), - onReady: (DraftEvent) -> Unit, - ) { - val tags = - arrayOf( - arrayOf("d", dTag), - arrayOf("k", "${originalNote.kind()}"), - ) + fun findCitations(): Set { + citedNotesCache?.let { + return it + } - signer.nip44Encrypt(originalNote.content(), signer.pubKey) { - signer.sign(createdAt, KIND, tags, it, onReady) + val citations = mutableSetOf() + // Removes citations from replies: + val matcher = tagSearch.matcher(content) + while (matcher.find()) { + try { + val tag = matcher.group(1)?.let { tags[it.toInt()] } + if (tag != null && tag.size > 1 && tag[0] == "e") { + citations.add(tag[1]) + } + if (tag != null && tag.size > 1 && tag[0] == "a") { + citations.add(tag[1]) + } + } catch (e: Exception) { + } + } + + val matcher2 = Nip19Bech32.nip19regex.matcher(content) + while (matcher2.find()) { + val type = matcher2.group(2) // npub1 + val key = matcher2.group(3) // bech32 + val additionalChars = matcher2.group(4) // additional chars + + if (type != null) { + val parsed = Nip19Bech32.parseComponents(type, key, additionalChars)?.entity + + if (parsed != null) { + when (parsed) { + is Nip19Bech32.NEvent -> citations.add(parsed.hex) + is Nip19Bech32.NAddress -> citations.add(parsed.atag) + is Nip19Bech32.Note -> citations.add(parsed.hex) + is Nip19Bech32.NEmbed -> citations.add(parsed.event.id) + } + } + } + } + + citedNotesCache = citations + return citations + } + + fun tagsWithoutCitations(): List { + val repliesTo = replyTos() + val tagAddresses = + taggedAddresses().filter { + it.kind != CommunityDefinitionEvent.KIND && + it.kind != WikiNoteEvent.KIND + }.map { it.toTag() } + if (repliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList() + + val citations = findCitations() + + return if (citations.isEmpty()) { + repliesTo + tagAddresses + } else { + repliesTo.filter { it !in citations } + } + } + + fun cachedContentFor(): Event? { + return decryptedContent[dTag()] + } + + fun plainContent( + signer: NostrSigner, + onReady: (Event) -> Unit, + ) { + decryptedContent[dTag()]?.let { + onReady(it) + return + } + + signer.nip44Decrypt(content, signer.pubKey) { retVal -> + val event = runCatching { fromJson(retVal) }.getOrNull() ?: return@nip44Decrypt + decryptedContent = decryptedContent + Pair(dTag(), event) + + onReady(event) + } + } + + companion object { + const val KIND = 31234 + + fun create( + dTag: String, + originalNote: EventInterface, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (DraftEvent) -> Unit, + ) { + val tags = mutableListOf>() + tags.add(arrayOf("d", dTag)) + tags.add(arrayOf("k", "${originalNote.kind()}")) + tags.addAll(originalNote.tags().filter { it.size > 1 && it[0] == "e" }) + tags.addAll(originalNote.tags().filter { it.size > 1 && it[0] == "a" }) + + signer.nip44Encrypt(originalNote.toJson(), signer.pubKey) { encryptedContent -> + signer.sign(createdAt, KIND, tags.toTypedArray(), encryptedContent, onReady) + } } } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt index 306277d04..d923354c8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt @@ -79,6 +79,7 @@ class EventFactory { 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) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP24Factory.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP24Factory.kt index 5ff107a50..4c623152f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP24Factory.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP24Factory.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.quartz.events -import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.signers.NostrSigner @@ -155,49 +154,4 @@ class NIP24Factory { } } } - - fun createTextNoteNIP24( - msg: String, - to: List, - signer: NostrSigner, - replyTos: List? = null, - mentions: List? = null, - addresses: List?, - extraTags: List?, - zapReceiver: List? = null, - markAsSensitive: Boolean = false, - replyingTo: String?, - root: String?, - directMentions: Set, - zapRaiserAmount: Long? = null, - geohash: String? = null, - onReady: (Result) -> Unit, - ) { - val senderPublicKey = signer.pubKey - - TextNoteEvent.create( - msg = msg, - signer = signer, - replyTos = replyTos, - mentions = mentions, - zapReceiver = zapReceiver, - root = root, - extraTags = extraTags, - addresses = addresses, - directMentions = directMentions, - replyingTo = replyingTo, - markAsSensitive = markAsSensitive, - zapRaiserAmount = zapRaiserAmount, - geohash = geohash, - ) { senderMessage -> - createWraps(senderMessage, to.plus(senderPublicKey).toSet(), signer) { wraps -> - onReady( - Result( - msg = senderMessage, - wraps = wraps, - ), - ) - } - } - } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/TextNoteEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/TextNoteEvent.kt index 13f81f043..fece5f0a5 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/TextNoteEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/TextNoteEvent.kt @@ -60,6 +60,7 @@ class TextNoteEvent( forkedFrom: Event? = null, signer: NostrSigner, createdAt: Long = TimeUtils.now(), + isDraft: Boolean, onReady: (TextNoteEvent) -> Unit, ) { val tags = mutableListOf>() @@ -121,7 +122,7 @@ class TextNoteEvent( } } - signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady) + signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft) } } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSigner.kt b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSigner.kt index 5014db2f2..ea1c56aaf 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSigner.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSigner.kt @@ -32,6 +32,7 @@ abstract class NostrSigner(val pubKey: HexKey) { tags: Array>, content: String, onReady: (T) -> Unit, + isDraft: Boolean = false, ) abstract fun nip04Encrypt( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt index 4f34af999..30cf69b46 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt @@ -40,7 +40,13 @@ class NostrSignerExternal( tags: Array>, content: String, onReady: (T) -> Unit, + isDraft: Boolean, ) { + if (isDraft) { + unsignedEvent(createdAt, kind, tags, content, onReady) + return + } + val id = Event.generateId(pubKey, createdAt, kind, tags, content).toHexKey() val event = @@ -86,6 +92,28 @@ class NostrSignerExternal( } } + fun unsignedEvent( + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + onReady: (T) -> Unit, + ) { + val id = Event.generateId(pubKey, createdAt, kind, tags, content) + + onReady( + EventFactory.create( + id.toHexKey(), + pubKey, + createdAt, + kind, + tags, + content, + "", + ) as T, + ) + } + override fun nip04Encrypt( decryptedContent: String, toPublicKey: HexKey, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerInternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerInternal.kt index 2537287b5..0df60b80d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerInternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerInternal.kt @@ -38,9 +38,15 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH tags: Array>, content: String, onReady: (T) -> Unit, + isDraft: Boolean, ) { if (keyPair.privKey == null) return + if (isDraft) { + unsignedEvent(createdAt, kind, tags, content, onReady) + return + } + if (isUnsignedPrivateEvent(kind, tags)) { // this is a private zap signPrivateZap(createdAt, kind, tags, content, onReady) @@ -82,6 +88,30 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH ) } + fun unsignedEvent( + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + onReady: (T) -> Unit, + ) { + if (keyPair.privKey == null) return + + val id = Event.generateId(pubKey, createdAt, kind, tags, content) + + onReady( + EventFactory.create( + id.toHexKey(), + pubKey, + createdAt, + kind, + tags, + content, + "", + ) as T, + ) + } + override fun nip04Encrypt( decryptedContent: String, toPublicKey: HexKey,