commonMain.message.protocol.impl.ImageProtocol.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mirai-core-jvm Show documentation
Show all versions of mirai-core-jvm Show documentation
Mirai Protocol implementation for QQ Android
/*
* Copyright 2019-2022 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/dev/LICENSE
*/
package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.internal.contact.GroupImpl
import net.mamoe.mirai.internal.message.data.transform
import net.mamoe.mirai.internal.message.image.*
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.contact
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor
import net.mamoe.mirai.internal.network.protocol.data.proto.CustomFace
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.ImagePatcher
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.ImageType
import net.mamoe.mirai.message.data.ShowImageFlag
import net.mamoe.mirai.utils.generateImageId
import net.mamoe.mirai.utils.toUHexString
internal class ImageProtocol : MessageProtocol() {
override fun ProcessorCollector.collectProcessorsImpl() {
add(ImageEncoder())
add(ImageDecoder())
add(ImagePatcherForGroup())
}
private class ImagePatcherForGroup : OutgoingMessagePreprocessor {
override suspend fun OutgoingMessagePipelineContext.process() {
val contact = attributes[CONTACT]
if (contact !is GroupImpl) return
val patcher = contact.bot.components[ImagePatcher]
currentMessageChain = currentMessageChain.transform { element ->
when (element) {
is OfflineGroupImage -> {
patcher.patchOfflineGroupImage(contact, element)
element
}
is FriendImage -> {
patcher.patchFriendImageToGroupImage(contact, element)
}
else -> element
}
}
}
}
private class ImageDecoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
markAsConsumed()
when {
data.notOnlineImage != null -> {
collect(OnlineFriendImageImpl(data.notOnlineImage))
}
data.customFace != null -> {
collect(OnlineGroupImageImpl(data.customFace))
data.customFace.pbReserve.let {
if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) {
collect(ShowImageFlag)
}
}
}
else -> {
markNotConsumed()
}
}
}
}
private class ImageEncoder : MessageEncoder {
override suspend fun MessageEncoderContext.process(data: AbstractImage) {
markAsConsumed()
when (data) {
is OfflineGroupImage -> {
if (contact is User) {
collect(ImMsgBody.Elem(notOnlineImage = data.toJceData().toNotOnlineImage()))
} else {
collect(ImMsgBody.Elem(customFace = data.toJceData()))
}
}
is OnlineGroupImageImpl -> {
if (contact is User) {
collect(ImMsgBody.Elem(notOnlineImage = data.delegate.toNotOnlineImage()))
} else {
collect(ImMsgBody.Elem(customFace = data.delegate))
}
}
is OnlineFriendImageImpl -> {
if (contact is User) {
collect(ImMsgBody.Elem(notOnlineImage = data.delegate))
} else {
collect(ImMsgBody.Elem(customFace = data.delegate.toCustomFace()))
}
}
is OfflineFriendImage -> {
if (contact is User) {
collect(ImMsgBody.Elem(notOnlineImage = data.toJceData()))
} else {
collect(ImMsgBody.Elem(customFace = data.toJceData().toCustomFace()))
}
}
}
}
companion object {
private fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace {
return ImMsgBody.CustomFace(
fileId = this.fileId ?: 0,
filePath = this.imageId,
picMd5 = this.md5,
flag = ByteArray(4),
size = size.toInt(),
width = width.coerceAtLeast(1),
height = height.coerceAtLeast(1),
imageType = getIdByImageType(imageType),
origin = if (imageType == ImageType.GIF) {
0
} else {
1
},
//_400Height = 235,
//_400Url = "/gchatpic_new/000000000/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2",
//_400Width = 351,
// pbReserve = "08 00 10 00 32 00 50 00 78 08".autoHexToBytes(),
bizType = 5,
fileType = 66,
useful = 1,
// pbReserve = CustomFaceExtPb.ResvAttr().toByteArray(CustomFaceExtPb.ResvAttr.serializer())
)
}
private fun ImMsgBody.CustomFace.toNotOnlineImage(): ImMsgBody.NotOnlineImage {
val resId = calculateResId()
return ImMsgBody.NotOnlineImage(
filePath = filePath,
resId = resId,
oldPicMd5 = false,
picWidth = width,
picHeight = height,
imgType = imageType,
picMd5 = picMd5,
fileLen = size.toLong(),
oldVerSendFile = oldData,
downloadPath = resId,
original = origin,
bizType = bizType,
pbReserve = byteArrayOf(0x78, 0x02),
)
}
private fun ImMsgBody.NotOnlineImage.toCustomFace(): ImMsgBody.CustomFace {
return ImMsgBody.CustomFace(
filePath = generateImageId(picMd5, getImageType(imgType)),
picMd5 = picMd5,
bizType = 5,
fileType = 66,
useful = 1,
flag = ByteArray(4),
bigUrl = bigUrl,
origUrl = origUrl,
width = picWidth.coerceAtLeast(1),
height = picHeight.coerceAtLeast(1),
imageType = imgType,
//_400Height = 235,
//_400Url = "/gchatpic_new/000000000/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2",
//_400Width = 351,
origin = original,
size = fileLen.toInt()
)
}
// aka friend image id
private fun ImMsgBody.NotOnlineImageOrCustomFace.calculateResId(): String {
val url = origUrl.takeIf { it.isNotBlank() }
?: thumbUrl.takeIf { it.isNotBlank() }
?: _400Url.takeIf { it.isNotBlank() }
?: ""
// gchatpic_new
// offpic_new
val picSenderId = url.substringAfter("pic_new/").substringBefore("/")
.takeIf { it.isNotBlank() } ?: "000000000"
val unknownInt = url.substringAfter("-").substringBefore("-")
.takeIf { it.isNotBlank() } ?: "000000000"
return "/$picSenderId-$unknownInt-${picMd5.toUHexString("")}"
}
private fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage {
val friendImageId = this.friendImageId
return ImMsgBody.NotOnlineImage(
filePath = friendImageId,
resId = friendImageId,
oldPicMd5 = false,
picMd5 = this.md5,
fileLen = size,
downloadPath = friendImageId,
original = if (imageType == ImageType.GIF) {
0
} else {
1
},
picWidth = width,
picHeight = height,
imgType = getIdByImageType(imageType),
pbReserve = byteArrayOf(0x78, 0x02)
)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy