mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Pretty fuzzy search, close #122
This commit is contained in:
parent
234eeb7540
commit
f38062e0ec
@ -12,10 +12,7 @@ package net.mamoe.mirai.console.command.description
|
|||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.*
|
||||||
import net.mamoe.mirai.console.internal.command.fuzzySearchMember
|
import net.mamoe.mirai.console.internal.command.fuzzySearchMember
|
||||||
import net.mamoe.mirai.contact.Friend
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.contact.Group
|
|
||||||
import net.mamoe.mirai.contact.Member
|
|
||||||
import net.mamoe.mirai.contact.User
|
|
||||||
import net.mamoe.mirai.getFriendOrNull
|
import net.mamoe.mirai.getFriendOrNull
|
||||||
import net.mamoe.mirai.getGroupOrNull
|
import net.mamoe.mirai.getGroupOrNull
|
||||||
import net.mamoe.mirai.message.data.At
|
import net.mamoe.mirai.message.data.At
|
||||||
@ -288,9 +285,25 @@ internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArg
|
|||||||
|
|
||||||
fun Group.findMemberOrFail(idOrCard: String): Member {
|
fun Group.findMemberOrFail(idOrCard: String): Member {
|
||||||
if (idOrCard == "\$") return members.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员")
|
if (idOrCard == "\$") return members.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员")
|
||||||
return idOrCard.toLongOrNull()?.let { getOrNull(it) }
|
idOrCard.toLongOrNull()?.let { getOrNull(it) }?.let { return it }
|
||||||
?: fuzzySearchMember(idOrCard)
|
this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard) }?.let { return it }
|
||||||
?: illegalArgument("无法找到目标群员 $idOrCard")
|
this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard, ignoreCase = true) }?.let { return it }
|
||||||
|
|
||||||
|
val candidates = this.fuzzySearchMember(idOrCard)
|
||||||
|
candidates.singleOrNull()?.let {
|
||||||
|
if (it.second == 1.0) return it.first // single match
|
||||||
|
}
|
||||||
|
if (candidates.isEmpty()) {
|
||||||
|
illegalArgument("无法找到成员 $idOrCard")
|
||||||
|
} else {
|
||||||
|
var index = 1
|
||||||
|
illegalArgument("无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" +
|
||||||
|
candidates.joinToString("\n", limit = 6) {
|
||||||
|
val percentage = (it.second * 100).toDecimalPlace(0)
|
||||||
|
"#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4%
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CommandSender.inferBotOrFail(): Bot =
|
fun CommandSender.inferBotOrFail(): Bot =
|
||||||
@ -306,3 +319,28 @@ internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArg
|
|||||||
fun CommandSender.inferFriendOrFail(): Friend =
|
fun CommandSender.inferFriendOrFail(): Friend =
|
||||||
(this as? FriendCommandSender)?.user ?: illegalArgument("当前语境下无法推断目标好友")
|
(this as? FriendCommandSender)?.user ?: illegalArgument("当前语境下无法推断目标好友")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Double.toDecimalPlace(n: Int): String {
|
||||||
|
return "%.${n}f".format(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun String.truncate(lengthLimit: Int, replacement: String = "..."): String = buildString {
|
||||||
|
var lengthSum = 0
|
||||||
|
for (char in this@truncate) {
|
||||||
|
lengthSum += char.chineseLength()
|
||||||
|
if (lengthSum > lengthLimit) {
|
||||||
|
append(replacement)
|
||||||
|
return toString()
|
||||||
|
} else append(char)
|
||||||
|
}
|
||||||
|
return toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Char.chineseLength(): Int {
|
||||||
|
return when (this) {
|
||||||
|
in '\u0000'..'\u007F' -> 1
|
||||||
|
in '\u0080'..'\u07FF' -> 2
|
||||||
|
in '\u0800'..'\uFFFF' -> 3
|
||||||
|
else -> 4
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,6 @@ import net.mamoe.mirai.console.command.*
|
|||||||
import net.mamoe.mirai.console.command.description.CommandArgumentParserException
|
import net.mamoe.mirai.console.command.description.CommandArgumentParserException
|
||||||
import net.mamoe.mirai.contact.Group
|
import net.mamoe.mirai.contact.Group
|
||||||
import net.mamoe.mirai.contact.Member
|
import net.mamoe.mirai.contact.Member
|
||||||
import net.mamoe.mirai.contact.nameCardOrNick
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
@ -97,8 +96,38 @@ internal inline fun <T : Any> Collection<T>.fuzzySearchOnly(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
|
/**
|
||||||
return this.members.fuzzySearch(nameCardTarget) { it.nameCardOrNick }
|
* @return candidates
|
||||||
|
*/
|
||||||
|
internal fun Group.fuzzySearchMember(
|
||||||
|
nameCardTarget: String,
|
||||||
|
minRate: Double = 0.5, // 参与判断, 用于提示可能的解
|
||||||
|
matchRate: Double = 0.6,// 最终选择的最少需要的匹配率, 减少歧义
|
||||||
|
/**
|
||||||
|
* 如果有多个值超过 [matchRate], 并相互差距小于等于 [disambiguationRate], 则认为有较大歧义风险, 返回可能的解的列表.
|
||||||
|
*/
|
||||||
|
disambiguationRate: Double = 0.1,
|
||||||
|
): List<Pair<Member, Double>> {
|
||||||
|
val candidates = (this.members + botAsMember)
|
||||||
|
.associateWith { it.nameCard.fuzzyMatchWith(nameCardTarget) }
|
||||||
|
.filter { it.value >= minRate }
|
||||||
|
.toList()
|
||||||
|
.sortedByDescending { it.second }
|
||||||
|
|
||||||
|
val bestMatches = candidates.filter { it.second >= matchRate }
|
||||||
|
|
||||||
|
return when {
|
||||||
|
bestMatches.isEmpty() -> candidates
|
||||||
|
bestMatches.size == 1 -> listOf(bestMatches.single().first to 1.0)
|
||||||
|
else -> {
|
||||||
|
if (bestMatches.first().second - bestMatches.last().second <= disambiguationRate) {
|
||||||
|
// resolution ambiguity
|
||||||
|
candidates
|
||||||
|
} else {
|
||||||
|
listOf(bestMatches.first().first to 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user