diff --git a/CHANGELOG.md b/CHANGELOG.md index a06f06061d8884a31eb583e1a3ab56db736dc888..a1932afe2deb76a9702d3305654f6541ef4704dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use event filter when filling timeline gaps - Retry operations are not stopped when sync is not running +- Improved APIs for parsing matrix links & mentions ### Deprecated diff --git a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/MatrixRegex.kt b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/MatrixRegex.kt index 3234cbd97298a26f9c3b1604d433469a8225cc77..48600c73a28cc10fb23403c1b83cbc763b2b461b 100644 --- a/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/MatrixRegex.kt +++ b/trixnity-core/src/commonMain/kotlin/net/folivo/trixnity/core/MatrixRegex.kt @@ -3,6 +3,7 @@ package net.folivo.trixnity.core import io.ktor.http.* import net.folivo.trixnity.core.model.* +@Suppress("DEPRECATION") object MatrixRegex { // Decode/Encode Grammar private fun makeSymboleRegex(symbole: String, code: String) = "(?:(?:$symbole)|(?:$code))" @@ -61,7 +62,7 @@ object MatrixRegex { """matrix:(roomid\/$opaqueIdRegex:$servernameRegex)\/e\/($opaqueIdRegex)$queryParameterRegex?""" // https://spec.matrix.org/v1.11/appendices/#matrixto-navigation - private val viaArgumentRegex = """(?:\?(via=$servernameRegex))""" + private val viaArgumentRegex = """(\?via=([^\s&]+)(?:\&via=([^\s&]+))*)""" private val matrixToRegex = """https?:\/\/matrix\.to\/$hash\/""" private val userPermalinkRegex = """$matrixToRegex$at($userLocalpartRegex)$colon($servernameRegex)$viaArgumentRegex?""" @@ -93,10 +94,14 @@ object MatrixRegex { val roomAliasUri by lazy { roomAliasUriRegex.toRegex(255) } val eventIdUri by lazy { eventUriRegex.toRegex(255) } - private val userIdPermalink by lazy { userPermalinkRegex.toRegex(255) } - private val roomIdPermalink by lazy { roomIdPermalinkRegex.toRegex(255) } - private val roomAliasPermalink by lazy { roomAliasPermalinkRegex.toRegex(255) } - private val eventIdPermalink by lazy { eventPermalinkRegex.toRegex(255) } + @Deprecated("matrix.to links should be replaced with matrix: links if possible") + val userIdPermalink by lazy { userPermalinkRegex.toRegex(255) } + @Deprecated("matrix.to links should be replaced with matrix: links if possible") + val roomIdPermalink by lazy { roomIdPermalinkRegex.toRegex(255) } + @Deprecated("matrix.to links should be replaced with matrix: links if possible") + val roomAliasPermalink by lazy { roomAliasPermalinkRegex.toRegex(255) } + @Deprecated("matrix.to links should be replaced with matrix: links if possible") + val eventIdPermalink by lazy { eventPermalinkRegex.toRegex(255) } internal val userIdPermalinkAnchor by lazy { getAnchor(userPermalinkRegex, 255).toRegex() } internal val roomIdPermalinkAnchor by lazy { getAnchor(roomIdPermalinkRegex, 255).toRegex() } @@ -289,6 +294,48 @@ object MatrixRegex { } } } + + fun parseUserLink(input: String, label: String?): Mention.User? { + val match = userIdUri.matchEntire(input) + ?: userIdPermalink.matchEntire(input) + return match?.groupValues?.filter(String::isNotBlank)?.let { result -> + val (match, localpart, domain) = result + val (params, _) = parseOptions(result.drop(3), false) + return Mention.User(UserId(localpart, domain), match, params, label) + } + } + + fun parseRoomIdLink(input: String, label: String?): Mention.Room? { + val match = roomIdUri.matchEntire(input) + ?: roomIdPermalink.matchEntire(input) + return match?.groupValues?.filter(String::isNotBlank)?.let { result -> + val (match, localpart, domain) = result + val (params, _) = parseOptions(result.drop(3), false) + return Mention.Room(RoomId(localpart, domain), match, params, label) + } + } + + fun parseRoomAliasLink(input: String, label: String?): Mention.RoomAlias? { + val match = roomAliasUri.matchEntire(input) + ?: roomAliasPermalink.matchEntire(input) + return match?.groupValues?.filter(String::isNotBlank)?.let { result -> + val (match, localpart, domain) = result + val (params, _) = parseOptions(result.drop(3), false) + return Mention.RoomAlias(RoomAliasId(localpart, domain), match, params, label) + } + } + + fun parseEventLink(input: String, label: String?): Mention.Event? { + val match = eventIdUri.matchEntire(input) + ?: eventIdPermalink.matchEntire(input) + return match?.groupValues?.filter(String::isNotBlank)?.let { result -> + val match = result[0] + val roomId = result[1].replaceFirst("roomid/", "!") + val eventId = result[2] + val (params, _) = parseOptions(result.drop(3), false) + return Mention.Event(RoomId(roomId), EventId("$$eventId"), match, label, params) + } + } } private fun String.toRegex(maxLength: Int) = "(?!.{${maxLength + 1},})$this".toRegex()