diff --git a/mirai-core/src/commonMain/kotlin/message/FlashImageImpl.kt b/mirai-core/src/commonMain/kotlin/message/FlashImageImpl.kt index 6e0bbcbff..937218054 100644 --- a/mirai-core/src/commonMain/kotlin/message/FlashImageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/FlashImageImpl.kt @@ -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 许可证的约束, 可以在以下链接找到该许可证. * 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( flashTroopPic = ImMsgBody.CustomFace( filePath = image.imageId, - md5 = image.md5, + picMd5 = image.md5, pbReserve = byteArrayOf(0x78, 0x06) ) ).toByteArray(HummerCommelem.MsgElemInfoServtype3.serializer()) diff --git a/mirai-core/src/commonMain/kotlin/message/conversions.kt b/mirai-core/src/commonMain/kotlin/message/conversions.kt index 82f284971..33a2388a3 100644 --- a/mirai-core/src/commonMain/kotlin/message/conversions.kt +++ b/mirai-core/src/commonMain/kotlin/message/conversions.kt @@ -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 许可证的约束, 可以在以下链接找到该许可证. * 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.ContactOrBot 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.ImMsgBody import net.mamoe.mirai.internal.utils.* @@ -56,15 +57,15 @@ internal fun MessageChain.toRichTextElems( var longTextResId: String? = null - fun transformOneMessage(it: Message) { - if (it is RichMessage) { - val content = it.content.toByteArray().zip() - when (it) { + fun transformOneMessage(currentMessage: Message) { + if (currentMessage is RichMessage) { + val content = currentMessage.content.toByteArray().zip() + when (currentMessage) { is ForwardMessageInternal -> { elements.add( ImMsgBody.Elem( richMsg = ImMsgBody.RichMsg( - serviceId = it.serviceId, // ok + serviceId = currentMessage.serviceId, // ok template1 = byteArrayOf(1) + content ) ) @@ -76,13 +77,13 @@ internal fun MessageChain.toRichTextElems( elements.add( ImMsgBody.Elem( richMsg = ImMsgBody.RichMsg( - serviceId = it.serviceId, // ok + serviceId = currentMessage.serviceId, // ok template1 = byteArrayOf(1) + content ) ) ) transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN) - longTextResId = it.resId + longTextResId = currentMessage.resId } is LightApp -> elements.add( ImMsgBody.Elem( @@ -94,9 +95,9 @@ internal fun MessageChain.toRichTextElems( else -> elements.add( ImMsgBody.Elem( richMsg = ImMsgBody.RichMsg( - serviceId = when (it) { - is ServiceMessage -> it.serviceId - else -> error("unsupported RichMessage: ${it::class.simpleName}") + serviceId = when (currentMessage) { + is ServiceMessage -> currentMessage.serviceId + else -> error("unsupported RichMessage: ${currentMessage::class.simpleName}") }, template1 = byteArrayOf(1) + content ) @@ -105,21 +106,24 @@ internal fun MessageChain.toRichTextElems( } } - when (it) { - is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.content))) + when (currentMessage) { + is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = currentMessage.content))) is CustomMessage -> { @Suppress("UNCHECKED_CAST") elements.add( ImMsgBody.Elem( customElem = ImMsgBody.CustomElem( enumType = MIRAI_CUSTOM_ELEM_TYPE, - data = CustomMessage.dump(it.getFactory() as CustomMessage.Factory, it) + data = CustomMessage.dump( + currentMessage.getFactory() as CustomMessage.Factory, + currentMessage + ) ) ) ) } 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 = " "))) // removed by https://github.com/mamoe/mirai/issues/524 // 发送 QuoteReply 消息时无可避免的产生多余空格 #524 @@ -129,34 +133,63 @@ internal fun MessageChain.toRichTextElems( ImMsgBody.Elem( commonElem = ImMsgBody.CommonElem( serviceType = 2, - businessType = it.pokeType, + businessType = currentMessage.pokeType, pbElem = HummerCommelem.MsgElemInfoServtype2( - pokeType = it.pokeType, - vaspokeId = it.id, + pokeType = currentMessage.pokeType, + vaspokeId = currentMessage.id, vaspokeMinver = "7.2.0", - vaspokeName = it.name + vaspokeName = currentMessage.name ).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer()) ) ) ) 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 OfflineFriendImage -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData())) - is FlashImage -> elements.add(it.toJceData()).also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) } + + + is OfflineGroupImage -> { + if (messageTarget is User) { + 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 Face -> elements.add( - if (it.id >= 260) { - ImMsgBody.Elem(commonElem = it.toCommData()) + if (currentMessage.id >= 260) { + ImMsgBody.Elem(commonElem = currentMessage.toCommData()) } else { - ImMsgBody.Elem(face = it.toJceData()) + ImMsgBody.Elem(face = currentMessage.toJceData()) } ) is QuoteReply -> { if (forGroup) { - when (val source = it.source) { + when (val source = currentMessage.source) { is OnlineMessageSource.Incoming.FromGroup -> { val sender0 = source.sender if (sender0 !is AnonymousMember) @@ -170,9 +203,9 @@ internal fun MessageChain.toRichTextElems( } //MarketFaceImpl继承于MarketFace 会自动添加兼容信息 //如果有用户不慎/强行使用也会转换为文本信息 - is MarketFaceImpl -> elements.add(ImMsgBody.Elem(marketFace = it.delegate)) - is MarketFace -> transformOneMessage(PlainText(it.contentToString())) - is VipFace -> transformOneMessage(PlainText(it.contentToString())) + is MarketFaceImpl -> elements.add(ImMsgBody.Elem(marketFace = currentMessage.delegate)) + is MarketFace -> transformOneMessage(PlainText(currentMessage.contentToString())) + is VipFace -> transformOneMessage(PlainText(currentMessage.contentToString())) is PttMessage -> { elements.add( 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) diff --git a/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt b/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt index 1949ab5af..c163fcc7d 100644 --- a/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt @@ -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 许可证的约束, 可以在以下链接找到该许可证. * 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.IMirai import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Contact.Companion.uploadImage import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody 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.GROUP_IMAGE_ID_REGEX import net.mamoe.mirai.message.data.md5 -import net.mamoe.mirai.utils.ExternalResource -import net.mamoe.mirai.utils.MiraiExperimentalApi -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 - */ +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.ExternalResource.Companion.DEFAULT_FORMAT_NAME internal class OnlineGroupImageImpl( internal val delegate: ImMsgBody.CustomFace ) : @Suppress("DEPRECATION") OnlineGroupImage() { override val imageId: String = generateImageId( - delegate.md5, + delegate.picMd5, delegate.filePath.substringAfterLast('.') ).takeIf { GROUP_IMAGE_ID_REGEX.matches(it) - } ?: generateImageId(delegate.md5) + } ?: generateImageId(delegate.picMd5) override val originUrl: String 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") internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { return ImMsgBody.CustomFace( filePath = this.imageId, - md5 = this.md5, + picMd5 = this.md5, flag = ByteArray(4), //_400Height = 235, //_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt index 2b4d22823..0862c536c 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt @@ -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 许可证的约束, 可以在以下链接找到该许可证. * 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(11) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val useful: Int = 0, - @ProtoNumber(13) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(14) @JvmField val thumbUrl: String = "", + @ProtoNumber(13) override val picMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoNumber(14) override val thumbUrl: 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(18) @JvmField val repeatIndex: 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(29) @JvmField val showLen: 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(33) @JvmField val _400Height: Int = 0, @ProtoNumber(34) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY - ) : ProtoBuf + ) : ProtoBuf, NotOnlineImageOrCustomFace @Serializable internal class DeliverGiftMsg( @@ -671,6 +671,13 @@ internal class ImMsgBody : ProtoBuf { @ProtoNumber(56) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf + interface NotOnlineImageOrCustomFace { + val thumbUrl: String + val origUrl: String + val _400Url: String + val picMd5: ByteArray + } + @Serializable internal class NotOnlineImage( @ProtoNumber(1) @JvmField val filePath: String = "", @@ -679,15 +686,15 @@ internal class ImMsgBody : ProtoBuf { @ProtoNumber(4) @JvmField val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val imgType: Int = 0, @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(9) @JvmField val picWidth: Int = 0, @ProtoNumber(10) @JvmField val resId: String = "", @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(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(17) @JvmField val result: 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(24) @JvmField val showLen: 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(28) @JvmField val _400Height: Int = 0, @ProtoNumber(29) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY - ) : ProtoBuf + ) : ProtoBuf, NotOnlineImageOrCustomFace @Serializable // 非官方. internal class PbReserve(