mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-17 05:29:25 +08:00
Support sending group image to friend, #307
This commit is contained in:
parent
54add6df78
commit
ac03d405f9
@ -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())
|
||||
|
@ -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<CustomMessage>, it)
|
||||
data = CustomMessage.dump(
|
||||
currentMessage.getFactory() as CustomMessage.Factory<CustomMessage>,
|
||||
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)
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user