Improves the order of search results
This commit is contained in:
@@ -204,6 +204,7 @@ import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
|
||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.nipC0CodeSnippets.CodeSnippetEvent
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
@@ -2276,20 +2277,32 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
}
|
||||
}
|
||||
|
||||
val dualCase =
|
||||
listOf(
|
||||
DualCase(username.lowercase(), username.uppercase()),
|
||||
)
|
||||
|
||||
val finds =
|
||||
users.filter { _, user: User ->
|
||||
(
|
||||
(user.metadataOrNull()?.anyNameStartsWith(username) == true) ||
|
||||
user.pubkeyHex.startsWith(username, true) ||
|
||||
val metadata = user.metadataOrNull()
|
||||
if (metadata == null) {
|
||||
user.pubkeyHex.startsWith(username, true) ||
|
||||
user.pubkeyNpub().startsWith(username, true)
|
||||
) &&
|
||||
(forAccount == null || (!forAccount.isHidden(user) && !user.containsAny(forAccount.hiddenUsers.flow.value.hiddenWordsCase)))
|
||||
} else {
|
||||
(
|
||||
metadata.anyNameOrAddressContains(dualCase) ||
|
||||
user.pubkeyHex.startsWith(username, true) ||
|
||||
user.pubkeyNpub().startsWith(username, true)
|
||||
) &&
|
||||
(forAccount == null || (!forAccount.isHidden(user) && !metadata.anyPropertyContains(forAccount.hiddenUsers.flow.value.hiddenWordsCase)))
|
||||
}
|
||||
}
|
||||
|
||||
return finds.sortedWith(
|
||||
compareBy(
|
||||
{ forAccount?.isFollowing(it) == false },
|
||||
{ !it.toBestDisplayName().startsWith(username, ignoreCase = true) },
|
||||
{ it.metadataOrNull()?.anyNameStartsWith(dualCase) == false },
|
||||
{ it.metadataOrNull()?.anyAddressStartsWith(dualCase) == false },
|
||||
{ it.toBestDisplayName().lowercase() },
|
||||
{ it.pubkeyHex },
|
||||
),
|
||||
|
||||
@@ -890,7 +890,7 @@ open class Note(
|
||||
return true
|
||||
}
|
||||
|
||||
if (author?.containsAny(accountChoices.hiddenWordsCase) == true) return true
|
||||
if (author?.metadataOrNull()?.anyPropertyContains(accountChoices.hiddenWordsCase) == true) return true
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
|
||||
interface UserDependencies
|
||||
@@ -141,8 +140,6 @@ class User(
|
||||
fun relayStateOrNull(): UserRelaysCache? = relays
|
||||
|
||||
fun relayState(): UserRelaysCache = relays ?: UserRelaysCache().also { relays = it }
|
||||
|
||||
fun containsAny(hiddenWordsCase: List<DualCase>) = metadataOrNull()?.containsAny(hiddenWordsCase) == true
|
||||
}
|
||||
|
||||
fun Set<User>.toHexSet() = mapTo(LinkedHashSet(size)) { it.pubkeyHex }
|
||||
|
||||
+7
-40
@@ -28,7 +28,6 @@ import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.IdentityClaimTag
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.identityClaims
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import com.vitorpamplona.quartz.utils.containsAny
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -60,48 +59,16 @@ class UserMetadataCache {
|
||||
|
||||
fun shouldUpdateWith(event: MetadataEvent) = event.createdAt > (flow.value?.createdAt ?: 0)
|
||||
|
||||
fun anyNameStartsWith(username: String): Boolean = flow.value?.info?.anyNameStartsWith(username) ?: false
|
||||
fun anyNameOrAddressContains(terms: List<DualCase>): Boolean = flow.value?.info?.anyNameOrAddressContains(terms) ?: false
|
||||
|
||||
fun containsAny(hiddenWordsCase: List<DualCase>): Boolean {
|
||||
fun anyNameStartsWith(terms: List<DualCase>): Boolean = flow.value?.info?.anyNameStartsWith(terms) ?: false
|
||||
|
||||
fun anyAddressStartsWith(terms: List<DualCase>): Boolean = flow.value?.info?.anyAddressStartsWith(terms) ?: false
|
||||
|
||||
fun anyPropertyContains(hiddenWordsCase: List<DualCase>): Boolean {
|
||||
if (hiddenWordsCase.isEmpty()) return false
|
||||
|
||||
flow.value?.let { userInfo ->
|
||||
val info = userInfo.info
|
||||
|
||||
if (info.name?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.displayName?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.picture?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.banner?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.about?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.lud06?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.lud16?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (info.nip05?.containsAny(hiddenWordsCase) == true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return flow.value?.info?.anyPropertyContains(hiddenWordsCase) ?: false
|
||||
}
|
||||
|
||||
fun bestName(): String? = flow.value?.info?.bestName()
|
||||
|
||||
Vendored
+17
-4
@@ -35,6 +35,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -85,15 +86,27 @@ class DesktopLocalCache : ICacheProvider {
|
||||
if (user != null) return listOf(user)
|
||||
}
|
||||
|
||||
val dualCase =
|
||||
listOf(
|
||||
DualCase(prefix.lowercase(), prefix.uppercase()),
|
||||
)
|
||||
|
||||
// Search by name/displayName/nip05/lud16
|
||||
return users.values
|
||||
.filter { user ->
|
||||
user.metadataOrNull()?.anyNameStartsWith(prefix) == true ||
|
||||
user.pubkeyHex.startsWith(prefix, ignoreCase = true) ||
|
||||
user.pubkeyNpub().startsWith(prefix, ignoreCase = true)
|
||||
val metadata = user.metadataOrNull()
|
||||
if (metadata == null) {
|
||||
user.pubkeyHex.startsWith(prefix, true) ||
|
||||
user.pubkeyNpub().startsWith(prefix, true)
|
||||
} else {
|
||||
metadata.anyNameOrAddressContains(dualCase) ||
|
||||
user.pubkeyHex.startsWith(prefix, true) ||
|
||||
user.pubkeyNpub().startsWith(prefix, true)
|
||||
}
|
||||
}.sortedWith(
|
||||
compareBy(
|
||||
{ !it.toBestDisplayName().startsWith(prefix, ignoreCase = true) },
|
||||
{ it.metadataOrNull()?.anyNameStartsWith(dualCase) == false },
|
||||
{ it.metadataOrNull()?.anyAddressStartsWith(dualCase) == false },
|
||||
{ it.toBestDisplayName().lowercase() },
|
||||
{ it.pubkeyHex },
|
||||
),
|
||||
|
||||
+30
-5
@@ -22,6 +22,9 @@ package com.vitorpamplona.quartz.nip01Core.metadata
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.lightning.Lud06
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import com.vitorpamplona.quartz.utils.containsAny
|
||||
import com.vitorpamplona.quartz.utils.startsWithAny
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -47,11 +50,6 @@ class UserMetadata {
|
||||
|
||||
fun anyName(): String? = displayName ?: name
|
||||
|
||||
fun anyNameStartsWith(prefix: String): Boolean =
|
||||
listOfNotNull(name, displayName, nip05, lud06, lud16).any {
|
||||
it.contains(prefix, true)
|
||||
}
|
||||
|
||||
fun lnAddress(): String? = lud16 ?: lud06
|
||||
|
||||
fun bestName(): String? = displayName ?: name
|
||||
@@ -104,6 +102,33 @@ class UserMetadata {
|
||||
if (pronouns?.isBlank() == true) pronouns = null
|
||||
}
|
||||
|
||||
fun anyNameOrAddressContains(terms: List<DualCase>): Boolean =
|
||||
name?.containsAny(terms) == true ||
|
||||
displayName?.containsAny(terms) == true ||
|
||||
lud16?.containsAny(terms) == true ||
|
||||
nip05?.containsAny(terms) == true ||
|
||||
website?.containsAny(terms) == true
|
||||
|
||||
fun anyNameStartsWith(terms: List<DualCase>): Boolean =
|
||||
name?.startsWithAny(terms) == true ||
|
||||
displayName?.startsWithAny(terms) == true
|
||||
|
||||
fun anyAddressStartsWith(terms: List<DualCase>): Boolean =
|
||||
lud16?.startsWithAny(terms) == true ||
|
||||
nip05?.startsWithAny(terms) == true ||
|
||||
website?.startsWithAny(terms) == true
|
||||
|
||||
fun anyPropertyContains(terms: List<DualCase>): Boolean =
|
||||
name?.containsAny(terms) == true ||
|
||||
displayName?.containsAny(terms) == true ||
|
||||
picture?.containsAny(terms) == true ||
|
||||
banner?.containsAny(terms) == true ||
|
||||
about?.containsAny(terms) == true ||
|
||||
lud06?.containsAny(terms) == true ||
|
||||
lud16?.containsAny(terms) == true ||
|
||||
nip05?.containsAny(terms) == true ||
|
||||
website?.containsAny(terms) == true
|
||||
|
||||
fun convertLud06toLud16IfNeeded() {
|
||||
if (lud16.isNullOrBlank()) {
|
||||
lud06?.let {
|
||||
|
||||
@@ -68,6 +68,27 @@ fun String.containsIgnoreCase(
|
||||
return false
|
||||
}
|
||||
|
||||
fun String.startsWithIgnoreCase(
|
||||
whatLowercase: String,
|
||||
whatUppercase: String,
|
||||
): Boolean {
|
||||
val termLength = min(whatUppercase.length, whatLowercase.length)
|
||||
|
||||
// If the search term is longer than the string itself, it can't be a prefix
|
||||
if (this.length < termLength) return false
|
||||
|
||||
var offset = 0
|
||||
while (offset < termLength) {
|
||||
val current = this[offset]
|
||||
if (current != whatLowercase[offset] && current != whatUppercase[offset]) {
|
||||
return false
|
||||
}
|
||||
offset++
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun String.containsAny(terms: List<DualCase>): Boolean {
|
||||
if (terms.isEmpty()) return true // Empty string is contained
|
||||
|
||||
@@ -78,6 +99,16 @@ fun String.containsAny(terms: List<DualCase>): Boolean {
|
||||
return terms.any { containsIgnoreCase(it.lowercase, it.uppercase) }
|
||||
}
|
||||
|
||||
fun String.startsWithAny(terms: List<DualCase>): Boolean {
|
||||
if (terms.isEmpty()) return true // Empty string is contained
|
||||
|
||||
if (terms.size == 1) {
|
||||
return startsWithIgnoreCase(terms[0].lowercase, terms[0].uppercase)
|
||||
}
|
||||
|
||||
return terms.any { startsWithIgnoreCase(it.lowercase, it.uppercase) }
|
||||
}
|
||||
|
||||
class DualCase(
|
||||
val lowercase: String,
|
||||
val uppercase: String,
|
||||
|
||||
Reference in New Issue
Block a user