Simplify platform structure: merge jvmMain into commonMain

This commit is contained in:
Him188 2020-12-01 12:13:43 +08:00
parent c030155505
commit 66464b87fd
13 changed files with 289 additions and 903 deletions

View File

@ -22,7 +22,6 @@ import net.mamoe.mirai.message.recall
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.OverFileSizeMaxException import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.WeakRefProperty import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.jvm.JvmSynthetic
/** /**

View File

@ -14,7 +14,6 @@
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.event.internal.registerEvent
import java.lang.reflect.Method import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext

View File

@ -15,11 +15,12 @@
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("SendImageUtilsJvmKt") @file:JvmName("SendImageUtilsJvmKt")
package net.mamoe.mirai.message package event
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group 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.Image
import net.mamoe.mirai.message.data.Voice import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*

View File

@ -18,6 +18,7 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import event.*
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.events.BotEvent 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.ExternalImage
import net.mamoe.mirai.utils.sendTo import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.upload import net.mamoe.mirai.utils.upload
import kotlin.jvm.JvmMultifileClass import java.awt.image.BufferedImage
import kotlin.jvm.JvmName import java.io.File
import kotlin.jvm.JvmSynthetic import java.io.InputStream
/** /**
* 一个 (收到的) 消息事件. * 一个 (收到的) 消息事件.
@ -158,9 +159,60 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta
} }
/** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */ /** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */
internal expect interface MessageEventPlatformExtensions<out TSender : User, out TSubject : Contact> {
/**
* 消息事件在 JVM 平台的扩展
* @see MessageEventExtensions
*/
internal interface MessageEventPlatformExtensions<out TSender : User, out TSubject : Contact> {
val subject: TSubject val subject: TSubject
val sender: TSender val sender: TSender
val message: MessageChain val message: MessageChain
val bot: Bot 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<TSubject> = subject.sendImage(image)
@JvmSynthetic
suspend fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image)
@JvmSynthetic
suspend fun sendImage(image: File): MessageReceipt<TSubject> = 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<TSubject> = sendTo(subject)
@JvmSynthetic
suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
@JvmSynthetic
suspend fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
// endregion 发送图片 (扩展)
} }

View File

@ -22,18 +22,18 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import kotlinx.io.core.Input
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.IMirai
import net.mamoe.mirai.Mirai import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.sendImage import net.mamoe.mirai.utils.sendImage
import java.io.File
import java.io.InputStream
import java.net.URL
import kotlin.js.JsName 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] * 在上传时服务器会根据其缓存情况回复已有的图片 ID 或要求客户端上传. 详见 [Contact.uploadImage]
* *
* *
* ### [toString] [contentToString]
* - [toString] 固定返回 `[mirai:image:<ID>]` 格式字符串, 其中 `<ID>` 代表 [imageId].
* - [contentToString] 固定返回 "\[图片]"
*
* ### 上传和发送图片 * ### 上传和发送图片
* @see Contact.uploadImage 上传 [图片文件][ExternalImage] 并得到 [Image] 消息 * @see Contact.uploadImage 上传 [图片文件][ExternalImage] 并得到 [Image] 消息
* @see Contact.sendImage 上传 [图片文件][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 Image.queryUrl 扩展函数. 查询图片下载链接
* @see IMirai.queryImageUrl 查询图片下载链接 (Java 使用) * @see Bot.queryImageUrl 查询图片下载链接 (Java 使用)
* *
* 查看平台 `actual` 定义以获取上传方式扩展. * 查看平台 `actual` 定义以获取上传方式扩展.
* *
@ -64,11 +70,12 @@ import kotlin.jvm.JvmSynthetic
* @see FlashImage 闪照 * @see FlashImage 闪照
* @see Image.flash 转换普通图片为闪照 * @see Image.flash 转换普通图片为闪照
*/ */
public expect interface Image : Message, MessageContent, CodableMessage { public interface Image : Message, MessageContent, CodableMessage {
public companion object Key : Message.Key<Image> { public companion object Key : Message.Key<Image> {
public override val typeName: String override val typeName: String get() = "Image"
} }
/** /**
* 图片的 id. * 图片的 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_1], 示例: `/f8f1ab55-bf8e-4236-b55e-955848d7069f`
* - [FRIEND_IMAGE_ID_REGEX_2], 示例: `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` * - [FRIEND_IMAGE_ID_REGEX_2], 示例: `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206`
* *
* @see Image 使用 id 构造图片 * @see Image 使用 id 构造图片
* @see md5 得到图片文件 MD5
*/ */
public val imageId: String public val imageId: String
/* 实现:
final override fun toString(): String = _stringValue!!
final override fun contentToString(): String = "[图片]"
*/
} }
/** /**
* 所有 [Image] 实现的基类. * 所有 [Image] 实现的基类.
*/ */

View File

@ -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))

View File

@ -9,10 +9,17 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.core.Closeable import kotlinx.io.core.*
import kotlinx.io.core.Input import net.mamoe.mirai.Bot
import kotlinx.io.core.use import net.mamoe.mirai.utils.internal.asReusableInput
import kotlinx.io.errors.IOException 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.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
@ -20,43 +27,179 @@ import kotlin.contracts.contract
* 缓存策略. * 缓存策略.
* *
* 图片上传时默认使用文件缓存. * 图片上传时默认使用文件缓存.
*
* @see BotConfiguration.fileCacheStrategy [Bot] 指定缓存策略
*/ */
@MiraiExperimentalApi @MiraiExperimentalApi
public expect interface FileCacheStrategy { public interface FileCacheStrategy {
/** /**
* [input] 缓存为 [ExternalImage]. * [input] 缓存为 [ExternalImage].
* 此函数应 close 这个 [Input] * 此函数应 close 这个 [Input]
*/ */
@MiraiExperimentalApi @MiraiExperimentalApi
@Throws(IOException::class) @Throws(java.io.IOException::class)
public fun newImageCache(input: Input): ExternalImage 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] 缓存为 [ExternalImage].
* [input] 的内容应是不变的. * [input] 的内容应是不变的.
*/ */
@MiraiExperimentalApi @MiraiExperimentalApi
@Throws(IOException::class) @Throws(java.io.IOException::class)
public fun newImageCache(input: ByteArray): ExternalImage public fun newImageCache(input: ByteArray): ExternalImage
/** /**
* 默认的缓存方案. JVM 平台使用系统临时文件. * [input] 缓存为 [ExternalImage].
* [input] 的内容应是不变的.
*/ */
@MiraiExperimentalApi @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 { public object MemoryCache : FileCacheStrategy {
@MiraiExperimentalApi @MiraiExperimentalApi
@Throws(IOException::class) @Throws(java.io.IOException::class)
public override fun newImageCache(input: Input): ExternalImage override fun newImageCache(input: Input): ExternalImage {
return newImageCache(input.readBytes())
}
@MiraiExperimentalApi @MiraiExperimentalApi
@Throws(IOException::class) @Throws(java.io.IOException::class)
public override fun newImageCache(input: ByteArray): ExternalImage 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 : Closeable, O : Closeable, R> I.withOut(output: O, block: I.(output: O) -> R): R { internal inline fun <I : Closeable, O : Closeable, R> I.withOut(output: O, block: I.(output: O) -> R): R {

View File

@ -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 <E : Event> Class<E>._subscribeEventForJaptOnly(
scope: CoroutineScope,
onEvent: Function<E, ListeningStatus>
): Listener<E> {
return this.kotlin.subscribeInternal(
scope.Handler(
scope.coroutineContext,
Listener.ConcurrencyKind.LOCKED
) { withContext(Dispatchers.IO) { onEvent.apply(it) } })
}
@Suppress("FunctionName")
internal fun <E : Event> Class<E>._subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Consumer<E>): Listener<E> {
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<Event> {
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<out Event>,
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<out Event>,
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<out Event>,
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<out Event>,
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}")
}
}
}

View File

@ -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<out TSender : User, out TSubject : Contact> {
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<TSubject> = subject.sendImage(image)
@JvmSynthetic
suspend fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image)
@JvmSynthetic
suspend fun sendImage(image: File): MessageReceipt<TSubject> = 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<TSubject> = sendTo(subject)
@JvmSynthetic
suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
@JvmSynthetic
suspend fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
// endregion 发送图片 (扩展)
@Deprecated(
"请自行通过 URL.openConnection 得到 InputStream 后调用其扩展",
replaceWith = ReplaceWith("this.openConnection().sendAsImageTo(contact)"),
level = DeprecationLevel.WARNING
)
@JvmSynthetic
@Suppress("DEPRECATION")
suspend fun URL.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
@Deprecated(
"已弃用对 kotlinx.io 的支持",
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
@JvmSynthetic
suspend fun Input.sendAsImage(): MessageReceipt<TSubject> = 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<TSubject> = subject.sendImage(image)
@Deprecated(
"已弃用对 kotlinx.io 的支持",
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
@JvmSynthetic
suspend fun sendImage(image: Input): MessageReceipt<TSubject> = 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)
}

View File

@ -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 码支持
* 格式: &#91;mirai:image:*[Image.imageId]*&#93;
*
* @see FlashImage 闪照
* @see Image.flash 转换普通图片为闪照
*/
public actual interface Image : Message, MessageContent, CodableMessage {
public actual companion object Key : Message.Key<Image> {
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
}

View File

@ -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 <C : Contact> URL.sendAsImageTo(contact: C): MessageReceipt<C> =
toExternalImage().sendTo(contact)
/**
* [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Deprecated(
"已弃用对 kotlinx.io 的支持",
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
@Throws(OverFileSizeMaxException::class)
public suspend fun <C : Contact> Input.sendAsImageTo(contact: C): MessageReceipt<C> =
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 : Contact> C.sendImage(imageUrl: URL): MessageReceipt<C> = imageUrl.sendAsImageTo(this)
/**
* [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Deprecated(
"已弃用对 kotlinx.io 的支持",
level = DeprecationLevel.ERROR
)
@Throws(OverFileSizeMaxException::class)
public suspend inline fun <C : Contact> C.sendImage(imageInput: Input): MessageReceipt<C> =
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)

View File

@ -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()

View File

@ -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
}