diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt index 40bcb20fc7..3b6149d987 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt @@ -26,8 +26,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.style.TextDecoration import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.quartz.nip01Core.core.toHexKey -import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey +import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull import kotlin.coroutines.cancellation.CancellationException class UrlUserTagOutputTransformation( @@ -36,15 +35,20 @@ class UrlUserTagOutputTransformation( override fun TextFieldBuffer.transformOutput() { val text = asCharSequence().toString() - // Find all @npub mentions using regex and replace in reverse order + // Find all user mentions using regex and replace in reverse order // so that earlier indices remain valid after replacements. - val npubRegex = Regex("@npub1[a-z0-9]{58}") - val matches = npubRegex.findAll(text).toList().reversed() + // Matches: @npub1..., nostr:npub1..., @nprofile1..., nostr:nprofile1... + val mentionRegex = Regex("(?:@|nostr:)(?:npub1[a-z0-9]{58}|nprofile1[a-z0-9]+)") + val matches = mentionRegex.findAll(text).toList().reversed() for (match in matches) { try { - val key = decodePublicKey(match.value.removePrefix("@")) - val user = LocalCache.getOrCreateUser(key.toHexKey()) + val bech32 = + match.value + .removePrefix("@") + .removePrefix("nostr:") + val hex = decodePublicKeyAsHexOrNull(bech32) ?: continue + val user = LocalCache.getOrCreateUser(hex) val displayName = "@${user.toBestDisplayName()}" replace(match.range.first, match.range.last + 1, displayName) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagTransformation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagTransformation.kt index 24f6fc7526..2bd2817afa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagTransformation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagTransformation.kt @@ -31,8 +31,10 @@ import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextDecoration import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.quartz.nip01Core.core.toHexKey -import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey +import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser +import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull +import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile +import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import kotlin.coroutines.cancellation.CancellationException data class RangesChanges( @@ -60,20 +62,19 @@ fun buildAnnotatedStringWithUrlHighlighting( text.text.split('\n').joinToString("\n") { paragraph: String -> paragraph.split(' ').joinToString(" ") { word: String -> try { - if (word.startsWith("@npub") && word.length >= 64) { - val keyB32 = word.substring(0, 64) - val restOfWord = word.substring(64) + val mention = parseUserMention(word) + if (mention != null) { + val (keyPortion, restOfWord, hexKey) = mention val startIndex = builderBefore.toString().length builderBefore.append( - "$keyB32$restOfWord ", + "$keyPortion$restOfWord ", ) // accounts for the \n at the end of each paragraph - val endIndex = startIndex + keyB32.length + val endIndex = startIndex + keyPortion.length - val key = decodePublicKey(keyB32.removePrefix("@")) - val user = LocalCache.getOrCreateUser(key.toHexKey()) + val user = LocalCache.getOrCreateUser(hexKey) val newWord = "@${user.toBestDisplayName()}" val startNew = builderAfter.toString().length @@ -181,3 +182,46 @@ fun buildAnnotatedStringWithUrlHighlighting( numberOffsetTranslator, ) } + +private data class UserMention( + val keyPortion: String, + val restOfWord: String, + val hexKey: String, +) + +private fun parseUserMention(word: String): UserMention? { + var key = word + val prefix: String + + if (key.startsWith("nostr:", true)) { + prefix = key.substring(0, 6) + key = key.substring(6) + } else if (key.startsWith("@")) { + prefix = "@" + key = key.substring(1) + } else { + return null + } + + if (key.startsWith("npub1", true) && key.length >= 63) { + val keyB32 = key.substring(0, 63) + val restOfWord = key.substring(63) + val hex = decodePublicKeyAsHexOrNull(keyB32) ?: return null + return UserMention("$prefix$keyB32", restOfWord, hex) + } else if (key.startsWith("nprofile1", true)) { + val parsed = Nip19Parser.uriToRoute(key) ?: return null + val entity = parsed.entity + if (entity !is NProfile && entity !is NPub) return null + val hex = + when (entity) { + is NProfile -> entity.hex + is NPub -> entity.hex + else -> return null + } + val bech32Len = parsed.nip19raw.length + val restOfWord = key.substring(bech32Len) + return UserMention("$prefix${parsed.nip19raw}", restOfWord, hex) + } + + return null +}