Applies new UrlDetector to the RichTextViewer
This commit is contained in:
@@ -79,7 +79,7 @@ object CachedUrlParser {
|
|||||||
cached
|
cached
|
||||||
} else {
|
} else {
|
||||||
val urlSet = UrlParser().parseValidUrls(content)
|
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)
|
parsedUrlsCache.put(key, newUrls)
|
||||||
newUrls
|
newUrls
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment
|
|||||||
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
|
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RelayUrlSegment
|
import com.vitorpamplona.amethyst.commons.richtext.RelayUrlSegment
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
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.SecretEmoji
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.Segment
|
import com.vitorpamplona.amethyst.commons.richtext.Segment
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.VideoSegment
|
import com.vitorpamplona.amethyst.commons.richtext.VideoSegment
|
||||||
@@ -168,6 +169,10 @@ fun RenderStrangeNamePreview() {
|
|||||||
is RelayUrlSegment -> {
|
is RelayUrlSegment -> {
|
||||||
ClickableRelayUrl(word.segmentText, EmptyNav())
|
ClickableRelayUrl(word.segmentText, EmptyNav())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is SchemelessUrlSegment -> {
|
||||||
|
NoProtocolUrlRenderer(word.segmentText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,6 +230,10 @@ fun RenderRegularPreview() {
|
|||||||
is RegularTextSegment -> {
|
is RegularTextSegment -> {
|
||||||
Text(word.segmentText)
|
Text(word.segmentText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is SchemelessUrlSegment -> {
|
||||||
|
NoProtocolUrlRenderer(word.segmentText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,6 +270,8 @@ fun RenderRegularPreview2() {
|
|||||||
is RegularTextSegment -> Text(word.segmentText)
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
|
||||||
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, EmptyNav())
|
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, EmptyNav())
|
||||||
|
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,6 +321,8 @@ fun RenderRegularPreview3() {
|
|||||||
is RegularTextSegment -> Text(word.segmentText)
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
|
||||||
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, EmptyNav())
|
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, EmptyNav())
|
||||||
|
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,6 +499,8 @@ private fun RenderWordWithoutPreview(
|
|||||||
is RegularTextSegment -> Text(word.segmentText)
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
|
||||||
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, nav)
|
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, nav)
|
||||||
|
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word.segmentText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,6 +532,7 @@ private fun RenderWordWithPreview(
|
|||||||
is RegularTextSegment -> Text(word.segmentText)
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
is Base64Segment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
is Base64Segment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
is RelayUrlSegment -> ClickableRelayUrl(word.segmentText, nav)
|
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
|
@Composable
|
||||||
fun RenderCustomEmoji(
|
fun RenderCustomEmoji(
|
||||||
word: String,
|
word: String,
|
||||||
|
|||||||
+2
-2
@@ -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.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
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.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -517,7 +517,7 @@ open class ChannelNewMessageViewModel :
|
|||||||
emojiSuggestions?.reset()
|
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) {
|
open fun addToMessage(it: String) {
|
||||||
updateMessage(TextFieldValue(message.text + " " + it))
|
updateMessage(TextFieldValue(message.text + " " + it))
|
||||||
|
|||||||
+11
-11
@@ -161,7 +161,7 @@ class RichTextParser {
|
|||||||
|
|
||||||
val emojiMap = CustomEmoji.createEmojiMap(tags.lists)
|
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)
|
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)) {
|
if (urls.withScheme.contains(word)) return LinkSegment(word)
|
||||||
return LinkSegment("https://$word")
|
|
||||||
} else if (urls.withScheme.contains(word)) {
|
if (urls.emails.contains(word)) return EmailSegment(word)
|
||||||
return LinkSegment(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)
|
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 (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 (isPotentialPhoneNumber(word) && !isDate(word)) {
|
||||||
if (Patterns.PHONE.matches(word)) return PhoneSegment(word)
|
if (Patterns.PHONE.matches(word)) return PhoneSegment(word)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -143,6 +143,11 @@ class RelayUrlSegment(
|
|||||||
segment: String,
|
segment: String,
|
||||||
) : Segment(segment)
|
) : Segment(segment)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class SchemelessUrlSegment(
|
||||||
|
segment: String,
|
||||||
|
) : Segment(segment)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class RegularTextSegment(
|
class RegularTextSegment(
|
||||||
segment: String,
|
segment: String,
|
||||||
|
|||||||
+12
-17
@@ -27,6 +27,8 @@ class Urls(
|
|||||||
val withScheme: Set<String> = emptySet(),
|
val withScheme: Set<String> = emptySet(),
|
||||||
val withoutScheme: Set<String> = emptySet(),
|
val withoutScheme: Set<String> = emptySet(),
|
||||||
val emails: Set<String> = emptySet(),
|
val emails: Set<String> = emptySet(),
|
||||||
|
val bech32s: Set<String> = emptySet(),
|
||||||
|
val relayUrls: Set<String> = emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
class UrlParser {
|
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 =
|
fun Url.isEmail(): Boolean =
|
||||||
urlMarker.hasUsernamePassword() &&
|
urlMarker.hasUsernamePassword() &&
|
||||||
@@ -54,33 +56,24 @@ class UrlParser {
|
|||||||
originalUrl.contains('@') &&
|
originalUrl.contains('@') &&
|
||||||
path == "/"
|
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 {
|
fun parseValidUrls(content: String): Urls {
|
||||||
val urls = UrlDetector(content).detect()
|
val urls = UrlDetector(content).detect()
|
||||||
|
|
||||||
val completeUrls = mutableSetOf<String>()
|
val completeUrls = mutableSetOf<String>()
|
||||||
val urlsWithoutScheme = mutableSetOf<String>()
|
val urlsWithoutScheme = mutableSetOf<String>()
|
||||||
val emails = mutableSetOf<String>()
|
val emails = mutableSetOf<String>()
|
||||||
|
val bech32 = mutableSetOf<String>()
|
||||||
|
val relays = mutableSetOf<String>()
|
||||||
|
|
||||||
urls.forEach {
|
urls.forEach {
|
||||||
if (it.isValidTopLevelDomain()) {
|
if (it.isValidTopLevelDomain()) {
|
||||||
if (it.wroteWithSchema()) {
|
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)
|
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 {
|
} else {
|
||||||
// emails are understood as urls from the detector.
|
// emails are understood as urls from the detector.
|
||||||
@@ -99,6 +92,8 @@ class UrlParser {
|
|||||||
withScheme = completeUrls,
|
withScheme = completeUrls,
|
||||||
withoutScheme = urlsWithoutScheme,
|
withoutScheme = urlsWithoutScheme,
|
||||||
emails = emails,
|
emails = emails,
|
||||||
|
bech32s = bech32,
|
||||||
|
relayUrls = relays,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -70,7 +70,7 @@ class RichTextParserMultibyteTest {
|
|||||||
.parseText(text, EmptyTagList, null)
|
.parseText(text, EmptyTagList, null)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Vitor, você http://test.com? \uD83E\uDD7A",
|
"Vitor, você http://test.com ? \uD83E\uDD7A",
|
||||||
state.paragraphs.joinToString("\n") { it.words.joinToString(" ") { it.segmentText } },
|
state.paragraphs.joinToString("\n") { it.words.joinToString(" ") { it.segmentText } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -157,9 +157,9 @@ class RichTextParserMultibyteTest {
|
|||||||
val state = RichTextParser().parseText(text, EmptyTagList, null)
|
val state = RichTextParser().parseText(text, EmptyTagList, null)
|
||||||
val allSegments = state.paragraphs.flatMap { it.words }
|
val allSegments = state.paragraphs.flatMap { it.words }
|
||||||
|
|
||||||
val urlSegments = allSegments.filterIsInstance<LinkSegment>()
|
val urlSegments = allSegments.filterIsInstance<SchemelessUrlSegment>()
|
||||||
assertTrue("Should have SchemelessUrlSegment", urlSegments.isNotEmpty())
|
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<RegularTextSegment>()
|
val textSegments = allSegments.filterIsInstance<RegularTextSegment>()
|
||||||
assertTrue("Should have prefix ああ", textSegments.any { it.segmentText == "ああ" })
|
assertTrue("Should have prefix ああ", textSegments.any { it.segmentText == "ああ" })
|
||||||
@@ -172,9 +172,9 @@ class RichTextParserMultibyteTest {
|
|||||||
val state = RichTextParser().parseText(text, EmptyTagList, null)
|
val state = RichTextParser().parseText(text, EmptyTagList, null)
|
||||||
val allSegments = state.paragraphs.flatMap { it.words }
|
val allSegments = state.paragraphs.flatMap { it.words }
|
||||||
|
|
||||||
val urlSegments = allSegments.filterIsInstance<LinkSegment>()
|
val urlSegments = allSegments.filterIsInstance<SchemelessUrlSegment>()
|
||||||
assertTrue("Should have SchemelessUrlSegment", urlSegments.isNotEmpty())
|
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<RegularTextSegment>()
|
val textSegments = allSegments.filterIsInstance<RegularTextSegment>()
|
||||||
assertTrue("Should have suffix ああ", textSegments.any { it.segmentText == "ああ" })
|
assertTrue("Should have suffix ああ", textSegments.any { it.segmentText == "ああ" })
|
||||||
|
|||||||
+25
-24
@@ -749,52 +749,52 @@ class RichTextParserTest {
|
|||||||
"RegularText(Events (%) Relay)",
|
"RegularText(Events (%) Relay)",
|
||||||
"RegularText(33.4K)",
|
"RegularText(33.4K)",
|
||||||
"RegularText((4.6%))",
|
"RegularText((4.6%))",
|
||||||
"Link(https://relay.shitforce.one)",
|
"SchemelessUrl(relay.shitforce.one)",
|
||||||
"RegularText(32.9K)",
|
"RegularText(32.9K)",
|
||||||
"RegularText((4.6%))",
|
"RegularText((4.6%))",
|
||||||
"Link(https://relayable.org)",
|
"SchemelessUrl(relayable.org)",
|
||||||
"RegularText(26.6K)",
|
"RegularText(26.6K)",
|
||||||
"RegularText((3.7%))",
|
"RegularText((3.7%))",
|
||||||
"Link(https://universe.nostrich.land)",
|
"SchemelessUrl(universe.nostrich.land)",
|
||||||
"RegularText(22.8K)",
|
"RegularText(22.8K)",
|
||||||
"RegularText((3.2%))",
|
"RegularText((3.2%))",
|
||||||
"Link(https://nos.lol)",
|
"SchemelessUrl(nos.lol)",
|
||||||
"RegularText(22.7K)",
|
"RegularText(22.7K)",
|
||||||
"RegularText((3.1%))",
|
"RegularText((3.1%))",
|
||||||
"Link(https://universe.nostrich.land?lang=zh)",
|
"SchemelessUrl(universe.nostrich.land?lang=zh)",
|
||||||
"RegularText(22.5K)",
|
"RegularText(22.5K)",
|
||||||
"RegularText((3.1%))",
|
"RegularText((3.1%))",
|
||||||
"Link(https://universe.nostrich.land?lang=en)",
|
"SchemelessUrl(universe.nostrich.land?lang=en)",
|
||||||
"RegularText(21.2K)",
|
"RegularText(21.2K)",
|
||||||
"RegularText((2.9%))",
|
"RegularText((2.9%))",
|
||||||
"Link(https://relay.damus.io)",
|
"SchemelessUrl(relay.damus.io)",
|
||||||
"RegularText(20.6K)",
|
"RegularText(20.6K)",
|
||||||
"RegularText((2.9%))",
|
"RegularText((2.9%))",
|
||||||
"Link(https://relay.nostr.wirednet.jp)",
|
"SchemelessUrl(relay.nostr.wirednet.jp)",
|
||||||
"RegularText(20.1K)",
|
"RegularText(20.1K)",
|
||||||
"RegularText((2.8%))",
|
"RegularText((2.8%))",
|
||||||
"Link(https://offchain.pub)",
|
"SchemelessUrl(offchain.pub)",
|
||||||
"RegularText(19.9K)",
|
"RegularText(19.9K)",
|
||||||
"RegularText((2.8%))",
|
"RegularText((2.8%))",
|
||||||
"Link(https://nostr.rocks)",
|
"SchemelessUrl(nostr.rocks)",
|
||||||
"RegularText(19.5K)",
|
"RegularText(19.5K)",
|
||||||
"RegularText((2.7%))",
|
"RegularText((2.7%))",
|
||||||
"Link(https://relay.wellorder.net)",
|
"SchemelessUrl(relay.wellorder.net)",
|
||||||
"RegularText(19.4K)",
|
"RegularText(19.4K)",
|
||||||
"RegularText((2.7%))",
|
"RegularText((2.7%))",
|
||||||
"Link(https://nostr.oxtr.dev)",
|
"SchemelessUrl(nostr.oxtr.dev)",
|
||||||
"RegularText(19K)",
|
"RegularText(19K)",
|
||||||
"RegularText((2.6%))",
|
"RegularText((2.6%))",
|
||||||
"Link(https://universe.nostrich.land?lang=ja)",
|
"SchemelessUrl(universe.nostrich.land?lang=ja)",
|
||||||
"RegularText(18.4K)",
|
"RegularText(18.4K)",
|
||||||
"RegularText((2.6%))",
|
"RegularText((2.6%))",
|
||||||
"Link(https://relay.mostr.pub)",
|
"SchemelessUrl(relay.mostr.pub)",
|
||||||
"RegularText(17.5K)",
|
"RegularText(17.5K)",
|
||||||
"RegularText((2.4%))",
|
"RegularText((2.4%))",
|
||||||
"Link(https://universe.nostrich.land?lang=zh)",
|
"SchemelessUrl(universe.nostrich.land?lang=zh)",
|
||||||
"RegularText(16.3K)",
|
"RegularText(16.3K)",
|
||||||
"RegularText((2.3%))",
|
"RegularText((2.3%))",
|
||||||
"Link(https://nostr.bitcoiner.social)",
|
"SchemelessUrl(nostr.bitcoiner.social)",
|
||||||
"RegularText()",
|
"RegularText()",
|
||||||
"RegularText(30 day global new events)",
|
"RegularText(30 day global new events)",
|
||||||
"RegularText()",
|
"RegularText()",
|
||||||
@@ -1633,7 +1633,7 @@ class RichTextParserTest {
|
|||||||
"RegularText(eb3b94533dafeb8ebd58a4947a3dce11d83a9931c622bdf30a4257d3347ee1bf)",
|
"RegularText(eb3b94533dafeb8ebd58a4947a3dce11d83a9931c622bdf30a4257d3347ee1bf)",
|
||||||
"HashTag(#109)",
|
"HashTag(#109)",
|
||||||
"RegularText(3%)",
|
"RegularText(3%)",
|
||||||
"Link(https://Nostr-Check.com)",
|
"SchemelessUrl(Nostr-Check.com)",
|
||||||
"RegularText(,)",
|
"RegularText(,)",
|
||||||
"Email(freeverification@Nostr-Check.com)",
|
"Email(freeverification@Nostr-Check.com)",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
@@ -1910,7 +1910,7 @@ class RichTextParserTest {
|
|||||||
"RegularText(d162a53c3b0bfb5c3ebd787d7b08feab206b112362eca25aa291251cd70fe225)",
|
"RegularText(d162a53c3b0bfb5c3ebd787d7b08feab206b112362eca25aa291251cd70fe225)",
|
||||||
"HashTag(#154)",
|
"HashTag(#154)",
|
||||||
"RegularText(3%)",
|
"RegularText(3%)",
|
||||||
"Link(https://MR.Rabbit)",
|
"SchemelessUrl(MR.Rabbit)",
|
||||||
"RegularText(,)",
|
"RegularText(,)",
|
||||||
"Email(Mr.Rabbit@BitcoinNostr.com)",
|
"Email(Mr.Rabbit@BitcoinNostr.com)",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
@@ -2087,7 +2087,7 @@ class RichTextParserTest {
|
|||||||
"RegularText(39ed0aea2338477103e0b5a820532ded27dbfe4f203e7270392d55f63e60271a)",
|
"RegularText(39ed0aea2338477103e0b5a820532ded27dbfe4f203e7270392d55f63e60271a)",
|
||||||
"HashTag(#183)",
|
"HashTag(#183)",
|
||||||
"RegularText(2%)",
|
"RegularText(2%)",
|
||||||
"Link(https://Ancap.su)",
|
"SchemelessUrl(Ancap.su)",
|
||||||
"RegularText(,)",
|
"RegularText(,)",
|
||||||
"Email(ancapsu@getalby.com)",
|
"Email(ancapsu@getalby.com)",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
@@ -2488,7 +2488,7 @@ class RichTextParserTest {
|
|||||||
"HashTag(#249)",
|
"HashTag(#249)",
|
||||||
"RegularText(2%)",
|
"RegularText(2%)",
|
||||||
"RegularText(micmad,)",
|
"RegularText(micmad,)",
|
||||||
"Link(https://miceliomad@miceliomad.github.io/nostr/)",
|
"SchemelessUrl(miceliomad@miceliomad.github.io/nostr/)",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
"RegularText(cd806edcf8ff40ea94fa574ea9cd97da16e5beb2b85aac6e1d648b8388504343)",
|
"RegularText(cd806edcf8ff40ea94fa574ea9cd97da16e5beb2b85aac6e1d648b8388504343)",
|
||||||
"HashTag(#250)",
|
"HashTag(#250)",
|
||||||
@@ -2667,7 +2667,7 @@ class RichTextParserTest {
|
|||||||
"HashTag(#278)",
|
"HashTag(#278)",
|
||||||
"RegularText(2%)",
|
"RegularText(2%)",
|
||||||
"RegularText(𝕬𝖓𝖔𝖓𝖞𝖒𝖔𝖚𝖘,)",
|
"RegularText(𝕬𝖓𝖔𝖓𝖞𝖒𝖔𝖚𝖘,)",
|
||||||
"Link(https://zapper.lol)",
|
"SchemelessUrl(zapper.lol)",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
"RegularText(96aceca84aa381eeda084167dd317e1bf7a45d874cd14147f0a9e0df86fb44c2)",
|
"RegularText(96aceca84aa381eeda084167dd317e1bf7a45d874cd14147f0a9e0df86fb44c2)",
|
||||||
"HashTag(#279)",
|
"HashTag(#279)",
|
||||||
@@ -3125,7 +3125,7 @@ class RichTextParserTest {
|
|||||||
"RegularText(c4a9caef93e93f484274c04cd981d1de1424902451aca2f5602bd0835fe4393d)",
|
"RegularText(c4a9caef93e93f484274c04cd981d1de1424902451aca2f5602bd0835fe4393d)",
|
||||||
"HashTag(#354)",
|
"HashTag(#354)",
|
||||||
"RegularText(2%)",
|
"RegularText(2%)",
|
||||||
"Link(https://smies.me)",
|
"SchemelessUrl(smies.me)",
|
||||||
"RegularText(,)",
|
"RegularText(,)",
|
||||||
"Email(jacksmies@iris.to)",
|
"Email(jacksmies@iris.to)",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
@@ -4006,7 +4006,7 @@ class RichTextParserTest {
|
|||||||
"RegularText(21a7014db2ba17acc8bbb9496645084866b46e1ba0062a80513afda405450183)",
|
"RegularText(21a7014db2ba17acc8bbb9496645084866b46e1ba0062a80513afda405450183)",
|
||||||
"HashTag(#499)",
|
"HashTag(#499)",
|
||||||
"RegularText(1%)",
|
"RegularText(1%)",
|
||||||
"Link(https://baller.hodl)",
|
"SchemelessUrl(baller.hodl)",
|
||||||
"RegularText(,)",
|
"RegularText(,)",
|
||||||
"RegularText()",
|
"RegularText()",
|
||||||
"RegularText(-)",
|
"RegularText(-)",
|
||||||
@@ -4287,7 +4287,8 @@ class RichTextParserTest {
|
|||||||
listOf<String>(
|
listOf<String>(
|
||||||
"RegularText(That’s)",
|
"RegularText(That’s)",
|
||||||
"RegularText(it!)",
|
"RegularText(it!)",
|
||||||
"Link(http://vitorpamplona.com/.)",
|
"Link(http://vitorpamplona.com/)",
|
||||||
|
"RegularText(.)",
|
||||||
"RegularText(That’s)",
|
"RegularText(That’s)",
|
||||||
"RegularText(the)",
|
"RegularText(the)",
|
||||||
"RegularText(note)",
|
"RegularText(note)",
|
||||||
|
|||||||
+3
-3
@@ -166,7 +166,7 @@ class UrlParserTest {
|
|||||||
fun testNostrUrls() =
|
fun testNostrUrls() =
|
||||||
test(
|
test(
|
||||||
"nostr:npub1aabbcc",
|
"nostr:npub1aabbcc",
|
||||||
Urls(withScheme = setOf("nostr:npub1aabbcc")),
|
Urls(bech32s = setOf("nostr:npub1aabbcc")),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -261,14 +261,14 @@ class UrlParserTest {
|
|||||||
fun testRelayUrl() =
|
fun testRelayUrl() =
|
||||||
test(
|
test(
|
||||||
"wss://test.com",
|
"wss://test.com",
|
||||||
Urls(withScheme = setOf("wss://test.com")),
|
Urls(relayUrls = setOf("wss://test.com")),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBech12() =
|
fun testBech12() =
|
||||||
test(
|
test(
|
||||||
"nostr:npub1aabbcc",
|
"nostr:npub1aabbcc",
|
||||||
Urls(withScheme = setOf("nostr:npub1aabbcc")),
|
Urls(bech32s = setOf("nostr:npub1aabbcc")),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+9
@@ -220,6 +220,15 @@ class DomainNameReader(
|
|||||||
// done = true
|
// done = true
|
||||||
|
|
||||||
lastWasAscii = isAscii
|
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++
|
index++
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-3
@@ -650,7 +650,9 @@ class UrlDetector(
|
|||||||
if (state == ReadEndState.ValidUrl && buffer.isNotEmpty()) {
|
if (state == ReadEndState.ValidUrl && buffer.isNotEmpty()) {
|
||||||
// Add the url to the list of good urls.
|
// Add the url to the list of good urls.
|
||||||
if (buffer.isNotEmpty()) {
|
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 {
|
companion object {
|
||||||
private val VALID_SCHEMES_NO_SLASHES: List<String> =
|
val VALID_SCHEMES_NO_SLASHES: List<String> =
|
||||||
listOf(
|
listOf(
|
||||||
"http:",
|
"http:",
|
||||||
"https:",
|
"https:",
|
||||||
@@ -681,9 +683,42 @@ class UrlDetector(
|
|||||||
"blossom:",
|
"blossom:",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val VALID_SCHEMES =
|
val VALID_SCHEMES =
|
||||||
VALID_SCHEMES_NO_SLASHES.map {
|
VALID_SCHEMES_NO_SLASHES.map {
|
||||||
"$it//"
|
"$it//"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val CANNOT_BEGIN_URLS_WITH =
|
||||||
|
setOf(
|
||||||
|
',',
|
||||||
|
'.',
|
||||||
|
';',
|
||||||
|
'?',
|
||||||
|
'!',
|
||||||
|
')',
|
||||||
|
'}',
|
||||||
|
'(',
|
||||||
|
'{',
|
||||||
|
'\u3002',
|
||||||
|
'\uFF0E',
|
||||||
|
'\uFF61',
|
||||||
|
)
|
||||||
|
|
||||||
|
val CANNOT_END_URLS_WITH =
|
||||||
|
setOf(
|
||||||
|
',',
|
||||||
|
'.',
|
||||||
|
';',
|
||||||
|
'?',
|
||||||
|
'!',
|
||||||
|
':',
|
||||||
|
')',
|
||||||
|
'}',
|
||||||
|
'(',
|
||||||
|
'{',
|
||||||
|
'\u3002',
|
||||||
|
'\uFF0E',
|
||||||
|
'\uFF61',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-7
@@ -58,7 +58,7 @@ class UriDetectionTest {
|
|||||||
runTest(
|
runTest(
|
||||||
"the url google.com is a lot better then www.google.com.",
|
"the url google.com is a lot better then www.google.com.",
|
||||||
"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" +
|
"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.",
|
"\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",
|
"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",
|
"www.www",
|
||||||
"yahoo.com",
|
"yahoo.com",
|
||||||
"yahoo.com.br",
|
"yahoo.com.br",
|
||||||
"hello.hello.",
|
"hello.hello",
|
||||||
"hello.com",
|
"hello.com",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -395,7 +395,7 @@ class UriDetectionTest {
|
|||||||
runTest("%2ewtfismyip")
|
runTest("%2ewtfismyip")
|
||||||
runTest("wtfismyip%2e")
|
runTest("wtfismyip%2e")
|
||||||
runTest("wtfismyip%2ecom%2e", "wtfismyip%2ecom%2e")
|
runTest("wtfismyip%2ecom%2e", "wtfismyip%2ecom%2e")
|
||||||
runTest("wtfismyip%2ecom.", "wtfismyip%2ecom.")
|
runTest("wtfismyip%2ecom.", "wtfismyip%2ecom")
|
||||||
runTest("%2ewtfismyip%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", "hello:password@go12")
|
runTest("hello:password@go12", "hello:password@go12")
|
||||||
runTest("hello:password@go12 lala", "hello:password@go12")
|
runTest("hello:password@go12 lala", "hello:password@go12")
|
||||||
runTest("hello.com..", "hello.com.")
|
runTest("hello.com..", "hello.com")
|
||||||
runTest("a/")
|
runTest("a/")
|
||||||
runTest("4/5")
|
runTest("4/5")
|
||||||
runTest("concerns/worries")
|
runTest("concerns/worries")
|
||||||
@@ -694,7 +694,7 @@ class UriDetectionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNostr() {
|
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
|
@Test
|
||||||
@@ -704,7 +704,7 @@ class UriDetectionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNostrSlashes() {
|
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
|
@Test
|
||||||
@@ -742,6 +742,45 @@ class UriDetectionTest {
|
|||||||
runTest("ほtest.comほ", "test.com")
|
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(
|
private fun runTest(
|
||||||
text: String,
|
text: String,
|
||||||
vararg expected: String?,
|
vararg expected: String?,
|
||||||
|
|||||||
Reference in New Issue
Block a user