From 5b2ae6e9ad8945e79caef20d0c41129230f2f831 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 23 Apr 2020 17:10:21 +0800 Subject: [PATCH] Simplify Image structure, close #244 --- .../mirai/qqandroid/contact/FriendImpl.kt | 25 +--- .../mirai/qqandroid/contact/GroupImpl.kt | 18 +-- .../mirai/qqandroid/message/FlashImageImpl.kt | 8 +- .../mirai/qqandroid/message/imagesImpl.kt | 69 ++--------- .../qqandroid/network/QQAndroidClient.kt | 3 +- .../network/protocol/data/proto/Cmd0x352.kt | 4 +- .../protocol/packet/chat/image/ImgStore.kt | 15 ++- .../net.mamoe.mirai/message/data/Image.kt | 116 ++++-------------- .../net.mamoe.mirai/utils/ExternalImage.kt | 92 +++----------- .../mamoe/mirai/utils/ExternalImageTest.kt | 6 +- .../net/mamoe/mirai/message/data/Image.kt | 3 +- .../net/mamoe/mirai/utils/ExternalImageJvm.kt | 39 +++--- .../net/mamoe/mirai/utils/SystemDeviceInfo.kt | 4 +- 13 files changed, 113 insertions(+), 289 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt index 46efcef23..9c00a644d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt @@ -91,24 +91,14 @@ internal class FriendImpl( fileId = 0, fileMd5 = image.md5, fileSize = image.inputSize.toInt(), - fileName = image.md5.toUHexString("") + "." + image.format, - imgOriginal = 1, - imgWidth = image.width, - imgHeight = image.height, - imgType = image.imageType + fileName = image.md5.toUHexString("") + ".gif", + imgOriginal = 1 ) ).sendAndExpect() @Suppress("UNCHECKED_CAST") // bug return when (response) { - is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage( - filepath = response.resourceId, - md5 = response.imageInfo.fileMd5, - fileLength = response.imageInfo.fileSize.toInt(), - height = response.imageInfo.fileHeight, - width = response.imageInfo.fileWidth, - resourceId = response.resourceId - ).also { + is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(response.resourceId).also { ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast() } is LongConn.OffPicUp.Response.RequireUpload -> { @@ -132,14 +122,7 @@ internal class FriendImpl( //) // 为什么不能 ?? - return OfflineFriendImage( - filepath = response.resourceId, - md5 = image.md5, - fileLength = image.inputSize.toInt(), - height = image.height, - width = image.width, - resourceId = response.resourceId - ).also { + return OfflineFriendImage(response.resourceId).also { ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast() } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt index 300f8851a..9194a771a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt @@ -400,11 +400,7 @@ internal class GroupImpl( uin = bot.id, groupCode = id, md5 = image.md5, - size = image.inputSize, - picWidth = image.width, - picHeight = image.height, - picType = image.imageType, - filename = image.filename + size = image.inputSize ).sendAndExpect() @Suppress("UNCHECKED_CAST") // bug @@ -427,10 +423,8 @@ internal class GroupImpl( // fileId = response.fileId.toInt() // ) // println("NMSL") - return OfflineGroupImage( - md5 = image.md5, - filepath = resourceId - ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } + return OfflineGroupImage(imageId = resourceId) + .also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } } is ImgStore.GroupPicUp.Response.RequireUpload -> { // 每 10KB 等 1 秒, 最少等待 5 秒 @@ -480,10 +474,8 @@ internal class GroupImpl( // imageType = image.imageType, // fileId = response.fileId.toInt() // ) - return OfflineGroupImage( - md5 = image.md5, - filepath = resourceId - ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } + return OfflineGroupImage(imageId = resourceId) + .also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } /* fileId = response.fileId.toInt(), fileType = 0, // ? diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/FlashImageImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/FlashImageImpl.kt index 703d09b7f..8c30ec181 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/FlashImageImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/FlashImageImpl.kt @@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.message import net.mamoe.mirai.message.data.FriendFlashImage import net.mamoe.mirai.message.data.GroupFlashImage +import net.mamoe.mirai.message.data.md5 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray @@ -13,7 +14,7 @@ internal fun GroupFlashImage.toJceData() = ImMsgBody.Elem( businessType = 0, pbElem = HummerCommelem.MsgElemInfoServtype3( flashTroopPic = ImMsgBody.CustomFace( - filePath = image.filepath, + filePath = image.imageId, md5 = image.md5, pbReserve = byteArrayOf(0x78, 0x06) ) @@ -27,9 +28,8 @@ internal fun FriendFlashImage.toJceData() = ImMsgBody.Elem( businessType = 0, pbElem = HummerCommelem.MsgElemInfoServtype3( flashC2cPic = ImMsgBody.NotOnlineImage( - filePath = image.filepath, - fileId = image.fileId, - resId = image.resourceId, + filePath = image.imageId, + resId = image.imageId, picMd5 = image.md5, oldPicMd5 = false, pbReserve = byteArrayOf(0x78, 0x06) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt index e3ab8744c..5c8424e52 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt @@ -9,10 +9,7 @@ package net.mamoe.mirai.qqandroid.message -import net.mamoe.mirai.message.data.OfflineFriendImage -import net.mamoe.mirai.message.data.OfflineGroupImage -import net.mamoe.mirai.message.data.OnlineFriendImage -import net.mamoe.mirai.message.data.OnlineGroupImage +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.utils.hexToBytes import net.mamoe.mirai.utils.ExternalImage @@ -20,28 +17,12 @@ import net.mamoe.mirai.utils.ExternalImage internal class OnlineGroupImageImpl( internal val delegate: ImMsgBody.CustomFace ) : OnlineGroupImage() { - override val filepath: String = delegate.filePath - override val fileId: Int get() = delegate.fileId - override val serverIp: Int get() = delegate.serverIp - override val serverPort: Int get() = delegate.serverPort - override val fileType: Int get() = delegate.fileType - override val signature: ByteArray get() = delegate.signature - override val useful: Int get() = delegate.useful - override val md5: ByteArray get() = delegate.md5 - override val bizType: Int get() = delegate.bizType - override val imageType: Int get() = delegate.imageType - override val width: Int get() = delegate.width - override val height: Int get() = delegate.height - override val source: Int get() = delegate.source - override val size: Int get() = delegate.size - override val original: Int get() = delegate.origin - override val pbReserve: ByteArray get() = delegate.pbReserve - override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType) + override val imageId: String = ExternalImage.generateImageId(delegate.md5) override val originUrl: String get() = "http://gchat.qpic.cn" + delegate.origUrl override fun equals(other: Any?): Boolean { - return other is OnlineGroupImageImpl && other.filepath == this.filepath && other.md5.contentEquals(this.md5) + return other is OnlineGroupImageImpl && other.imageId == this.imageId } override fun hashCode(): Int { @@ -52,23 +33,13 @@ internal class OnlineGroupImageImpl( internal class OnlineFriendImageImpl( internal val delegate: ImMsgBody.NotOnlineImage ) : OnlineFriendImage() { - override val resourceId: String get() = delegate.resId - override val md5: ByteArray get() = delegate.picMd5 - override val filepath: String get() = delegate.filePath - override val fileLength: Int get() = delegate.fileLen - override val height: Int get() = delegate.picHeight - override val width: Int get() = delegate.picWidth - override val bizType: Int get() = delegate.bizType - override val imageType: Int get() = delegate.imgType - override val downloadPath: String get() = delegate.downloadPath - override val fileId: Int get() = delegate.fileId + override val imageId: String get() = delegate.resId override val original: Int get() = delegate.original override val originUrl: String get() = "http://c2cpicdw.qpic.cn" + this.delegate.origUrl override fun equals(other: Any?): Boolean { - return other is OnlineFriendImageImpl && other.resourceId == this.resourceId && other.md5 - .contentEquals(this.md5) + return other is OnlineFriendImageImpl && other.imageId == this.imageId } override fun hashCode(): Int { @@ -78,22 +49,8 @@ internal class OnlineFriendImageImpl( internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { return ImMsgBody.CustomFace( - filePath = this.filepath, - fileId = this.fileId, - serverIp = this.serverIp, - serverPort = this.serverPort, - fileType = this.fileType, - signature = this.signature, - useful = this.useful, + filePath = this.imageId, md5 = this.md5, - bizType = this.bizType, - imageType = this.imageType, - width = this.width, - height = this.height, - source = this.source, - size = this.size, - origin = this.original, - pbReserve = this.pbReserve, flag = ByteArray(4), //_400Height = 235, //_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", @@ -108,18 +65,12 @@ private val oldData: ByteArray = internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage { return ImMsgBody.NotOnlineImage( - filePath = this.filepath, - resId = this.resourceId, + filePath = this.imageId, + resId = this.imageId, oldPicMd5 = false, picMd5 = this.md5, - fileLen = this.fileLength, - picHeight = this.height, - picWidth = this.width, - bizType = this.bizType, - imgType = this.imageType, - downloadPath = this.downloadPath, - original = this.original, - fileId = this.fileId, + downloadPath = this.imageId, + original = 1, pbReserve = byteArrayOf(0x78, 0x02) ) } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index fc6b882e2..b6a5bee68 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -28,7 +28,6 @@ import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.utils.* import kotlin.random.Random -import kotlin.random.nextInt internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress) @@ -43,7 +42,7 @@ private fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray /** * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] */ -internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } +internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0, 255).toByte() } internal object DefaultServerList : Set> by setOf( "42.81.169.46" to 8080, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt index 16a08bd8f..6c528795d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt @@ -129,8 +129,8 @@ internal class Cmd0x352 : ProtoBuf { @ProtoId(11) @JvmField val retry: Int = 0,//default @ProtoId(12) @JvmField val buType: Int = 1,//1或96 不确定 @ProtoId(13) @JvmField val imgOriginal: Int,//是否为原图 - @ProtoId(14) @JvmField val imgWidth: Int, - @ProtoId(15) @JvmField val imgHeight: Int, + @ProtoId(14) @JvmField val imgWidth: Int = 0, + @ProtoId(15) @JvmField val imgHeight: Int = 0, /** * ImgType: * JPG: 1000 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt index b1c7d2697..fdbbf0c21 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt @@ -19,6 +19,19 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf +import kotlin.random.Random +import kotlin.random.nextInt + +internal fun getRandomString(length: Int): String = + getRandomString(length, *defaultRanges) + +private val defaultRanges: Array = arrayOf('a'..'z', 'A'..'Z', '0'..'9') + +internal fun getRandomString(length: Int, charRange: CharRange): String = + String(CharArray(length) { charRange.random() }) + +internal fun getRandomString(length: Int, vararg charRanges: CharRange): String = + String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }) internal class ImgStore { object GroupPicUp : OutgoingPacketFactory("ImgStore.GroupPicUp") { @@ -33,7 +46,7 @@ internal class ImgStore { picHeight: Int = 0, // not orthodox picType: Int = 1000, fileId: Long = 0, - filename: String, + filename: String = getRandomString(16) + ".gif", // make server happier srcTerm: Int = 5, platformType: Int = 9, buType: Int = 1, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index 663b8c092..d504c68c1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -15,12 +15,12 @@ package net.mamoe.mirai.message.data import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient import net.mamoe.mirai.Bot import net.mamoe.mirai.BotImpl import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.SinceMirai import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -65,7 +65,18 @@ expect interface Image : Message, MessageContent { @Suppress("FunctionName") @JsName("newImage") @JvmName("newImage") -fun Image(imageId: String): Image = when { +fun Image(imageId: String): OfflineImage = when { + imageId.startsWith('/') -> OfflineFriendImage(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f + imageId.length == 42 || imageId.startsWith('{') && imageId.endsWith('}') -> OfflineGroupImage(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png + else -> throw IllegalArgumentException("illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE") +} + +@JvmSynthetic +@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) +@Suppress("FunctionName") +@JsName("newImage") +@JvmName("newImage") +fun Image2(imageId: String): Image = when { imageId.startsWith('/') -> OfflineFriendImage(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f imageId.length == 42 || imageId.startsWith('{') && imageId.endsWith('}') -> OfflineGroupImage(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png else -> throw IllegalArgumentException("illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE") @@ -100,8 +111,7 @@ sealed class AbstractImage : Image { */ interface OnlineImage : Image { companion object Key : Message.Key { - override val typeName: String - get() = "OnlineImage" + override val typeName: String get() = "OnlineImage" } /** @@ -113,6 +123,7 @@ interface OnlineImage : Image { /** * 查询原图下载链接. */ +@JvmSynthetic suspend fun Image.queryUrl(): String { @OptIn(MiraiInternalAPI::class) return when (this) { @@ -135,8 +146,7 @@ suspend fun Image.queryUrl(): String { */ interface OfflineImage : Image { companion object Key : Message.Key { - override val typeName: String - get() = "OfflineImage" + override val typeName: String get() = "OfflineImage" } } @@ -163,26 +173,8 @@ suspend fun OfflineImage.queryUrl(): String { @OptIn(MiraiInternalAPI::class) sealed class GroupImage : AbstractImage() { companion object Key : Message.Key { - override val typeName: String - get() = "GroupImage" + override val typeName: String get() = "GroupImage" } - - abstract val filepath: String - abstract val fileId: Int - abstract val serverIp: Int - abstract val serverPort: Int - abstract val fileType: Int - abstract val signature: ByteArray - abstract val useful: Int - abstract val md5: ByteArray - abstract val bizType: Int - abstract val imageType: Int - abstract val width: Int - abstract val height: Int - abstract val source: Int - abstract val size: Int - abstract val pbReserve: ByteArray - abstract val original: Int } /** @@ -190,35 +182,13 @@ sealed class GroupImage : AbstractImage() { */ @Serializable data class OfflineGroupImage( - override val filepath: String, // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png - override val md5: ByteArray -) : GroupImage(), OfflineImage { - constructor(imageId: String) : this(filepath = imageId, md5 = calculateImageMd5ByImageId(imageId)) + override val imageId: String +) : GroupImage(), OfflineImage - override val fileId: Int get() = 0 - override val serverIp: Int get() = 0 - override val serverPort: Int get() = 0 - override val fileType: Int get() = 0 // 0 - override val signature: ByteArray get() = EMPTY_BYTE_ARRAY - override val useful: Int get() = 1 - override val bizType: Int get() = 0 - override val imageType: Int get() = 0 - override val width: Int get() = 0 - override val height: Int get() = 0 - override val source: Int get() = 200 - override val size: Int get() = 0 - override val original: Int get() = 1 - override val pbReserve: ByteArray get() = EMPTY_BYTE_ARRAY - override val imageId: String get() = filepath - - override fun hashCode(): Int { - return filepath.hashCode() + 31 * md5.hashCode() - } - - override fun equals(other: Any?): Boolean { - return other is OfflineGroupImage && other::class == this::class && other.md5.contentEquals(this.md5) && other.filepath == this.filepath - } -} +@get:JvmName("calculateImageMd5") +@SinceMirai("0.39.0") +val Image.md5: ByteArray + get() = calculateImageMd5ByImageId(imageId) /** * 接收消息时获取到的 [GroupImage]. 它可以直接获取下载链接 [originUrl] @@ -239,51 +209,19 @@ abstract class OnlineGroupImage : GroupImage(), OnlineImage @OptIn(MiraiInternalAPI::class) sealed class FriendImage : AbstractImage() { companion object Key : Message.Key { - override val typeName: String - get() = "FriendImage" + override val typeName: String get() = "FriendImage" } - abstract val resourceId: String - abstract val md5: ByteArray - abstract val filepath: String - abstract val fileLength: Int - abstract val height: Int - abstract val width: Int - open val bizType: Int get() = 0 - open val imageType: Int get() = 1000 - abstract val fileId: Int - open val downloadPath: String get() = resourceId open val original: Int get() = 1 - - override val imageId: String get() = resourceId } /** * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl] */ +@Serializable data class OfflineFriendImage( - override val resourceId: String, - override val md5: ByteArray, - @Transient override val filepath: String = resourceId, - @Transient override val fileLength: Int = 0, - @Transient override val height: Int = 0, - @Transient override val width: Int = 0, - @Transient override val bizType: Int = 0, - @Transient override val imageType: Int = 1000, - @Transient override val downloadPath: String = resourceId, - @Transient override val fileId: Int = 0 -) : FriendImage(), OfflineImage { - constructor(imageId: String) : this(resourceId = imageId, md5 = calculateImageMd5ByImageId(imageId)) - - override fun hashCode(): Int { - return resourceId.hashCode() + 31 * md5.hashCode() - } - - override fun equals(other: Any?): Boolean { - return other is OfflineFriendImage && other::class == this::class && - other.md5.contentEquals(this.md5) && other.resourceId == this.resourceId - } -} + override val imageId: String +) : FriendImage(), OfflineImage /** * 接收消息时获取到的 [FriendImage]. 它可以直接获取下载链接 [originUrl] diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index 7ef989a4d..80d755286 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.OfflineImage import net.mamoe.mirai.message.data.sendTo +import net.mamoe.mirai.message.data.toLongUnsigned import kotlin.jvm.JvmSynthetic /** @@ -34,52 +35,32 @@ import kotlin.jvm.JvmSynthetic * @See ExternalImage.upload 上传图片并得到 [Image] 消息 */ class ExternalImage private constructor( - val width: Int, - val height: Int, val md5: ByteArray, - imageFormat: String, val input: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor - val inputSize: Long, // dont be greater than Int.MAX - val filename: String + val inputSize: Long // dont be greater than Int.MAX ) { constructor( - width: Int, - height: Int, md5: ByteArray, - imageFormat: String, input: ByteReadChannel, - inputSize: Long, // dont be greater than Int.MAX - filename: String - ) : this(width, height, md5, imageFormat, input as Any, inputSize, filename) + inputSize: Long // dont be greater than Int.MAX + ) : this(md5, input as Any, inputSize) constructor( - width: Int, - height: Int, md5: ByteArray, - imageFormat: String, input: Input, - inputSize: Long, // dont be greater than Int.MAX - filename: String - ) : this(width, height, md5, imageFormat, input as Any, inputSize, filename) + inputSize: Long // dont be greater than Int.MAX + ) : this(md5, input as Any, inputSize) constructor( - width: Int, - height: Int, md5: ByteArray, - imageFormat: String, - input: ByteReadPacket, - filename: String - ) : this(width, height, md5, imageFormat, input as Any, input.remaining, filename) + input: ByteReadPacket + ) : this(md5, input as Any, input.remaining) @OptIn(InternalSerializationApi::class) constructor( - width: Int, - height: Int, md5: ByteArray, - imageFormat: String, - input: InputStream, - filename: String - ) : this(width, height, md5, imageFormat, input as Any, input.available().toLong(), filename) + input: InputStream + ) : this(md5, input as Any, input.available().toLongUnsigned()) init { require(inputSize < 30L * 1024 * 1024) { "file is too big. Maximum is about 20MB" } @@ -87,63 +68,30 @@ class ExternalImage private constructor( companion object { fun generateUUID(md5: ByteArray): String { - return "${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}" + return "${md5[0, 3]}-${md5[4, 5]}-${md5[6, 7]}-${md5[8, 9]}-${md5[10, 15]}" } - fun generateImageId(md5: ByteArray, imageType: Int): String { - return """{${generateUUID(md5)}}.${determineFormat(imageType)}""" - } - - fun determineImageType(format: String): Int { - return when (format) { - "jpg" -> 1000 - "png" -> 1001 - "webp" -> 1002 - "bmp" -> 1005 - "gig" -> 2000 - "apng" -> 2001 - "sharpp" -> 1004 - else -> 1000 // unsupported, just make it jpg - } - } - - fun determineFormat(imageType: Int): String { - return when (imageType) { - 1000 -> "jpg" - 1001 -> "png" - 1002 -> "webp" - 1005 -> "bmp" - 2000 -> "gig" - 2001 -> "apng" - 1004 -> "sharpp" - else -> "jpg" // unsupported, just make it jpg - } + fun generateImageId(md5: ByteArray): String { + return """{${generateUUID(md5)}}.gif""" } } - val format: String = - when (val it = imageFormat.toLowerCase()) { - "jpeg" -> "jpg" //必须转换 - else -> it - } - - /** + /* * ImgType: * JPG: 1000 * PNG: 1001 * WEBP: 1002 * BMP: 1005 - * GIG: 2000 // TODO gig? gif? + * GIG: 2000 // gig? gif? * APNG: 2001 * SHARPP: 1004 */ - val imageType: Int - get() = determineImageType(format) - override fun toString(): String = "[ExternalImage(${width}x$height $format)]" + + override fun toString(): String = "[ExternalImage(${generateUUID(md5)})]" fun calculateImageResourceId(): String { - return "{${generateUUID(md5)}}.$format" + return "{${generateUUID(md5)}}.gif" } } @@ -176,8 +124,8 @@ suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact @JvmSynthetic suspend inline fun C.sendImage(image: ExternalImage): MessageReceipt = image.sendTo(this) -internal operator fun ByteArray.get(range: IntRange): String = buildString { - range.forEach { +internal operator fun ByteArray.get(rangeStart: Int, rangeEnd: Int): String = buildString { + for (it in rangeStart..rangeEnd) { append(this@get[it].fixToString()) } } diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt index 50cc3b5fd..04888bb88 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt @@ -7,8 +7,8 @@ internal class ExternalImageTest { @Test fun testByteArrayGet() { - assertEquals("0F", byteArrayOf(0x0f)[0..0]) - assertEquals("10", byteArrayOf(0x10)[0..0]) - assertEquals("0FFE", byteArrayOf(0x0F, 0xFE.toByte())[0..1]) + assertEquals("0F", byteArrayOf(0x0f)[0, 0]) + assertEquals("10", byteArrayOf(0x10)[0, 0]) + assertEquals("0FFE", byteArrayOf(0x0F, 0xFE.toByte())[0, 1]) } } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt index d4c422137..89aa9ac98 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt @@ -43,8 +43,7 @@ import java.net.URL */ actual interface Image : Message, MessageContent { actual companion object Key : Message.Key { - actual override val typeName: String - get() = "Image" + actual override val typeName: String get() = "Image" } /** diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index 2584dd8f1..7f5f93a1c 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.io.ByteReadChannel import kotlinx.coroutines.withContext import kotlinx.io.core.Input -import kotlinx.io.core.buildPacket import kotlinx.io.core.copyTo import kotlinx.io.errors.IOException import kotlinx.io.streams.asOutput @@ -34,49 +33,49 @@ import javax.imageio.ImageIO /** - * 读取 [BufferedImage] 的属性, 然后构造 [ExternalImage] + * 将 [BufferedImage] 保存稳临时文件, 然后构造 [ExternalImage] */ @JvmOverloads @Throws(IOException::class) fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage { + val file = createTempFile().apply { deleteOnExit() } + val digest = MessageDigest.getInstance("md5") digest.reset() - val buffer = buildPacket { + file.outputStream().use { out -> ImageIO.write(this@toExternalImage, formatName, object : OutputStream() { override fun write(b: Int) { - b.toByte().let { - this@buildPacket.writeByte(it) - digest.update(it) - } + 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) } }) } - return ExternalImage(width, height, digest.digest(), formatName, buffer, getRandomString(16) + "." + formatName) + return ExternalImage(digest.digest(), file.inputStream()) } suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** - * 读取文件头识别图片属性, 然后构造 [ExternalImage] + * 直接使用文件 [inputStream] 构造 [ExternalImage] */ @OptIn(MiraiInternalAPI::class) @Throws(IOException::class) fun File.toExternalImage(): ExternalImage { - val input = ImageIO.createImageInputStream(this) - checkNotNull(input) { "Unable to read file(path=${this.path}), no ImageInputStream found" } - val image = ImageIO.getImageReaders(input).asSequence().firstOrNull() - ?: error("Unable to read file(path=${this.path}), no ImageReader found (file type not supported)") - image.input = input - return ExternalImage( - width = image.getWidth(0), - height = image.getHeight(0), md5 = this.inputStream().md5(), // dont change - imageFormat = image.formatName, - input = this.inputStream(), - filename = this.name + input = this.inputStream() ) } diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt index a2bd20e8e..e51f58a0f 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt @@ -87,7 +87,9 @@ internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Ra * 随机生成长度为 [length] 的 [String]. */ internal fun getRandomString(length: Int): String = - getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9') + getRandomString(length, *defaultRanges) + +private val defaultRanges: Array = arrayOf('a'..'z', 'A'..'Z', '0'..'9') /** * 根据所给 [charRange] 随机生成长度为 [length] 的 [String].