Commit 1786c95e authored by Him188's avatar Him188

Support merged forward messages!

parent bb4c13d7
...@@ -550,12 +550,18 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -550,12 +550,18 @@ internal abstract class QQAndroidBotBase constructor(
@JvmSynthetic @JvmSynthetic
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
internal suspend fun lowLevelSendLongGroupMessage(groupCode: Long, message: MessageChain): MessageReceipt<Group> { internal suspend fun lowLevelSendGroupLongOrForwardMessage(
groupCode: Long,
message: Collection<MessageChain>,
isLong: Boolean
): MessageReceipt<Group> {
message.forEach {
it.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
}
val group = getGroup(groupCode) val group = getGroup(groupCode)
val time = currentTimeSeconds val time = currentTimeSeconds
val sequenceId = client.atomicNextMessageSequenceId() val sequenceId = client.atomicNextMessageSequenceId()
message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
network.run { network.run {
val data = message.calculateValidationDataForGroup( val data = message.calculateValidationDataForGroup(
...@@ -569,6 +575,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -569,6 +575,7 @@ internal abstract class QQAndroidBotBase constructor(
val response = val response =
MultiMsg.ApplyUp.createForGroupLongMessage( MultiMsg.ApplyUp.createForGroupLongMessage(
buType = if (isLong) 1 else 2,
client = this@QQAndroidBotBase.client, client = this@QQAndroidBotBase.client,
messageData = data, messageData = data,
dstUin = Group.calculateGroupUinByGroupCode(groupCode) dstUin = Group.calculateGroupUinByGroupCode(groupCode)
...@@ -578,10 +585,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -578,10 +585,7 @@ internal abstract class QQAndroidBotBase constructor(
when (response) { when (response) {
is MultiMsg.ApplyUp.Response.MessageTooLarge -> is MultiMsg.ApplyUp.Response.MessageTooLarge ->
error( error(
"Internal error: message is too large, but this should be handled before sending. Message content:" + "Internal error: message is too large, but this should be handled before sending. "
message.joinToString {
"${it::class.simpleName}(l=${it.toString().length})"
}
) )
is MultiMsg.ApplyUp.Response.RequireUpload -> { is MultiMsg.ApplyUp.Response.RequireUpload -> {
resId = response.proto.msgResid resId = response.proto.msgResid
...@@ -639,13 +643,27 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -639,13 +643,27 @@ internal abstract class QQAndroidBotBase constructor(
} }
} }
return group.sendMessage( return if (isLong) {
RichMessage.longMessage( group.sendMessage(
brief = message.joinToString(limit = 27) { it.contentToString() }, RichMessage.longMessage(
resId = resId, brief = message.joinToString(limit = 27) { it.contentToString() },
timeSeconds = time resId = resId,
timeSeconds = time
)
) )
) } else {
group.sendMessage(
RichMessage.forwardMessage(
resId = resId,
timeSeconds = time,
preview = message.take(3).joinToString {
"""
<title size="26" color="#777777" maxLines="2" lineSpace="12">${it.joinToString(limit = 10)}</title>
""".trimIndent()
}
)
)
}
} }
} }
...@@ -746,3 +764,26 @@ private fun RichMessage.Templates.longMessage(brief: String, resId: String, time ...@@ -746,3 +764,26 @@ private fun RichMessage.Templates.longMessage(brief: String, resId: String, time
return LongMessage(template, resId) return LongMessage(template, resId)
} }
private fun RichMessage.Templates.forwardMessage(
resId: String,
timeSeconds: Long,
preview: String
): ForwardMessageInternal {
val template = """
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<msg serviceID="35" templateID="1" action="viewMultiMsg" brief="[聊天记录]"
m_resid="$resId" m_fileName="$timeSeconds"
tSum="3" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="0">
<item layout="1" advertiser_id="0" aid="0">
<title size="34" maxLines="2" lineSpace="12">群聊的聊天记录</title>
$preview
<hr hidden="false" style="0"/>
<summary size="26" color="#777777">查看3条转发消息</summary>
</item>
<source name="聊天记录" icon="" action="" appid="-1"/>
</msg>
""".trimIndent()
return ForwardMessageInternal(template)
}
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") @file:Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
package net.mamoe.mirai.qqandroid.contact package net.mamoe.mirai.qqandroid.contact
...@@ -289,10 +289,18 @@ internal class GroupImpl( ...@@ -289,10 +289,18 @@ internal class GroupImpl(
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
private suspend fun sendMessageImpl(message: Message): MessageReceipt<Group> { private suspend fun sendMessageImpl(message: Message): MessageReceipt<Group> {
if (message is MessageChain) {
if (message.anyIsInstance<ForwardMessage>()) {
return sendMessageImpl(message.singleOrNull() ?: error("ForwardMessage must be standalone"))
}
}
if (message is ForwardMessage) {
return bot.lowLevelSendGroupLongOrForwardMessage(this.id, message.messageList, false)
}
val msg: MessageChain val msg: MessageChain
if (message !is LongMessage) { if (message !is LongMessage && message !is ForwardMessageInternal) {
val event = GroupMessageSendEvent(this, message.asMessageChain()).broadcast() val event = GroupMessageSendEvent(this, message.asMessageChain()).broadcast()
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by GroupMessageSendEvent") throw EventCancelledException("cancelled by GroupMessageSendEvent")
...@@ -314,7 +322,7 @@ internal class GroupImpl( ...@@ -314,7 +322,7 @@ internal class GroupImpl(
} }
if (length > 702 || imageCnt > 2) if (length > 702 || imageCnt > 2)
return bot.lowLevelSendLongGroupMessage(this.id, event.message) return bot.lowLevelSendGroupLongOrForwardMessage(this.id, listOf(event.message), true)
msg = event.message msg = event.message
} else msg = message.asMessageChain() } else msg = message.asMessageChain()
...@@ -334,7 +342,7 @@ internal class GroupImpl( ...@@ -334,7 +342,7 @@ internal class GroupImpl(
120 -> throw BotIsBeingMutedException(this@GroupImpl) 120 -> throw BotIsBeingMutedException(this@GroupImpl)
34 -> { 34 -> {
kotlin.runCatching { // allow retry once kotlin.runCatching { // allow retry once
return bot.lowLevelSendLongGroupMessage(id, msg) return bot.lowLevelSendGroupLongOrForwardMessage(id, listOf(msg), true)
}.getOrElse { }.getOrElse {
throw IllegalStateException("internal error: send message failed(34)", it) throw IllegalStateException("internal error: send message failed(34)", it)
} }
......
...@@ -52,6 +52,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B ...@@ -52,6 +52,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
if (it is RichMessage) { if (it is RichMessage) {
val content = MiraiPlatformUtils.zip(it.content.toByteArray()) val content = MiraiPlatformUtils.zip(it.content.toByteArray())
when (it) { when (it) {
is ForwardMessageInternal -> {
elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = it.serviceId, // ok
template1 = byteArrayOf(1) + content
)
)
)
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
}
is LongMessage -> { is LongMessage -> {
check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" } check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" }
elements.add( elements.add(
...@@ -136,6 +147,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B ...@@ -136,6 +147,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
} }
} }
} }
is ForwardMessage,
is MessageSource, // mirai metadata only is MessageSource, // mirai metadata only
is RichMessage // already transformed above is RichMessage // already transformed above
-> { -> {
...@@ -324,7 +336,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B ...@@ -324,7 +336,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
if (resId != null) { if (resId != null) {
list.add(LongMessage(content, resId)) list.add(LongMessage(content, resId))
} else { } else {
list.add(ForwardMessage(content)) list.add(ForwardMessageInternal(content))
} }
} }
......
...@@ -42,7 +42,7 @@ internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor ...@@ -42,7 +42,7 @@ internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor
} }
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal fun MessageChain.calculateValidationDataForGroup( internal fun Collection<MessageChain>.calculateValidationDataForGroup(
sequenceId: Int, sequenceId: Int,
time: Int, time: Int,
random: UInt, random: UInt,
...@@ -50,10 +50,9 @@ internal fun MessageChain.calculateValidationDataForGroup( ...@@ -50,10 +50,9 @@ internal fun MessageChain.calculateValidationDataForGroup(
botId: Long, botId: Long,
botMemberNameCard: String botMemberNameCard: String
): MessageValidationData { ): MessageValidationData {
val richTextElems = this.toRichTextElems(forGroup = true, withGeneralFlags = false)
val msgTransmit = MsgTransmit.PbMultiMsgTransmit( val msgTransmit = MsgTransmit.PbMultiMsgTransmit(
msg = listOf( msg = this.map { chain ->
MsgComm.Msg( MsgComm.Msg(
msgHead = MsgComm.MsgHead( msgHead = MsgComm.MsgHead(
fromUin = botId, fromUin = botId,
...@@ -73,11 +72,11 @@ internal fun MessageChain.calculateValidationDataForGroup( ...@@ -73,11 +72,11 @@ internal fun MessageChain.calculateValidationDataForGroup(
), ),
msgBody = ImMsgBody.MsgBody( msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText( richText = ImMsgBody.RichText(
elems = richTextElems.toMutableList() elems = chain.toRichTextElems(forGroup = true, withGeneralFlags = false).toMutableList()
) )
) )
) )
) }
) )
val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer()) val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer())
...@@ -105,6 +104,7 @@ internal class MultiMsg { ...@@ -105,6 +104,7 @@ internal class MultiMsg {
// captured from group // captured from group
fun createForGroupLongMessage( fun createForGroupLongMessage(
buType: Int,
client: QQAndroidClient, client: QQAndroidClient,
messageData: MessageValidationData, messageData: MessageValidationData,
dstUin: Long // group uin dstUin: Long // group uin
...@@ -112,7 +112,7 @@ internal class MultiMsg { ...@@ -112,7 +112,7 @@ internal class MultiMsg {
writeProtoBuf( writeProtoBuf(
MultiMsg.ReqBody.serializer(), MultiMsg.ReqBody.serializer(),
MultiMsg.ReqBody( MultiMsg.ReqBody(
buType = 1, buType = buType, // 1: long, 2: 合并转发
buildVer = "8.2.0.1296", buildVer = "8.2.0.1296",
multimsgApplyupReq = listOf( multimsgApplyupReq = listOf(
MultiMsg.MultiMsgApplyUpReq( MultiMsg.MultiMsgApplyUpReq(
......
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
/**
* 合并转发
*/
@SinceMirai("0.39.0")
class ForwardMessage(
val messageList: Collection<MessageChain>
) : MessageContent {
companion object Key : Message.Key<ForwardMessage> {
override val typeName: String get() = "ForwardMessage"
}
override fun toString(): String = "[mirai:forward:$messageList]"
private val contentToString: String by lazy {
messageList.joinToString("\n")
}
@MiraiExperimentalAPI
override fun contentToString(): String = contentToString
override val length: Int
get() = contentToString.length
override fun get(index: Int): Char = contentToString[length]
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
contentToString.subSequence(startIndex, endIndex)
override fun compareTo(other: String): Int = contentToString.compareTo(other)
}
\ No newline at end of file
...@@ -155,22 +155,18 @@ constructor(serviceId: Int = 60, content: String) : ServiceMessage(serviceId, co ...@@ -155,22 +155,18 @@ constructor(serviceId: Int = 60, content: String) : ServiceMessage(serviceId, co
@SinceMirai("0.31.0") @SinceMirai("0.31.0")
@MiraiExperimentalAPI @MiraiExperimentalAPI
class LongMessage internal constructor(content: String, val resId: String) : ServiceMessage(35, content) { class LongMessage internal constructor(content: String, val resId: String) : ServiceMessage(35, content) {
companion object Key : Message.Key<XmlMessage> { companion object Key : Message.Key<LongMessage> {
override val typeName: String get() = "LongMessage" override val typeName: String get() = "LongMessage"
} }
} }
/** /**
* 合并转发消息 * 合并转发消息
* @suppress 此 API 不稳定 * @suppress 此 API 非常不稳定
*/ */
@SinceMirai("0.36.0") @SinceMirai("0.39.0")
@MiraiExperimentalAPI @MiraiExperimentalAPI("此 API 非常不稳定")
class ForwardMessage(content: String) : ServiceMessage(35, content) { internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content)
companion object Key : Message.Key<XmlMessage> {
override val typeName: String get() = "ForwardMessage"
}
}
/* /*
commonElem=CommonElem#750141174 { commonElem=CommonElem#750141174 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment