diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Contact.kt b/mirai-core-api/src/commonMain/kotlin/contact/Contact.kt index 3b6f2b75f..1cb499503 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Contact.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Contact.kt @@ -22,7 +22,6 @@ import net.mamoe.mirai.message.recall import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.OverFileSizeMaxException import net.mamoe.mirai.utils.WeakRefProperty -import kotlin.jvm.JvmSynthetic /** diff --git a/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt b/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt index 71cbf599a..6c1b44b6a 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt @@ -14,7 +14,6 @@ package net.mamoe.mirai.event import kotlinx.coroutines.* -import net.mamoe.mirai.event.internal.registerEvent import java.lang.reflect.Method import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext diff --git a/mirai-core-api/src/jvmMain/kotlin/message/SendImageUtilsJvm.kt b/mirai-core-api/src/commonMain/kotlin/event/sendTo.kt similarity index 98% rename from mirai-core-api/src/jvmMain/kotlin/message/SendImageUtilsJvm.kt rename to mirai-core-api/src/commonMain/kotlin/event/sendTo.kt index ee66b75b9..7468d1f83 100644 --- a/mirai-core-api/src/jvmMain/kotlin/message/SendImageUtilsJvm.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/sendTo.kt @@ -15,11 +15,12 @@ @file:JvmMultifileClass @file:JvmName("SendImageUtilsJvmKt") -package net.mamoe.mirai.message +package event import kotlinx.coroutines.Dispatchers import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Voice import net.mamoe.mirai.utils.* diff --git a/mirai-core-api/src/commonMain/kotlin/message/MessageEvent.kt b/mirai-core-api/src/commonMain/kotlin/message/MessageEvent.kt index 29f26b117..1f4f16333 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/MessageEvent.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/MessageEvent.kt @@ -18,6 +18,7 @@ package net.mamoe.mirai.message +import event.* import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.BotEvent @@ -26,9 +27,9 @@ import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.sendTo import net.mamoe.mirai.utils.upload -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic +import java.awt.image.BufferedImage +import java.io.File +import java.io.InputStream /** * 一个 (收到的) 消息事件. @@ -158,9 +159,60 @@ public interface MessageEventExtensions { + +/** + * 消息事件在 JVM 平台的扩展 + * @see MessageEventExtensions + */ +internal interface MessageEventPlatformExtensions { val subject: TSubject val sender: TSender val message: MessageChain val bot: Bot -} + + // region 上传图片 + + @JvmSynthetic + suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) + + @JvmSynthetic + suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image) + + @JvmSynthetic + suspend fun uploadImage(image: File): Image = subject.uploadImage(image) + // endregion + + // region 发送图片 + @JvmSynthetic + suspend fun sendImage(image: BufferedImage): MessageReceipt = subject.sendImage(image) + + @JvmSynthetic + suspend fun sendImage(image: InputStream): MessageReceipt = subject.sendImage(image) + + @JvmSynthetic + suspend fun sendImage(image: File): MessageReceipt = subject.sendImage(image) + // endregion + + // region 上传图片 (扩展) + @JvmSynthetic + suspend fun BufferedImage.upload(): Image = upload(subject) + + @JvmSynthetic + suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) + + @JvmSynthetic + suspend fun File.uploadAsImage(): Image = uploadAsImage(subject) + // endregion 上传图片 (扩展) + + // region 发送图片 (扩展) + @JvmSynthetic + suspend fun BufferedImage.send(): MessageReceipt = sendTo(subject) + + @JvmSynthetic + suspend fun InputStream.sendAsImage(): MessageReceipt = sendAsImageTo(subject) + + @JvmSynthetic + suspend fun File.sendAsImage(): MessageReceipt = sendAsImageTo(subject) + // endregion 发送图片 (扩展) + +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt index 7046d4032..fe3d97686 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt @@ -22,18 +22,18 @@ package net.mamoe.mirai.message.data +import kotlinx.io.core.Input import net.mamoe.mirai.Bot -import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.sendImage +import java.io.File +import java.io.InputStream +import java.net.URL import kotlin.js.JsName -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic /** * 自定义表情 (收藏的表情) 和普通图片. @@ -43,18 +43,24 @@ import kotlin.jvm.JvmSynthetic * 在上传时服务器会根据其缓存情况回复已有的图片 ID 或要求客户端上传. 详见 [Contact.uploadImage] * * - * ### [toString] 和 [contentToString] - * - [toString] 固定返回 `[mirai:image:]` 格式字符串, 其中 `` 代表 [imageId]. - * - [contentToString] 固定返回 "\[图片]" - * * ### 上传和发送图片 * @see Contact.uploadImage 上传 [图片文件][ExternalImage] 并得到 [Image] 消息 * @see Contact.sendImage 上传 [图片文件][ExternalImage] 并发送返回的 [Image] 作为一条消息 - * @see Image.sendTo 上传图片并得到 [Image] 消息 + * @see Image.sendTo 上传 [图片文件][ExternalImage] 并得到 [Image] 消息 + * + * @see File.uploadAsImage + * @see InputStream.uploadAsImage + * @see Input.uploadAsImage + * @see URL.uploadAsImage + * + * @see File.sendAsImageTo + * @see InputStream.sendAsImageTo + * @see Input.sendAsImageTo + * @see URL.sendAsImageTo * * ### 下载图片 * @see Image.queryUrl 扩展函数. 查询图片下载链接 - * @see IMirai.queryImageUrl 查询图片下载链接 (Java 使用) + * @see Bot.queryImageUrl 查询图片下载链接 (Java 使用) * * 查看平台 `actual` 定义以获取上传方式扩展. * @@ -64,11 +70,12 @@ import kotlin.jvm.JvmSynthetic * @see FlashImage 闪照 * @see Image.flash 转换普通图片为闪照 */ -public expect interface Image : Message, MessageContent, CodableMessage { +public interface Image : Message, MessageContent, CodableMessage { public companion object Key : Message.Key { - public override val typeName: String + override val typeName: String get() = "Image" } + /** * 图片的 id. * @@ -76,24 +83,16 @@ public expect interface Image : Message, MessageContent, CodableMessage { * * ### 格式 * 群图片: - * - [GROUP_IMAGE_ID_REGEX], 示例: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.ext` (ext系扩展名) + * - [GROUP_IMAGE_ID_REGEX], 示例: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 ".mirai") * * 好友图片: * - [FRIEND_IMAGE_ID_REGEX_1], 示例: `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * - [FRIEND_IMAGE_ID_REGEX_2], 示例: `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` * * @see Image 使用 id 构造图片 - * @see md5 得到图片文件 MD5 */ public val imageId: String - - /* 实现: - final override fun toString(): String = _stringValue!! - - final override fun contentToString(): String = "[图片]" - */ } - /** * 所有 [Image] 实现的基类. */ diff --git a/mirai-core-api/src/commonMain/kotlin/utils/ExternalImageJvm.kt b/mirai-core-api/src/commonMain/kotlin/utils/ExternalImageJvm.kt new file mode 100644 index 000000000..fa4ae7ad5 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/utils/ExternalImageJvm.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") + +package net.mamoe.mirai.utils + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.utils.internal.DeferredReusableInput +import net.mamoe.mirai.utils.internal.asReusableInput +import java.awt.image.BufferedImage +import java.io.File +import java.io.InputStream + +/* + * 将各类型图片容器转为 [ExternalImage] + */ + + +/** + * 将 [BufferedImage] 保存为临时文件, 然后构造 [ExternalImage] + */ +@JvmOverloads +public fun BufferedImage.toExternalImage(formatName: String = "png"): ExternalImage = + ExternalImage(DeferredReusableInput(this, formatName)) + +/** + * 将文件作为 [ExternalImage] 使用. 只会在需要的时候打开文件并读取数据. + * @param deleteOnClose 若为 `true`, 图片发送后将会删除这个文件 + */ +@JvmOverloads +public fun File.toExternalImage(deleteOnClose: Boolean = false): ExternalImage { + require(this.isFile) { "File must be a file" } + require(this.exists()) { "File must exist" } + require(this.canRead()) { "File must can be read" } + return ExternalImage(asReusableInput(deleteOnClose)) +} + +/** + * 将 [InputStream] 委托为 [ExternalImage]. + * 只会在上传图片时才读取 [InputStream] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] + */ +public fun InputStream.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) + +/** + * 将 [ByteArray] 委托为 [ExternalImage]. + */ +public fun ByteArray.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/utils/FileCacheStrategy.common.kt b/mirai-core-api/src/commonMain/kotlin/utils/FileCacheStrategy.common.kt index 3cc627e87..bffe3b98f 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/FileCacheStrategy.common.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/FileCacheStrategy.common.kt @@ -9,10 +9,17 @@ package net.mamoe.mirai.utils -import kotlinx.io.core.Closeable -import kotlinx.io.core.Input -import kotlinx.io.core.use -import kotlinx.io.errors.IOException +import kotlinx.io.core.* +import net.mamoe.mirai.Bot +import net.mamoe.mirai.utils.internal.asReusableInput +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.net.URL +import java.security.MessageDigest +import javax.imageio.ImageIO import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -20,43 +27,179 @@ import kotlin.contracts.contract * 缓存策略. * * 图片上传时默认使用文件缓存. + * + * @see BotConfiguration.fileCacheStrategy 为 [Bot] 指定缓存策略 */ @MiraiExperimentalApi -public expect interface FileCacheStrategy { +public interface FileCacheStrategy { /** * 将 [input] 缓存为 [ExternalImage]. * 此函数应 close 这个 [Input] */ @MiraiExperimentalApi - @Throws(IOException::class) + @Throws(java.io.IOException::class) public fun newImageCache(input: Input): ExternalImage + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此函数应 close 这个 [InputStream] + */ + @MiraiExperimentalApi + @Throws(java.io.IOException::class) + public fun newImageCache(input: InputStream): ExternalImage + /** * 将 [input] 缓存为 [ExternalImage]. * 此 [input] 的内容应是不变的. */ @MiraiExperimentalApi - @Throws(IOException::class) + @Throws(java.io.IOException::class) public fun newImageCache(input: ByteArray): ExternalImage /** - * 默认的缓存方案. 在 JVM 平台使用系统临时文件. + * 将 [input] 缓存为 [ExternalImage]. + * 此 [input] 的内容应是不变的. */ @MiraiExperimentalApi - public object PlatformDefault : FileCacheStrategy + @Throws(java.io.IOException::class) + public fun newImageCache(input: BufferedImage, format: String = "png"): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + */ + @MiraiExperimentalApi + @Throws(java.io.IOException::class) + public fun newImageCache(input: URL): ExternalImage + + /** + * 默认的缓存方案, 使用系统临时文件夹存储. + */ + @MiraiExperimentalApi + public object PlatformDefault : FileCacheStrategy by TempCache(null) /** * 使用内存直接存储所有图片文件. */ public object MemoryCache : FileCacheStrategy { @MiraiExperimentalApi - @Throws(IOException::class) - public override fun newImageCache(input: Input): ExternalImage + @Throws(java.io.IOException::class) + override fun newImageCache(input: Input): ExternalImage { + return newImageCache(input.readBytes()) + } @MiraiExperimentalApi - @Throws(IOException::class) - public override fun newImageCache(input: ByteArray): ExternalImage + @Throws(java.io.IOException::class) + override fun newImageCache(input: InputStream): ExternalImage { + return newImageCache(input.readBytes()) + } + + @MiraiExperimentalApi + @Throws(java.io.IOException::class) + override fun newImageCache(input: ByteArray): ExternalImage { + return ExternalImage(input.asReusableInput()) + } + + @MiraiExperimentalApi + override fun newImageCache(input: BufferedImage, format: String): ExternalImage { + val out = ByteArrayOutputStream() + ImageIO.write(input, format, out) + return newImageCache(out.toByteArray()) + } + + @MiraiExperimentalApi + override fun newImageCache(input: URL): ExternalImage { + val out = ByteArrayOutputStream() + input.openConnection().getInputStream().use { it.copyTo(out) } + return newImageCache(out.toByteArray()) + } } + + /** + * 使用系统临时文件夹缓存图片文件. 在图片使用完毕后删除临时文件. + */ + @MiraiExperimentalApi + public class TempCache @JvmOverloads constructor( + /** + * 缓存图片存放位置. 为 `null` 时使用主机系统的临时文件夹 + */ + public val directory: File? = null + ) : FileCacheStrategy { + @MiraiExperimentalApi + @Throws(java.io.IOException::class) + override fun newImageCache(input: Input): ExternalImage { + return ExternalImage(createTempFile(directory = directory).apply { + deleteOnExit() + input.withOut(this.outputStream()) { copyTo(it) } + }.asReusableInput(true)) + } + + @MiraiExperimentalApi + @Throws(java.io.IOException::class) + override fun newImageCache(input: InputStream): ExternalImage { + return ExternalImage(createTempFile(directory = directory).apply { + deleteOnExit() + input.withOut(this.outputStream()) { copyTo(it) } + }.asReusableInput(true)) + } + + @MiraiExperimentalApi + @Throws(java.io.IOException::class) + override fun newImageCache(input: ByteArray): ExternalImage { + return ExternalImage(input.asReusableInput()) + } + + @MiraiExperimentalApi + override fun newImageCache(input: BufferedImage, format: String): ExternalImage { + val file = createTempFile(directory = directory).apply { deleteOnExit() } + + val digest = MessageDigest.getInstance("md5") + digest.reset() + + file.outputStream().use { out -> + ImageIO.write(input, format, object : OutputStream() { + override fun write(b: Int) { + out.write(b) + digest.update(b.toByte()) + } + + override fun write(b: ByteArray) { + out.write(b) + digest.update(b) + } + + override fun write(b: ByteArray, off: Int, len: Int) { + out.write(b, off, len) + digest.update(b, off, len) + } + }) + } + + @Suppress("DEPRECATION_ERROR") + return ExternalImage(file.asReusableInput(true, digest.digest())) + } + + @MiraiExperimentalApi + override fun newImageCache(input: URL): ExternalImage { + return ExternalImage(createTempFile(directory = directory).apply { + deleteOnExit() + input.openConnection().getInputStream().withOut(this.outputStream()) { copyTo(it) } + }.asReusableInput(true)) + } + } +} + + +@Throws(java.io.IOException::class) +internal fun Input.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE): Long { + var bytesCopied: Long = 0 + val buffer = ByteArray(bufferSize) + var bytes = readAvailable(buffer) + while (bytes >= 0) { + out.write(buffer, 0, bytes) + bytesCopied += bytes + bytes = readAvailable(buffer) + } + return bytesCopied } internal inline fun I.withOut(output: O, block: I.(output: O) -> R): R { diff --git a/mirai-core-api/src/jvmMain/kotlin/event/internal/EventInternalJvm.kt b/mirai-core-api/src/jvmMain/kotlin/event/internal/EventInternalJvm.kt deleted file mode 100644 index 6ca7e8b57..000000000 --- a/mirai-core-api/src/jvmMain/kotlin/event/internal/EventInternalJvm.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("unused") - -package net.mamoe.mirai.event.internal - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import net.mamoe.mirai.event.* -import java.lang.reflect.Method -import java.util.function.Consumer -import java.util.function.Function -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.reflect.KClass -import kotlin.reflect.full.IllegalCallableAccessException -import kotlin.reflect.full.callSuspend -import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.kotlinFunction - -@Suppress("FunctionName") -internal fun Class._subscribeEventForJaptOnly( - scope: CoroutineScope, - onEvent: Function -): Listener { - return this.kotlin.subscribeInternal( - scope.Handler( - scope.coroutineContext, - Listener.ConcurrencyKind.LOCKED - ) { withContext(Dispatchers.IO) { onEvent.apply(it) } }) -} - -@Suppress("FunctionName") -internal fun Class._subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Consumer): Listener { - return this.kotlin.subscribeInternal( - scope.Handler( - EmptyCoroutineContext, - Listener.ConcurrencyKind.LOCKED - ) { withContext(Dispatchers.IO) { onEvent.accept(it) }; ListeningStatus.LISTENING; }) -} - - -@Suppress("UNCHECKED_CAST") -internal fun Method.registerEvent( - owner: Any, - scope: CoroutineScope, - annotation: EventHandler, - coroutineContext: CoroutineContext -): Listener { - this.isAccessible = true - val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull() - return if (kotlinFunction != null) { - // kotlin functions - - val param = kotlinFunction.parameters - when (param.size) { - 3 -> { // ownerClass, receiver, event - check(param[1].type == param[2].type) { "Illegal kotlin function ${kotlinFunction.name}. Receiver and param must have same type" } - check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) { - "Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}" - } - } - 2 -> { // ownerClass, event - check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) { - "Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}" - } - } - else -> error("function ${kotlinFunction.name} must have one Event param") - } - lateinit var listener: Listener<*> - kotlin.runCatching { - kotlinFunction.isAccessible = true - } - suspend fun callFunction(event: Event): Any? { - try { - return when (param.size) { - 3 -> { - if (kotlinFunction.isSuspend) { - kotlinFunction.callSuspend(owner, event, event) - } else withContext(Dispatchers.IO) { // for safety - kotlinFunction.call(owner, event, event) - } - - } - 2 -> { - if (kotlinFunction.isSuspend) { - kotlinFunction.callSuspend(owner, event) - } else withContext(Dispatchers.IO) { // for safety - kotlinFunction.call(owner, event) - } - } - else -> error("stub") - } - } catch (e: IllegalCallableAccessException) { - listener.completeExceptionally(e) - return ListeningStatus.STOPPED - } - } - require(!kotlinFunction.returnType.isMarkedNullable) { - "Kotlin event handlers cannot have nullable return type." - } - require(kotlinFunction.parameters.none { it.type.isMarkedNullable }) { - "Kotlin event handlers cannot have nullable parameter type." - } - when (kotlinFunction.returnType.classifier) { - Unit::class, Nothing::class -> { - scope.subscribeAlways( - param[1].type.classifier as KClass, - priority = annotation.priority, - concurrency = annotation.concurrency, - coroutineContext = coroutineContext - ) { - if (annotation.ignoreCancelled) { - if ((this as? CancellableEvent)?.isCancelled != true) { - callFunction(this) - } - } else callFunction(this) - }.also { listener = it } - } - ListeningStatus::class -> { - scope.subscribe( - param[1].type.classifier as KClass, - priority = annotation.priority, - concurrency = annotation.concurrency, - coroutineContext = coroutineContext - ) { - if (annotation.ignoreCancelled) { - if ((this as? CancellableEvent)?.isCancelled != true) { - callFunction(this) as ListeningStatus - } else ListeningStatus.LISTENING - } else callFunction(this) as ListeningStatus - }.also { listener = it } - } - else -> error("Illegal method return type. Required Void, Nothing or ListeningStatus, found ${kotlinFunction.returnType.classifier}") - } - } else { - // java methods - - val paramType = this.parameters[0].type - check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) { - "Illegal method parameter. Required one exact Event subclass. found $paramType" - } - when (this.returnType) { - Void::class.java, Void.TYPE, Nothing::class.java -> { - scope.subscribeAlways( - paramType.kotlin as KClass, - priority = annotation.priority, - concurrency = annotation.concurrency, - coroutineContext = coroutineContext - ) { - if (annotation.ignoreCancelled) { - if ((this as? CancellableEvent)?.isCancelled != true) { - withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this@subscribeAlways) - } - } - } else withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this@subscribeAlways) - } - } - } - ListeningStatus::class.java -> { - scope.subscribe( - paramType.kotlin as KClass, - priority = annotation.priority, - concurrency = annotation.concurrency, - coroutineContext = coroutineContext - ) { - if (annotation.ignoreCancelled) { - if ((this as? CancellableEvent)?.isCancelled != true) { - withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this@subscribe) as ListeningStatus - } - } else ListeningStatus.LISTENING - } else withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this@subscribe) as ListeningStatus - } - - } - } - else -> error("Illegal method return type. Required Void or ListeningStatus, but found ${this.returnType.canonicalName}") - } - } -} \ No newline at end of file diff --git a/mirai-core-api/src/jvmMain/kotlin/message/MessageEventPlatform.kt b/mirai-core-api/src/jvmMain/kotlin/message/MessageEventPlatform.kt deleted file mode 100644 index 121c5a103..000000000 --- a/mirai-core-api/src/jvmMain/kotlin/message/MessageEventPlatform.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("unused") - -package net.mamoe.mirai.message - -import kotlinx.io.core.Input -import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.contact.User -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.MessageChain -import java.awt.image.BufferedImage -import java.io.File -import java.io.InputStream -import java.net.URL - -/** - * 消息事件在 JVM 平台的扩展 - * @see MessageEventExtensions - */ -internal actual interface MessageEventPlatformExtensions { - actual val subject: TSubject - actual val sender: TSender - actual val message: MessageChain - actual val bot: Bot - - // region 上传图片 - - @JvmSynthetic - suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) - - @JvmSynthetic - suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image) - - @JvmSynthetic - suspend fun uploadImage(image: File): Image = subject.uploadImage(image) - // endregion - - // region 发送图片 - @JvmSynthetic - suspend fun sendImage(image: BufferedImage): MessageReceipt = subject.sendImage(image) - - @JvmSynthetic - suspend fun sendImage(image: InputStream): MessageReceipt = subject.sendImage(image) - - @JvmSynthetic - suspend fun sendImage(image: File): MessageReceipt = subject.sendImage(image) - // endregion - - // region 上传图片 (扩展) - @JvmSynthetic - suspend fun BufferedImage.upload(): Image = upload(subject) - - @JvmSynthetic - suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) - - @JvmSynthetic - suspend fun File.uploadAsImage(): Image = uploadAsImage(subject) - // endregion 上传图片 (扩展) - - // region 发送图片 (扩展) - @JvmSynthetic - suspend fun BufferedImage.send(): MessageReceipt = sendTo(subject) - - @JvmSynthetic - suspend fun InputStream.sendAsImage(): MessageReceipt = sendAsImageTo(subject) - - @JvmSynthetic - suspend fun File.sendAsImage(): MessageReceipt = sendAsImageTo(subject) - // endregion 发送图片 (扩展) - - - @Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING - ) - @JvmSynthetic - @Suppress("DEPRECATION") - suspend fun URL.sendAsImage(): MessageReceipt = sendAsImageTo(subject) - - @Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR - ) - @Suppress("DEPRECATION_ERROR") - @JvmSynthetic - suspend fun Input.sendAsImage(): MessageReceipt = sendAsImageTo(subject) - - @Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING - ) - @JvmSynthetic - @Suppress("DEPRECATION") - suspend fun uploadImage(image: URL): Image = subject.uploadImage(image) - - @Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR - ) - @Suppress("DEPRECATION_ERROR") - @JvmSynthetic - suspend fun uploadImage(image: Input): Image = subject.uploadImage(image) - - @Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING - ) - @Suppress("DEPRECATION") - @JvmSynthetic - suspend fun sendImage(image: URL): MessageReceipt = subject.sendImage(image) - - @Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR - ) - @Suppress("DEPRECATION_ERROR") - @JvmSynthetic - suspend fun sendImage(image: Input): MessageReceipt = subject.sendImage(image) - - @Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING - ) - @Suppress("DEPRECATION") - @JvmSynthetic - suspend fun URL.uploadAsImage(): Image = uploadAsImage(subject) - - @Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR - ) - @Suppress("DEPRECATION_ERROR") - @JvmSynthetic - suspend fun Input.uploadAsImage(): Image = uploadAsImage(subject) - -} \ No newline at end of file diff --git a/mirai-core-api/src/jvmMain/kotlin/message/data/Image.kt b/mirai-core-api/src/jvmMain/kotlin/message/data/Image.kt deleted file mode 100644 index fbd27c3de..000000000 --- a/mirai-core-api/src/jvmMain/kotlin/message/data/Image.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:JvmMultifileClass -@file:JvmName("MessageUtils") - -package net.mamoe.mirai.message.data - -import kotlinx.io.core.Input -import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.message.code.CodableMessage -import net.mamoe.mirai.message.sendAsImageTo -import net.mamoe.mirai.message.sendImage -import net.mamoe.mirai.message.uploadAsImage -import net.mamoe.mirai.message.uploadImage -import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.sendImage -import java.io.File -import java.io.InputStream -import java.net.URL - -/** - * 自定义表情 (收藏的表情) 和普通图片. - * - * - * 最推荐的存储方式是存储图片原文件, 每次发送图片时都使用文件上传. - * 在上传时服务器会根据其缓存情况回复已有的图片 ID 或要求客户端上传. 详见 [Contact.uploadImage] - * - * - * ### 上传和发送图片 - * @see Contact.uploadImage 上传 [图片文件][ExternalImage] 并得到 [Image] 消息 - * @see Contact.sendImage 上传 [图片文件][ExternalImage] 并发送返回的 [Image] 作为一条消息 - * @see Image.sendTo 上传 [图片文件][ExternalImage] 并得到 [Image] 消息 - * - * @see File.uploadAsImage - * @see InputStream.uploadAsImage - * @see Input.uploadAsImage - * @see URL.uploadAsImage - * - * @see File.sendAsImageTo - * @see InputStream.sendAsImageTo - * @see Input.sendAsImageTo - * @see URL.sendAsImageTo - * - * ### 下载图片 - * @see Image.queryUrl 扩展函数. 查询图片下载链接 - * @see Bot.queryImageUrl 查询图片下载链接 (Java 使用) - * - * 查看平台 `actual` 定义以获取上传方式扩展. - * - * ## mirai 码支持 - * 格式: [mirai:image:*[Image.imageId]*] - * - * @see FlashImage 闪照 - * @see Image.flash 转换普通图片为闪照 - */ -public actual interface Image : Message, MessageContent, CodableMessage { - public actual companion object Key : Message.Key { - actual override val typeName: String get() = "Image" - } - - - /** - * 图片的 id. - * - * 图片 id 不一定会长时间保存, 也可能在将来改变格式, 因此不建议使用 id 发送图片. - * - * ### 格式 - * 群图片: - * - [GROUP_IMAGE_ID_REGEX], 示例: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 ".mirai") - * - * 好友图片: - * - [FRIEND_IMAGE_ID_REGEX_1], 示例: `/f8f1ab55-bf8e-4236-b55e-955848d7069f` - * - [FRIEND_IMAGE_ID_REGEX_2], 示例: `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` - * - * @see Image 使用 id 构造图片 - */ - public actual val imageId: String -} \ No newline at end of file diff --git a/mirai-core-api/src/jvmMain/kotlin/message/deprecated.kt b/mirai-core-api/src/jvmMain/kotlin/message/deprecated.kt deleted file mode 100644 index a8d909f3d..000000000 --- a/mirai-core-api/src/jvmMain/kotlin/message/deprecated.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("DEPRECATION", "DEPRECATION_ERROR") -@file:JvmMultifileClass -@file:JvmName("SendImageUtilsJvmKt") - -package net.mamoe.mirai.message - -import kotlinx.coroutines.Dispatchers -import kotlinx.io.core.Input -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.utils.OverFileSizeMaxException -import net.mamoe.mirai.utils.sendTo -import net.mamoe.mirai.utils.toExternalImage -import net.mamoe.mirai.utils.upload -import java.net.URL - - -/** - * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 - * @throws OverFileSizeMaxException - */ -@Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING -) -@Throws(OverFileSizeMaxException::class) -public suspend fun URL.sendAsImageTo(contact: C): MessageReceipt = - toExternalImage().sendTo(contact) - -/** - * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 - * @throws OverFileSizeMaxException - */ -@Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR -) -@Suppress("DEPRECATION_ERROR") -@Throws(OverFileSizeMaxException::class) -public suspend fun Input.sendAsImageTo(contact: C): MessageReceipt = - toExternalImage().sendTo(contact) - - -/** - * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image] - * @throws OverFileSizeMaxException - */ -@Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING -) -@Throws(OverFileSizeMaxException::class) -public suspend fun URL.uploadAsImage(contact: Contact): Image = - toExternalImage().upload(contact) - - -/** - * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 - * @throws OverFileSizeMaxException - */ -@Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING -) -@Throws(OverFileSizeMaxException::class) -public suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this) - -/** - * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 - * @throws OverFileSizeMaxException - */ -@Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR -) -@Throws(OverFileSizeMaxException::class) -public suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this) - - -/** - * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 - * @throws OverFileSizeMaxException - */ -@Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"), - level = DeprecationLevel.WARNING -) -@Throws(OverFileSizeMaxException::class) -public suspend inline fun C.sendImage(imageUrl: URL): MessageReceipt = imageUrl.sendAsImageTo(this) - -/** - * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 - * @throws OverFileSizeMaxException - */ - -@Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR -) -@Throws(OverFileSizeMaxException::class) -public suspend inline fun C.sendImage(imageInput: Input): MessageReceipt = - imageInput.sendAsImageTo(this) - -/** - * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image] - * @throws OverFileSizeMaxException - */ -@Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR -) -@Throws(OverFileSizeMaxException::class) -public suspend fun Input.uploadAsImage(contact: Contact): Image = - toExternalImage().upload(contact) diff --git a/mirai-core-api/src/jvmMain/kotlin/utils/ExternalImageJvm.kt b/mirai-core-api/src/jvmMain/kotlin/utils/ExternalImageJvm.kt deleted file mode 100644 index 30ef74cbd..000000000 --- a/mirai-core-api/src/jvmMain/kotlin/utils/ExternalImageJvm.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") - -package net.mamoe.mirai.utils - -import kotlinx.io.core.Input -import net.mamoe.mirai.Bot -import net.mamoe.mirai.utils.internal.DeferredReusableInput -import net.mamoe.mirai.utils.internal.asReusableInput -import java.awt.image.BufferedImage -import java.io.File -import java.io.InputStream -import java.net.URL - -/* - * 将各类型图片容器转为 [ExternalImage] - */ - - -/** - * 将 [BufferedImage] 保存为临时文件, 然后构造 [ExternalImage] - */ -@JvmOverloads -public fun BufferedImage.toExternalImage(formatName: String = "png"): ExternalImage = - ExternalImage(DeferredReusableInput(this, formatName)) - -/** - * 将文件作为 [ExternalImage] 使用. 只会在需要的时候打开文件并读取数据. - * @param deleteOnClose 若为 `true`, 图片发送后将会删除这个文件 - */ -@JvmOverloads -public fun File.toExternalImage(deleteOnClose: Boolean = false): ExternalImage { - require(this.isFile) { "File must be a file" } - require(this.exists()) { "File must exist" } - require(this.canRead()) { "File must can be read" } - return ExternalImage(asReusableInput(deleteOnClose)) -} - -/** - * 将 [InputStream] 委托为 [ExternalImage]. - * 只会在上传图片时才读取 [InputStream] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] - */ -public fun InputStream.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) - -/** - * 将 [ByteArray] 委托为 [ExternalImage]. - */ -public fun ByteArray.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) - -/** - * 将 [URL] 委托为 [ExternalImage]. - * - * 只会在上传图片时才读取 [URL] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] - */ -@Deprecated( - "请自行通过 URL.openConnection 得到 InputStream 后调用其扩展", - replaceWith = ReplaceWith("this.openConnection().toExternalImage"), - level = DeprecationLevel.WARNING -) -public fun URL.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) - -/** - * 将 [Input] 委托为 [ExternalImage]. - * 只会在上传图片时才读取 [Input] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] - */ -@Deprecated( - "已弃用对 kotlinx.io 的支持", - level = DeprecationLevel.ERROR -) -public fun Input.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) - - -@PlannedRemoval("1.2.0") -@Suppress("RedundantSuspendModifier", "DEPRECATION_ERROR") -@Deprecated("no need", ReplaceWith("toExternalImage()"), level = DeprecationLevel.HIDDEN) -public suspend fun Input.suspendToExternalImage(): ExternalImage = toExternalImage() - -@Suppress("RedundantSuspendModifier") -@PlannedRemoval("1.2.0") -@Deprecated("no need", ReplaceWith("toExternalImage()"), level = DeprecationLevel.HIDDEN) -public suspend fun InputStream.suspendToExternalImage(): ExternalImage = toExternalImage() - -@Suppress("RedundantSuspendModifier", "DEPRECATION") -@PlannedRemoval("1.2.0") -@Deprecated("no need", ReplaceWith("toExternalImage()"), level = DeprecationLevel.HIDDEN) -public suspend fun URL.suspendToExternalImage(): ExternalImage = toExternalImage() - -@Suppress("RedundantSuspendModifier") -@PlannedRemoval("1.2.0") -@Deprecated("no need", ReplaceWith("toExternalImage()"), level = DeprecationLevel.HIDDEN) -public suspend fun File.suspendToExternalImage(): ExternalImage = toExternalImage() - -@Suppress("RedundantSuspendModifier") -@PlannedRemoval("1.2.0") -@Deprecated("no need", ReplaceWith("toExternalImage()"), level = DeprecationLevel.HIDDEN) -public suspend fun BufferedImage.suspendToExternalImage(): ExternalImage = toExternalImage() diff --git a/mirai-core-api/src/jvmMain/kotlin/utils/FileCacheStrategy.jvm.kt b/mirai-core-api/src/jvmMain/kotlin/utils/FileCacheStrategy.jvm.kt deleted file mode 100644 index f24984f39..000000000 --- a/mirai-core-api/src/jvmMain/kotlin/utils/FileCacheStrategy.jvm.kt +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("MemberVisibilityCanBePrivate") - -package net.mamoe.mirai.utils - -import kotlinx.io.core.Input -import kotlinx.io.core.readAvailable -import kotlinx.io.core.readBytes -import net.mamoe.mirai.Bot -import net.mamoe.mirai.utils.internal.asReusableInput -import java.awt.image.BufferedImage -import java.io.* -import java.net.URL -import java.security.MessageDigest -import javax.imageio.ImageIO - -/** - * 缓存策略. - * - * 图片上传时默认使用文件缓存. - * - * @see BotConfiguration.fileCacheStrategy 为 [Bot] 指定缓存策略 - */ -@MiraiExperimentalApi -public actual interface FileCacheStrategy { - /** - * 将 [input] 缓存为 [ExternalImage]. - * 此函数应 close 这个 [Input] - */ - @MiraiExperimentalApi - @Throws(IOException::class) - public actual fun newImageCache(input: Input): ExternalImage - - /** - * 将 [input] 缓存为 [ExternalImage]. - * 此函数应 close 这个 [InputStream] - */ - @MiraiExperimentalApi - @Throws(IOException::class) - public fun newImageCache(input: InputStream): ExternalImage - - /** - * 将 [input] 缓存为 [ExternalImage]. - * 此 [input] 的内容应是不变的. - */ - @MiraiExperimentalApi - @Throws(IOException::class) - public actual fun newImageCache(input: ByteArray): ExternalImage - - /** - * 将 [input] 缓存为 [ExternalImage]. - * 此 [input] 的内容应是不变的. - */ - @MiraiExperimentalApi - @Throws(IOException::class) - public fun newImageCache(input: BufferedImage, format: String = "png"): ExternalImage - - /** - * 将 [input] 缓存为 [ExternalImage]. - */ - @MiraiExperimentalApi - @Throws(IOException::class) - public fun newImageCache(input: URL): ExternalImage - - /** - * 默认的缓存方案, 使用系统临时文件夹存储. - */ - @MiraiExperimentalApi - public actual object PlatformDefault : FileCacheStrategy by TempCache(null) - - /** - * 使用内存直接存储所有图片文件. - */ - public actual object MemoryCache : FileCacheStrategy { - @MiraiExperimentalApi - @Throws(IOException::class) - actual override fun newImageCache(input: Input): ExternalImage { - return newImageCache(input.readBytes()) - } - - @MiraiExperimentalApi - @Throws(IOException::class) - override fun newImageCache(input: InputStream): ExternalImage { - return newImageCache(input.readBytes()) - } - - @MiraiExperimentalApi - @Throws(IOException::class) - actual override fun newImageCache(input: ByteArray): ExternalImage { - return ExternalImage(input.asReusableInput()) - } - - @MiraiExperimentalApi - override fun newImageCache(input: BufferedImage, format: String): ExternalImage { - val out = ByteArrayOutputStream() - ImageIO.write(input, format, out) - return newImageCache(out.toByteArray()) - } - - @MiraiExperimentalApi - override fun newImageCache(input: URL): ExternalImage { - val out = ByteArrayOutputStream() - input.openConnection().getInputStream().use { it.copyTo(out) } - return newImageCache(out.toByteArray()) - } - } - - /** - * 使用系统临时文件夹缓存图片文件. 在图片使用完毕后删除临时文件. - */ - @MiraiExperimentalApi - public class TempCache @JvmOverloads constructor( - /** - * 缓存图片存放位置. 为 `null` 时使用主机系统的临时文件夹 - */ - public val directory: File? = null - ) : FileCacheStrategy { - @MiraiExperimentalApi - @Throws(IOException::class) - override fun newImageCache(input: Input): ExternalImage { - return ExternalImage(createTempFile(directory = directory).apply { - deleteOnExit() - input.withOut(this.outputStream()) { copyTo(it) } - }.asReusableInput(true)) - } - - @MiraiExperimentalApi - @Throws(IOException::class) - override fun newImageCache(input: InputStream): ExternalImage { - return ExternalImage(createTempFile(directory = directory).apply { - deleteOnExit() - input.withOut(this.outputStream()) { copyTo(it) } - }.asReusableInput(true)) - } - - @MiraiExperimentalApi - @Throws(IOException::class) - override fun newImageCache(input: ByteArray): ExternalImage { - return ExternalImage(input.asReusableInput()) - } - - @MiraiExperimentalApi - override fun newImageCache(input: BufferedImage, format: String): ExternalImage { - val file = createTempFile(directory = directory).apply { deleteOnExit() } - - val digest = MessageDigest.getInstance("md5") - digest.reset() - - file.outputStream().use { out -> - ImageIO.write(input, format, object : OutputStream() { - override fun write(b: Int) { - out.write(b) - digest.update(b.toByte()) - } - - override fun write(b: ByteArray) { - out.write(b) - digest.update(b) - } - - override fun write(b: ByteArray, off: Int, len: Int) { - out.write(b, off, len) - digest.update(b, off, len) - } - }) - } - - @Suppress("DEPRECATION_ERROR") - return ExternalImage(file.asReusableInput(true, digest.digest())) - } - - @MiraiExperimentalApi - override fun newImageCache(input: URL): ExternalImage { - return ExternalImage(createTempFile(directory = directory).apply { - deleteOnExit() - input.openConnection().getInputStream().withOut(this.outputStream()) { copyTo(it) } - }.asReusableInput(true)) - } - } -} - - -@Throws(IOException::class) -internal fun Input.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE): Long { - var bytesCopied: Long = 0 - val buffer = ByteArray(bufferSize) - var bytes = readAvailable(buffer) - while (bytes >= 0) { - out.write(buffer, 0, bytes) - bytesCopied += bytes - bytes = readAvailable(buffer) - } - return bytesCopied -} \ No newline at end of file