mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 11:57:29 +01:00
Merge pull request #749 from greenart7c3/main
save a draft while you are typing the post
This commit is contained in:
@@ -60,6 +60,7 @@ class ChannelMessageEvent(
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
isDraft: Boolean,
|
||||
onReady: (ChannelMessageEvent) -> Unit,
|
||||
) {
|
||||
val tags =
|
||||
@@ -87,7 +88,7 @@ class ChannelMessageEvent(
|
||||
arrayOf("alt", ALT),
|
||||
)
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ class ChatMessageEvent(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
isDraft: Boolean,
|
||||
onReady: (ChatMessageEvent) -> Unit,
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
@@ -106,7 +107,7 @@ class ChatMessageEvent(
|
||||
}
|
||||
// tags.add(arrayOf("alt", alt))
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ class ClassifiedsEvent(
|
||||
nip94attachments: List<Event>? = null,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
isDraft: Boolean,
|
||||
onReady: (ClassifiedsEvent) -> Unit,
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
@@ -192,7 +193,7 @@ class ClassifiedsEvent(
|
||||
}
|
||||
tags.add(arrayOf("alt", ALT))
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Copyright (c) 2024 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.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class DraftEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
@Transient private var decryptedContent: Map<HexKey, Event> = mapOf()
|
||||
|
||||
@Transient private var citedNotesCache: Set<String>? = null
|
||||
|
||||
fun replyTos(): List<HexKey> {
|
||||
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 findCitations(): Set<HexKey> {
|
||||
citedNotesCache?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val citations = mutableSetOf<HexKey>()
|
||||
// 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<String> {
|
||||
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<Array<String>>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -94,6 +94,7 @@ class GitReplyEvent(
|
||||
forkedFrom: Event? = null,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
isDraft: Boolean,
|
||||
onReady: (GitReplyEvent) -> Unit,
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
@@ -156,7 +157,7 @@ class GitReplyEvent(
|
||||
}
|
||||
tags.add(arrayOf("alt", "a git issue reply"))
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ class LiveActivitiesChatMessageEvent(
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
isDraft: Boolean,
|
||||
onReady: (LiveActivitiesChatMessageEvent) -> Unit,
|
||||
) {
|
||||
val content = message
|
||||
@@ -98,7 +99,7 @@ class LiveActivitiesChatMessageEvent(
|
||||
}
|
||||
tags.add(arrayOf("alt", ALT))
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,6 +77,7 @@ class NIP24Factory {
|
||||
zapRaiserAmount: Long? = null,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
draftTag: String? = null,
|
||||
onReady: (Result) -> Unit,
|
||||
) {
|
||||
val senderPublicKey = signer.pubKey
|
||||
@@ -93,15 +93,25 @@ class NIP24Factory {
|
||||
markAsSensitive = markAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash,
|
||||
isDraft = draftTag != null,
|
||||
nip94attachments = nip94attachments,
|
||||
) { senderMessage ->
|
||||
createWraps(senderMessage, to.plus(senderPublicKey).toSet(), signer) { wraps ->
|
||||
if (draftTag != null) {
|
||||
onReady(
|
||||
Result(
|
||||
msg = senderMessage,
|
||||
wraps = wraps,
|
||||
wraps = listOf(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
createWraps(senderMessage, to.plus(senderPublicKey).toSet(), signer) { wraps ->
|
||||
onReady(
|
||||
Result(
|
||||
msg = senderMessage,
|
||||
wraps = wraps,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,49 +165,4 @@ class NIP24Factory {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createTextNoteNIP24(
|
||||
msg: String,
|
||||
to: List<HexKey>,
|
||||
signer: NostrSigner,
|
||||
replyTos: List<String>? = null,
|
||||
mentions: List<String>? = null,
|
||||
addresses: List<ATag>?,
|
||||
extraTags: List<String>?,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean = false,
|
||||
replyingTo: String?,
|
||||
root: String?,
|
||||
directMentions: Set<HexKey>,
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ class PollNoteEvent(
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
isDraft: Boolean,
|
||||
onReady: (PollNoteEvent) -> Unit,
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
@@ -112,7 +113,7 @@ class PollNoteEvent(
|
||||
}
|
||||
tags.add(arrayOf("alt", ALT))
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ class PrivateDmEvent(
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null,
|
||||
nip94attachments: List<FileHeaderEvent>? = null,
|
||||
isDraft: Boolean,
|
||||
onReady: (PrivateDmEvent) -> Unit,
|
||||
) {
|
||||
var message = msg
|
||||
@@ -167,7 +168,7 @@ class PrivateDmEvent(
|
||||
tags.add(arrayOf("alt", ALT))
|
||||
|
||||
signer.nip04Encrypt(message, recipientPubKey) { content ->
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class TextNoteEvent(
|
||||
forkedFrom: Event? = null,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
isDraft: Boolean,
|
||||
onReady: (TextNoteEvent) -> Unit,
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
@@ -124,7 +125,7 @@ class TextNoteEvent(
|
||||
}
|
||||
}
|
||||
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,10 @@ class ExternalSignerLauncher(
|
||||
"sign_event",
|
||||
22242,
|
||||
),
|
||||
Permission(
|
||||
"sign_event",
|
||||
31234,
|
||||
),
|
||||
Permission(
|
||||
"nip04_encrypt",
|
||||
),
|
||||
|
||||
@@ -32,6 +32,7 @@ abstract class NostrSigner(val pubKey: HexKey) {
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
onReady: (T) -> Unit,
|
||||
isDraft: Boolean = false,
|
||||
)
|
||||
|
||||
abstract fun nip04Encrypt(
|
||||
|
||||
@@ -40,7 +40,13 @@ class NostrSignerExternal(
|
||||
tags: Array<Array<String>>,
|
||||
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 <T : Event> unsignedEvent(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
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,
|
||||
|
||||
@@ -38,9 +38,15 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
|
||||
tags: Array<Array<String>>,
|
||||
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 <T : Event> unsignedEvent(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user