diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt index f0927e81b..70c802ac9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt @@ -79,7 +79,7 @@ object CachedUrlParser { cached } else { val urlSet = UrlParser().parseValidUrls(content) - val newUrls = urlSet.withScheme.filter { it.startsWith("http") } + urlSet.withoutScheme.map { "http://$it" } + val newUrls = urlSet.withScheme.filter { it.startsWith("http") } + urlSet.withoutScheme.map { "http://$it" } + urlSet.bech32s.map { "http://$it" } parsedUrlsCache.put(key, newUrls) newUrls } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index fc5752621..1dd39aa23 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -81,6 +81,7 @@ import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment import com.vitorpamplona.amethyst.commons.richtext.RelayUrlSegment import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState +import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment import com.vitorpamplona.amethyst.commons.richtext.SecretEmoji import com.vitorpamplona.amethyst.commons.richtext.Segment import com.vitorpamplona.amethyst.commons.richtext.VideoSegment @@ -168,6 +169,10 @@ fun RenderStrangeNamePreview() { is RelayUrlSegment -> { ClickableRelayUrl(word.segmentText, EmptyNav()) } + + is SchemelessUrlSegment -> { + NoProtocolUrlRenderer(word.segmentText) + } } } } @@ -225,6 +230,10 @@ fun RenderRegularPreview() { is RegularTextSegment -> { Text(word.segmentText) } + + is SchemelessUrlSegment -> { + NoProtocolUrlRenderer(word.segmentText) + } } } } @@ -261,6 +270,8 @@ fun RenderRegularPreview2() { is RegularTextSegment -> Text(word.segmentText) is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, EmptyNav()) + + is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText) } } } @@ -310,6 +321,8 @@ fun RenderRegularPreview3() { is RegularTextSegment -> Text(word.segmentText) is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, EmptyNav()) + + is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText) } } } @@ -486,6 +499,8 @@ private fun RenderWordWithoutPreview( is RegularTextSegment -> Text(word.segmentText) is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, nav) + + is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText) } } @@ -517,6 +532,7 @@ private fun RenderWordWithPreview( is RegularTextSegment -> Text(word.segmentText) is Base64Segment -> ZoomableContentView(word.segmentText, state, accountViewModel) is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, nav) + is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText) } } @@ -533,6 +549,11 @@ private fun ZoomableContentView( } } +@Composable +private fun NoProtocolUrlRenderer(url: String) { + ClickableUrl(url, "https://$url") +} + @Composable fun RenderCustomEmoji( word: String, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt index a38d97ad9..05db51ae1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt @@ -38,7 +38,7 @@ import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel -import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.richtext.UrlParser import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -517,7 +517,7 @@ open class ChannelNewMessageViewModel : emojiSuggestions?.reset() } - open fun findUrlInMessage(): String? = RichTextParser().parseValidUrls(message.text).firstOrNull() + open fun findUrlInMessage(): String? = UrlParser().parseValidUrls(message.text).withScheme.firstOrNull() open fun addToMessage(it: String) { updateMessage(TextFieldValue(message.text + " " + it)) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt index f10fe9c5e..7f39960b6 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParser.kt @@ -161,7 +161,7 @@ class RichTextParser { val emojiMap = CustomEmoji.createEmojiMap(tags.lists) - val allUrls = urlSet.withScheme + urlSet.withoutScheme + urlSet.emails + val allUrls = urlSet.withScheme + urlSet.withoutScheme + urlSet.emails + urlSet.bech32s + urlSet.relayUrls val newContent = fixMissingSpaces(content, allUrls) @@ -280,13 +280,17 @@ class RichTextParser { } } - if (word.startsWith("ws://") || word.startsWith("wss://")) return RelayUrlSegment(word) + if (urls.withoutScheme.contains(word)) return SchemelessUrlSegment(word) - if (urls.withoutScheme.contains(word)) { - return LinkSegment("https://$word") - } else if (urls.withScheme.contains(word)) { - return LinkSegment(word) - } + if (urls.withScheme.contains(word)) return LinkSegment(word) + + if (urls.emails.contains(word)) return EmailSegment(word) + + if (urls.bech32s.contains(word)) return BechSegment(word) + + if (urls.relayUrls.contains(word)) return RelayUrlSegment(word) + + if (startsWithNIP19Scheme(word)) return BechSegment(word) if (CustomEmoji.fastMightContainEmoji(word, emojis) && emojis.any { word.contains(it.key) }) return EmojiSegment(word) @@ -300,10 +304,6 @@ class RichTextParser { if (EmojiCoder.isCoded(word)) return SecretEmoji(word) - if (urls.emails.contains(word)) return EmailSegment(word) - - if (startsWithNIP19Scheme(word)) return BechSegment(word) - if (isPotentialPhoneNumber(word) && !isDate(word)) { if (Patterns.PHONE.matches(word)) return PhoneSegment(word) } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt index 91c9598de..b56a47b19 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserSegments.kt @@ -143,6 +143,11 @@ class RelayUrlSegment( segment: String, ) : Segment(segment) +@Immutable +class SchemelessUrlSegment( + segment: String, +) : Segment(segment) + @Immutable class RegularTextSegment( segment: String, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParser.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParser.kt index 9c8f3e13c..5441db97e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParser.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParser.kt @@ -27,6 +27,8 @@ class Urls( val withScheme: Set = emptySet(), val withoutScheme: Set = emptySet(), val emails: Set = emptySet(), + val bech32s: Set = emptySet(), + val relayUrls: Set = emptySet(), ) class UrlParser { @@ -45,7 +47,7 @@ class UrlParser { } } - fun Url.wroteWithSchema(): Boolean = originalUrl.startsWith(scheme) + fun Url.wroteWithSchema(): Boolean = urlMarker.hasScheme() fun Url.isEmail(): Boolean = urlMarker.hasUsernamePassword() && @@ -54,33 +56,24 @@ class UrlParser { originalUrl.contains('@') && path == "/" - fun Char.isValidLastHostnameChar(): Boolean = (this in 'a'..'z' || this in 'A'..'Z' || this in '0'..'9') - - fun Url.isValidLastHostnameChar(): Boolean = host[host.length - 1].isValidLastHostnameChar() - - fun Url.endsWithHost(): Boolean = originalUrl.endsWith(host) - - val notAHostNameChar = "[^a-zA-Z0-9.-]".toRegex() - fun parseValidUrls(content: String): Urls { val urls = UrlDetector(content).detect() val completeUrls = mutableSetOf() val urlsWithoutScheme = mutableSetOf() val emails = mutableSetOf() + val bech32 = mutableSetOf() + val relays = mutableSetOf() urls.forEach { if (it.isValidTopLevelDomain()) { if (it.wroteWithSchema()) { - if (it.isValidLastHostnameChar()) { + if (it.originalUrl.startsWith("nostr")) { + bech32.add(it.originalUrl) + } else if (it.originalUrl.startsWith("ws")) { + relays.add(it.originalUrl) + } else { completeUrls.add(it.originalUrl) - } else if (it.endsWithHost()) { - val match = notAHostNameChar.find(it.host) - if (match != null) { - completeUrls.add(it.originalUrl.substring(0, (it.originalUrl.length - it.host.length) + match.range.first)) - } else { - completeUrls.add(it.originalUrl) - } } } else { // emails are understood as urls from the detector. @@ -99,6 +92,8 @@ class UrlParser { withScheme = completeUrls, withoutScheme = urlsWithoutScheme, emails = emails, + bech32s = bech32, + relayUrls = relays, ) } } diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserMultibyteTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserMultibyteTest.kt index a8b83a857..a1b896e45 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserMultibyteTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserMultibyteTest.kt @@ -70,7 +70,7 @@ class RichTextParserMultibyteTest { .parseText(text, EmptyTagList, null) assertEquals( - "Vitor, você http://test.com? \uD83E\uDD7A", + "Vitor, você http://test.com ? \uD83E\uDD7A", state.paragraphs.joinToString("\n") { it.words.joinToString(" ") { it.segmentText } }, ) } @@ -157,9 +157,9 @@ class RichTextParserMultibyteTest { val state = RichTextParser().parseText(text, EmptyTagList, null) val allSegments = state.paragraphs.flatMap { it.words } - val urlSegments = allSegments.filterIsInstance() + val urlSegments = allSegments.filterIsInstance() assertTrue("Should have SchemelessUrlSegment", urlSegments.isNotEmpty()) - assertTrue("URL should be example.com", urlSegments.any { it.segmentText == "https://example.com" }) + assertTrue("URL should be example.com", urlSegments.any { it.segmentText == "example.com" }) val textSegments = allSegments.filterIsInstance() assertTrue("Should have prefix ああ", textSegments.any { it.segmentText == "ああ" }) @@ -172,9 +172,9 @@ class RichTextParserMultibyteTest { val state = RichTextParser().parseText(text, EmptyTagList, null) val allSegments = state.paragraphs.flatMap { it.words } - val urlSegments = allSegments.filterIsInstance() + val urlSegments = allSegments.filterIsInstance() assertTrue("Should have SchemelessUrlSegment", urlSegments.isNotEmpty()) - assertTrue("URL should be example.com", urlSegments.any { it.segmentText == "https://example.com" }) + assertTrue("URL should be example.com", urlSegments.any { it.segmentText == "example.com" }) val textSegments = allSegments.filterIsInstance() assertTrue("Should have suffix ああ", textSegments.any { it.segmentText == "ああ" }) diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserTest.kt index 41490ec27..0fc584c52 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/RichTextParserTest.kt @@ -749,52 +749,52 @@ class RichTextParserTest { "RegularText(Events (%) Relay)", "RegularText(33.4K)", "RegularText((4.6%))", - "Link(https://relay.shitforce.one)", + "SchemelessUrl(relay.shitforce.one)", "RegularText(32.9K)", "RegularText((4.6%))", - "Link(https://relayable.org)", + "SchemelessUrl(relayable.org)", "RegularText(26.6K)", "RegularText((3.7%))", - "Link(https://universe.nostrich.land)", + "SchemelessUrl(universe.nostrich.land)", "RegularText(22.8K)", "RegularText((3.2%))", - "Link(https://nos.lol)", + "SchemelessUrl(nos.lol)", "RegularText(22.7K)", "RegularText((3.1%))", - "Link(https://universe.nostrich.land?lang=zh)", + "SchemelessUrl(universe.nostrich.land?lang=zh)", "RegularText(22.5K)", "RegularText((3.1%))", - "Link(https://universe.nostrich.land?lang=en)", + "SchemelessUrl(universe.nostrich.land?lang=en)", "RegularText(21.2K)", "RegularText((2.9%))", - "Link(https://relay.damus.io)", + "SchemelessUrl(relay.damus.io)", "RegularText(20.6K)", "RegularText((2.9%))", - "Link(https://relay.nostr.wirednet.jp)", + "SchemelessUrl(relay.nostr.wirednet.jp)", "RegularText(20.1K)", "RegularText((2.8%))", - "Link(https://offchain.pub)", + "SchemelessUrl(offchain.pub)", "RegularText(19.9K)", "RegularText((2.8%))", - "Link(https://nostr.rocks)", + "SchemelessUrl(nostr.rocks)", "RegularText(19.5K)", "RegularText((2.7%))", - "Link(https://relay.wellorder.net)", + "SchemelessUrl(relay.wellorder.net)", "RegularText(19.4K)", "RegularText((2.7%))", - "Link(https://nostr.oxtr.dev)", + "SchemelessUrl(nostr.oxtr.dev)", "RegularText(19K)", "RegularText((2.6%))", - "Link(https://universe.nostrich.land?lang=ja)", + "SchemelessUrl(universe.nostrich.land?lang=ja)", "RegularText(18.4K)", "RegularText((2.6%))", - "Link(https://relay.mostr.pub)", + "SchemelessUrl(relay.mostr.pub)", "RegularText(17.5K)", "RegularText((2.4%))", - "Link(https://universe.nostrich.land?lang=zh)", + "SchemelessUrl(universe.nostrich.land?lang=zh)", "RegularText(16.3K)", "RegularText((2.3%))", - "Link(https://nostr.bitcoiner.social)", + "SchemelessUrl(nostr.bitcoiner.social)", "RegularText()", "RegularText(30 day global new events)", "RegularText()", @@ -1633,7 +1633,7 @@ class RichTextParserTest { "RegularText(eb3b94533dafeb8ebd58a4947a3dce11d83a9931c622bdf30a4257d3347ee1bf)", "HashTag(#109)", "RegularText(3%)", - "Link(https://Nostr-Check.com)", + "SchemelessUrl(Nostr-Check.com)", "RegularText(,)", "Email(freeverification@Nostr-Check.com)", "RegularText(-)", @@ -1910,7 +1910,7 @@ class RichTextParserTest { "RegularText(d162a53c3b0bfb5c3ebd787d7b08feab206b112362eca25aa291251cd70fe225)", "HashTag(#154)", "RegularText(3%)", - "Link(https://MR.Rabbit)", + "SchemelessUrl(MR.Rabbit)", "RegularText(,)", "Email(Mr.Rabbit@BitcoinNostr.com)", "RegularText(-)", @@ -2087,7 +2087,7 @@ class RichTextParserTest { "RegularText(39ed0aea2338477103e0b5a820532ded27dbfe4f203e7270392d55f63e60271a)", "HashTag(#183)", "RegularText(2%)", - "Link(https://Ancap.su)", + "SchemelessUrl(Ancap.su)", "RegularText(,)", "Email(ancapsu@getalby.com)", "RegularText(-)", @@ -2488,7 +2488,7 @@ class RichTextParserTest { "HashTag(#249)", "RegularText(2%)", "RegularText(micmad,)", - "Link(https://miceliomad@miceliomad.github.io/nostr/)", + "SchemelessUrl(miceliomad@miceliomad.github.io/nostr/)", "RegularText(-)", "RegularText(cd806edcf8ff40ea94fa574ea9cd97da16e5beb2b85aac6e1d648b8388504343)", "HashTag(#250)", @@ -2667,7 +2667,7 @@ class RichTextParserTest { "HashTag(#278)", "RegularText(2%)", "RegularText(𝕬𝖓𝖔𝖓𝖞𝖒𝖔𝖚𝖘,)", - "Link(https://zapper.lol)", + "SchemelessUrl(zapper.lol)", "RegularText(-)", "RegularText(96aceca84aa381eeda084167dd317e1bf7a45d874cd14147f0a9e0df86fb44c2)", "HashTag(#279)", @@ -3125,7 +3125,7 @@ class RichTextParserTest { "RegularText(c4a9caef93e93f484274c04cd981d1de1424902451aca2f5602bd0835fe4393d)", "HashTag(#354)", "RegularText(2%)", - "Link(https://smies.me)", + "SchemelessUrl(smies.me)", "RegularText(,)", "Email(jacksmies@iris.to)", "RegularText(-)", @@ -4006,7 +4006,7 @@ class RichTextParserTest { "RegularText(21a7014db2ba17acc8bbb9496645084866b46e1ba0062a80513afda405450183)", "HashTag(#499)", "RegularText(1%)", - "Link(https://baller.hodl)", + "SchemelessUrl(baller.hodl)", "RegularText(,)", "RegularText()", "RegularText(-)", @@ -4287,7 +4287,8 @@ class RichTextParserTest { listOf( "RegularText(That’s)", "RegularText(it!)", - "Link(http://vitorpamplona.com/.)", + "Link(http://vitorpamplona.com/)", + "RegularText(.)", "RegularText(That’s)", "RegularText(the)", "RegularText(note)", diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt index 41fba8e23..438939b2c 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/richtext/UrlParserTest.kt @@ -166,7 +166,7 @@ class UrlParserTest { fun testNostrUrls() = test( "nostr:npub1aabbcc", - Urls(withScheme = setOf("nostr:npub1aabbcc")), + Urls(bech32s = setOf("nostr:npub1aabbcc")), ) @Test @@ -261,14 +261,14 @@ class UrlParserTest { fun testRelayUrl() = test( "wss://test.com", - Urls(withScheme = setOf("wss://test.com")), + Urls(relayUrls = setOf("wss://test.com")), ) @Test fun testBech12() = test( "nostr:npub1aabbcc", - Urls(withScheme = setOf("nostr:npub1aabbcc")), + Urls(bech32s = setOf("nostr:npub1aabbcc")), ) @Test diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/DomainNameReader.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/DomainNameReader.kt index 7704434b0..aa77113ce 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/DomainNameReader.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/DomainNameReader.kt @@ -220,6 +220,15 @@ class DomainNameReader( // done = true lastWasAscii = isAscii + } else if (index == 0) { + println("First Char: $curr ${curr in UrlDetector.CANNOT_BEGIN_URLS_WITH}") + if (curr in UrlDetector.CANNOT_BEGIN_URLS_WITH) { + newStart = index + 1 + currentLabelLength = 0 + topLevelLength = 0 + numeric = true + dots = 0 + } } index++ } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UrlDetector.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UrlDetector.kt index 20698e294..70f0a19cf 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UrlDetector.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UrlDetector.kt @@ -650,7 +650,9 @@ class UrlDetector( if (state == ReadEndState.ValidUrl && buffer.isNotEmpty()) { // Add the url to the list of good urls. if (buffer.isNotEmpty()) { - urlList.add(currentUrlMarker.createUrl(buffer.toString())) + var url = buffer.toString() + if (url.lastOrNull() in CANNOT_END_URLS_WITH) url = url.dropLast(1) + urlList.add(currentUrlMarker.createUrl(url)) } } @@ -669,7 +671,7 @@ class UrlDetector( } companion object { - private val VALID_SCHEMES_NO_SLASHES: List = + val VALID_SCHEMES_NO_SLASHES: List = listOf( "http:", "https:", @@ -681,9 +683,42 @@ class UrlDetector( "blossom:", ) - private val VALID_SCHEMES = + val VALID_SCHEMES = VALID_SCHEMES_NO_SLASHES.map { "$it//" } + + val CANNOT_BEGIN_URLS_WITH = + setOf( + ',', + '.', + ';', + '?', + '!', + ')', + '}', + '(', + '{', + '\u3002', + '\uFF0E', + '\uFF61', + ) + + val CANNOT_END_URLS_WITH = + setOf( + ',', + '.', + ';', + '?', + '!', + ':', + ')', + '}', + '(', + '{', + '\u3002', + '\uFF0E', + '\uFF61', + ) } } diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UriDetectionTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UriDetectionTest.kt index b0af22216..e7fac49a0 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UriDetectionTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/utils/urldetector/detection/UriDetectionTest.kt @@ -58,7 +58,7 @@ class UriDetectionTest { runTest( "the url google.com is a lot better then www.google.com.", "google.com", - "www.google.com.", + "www.google.com", ) } @@ -76,7 +76,7 @@ class UriDetectionTest { "this is an international domain: http://\u043F\u0440\u0438\u043c\u0435\u0440.\u0438\u0441\u043f\u044b" + "\u0442\u0430\u043d\u0438\u0435 so is this: \u4e94\u7926\u767c\u5c55.\u4e2d\u570b.", "http://\u043F\u0440\u0438\u043c\u0435\u0440.\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435", - "\u4e94\u7926\u767c\u5c55.\u4e2d\u570b.", + "\u4e94\u7926\u767c\u5c55.\u4e2d\u570b", ) } @@ -215,7 +215,7 @@ class UriDetectionTest { "www.www", "yahoo.com", "yahoo.com.br", - "hello.hello.", + "hello.hello", "hello.com", ) } @@ -395,7 +395,7 @@ class UriDetectionTest { runTest("%2ewtfismyip") runTest("wtfismyip%2e") runTest("wtfismyip%2ecom%2e", "wtfismyip%2ecom%2e") - runTest("wtfismyip%2ecom.", "wtfismyip%2ecom.") + runTest("wtfismyip%2ecom.", "wtfismyip%2ecom") runTest("%2ewtfismyip%2ecom", "wtfismyip%2ecom") } @@ -662,7 +662,7 @@ class UriDetectionTest { runTest("hello:password@go12//", "hello:password@go12//") runTest("hello:password@go12", "hello:password@go12") runTest("hello:password@go12 lala", "hello:password@go12") - runTest("hello.com..", "hello.com.") + runTest("hello.com..", "hello.com") runTest("a/") runTest("4/5") runTest("concerns/worries") @@ -694,7 +694,7 @@ class UriDetectionTest { @Test fun testNostr() { - runTest("Check this post nostr:somethingsomething . I think it is really cool", "nostr:somethingsomething") + runTest("Check this post nostr:npub1048qg5p6kfnpth2l98kq3dffg097tutm4npsz2exygx25ge2k9xqf5x3nf . I think it is really cool", "nostr:npub1048qg5p6kfnpth2l98kq3dffg097tutm4npsz2exygx25ge2k9xqf5x3nf") } @Test @@ -704,7 +704,7 @@ class UriDetectionTest { @Test fun testNostrSlashes() { - runTest("Check this post nostr://somethingsomething . I think it is really cool", "nostr://somethingsomething") + runTest("Check this post nostr://npub1048qg5p6kfnpth2l98kq3dffg097tutm4npsz2exygx25ge2k9xqf5x3nf . I think it is really cool", "nostr://npub1048qg5p6kfnpth2l98kq3dffg097tutm4npsz2exygx25ge2k9xqf5x3nf") } @Test @@ -742,6 +742,45 @@ class UriDetectionTest { runTest("ほtest.comほ", "test.com") } + @Test + fun testBeginsAndEndsWithPunctuation() { + UrlDetector.CANNOT_END_URLS_WITH.forEach { punctuation -> + runTest("${punctuation}http://test.com?s=dd", "http://test.com?s=dd") + runTest("http://test.com?s=dd$punctuation", "http://test.com?s=dd") + runTest("${punctuation}http://test.com?s=dd$punctuation", "http://test.com?s=dd") + + runTest("${punctuation}http://test.com/", "http://test.com/") + runTest("http://test.com/.", "http://test.com/") + runTest("${punctuation}http://test.com/$punctuation", "http://test.com/") + + runTest("${punctuation}http://test.com", "http://test.com") + runTest("http://test.com$punctuation", "http://test.com") + runTest("${punctuation}http://test.com$punctuation", "http://test.com") + + runTest("${punctuation}test.com", "test.com") + runTest("test.com$punctuation", "test.com") + runTest("${punctuation}test.com$punctuation", "test.com") + } + + UrlDetector.CANNOT_BEGIN_URLS_WITH.forEach { punctuation -> + runTest("${punctuation}http://test.com?s=dd", "http://test.com?s=dd") + runTest("http://test.com?s=dd$punctuation", "http://test.com?s=dd") + runTest("${punctuation}http://test.com?s=dd$punctuation", "http://test.com?s=dd") + + runTest("${punctuation}http://test.com/", "http://test.com/") + runTest("http://test.com/.", "http://test.com/") + runTest("${punctuation}http://test.com/$punctuation", "http://test.com/") + + runTest("${punctuation}http://test.com", "http://test.com") + runTest("http://test.com$punctuation", "http://test.com") + runTest("${punctuation}http://test.com$punctuation", "http://test.com") + + runTest("${punctuation}test.com", "test.com") + runTest("test.com$punctuation", "test.com") + runTest("${punctuation}test.com$punctuation", "test.com") + } + } + private fun runTest( text: String, vararg expected: String?,