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:
@@ -1,11 +1,13 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import nostr.postr.Bech32
|
import nostr.postr.Bech32
|
||||||
import nostr.postr.Persona
|
import nostr.postr.Persona
|
||||||
import nostr.postr.bechToBytes
|
import nostr.postr.bechToBytes
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
/** Makes the distinction between String and Hex **/
|
/** Makes the distinction between String and Hex **/
|
||||||
typealias HexKey = String
|
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? {
|
fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? {
|
||||||
var key = mightBeAKey
|
var key = mightBeAKey
|
||||||
@@ -67,11 +69,30 @@ fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? {
|
|||||||
val restOfWord = key.substring(63)
|
val restOfWord = key.substring(63)
|
||||||
|
|
||||||
if (key.startsWith("nsec1", true)) {
|
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)) {
|
} 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)) {
|
} 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) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|||||||
@@ -189,8 +189,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
val replyTo = replyToWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) } +
|
val replyTo = tagsWithoutCitations(event).mapNotNull { checkGetOrCreateNote(it) }
|
||||||
event.taggedAddresses().mapNotNull { getOrCreateAddressableNote(it) }
|
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, mentions, replyTo)
|
||||||
|
|
||||||
@@ -235,7 +234,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val mentions = event.mentions().mapNotNull { checkGetOrCreateUser(it) }
|
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)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, mentions, replyTo)
|
||||||
@@ -329,33 +328,38 @@ object LocalCache {
|
|||||||
if (tag != null && tag[0] == "e") {
|
if (tag != null && tag[0] == "e") {
|
||||||
citations.add(tag[1])
|
citations.add(tag[1])
|
||||||
}
|
}
|
||||||
|
if (tag != null && tag[0] == "a") {
|
||||||
|
citations.add(tag[1])
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return citations
|
return citations
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replyToWithoutCitations(event: TextNoteEvent): List<String> {
|
private fun tagsWithoutCitations(event: TextNoteEvent): List<String> {
|
||||||
val repliesTo = event.replyTos()
|
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)
|
val citations = findCitations(event)
|
||||||
|
|
||||||
return if (citations.isEmpty()) {
|
return if (citations.isEmpty()) {
|
||||||
repliesTo
|
repliesTo + tagAddresses
|
||||||
} else {
|
} else {
|
||||||
repliesTo.filter { it !in citations }
|
repliesTo.filter { it !in citations }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replyToWithoutCitations(event: LongTextNoteEvent): List<String> {
|
private fun tagsWithoutCitations(event: LongTextNoteEvent): List<String> {
|
||||||
val repliesTo = event.replyTos()
|
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)
|
val citations = findCitations(event)
|
||||||
|
|
||||||
return if (citations.isEmpty()) {
|
return if (citations.isEmpty()) {
|
||||||
repliesTo
|
repliesTo + tagAddresses
|
||||||
} else {
|
} else {
|
||||||
repliesTo.filter { it !in citations }
|
repliesTo.filter { it !in citations }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ class LongTextNoteEvent(
|
|||||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||||
fun mentions() = tags.filter { it.firstOrNull() == "p" }.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 dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
|
||||||
fun address() = ATag(kind, pubKey, dTag(), null)
|
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.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.*
|
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.isValidURL
|
||||||
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@@ -76,10 +77,15 @@ class NewPostViewModel : ViewModel() {
|
|||||||
paragraph.split(' ').forEach { word: String ->
|
paragraph.split(' ').forEach { word: String ->
|
||||||
val results = parseDirtyWordForKey(word)
|
val results = parseDirtyWordForKey(word)
|
||||||
|
|
||||||
if (results?.type == "npub") {
|
if (results?.key?.type == Nip19.Type.USER) {
|
||||||
addUserToMentions(LocalCache.getOrCreateUser(results.keyHex))
|
addUserToMentions(LocalCache.getOrCreateUser(results.key.hex))
|
||||||
} else if (results?.type == "note") {
|
} else if (results?.key?.type == Nip19.Type.NOTE) {
|
||||||
addNoteToReplyTos(LocalCache.getOrCreateNote(results.keyHex))
|
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 ->
|
val newMessage = message.text.split('\n').map { paragraph: String ->
|
||||||
paragraph.split(' ').map { word: String ->
|
paragraph.split(' ').map { word: String ->
|
||||||
val results = parseDirtyWordForKey(word)
|
val results = parseDirtyWordForKey(word)
|
||||||
if (results?.type == "npub") {
|
if (results?.key?.type == Nip19.Type.USER) {
|
||||||
val user = LocalCache.getOrCreateUser(results.keyHex)
|
val user = LocalCache.getOrCreateUser(results.key.hex)
|
||||||
|
|
||||||
"#[${tagIndex(user)}]${results.restOfWord}"
|
"#[${tagIndex(user)}]${results.restOfWord}"
|
||||||
} else if (results?.type == "note") {
|
} else if (results?.key?.type == Nip19.Type.NOTE) {
|
||||||
val note = LocalCache.getOrCreateNote(results.keyHex)
|
val note = LocalCache.getOrCreateNote(results.key.hex)
|
||||||
|
|
||||||
"#[${tagIndex(note)}]${results.restOfWord}"
|
"#[${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 {
|
} else {
|
||||||
word
|
word
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user