diff --git a/src/main/kotlin/top/jie65535/jcf/MessageHandler.kt b/src/main/kotlin/top/jie65535/jcf/MessageHandler.kt index be771d0..f8e0095 100644 --- a/src/main/kotlin/top/jie65535/jcf/MessageHandler.kt +++ b/src/main/kotlin/top/jie65535/jcf/MessageHandler.kt @@ -1,15 +1,22 @@ package top.jie65535.jcf +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageSource.Key.quote +import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.MiraiLogger import top.jie65535.jcf.model.file.File import top.jie65535.jcf.model.mod.Mod import top.jie65535.jcf.util.PagedList +import top.jie65535.jcf.util.HttpUtil +import java.text.DecimalFormat +import java.util.regex.Pattern +import kotlin.math.min class MessageHandler( private val service: MinecraftService, @@ -28,14 +35,21 @@ class MessageHandler( } else { try { val pagedList = service.search(modClass, filter) - handleModsSearchResult(pagedList) + val msg = with(pagedList.current()) { + if (size == 1) { + handleShowMod(get(0)) + } else { + handleModsSearchResult(pagedList) + } + } + subject.sendMessage(msg) } catch (e: Throwable) { subject.sendMessage(message.quote() + "发生内部错误,请稍后重试") logger.error("消息\"$message\"引发异常", e) } - } + }// if } - } + }// for } } @@ -46,31 +60,124 @@ class MessageHandler( val list = pagedList.current() // mod list val hasNext = pagedList.hasNext // 是否有下一页 val hasPrev = pagedList.hasPrev // 是否有上一页 - TODO("将搜索列表转为QQ消息") + return buildForwardMessage { + bot says "30秒内回复编号查看" + for ((x, mod) in list.withIndex()) { + bot.id named x.toString() says PlainText( + """ + [${mod.name}] by ${mod.authors[0].name} + ${formatCount(mod.downloadCount)} Downloads Updated ${mod.dateModified.toLocalDate()} + ${mod.summary} + ${mod.links.websiteUrl} + """.trimIndent() + ) + } + var tmp = "回复:" + if (hasPrev) tmp += "[P]上一页 " + if (hasNext) tmp += "[N]下一页" + bot says tmp + } } /** * 处理展示单个mod */ - private fun handleShowMod(mod: Mod): Message { - TODO("将mod转为QQ消息") + private suspend fun MessageEvent.handleShowMod(mod: Mod): Message { + return buildForwardMessage { + // logo + mod.logo.thumbnailUrl?.let { + if (it.isNotBlank()) bot says loadImage(it) + } + // basic info + bot says PlainText(with(mod) { + """ + $name + 作者:${authors[0].name} + 概括:$summary + 项目ID:$id + 创建时间:${dateCreated.toLocalDate()} + 最近更新:${dateModified.toLocalDate()} + 总下载:$downloadCount + """.trimIndent() + }) + // links + bot says PlainText(with(mod.links) { + """ + 主页:$websiteUrl + 支持页:$issuesUrl + wiki:$wikiUrl + 开源页:$sourceUrl + """.trimIndent() + }) + } } /** * 处理模组文件列表 */ - private suspend fun handleModFileList(pagedList: PagedList): Message { + private suspend fun MessageEvent.handleModFileList(pagedList: PagedList): Message { val list = pagedList.current() // mod list val hasNext = pagedList.hasNext // 是否有下一页 val hasPrev = pagedList.hasPrev // 是否有上一页 - TODO("将文件列表转为QQ消息") + return buildForwardMessage { + bot says "30秒内回复编号查看" + for ((x, file) in list.withIndex()) { + bot.id named x.toString() says PlainText( + """ + ${file.displayName} [${file.releaseType}] [${file.fileDate.toLocalDate()}] + ${file.downloadUrl} + """.trimIndent() + ) + } + var tmp = "回复:" + if (hasPrev) tmp += "[P]上一页 " + if (hasNext) tmp += "[N]下一页" + bot says tmp + } } /** * 处理模组文件更改日志 * @param changelog 更改日志(HTML) */ - private fun handleModFileChangelog(changelog: String): Message { - TODO("将文件更改日志渲染为QQ消息") + private fun MessageEvent.handleModFileChangelog(changelog: String): Message { + val logs = HTMLPattern.matcher(changelog).replaceAll("") + return sendLargeMessage(logs) } -} \ No newline at end of file + + private val singleDecimalFormat = DecimalFormat("0.#") + + private fun formatCount(count: Long): String = when { + count < 1000000 -> singleDecimalFormat.format(count / 1000) + "K" + count < 1000000000 -> singleDecimalFormat.format(count / 1000000) + "M" + else -> count.toString() + } + + private suspend fun MessageEvent.loadImage(url: String): Image { + val imgFileName = url.substringAfterLast("/") + val file = PluginMain.resolveDataFile("cache/$imgFileName") + val res = if (file.exists()) { + file.readBytes().toExternalResource() + } else { + HttpUtil.downloadImage(url, file).toExternalResource() + } + val image = subject.uploadImage(res) + withContext(Dispatchers.IO) { + res.close() + } + return image + } + + private val HTMLPattern = Pattern.compile("<[^>]+>", Pattern.CASE_INSENSITIVE) + private val ONE_GRP_SIZE = 5000 + private val ONE_MSG_SIZE = 500 + fun MessageEvent.sendLargeMessage(message: String): Message { + return buildForwardMessage { + for (g in message.indices step ONE_GRP_SIZE) { + for (i in g until g + min(ONE_GRP_SIZE, message.length - g) step ONE_MSG_SIZE) { + bot says PlainText(message.subSequence(i, i + (min(ONE_MSG_SIZE, message.length - i)))) + } + } + } + }// fun +} diff --git a/src/main/kotlin/top/jie65535/jcf/util/HttpUtil.kt b/src/main/kotlin/top/jie65535/jcf/util/HttpUtil.kt new file mode 100644 index 0000000..16adee1 --- /dev/null +++ b/src/main/kotlin/top/jie65535/jcf/util/HttpUtil.kt @@ -0,0 +1,29 @@ +package top.jie65535.jcf.util + +//import okhttp3.MediaType +//import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.File +import java.util.concurrent.TimeUnit + +object HttpUtil { +// private val JSON: MediaType? = "application/json; charset=utf-8".toMediaTypeOrNull() + private val okHttpClient: OkHttpClient by lazy { + OkHttpClient.Builder() + .readTimeout(10, TimeUnit.SECONDS) + .build() + } + + /** + * ### 下载图片 + */ + fun downloadImage(url: String, file: File): ByteArray { + val request = Request.Builder().url(url).build() + val imageByte = okHttpClient.newCall(request).execute().body!!.bytes() + val fileParent = file.parentFile + if (!fileParent.exists()) fileParent.mkdirs() + file.writeBytes(imageByte) + return imageByte + } +}