Support sending group image to friend, #307

This commit is contained in:
Him188 2021-01-05 22:19:30 +08:00
parent 54add6df78
commit ac03d405f9
4 changed files with 160 additions and 64 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -45,7 +45,7 @@ internal fun FlashImage.toJceData(): ImMsgBody.Elem {
pbElem = HummerCommelem.MsgElemInfoServtype3( pbElem = HummerCommelem.MsgElemInfoServtype3(
flashTroopPic = ImMsgBody.CustomFace( flashTroopPic = ImMsgBody.CustomFace(
filePath = image.imageId, filePath = image.imageId,
md5 = image.md5, picMd5 = image.md5,
pbReserve = byteArrayOf(0x78, 0x06) pbReserve = byteArrayOf(0x78, 0x06)
) )
).toByteArray(HummerCommelem.MsgElemInfoServtype3.serializer()) ).toByteArray(HummerCommelem.MsgElemInfoServtype3.serializer())

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -21,6 +21,7 @@ import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.contact.AnonymousMember import net.mamoe.mirai.contact.AnonymousMember
import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.internal.network.protocol.data.proto.* import net.mamoe.mirai.internal.network.protocol.data.proto.*
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.internal.utils.*
@ -56,15 +57,15 @@ internal fun MessageChain.toRichTextElems(
var longTextResId: String? = null var longTextResId: String? = null
fun transformOneMessage(it: Message) { fun transformOneMessage(currentMessage: Message) {
if (it is RichMessage) { if (currentMessage is RichMessage) {
val content = it.content.toByteArray().zip() val content = currentMessage.content.toByteArray().zip()
when (it) { when (currentMessage) {
is ForwardMessageInternal -> { is ForwardMessageInternal -> {
elements.add( elements.add(
ImMsgBody.Elem( ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg( richMsg = ImMsgBody.RichMsg(
serviceId = it.serviceId, // ok serviceId = currentMessage.serviceId, // ok
template1 = byteArrayOf(1) + content template1 = byteArrayOf(1) + content
) )
) )
@ -76,13 +77,13 @@ internal fun MessageChain.toRichTextElems(
elements.add( elements.add(
ImMsgBody.Elem( ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg( richMsg = ImMsgBody.RichMsg(
serviceId = it.serviceId, // ok serviceId = currentMessage.serviceId, // ok
template1 = byteArrayOf(1) + content template1 = byteArrayOf(1) + content
) )
) )
) )
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN) transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
longTextResId = it.resId longTextResId = currentMessage.resId
} }
is LightApp -> elements.add( is LightApp -> elements.add(
ImMsgBody.Elem( ImMsgBody.Elem(
@ -94,9 +95,9 @@ internal fun MessageChain.toRichTextElems(
else -> elements.add( else -> elements.add(
ImMsgBody.Elem( ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg( richMsg = ImMsgBody.RichMsg(
serviceId = when (it) { serviceId = when (currentMessage) {
is ServiceMessage -> it.serviceId is ServiceMessage -> currentMessage.serviceId
else -> error("unsupported RichMessage: ${it::class.simpleName}") else -> error("unsupported RichMessage: ${currentMessage::class.simpleName}")
}, },
template1 = byteArrayOf(1) + content template1 = byteArrayOf(1) + content
) )
@ -105,21 +106,24 @@ internal fun MessageChain.toRichTextElems(
} }
} }
when (it) { when (currentMessage) {
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.content))) is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = currentMessage.content)))
is CustomMessage -> { is CustomMessage -> {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
elements.add( elements.add(
ImMsgBody.Elem( ImMsgBody.Elem(
customElem = ImMsgBody.CustomElem( customElem = ImMsgBody.CustomElem(
enumType = MIRAI_CUSTOM_ELEM_TYPE, enumType = MIRAI_CUSTOM_ELEM_TYPE,
data = CustomMessage.dump(it.getFactory() as CustomMessage.Factory<CustomMessage>, it) data = CustomMessage.dump(
currentMessage.getFactory() as CustomMessage.Factory<CustomMessage>,
currentMessage
)
) )
) )
) )
} }
is At -> { is At -> {
elements.add(ImMsgBody.Elem(text = it.toJceData(messageTarget.safeCast()))) elements.add(ImMsgBody.Elem(text = currentMessage.toJceData(messageTarget.safeCast())))
// elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) // elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
// removed by https://github.com/mamoe/mirai/issues/524 // removed by https://github.com/mamoe/mirai/issues/524
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524 // 发送 QuoteReply 消息时无可避免的产生多余空格 #524
@ -129,34 +133,63 @@ internal fun MessageChain.toRichTextElems(
ImMsgBody.Elem( ImMsgBody.Elem(
commonElem = ImMsgBody.CommonElem( commonElem = ImMsgBody.CommonElem(
serviceType = 2, serviceType = 2,
businessType = it.pokeType, businessType = currentMessage.pokeType,
pbElem = HummerCommelem.MsgElemInfoServtype2( pbElem = HummerCommelem.MsgElemInfoServtype2(
pokeType = it.pokeType, pokeType = currentMessage.pokeType,
vaspokeId = it.id, vaspokeId = currentMessage.id,
vaspokeMinver = "7.2.0", vaspokeMinver = "7.2.0",
vaspokeName = it.name vaspokeName = currentMessage.name
).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer()) ).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer())
) )
) )
) )
transformOneMessage(UNSUPPORTED_POKE_MESSAGE_PLAIN) transformOneMessage(UNSUPPORTED_POKE_MESSAGE_PLAIN)
} }
is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) is OfflineGroupImage -> {
is OfflineFriendImage -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData())) if (messageTarget is User) {
is FlashImage -> elements.add(it.toJceData()).also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) } elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.toJceData().toNotOnlineImage()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.toJceData()))
}
}
is OnlineGroupImageImpl -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.delegate.toNotOnlineImage()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.delegate))
}
}
is OnlineFriendImageImpl -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.delegate))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.delegate.toCustomFace()))
}
}
is OfflineFriendImage -> {
if (messageTarget is User) {
elements.add(ImMsgBody.Elem(notOnlineImage = currentMessage.toJceData()))
} else {
elements.add(ImMsgBody.Elem(customFace = currentMessage.toJceData().toCustomFace()))
}
}
is FlashImage -> elements.add(currentMessage.toJceData())
.also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) }
is AtAll -> elements.add(atAllData) is AtAll -> elements.add(atAllData)
is Face -> elements.add( is Face -> elements.add(
if (it.id >= 260) { if (currentMessage.id >= 260) {
ImMsgBody.Elem(commonElem = it.toCommData()) ImMsgBody.Elem(commonElem = currentMessage.toCommData())
} else { } else {
ImMsgBody.Elem(face = it.toJceData()) ImMsgBody.Elem(face = currentMessage.toJceData())
} }
) )
is QuoteReply -> { is QuoteReply -> {
if (forGroup) { if (forGroup) {
when (val source = it.source) { when (val source = currentMessage.source) {
is OnlineMessageSource.Incoming.FromGroup -> { is OnlineMessageSource.Incoming.FromGroup -> {
val sender0 = source.sender val sender0 = source.sender
if (sender0 !is AnonymousMember) if (sender0 !is AnonymousMember)
@ -170,9 +203,9 @@ internal fun MessageChain.toRichTextElems(
} }
//MarketFaceImpl继承于MarketFace 会自动添加兼容信息 //MarketFaceImpl继承于MarketFace 会自动添加兼容信息
//如果有用户不慎/强行使用也会转换为文本信息 //如果有用户不慎/强行使用也会转换为文本信息
is MarketFaceImpl -> elements.add(ImMsgBody.Elem(marketFace = it.delegate)) is MarketFaceImpl -> elements.add(ImMsgBody.Elem(marketFace = currentMessage.delegate))
is MarketFace -> transformOneMessage(PlainText(it.contentToString())) is MarketFace -> transformOneMessage(PlainText(currentMessage.contentToString()))
is VipFace -> transformOneMessage(PlainText(it.contentToString())) is VipFace -> transformOneMessage(PlainText(currentMessage.contentToString()))
is PttMessage -> { is PttMessage -> {
elements.add( elements.add(
ImMsgBody.Elem( ImMsgBody.Elem(
@ -193,7 +226,7 @@ internal fun MessageChain.toRichTextElems(
-> { -> {
} }
else -> error("unsupported message type: ${it::class.simpleName}") else -> error("unsupported message type: ${currentMessage::class.simpleName}")
} }
} }
this.forEach(::transformOneMessage) this.forEach(::transformOneMessage)

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -15,6 +15,7 @@ import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.IMirai import net.mamoe.mirai.IMirai
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Contact.Companion.uploadImage
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.FriendImage import net.mamoe.mirai.message.data.FriendImage
@ -24,32 +25,19 @@ import net.mamoe.mirai.message.data.Image.Key.FRIEND_IMAGE_ID_REGEX_1
import net.mamoe.mirai.message.data.Image.Key.FRIEND_IMAGE_ID_REGEX_2 import net.mamoe.mirai.message.data.Image.Key.FRIEND_IMAGE_ID_REGEX_2
import net.mamoe.mirai.message.data.Image.Key.GROUP_IMAGE_ID_REGEX import net.mamoe.mirai.message.data.Image.Key.GROUP_IMAGE_ID_REGEX
import net.mamoe.mirai.message.data.md5 import net.mamoe.mirai.message.data.md5
import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.ExternalResource.Companion.DEFAULT_FORMAT_NAME
import net.mamoe.mirai.utils.generateImageId
import net.mamoe.mirai.utils.hexToBytes
/*
* ImgType:
* JPG: 1000
* PNG: 1001
* WEBP: 1002
* BMP: 1005
* GIG: 2000 // gig? gif?
* APNG: 2001
* SHARPP: 1004
*/
internal class OnlineGroupImageImpl( internal class OnlineGroupImageImpl(
internal val delegate: ImMsgBody.CustomFace internal val delegate: ImMsgBody.CustomFace
) : @Suppress("DEPRECATION") ) : @Suppress("DEPRECATION")
OnlineGroupImage() { OnlineGroupImage() {
override val imageId: String = generateImageId( override val imageId: String = generateImageId(
delegate.md5, delegate.picMd5,
delegate.filePath.substringAfterLast('.') delegate.filePath.substringAfterLast('.')
).takeIf { ).takeIf {
GROUP_IMAGE_ID_REGEX.matches(it) GROUP_IMAGE_ID_REGEX.matches(it)
} ?: generateImageId(delegate.md5) } ?: generateImageId(delegate.picMd5)
override val originUrl: String override val originUrl: String
get() = if (delegate.origUrl.isBlank()) { get() = if (delegate.origUrl.isBlank()) {
@ -90,11 +78,79 @@ OnlineFriendImage() {
} }
} }
/*
* ImgType:
* JPG: 1000
* PNG: 1001
* WEBP: 1002
* BMP: 1005
* GIG: 2000 // gig? gif?
* APNG: 2001
* SHARPP: 1004
*/
internal fun getImageType(id: Int): String {
return when (id) {
1000 -> "jpg"
1001 -> "png"
1002 -> "webp"
1005 -> "bmp"
2000 -> "gif"
2001 -> "png"
else -> DEFAULT_FORMAT_NAME
}
}
internal fun ImMsgBody.NotOnlineImage.toCustomFace(): ImMsgBody.CustomFace {
return ImMsgBody.CustomFace(
filePath = generateImageId(picMd5, getImageType(imgType)),
picMd5 = picMd5,
flag = ByteArray(4),
bigUrl = bigUrl,
origUrl = origUrl,
//_400Height = 235,
//_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2",
//_400Width = 351,
oldData = this.oldVerSendFile
)
}
internal 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("")}"
}
internal fun ImMsgBody.CustomFace.toNotOnlineImage(): ImMsgBody.NotOnlineImage {
val resId = calculateResId()
return ImMsgBody.NotOnlineImage(
filePath = filePath,
resId = resId,
oldPicMd5 = false,
picMd5 = picMd5,
downloadPath = resId,
original = 1,
pbReserve = byteArrayOf(0x78, 0x02),
)
}
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace {
return ImMsgBody.CustomFace( return ImMsgBody.CustomFace(
filePath = this.imageId, filePath = this.imageId,
md5 = this.md5, picMd5 = this.md5,
flag = ByteArray(4), flag = ByteArray(4),
//_400Height = 235, //_400Height = 235,
//_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", //_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2",

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2020 Mamoe Technologies and contributors. * Copyright 2019-2021 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -270,10 +270,10 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(10) @JvmField val fileType: Int = 0, @ProtoNumber(10) @JvmField val fileType: Int = 0,
@ProtoNumber(11) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(12) @JvmField val useful: Int = 0, @ProtoNumber(12) @JvmField val useful: Int = 0,
@ProtoNumber(13) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) override val picMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(14) @JvmField val thumbUrl: String = "", @ProtoNumber(14) override val thumbUrl: String = "",
@ProtoNumber(15) @JvmField val bigUrl: String = "", @ProtoNumber(15) @JvmField val bigUrl: String = "",
@ProtoNumber(16) @JvmField val origUrl: String = "", @ProtoNumber(16) override val origUrl: String = "",
@ProtoNumber(17) @JvmField val bizType: Int = 0, @ProtoNumber(17) @JvmField val bizType: Int = 0,
@ProtoNumber(18) @JvmField val repeatIndex: Int = 0, @ProtoNumber(18) @JvmField val repeatIndex: Int = 0,
@ProtoNumber(19) @JvmField val repeatImage: Int = 0, @ProtoNumber(19) @JvmField val repeatImage: Int = 0,
@ -288,11 +288,11 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(28) @JvmField val thumbHeight: Int = 0, @ProtoNumber(28) @JvmField val thumbHeight: Int = 0,
@ProtoNumber(29) @JvmField val showLen: Int = 0, @ProtoNumber(29) @JvmField val showLen: Int = 0,
@ProtoNumber(30) @JvmField val downloadLen: Int = 0, @ProtoNumber(30) @JvmField val downloadLen: Int = 0,
@ProtoNumber(31) @JvmField val _400Url: String = "", @ProtoNumber(31) override val _400Url: String = "",
@ProtoNumber(32) @JvmField val _400Width: Int = 0, @ProtoNumber(32) @JvmField val _400Width: Int = 0,
@ProtoNumber(33) @JvmField val _400Height: Int = 0, @ProtoNumber(33) @JvmField val _400Height: Int = 0,
@ProtoNumber(34) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY @ProtoNumber(34) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf, NotOnlineImageOrCustomFace
@Serializable @Serializable
internal class DeliverGiftMsg( internal class DeliverGiftMsg(
@ -671,6 +671,13 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(56) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY @ProtoNumber(56) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
interface NotOnlineImageOrCustomFace {
val thumbUrl: String
val origUrl: String
val _400Url: String
val picMd5: ByteArray
}
@Serializable @Serializable
internal class NotOnlineImage( internal class NotOnlineImage(
@ProtoNumber(1) @JvmField val filePath: String = "", @ProtoNumber(1) @JvmField val filePath: String = "",
@ -679,15 +686,15 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(4) @JvmField val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(5) @JvmField val imgType: Int = 0, @ProtoNumber(5) @JvmField val imgType: Int = 0,
@ProtoNumber(6) @JvmField val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val previewsImage: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(7) @JvmField val picMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) override val picMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(8) @JvmField val picHeight: Int = 0, @ProtoNumber(8) @JvmField val picHeight: Int = 0,
@ProtoNumber(9) @JvmField val picWidth: Int = 0, @ProtoNumber(9) @JvmField val picWidth: Int = 0,
@ProtoNumber(10) @JvmField val resId: String = "", @ProtoNumber(10) @JvmField val resId: String = "",
@ProtoNumber(11) @JvmField val flag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val flag: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(12) @JvmField val thumbUrl: String = "", @ProtoNumber(12) override val thumbUrl: String = "",
@ProtoNumber(13) @JvmField val original: Int = 0, @ProtoNumber(13) @JvmField val original: Int = 0,
@ProtoNumber(14) @JvmField val bigUrl: String = "", @ProtoNumber(14) @JvmField val bigUrl: String = "",
@ProtoNumber(15) @JvmField val origUrl: String = "", @ProtoNumber(15) override val origUrl: String = "",
@ProtoNumber(16) @JvmField val bizType: Int = 0, @ProtoNumber(16) @JvmField val bizType: Int = 0,
@ProtoNumber(17) @JvmField val result: Int = 0, @ProtoNumber(17) @JvmField val result: Int = 0,
@ProtoNumber(18) @JvmField val index: Int = 0, @ProtoNumber(18) @JvmField val index: Int = 0,
@ -698,11 +705,11 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(23) @JvmField val fileId: Int = 0, @ProtoNumber(23) @JvmField val fileId: Int = 0,
@ProtoNumber(24) @JvmField val showLen: Int = 0, @ProtoNumber(24) @JvmField val showLen: Int = 0,
@ProtoNumber(25) @JvmField val downloadLen: Int = 0, @ProtoNumber(25) @JvmField val downloadLen: Int = 0,
@ProtoNumber(26) @JvmField val _400Url: String = "", @ProtoNumber(26) override val _400Url: String = "",
@ProtoNumber(27) @JvmField val _400Width: Int = 0, @ProtoNumber(27) @JvmField val _400Width: Int = 0,
@ProtoNumber(28) @JvmField val _400Height: Int = 0, @ProtoNumber(28) @JvmField val _400Height: Int = 0,
@ProtoNumber(29) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY @ProtoNumber(29) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf, NotOnlineImageOrCustomFace
@Serializable // 非官方. @Serializable // 非官方.
internal class PbReserve( internal class PbReserve(