mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-02 04:30:25 +08:00
Simplify Image structure, close #244
This commit is contained in:
parent
eb123bb3a9
commit
5b2ae6e9ad
@ -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<LongConn.OffPicUp.Response>()
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
||||
|
@ -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, // ?
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
@ -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<Pair<String, Int>> by setOf(
|
||||
"42.81.169.46" to 8080,
|
||||
|
@ -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
|
||||
|
@ -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<CharRange> = 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<GroupPicUp.Response>("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,
|
||||
|
@ -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<OnlineImage> {
|
||||
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<OfflineImage> {
|
||||
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<GroupImage> {
|
||||
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<FriendImage> {
|
||||
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]
|
||||
|
@ -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 : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = 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())
|
||||
}
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
@ -43,8 +43,7 @@ import java.net.URL
|
||||
*/
|
||||
actual interface Image : Message, MessageContent {
|
||||
actual companion object Key : Message.Key<Image> {
|
||||
actual override val typeName: String
|
||||
get() = "Image"
|
||||
actual override val typeName: String get() = "Image"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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<CharRange> = arrayOf('a'..'z', 'A'..'Z', '0'..'9')
|
||||
|
||||
/**
|
||||
* 根据所给 [charRange] 随机生成长度为 [length] 的 [String].
|
||||
|
Loading…
Reference in New Issue
Block a user