Adds a parser for naddr when composing messages and a filter to avoid thinking the a tag is a reply instead of a quote.

This commit is contained in:
Vitor Pamplona
2023-03-08 11:47:13 -05:00
parent 40a6feab8a
commit 4d7f29ba8f
4 changed files with 65 additions and 21 deletions
@@ -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()
@@ -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<String> {
private fun tagsWithoutCitations(event: TextNoteEvent): List<String> {
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<String> {
private fun tagsWithoutCitations(event: LongTextNoteEvent): List<String> {
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 }
}
@@ -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)
@@ -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
}