Update version to 1.8.0

Add SendVoiceMessage tool
Move prompt to SystemPrompt.md file
Add tool calling message switch config
This commit is contained in:
2025-08-31 15:57:16 +08:00
parent e79bcd9983
commit af17f1e698
13 changed files with 315 additions and 80 deletions

View File

@@ -7,15 +7,22 @@ plugins {
} }
group = "top.jie65535.mirai" group = "top.jie65535.mirai"
version = "1.7.0" version = "1.8.0"
mirai { mirai {
jvmTarget = JavaVersion.VERSION_11 jvmTarget = JavaVersion.VERSION_11
noTestCore = true
setupConsoleTestRuntime {
// 移除 mirai-core 依赖
classpath = classpath.filter {
!it.nameWithoutExtension.startsWith("mirai-core-jvm")
}
}
} }
repositories { repositories {
mavenCentral()
maven("https://maven.aliyun.com/repository/public") maven("https://maven.aliyun.com/repository/public")
mavenCentral()
} }
val openaiClientVersion = "4.0.1" val openaiClientVersion = "4.0.1"
@@ -23,6 +30,7 @@ val ktorVersion = "3.0.3"
val jLatexMathVersion = "1.0.7" val jLatexMathVersion = "1.0.7"
val commonTextVersion = "1.13.0" val commonTextVersion = "1.13.0"
val hibernateVersion = "2.9.0" val hibernateVersion = "2.9.0"
val overflowVersion = "1.0.7"
dependencies { dependencies {
implementation("com.aallam.openai:openai-client:$openaiClientVersion") implementation("com.aallam.openai:openai-client:$openaiClientVersion")
@@ -32,4 +40,6 @@ dependencies {
// 聊天记录插件 // 聊天记录插件
compileOnly("xyz.cssxsh.mirai:mirai-hibernate-plugin:$hibernateVersion") compileOnly("xyz.cssxsh.mirai:mirai-hibernate-plugin:$hibernateVersion")
testConsoleRuntime("top.mrxiaom.mirai:overflow-core:$overflowVersion")
} }

View File

@@ -46,7 +46,7 @@ object JChatGPT : KotlinPlugin(
JvmPluginDescription( JvmPluginDescription(
id = "top.jie65535.mirai.JChatGPT", id = "top.jie65535.mirai.JChatGPT",
name = "J ChatGPT", name = "J ChatGPT",
version = "1.7.0", version = "1.8.0",
) { ) {
author("jie65535") author("jie65535")
// dependsOn("xyz.cssxsh.mirai.plugin.mirai-hibernate-plugin", true) // dependsOn("xyz.cssxsh.mirai.plugin.mirai-hibernate-plugin", true)
@@ -57,8 +57,14 @@ object JChatGPT : KotlinPlugin(
*/ */
private var includeHistory: Boolean = false private var includeHistory: Boolean = false
/**
* 聊天权限
*/
val chatPermission = PermissionId("JChatGPT", "Chat") val chatPermission = PermissionId("JChatGPT", "Chat")
/**
* 唤醒关键字
*/
private var keyword: Regex? = null private var keyword: Regex? = null
override fun onEnable() { override fun onEnable() {
@@ -130,7 +136,7 @@ object JChatGPT : KotlinPlugin(
private fun getSystemPrompt(event: MessageEvent): String { private fun getSystemPrompt(event: MessageEvent): String {
val now = OffsetDateTime.now() val now = OffsetDateTime.now()
val prompt = StringBuilder(PluginConfig.prompt) val prompt = StringBuilder(LargeLanguageModels.systemPrompt)
fun replace(target: String, replacement: () -> String) { fun replace(target: String, replacement: () -> String) {
val i = prompt.indexOf(target) val i = prompt.indexOf(target)
if (i != -1) { if (i != -1) {
@@ -297,7 +303,7 @@ object JChatGPT : KotlinPlugin(
val imageUrl = runBlocking { val imageUrl = runBlocking {
it.queryUrl() it.queryUrl()
} }
"![图片]($imageUrl)" "![${if (it.isEmoji) "表情包" else "图片"}]($imageUrl)"
} catch (e: Throwable) { } catch (e: Throwable) {
logger.warning("图片地址获取失败", e) logger.warning("图片地址获取失败", e)
it.content it.content
@@ -320,13 +326,13 @@ object JChatGPT : KotlinPlugin(
try { try {
val history = mutableListOf<ChatMessage>() val history = mutableListOf<ChatMessage>()
if (PluginConfig.prompt.isNotEmpty()) {
val prompt = getSystemPrompt(event) val prompt = getSystemPrompt(event)
if (PluginConfig.logPrompt) { if (PluginConfig.logPrompt) {
logger.info("Prompt: $prompt") logger.info("Prompt: $prompt")
}
history.add(ChatMessage(ChatRole.System, prompt))
} }
history.add(ChatMessage(ChatRole.System, prompt))
val historyText = getHistory(event) val historyText = getHistory(event)
logger.info("History: $historyText") logger.info("History: $historyText")
history.add(ChatMessage.User(historyText)) history.add(ChatMessage.User(historyText))
@@ -563,6 +569,9 @@ object JChatGPT : KotlinPlugin(
// 发送组合消息 // 发送组合消息
SendCompositeMessage(), SendCompositeMessage(),
// 发送语音消息
SendVoiceMessage(),
// 结束循环 // 结束循环
StopLoopAgent(), StopLoopAgent(),
@@ -669,7 +678,7 @@ object JChatGPT : KotlinPlugin(
val agent = myTools.find { it.tool.function.name == function.name } val agent = myTools.find { it.tool.function.name == function.name }
?: return "Function ${function.name} not found" ?: return "Function ${function.name} not found"
// 提示正在执行函数 // 提示正在执行函数
val receipt = if (agent.loadingMessage.isNotEmpty()) { val receipt = if (PluginConfig.showToolCallingMessage && agent.loadingMessage.isNotEmpty()) {
event.subject.sendMessage(agent.loadingMessage) event.subject.sendMessage(agent.loadingMessage)
} else null } else null
// 执行函数 // 执行函数

View File

@@ -7,12 +7,34 @@ import com.aallam.openai.client.OpenAIHost
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
object LargeLanguageModels { object LargeLanguageModels {
/**
* 系统提示词
*/
var systemPrompt: String = "你是一个乐于助人的助手"
private set
/**
* 聊天助手
*/
var chat: Chat? = null var chat: Chat? = null
/**
* 推理模型
*/
var reasoning: Chat? = null var reasoning: Chat? = null
/**
* 视觉模型
*/
var visual: Chat? = null var visual: Chat? = null
fun reload() { fun reload() {
// 载入超时时间
val timeout = PluginConfig.timeout.milliseconds val timeout = PluginConfig.timeout.milliseconds
// 初始化聊天模型
if (PluginConfig.openAiApi.isNotBlank() && PluginConfig.openAiToken.isNotBlank()) { if (PluginConfig.openAiApi.isNotBlank() && PluginConfig.openAiToken.isNotBlank()) {
chat = OpenAI( chat = OpenAI(
token = PluginConfig.openAiToken, token = PluginConfig.openAiToken,
@@ -21,6 +43,7 @@ object LargeLanguageModels {
) )
} }
// 初始化推理模型
if (PluginConfig.reasoningModelApi.isNotBlank() && PluginConfig.reasoningModelToken.isNotBlank()) { if (PluginConfig.reasoningModelApi.isNotBlank() && PluginConfig.reasoningModelToken.isNotBlank()) {
reasoning = OpenAI( reasoning = OpenAI(
token = PluginConfig.reasoningModelToken, token = PluginConfig.reasoningModelToken,
@@ -29,6 +52,7 @@ object LargeLanguageModels {
) )
} }
// 初始化视觉模型
if (PluginConfig.visualModelApi.isNotBlank() && PluginConfig.visualModelToken.isNotBlank()) { if (PluginConfig.visualModelApi.isNotBlank() && PluginConfig.visualModelToken.isNotBlank()) {
visual = OpenAI( visual = OpenAI(
token = PluginConfig.visualModelToken, token = PluginConfig.visualModelToken,
@@ -36,5 +60,22 @@ object LargeLanguageModels {
timeout = Timeout(request = timeout, connect = timeout, socket = timeout) timeout = Timeout(request = timeout, connect = timeout, socket = timeout)
) )
} }
// 载入提示词
if (PluginConfig.promptFile.isNotEmpty()) {
val file = JChatGPT.resolveConfigFile(PluginConfig.promptFile)
systemPrompt = if (file.exists()) {
file.readText()
} else {
// 迁移提示词
file.writeText(PluginConfig.prompt)
PluginConfig.prompt
}
// 空提示词兜底
if (systemPrompt.isEmpty()) {
systemPrompt = "你是一个乐于助人的助手"
}
}
} }
} }

View File

@@ -41,6 +41,9 @@ object PluginConfig : AutoSavePluginConfig("Config") {
@ValueDescription("百炼平台图片编辑模型") @ValueDescription("百炼平台图片编辑模型")
val imageEditModel: String by value("qwen-image-edit") val imageEditModel: String by value("qwen-image-edit")
@ValueDescription("百炼平台TTS模型")
val ttsModel: String by value("qwen-tts")
@ValueDescription("Jina API Key") @ValueDescription("Jina API Key")
val jinaApiKey by value("") val jinaApiKey by value("")
@@ -65,9 +68,13 @@ object PluginConfig : AutoSavePluginConfig("Config") {
@ValueDescription("等待响应超时时间单位毫秒默认60秒") @ValueDescription("等待响应超时时间单位毫秒默认60秒")
val timeout: Long by value(60000L) val timeout: Long by value(60000L)
@Deprecated("使用外部文件而不是在配置文件内保存提示词")
@ValueDescription("系统提示词") @ValueDescription("系统提示词")
var prompt: String by value("你是一个乐于助人的助手") var prompt: String by value("你是一个乐于助人的助手")
@ValueDescription("系统提示词文件路径,相对于插件配置目录")
val promptFile: String by value("SystemPrompt.md")
@ValueDescription("创建Prompt时取最近多少分钟内的消息") @ValueDescription("创建Prompt时取最近多少分钟内的消息")
val historyWindowMin: Int by value(10) val historyWindowMin: Int by value(10)
@@ -85,4 +92,7 @@ object PluginConfig : AutoSavePluginConfig("Config") {
@ValueDescription("关键字呼叫,支持正则表达式") @ValueDescription("关键字呼叫,支持正则表达式")
val callKeyword by value("[小筱][林淋月玥]") val callKeyword by value("[小筱][林淋月玥]")
@ValueDescription("是否显示工具调用消息,默认是")
val showToolCallingMessage by value(true)
} }

View File

@@ -30,6 +30,7 @@ object PluginData : AutoSavePluginData("data") {
contactMemory[contactId] = newMemory contactMemory[contactId] = newMemory
} else { } else {
contactMemory[contactId] = memory.replace(oldMemory, newMemory) contactMemory[contactId] = memory.replace(oldMemory, newMemory)
.replace("\n\n", "\n")
} }
} }
} }

View File

@@ -2,7 +2,6 @@ package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool import com.aallam.openai.api.chat.Tool
import com.aallam.openai.api.core.Parameters import com.aallam.openai.api.core.Parameters
import io.ktor.client.call.body
import io.ktor.client.request.header import io.ktor.client.request.header
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
@@ -38,11 +37,11 @@ class ImageEdit : BaseAgent(
put("type", "string") put("type", "string")
put("description", "正向提示词,用来描述需要对图片进行修改的要求。") put("description", "正向提示词,用来描述需要对图片进行修改的要求。")
} }
putJsonObject("negative_prompt") { // putJsonObject("negative_prompt") {
put("type", "string") // put("type", "string")
put("description", "反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制。" + // put("description", "反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制。" +
"示例值:低分辨率、错误、最差质量、低质量、残缺、多余的手指、比例不良等。") // "示例值:低分辨率、错误、最差质量、低质量、残缺、多余的手指、比例不良等。")
} // }
} }
putJsonArray("required") { putJsonArray("required") {
add("image_url") add("image_url")
@@ -59,13 +58,13 @@ class ImageEdit : BaseAgent(
get() = PluginConfig.dashScopeApiKey.isNotEmpty() get() = PluginConfig.dashScopeApiKey.isNotEmpty()
override val loadingMessage: String override val loadingMessage: String
get() = "片编辑中..." get() = "图中..."
override suspend fun execute(args: JsonObject?): String { override suspend fun execute(args: JsonObject?): String {
requireNotNull(args) requireNotNull(args)
val imageUrl = args.getValue("image_url").jsonPrimitive.content val imageUrl = args.getValue("image_url").jsonPrimitive.content
val prompt = args.getValue("prompt").jsonPrimitive.content val prompt = args.getValue("prompt").jsonPrimitive.content
val negativePrompt = args["negative_prompt"]?.jsonPrimitive?.content // val negativePrompt = args["negative_prompt"]?.jsonPrimitive?.content
val response = httpClient.post(API_URL) { val response = httpClient.post(API_URL) {
contentType(ContentType("application", "json")) contentType(ContentType("application", "json"))
header("Authorization", "Bearer " + PluginConfig.dashScopeApiKey) header("Authorization", "Bearer " + PluginConfig.dashScopeApiKey)
@@ -86,11 +85,11 @@ class ImageEdit : BaseAgent(
} }
} }
} }
if (negativePrompt != null) { // if (negativePrompt != null) {
putJsonObject("parameters") { // putJsonObject("parameters") {
put("negative_prompt", negativePrompt) // put("negative_prompt", negativePrompt)
} // }
} // }
}.toString()) }.toString())
} }
@@ -103,10 +102,10 @@ class ImageEdit : BaseAgent(
.getValue("message").jsonObject .getValue("message").jsonObject
.getValue("content").jsonArray[0].jsonObject .getValue("content").jsonArray[0].jsonObject
.getValue("image").jsonPrimitive.content .getValue("image").jsonPrimitive.content
"图片已编辑完成发送时请务必包含完整的url和查询参数因为下载地址存在鉴权。图片地址$url" "图片已编辑完成发送时请务必包含完整的url和查询参数因为下载地址存在鉴权![图片]($url)"
} catch (e: Exception) { } catch (e: Throwable) {
JChatGPT.logger.error("图像编辑结果解析异常", e) JChatGPT.logger.error("图像编辑结果解析异常", e)
responseObject.toString() responseJson
} }
} }
} }

View File

@@ -28,7 +28,7 @@ class ReasoningAgent : BaseAgent(
) )
) { ) {
override val loadingMessage: String override val loadingMessage: String
get() = "深度思考中..." get() = "思考中..."
override val isEnabled: Boolean override val isEnabled: Boolean
get() = LargeLanguageModels.reasoning != null get() = LargeLanguageModels.reasoning != null

View File

@@ -88,7 +88,7 @@ class RunCode : BaseAgent(
get() = PluginConfig.glotToken.isNotEmpty() get() = PluginConfig.glotToken.isNotEmpty()
override val loadingMessage: String override val loadingMessage: String
get() = "执行代码中..." get() = "执行中..."
override suspend fun execute(args: JsonObject?): String { override suspend fun execute(args: JsonObject?): String {
requireNotNull(args) requireNotNull(args)

View File

@@ -0,0 +1,140 @@
package top.jie65535.mirai.tools
import com.aallam.openai.api.chat.Tool
import com.aallam.openai.api.core.Parameters
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.json.*
import net.mamoe.mirai.contact.AudioSupported
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
import top.jie65535.mirai.JChatGPT
import top.jie65535.mirai.PluginConfig
import java.io.File
import java.util.concurrent.TimeUnit
import kotlin.time.measureTime
/**
* 发送语音消息调用阿里TTS需要系统中存在ffmpeg因为要转换到QQ支持的amr格式。
*/
class SendVoiceMessage : BaseAgent(
tool = Tool.function(
name = "sendVoiceMessage",
description = "发送一条文本转语音消息。",
parameters = Parameters.buildJsonObject {
put("type", "object")
putJsonObject("properties") {
putJsonObject("content") {
put("type", "string")
put("description", "语音消息文本内容")
}
}
putJsonArray("required") {
add("content")
}
}
)
) {
companion object {
const val API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
}
override val loadingMessage: String
get() = "录音中..."
override val isEnabled: Boolean
get() = PluginConfig.dashScopeApiKey.isNotEmpty()
override suspend fun execute(args: JsonObject?, event: MessageEvent): String {
requireNotNull(args)
if (event.subject !is AudioSupported) return "当前聊天环境不支持发送语音!"
val content = args.getValue("content").jsonPrimitive.content
// https://help.aliyun.com/zh/model-studio/qwen-tts
val response = httpClient.post(API_URL) {
contentType(ContentType("application", "json"))
header("Authorization", "Bearer " + PluginConfig.dashScopeApiKey)
setBody(buildJsonObject {
put("model", PluginConfig.ttsModel)
putJsonObject("input") {
put("text", content)
put("voice", "Chelsie") // Chelsie Cherry Ethan Serena
}
}.toString())
}
val responseJson = response.bodyAsText()
val responseObject = Json.parseToJsonElement(responseJson).jsonObject
return try {
val url = responseObject
.getValue("output").jsonObject
.getValue("audio").jsonObject
.getValue("url").jsonPrimitive.content
val voiceFolder = JChatGPT.resolveDataFile("voice")
voiceFolder.mkdir()
val amrFile = File(voiceFolder, "${System.currentTimeMillis()}.amr")
// 下载WAV并转到AMR
downloadWav2Amr(url, amrFile.absolutePath)
// 如果转换出来了则发送消息
if (amrFile.exists()) {
val audioMessage = amrFile.toExternalResource("amr").use {
(event.subject as AudioSupported).uploadAudio(it)
}
event.subject.sendMessage(audioMessage)
"OK"
} else {
"语音转换失败"
}
} catch (e: Throwable) {
JChatGPT.logger.error("语音生成结果解析异常", e)
responseJson
}
}
/**
* 下载WAV并转换到AMR语音文件
* @param url 下载地址
* @param outputAmrPath 目标文件路径
*/
private suspend fun downloadWav2Amr(url: String, outputAmrPath: String) {
val wavBytes: ByteArray
val downloadDuration = measureTime {
wavBytes = httpClient.get(url).bodyAsBytes()
}
JChatGPT.logger.info("下载语音文件耗时 $downloadDuration,文件大小 ${wavBytes.size} Bytes开始转换为AMR...")
val convertDuration = measureTime {
val ffmpeg = ProcessBuilder(
"ffmpeg",
"-f", "wav", // 指定输入格式
"-i", "pipe:0", // 从标准输入读取
"-ar", "8000",
"-ac", "1",
"-b:a", "12.2k",
"-y", // 覆盖输出文件
outputAmrPath // 输出到目标文件位置
).start()
ffmpeg.outputStream.use {
it.write(wavBytes)
}
// 等待FFmpeg处理完成
val completed = ffmpeg.waitFor(PluginConfig.timeout, TimeUnit.MILLISECONDS)
if (!completed) {
ffmpeg.destroy()
JChatGPT.logger.error("转换文件超时")
}
if (ffmpeg.exitValue() != 0) {
JChatGPT.logger.error("FFmpeg执行失败退出代码${ffmpeg.exitValue()}")
}
}
JChatGPT.logger.info("转换音频耗时 $convertDuration")
}
}

View File

@@ -44,7 +44,7 @@ class VisitWeb : BaseAgent(
get() = PluginConfig.jinaApiKey.isNotEmpty() get() = PluginConfig.jinaApiKey.isNotEmpty()
override val loadingMessage: String override val loadingMessage: String
get() = "访问网页中..." get() = "上网中..."
override suspend fun execute(args: JsonObject?): String { override suspend fun execute(args: JsonObject?): String {
requireNotNull(args) requireNotNull(args)

View File

@@ -40,7 +40,7 @@ class VisualAgent : BaseAgent(
) )
) { ) {
override val loadingMessage: String override val loadingMessage: String
get() = "图片识别中..." get() = "识别中..."
override val isEnabled: Boolean override val isEnabled: Boolean
get() = LargeLanguageModels.visual != null get() = LargeLanguageModels.visual != null

View File

@@ -34,7 +34,7 @@ class WeatherService : BaseAgent(
) )
) { ) {
override val loadingMessage: String override val loadingMessage: String
get() = "查询天气中..." get() = "观天中..."
override suspend fun execute(args: JsonObject?): String { override suspend fun execute(args: JsonObject?): String {
requireNotNull(args) requireNotNull(args)

View File

@@ -5,6 +5,8 @@ import com.aallam.openai.api.core.Parameters
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import org.apache.commons.text.StringEscapeUtils import org.apache.commons.text.StringEscapeUtils
import top.jie65535.mirai.JChatGPT import top.jie65535.mirai.JChatGPT
@@ -19,8 +21,15 @@ class WebSearch : BaseAgent(
put("type", "object") put("type", "object")
putJsonObject("properties") { putJsonObject("properties") {
putJsonObject("q") { putJsonObject("q") {
put("type", "string") putJsonArray("type") {
put("description", "查询内容关键字") add("string")
add("array")
}
putJsonObject("items") {
put("type", "string")
}
put("minItems", 1)
put("description", "查询关键字,可为单组关键字查询,也可并发多组同时查询。")
} }
} }
putJsonArray("required") { putJsonArray("required") {
@@ -36,57 +45,73 @@ class WebSearch : BaseAgent(
get() = PluginConfig.searXngUrl.isNotEmpty() get() = PluginConfig.searXngUrl.isNotEmpty()
override val loadingMessage: String override val loadingMessage: String
get() = "联网搜索中..." get() = "搜索中..."
override suspend fun execute(args: JsonObject?): String { override suspend fun execute(args: JsonObject?): String {
requireNotNull(args) requireNotNull(args)
val q = args.getValue("q").jsonPrimitive.content val q = args.getValue("q")
val url = buildString { if (q is JsonPrimitive) {
append(PluginConfig.searXngUrl) return search(q.content)
append("?q=") } else if (q is JsonArray) {
append(q.encodeURLParameter()) return q.map {
append("&format=json") scope.async { search(it.jsonPrimitive.content) }
}.awaitAll().joinToString()
} }
return ""
}
val response = httpClient.get(url) private suspend fun search(q: String): String {
JChatGPT.logger.info("Request: $url") return try {
val body = response.bodyAsText() val url = buildString {
JChatGPT.logger.info("Response: $body") append(PluginConfig.searXngUrl)
val responseJsonElement = Json.parseToJsonElement(body) append("?q=")
val filteredResponse = buildJsonObject { append(q.encodeURLParameter())
val root = responseJsonElement.jsonObject append("&format=json")
// 查询内容原样转发
root["query"]?.let { put("query", it) }
// 过滤搜索结果
val results = root["results"]?.jsonArray
if (results != null) {
val filteredResults = results
.filter {
// 去掉所有内容为空的结果
!it.jsonObject.getValue("content").jsonPrimitive.contentOrNull.isNullOrEmpty()
}.sortedByDescending {
it.jsonObject.getValue("score").jsonPrimitive.double
}.take(5) // 只取得分最高的前5条结果
.map {
// 移除掉我不想要的字段
val item = it.jsonObject.toMutableMap()
item.remove("engine")
item.remove("parsed_url")
item.remove("template")
item.remove("engines")
item.remove("positions")
item.remove("metadata")
item.remove("thumbnail")
JsonObject(item)
}
put("results", JsonArray(filteredResults))
} }
// 答案和信息盒子原样转发 val response = httpClient.get(url)
root["answers"]?.let { put("answers", it) } JChatGPT.logger.info("Request: $url")
root["infoboxes"]?.let { put("infoboxes", it) } val body = response.bodyAsText()
}.toString() JChatGPT.logger.debug("Response: $body")
return StringEscapeUtils.unescapeJava(filteredResponse) val responseJsonElement = Json.parseToJsonElement(body)
val filteredResponse = buildJsonObject {
val root = responseJsonElement.jsonObject
// 查询内容原样转发
root["query"]?.let { put("query", it) }
// 过滤搜索结果
val results = root["results"]?.jsonArray
if (results != null) {
val filteredResults = results
.filter {
// 去掉所有内容为空的结果
!it.jsonObject.getValue("content").jsonPrimitive.contentOrNull.isNullOrEmpty()
}.sortedByDescending {
it.jsonObject.getValue("score").jsonPrimitive.double
}.take(5) // 只取得分最高的前5条结果
.map {
// 移除掉我不想要的字段
val item = it.jsonObject.toMutableMap()
item.remove("engine")
item.remove("parsed_url")
item.remove("template")
item.remove("engines")
item.remove("positions")
item.remove("metadata")
item.remove("thumbnail")
JsonObject(item)
}
put("results", JsonArray(filteredResults))
}
// 答案和信息盒子原样转发
root["answers"]?.let { put("answers", it) }
root["infoboxes"]?.let { put("infoboxes", it) }
}.toString()
StringEscapeUtils.unescapeJava(filteredResponse)
} catch (e: Throwable) {
"Failed to search \"$q\": ${e.message}"
}
} }
} }