Commit 1786c95e authored by Him188's avatar Him188

Support merged forward messages!

parent bb4c13d7
......@@ -550,12 +550,18 @@ internal abstract class QQAndroidBotBase constructor(
@JvmSynthetic
@LowLevelAPI
@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 time = currentTimeSeconds
val sequenceId = client.atomicNextMessageSequenceId()
message.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
network.run {
val data = message.calculateValidationDataForGroup(
......@@ -569,6 +575,7 @@ internal abstract class QQAndroidBotBase constructor(
val response =
MultiMsg.ApplyUp.createForGroupLongMessage(
buType = if (isLong) 1 else 2,
client = this@QQAndroidBotBase.client,
messageData = data,
dstUin = Group.calculateGroupUinByGroupCode(groupCode)
......@@ -578,10 +585,7 @@ internal abstract class QQAndroidBotBase constructor(
when (response) {
is MultiMsg.ApplyUp.Response.MessageTooLarge ->
error(
"Internal error: message is too large, but this should be handled before sending. Message content:" +
message.joinToString {
"${it::class.simpleName}(l=${it.toString().length})"
}
"Internal error: message is too large, but this should be handled before sending. "
)
is MultiMsg.ApplyUp.Response.RequireUpload -> {
resId = response.proto.msgResid
......@@ -639,13 +643,27 @@ internal abstract class QQAndroidBotBase constructor(
}
}
return group.sendMessage(
RichMessage.longMessage(
brief = message.joinToString(limit = 27) { it.contentToString() },
resId = resId,
timeSeconds = time
return if (isLong) {
group.sendMessage(
RichMessage.longMessage(
brief = message.joinToString(limit = 27) { it.contentToString() },
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
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 @@
* 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)
package net.mamoe.mirai.qqandroid.contact
......@@ -289,10 +289,18 @@ internal class GroupImpl(
@OptIn(MiraiExperimentalAPI::class)
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
if (message !is LongMessage) {
if (message !is LongMessage && message !is ForwardMessageInternal) {
val event = GroupMessageSendEvent(this, message.asMessageChain()).broadcast()
if (event.isCancelled) {
throw EventCancelledException("cancelled by GroupMessageSendEvent")
......@@ -314,7 +322,7 @@ internal class GroupImpl(
}
if (length > 702 || imageCnt > 2)
return bot.lowLevelSendLongGroupMessage(this.id, event.message)
return bot.lowLevelSendGroupLongOrForwardMessage(this.id, listOf(event.message), true)
msg = event.message
} else msg = message.asMessageChain()
......@@ -334,7 +342,7 @@ internal class GroupImpl(
120 -> throw BotIsBeingMutedException(this@GroupImpl)
34 -> {
kotlin.runCatching { // allow retry once
return bot.lowLevelSendLongGroupMessage(id, msg)
return bot.lowLevelSendGroupLongOrForwardMessage(id, listOf(msg), true)
}.getOrElse {
throw IllegalStateException("internal error: send message failed(34)", it)
}
......
......@@ -52,6 +52,17 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
if (it is RichMessage) {
val content = MiraiPlatformUtils.zip(it.content.toByteArray())
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 -> {
check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" }
elements.add(
......@@ -136,6 +147,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
}
}
}
is ForwardMessage,
is MessageSource, // mirai metadata only
is RichMessage // already transformed above
-> {
......@@ -324,7 +336,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
if (resId != null) {
list.add(LongMessage(content, resId))
} else {
list.add(ForwardMessage(content))
list.add(ForwardMessageInternal(content))
}
}
......
......@@ -42,7 +42,7 @@ internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor
}
@OptIn(MiraiInternalAPI::class)
internal fun MessageChain.calculateValidationDataForGroup(
internal fun Collection<MessageChain>.calculateValidationDataForGroup(
sequenceId: Int,
time: Int,
random: UInt,
......@@ -50,10 +50,9 @@ internal fun MessageChain.calculateValidationDataForGroup(
botId: Long,
botMemberNameCard: String
): MessageValidationData {
val richTextElems = this.toRichTextElems(forGroup = true, withGeneralFlags = false)
val msgTransmit = MsgTransmit.PbMultiMsgTransmit(
msg = listOf(
msg = this.map { chain ->
MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = botId,
......@@ -73,11 +72,11 @@ internal fun MessageChain.calculateValidationDataForGroup(
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = richTextElems.toMutableList()
elems = chain.toRichTextElems(forGroup = true, withGeneralFlags = false).toMutableList()
)
)
)
)
}
)
val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer())
......@@ -105,6 +104,7 @@ internal class MultiMsg {
// captured from group
fun createForGroupLongMessage(
buType: Int,
client: QQAndroidClient,
messageData: MessageValidationData,
dstUin: Long // group uin
......@@ -112,7 +112,7 @@ internal class MultiMsg {
writeProtoBuf(
MultiMsg.ReqBody.serializer(),
MultiMsg.ReqBody(
buType = 1,
buType = buType, // 1: long, 2: 合并转发
buildVer = "8.2.0.1296",
multimsgApplyupReq = listOf(
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
@SinceMirai("0.31.0")
@MiraiExperimentalAPI
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"
}
}
/**
* 合并转发消息
* @suppress 此 API 不稳定
* @suppress 此 API 非常不稳定
*/
@SinceMirai("0.36.0")
@MiraiExperimentalAPI
class ForwardMessage(content: String) : ServiceMessage(35, content) {
companion object Key : Message.Key<XmlMessage> {
override val typeName: String get() = "ForwardMessage"
}
}
@SinceMirai("0.39.0")
@MiraiExperimentalAPI("此 API 非常不稳定")
internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content)
/*
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