diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/MessageLinks.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/MessageLinks.kt new file mode 100644 index 00000000..619a387f --- /dev/null +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/MessageLinks.kt @@ -0,0 +1,34 @@ +package com.utun.chat.data + +import java.net.URI + +/** Границы UTF-16 и полный адрес; разбор не меняет текст и не открывает ссылки. */ +data class MessageLink(val start: Int, val end: Int, val url: String) + +object MessageLinks { + private val pattern = Regex("utun://[A-Za-z0-9+/]+={0,2}|(?i:https?://)[^\\s\\p{Z}\\p{Cc}<>\"'`«»“”‘’]+") + + /** Некорректные URL в пользовательском тексте остаются обычным текстом. */ + fun isWebUrl(url: String): Boolean = try { + val uri = URI(url) + (uri.scheme.equals("http", true) || uri.scheme.equals("https", true)) && + uri.toURL().let { it.host.isNotEmpty() && it.port in -1..65535 } + } catch (_: IllegalArgumentException) { false } + catch (_: java.net.URISyntaxException) { false } + catch (_: java.net.MalformedURLException) { false } + + fun parse(text: String): List = pattern.findAll(text).mapNotNull { match -> + var url = match.value + if (!url.startsWith("utun://")) { + while (url.isNotEmpty()) { + val last = url.last() + val closing = ")]}".indexOf(last) + val unmatched = closing >= 0 && url.count { it == last } > url.count { it == "([{"[closing] } + if (last !in ".,;:!?" && !unmatched) break + url = url.dropLast(1) + } + if (!isWebUrl(url)) return@mapNotNull null + } + MessageLink(match.range.first, match.range.first + url.length, url) + }.toList() +}