Add LongMessageProtocolTest and various improvements:

Change attributes carrying helper objects to components

Make ClockHolder open

Use originalMessage for MessageReceipt
This commit is contained in:
Him188 2022-05-23 13:29:13 +01:00
parent 3b7eb40529
commit c8fb354d13
22 changed files with 438 additions and 159 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -11,7 +11,7 @@ package net.mamoe.mirai.utils
public interface Clock {
public fun currentTimeMillis(): Long
public fun currentTimeSeconds(): Long
public fun currentTimeSeconds(): Long = currentTimeMillis() / 1000
public object SystemDefault : Clock {
override fun currentTimeMillis(): Long = net.mamoe.mirai.utils.currentTimeMillis()

View File

@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.network.component.buildComponentStorage
import net.mamoe.mirai.internal.network.components.BdhSession
import net.mamoe.mirai.internal.network.components.ClockHolder
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind.PRIVATE_IMAGE
@ -261,6 +262,7 @@ internal suspend fun <C : AbstractContact> C.sendMessageImpl(
MessageProtocolFacade.preprocessAndSendOutgoing(this, message, buildComponentStorage {
set(MessageProtocolStrategy, messageProtocolStrategy)
set(HighwayUploader, HighwayUploader.Default)
set(ClockHolder, bot.components[ClockHolder])
})
}

View File

@ -45,16 +45,27 @@ internal suspend fun <C : Contact> C.broadcastMessagePreSendEvent(
internal enum class SendMessageStep {
/**
* 尝试单包直接发送全部消息
*/
FIRST {
override fun nextStepOrNull(): SendMessageStep {
return LONG_MESSAGE
}
},
/**
* 尝试通过长消息通道上传长消息取得 resId 后再通过普通消息通道发送长消息标识
*/
LONG_MESSAGE {
override fun nextStepOrNull(): SendMessageStep {
return FRAGMENTED
}
},
/**
* 发送分片多包发送
*/
FRAGMENTED {
override fun nextStepOrNull(): SendMessageStep? {
return null

View File

@ -152,34 +152,6 @@ internal data class ForwardMessageInternal(
}
}
internal fun RichMessage.Key.longMessage(brief: String, resId: String, timeSeconds: Long): LongMessageInternal {
val limited: String = if (brief.length > 30) {
brief.take(30) + ""
} else {
brief
}
val template = """
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<msg serviceID="35" templateID="1" action="viewMultiMsg"
brief="$limited"
m_resid="$resId"
m_fileName="$timeSeconds" sourceMsgId="0" url=""
flag="3" adverSign="0" multiMsgFlag="1">
<item layout="1">
<title>$limited</title>
<hr hidden="false" style="0"/>
<summary>点击查看完整消息</summary>
</item>
<source name="聊天记录" icon="" action="" appid="-1"/>
</msg>
""".trimIndent().trim()
return LongMessageInternal(template, resId)
}
private fun String.xmlEnc(): String {
return this.replace("&", "&amp;")
}

View File

@ -17,11 +17,9 @@ import net.mamoe.mirai.internal.contact.impl
import net.mamoe.mirai.internal.contact.uin
import net.mamoe.mirai.internal.contact.userIdOrNull
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.source.MessageSourceInternal
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.component.buildComponentStorage
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
@ -42,21 +40,19 @@ import kotlin.random.Random
internal open class MultiMsgUploader(
val client: QQAndroidClient,
val isLong: Boolean,
val tmpRand: Random = Random.Default,
val facade: MessageProtocolFacade,
val random: Random,
val contact: Contact,
val strategy: MessageProtocolStrategy<*>,
val components: ComponentStorage,
val senderName: String
) {
protected open fun newUploader(): MultiMsgUploader = MultiMsgUploader(
isLong = isLong,
client = client,
tmpRand = tmpRand,
facade = facade,
isLong = isLong,
random = random,
contact = contact,
senderName = senderName,
strategy = strategy
components = components,
senderName = senderName
)
val mainMsg = mutableListOf<MsgComm.Msg>()
@ -69,7 +65,7 @@ internal open class MultiMsgUploader(
protected open fun newNid(): String {
var nid: String
do {
nid = "${tmpRand.nextInt().absoluteValue}"
nid = "${random.nextInt().absoluteValue}"
} while (nestedMsgs.containsKey(nid))
return nid
}
@ -140,10 +136,7 @@ internal open class MultiMsgUploader(
msgChain = convertNestedForwardMessage(nestedForward, msgChain)
}
msgChain = facade.preprocess(contact.impl(), msgChain, buildComponentStorage {
set(MessageProtocolStrategy, strategy)
set(HighwayUploader, HighwayUploader.Default)
})
msgChain = components[MessageProtocolFacade].preprocess(contact.impl(), msgChain, components)
var seq: Int = -1
var uid: Int = -1
@ -157,8 +150,8 @@ internal open class MultiMsgUploader(
if (seq != -1 && uid != -1) {
if (existsIds.add(seq.concatAsLong(uid))) break
}
seq = tmpRand.nextInt().absoluteValue
uid = tmpRand.nextInt().absoluteValue
seq = random.nextInt().absoluteValue
uid = random.nextInt().absoluteValue
}
val msg0 = MsgComm.Msg(
@ -176,18 +169,13 @@ internal open class MultiMsgUploader(
),
msgType = 82, // troop,
groupInfo = if (contact is Group) MsgComm.GroupInfo(
groupCode = contact.groupCode,
groupCard = senderName // Cinnamon
groupCode = contact.groupCode, groupCard = senderName // Cinnamon
) else null,
isSrcMsg = false,
),
msgBody = ImMsgBody.MsgBody(
), msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = MessageProtocolFacade.encode(
msgChain,
messageTarget = contact,
withGeneralFlags = false,
isForward = true
msgChain, messageTarget = contact, withGeneralFlags = false, isForward = true
)
)
)
@ -197,17 +185,13 @@ internal open class MultiMsgUploader(
}
open fun toMessageValidationData(): MessageValidationData {
val msgTransmit = MsgTransmit.PbMultiMsgTransmit(
msg = mainMsg,
pbItemList = nestedMsgs.asSequence()
.map { (name, msgList) ->
MsgTransmit.PbMultiMsgItem(
fileName = name,
buffer = MsgTransmit.PbMultiMsgNew(msgList).toByteArray(MsgTransmit.PbMultiMsgNew.serializer())
)
}
.toList()
)
val msgTransmit =
MsgTransmit.PbMultiMsgTransmit(msg = mainMsg, pbItemList = nestedMsgs.asSequence().map { (name, msgList) ->
MsgTransmit.PbMultiMsgItem(
fileName = name,
buffer = MsgTransmit.PbMultiMsgNew(msgList).toByteArray(MsgTransmit.PbMultiMsgNew.serializer())
)
}.toList())
val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer())
return MessageValidationData(bytes.gzip())
@ -218,27 +202,20 @@ internal open class MultiMsgUploader(
val response = client.bot.network.sendAndExpect(
MultiMsg.ApplyUp.createForGroup(
buType = if (isLong) 1 else 2,
client = client,
messageData = data,
dstUin = contact.uin
buType = if (isLong) 1 else 2, client = client, messageData = data, dstUin = contact.uin
)
)
lateinit var resId: String
when (response) {
is MultiMsg.ApplyUp.Response.MessageTooLarge ->
error(
"Internal error: message is too large, but this should be handled before sending. "
)
is MultiMsg.ApplyUp.Response.MessageTooLarge -> error(
"Internal error: message is too large, but this should be handled before sending. "
)
is MultiMsg.ApplyUp.Response.RequireUpload -> {
resId = response.proto.msgResid
val body = LongMsg.ReqBody(
subcmd = 1,
platformType = 9,
termType = 5,
msgUpReq = listOf(
subcmd = 1, platformType = 9, termType = 5, msgUpReq = listOf(
LongMsg.MsgUpReq(
msgType = 3, // group
dstUin = contact.uin,
@ -253,14 +230,10 @@ internal open class MultiMsgUploader(
body.toExternalResource().use { resource ->
Highway.uploadResourceBdh(
bot = client.bot,
resource = resource,
kind = when (isLong) {
bot = client.bot, resource = resource, kind = when (isLong) {
true -> ResourceKind.LONG_MESSAGE
false -> ResourceKind.FORWARD_MESSAGE
},
commandId = 27,
initialTicket = response.proto.msgSig
}, commandId = 27, initialTicket = response.proto.msgSig
)
}
}

View File

@ -22,7 +22,10 @@ import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.message.protocol.decode.*
import net.mamoe.mirai.internal.message.protocol.encode.*
import net.mamoe.mirai.internal.message.protocol.outgoing.*
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.component.buildComponentStorage
import net.mamoe.mirai.internal.network.component.withFallback
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.pipeline.ProcessResult
import net.mamoe.mirai.internal.utils.runCoroutineInPlace
@ -39,6 +42,8 @@ import java.util.*
import kotlin.reflect.KClass
internal interface MessageProtocolFacade {
val remark: String get() = "MessageProtocolFacade"
val encoderPipeline: MessageEncoderPipeline
val decoderPipeline: MessageDecoderPipeline
val preprocessorPipeline: OutgoingMessagePipeline
@ -129,7 +134,8 @@ internal interface MessageProtocolFacade {
/**
* The default global instance.
*/
companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl()
companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl(),
ComponentKey<MessageProtocolFacade>
}
internal fun MessageProtocolFacade.decodeAndRefineLight(
@ -150,7 +156,8 @@ internal suspend fun MessageProtocolFacade.decodeAndRefineDeep(
internal class MessageProtocolFacadeImpl(
private val protocols: Iterable<MessageProtocol> = ServiceLoader.load(MessageProtocol::class.java)
private val protocols: Iterable<MessageProtocol> = ServiceLoader.load(MessageProtocol::class.java),
override val remark: String = "MessageProtocolFacade"
) : MessageProtocolFacade {
override val encoderPipeline: MessageEncoderPipeline = MessageEncoderPipelineImpl()
override val decoderPipeline: MessageDecoderPipeline = MessageDecoderPipelineImpl()
@ -243,6 +250,15 @@ internal class MessageProtocolFacadeImpl(
}
}
private val thisComponentStorage by lazy {
buildComponentStorage {
set(
MessageProtocolFacade,
this@MessageProtocolFacadeImpl
)
}
}
override suspend fun <C : AbstractContact> preprocess(
target: C,
message: Message,
@ -314,9 +330,9 @@ internal class MessageProtocolFacadeImpl(
val attributes = buildTypeSafeMap {
set(OutgoingMessagePipelineContext.CONTACT, target.impl())
set(OutgoingMessagePipelineContext.ORIGINAL_MESSAGE, message)
set(OutgoingMessagePipelineContext.ORIGINAL_MESSAGE_AS_CHAIN, message.toMessageChain())
set(OutgoingMessagePipelineContext.STEP, SendMessageStep.FIRST)
set(OutgoingMessagePipelineContext.PROTOCOL_STRATEGY, context[MessageProtocolStrategy].castUp())
set(OutgoingMessagePipelineContext.HIGHWAY_UPLOADER, context[HighwayUploader])
set(OutgoingMessagePipelineContext.COMPONENTS, thisComponentStorage.withFallback(context))
}
return attributes
}

View File

@ -19,9 +19,11 @@ import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer
import net.mamoe.mirai.internal.message.source.createMessageReceipt
@ -81,11 +83,11 @@ internal class FileMessageProtocol : MessageProtocol() {
val contact = attributes[CONTACT]
val bot = contact.bot
val strategy = attributes[PROTOCOL_STRATEGY]
val strategy = components[MessageProtocolStrategy]
val source = coroutineScope {
val source = async {
strategy.constructSourceForSpecialMessage(currentMessageChain, 2021)
strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 2021)
}
bot.network.sendAndExpect(FileManagement.Feed(bot.client, contact.id, file.busId, file.id))

View File

@ -14,15 +14,15 @@ import net.mamoe.mirai.internal.message.data.forwardMessage
import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.HIGHWAY_UPLOADER
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor
import net.mamoe.mirai.internal.network.components.ClockHolder
import net.mamoe.mirai.message.data.ForwardMessage
import net.mamoe.mirai.message.data.RichMessage
import net.mamoe.mirai.message.data.toMessageChain
import net.mamoe.mirai.utils.currentTimeSeconds
internal class ForwardMessageProtocol : MessageProtocol() {
override fun ProcessorCollector.collectProcessorsImpl() {
@ -46,16 +46,16 @@ internal class ForwardMessageProtocol : MessageProtocol() {
}.asIterable().verifyLength(forward, contact)
}
val resId = attributes[HIGHWAY_UPLOADER].uploadMessages(
val resId = components[HighwayUploader].uploadMessages(
contact,
attributes[PROTOCOL_STRATEGY],
components,
forward.nodeList,
false
)
currentMessageChain = RichMessage.forwardMessage(
resId = resId,
fileName = currentTimeSeconds().toString(),
fileName = components[ClockHolder].local.currentTimeSeconds().toString(),
forwardMessage = forward,
).toMessageChain()
}

View File

@ -12,14 +12,17 @@ package net.mamoe.mirai.internal.message.protocol.impl
import kotlinx.coroutines.Deferred
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.internal.AbstractBot
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.contact.SendMessageStep
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender
import net.mamoe.mirai.internal.message.source.createMessageReceipt
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
@ -41,7 +44,8 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S
override suspend fun OutgoingMessagePipelineContext.process() {
markAsConsumed()
val strategy = attributes[PROTOCOL_STRATEGY]
@Suppress("UNCHECKED_CAST")
val strategy = components[MessageProtocolStrategy] as MessageProtocolStrategy<AbstractContact>
val step = attributes[STEP]
val contact = attributes[CONTACT]
val bot = contact.bot
@ -52,9 +56,9 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S
client = bot.client,
contact = contact,
message = currentMessageChain,
fragmented = step == SendMessageStep.FRAGMENTED,
sourceCallback = { source = it }
)
originalMessage = attributes[ORIGINAL_MESSAGE_AS_CHAIN],
fragmented = step == SendMessageStep.FRAGMENTED
) { source = it }
sendAllPackets(bot, step, contact, packets)
@ -70,7 +74,7 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S
packets: List<OutgoingPacket>
) = packets.forEach { packet ->
val originalMessage = attributes[ORIGINAL_MESSAGE]
val protocolStrategy = attributes[PROTOCOL_STRATEGY]
val protocolStrategy = components[MessageProtocolStrategy]
val finalMessage = currentMessageChain
val resp = protocolStrategy.sendPacket(bot, packet) as MessageSvcPbSendMsg.Response

View File

@ -12,22 +12,21 @@ package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.contact.SendMessageStep
import net.mamoe.mirai.internal.contact.takeContent
import net.mamoe.mirai.internal.message.data.longMessage
import net.mamoe.mirai.internal.message.data.LongMessageInternal
import net.mamoe.mirai.internal.message.flags.DontAsLongMessage
import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage
import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.HIGHWAY_UPLOADER
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.PROTOCOL_STRATEGY
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer
import net.mamoe.mirai.internal.network.components.ClockHolder
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.RichMessage
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.truncated
internal class LongMessageProtocol : MessageProtocol() {
override fun ProcessorCollector.collectProcessorsImpl() {
@ -36,24 +35,25 @@ internal class LongMessageProtocol : MessageProtocol() {
convertToLongMessageIfNeeded(
currentMessageChain,
attributes[STEP],
attributes[CONTACT],
attributes[PROTOCOL_STRATEGY]
attributes[CONTACT]
)
})
}
/**
* Convert to [LongMessageInternal] iff [SendMessageStep.FIRST] has failed.
*/
private suspend fun OutgoingMessagePipelineContext.convertToLongMessageIfNeeded(
chain: MessageChain,
step: SendMessageStep,
contact: AbstractContact,
strategy: MessageProtocolStrategy<*>
): MessageChain {
val uploader = attributes[HIGHWAY_UPLOADER]
val uploader = components[HighwayUploader]
suspend fun sendLongImpl(): MessageChain {
val time = currentTimeSeconds()
val resId = uploader.uploadLongMessage(contact, strategy, chain, time.toInt())
return chain + RichMessage.longMessage(
val time = components[ClockHolder].local.currentTimeSeconds()
val resId = uploader.uploadLongMessage(contact, components, chain, time.toInt())
return chain + createLongMessage(
brief = chain.takeContent(27),
resId = resId,
timeSeconds = time
@ -81,4 +81,26 @@ internal class LongMessageProtocol : MessageProtocol() {
SendMessageStep.FRAGMENTED -> chain
}
}
private fun createLongMessage(brief: String, resId: String, timeSeconds: Long): LongMessageInternal {
val limited: String = brief.truncated(30)
val template = """
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<msg serviceID="35" templateID="1" action="viewMultiMsg"
brief="$limited"
m_resid="$resId"
m_fileName="$timeSeconds" sourceMsgId="0" url=""
flag="3" adverSign="0" multiMsgFlag="1">
<item layout="1">
<title>$limited</title>
<hr hidden="false" style="0"/>
<summary>点击查看完整消息</summary>
</item>
<source name="聊天记录" icon="" action="" appid="-1"/>
</msg>
""".trimIndent().trim()
return LongMessageInternal(template, resId)
}
}

View File

@ -14,8 +14,11 @@ import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender
import net.mamoe.mirai.internal.message.source.createMessageReceipt
import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket
@ -55,8 +58,8 @@ internal class MusicShareProtocol : MessageProtocol() {
val result = bot.network.sendAndExpect(packet)
result.pkg.checkSuccess("send music share")
val strategy = attributes[OutgoingMessagePipelineContext.PROTOCOL_STRATEGY]
val source = strategy.constructSourceForSpecialMessage(currentMessageChain, 3116)
val strategy = components[MessageProtocolStrategy]
val source = strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 3116)
source.tryEnsureSequenceIdAvailable()
collect(source.createMessageReceipt(contact, true))

View File

@ -12,19 +12,20 @@ package net.mamoe.mirai.internal.message.protocol.outgoing
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.contact.nickIn
import net.mamoe.mirai.internal.message.data.MultiMsgUploader
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.components.ClockHolder
import net.mamoe.mirai.message.data.ForwardMessage
import net.mamoe.mirai.message.data.MessageChain
import kotlin.random.Random
internal interface HighwayUploader {
suspend fun uploadMessages(
contact: AbstractContact,
strategy: MessageProtocolStrategy<*>,
components: ComponentStorage,
nodes: Collection<ForwardMessage.INode>,
isLong: Boolean,
facade: MessageProtocolFacade = MessageProtocolFacade,
senderName: String = contact.bot.nickIn(contact),
): String {
nodes.forEach { it.messageChain.ensureSequenceIdAvailable() }
@ -32,10 +33,10 @@ internal interface HighwayUploader {
val uploader = MultiMsgUploader(
client = contact.bot.client,
isLong = isLong,
facade = facade,
contact = contact,
random = Random(components[ClockHolder].local.currentTimeSeconds()),
senderName = senderName,
strategy = strategy
components = components
).also { it.emitMain(nodes) }
return uploader.uploadAndReturnResId()
@ -43,7 +44,7 @@ internal interface HighwayUploader {
suspend fun uploadLongMessage(
contact: AbstractContact,
strategy: MessageProtocolStrategy<*>,
components: ComponentStorage,
chain: MessageChain,
timeSeconds: Int,
senderName: String = contact.bot.nickIn(contact),
@ -51,7 +52,7 @@ internal interface HighwayUploader {
val bot = contact.bot
return uploadMessages(
contact,
strategy,
components,
listOf(
ForwardMessage.Node(
senderId = bot.id,
@ -61,7 +62,7 @@ internal interface HighwayUploader {
)
),
true,
senderName = senderName
senderName = senderName,
)
}

View File

@ -37,13 +37,14 @@ internal interface MessageProtocolStrategy<in C : AbstractContact> {
suspend fun createPacketsForGeneralMessage(
client: QQAndroidClient,
contact: C,
message: MessageChain,
message: MessageChain, // to send
originalMessage: MessageChain, // to create Receipt
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit,
): List<OutgoingPacket>
suspend fun constructSourceForSpecialMessage(
finalMessage: MessageChain,
originalMessage: MessageChain,
fromAppId: Int,
): OnlineMessageSource.Outgoing
@ -52,7 +53,7 @@ internal interface MessageProtocolStrategy<in C : AbstractContact> {
internal sealed class UserMessageProtocolStrategy<C : AbstractUser> : MessageProtocolStrategy<C> {
override suspend fun constructSourceForSpecialMessage(
finalMessage: MessageChain,
originalMessage: MessageChain,
fromAppId: Int
): OnlineMessageSource.Outgoing {
throw UnsupportedOperationException("Sending MusicShare or FileMessage to User is not yet supported")
@ -66,14 +67,15 @@ internal class FriendMessageProtocolStrategy(
client: QQAndroidClient,
contact: FriendImpl,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
return MessageSvcPbSendMsg.createToFriend(client, contact, message, fragmented, sourceCallback)
return MessageSvcPbSendMsg.createToFriend(client, contact, message, originalMessage, fragmented, sourceCallback)
}
override suspend fun constructSourceForSpecialMessage(
finalMessage: MessageChain,
originalMessage: MessageChain,
fromAppId: Int
): OnlineMessageSource.Outgoing {
val receipt: PrivateMessageProcessor.SendPrivateMessageReceipt = withTimeoutOrNull(3000) {
@ -88,7 +90,7 @@ internal class FriendMessageProtocolStrategy(
sender = contact.bot,
target = contact,
time = contact.bot.clock.server.currentTimeSeconds().toInt(),
originalMessage = finalMessage
originalMessage = originalMessage
)
}
}
@ -98,10 +100,18 @@ internal object StrangerMessageProtocolStrategy : UserMessageProtocolStrategy<St
client: QQAndroidClient,
contact: StrangerImpl,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
return MessageSvcPbSendMsg.createToStranger(client, contact, message, fragmented, sourceCallback)
return MessageSvcPbSendMsg.createToStranger(
client,
contact,
message,
originalMessage,
fragmented,
sourceCallback
)
}
}
@ -110,10 +120,11 @@ internal object GroupTempMessageProtocolStrategy : UserMessageProtocolStrategy<N
client: QQAndroidClient,
contact: NormalMemberImpl,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
return MessageSvcPbSendMsg.createToTemp(client, contact, message, fragmented, sourceCallback)
return MessageSvcPbSendMsg.createToTemp(client, contact, message, originalMessage, fragmented, sourceCallback)
}
}
@ -124,14 +135,15 @@ internal open class GroupMessageProtocolStrategy(
client: QQAndroidClient,
contact: GroupImpl,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
return MessageSvcPbSendMsg.createToGroup(client, contact, message, fragmented, sourceCallback)
return MessageSvcPbSendMsg.createToGroup(client, contact, message, originalMessage, fragmented, sourceCallback)
}
override suspend fun constructSourceForSpecialMessage(
finalMessage: MessageChain,
originalMessage: MessageChain,
fromAppId: Int
): OnlineMessageSource.Outgoing {
val receipt: GroupMessageProcessor.SendGroupMessageReceipt = withTimeoutOrNull(3000) {
@ -147,7 +159,7 @@ internal open class GroupMessageProtocolStrategy(
sender = contact.bot,
target = contact,
time = contact.bot.clock.server.currentTimeSeconds().toInt(),
originalMessage = finalMessage
originalMessage = originalMessage
)
}

View File

@ -14,6 +14,7 @@ import net.mamoe.mirai.contact.MessageTooLargeException
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.contact.SendMessageStep
import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline
import net.mamoe.mirai.internal.pipeline.PipelineConfiguration
@ -104,6 +105,11 @@ internal interface OutgoingMessagePipelineContext :
*/
val ORIGINAL_MESSAGE = TypeKey<Message>("originalMessage")
/**
* You should only use [ORIGINAL_MESSAGE_AS_CHAIN] if you can't use [ORIGINAL_MESSAGE]
*/
val ORIGINAL_MESSAGE_AS_CHAIN = TypeKey<MessageChain>("originalMessageAsChain")
/**
* Message target
@ -112,9 +118,8 @@ internal interface OutgoingMessagePipelineContext :
val STEP = TypeKey<SendMessageStep>("step")
val PROTOCOL_STRATEGY = TypeKey<MessageProtocolStrategy<AbstractContact>>("protocolStrategy")
val HIGHWAY_UPLOADER = TypeKey<HighwayUploader>("highwayUploader")
val COMPONENTS = TypeKey<ComponentStorage>("components")
val OutgoingMessagePipelineContext.components: ComponentStorage get() = attributes[COMPONENTS]
}
}

View File

@ -10,6 +10,8 @@
package net.mamoe.mirai.internal.network.component
import org.jetbrains.annotations.TestOnly
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Mediator for [component][ComponentKey]s accessing each other.
@ -38,6 +40,7 @@ internal interface ComponentStorage {
}
internal fun buildComponentStorage(builderAction: MutableComponentStorage.() -> Unit): ComponentStorage {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return ConcurrentComponentStorage(builderAction)
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
* Copyright 2019-2022 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.
@ -12,10 +12,11 @@ package net.mamoe.mirai.internal.network.components
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.utils.Clock
import net.mamoe.mirai.utils.lateinitMutableProperty
internal class ClockHolder {
val local: Clock get() = Clock.SystemDefault
var server: Clock = local
internal open class ClockHolder {
open val local: Clock get() = Clock.SystemDefault
open var server: Clock by lateinitMutableProperty { local }
companion object : ComponentKey<ClockHolder> {
val QQAndroidBot.clock get() = components[ClockHolder]

View File

@ -186,6 +186,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
client: QQAndroidClient,
target: Stranger,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
source: (OnlineMessageSourceToStrangerImpl) -> Unit,
): List<OutgoingPacket> {
@ -226,7 +227,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
target = target,
time = client.bot.clock.server.currentTimeSeconds().toInt(),
sequenceIds = sequenceIds.get(),
originalMessage = message,
originalMessage = originalMessage,
),
)
},
@ -242,6 +243,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
client: QQAndroidClient,
targetFriend: Friend,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
crossinline sourceCallback: (OnlineMessageSourceToFriendImpl) -> Unit,
): List<OutgoingPacket> {
@ -290,7 +292,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
target = targetFriend,
time = client.bot.clock.server.currentTimeSeconds().toInt(),
sequenceIds = sequenceIds.get(),
originalMessage = message,
originalMessage = originalMessage,
),
)
},
@ -372,6 +374,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
client: QQAndroidClient,
targetGroup: Group,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
crossinline sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit,
): List<OutgoingPacket> {
@ -422,7 +425,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
sender = client.bot,
target = targetGroup,
time = client.bot.clock.server.currentTimeSeconds().toInt(),
originalMessage = message, //,
originalMessage = originalMessage, //,
// sourceMessage = message
),
)
@ -500,6 +503,7 @@ internal inline fun MessageSvcPbSendMsg.createToTemp(
client: QQAndroidClient,
member: Member,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
crossinline sourceCallback: (Deferred<OnlineMessageSourceToTempImpl>) -> Unit,
): List<OutgoingPacket> {
@ -512,7 +516,7 @@ internal inline fun MessageSvcPbSendMsg.createToTemp(
target = member,
time = client.bot.clock.server.currentTimeSeconds().toInt(),
sequenceIds = intArrayOf(client.atomicNextMessageSequenceId()),
originalMessage = message,
originalMessage = originalMessage,
)
sourceCallback(CompletableDeferred(source))
return createToTempImpl(
@ -526,7 +530,8 @@ internal inline fun MessageSvcPbSendMsg.createToTemp(
internal inline fun MessageSvcPbSendMsg.createToStranger(
client: QQAndroidClient,
stranger: Stranger,
message: MessageChain,
message: MessageChain, // to send
originalMessage: MessageChain, // for Receipt
fragmented: Boolean,
crossinline sourceCallback: (Deferred<OnlineMessageSourceToStrangerImpl>) -> Unit,
): List<OutgoingPacket> {
@ -537,6 +542,7 @@ internal inline fun MessageSvcPbSendMsg.createToStranger(
client,
stranger,
message,
originalMessage,
fragmented,
) { sourceCallback(CompletableDeferred(it)) }
}
@ -545,6 +551,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend(
client: QQAndroidClient,
qq: Friend,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
crossinline sourceCallback: (Deferred<OnlineMessageSourceToFriendImpl>) -> Unit,
): List<OutgoingPacket> {
@ -555,6 +562,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend(
client,
qq,
message,
originalMessage,
fragmented,
) { sourceCallback(CompletableDeferred(it)) }
}
@ -564,6 +572,7 @@ internal inline fun MessageSvcPbSendMsg.createToGroup(
client: QQAndroidClient,
group: Group,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
crossinline sourceCallback: (Deferred<OnlineMessageSourceToGroupImpl>) -> Unit,
): List<OutgoingPacket> {
@ -574,6 +583,7 @@ internal inline fun MessageSvcPbSendMsg.createToGroup(
client,
group,
message,
originalMessage,
fragmented,
) { sourceCallback(CompletableDeferred(it)) }
}

View File

@ -33,8 +33,11 @@ internal class MessageProtocolFacadeTest : AbstractTest() {
PttMessageProtocol
RichMessageProtocol
TextProtocol
UnsupportedMessageProtocol
VipFaceProtocol
ForwardMessageProtocol
LongMessageProtocol
UnsupportedMessageProtocol
GeneralMessageSenderProtocol
""".trimIndent(),
MessageProtocolFacadeImpl().loaded.joinToString("\n") { it::class.simpleName.toString() }
)

View File

@ -12,8 +12,12 @@ package net.mamoe.mirai.internal.message.protocol.impl
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.AbstractBot
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.message.data.inferMessageSourceKind
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
@ -21,17 +25,31 @@ import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacadeImpl
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderPipelineImpl
import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderPipelineImpl
import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.components.ClockHolder
import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.internal.notice.processors.GroupExtensions
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageChainBuilder
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.internal.test.runBlockingUnit
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.Clock
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toUHexString
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.test.Asserter
import kotlin.test.assertEquals
import kotlin.test.asserter
internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions {
@ -60,7 +78,10 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
}
protected fun facadeOf(vararg protocols: MessageProtocol): MessageProtocolFacade {
return MessageProtocolFacadeImpl(protocols.toList())
return MessageProtocolFacadeImpl(
protocols.toList(),
remark = "MessageProtocolFacade with ${protocols.joinToString { it::class.simpleName!! }}"
)
}
///////////////////////////////////////////////////////////////////////////
@ -211,5 +232,121 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
// sending
///////////////////////////////////////////////////////////////////////////
init {
components[MessageProtocolStrategy] = object : MessageProtocolStrategy<AbstractContact> {
override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet {
assertEquals(0x123, packet.sequenceId)
return MessageSvcPbSendMsg.Response.SUCCESS
}
override suspend fun createPacketsForGeneralMessage(
client: QQAndroidClient,
contact: AbstractContact,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
sourceCallback(CompletableDeferred(constructSourceForSpecialMessage(originalMessage, 1000)))
return listOf(OutgoingPacket("Test", "test", 0x123, ByteReadPacket.Empty))
}
override suspend fun constructSourceForSpecialMessage(
originalMessage: MessageChain,
fromAppId: Int
): OnlineMessageSource.Outgoing {
return when (val defaultTarget = defaultTarget) {
is Group -> OnlineMessageSourceToGroupImpl(
coroutineScope = defaultTarget,
internalIds = intArrayOf(1),
time = 1,
originalMessage = originalMessage,
sender = bot,
target = defaultTarget
)
is Friend -> OnlineMessageSourceToFriendImpl(
sequenceIds = intArrayOf(1),
internalIds = intArrayOf(1),
time = 1,
originalMessage = originalMessage,
sender = bot,
target = defaultTarget
)
else -> error("Unexpected target: $defaultTarget")
}
}
}
components[HighwayUploader] = object : HighwayUploader {
override suspend fun uploadMessages(
contact: AbstractContact,
components: ComponentStorage,
nodes: Collection<ForwardMessage.INode>,
isLong: Boolean,
senderName: String
): String {
return "(size=${nodes.size})${
nodes.joinToString().replace(bot.id.toString(), "123123").md5().toUHexString("")
}"
}
}
components[ClockHolder] = object : ClockHolder() {
override val local: Clock = object : Clock {
override fun currentTimeMillis(): Long = 160023456
}
}
}
fun runWithFacade(action: suspend MessageProtocolFacade.() -> Unit) {
runBlockingUnit {
facadeOf(*protocols).run { action() }
MessageProtocolFacade.INSTANCE.run { action() }
}
}
companion object {
fun assertMessageEquals(expected: Message, actual: Message) {
val expectedChain = expected.toMessageChain()
val actualChain = actual.toMessageChain()
val message = String.format(
"""
Expected: %s
Actual: %s
""".trimIndent(), expectedChain.render(), actualChain.render()
)
assertEquals(expectedChain.size, actualChain.size, message)
asserter.assertEquals(message, expectedChain, actualChain)
}
fun MessageProtocolFacade.assertMessageEquals(expected: Message, actual: Message) {
val expectedChain = expected.toMessageChain()
val actualChain = actual.toMessageChain()
val message = String.format(
"""
Facade: ${this.remark}
Expected: %s
Actual: %s
""".trimIndent(), expectedChain.render(), actualChain.render()
)
assertEquals(expectedChain.size, actualChain.size, message)
asserter.assertEquals(message, expectedChain, actualChain)
}
inline fun Asserter.assertEquals(crossinline message: () -> String, expected: Any?, actual: Any?) {
assertTrue({ message() + ". Expected <$expected>, actual <$actual>." }, actual == expected)
}
fun MessageChain.render(): String = buildString {
appendLine("size = $size")
for (singleMessage in distinct()) {
val count = this@render.count { it == singleMessage }
appendLine("$count x [${singleMessage::class.simpleName}] $singleMessage")
}
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2019-2022 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/dev/LICENSE
*/
package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags
import net.mamoe.mirai.internal.message.data.LongMessageInternal
import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage
import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.repeat
import net.mamoe.mirai.message.data.toPlainText
import net.mamoe.mirai.utils.castUp
import net.mamoe.mirai.utils.getRandomString
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
internal class LongMessageProtocolTest : AbstractMessageProtocolTest() {
override val protocols: Array<out MessageProtocol> =
arrayOf(
TextProtocol(),
ImageProtocol(),
LongMessageProtocol(),
GeneralMessageSenderProtocol(),
)
init {
defaultTarget = bot.addGroup(123, 1230003).apply {
addMember(1230003, "user3", MemberPermission.OWNER)
}
}
@Test
fun precondition() {
assertEquals(getRandomString(5000, Random(1)), getRandomString(5000, Random(1)))
assertMessageEquals(
"test".toPlainText() + getRandomString(5000, Random(1)) +
Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200),
"test".toPlainText() + getRandomString(5000, Random(1)) +
Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200)
)
}
@Test
fun `can convert messages to LongMessageInternal`() {
var message = "test".toPlainText() + getRandomString(5000, Random(1)) +
Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200)
message += IgnoreLengthCheck
message += ForceAsLongMessage
runWithFacade {
preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) ->
val receipt = receipts.single()
assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage)
assertMessageEquals(
LongMessageInternal(
"""
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<msg serviceID="35" templateID="1" action="viewMultiMsg"
brief="testqGnJ1R..."
m_resid="(size=1)DBD2AB20196EEB631C95DEF40E20C709"
m_fileName="160023" sourceMsgId="0" url=""
flag="3" adverSign="0" multiMsgFlag="1">
<item layout="1">
<title>testqGnJ1R...</title>
<hr hidden="false" style="0"/>
<summary>点击查看完整消息</summary>
</item>
<source name="聊天记录" icon="" action="" appid="-1"/>
</msg>
""".trimIndent(), "(size=1)DBD2AB20196EEB631C95DEF40E20C709"
) + IgnoreLengthCheck + ForceAsLongMessage, context.currentMessageChain
)
}
}
}
}

View File

@ -17,7 +17,10 @@ import net.mamoe.mirai.internal.MockBot
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
import net.mamoe.mirai.internal.network.components.EventDispatcher
import net.mamoe.mirai.internal.network.components.PacketLoggingStrategy
import net.mamoe.mirai.internal.network.components.PacketLoggingStrategyImpl
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.framework.components.TestImagePatcher
import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver
@ -61,7 +64,8 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT
MiraiLogger.Factory.create(SafeStateObserver::class, "StateObserver errors")
)
)
set(ImagePatcher, ImagePatcher())
set(ImagePatcher, TestImagePatcher())
set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot))
}
fun NetworkHandler.assertState(state: NetworkHandler.State) {

View File

@ -16,7 +16,9 @@ import net.mamoe.mirai.internal.message.protocol.impl.GeneralMessageSenderProtoc
import net.mamoe.mirai.internal.message.protocol.outgoing.*
import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl
import net.mamoe.mirai.internal.message.source.createMessageReceipt
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
import net.mamoe.mirai.internal.network.components.ClockHolder
import net.mamoe.mirai.internal.notice.processors.GroupExtensions
import net.mamoe.mirai.internal.pipeline.replaceProcessor
import net.mamoe.mirai.internal.test.AbstractTest
@ -24,6 +26,7 @@ import net.mamoe.mirai.internal.test.runBlockingUnit
import net.mamoe.mirai.message.data.ForwardMessage
import net.mamoe.mirai.message.data.buildForwardMessage
import net.mamoe.mirai.message.data.toMessageChain
import net.mamoe.mirai.utils.Clock
import net.mamoe.mirai.utils.currentTimeSeconds
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
@ -79,15 +82,21 @@ internal class MessageReceiptTest : AbstractTest(), GroupExtensions {
set(HighwayUploader, object : HighwayUploader {
override suspend fun uploadMessages(
contact: AbstractContact,
strategy: MessageProtocolStrategy<*>,
components: ComponentStorage,
nodes: Collection<ForwardMessage.INode>,
isLong: Boolean,
facade: MessageProtocolFacade,
senderName: String
): String {
return "id"
}
})
set(ClockHolder, object : ClockHolder() {
override val local: Clock = object : Clock {
override fun currentTimeMillis(): Long {
return 160023456
}
}
})
})
assertIs<ForwardMessage>(result.source.originalMessage[ForwardMessage])