mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-06 11:16:54 +08:00
Simplify platform structure: merge jvmMain into commonMain
This commit is contained in:
parent
c030155505
commit
66464b87fd
@ -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
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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.*
|
@ -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<out TSender : User, out TSubject : Conta
|
||||
}
|
||||
|
||||
/** 一个消息事件在各平台的相关扩展. 请使用 [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 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<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 发送图片 (扩展)
|
||||
|
||||
}
|
@ -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:<ID>]` 格式字符串, 其中 `<ID>` 代表 [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<Image> {
|
||||
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] 实现的基类.
|
||||
*/
|
||||
|
@ -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))
|
@ -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 : Closeable, O : Closeable, R> I.withOut(output: O, block: I.(output: O) -> R): R {
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
@ -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<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
|
||||
}
|
@ -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)
|
@ -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()
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user