From 4d7f29ba8f7a91425faf52f082fb83a2a16f574a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 8 Mar 2023 11:47:13 -0500 Subject: [PATCH] Adds a parser for naddr when composing messages and a filter to avoid thinking the a tag is a reply instead of a quote. --- .../com/vitorpamplona/amethyst/model/Hex.kt | 29 ++++++++++++++++--- .../amethyst/model/LocalCache.kt | 22 ++++++++------ .../service/model/LongTextNoteEvent.kt | 6 ++++ .../amethyst/ui/actions/NewPostViewModel.kt | 29 ++++++++++++++----- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt index eaac03ef6..c50ff5781 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt @@ -1,11 +1,13 @@ package com.vitorpamplona.amethyst.model +import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.ui.note.toShortenHex import fr.acinq.secp256k1.Hex import nostr.postr.Bech32 import nostr.postr.Persona import nostr.postr.bechToBytes import nostr.postr.toHex +import nostr.postr.toNpub /** Makes the distinction between String and Hex **/ typealias HexKey = String @@ -48,7 +50,7 @@ fun decodePublicKey(key: String): ByteArray { } } -data class DirtyKeyInfo(val type: String, val keyHex: String, val restOfWord: String) +data class DirtyKeyInfo(val key: Nip19.Return, val restOfWord: String) fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? { var key = mightBeAKey @@ -67,11 +69,30 @@ fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? { val restOfWord = key.substring(63) if (key.startsWith("nsec1", true)) { - return DirtyKeyInfo("npub", Persona(privKey = keyB32.bechToBytes()).pubKey.toHexKey(), restOfWord) + // Converts to npub + val pubkey = Nip19.uriToRoute(Persona(privKey = keyB32.bechToBytes()).pubKey.toNpub()) ?: return null + + return DirtyKeyInfo(pubkey, restOfWord) } else if (key.startsWith("npub1", true)) { - return DirtyKeyInfo("npub", keyB32.bechToBytes().toHexKey(), restOfWord) + val pubkey = Nip19.uriToRoute(keyB32) ?: return null + + return DirtyKeyInfo(pubkey, restOfWord) } else if (key.startsWith("note1", true)) { - return DirtyKeyInfo("note", keyB32.bechToBytes().toHexKey(), restOfWord) + val noteId = Nip19.uriToRoute(keyB32) ?: return null + + return DirtyKeyInfo(noteId, restOfWord) + } else if (key.startsWith("nprofile", true)) { + val pubkeyRelay = Nip19.uriToRoute(keyB32 + restOfWord) ?: return null + + return DirtyKeyInfo(pubkeyRelay, restOfWord) + } else if (key.startsWith("nevent", true)) { + val noteRelayId = Nip19.uriToRoute(keyB32 + restOfWord) ?: return null + + return DirtyKeyInfo(noteRelayId, restOfWord) + } else if (key.startsWith("naddr1", true)) { + val address = Nip19.uriToRoute(keyB32 + restOfWord) ?: return null + + return DirtyKeyInfo(address, "") // no way to know when they address ends and dirt begins } } catch (e: Exception) { e.printStackTrace() 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 7d472f062..57142e2dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -189,8 +189,7 @@ object LocalCache { } val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) } - val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) } + - event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) } + val replyTo = tagsWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) } note.loadEvent(event, author, mentions, replyTo) @@ -235,7 +234,7 @@ object LocalCache { } val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) } - val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) } + val replyTo = tagsWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) } if (event.createdAt > (note.createdAt() ?: 0)) { note.loadEvent(event, author, mentions, replyTo) @@ -329,33 +328,38 @@ object LocalCache { if (tag != null && tag[0] == "e") { citations.add(tag[1]) } + if (tag != null && tag[0] == "a") { + citations.add(tag[1]) + } } catch (e: Exception) { } } return citations } - private fun replyToWithoutCitations(event: TextNoteEvent): List { + private fun tagsWithoutCitations(event: TextNoteEvent): List { val repliesTo = event.replyTos() - if (repliesTo.isEmpty()) return repliesTo + val tagAddresses = event.taggedAddresses().map { it.toTag() } + if (repliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList() val citations = findCitations(event) return if (citations.isEmpty()) { - repliesTo + repliesTo + tagAddresses } else { repliesTo.filter { it !in citations } } } - private fun replyToWithoutCitations(event: LongTextNoteEvent): List { + private fun tagsWithoutCitations(event: LongTextNoteEvent): List { val repliesTo = event.replyTos() - if (repliesTo.isEmpty()) return repliesTo + val tagAddresses = event.taggedAddresses().map { it.toTag() } + if (repliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList() val citations = findCitations(event) return if (citations.isEmpty()) { - repliesTo + repliesTo + tagAddresses } else { repliesTo.filter { it !in citations } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt index 45cce1be8..f0ff6c3e7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt @@ -15,6 +15,12 @@ class LongTextNoteEvent( ) : Event(id, pubKey, createdAt, kind, tags, content, sig) { fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } + fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { + val aTagValue = it.getOrNull(1) + val relay = it.getOrNull(2) + + if (aTagValue != null) ATag.parse(aTagValue, relay) else null + } fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: "" fun address() = ATag(kind, pubKey, dTag(), null) 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 34d78dbad..1df19f6c3 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 @@ -10,6 +10,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.* +import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.ui.components.isValidURL import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator import kotlinx.coroutines.flow.MutableSharedFlow @@ -76,10 +77,15 @@ class NewPostViewModel : ViewModel() { paragraph.split(' ').forEach { word: String -> val results = parseDirtyWordForKey(word) - if (results?.type == "npub") { - addUserToMentions(LocalCache.getOrCreateUser(results.keyHex)) - } else if (results?.type == "note") { - addNoteToReplyTos(LocalCache.getOrCreateNote(results.keyHex)) + if (results?.key?.type == Nip19.Type.USER) { + addUserToMentions(LocalCache.getOrCreateUser(results.key.hex)) + } else if (results?.key?.type == Nip19.Type.NOTE) { + addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex)) + } else if (results?.key?.type == Nip19.Type.ADDRESS) { + val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex) + if (note != null) { + addNoteToReplyTos(note) + } } } } @@ -88,14 +94,21 @@ class NewPostViewModel : ViewModel() { val newMessage = message.text.split('\n').map { paragraph: String -> paragraph.split(' ').map { word: String -> val results = parseDirtyWordForKey(word) - if (results?.type == "npub") { - val user = LocalCache.getOrCreateUser(results.keyHex) + if (results?.key?.type == Nip19.Type.USER) { + val user = LocalCache.getOrCreateUser(results.key.hex) "#[${tagIndex(user)}]${results.restOfWord}" - } else if (results?.type == "note") { - val note = LocalCache.getOrCreateNote(results.keyHex) + } else if (results?.key?.type == Nip19.Type.NOTE) { + val note = LocalCache.getOrCreateNote(results.key.hex) "#[${tagIndex(note)}]${results.restOfWord}" + } else if (results?.key?.type == Nip19.Type.ADDRESS) { + val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex) + if (note != null) { + "#[${tagIndex(note)}]${results.restOfWord}" + } else { + word + } } else { word }