From aff1da1ccdede7981c23d3cdebede12729379d05 Mon Sep 17 00:00:00 2001 From: Kurenai Date: Wed, 7 Aug 2024 02:59:29 +0800 Subject: [PATCH] chore(overflow): migration to overflow --- build.gradle.kts | 3 +- .../kurenai/imsyncbot/ConfigProperties.kt | 1 - .../imsyncbot/bot/qq/MessageContext.kt | 395 +++--------------- .../kotlin/kurenai/imsyncbot/bot/qq/QQBot.kt | 44 +- .../kurenai/imsyncbot/service/FileService.kt | 45 +- .../kotlin/kurenai/imsyncbot/utils/BotUtil.kt | 16 + .../kotlin/kurenai/imsyncbot/utils/Utils.kt | 39 +- 7 files changed, 161 insertions(+), 382 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2bf2384..d78bdf3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,6 +22,7 @@ repositories { mavenLocal { content { includeGroup("com.github.Nyayurn") + includeGroup("top.mrxiaom") } } maven { @@ -119,7 +120,7 @@ dependencies { implementation(platform("net.mamoe:mirai-bom:${Versions.MIRAI}")) implementation("net.mamoe:mirai-core") implementation("net.mamoe:mirai-core-utils") - implementation("top.mrxiaom:overflow-core:2.16.0+") + implementation("top.mrxiaom:overflow-core:2.16+") //tdlib implementation(platform("it.tdlight:tdlight-java-bom:${Versions.TD_LIGHT}")) diff --git a/src/main/kotlin/kurenai/imsyncbot/ConfigProperties.kt b/src/main/kotlin/kurenai/imsyncbot/ConfigProperties.kt index 7ab3bd6..4188409 100644 --- a/src/main/kotlin/kurenai/imsyncbot/ConfigProperties.kt +++ b/src/main/kotlin/kurenai/imsyncbot/ConfigProperties.kt @@ -46,7 +46,6 @@ data class BotProperties( ) @Serializable -@Deprecated("No longer needed") data class QQProperties( val host: String = "localhost", val port: Int = 9000, diff --git a/src/main/kotlin/kurenai/imsyncbot/bot/qq/MessageContext.kt b/src/main/kotlin/kurenai/imsyncbot/bot/qq/MessageContext.kt index acd2af6..1839e88 100644 --- a/src/main/kotlin/kurenai/imsyncbot/bot/qq/MessageContext.kt +++ b/src/main/kotlin/kurenai/imsyncbot/bot/qq/MessageContext.kt @@ -3,7 +3,6 @@ package kurenai.imsyncbot.bot.qq import it.tdlight.jni.TdApi import it.tdlight.jni.TdApi.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch @@ -11,7 +10,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kurenai.imsyncbot.ImSyncBot -import kurenai.imsyncbot.bot.telegram.TgMessageHandler import kurenai.imsyncbot.domain.QQMessage import kurenai.imsyncbot.service.FileService import kurenai.imsyncbot.service.MessageService @@ -29,6 +27,8 @@ import nl.adaptivity.xmlutil.serialization.XmlSerializationPolicy import org.apache.logging.log4j.LogManager import org.jsoup.Jsoup import org.jsoup.parser.Parser +import java.nio.file.Path +import kotlin.io.path.fileSize import kotlin.io.path.pathString /** @@ -54,18 +54,21 @@ sealed class MessageContext( * @return */ protected fun String.formatMsg(senderId: Long, senderName: String? = null): String { - return tgMsgFormat.escapeMarkdown().replace(BotUtil.NEWLINE_PATTERN.escapeMarkdown(), "\n", true) - .replace(BotUtil.ID_PATTERN.escapeMarkdown(), senderId.toString(), true) - .let { - if (senderName?.isNotBlank() == true) - it.replace( - BotUtil.NAME_PATTERN.escapeMarkdown(), - senderName.replace('-', '_').escapeMarkdown(), - true - ) - else - it.replace(BotUtil.NAME_PATTERN.escapeMarkdown(), "", true) - }.replace(BotUtil.MSG_PATTERN.escapeMarkdown(), this, true) + val formatName = if (senderName?.isNotBlank() == true) senderName else "BLANK_NAME" + return "`${formatName.escapeMarkdown()}` \\#id$senderId\n\n$this" + +// return tgMsgFormat.escapeMarkdown().replace(BotUtil.NEWLINE_PATTERN.escapeMarkdown(), "\n", true) +// .replace(BotUtil.ID_PATTERN.escapeMarkdown(), senderId.toString(), true) +// .let { +// if (senderName?.isNotBlank() == true) +// it.replace( +// BotUtil.NAME_PATTERN.escapeMarkdown(), +// senderName.replace('-', '_').escapeMarkdown(), +// true +// ) +// else +// it.replace(BotUtil.NAME_PATTERN.escapeMarkdown(), "", true) +// }.replace(BotUtil.MSG_PATTERN.escapeMarkdown(), this, true) } protected fun String.handleUrl(): String { @@ -261,58 +264,60 @@ class GroupMessageContext( inner class SingleImage(private val image: Image, private val onlyImage: Boolean = false) : ReadyToSendMessage { private val shouldBeFile: Boolean = image.shouldBeFile() + private var fileSize: Long = 0L override suspend fun send(): Array { val inputFile = FileService.download(image) + + return if (inputFile is InputFileLocal) { - val f = bot.tg.send(PreliminaryUploadFile().apply { - this.file = inputFile - this.fileType = if (shouldBeFile) FileTypeDocument() else FileTypePhoto() - this.priority = 1 - }) - val deferred = bot.tg.messageHandler.addListener(matchBlock = { event -> - event is UpdateFile && event.file.id == f.id && event.file.remote.isUploadingCompleted - }) { event: UpdateFile -> - TgMessageHandler.ListenerResult.complete(event.file) - } +// val f = bot.tg.send(PreliminaryUploadFile().apply { +// this.file = inputFile +// this.fileType = if (shouldBeFile) FileTypeDocument() else FileTypePhoto() +// this.priority = 1 +// }) +// val deferred = bot.tg.messageHandler.addListener(matchBlock = { event -> +// event is UpdateFile && event.file.id == f.id && event.file.remote.isUploadingCompleted +// }) { event: UpdateFile -> +// TgMessageHandler.ListenerResult.complete(event.file) +// } runCatching { -// withTimeout(3.seconds) { - val file = deferred.await() - sendMessage(InputFileRemote(file.remote.id)) -// } + fileSize = Path.of(inputFile.path).fileSize() + sendFileMessage(inputFile) }.recover { ex -> - if (ex !is TimeoutCancellationException) throw ex - - val messages = - sendMessage(InputFileRemote("AgACAv7___8dBEqDOiEAAQGUXWUpby1uzGVg8oBeQUASzpcAAfjmzgACj70xG3SWSFZfHeKh_spbbgEAAwIAA2kAAzAE")) - val message = messages[0] - CoroutineScope(bot.qq.coroutineContext).launch { - runCatching { - log.debug("Wait for upload file...") - val file = deferred.await() - log.debug("Uploaded file: {}", file) - bot.tg.send(EditMessageMedia().apply { - this.chatId = message.chatId - this.messageId = message.id - this.inputMessageContent = buildContent(InputFileRemote(file.remote.id)) - }) - }.onFailure { ex -> - log.warn("Send photo fail: {}", ex.message, ex) - normalType.send() - } - } - messages + throw ex +// log.warn("Send image failed", ex) +// +// val messages = +// sendMessage(InputFileRemote("AgACAv7___8dBEqDOiEAAQGUXWUpby1uzGVg8oBeQUASzpcAAfjmzgACj70xG3SWSFZfHeKh_spbbgEAAwIAA2kAAzAE")) +// val message = messages[0] +// CoroutineScope(bot.qq.coroutineContext).launch { +// runCatching { +// log.debug("Wait for upload file...") +// val file = deferred.await() +// log.debug("Uploaded file: {}", file) +// bot.tg.send(EditMessageMedia().apply { +// this.chatId = message.chatId +// this.messageId = message.id +// this.inputMessageContent = buildContent(InputFileRemote(file.remote.id)) +// }) +// }.onFailure { ex -> +// log.warn("Send photo fail: {}", ex.message, ex) +// normalType.send() +// } +// } +// messages }.getOrThrow() } else { - sendMessage(inputFile) + sendFileMessage(inputFile) } } - private suspend fun sendMessage(inputFile: InputFile): Array { + private suspend fun sendFileMessage(inputFile: InputFile): Array { val func = SendMessage().apply { this.chatId = this@GroupMessageContext.chatId this@GroupMessageContext.getReplayToMessageId().takeIf { it > 0 }?.let { this.setReplyToMessageId(it) } - this.inputMessageContent = buildContent(inputFile) + this.inputMessageContent = buildFileMessageContent(inputFile) } return arrayOf(bot.tg.send(untilPersistent = true, function = func).also { CoroutineScope(bot.coroutineContext).launch { @@ -321,13 +326,13 @@ class GroupMessageContext( }) } - private suspend fun buildContent(file: InputFile): InputMessageContent { + private suspend fun buildFileMessageContent(file: InputFile): InputMessageContent { val caption = if (onlyImage) { "".formatMsg(senderId, senderName) } else { getContentWithAtAndWithoutImage().formatMsg(senderId, senderName) }.fmt() - return if (shouldBeFile) { + return if (shouldBeFile && fileSize > 300 * 1024) { InputMessageDocument().apply { this.caption = caption document = file @@ -522,288 +527,6 @@ class GroupMessageContext( } } -//class PrivateMessageContext( -// entity: QQMessage?, -// bot: ImSyncBot, -// val messageChain: MessageChain, -// val chat: User, -// val senderId: Long = chat.id, -// val senderName: String = chat.remarkOrNick -//) : MessageContext(entity, bot) { -// -// private var type: MessageType? = null -// val infoString: String by lazy { "[${this.chat.remarkOrNick}(${this.chat.id})]" } -// val simpleContent: String = messageChain.contentToString() -// val chatId: String = (bot.userConfig.friendChatIds[chat.id] ?: bot.userConfig.defaultChatId).toString() -// val replyId: Long? by lazy { -// messageChain[QuoteReply.Key]?.let { -// runBlocking(bot.coroutineContext) { -// MessageService.findTgIdByQQ(bot.qq.qqBot.id, it.source.targetId, it.source.ids.first()) -// } -// }?.tgMsgId -// } -// val hasReply: Boolean by lazy { replyId != null } -// val normalType: Normal = Normal() -// -// fun getType() = type ?: handleType(messageChain).also { type = it } -// -// @OptIn(MiraiExperimentalApi::class) -// private fun handleType(messageChain: MessageChain): MessageType { -// return if (messageChain.contains(RichMessage.Key)) { -// val content = messageChain[RichMessage.Key]!!.content -// kotlin.runCatching { -// JsonMessage(json.decodeFromString(JsonMessageContent.serializer(), content)) -// }.recoverCatching { -// val document = Jsoup.parse(content, Parser.xmlParser()) -// val uuid = document.selectXpath("//image").attr("uuid") -// if (uuid.isNotBlank()) { -// SingleImage(Image(uuid), true) -// } else { -// XmlMessage(xml.decodeFromString(XmlMessageContent.serializer(), content)) -// } -// }.recover { Normal() }.getOrThrow() -// } else if (messageChain.contains(Image.Key)) { -// val images = messageChain.filterIsInstance() -// if (images.size == 1) { -// val image = images.first() -// if (image.imageType == ImageType.GIF || image.imageType == ImageType.APNG) -// GifImage(image) -// else -// SingleImage(image) -// } else -// MultiImage(images) -// } else { -// Normal() -// } -// } -// -// private fun getContentWithAtAndWithoutImage(): String { -// var content = "" -// for (msg in messageChain) { -// if (msg !is Image) { -// content += msg.contentToString().escapeMarkdownChar() -// } -// } -// return if (content.startsWith("\n")) content.substring(1) -// else content -// } -// -// sealed interface MessageType -// -// inner class XmlMessage(val msg: XmlMessageContent) : MessageType { -// val url: String? = msg.url?.handleUrl() -// val telegramMessage: SendMessage = SendMessage( -// chatId, -// (url?.escapeMarkdownChar() ?: simpleContent).formatMsg(senderId, senderName) -// ).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// replyId?.let { replyToMessageId = replyId } -// } -// } -// -// inner class JsonMessage(val msg: JsonMessageContent) : MessageType { -// val url: String? = (msg.meta.news?.jumpUrl ?: msg.meta.detail1.qqdocurl)?.handleUrl() -// val telegramMessage: SendMessage = SendMessage( -// chatId, -// (url?.escapeMarkdownChar() ?: simpleContent).formatMsg(senderId, senderName) -// ).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// replyId?.let { replyToMessageId = replyId } -// } -// } -// -// inner class SingleImage(private val image: Image, private val onlyImage: Boolean = false) : MessageType { -// -// private val shouldBeFile: Boolean = image.shouldBeFile() -// private var inputFile: InputFile? = null -// private var telegramMessage: Request>? = null -// -// suspend fun getTelegramMessage(): Request> { -// return telegramMessage ?: buildMessage() -// } -// -// suspend fun resolvedHttpUrlInvalidByModifyUrl(): Request> { -// val message = getTelegramMessage() -// inputFile!!.attachName = inputFile!!.attachName.substringBefore("?") -// return message -// } -// -// suspend fun resolvedHttpUrlInvalidByLocalDownload(): Request> { -// val message = getTelegramMessage() -// val path = kotlin.runCatching { -// BotUtil.downloadImg(inputFile!!.fileName!!, inputFile!!.attachName, overwrite = false) -// }.recover { -// val url = if (inputFile!!.attachName.endsWith("?term=2")) { -// inputFile!!.attachName.substringBefore("?") -// } else { -// inputFile!!.attachName + "?term=2" -// } -// BotUtil.downloadImg(inputFile!!.fileName!!, url, overwrite = false) -// }.getOrThrow() -// inputFile!!.file = path.toFile() -// inputFile!!.attachName = "attach://${path.fileName}" -// return message -// } -// -// private suspend fun buildMessage(): Request> { -// return if (shouldBeFile) { -// SendDocument( -// chatId, -// InputFile(image.queryUrl()).apply { fileName = image.imageId }.also { inputFile = it }).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// caption = if (onlyImage) { -// "".formatMsg(senderId, senderName) -// } else { -// getContentWithAtAndWithoutImage().formatMsg(senderId, senderName) -// } -// replyId?.let { replyToMessageId = replyId } -// } -// } else { -// SendPhoto( -// chatId, -// InputFile(image.queryUrl()).apply { fileName = image.imageId }.also { inputFile = it }).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// caption = if (onlyImage) { -// "".formatMsg(senderId, senderName) -// } else { -// getContentWithAtAndWithoutImage().formatMsg(senderId, senderName) -// } -// replyId?.let { replyToMessageId = replyId } -// } -// }.also { -// telegramMessage = it -// } -// } -// -// -// } -// -// inner class MultiImage(val images: List) : MessageType { -// val shouldBeFile: Boolean = images.any { -// if (it.imageType == ImageType.GIF || -// it.imageType == ImageType.APNG || -// it.imageType == ImageType.UNKNOWN && !it.isEmoji -// ) -// true -// else -// it.shouldBeFile() -// } -// -// private val inputFiles = mutableListOf() -// private var telegramMessage: SendMediaGroup? = null -// private lateinit var inputMedias: List -// -// suspend fun getTelegramMessage(): SendMediaGroup { -// return telegramMessage ?: buildTelegramMessage() -// } -// -// suspend fun resolvedHttpUrlInvalidByModifyUrl(): SendMediaGroup { -// val message = getTelegramMessage() -// inputFiles.forEach { -// it.attachName = it.attachName.substringBefore("?") -// } -// return message -// } -// -// suspend fun resolvedHttpUrlInvalidByLocalDownload(): SendMediaGroup { -// val message = getTelegramMessage() -// inputFiles.forEach { inputFile -> -// val path = kotlin.runCatching { -// BotUtil.downloadImg(inputFile.fileName!!, inputFile.attachName, overwrite = false) -// }.recover { -// val url = if (inputFile.attachName.endsWith("?term=2")) { -// inputFile.attachName.substringBefore("?") -// } else { -// inputFile.attachName + "?term=2" -// } -// BotUtil.downloadImg(inputFile.fileName!!, url, overwrite = false) -// }.getOrThrow() -// inputFile.file = path.toFile() -// inputFile.attachName = "attach://${path.fileName}" -// } -// return message -// } -// -// private suspend fun buildTelegramMessage(): SendMediaGroup { -// inputFiles.clear() -// -// inputMedias = images.mapNotNull { -// if (it.imageType == ImageType.GIF) null -// else if (shouldBeFile) -// InputMediaDocument(InputFile(it.queryUrl()).also(inputFiles::add)).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// } -// else -// InputMediaPhoto(InputFile(it.queryUrl()).also(inputFiles::add)).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// } -// }.also { -// it[it.lastIndex].caption = getContentWithAtAndWithoutImage().formatMsg(senderId, senderName) -// } -// inputMedias.windowed(10).forEach { sub -> -// SendMediaGroup(chatId).apply { -// media = inputMedias -// replyId?.let { replyToMessageId = replyId } -// }.also { -// telegramMessage = it -// } -// } -// return SendMediaGroup(chatId).apply { -// media = images.mapNotNull { -// if (it.imageType == ImageType.GIF) null -// else if (shouldBeFile) { -// InputMediaDocument(InputFile(it.queryUrl()).also(inputFiles::add)).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// } -// } else { -// InputMediaPhoto(InputFile(it.queryUrl()).also(inputFiles::add)).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// } -// } -// }.also { -// it[it.lastIndex].caption = getContentWithAtAndWithoutImage().formatMsg(senderId, senderName) -// } -// replyId?.let { replyToMessageId = replyId } -// }.also { -// telegramMessage = it -// } -// } -// } -// -// inner class GifImage( -// val image: Image, -// ) : MessageType { -// suspend fun getTelegramMessage(): SendAnimation { -// return SendAnimation(chatId, InputFile(image.queryUrl()).apply { fileName = image.imageId }).apply { -// caption = getContentWithAtAndWithoutImage().formatMsg(senderId, senderName) -// parseMode = ParseMode.MARKDOWN_V2 -// replyId?.let { replyToMessageId = replyId } -// } -// } -// } -// -// inner class Forward(val msg: ForwardMessage) : MessageType { -// val contextList = msg.nodeList.map { -// PrivateMessageContext( -// null, -// bot, -// it.messageChain, -// chat, -// it.senderId, -// "$senderName forward from ${it.senderName}" -// ) -// } -// } -// -// inner class Normal : MessageType { -// val telegramMessage: SendMessage = -// SendMessage(chatId, getContentWithAtAndWithoutImage().formatMsg(senderId, senderName)).apply { -// parseMode = ParseMode.MARKDOWN_V2 -// replyId?.let { replyToMessageId = replyId } -// } -// } -//} - @Serializable data class XmlMessageContent( val action: String, diff --git a/src/main/kotlin/kurenai/imsyncbot/bot/qq/QQBot.kt b/src/main/kotlin/kurenai/imsyncbot/bot/qq/QQBot.kt index 0dc2c2d..498db14 100644 --- a/src/main/kotlin/kurenai/imsyncbot/bot/qq/QQBot.kt +++ b/src/main/kotlin/kurenai/imsyncbot/bot/qq/QQBot.kt @@ -25,7 +25,6 @@ import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.ids import net.mamoe.mirai.utils.ConcurrentHashMap @@ -70,7 +69,8 @@ class QQBot( private val groupMessageLockMap = ConcurrentHashMap() private suspend fun buildBot(): Bot { - val url = "ws://${qqProperties.host}:${qqProperties.port}" + val url = "ws://${qqProperties.host}:${qqProperties.port}/" + log.info("Connecting to $url") return BotBuilder.positive(url) .token(qqProperties.token) .overrideLogger(log) @@ -183,23 +183,24 @@ class QQBot( try { when (event) { is MessageEvent -> { - val json = event.message.serializeToJsonString() + val json = event.message.toString() when (event) { is FriendMessageEvent -> { - MessageService.save( - QQMessage().apply { - - messageId = event.message.ids[0] - botId = event.bot.id - objId = event.subject.id - sender = event.sender.id - target = event.source.targetId - type = QQMessageType.FRIEND - this.json = json - handled = false - msgTime = event.source.getLocalDateTime() - } - ) + CoroutineScope(Dispatchers.IO).launch { + MessageService.save( + QQMessage().apply { + messageId = event.message.ids[0] + botId = event.bot.id + objId = event.subject.id + sender = event.sender.id + target = event.source.targetId + type = QQMessageType.FRIEND + this.json = json + handled = false + msgTime = event.source.getLocalDateTime() + } + ) + } // bot.qqMessageHandler.onFriendMessage( // PrivateMessageContext( @@ -223,7 +224,9 @@ class QQBot( this.handled = false this.msgTime = event.source.getLocalDateTime() } - MessageService.save(message) + CoroutineScope(Dispatchers.IO).launch { + MessageService.save(message) + } bot.qqMessageHandler.onGroupMessage( GroupMessageContext( @@ -248,7 +251,10 @@ class QQBot( this.handled = false this.msgTime = event.source.getLocalDateTime() } - MessageService.save(message) + + CoroutineScope(Dispatchers.IO).launch { + MessageService.save(message) + } bot.qqMessageHandler.onGroupMessage( GroupMessageContext( diff --git a/src/main/kotlin/kurenai/imsyncbot/service/FileService.kt b/src/main/kotlin/kurenai/imsyncbot/service/FileService.kt index 64d6ce7..13589c7 100644 --- a/src/main/kotlin/kurenai/imsyncbot/service/FileService.kt +++ b/src/main/kotlin/kurenai/imsyncbot/service/FileService.kt @@ -2,7 +2,6 @@ package kurenai.imsyncbot.service import it.tdlight.jni.TdApi import it.tdlight.jni.TdApi.InputFileLocal -import it.tdlight.jni.TdApi.InputFileRemote import kotlinx.coroutines.flow.channelFlow import kurenai.imsyncbot.domain.FileCache import kurenai.imsyncbot.fileCacheRepository @@ -14,7 +13,6 @@ import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image.Key.queryUrl import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.io.path.pathString -import kotlin.jvm.optionals.getOrNull /** * @author Kurenai @@ -25,30 +23,35 @@ object FileService { @OptIn(MiraiInternalApi::class) suspend fun download(image: Image) = withIO { - fileCacheRepository.findById(image.md5.toHex()).getOrNull()?.let { - InputFileRemote(it.fileId) - } ?: run { - InputFileLocal( - BotUtil.downloadImg( - "${image.imageId.substring(1..36).replace("-", "")}.${image.imageType.formatName}", - image.queryUrl() - ).pathString - ) - } +// fileCacheRepository.findById(image.md5.toHex()).getOrNull()?.let { +// InputFileRemote(it.fileId) +// } ?: run { +// InputFileLocal( +// BotUtil.downloadImg( +// "${image.imageId.substring(1..36).replace("-", "")}.${image.imageType.formatName}", +// image.queryUrl() +// ).pathString +// ) +// } + InputFileLocal(BotUtil.downloadImg(image.queryUrl()).pathString) } @OptIn(MiraiInternalApi::class) suspend fun download(images: Iterable) = channelFlow { - val imgMap = images.associateBy { it.md5.toHex() }.toMutableMap() - val caches = withIO { fileCacheRepository.findAllById(imgMap.keys) } - caches.forEach { - send(InputFileRemote(it.fileId)) - imgMap.remove(it.id) - } +// val imgMap = images.associateBy { it.md5.toHex() }.toMutableMap() +// val caches = withIO { fileCacheRepository.findAllById(imgMap.keys) } +// caches.forEach { +// send(InputFileRemote(it.fileId)) +// imgMap.remove(it.id) +// } +// +// imgMap.entries.takeIf { it.isNotEmpty() }?.forEach { (_, img) -> +// val filename = "${img.imageId.substring(1..36).replace("-", "")}.${img.imageType.formatName}" +// send(InputFileLocal(BotUtil.downloadImg(filename, img.queryUrl()).pathString)) +// } - imgMap.entries.takeIf { it.isNotEmpty() }?.forEach { (_, img) -> - val filename = "${img.imageId.substring(1..36).replace("-", "")}.${img.imageType.formatName}" - send(InputFileLocal(BotUtil.downloadImg(filename, img.queryUrl()).pathString)) + images.forEach { + send(InputFileLocal(BotUtil.downloadImg(it.queryUrl()).pathString)) } } diff --git a/src/main/kotlin/kurenai/imsyncbot/utils/BotUtil.kt b/src/main/kotlin/kurenai/imsyncbot/utils/BotUtil.kt index 3686c6b..fbb48ee 100644 --- a/src/main/kotlin/kurenai/imsyncbot/utils/BotUtil.kt +++ b/src/main/kotlin/kurenai/imsyncbot/utils/BotUtil.kt @@ -5,6 +5,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.future.await import kotlinx.coroutines.withContext import kurenai.imsyncbot.exception.BotException +import kurenai.imsyncbot.snowFlake +import java.nio.file.Files import java.nio.file.Path import kotlin.io.path.createDirectories import kotlin.io.path.exists @@ -80,6 +82,20 @@ object BotUtil { return download(image, url, onlyCache, overwrite) } + suspend fun downloadImg( + url: String, + ext: String? = null, + onlyCache: Boolean = false, + ): Path { + val image = Path.of(getImagePath(snowFlake.nextAlpha())) + val tmpPath = download(image, url, onlyCache, false) + val path = Path.of(getImagePath(tmpPath.crc32c() + if (ext?.isNotBlank() == true) ".$ext" else "")) + withContext(Dispatchers.IO) { + Files.move(tmpPath, path) + } + return path + } + private suspend fun download(path: Path, url: String, onlyCache: Boolean, overwrite: Boolean): Path { if (!onlyCache) { HttpUtil.download(path, url, overwrite = overwrite) diff --git a/src/main/kotlin/kurenai/imsyncbot/utils/Utils.kt b/src/main/kotlin/kurenai/imsyncbot/utils/Utils.kt index c8c28c6..035bd29 100644 --- a/src/main/kotlin/kurenai/imsyncbot/utils/Utils.kt +++ b/src/main/kotlin/kurenai/imsyncbot/utils/Utils.kt @@ -9,13 +9,11 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator import com.fasterxml.jackson.module.kotlin.KotlinFeature import com.fasterxml.jackson.module.kotlin.kotlinModule import io.ktor.client.* -import io.ktor.util.* -import it.tdlight.jni.TdApi import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.serialization.json.Json -import okio.ByteString.Companion.decodeHex +import okhttp3.internal.toHexString import org.reflections.Reflections import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -27,7 +25,10 @@ import java.text.StringCharacterIterator import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId -import java.util.HexFormat +import java.util.* +import java.util.zip.CRC32 +import java.util.zip.CRC32C +import java.util.zip.Checksum import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.io.path.inputStream @@ -140,6 +141,36 @@ fun Long.toLocalDateTime(): LocalDateTime { return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault()) } +fun ByteArray.crc32c(): String { + val checksum = CRC32C() + checksum.update(this) + return checksum.value.toHexString() +} + +fun ByteArray.crc32(): String { + val checksum = CRC32() + checksum.update(this) + return checksum.value.toHexString() +} + +fun Path.crc32c(): String { + return checksum(this, CRC32C()) +} + +fun Path.crc32(): String { + return checksum(this, CRC32()) +} + +private fun checksum(path: Path, checksum: Checksum): String { + path.inputStream().use { input -> + val buff = ByteArray(DEFAULT_BUFFER_SIZE) + while (input.read(buff) != -1) { + checksum.update(buff) + } + } + return checksum.value.toHexString() +} + fun ByteArray.md5(): String { val md = MessageDigest.getInstance("MD5") return md.digest(this).toHex()