mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-27 08:57:35 +02:00
Correctly parsing NIP19s in Text
This commit is contained in:
@@ -1,35 +1,47 @@
|
|||||||
package com.vitorpamplona.amethyst.service.nip19
|
package com.vitorpamplona.amethyst.service.nip19
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.bechToBytes
|
import nostr.postr.bechToBytes
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
object Nip19 {
|
object Nip19 {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
USER, NOTE, RELAY, ADDRESS
|
USER, NOTE, RELAY, ADDRESS
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Return(val type: Type, val hex: String, val relay: String? = null)
|
val nip19regex = Pattern.compile("(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)(.*)", Pattern.CASE_INSENSITIVE)
|
||||||
|
|
||||||
|
data class Return(val type: Type, val hex: String, val relay: String? = null, val additionalChars: String = "")
|
||||||
|
|
||||||
fun uriToRoute(uri: String?): Return? {
|
fun uriToRoute(uri: String?): Return? {
|
||||||
try {
|
if (uri == null) return null
|
||||||
val key = uri?.removePrefix("nostr:") ?: return null
|
|
||||||
|
|
||||||
val bytes = key.bechToBytes()
|
try {
|
||||||
if (key.startsWith("npub")) {
|
println("Issue trying to Decode NIP19 $uri")
|
||||||
return npub(bytes)
|
|
||||||
} else if (key.startsWith("note")) {
|
val matcher = nip19regex.matcher(uri)
|
||||||
return note(bytes)
|
matcher.find()
|
||||||
} else if (key.startsWith("nprofile")) {
|
val uriScheme = matcher.group(1) // nostr:
|
||||||
return nprofile(bytes)
|
val type = matcher.group(2) // npub1
|
||||||
} else if (key.startsWith("nevent")) {
|
val key = matcher.group(3) // bech32
|
||||||
return nevent(bytes)
|
val additionalChars = matcher.group(4) ?: "" // additional chars
|
||||||
} else if (key.startsWith("nrelay")) {
|
|
||||||
return nrelay(bytes)
|
println("Issue trying to Decode NIP19 Additional Chars $additionalChars")
|
||||||
} else if (key.startsWith("naddr")) {
|
|
||||||
return naddr(bytes)
|
val bytes = (type + key).bechToBytes()
|
||||||
|
val parsed = when (type.lowercase()) {
|
||||||
|
"npub1" -> npub(bytes)
|
||||||
|
"note1" -> note(bytes)
|
||||||
|
"nprofile1" -> nprofile(bytes)
|
||||||
|
"nevent1" -> nevent(bytes)
|
||||||
|
"nrelay1" -> nrelay(bytes)
|
||||||
|
"naddr1" -> naddr(bytes)
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
return parsed?.copy(additionalChars = additionalChars)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
println("Issue trying to Decode NIP19 $uri: ${e.message}")
|
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@@ -7,7 +7,8 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
@@ -24,58 +25,55 @@ fun ClickableRoute(
|
|||||||
val userState by userBase.live().metadata.observeAsState()
|
val userState by userBase.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
val route = "User/${nip19.hex}"
|
CreateClickableText(user.toBestDisplayName(), nip19.additionalChars, "User/${nip19.hex}", navController)
|
||||||
val text = user.toBestDisplayName()
|
|
||||||
|
|
||||||
ClickableText(
|
|
||||||
text = AnnotatedString("@$text "),
|
|
||||||
onClick = { navController.navigate(route) },
|
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
|
||||||
)
|
|
||||||
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
||||||
val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
|
val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
|
||||||
|
|
||||||
if (noteBase == null) {
|
if (noteBase == null) {
|
||||||
Text(
|
Text(
|
||||||
"@${nip19.hex} "
|
"@${nip19.hex}${nip19.additionalChars} "
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val noteState by noteBase.live().metadata.observeAsState()
|
val noteState by noteBase.live().metadata.observeAsState()
|
||||||
val note = noteState?.note ?: return
|
val note = noteState?.note ?: return
|
||||||
|
|
||||||
ClickableText(
|
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController)
|
||||||
text = AnnotatedString("@${note.idDisplayNote()} "),
|
|
||||||
onClick = { navController.navigate("Note/${nip19.hex}") },
|
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else if (nip19.type == Nip19.Type.NOTE) {
|
} else if (nip19.type == Nip19.Type.NOTE) {
|
||||||
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
|
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
|
||||||
val noteState by noteBase.live().metadata.observeAsState()
|
val noteState by noteBase.live().metadata.observeAsState()
|
||||||
val note = noteState?.note ?: return
|
val note = noteState?.note ?: return
|
||||||
|
val channel = note.channel()
|
||||||
|
|
||||||
if (note.event is ChannelCreateEvent) {
|
if (note.event is ChannelCreateEvent) {
|
||||||
ClickableText(
|
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController)
|
||||||
text = AnnotatedString("@${note.idDisplayNote()} "),
|
} else if (channel != null) {
|
||||||
onClick = { navController.navigate("Channel/${nip19.hex}") },
|
CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController)
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
|
||||||
)
|
|
||||||
} else if (note.channel() != null) {
|
|
||||||
ClickableText(
|
|
||||||
text = AnnotatedString("@${note.channel()?.toBestDisplayName()} "),
|
|
||||||
onClick = { navController.navigate("Channel/${note.channel()?.idHex}") },
|
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
ClickableText(
|
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController)
|
||||||
text = AnnotatedString("@${note.idDisplayNote()} "),
|
|
||||||
onClick = { navController.navigate("Note/${nip19.hex}") },
|
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
"@${nip19.hex} "
|
"@${nip19.hex}${nip19.additionalChars} "
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CreateClickableText(clickablePart: String, suffix: String, route: String, navController: NavController) {
|
||||||
|
ClickableText(
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(
|
||||||
|
LocalTextStyle.current.copy(color = MaterialTheme.colors.primary).toSpanStyle()
|
||||||
|
) {
|
||||||
|
append("@$clickablePart")
|
||||||
|
}
|
||||||
|
withStyle(
|
||||||
|
LocalTextStyle.current.copy(color = MaterialTheme.colors.onBackground).toSpanStyle()
|
||||||
|
) {
|
||||||
|
append("$suffix ")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = { navController.navigate(route) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@@ -43,8 +43,8 @@ import java.net.URISyntaxException
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$")
|
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$", Pattern.CASE_INSENSITIVE)
|
||||||
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$")
|
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$", Pattern.CASE_INSENSITIVE)
|
||||||
val noProtocolUrlValidator = Pattern.compile("^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$")
|
val noProtocolUrlValidator = Pattern.compile("^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$")
|
||||||
val tagIndex = Pattern.compile(".*\\#\\[([0-9]+)\\].*")
|
val tagIndex = Pattern.compile(".*\\#\\[([0-9]+)\\].*")
|
||||||
|
|
||||||
@@ -197,19 +197,7 @@ fun isBechLink(word: String): Boolean {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BechLink(word: String, navController: NavController) {
|
fun BechLink(word: String, navController: NavController) {
|
||||||
val uri = if (word.startsWith("nostr", true)) {
|
val nip19Route = Nip19.uriToRoute(word)
|
||||||
word
|
|
||||||
} else if (word.startsWith("@")) {
|
|
||||||
word.replaceFirst("@", "nostr:")
|
|
||||||
} else {
|
|
||||||
"nostr:$word"
|
|
||||||
}
|
|
||||||
|
|
||||||
val nip19Route = try {
|
|
||||||
Nip19.uriToRoute(uri)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nip19Route == null) {
|
if (nip19Route == null) {
|
||||||
Text(text = "$word ")
|
Text(text = "$word ")
|
||||||
|
Reference in New Issue
Block a user