Add more MessageProtocolTest:

Add fragmented test

Fix MusicShareProtocol and implement sending tests for MusicShareProtocolTest

OutgoingMessagePipelineImpl: set stopWhenConsumed

Fix `currentMessageChain` not updated in `processAlso`, add strong message packets checks, add tests for converting messages failed to send at FIRST step to LongMessageInternal

Add notes for receiving ForwardMessage

Add facade parameters to `download ForwardMessage*`

Add ForwardMessageProtocolTest

Add QuoteReplyProtocolTest

Add CONTAINING_MSG for MessageDecoderContext.attributes, for information-use only

Fix MessageReceiptTest

Fix QuoteReplyProtocolTest
This commit is contained in:
Him188 2022-05-23 18:08:32 +01:00
parent 38465972ba
commit ab3280f6b7
24 changed files with 1020 additions and 223 deletions

View File

@ -44,11 +44,13 @@ internal suspend fun <C : Contact> C.broadcastMessagePreSendEvent(
} }
internal enum class SendMessageStep { internal enum class SendMessageStep(
val allowMultiplePackets: Boolean
) {
/** /**
* 尝试单包直接发送全部消息 * 尝试单包直接发送全部消息
*/ */
FIRST { FIRST(false) {
override fun nextStepOrNull(): SendMessageStep { override fun nextStepOrNull(): SendMessageStep {
return LONG_MESSAGE return LONG_MESSAGE
} }
@ -57,7 +59,7 @@ internal enum class SendMessageStep {
/** /**
* 尝试通过长消息通道上传长消息取得 resId 后再通过普通消息通道发送长消息标识 * 尝试通过长消息通道上传长消息取得 resId 后再通过普通消息通道发送长消息标识
*/ */
LONG_MESSAGE { LONG_MESSAGE(false) {
override fun nextStepOrNull(): SendMessageStep { override fun nextStepOrNull(): SendMessageStep {
return FRAGMENTED return FRAGMENTED
} }
@ -66,7 +68,7 @@ internal enum class SendMessageStep {
/** /**
* 发送分片多包发送 * 发送分片多包发送
*/ */
FRAGMENTED { FRAGMENTED(true) {
override fun nextStepOrNull(): SendMessageStep? { override fun nextStepOrNull(): SendMessageStep? {
return null return null
} }

View File

@ -13,7 +13,6 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageChain
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio
import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.LongMessageInternal
import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.data.OnlineAudioImpl
@ -34,10 +33,11 @@ internal fun ImMsgBody.SourceMsg.toMessageChainNoSource(
messageSourceKind: MessageSourceKind, messageSourceKind: MessageSourceKind,
groupIdOrZero: Long, groupIdOrZero: Long,
refineContext: RefineContext = EmptyRefineContext, refineContext: RefineContext = EmptyRefineContext,
facade: MessageProtocolFacade = MessageProtocolFacade
): MessageChain { ): MessageChain {
val elements = this.elems val elements = this.elems
return buildMessageChain(elements.size + 1) { return buildMessageChain(elements.size + 1) {
joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, this) facade.decode(elements, groupIdOrZero, messageSourceKind, bot, this, null)
}.cleanupRubbishMessageElements().refineLight(bot, refineContext) }.cleanupRubbishMessageElements().refineLight(bot, refineContext)
} }
@ -47,13 +47,15 @@ internal suspend fun List<MsgComm.Msg>.toMessageChainOnline(
groupIdOrZero: Long, groupIdOrZero: Long,
messageSourceKind: MessageSourceKind, messageSourceKind: MessageSourceKind,
refineContext: RefineContext = EmptyRefineContext, refineContext: RefineContext = EmptyRefineContext,
facade: MessageProtocolFacade = MessageProtocolFacade
): MessageChain { ): MessageChain {
return toMessageChain(bot, groupIdOrZero, true, messageSourceKind).refineDeep(bot, refineContext) return toMessageChain(bot, groupIdOrZero, true, messageSourceKind, facade).refineDeep(bot, refineContext)
} }
internal suspend fun MsgComm.Msg.toMessageChainOnline( internal suspend fun MsgComm.Msg.toMessageChainOnline(
bot: Bot, bot: Bot,
refineContext: RefineContext = EmptyRefineContext, refineContext: RefineContext = EmptyRefineContext,
facade: MessageProtocolFacade = MessageProtocolFacade,
): MessageChain { ): MessageChain {
fun getSourceKind(c2cCmd: Int): MessageSourceKind { fun getSourceKind(c2cCmd: Int): MessageSourceKind {
return when (c2cCmd) { return when (c2cCmd) {
@ -69,7 +71,7 @@ internal suspend fun MsgComm.Msg.toMessageChainOnline(
MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0 MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0
else -> 0 else -> 0
} }
return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext) return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext, facade)
} }
//internal fun List<MsgComm.Msg>.toMessageChainOffline( //internal fun List<MsgComm.Msg>.toMessageChainOffline(
@ -95,20 +97,21 @@ private fun List<MsgComm.Msg>.toMessageChain(
groupIdOrZero: Long, groupIdOrZero: Long,
onlineSource: Boolean?, onlineSource: Boolean?,
messageSourceKind: MessageSourceKind, messageSourceKind: MessageSourceKind,
facade: MessageProtocolFacade = MessageProtocolFacade,
): MessageChain { ): MessageChain {
val messageList = this val messageList = this
val elements = messageList.flatMap { it.msgBody.richText.elems } val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size })
val builder = MessageChainBuilder(elements.size)
if (onlineSource != null) { if (onlineSource != null) {
builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList)) builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList))
} }
joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, builder) messageList.forEach { msg ->
facade.decode(msg.msgBody.richText.elems, groupIdOrZero, messageSourceKind, bot, builder, msg)
}
for (msg in messageList) { for (msg in messageList) {
msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) } msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) }
@ -143,16 +146,6 @@ internal object ReceiveMessageTransformer {
} }
} }
fun joinToMessageChain(
elements: List<ImMsgBody.Elem>,
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind,
bot: Bot,
builder: MessageChainBuilder,
) {
return MessageProtocolFacade.decode(elements, groupIdOrZero, messageSourceKind, bot, builder)
}
fun MessageChainBuilder.compressContinuousPlainText() { fun MessageChainBuilder.compressContinuousPlainText() {
var index = 0 var index = 0
val builder = StringBuilder() val builder = StringBuilder()

View File

@ -12,10 +12,7 @@
package net.mamoe.mirai.internal.message.flags package net.mamoe.mirai.internal.message.flags
import net.mamoe.mirai.internal.message.visitor.ex import net.mamoe.mirai.internal.message.visitor.ex
import net.mamoe.mirai.message.data.AbstractMessageKey import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.ConstrainSingle
import net.mamoe.mirai.message.data.MessageKey
import net.mamoe.mirai.message.data.MessageMetadata
import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.safeCast import net.mamoe.mirai.utils.safeCast
@ -24,17 +21,35 @@ import net.mamoe.mirai.utils.safeCast
*/ */
internal sealed interface InternalFlagOnlyMessage : MessageMetadata internal sealed interface InternalFlagOnlyMessage : MessageMetadata
internal sealed interface ForceAs : InternalFlagOnlyMessage, ConstrainSingle {
companion object Key : AbstractMessageKey<ForceAs>({ it.safeCast() })
}
/** /**
* 内部 flag, 放入 chain 强制作为 long 发送 * 内部 flag, 放入 chain 强制作为 long 发送
*/ */
internal object ForceAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, internal object ForceAsLongMessage : ForceAs,
AbstractMessageKey<ForceAsLongMessage>({ it.safeCast() }) { AbstractPolymorphicMessageKey<ForceAs, ForceAsLongMessage>(ForceAs, { it.safeCast() }) {
override val key: MessageKey<ForceAsLongMessage> get() = this override val key: MessageKey<ForceAsLongMessage> get() = this
override fun toString(): String = "" override fun toString(): String = ""
override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R {
return visitor.ex()?.visitForceAsLongMessage(this, data) ?: super<InternalFlagOnlyMessage>.accept(visitor, data) return visitor.ex()?.visitForceAsLongMessage(this, data) ?: super.accept(visitor, data)
}
}
/**
* 内部 flag, 放入 chain 强制作为 fragmented 发送
*/
internal object ForceAsFragmentedMessage : ForceAs,
AbstractPolymorphicMessageKey<ForceAs, ForceAsFragmentedMessage>(ForceAs, { it.safeCast() }) {
override val key: MessageKey<ForceAsFragmentedMessage> get() = this
override fun toString(): String = ""
override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R {
return visitor.ex()?.visitForceAsFragmentedMessage(this, data) ?: super.accept(visitor, data)
} }
} }

View File

@ -16,12 +16,19 @@ import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreproc
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.systemProp
import net.mamoe.mirai.utils.withSwitch
import kotlin.reflect.KClass import kotlin.reflect.KClass
// Loaded by ServiceLoader // Loaded by ServiceLoader
internal abstract class MessageProtocol( internal abstract class MessageProtocol(
val priority: UInt = PRIORITY_CONTENT // the higher, the prior it being called val priority: UInt = PRIORITY_CONTENT // the higher, the prior it being called
) { ) {
val logger: MiraiLogger by lazy {
MiraiLogger.Factory.create(this::class).withSwitch(systemProp("mirai.message.protocol.log.full", false))
}
fun collectProcessors(processorCollector: ProcessorCollector) { fun collectProcessors(processorCollector: ProcessorCollector) {
processorCollector.collectProcessorsImpl() processorCollector.collectProcessorsImpl()
} }

View File

@ -22,11 +22,18 @@ import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.message.protocol.decode.* import net.mamoe.mirai.internal.message.protocol.decode.*
import net.mamoe.mirai.internal.message.protocol.encode.* import net.mamoe.mirai.internal.message.protocol.encode.*
import net.mamoe.mirai.internal.message.protocol.outgoing.* import net.mamoe.mirai.internal.message.protocol.outgoing.*
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.COMPONENTS
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.MESSAGE_TO_RETRY
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE
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.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.component.buildComponentStorage import net.mamoe.mirai.internal.network.component.buildComponentStorage
import net.mamoe.mirai.internal.network.component.withFallback import net.mamoe.mirai.internal.network.component.withFallback
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.pipeline.ProcessResult import net.mamoe.mirai.internal.pipeline.ProcessResult
import net.mamoe.mirai.internal.utils.runCoroutineInPlace import net.mamoe.mirai.internal.utils.runCoroutineInPlace
import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.internal.utils.structureToString
@ -34,10 +41,7 @@ import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor
import net.mamoe.mirai.message.data.visitor.accept import net.mamoe.mirai.message.data.visitor.accept
import net.mamoe.mirai.utils.MutableTypeSafeMap import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.TestOnly
import net.mamoe.mirai.utils.buildTypeSafeMap
import net.mamoe.mirai.utils.castUp
import java.util.* import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -72,6 +76,7 @@ internal interface MessageProtocolFacade {
messageSourceKind: MessageSourceKind, messageSourceKind: MessageSourceKind,
bot: Bot, bot: Bot,
builder: MessageChainBuilder, builder: MessageChainBuilder,
containingMsg: MsgComm.Msg? = null,
) )
@ -127,7 +132,7 @@ internal interface MessageProtocolFacade {
groupIdOrZero: Long, groupIdOrZero: Long,
messageSourceKind: MessageSourceKind, messageSourceKind: MessageSourceKind,
bot: Bot, bot: Bot,
): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this) } ): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) }
fun copy(): MessageProtocolFacade fun copy(): MessageProtocolFacade
@ -235,7 +240,8 @@ internal class MessageProtocolFacadeImpl(
groupIdOrZero: Long, groupIdOrZero: Long,
messageSourceKind: MessageSourceKind, messageSourceKind: MessageSourceKind,
bot: Bot, bot: Bot,
builder: MessageChainBuilder builder: MessageChainBuilder,
containingMsg: MsgComm.Msg?
) { ) {
val pipeline = decoderPipeline val pipeline = decoderPipeline
@ -243,6 +249,7 @@ internal class MessageProtocolFacadeImpl(
set(MessageDecoderContext.BOT, bot) set(MessageDecoderContext.BOT, bot)
set(MessageDecoderContext.MESSAGE_SOURCE_KIND, messageSourceKind) set(MessageDecoderContext.MESSAGE_SOURCE_KIND, messageSourceKind)
set(MessageDecoderContext.GROUP_ID, groupIdOrZero) set(MessageDecoderContext.GROUP_ID, groupIdOrZero)
set(MessageDecoderContext.CONTAINING_MSG, containingMsg)
} }
runCoroutineInPlace { runCoroutineInPlace {
@ -297,8 +304,15 @@ internal class MessageProtocolFacadeImpl(
): ProcessResult<OutgoingMessagePipelineContext, MessageReceipt<*>> { ): ProcessResult<OutgoingMessagePipelineContext, MessageReceipt<*>> {
val attributes = createAttributesForOutgoingMessage(target, message, components) val attributes = createAttributesForOutgoingMessage(target, message, components)
val (context, _) = preprocessorPipeline.process(message.toMessageChain(), attributes) val data = message.toMessageChain()
return outgoingPipeline.process(message.toMessageChain(), context, attributes) val (context, _) = preprocessorPipeline.process(data, attributes)
val preprocessed = context.currentMessageChain
return outgoingPipeline.process(
data,
outgoingPipeline.createContext(preprocessed, context.attributes.plus(MESSAGE_TO_RETRY to preprocessed)),
attributes
)
} }
override fun copy(): MessageProtocolFacade { override fun copy(): MessageProtocolFacade {
@ -327,12 +341,14 @@ internal class MessageProtocolFacadeImpl(
message: Message, message: Message,
context: ComponentStorage context: ComponentStorage
): MutableTypeSafeMap { ): MutableTypeSafeMap {
val chain = message.toMessageChain()
val attributes = buildTypeSafeMap { val attributes = buildTypeSafeMap {
set(OutgoingMessagePipelineContext.CONTACT, target.impl()) set(CONTACT, target.impl())
set(OutgoingMessagePipelineContext.ORIGINAL_MESSAGE, message) set(ORIGINAL_MESSAGE, message)
set(OutgoingMessagePipelineContext.ORIGINAL_MESSAGE_AS_CHAIN, message.toMessageChain()) set(ORIGINAL_MESSAGE_AS_CHAIN, chain)
set(OutgoingMessagePipelineContext.STEP, SendMessageStep.FIRST) set(STEP, SendMessageStep.FIRST)
set(OutgoingMessagePipelineContext.COMPONENTS, thisComponentStorage.withFallback(context)) set(COMPONENTS, thisComponentStorage.withFallback(context))
set(MESSAGE_TO_RETRY, chain)
} }
return attributes return attributes
} }

View File

@ -10,11 +10,11 @@
package net.mamoe.mirai.internal.message.protocol.decode package net.mamoe.mirai.internal.message.protocol.decode
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext.Companion.CONTAINING_MSG
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.pipeline.PipelineConfiguration import net.mamoe.mirai.internal.pipeline.*
import net.mamoe.mirai.internal.pipeline.ProcessorPipeline import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable
import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -29,6 +29,7 @@ internal interface MessageDecoderContext : ProcessorPipelineContext<ImMsgBody.El
val BOT = TypeKey<Bot>("bot") val BOT = TypeKey<Bot>("bot")
val MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind") val MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind")
val GROUP_ID = TypeKey<Long>("groupId") // zero if not group val GROUP_ID = TypeKey<Long>("groupId") // zero if not group
val CONTAINING_MSG = TypeKey<MsgComm.Msg?>("containingMsg")
} }
} }
@ -45,6 +46,17 @@ internal open class MessageDecoderPipelineImpl :
override fun createContext(data: ImMsgBody.Elem, attributes: TypeSafeMap): MessageDecoderContext = override fun createContext(data: ImMsgBody.Elem, attributes: TypeSafeMap): MessageDecoderContext =
MessageDecoderContextImpl(attributes) MessageDecoderContextImpl(attributes)
override suspend fun process(
data: ImMsgBody.Elem,
context: MessageDecoderContext,
attributes: TypeSafeMap
): ProcessResult<MessageDecoderContext, Message> {
context.attributes[CONTAINING_MSG]?.let { msg ->
traceLogging.info { "Processing MsgCommon.Msg: ${msg.structureToStringAndDesensitizeIfAvailable()}" }
}
return super.process(data, context, attributes)
}
companion object { companion object {
@TestOnly @TestOnly
val defaultTraceLogging: MiraiLoggerWithSwitch by lazy { val defaultTraceLogging: MiraiLoggerWithSwitch by lazy {

View File

@ -53,7 +53,7 @@ internal class ForwardMessageProtocol : MessageProtocol() {
false false
) )
currentMessageChain = RichMessage.forwardMessage( currentMessageChain += RichMessage.forwardMessage(
resId = resId, resId = resId,
fileName = components[ClockHolder].local.currentTimeSeconds().toString(), fileName = components[ClockHolder].local.currentTimeSeconds().toString(),
forwardMessage = forward, forwardMessage = forward,

View File

@ -14,11 +14,13 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.AbstractBot
import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.contact.SendMessageStep
import net.mamoe.mirai.internal.message.flags.ForceAsFragmentedMessage
import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector 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.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext 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.CONTACT
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.MESSAGE_TO_RETRY
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN 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.STEP
@ -30,17 +32,20 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcP
import net.mamoe.mirai.message.data.AtAll import net.mamoe.mirai.message.data.AtAll
import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.content import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.buildTypeSafeMap import net.mamoe.mirai.utils.buildTypeSafeMap
import net.mamoe.mirai.utils.info
import net.mamoe.mirai.utils.truncated import net.mamoe.mirai.utils.truncated
internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_SENDER) { internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_SENDER) {
override fun ProcessorCollector.collectProcessorsImpl() { override fun ProcessorCollector.collectProcessorsImpl() {
add(GeneralMessageSender()) add(GeneralMessageSender(logger))
} }
class GeneralMessageSender : OutgoingMessageSender { class GeneralMessageSender(
private val logger: MiraiLogger,
) : OutgoingMessageSender {
override suspend fun OutgoingMessagePipelineContext.process() { override suspend fun OutgoingMessagePipelineContext.process() {
markAsConsumed() markAsConsumed()
@ -57,28 +62,49 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S
contact = contact, contact = contact,
message = currentMessageChain, message = currentMessageChain,
originalMessage = attributes[ORIGINAL_MESSAGE_AS_CHAIN], originalMessage = attributes[ORIGINAL_MESSAGE_AS_CHAIN],
fragmented = step == SendMessageStep.FRAGMENTED fragmented = step == SendMessageStep.FRAGMENTED || currentMessageChain.contains(ForceAsFragmentedMessage)
) { source = it } ) { source = it }
sendAllPackets(bot, step, contact, packets) if (sendAllPackets(bot, step, contact, packets)) {
val sourceAwait = source?.await() ?: error("Internal error: source is not initialized")
val sourceAwait = source?.await() ?: error("Internal error: source is not initialized") sourceAwait.tryEnsureSequenceIdAvailable()
sourceAwait.tryEnsureSequenceIdAvailable() collect(sourceAwait.createMessageReceipt(contact, true))
collect(sourceAwait.createMessageReceipt(contact, true)) }
} }
/**
* @return `true`, if source needs to be added
*/
private suspend fun OutgoingMessagePipelineContext.sendAllPackets( private suspend fun OutgoingMessagePipelineContext.sendAllPackets(
bot: AbstractBot, bot: AbstractBot,
step: SendMessageStep, step: SendMessageStep,
contact: Contact, contact: Contact,
packets: List<OutgoingPacket> packets: List<OutgoingPacket>
) = packets.forEach { packet -> ): Boolean {
if (!step.allowMultiplePackets && packets.size != 1) {
throw IllegalStateException("Internal error: step $step doesn't allow multiple packets while found ${packets.size} ones.")
}
packets.forEach { packet ->
if (!sendSinglePacket(bot, packet, step, contact)) return@sendAllPackets false
}
return true
}
private suspend fun OutgoingMessagePipelineContext.sendSinglePacket(
bot: AbstractBot,
packet: OutgoingPacket,
step: SendMessageStep,
contact: Contact,
): Boolean {
val originalMessage = attributes[ORIGINAL_MESSAGE] val originalMessage = attributes[ORIGINAL_MESSAGE]
val protocolStrategy = components[MessageProtocolStrategy] val protocolStrategy = components[MessageProtocolStrategy]
val finalMessage = currentMessageChain val finalMessage = currentMessageChain
val resp = protocolStrategy.sendPacket(bot, packet) as MessageSvcPbSendMsg.Response val resp = protocolStrategy.sendPacket(bot, packet) as MessageSvcPbSendMsg.Response
if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
logger.info { "STEP $step: message too large." }
val next = step.nextStepOrNull() val next = step.nextStepOrNull()
?: throw MessageTooLargeException( ?: throw MessageTooLargeException(
contact, contact,
@ -88,13 +114,16 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S
) )
// retry with next step // retry with next step
processAlso( logger.info { "Retrying with STEP $next" }
originalMessage.toMessageChain(), val (_, receipts) = processAlso(
attributes[MESSAGE_TO_RETRY],
extraAttributes = buildTypeSafeMap { extraAttributes = buildTypeSafeMap {
set(STEP, next) set(STEP, next)
}, },
) // We expect to get a Receipt from processAlso )
return@forEach check(receipts.size == 1) { "Internal error: expected exactly one receipt collected from sub-process, but found ${receipts.size}." }
// We expect to get a Receipt from processAlso
return false
} }
if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable) { if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable) {
throw IllegalStateException("Send message to $contact failed, server service is unavailable.") throw IllegalStateException("Send message to $contact failed, server service is unavailable.")
@ -117,7 +146,7 @@ internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_S
check(resp is MessageSvcPbSendMsg.Response.SUCCESS) { check(resp is MessageSvcPbSendMsg.Response.SUCCESS) {
"Send message failed: $resp" "Send message failed: $resp"
} }
return true
} }
} }

View File

@ -10,6 +10,8 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector 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.MessageEncoder
@ -45,24 +47,35 @@ internal class MusicShareProtocol : MessageProtocol() {
} }
} }
private class Sender : OutgoingMessageSender { open class Sender : OutgoingMessageSender {
override suspend fun OutgoingMessagePipelineContext.process() { override suspend fun OutgoingMessagePipelineContext.process() {
val contact = attributes[CONTACT]
val bot = contact.bot
val musicShare = currentMessageChain[MusicShare] ?: return val musicShare = currentMessageChain[MusicShare] ?: return
markAsConsumed()
val packet = MusicSharePacket( val contact = attributes[CONTACT]
bot.client, musicShare, contact.id,
targetKind = if (contact is Group) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND
)
val result = bot.network.sendAndExpect(packet)
result.pkg.checkSuccess("send music share")
val strategy = components[MessageProtocolStrategy] val strategy = components[MessageProtocolStrategy]
val bot = contact.bot
sendMusicSharePacket(bot, musicShare, contact, strategy)
val source = strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 3116) val source = strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 3116)
source.tryEnsureSequenceIdAvailable() source.tryEnsureSequenceIdAvailable()
collect(source.createMessageReceipt(contact, true)) collect(source.createMessageReceipt(contact, true))
} }
protected open suspend fun sendMusicSharePacket(
bot: QQAndroidBot,
musicShare: MusicShare,
contact: AbstractContact,
strategy: MessageProtocolStrategy<*>
) {
val packet = MusicSharePacket(
bot.client, musicShare, contact.id,
targetKind = if (contact is Group) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND
)
val result = strategy.sendPacket(bot, packet)
result.pkg.checkSuccess("send music share")
}
} }
} }

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock
import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor
import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.* import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.*
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.OnlineMessageSource
@ -34,6 +35,13 @@ internal interface MessageProtocolStrategy<in C : AbstractContact> {
return bot.network.sendAndExpect(packet) return bot.network.sendAndExpect(packet)
} }
suspend fun <R : Packet?> sendPacket(bot: AbstractBot, packet: OutgoingPacketWithRespType<R>): R {
return bot.network.sendAndExpect(packet)
}
/**
* If [fragmented] is false, returned list must contain at most one element.
*/
suspend fun createPacketsForGeneralMessage( suspend fun createPacketsForGeneralMessage(
client: QQAndroidClient, client: QQAndroidClient,
contact: C, contact: C,

View File

@ -16,10 +16,7 @@ import net.mamoe.mirai.internal.contact.SendMessageStep
import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable
import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline import net.mamoe.mirai.internal.pipeline.*
import net.mamoe.mirai.internal.pipeline.PipelineConfiguration
import net.mamoe.mirai.internal.pipeline.ProcessorPipeline
import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext
import net.mamoe.mirai.internal.utils.estimateLength import net.mamoe.mirai.internal.utils.estimateLength
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
@ -38,12 +35,21 @@ internal interface OutgoingMessagePipeline :
internal open class OutgoingMessagePipelineImpl : internal open class OutgoingMessagePipelineImpl :
AbstractProcessorPipeline<OutgoingMessagePipelineProcessor, OutgoingMessagePipelineContext, OutgoingMessagePipelineInput, MessageReceipt<*>>( AbstractProcessorPipeline<OutgoingMessagePipelineProcessor, OutgoingMessagePipelineContext, OutgoingMessagePipelineInput, MessageReceipt<*>>(
PipelineConfiguration(stopWhenConsumed = false), @OptIn(TestOnly::class) defaultTraceLogging PipelineConfiguration(stopWhenConsumed = true), @OptIn(TestOnly::class) defaultTraceLogging
), OutgoingMessagePipeline { ), OutgoingMessagePipeline {
inner class OutgoingMessagePipelineContextImpl( inner class OutgoingMessagePipelineContextImpl(
attributes: TypeSafeMap, override var currentMessageChain: MessageChain attributes: TypeSafeMap, override var currentMessageChain: MessageChain
) : OutgoingMessagePipelineContext, BaseContextImpl(attributes) ) : OutgoingMessagePipelineContext, BaseContextImpl(attributes) {
override suspend fun processAlso(
data: OutgoingMessagePipelineInput,
extraAttributes: TypeSafeMap
): ProcessResult<out ProcessorPipelineContext<OutgoingMessagePipelineInput, MessageReceipt<*>>, MessageReceipt<*>> {
return super.processAlso(data, extraAttributes).also { (context, _) ->
this.currentMessageChain = (context as OutgoingMessagePipelineContext).currentMessageChain
}
}
}
override fun createContext( override fun createContext(
data: OutgoingMessagePipelineInput, attributes: TypeSafeMap data: OutgoingMessagePipelineInput, attributes: TypeSafeMap
@ -61,6 +67,9 @@ internal open class OutgoingMessagePipelineImpl :
internal interface OutgoingMessagePipelineContext : internal interface OutgoingMessagePipelineContext :
ProcessorPipelineContext<OutgoingMessagePipelineInput, MessageReceipt<*>> { ProcessorPipelineContext<OutgoingMessagePipelineInput, MessageReceipt<*>> {
/**
* Current message chain updated throughout the process. Will be updated from the [sub-processes][processAlso].
*/
var currentMessageChain: MessageChain var currentMessageChain: MessageChain
suspend fun MessageSource.tryEnsureSequenceIdAvailable() { suspend fun MessageSource.tryEnsureSequenceIdAvailable() {
@ -110,6 +119,10 @@ internal interface OutgoingMessagePipelineContext :
*/ */
val ORIGINAL_MESSAGE_AS_CHAIN = TypeKey<MessageChain>("originalMessageAsChain") val ORIGINAL_MESSAGE_AS_CHAIN = TypeKey<MessageChain>("originalMessageAsChain")
/**
* Message chain used when retrying with next [step][SendMessageStep]s.
*/
val MESSAGE_TO_RETRY = TypeKey<MessageChain>("messageToRetry")
/** /**
* Message target * Message target

View File

@ -49,6 +49,10 @@ internal interface MessageVisitorEx<in D, out R> : MessageVisitor<D, R> {
return visitInternalFlagOnlyMessage(message, data) return visitInternalFlagOnlyMessage(message, data)
} }
fun visitForceAsFragmentedMessage(message: ForceAsFragmentedMessage, data: D): R {
return visitInternalFlagOnlyMessage(message, data)
}
fun visitDontAsLongMessage(message: DontAsLongMessage, data: D): R { fun visitDontAsLongMessage(message: DontAsLongMessage, data: D): R {
return visitInternalFlagOnlyMessage(message, data) return visitInternalFlagOnlyMessage(message, data)
} }

View File

@ -30,7 +30,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushP
import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.network.toPacket
import net.mamoe.mirai.internal.pipeline.* import net.mamoe.mirai.internal.pipeline.*
import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -90,17 +89,6 @@ internal open class NoticeProcessorPipelineImpl protected constructor(
) : BaseContextImpl(attributes), NoticePipelineContext { ) : BaseContextImpl(attributes), NoticePipelineContext {
override val bot: QQAndroidBot override val bot: QQAndroidBot
get() = this@NoticeProcessorPipelineImpl.bot get() = this@NoticeProcessorPipelineImpl.bot
override suspend fun processAlso(
data: ProtocolStruct,
attributes: TypeSafeMap
): ProcessResult<out ProcessorPipelineContext<ProtocolStruct, Packet>, Packet> {
traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" }
return process(data, this.attributes + attributes).also { packets ->
this.collected.data += packets.collected
traceLogging.info { "processAlso: result=$packets" }
}
}
} }
override fun handleExceptionInProcess( override fun handleExceptionInProcess(

View File

@ -16,6 +16,7 @@ import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructure
import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer
import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.ProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.structureToStringIfAvailable import net.mamoe.mirai.internal.utils.structureToStringIfAvailable
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.unzip import net.mamoe.mirai.utils.unzip
@ -1005,11 +1006,18 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(6) @JvmField val type: Int = 0,
@ProtoNumber(7) @JvmField val richMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val richMsg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(8) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY,
@NestedStructure(SrcMsgDesensitizer::class)
@ProtoNumber(9) @JvmField val srcMsg: ByteArray? = null, @ProtoNumber(9) @JvmField val srcMsg: ByteArray? = null,
@ProtoNumber(10) @JvmField val toUin: Long = 0L, @ProtoNumber(10) @JvmField val toUin: Long = 0L,
@ProtoNumber(11) @JvmField val troopName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val troopName: ByteArray = EMPTY_BYTE_ARRAY,
) : ProtoBuf ) : ProtoBuf
internal object SrcMsgDesensitizer : NestedStructureDesensitizer<SourceMsg, MsgComm.Msg> {
override fun deserialize(context: SourceMsg, byteArray: ByteArray): MsgComm.Msg {
return byteArray.loadAs(MsgComm.Msg.serializer())
}
}
@Serializable @Serializable
internal class Text( internal class Text(
@ProtoNumber(1) @JvmField val str: String = "", @ProtoNumber(1) @JvmField val str: String = "",

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.internal.pipeline package net.mamoe.mirai.internal.pipeline
import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext
import net.mamoe.mirai.internal.network.components.NoticeProcessor import net.mamoe.mirai.internal.network.components.NoticeProcessor
import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable import net.mamoe.mirai.internal.utils.structureToStringAndDesensitizeIfAvailable
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -39,6 +40,7 @@ internal interface ProcessorPipeline<P : Processor<C, D>, C : ProcessorPipelineC
fun registerBefore(processor: P): DisposableRegistry fun registerBefore(processor: P): DisposableRegistry
fun createContext(data: D, attributes: TypeSafeMap): C
/** /**
* Process using the [context]. * Process using the [context].
@ -139,7 +141,7 @@ internal interface ProcessorPipelineContext<D, R> {
annotation class ConsumptionMarker // to give an explicit color. annotation class ConsumptionMarker // to give an explicit color.
/** /**
* Fire the [data] into the processor pipeline, and collect the results to current [collected]. * Fire the [data] into the processor pipeline, and collect the results to current [collected], updating *some mutable properties* in contexts, e.g. [OutgoingMessagePipelineContext.currentMessageChain]
* *
* @param extraAttributes extra attributes * @param extraAttributes extra attributes
* @return result collected from processors. This would also have been collected to this context (where you call [processAlso]). * @return result collected from processors. This would also have been collected to this context (where you call [processAlso]).
@ -216,8 +218,6 @@ protected constructor(
} }
} }
protected abstract fun createContext(data: D, attributes: TypeSafeMap): C
abstract inner class BaseContextImpl( abstract inner class BaseContextImpl(
attributes: TypeSafeMap, attributes: TypeSafeMap,
) : AbstractProcessorPipelineContext<D, R>(attributes, traceLogging) { ) : AbstractProcessorPipelineContext<D, R>(attributes, traceLogging) {
@ -226,7 +226,10 @@ protected constructor(
extraAttributes: TypeSafeMap extraAttributes: TypeSafeMap
): ProcessResult<out ProcessorPipelineContext<D, R>, R> { ): ProcessResult<out ProcessorPipelineContext<D, R>, R> {
traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" } traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" }
return process(data, this.attributes + extraAttributes).also { traceLogging.info { "extraAttributes = $extraAttributes" }
val newAttributes = this.attributes + extraAttributes
traceLogging.info { "newAttributes = $newAttributes" }
return process(data, newAttributes).also {
this.collected.data += it.collected this.collected.data += it.collected
traceLogging.info { "processAlso: result=$it" } traceLogging.info { "processAlso: result=$it" }
} }

View File

@ -16,7 +16,9 @@ import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.AbstractBot
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.message.data.inferMessageSourceKind import net.mamoe.mirai.internal.message.data.inferMessageSourceKind
import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocol
@ -41,6 +43,7 @@ import net.mamoe.mirai.internal.notice.processors.GroupExtensions
import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.test.runBlockingUnit
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.Clock import net.mamoe.mirai.utils.Clock
import net.mamoe.mirai.utils.lateinitMutableProperty
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.toUHexString
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
@ -52,9 +55,19 @@ import kotlin.test.assertEquals
import kotlin.test.asserter import kotlin.test.asserter
internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions { internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions {
init {
System.setProperty("mirai.message.protocol.log.full", "true")
System.setProperty("mirai.message.outgoing.pipeline.log.full", "true")
}
override fun createAccount(): BotAccount = BotAccount(1230001L, "pwd")
protected abstract val protocols: Array<out MessageProtocol> protected abstract val protocols: Array<out MessageProtocol>
protected var defaultTarget: ContactOrBot? = null protected var defaultTarget: ContactOrBot by lateinitMutableProperty {
bot.addGroup(123, 1230003).apply {
addMember(1230003, "user3", MemberPermission.OWNER)
}
}
private var decoderLoggerEnabled = false private var decoderLoggerEnabled = false
private var encoderLoggerEnabled = false private var encoderLoggerEnabled = false
@ -144,8 +157,8 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
var messages: MessageChainBuilder = MessageChainBuilder() var messages: MessageChainBuilder = MessageChainBuilder()
var groupIdOrZero: Long = 0 var groupIdOrZero: Long = 0
var messageSourceKind: MessageSourceKind = MessageSourceKind.GROUP var target: ContactOrBot = defaultTarget
var target: ContactOrBot? = defaultTarget var messageSourceKind: MessageSourceKind by lateinitMutableProperty { target.inferMessageSourceKind() }
var withGeneralFlags = true var withGeneralFlags = true
var isForward = false var isForward = false
@ -157,12 +170,10 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
messages.addAll(message) messages.addAll(message)
} }
fun target(target: ContactOrBot?) { fun target(target: ContactOrBot) {
this.target = target this.target = target
if (target != null) { messageSourceKind = target.inferMessageSourceKind()
messageSourceKind = target.inferMessageSourceKind()
}
if (target is Group) { if (target is Group) {
groupIdOrZero = target.id groupIdOrZero = target.id
@ -232,51 +243,53 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
// sending // sending
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
init { open inner class TestMessageProtocolStrategy : MessageProtocolStrategy<AbstractContact> {
components[MessageProtocolStrategy] = object : MessageProtocolStrategy<AbstractContact> { override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet {
override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { assertEquals(0x123, packet.sequenceId)
assertEquals(0x123, packet.sequenceId) return MessageSvcPbSendMsg.Response.SUCCESS
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")
}
}
} }
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")
}
}
}
init {
components[MessageProtocolStrategy] = TestMessageProtocolStrategy()
components[HighwayUploader] = object : HighwayUploader { components[HighwayUploader] = object : HighwayUploader {
override suspend fun uploadMessages( override suspend fun uploadMessages(
contact: AbstractContact, contact: AbstractContact,
@ -300,7 +313,7 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
fun runWithFacade(action: suspend MessageProtocolFacade.() -> Unit) { fun runWithFacade(action: suspend MessageProtocolFacade.() -> Unit) {
runBlockingUnit { runBlockingUnit {
facadeOf(*protocols).run { action() } facadeOf(*protocols).run { action() }
MessageProtocolFacade.INSTANCE.run { action() } MessageProtocolFacade.INSTANCE.copy().run { action() }
} }
} }

View File

@ -0,0 +1,146 @@
/*
* 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.ForwardMessageInternal
import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.cast
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 ForwardMessageProtocolTest : AbstractMessageProtocolTest() {
override val protocols: Array<out MessageProtocol> =
arrayOf(
TextProtocol(),
ImageProtocol(),
ForwardMessageProtocol(),
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 ForwardMessage to ForwardMessageInternal`() {
var message = buildForwardMessage(defaultTarget.cast()) {
currentTime = 16000000
1 named "Name1" says "Hello"
}.toMessageChain()
message += IgnoreLengthCheck
runWithFacade {
preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) ->
val receipt = receipts.single()
assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage)
assertMessageEquals(
ForwardMessageInternal(
"""<?xml version="1.0" encoding="utf-8"?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="[聊天记录]" m_resid="(size=1)501389E3070B20D87A80A67961F4EA0E" m_fileName="160023" 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> <title size="26" color="#777777" maxLines="2" lineSpace="12">Name1: Hello</title> <hr hidden="false" style="0"/> <summary size="26" color="#777777">查看1条转发消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg>""",
"(size=1)501389E3070B20D87A80A67961F4EA0E",
null,
origin = message[ForwardMessage]
) + IgnoreLengthCheck, context.currentMessageChain
)
}
}
}
@Test
fun `can convert empty ForwardMessage`() {
val message = buildForwardMessage(defaultTarget.cast()) {}.toMessageChain()
runWithFacade {
preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) ->
val receipt = receipts.single()
assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage)
assertMessageEquals(
ForwardMessageInternal(
"""<?xml version="1.0" encoding="utf-8"?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="[聊天记录]" m_resid="(size=0)D41D8CD98F00B204E9800998ECF8427E" m_fileName="160023" 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> <hr hidden="false" style="0"/> <summary size="26" color="#777777">查看0条转发消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg>""",
"(size=0)D41D8CD98F00B204E9800998ECF8427E",
null,
origin = message[ForwardMessage]
), context.currentMessageChain
)
}
}
}
// // TODO: 2022/5/23 test for download ForwardMessage
// @Test
// fun `can receive and download ForwardMessage`() {
// val message = runBlocking {
// runWithFacade {
// net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(
// msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead(
// fromUin = 1230001,
// toUin = 1230002,
// msgType = 166,
// c2cCmd = 11,
// msgSeq = 34437,
// msgTime = 1653334690,
// msgUid = 72057594524997436,
// wseqInC2cMsghead = 34437,
// ),
// msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody(
// richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText(
// attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr(
// codePage = 0,
// time = 1653334690,
// random = 487069500,
// effect = 0,
// charSet = 134,
// pitchAndFamily = 2,
// fontName = "宋体",
// ),
// elems = mutableListOf(
// net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
// richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg(
// template1 = "01 78 9C B5 92 4F 6F D3 30 18 C6 BF 8A 65 0E 39 4C 23 4D FA 2F A0 38 53 B7 29 EB D8 12 89 65 45 FC 11 9A DC C4 C9 2C D9 49 65 3B DD DA DB 24 0E 08 0E 88 03 17 04 42 48 70 40 42 C0 89 DB 3E 4E BB 7E 0C 1C 77 12 E3 80 C4 85 F8 10 47 AF F3 F3 F3 3C EF EB 6F 9D 73 06 A6 44 48 5A 95 C8 72 6E B7 2C 40 CA B4 CA 68 59 20 6B 74 1C 6E 7A 16 90 0A 97 19 66 55 49 90 35 23 D2 02 5B 81 CF 65 01 24 11 53 9A 92 FD 5D 04 DB 5D 08 14 E1 13 86 95 F9 76 20 C0 A9 6A 98 70 4A C9 59 54 33 45 23 59 40 30 16 94 E4 08 3E 59 5D BC 58 7C FA B2 FA F6 63 71 F9 E6 29 04 FC 44 10 49 33 04 C7 C7 E1 91 3C 4C F2 78 F4 28 89 C2 68 7B 63 E0 B9 03 B1 71 FF E1 78 87 1E A4 C5 38 8E 6B 6F 6F BB FF 60 CF 3E 9A 77 0E 1E C7 C3 D1 4E 31 BC 37 8F C2 DD 3C 12 83 44 34 A8 9C 32 12 63 4E 10 EC 3B 2D BD BC 8E 73 C7 E9 BA 1D A7 D7 ED 35 3A 93 9A 23 E8 42 20 AB 5A A4 44 CB DA D7 17 B7 20 A8 05 43 10 82 9C E1 42 3B D2 0E 32 1D 4C 42 8B D2 54 F9 B5 87 D0 94 5B 30 F0 A9 76 0C 18 9E 55 B5 5A 3B 6E CE 2B AA 63 39 A1 6B 22 5E BF 03 5F 51 C5 08 90 74 AE 45 B5 3B 1A 86 CF 0F 69 49 A4 D1 C1 F4 2E 99 E0 54 D7 1C 17 06 43 CA 1D CF E3 15 B9 7A FB EC 66 4C BE 6D 28 7F C2 DC 1E 04 69 C5 2A 81 E0 AD BE 79 FE 11 7E 17 80 FC 2C A3 ED FF 4C 3D 15 E0 94 66 19 D1 19 E6 98 49 A2 53 57 33 46 4C 3A 76 E0 CB 9A 73 2C 66 7F BF 36 58 7E F8 7C F5 EE A5 BB 7C FF 71 75 F9 75 F1 EA F5 F2 E7 F3 E5 C5 77 DF BE FE 33 F0 ED A6 0D 9A 64 9A 09 4A D3 F7 9B B1 41 40 D3 66 0E 7F 4F A4 DE 4D 26 4D 67 36 1D 23 C2 D6 C3 1C FC 02 21 C3 0A 7E".hexToBytes(),
// serviceId = 35,
// ),
// ),
// net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
// generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
// pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 6A".hexToBytes(),
// ),
// ),
// net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
// ),
// ),
// ),
// ),
// ).toMessageChainOnline(bot, facade = this)
// }
// }
// }
}

View File

@ -0,0 +1,114 @@
/*
* 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 kotlinx.coroutines.Deferred
import net.mamoe.mirai.internal.AbstractBot
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags
import net.mamoe.mirai.internal.message.flags.ForceAsFragmentedMessage
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.messageChainOf
import net.mamoe.mirai.utils.castUp
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
internal class GeneralMessageSenderProtocolTest : AbstractMessageProtocolTest() {
override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), GeneralMessageSenderProtocol())
@Test
fun `can convert messages failed to send to fragmented`() {
val message = messageChainOf(PlainText("test"), PlainText("test"))
runWithFacade {
components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() {
var count = 0
override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet {
println("MessageProtocolStrategy.sendPacket called: $count")
if (count++ <= 1) { // fail the first and second attempt
return MessageSvcPbSendMsg.Response.MessageTooLarge
}
return super.sendPacket(bot, packet)
}
override suspend fun createPacketsForGeneralMessage(
client: QQAndroidClient,
contact: AbstractContact,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
if (count == 2) {
assertTrue { fragmented }
} else {
assertFalse { fragmented }
}
return super.createPacketsForGeneralMessage(
client,
contact,
message,
originalMessage,
fragmented,
sourceCallback
)
}
}
preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) ->
val receipt = receipts.single()
assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage)
assertMessageEquals(message.dropMiraiInternalFlags(), context.currentMessageChain)
}
}
}
@Test
fun `can convert messages to fragmented`() {
val message = messageChainOf(PlainText("test"), PlainText("test"), ForceAsFragmentedMessage)
runWithFacade {
components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() {
override suspend fun createPacketsForGeneralMessage(
client: QQAndroidClient,
contact: AbstractContact,
message: MessageChain,
originalMessage: MessageChain,
fragmented: Boolean,
sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit
): List<OutgoingPacket> {
assertTrue { fragmented }
return super.createPacketsForGeneralMessage(
client,
contact,
message,
originalMessage,
fragmented,
sourceCallback
)
}
}
preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) ->
val receipt = receipts.single()
assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage)
assertMessageEquals(message, context.currentMessageChain)
}
}
}
}

View File

@ -10,13 +10,19 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.AbstractBot
import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags
import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.LongMessageInternal
import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage
import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck
import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.repeat import net.mamoe.mirai.message.data.repeat
import net.mamoe.mirai.message.data.toMessageChain
import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.message.data.toPlainText
import net.mamoe.mirai.utils.castUp import net.mamoe.mirai.utils.castUp
import net.mamoe.mirai.utils.getRandomString import net.mamoe.mirai.utils.getRandomString
@ -86,4 +92,46 @@ internal class LongMessageProtocolTest : AbstractMessageProtocolTest() {
} }
} }
} }
@Test
fun `can convert messages failed to send at FIRST step to LongMessageInternal`() {
val message = "test".toPlainText().toMessageChain()
runWithFacade {
components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() {
var count = 0
override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet {
println("MessageProtocolStrategy.sendPacket called: $count")
if (count++ == 0) { // fail the first attempt
return MessageSvcPbSendMsg.Response.MessageTooLarge
}
return super.sendPacket(bot, packet)
}
}
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="test"
m_resid="(size=1)8698F15C27DA63648CEF9A93EA76B084"
m_fileName="160023" sourceMsgId="0" url=""
flag="3" adverSign="0" multiMsgFlag="1">
<item layout="1">
<title>test</title>
<hr hidden="false" style="0"/>
<summary>点击查看完整消息</summary>
</item>
<source name="聊天记录" icon="" action="" appid="-1"/>
</msg>
""".trimIndent(), "(size=1)8698F15C27DA63648CEF9A93EA76B084"
), context.currentMessageChain
)
}
}
}
} }

View File

@ -10,19 +10,26 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.AbstractContact
import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy
import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageProcessorAdapter
import net.mamoe.mirai.internal.pipeline.replaceProcessor
import net.mamoe.mirai.message.data.LightApp import net.mamoe.mirai.message.data.LightApp
import net.mamoe.mirai.message.data.MessageOrigin import net.mamoe.mirai.message.data.MessageOrigin
import net.mamoe.mirai.message.data.MessageOriginKind import net.mamoe.mirai.message.data.MessageOriginKind
import net.mamoe.mirai.message.data.MusicKind.NeteaseCloudMusic import net.mamoe.mirai.message.data.MusicKind.NeteaseCloudMusic
import net.mamoe.mirai.message.data.MusicShare import net.mamoe.mirai.message.data.MusicShare
import net.mamoe.mirai.utils.castUp
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.hexToBytes
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.assertTrue
internal class MusicShareProtocolTest : AbstractMessageProtocolTest() { internal class MusicShareProtocolTest : AbstractMessageProtocolTest() {
override val protocols: Array<out MessageProtocol> = override val protocols: Array<out MessageProtocol> =
arrayOf(TextProtocol(), MusicShareProtocol(), RichMessageProtocol()) arrayOf(TextProtocol(), MusicShareProtocol(), RichMessageProtocol(), GeneralMessageSenderProtocol())
@BeforeEach @BeforeEach
fun `init group`() { fun `init group`() {
@ -73,5 +80,40 @@ internal class MusicShareProtocolTest : AbstractMessageProtocolTest() {
}.doDecoderChecks() }.doDecoderChecks()
} }
// no encoder. specially handled, no test for now. @Test
fun `can send MusicShare to group`() {
val message = MusicShare(
kind = NeteaseCloudMusic,
title = "ジェリーフィッシュ",
summary = "Yunomi/ローラーガール",
jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46",
pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg",
musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&userid=324076307&sc=wmv&tn=",
brief = "[分享]ジェリーフィッシュ",
)
runWithFacade {
assertTrue {
outgoingPipeline.replaceProcessor(
{ it is MusicShareProtocol.Sender },
OutgoingMessageProcessorAdapter(object : MusicShareProtocol.Sender() {
override suspend fun sendMusicSharePacket(
bot: QQAndroidBot,
musicShare: MusicShare,
contact: AbstractContact,
strategy: MessageProtocolStrategy<*>
) {
// nop
}
})
)
}
preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) ->
val receipt = receipts.single()
assertMessageEquals(message, receipt.source.originalMessage)
assertMessageEquals(message, context.currentMessageChain)
}
}
}
} }

View File

@ -7,48 +7,16 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
/*
* 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
*/
/*
* 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
*/
/*
* 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
*/
/*
* 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 package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData
import net.mamoe.mirai.message.data.Face import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.internal.utils.runCoroutineInPlace
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.QuoteReply
import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.message.data.messageChainOf
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.hexToBytes
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -57,8 +25,9 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() {
override val protocols: Array<out MessageProtocol> = arrayOf(QuoteReplyProtocol(), TextProtocol()) override val protocols: Array<out MessageProtocol> = arrayOf(QuoteReplyProtocol(), TextProtocol())
@Test @Test
fun `decode group reference group`() { fun `decode referencing online group message in group`() {
buildCodingChecks { buildCodingChecks {
targetGroup()
elem( elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
@ -82,38 +51,388 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() {
), ),
), ),
) )
// message( message(
// QuoteReply( QuoteReply(
//// OfflineMessageSourceImplData( OfflineMessageSourceImplData(
//// ids = intArrayOf(1803),
//// ) internalIds = intArrayOf(539443883),
// ) time = 1653147259,
// ) originalMessage = messageChainOf(PlainText("a")),
targetGroup() kind = messageSourceKind,
useOrdinaryEquality() fromId = 1230001,
targetId = 1,
botId = bot.id,
)
), PlainText("s")
)
}.doDecoderChecks() }.doDecoderChecks()
} }
@Test @Test
fun `can decode`() { fun `encode referencing offline group message in group`() {
doDecoderChecks( buildCodingChecks {
messageChainOf(Face(Face.YIN_XIAN)), targetGroup()
) { elem(
decodeAndRefineLight( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
listOf( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( origSeqs = intArrayOf(-31257),
face = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Face( senderUin = 1230001,
index = 108, time = 1653326514,
old = "14 AD".hexToBytes(), flag = 1,
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
), ),
) // mirai's OfflineMessageSource has no enough information to create 'srcMsg'
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "s",
),
), ),
groupIdOrZero = 0,
MessageSourceKind.GROUP,
bot,
) )
} message(
QuoteReply(
OfflineMessageSourceImplData(
ids = intArrayOf(-31257),
internalIds = intArrayOf(1860746670),
time = 1653326514,
originalMessage = messageChainOf(PlainText("a")),
kind = messageSourceKind,
fromId = 1230001,
targetId = 1994701021,
botId = bot.id,
)
), PlainText("s")
)
}.doEncoderChecks()
}
private val onlineIncomingGroupMessage = runCoroutineInPlace {
net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(
msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead(
fromUin = 1230001,
toUin = 1230002,
msgType = 166,
c2cCmd = 11,
msgSeq = 31245,
msgTime = 1653330864,
msgUid = 72057594652150074,
wseqInC2cMsghead = 31245,
),
msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody(
richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText(
attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr(
codePage = 0,
time = 1653330864,
random = 614222138,
size = 9,
effect = 0,
charSet = 134,
pitchAndFamily = 0,
fontName = "Helvetica",
),
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61".hexToBytes(),
),
),
),
),
),
).toMessageChainOnline(bot)
}
@Test
fun `encode referencing online incoming group message in group`() {
buildCodingChecks {
targetGroup()
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
origSeqs = intArrayOf(31245),
senderUin = 1230001,
time = 1653330864,
flag = 1,
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61".hexToBytes(),
),
),
),
pbReserve = "18 BA 92 F1 A4 02".hexToBytes(),
srcMsg = "0A 20 08 B1 89 4B 10 B2 89 4B 18 A6 01 20 0B 28 8D F4 01 30 B0 A7 AF 94 06 38 BA 92 F1 A4 02 E0 01 01 1A 2D 0A 2B 12 05 0A 03 0A 01 61 12 00 12 1C AA 02 19 9A 01 16 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61 12 02 4A 00".hexToBytes(),
toUin = 1230002,
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "s",
),
)
)
message(onlineIncomingGroupMessage.quote(), PlainText("s"))
}.doEncoderChecks()
}
// stranger and group temp are almost the same for friend.
@Test
fun `decode referencing online incoming private message in friend`() {
buildCodingChecks {
targetFriend()
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
origSeqs = intArrayOf(34279),
senderUin = 1230001,
time = 1653326514,
flag = 1,
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
),
pbReserve = "18 AE FB A2 F7 86 80 80 80 01".hexToBytes(),
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "s",
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 51".hexToBytes(),
),
),
)
message(
QuoteReply(
OfflineMessageSourceImplData(
ids = intArrayOf(34279),
internalIds = intArrayOf(1860746670),
time = 1653326514,
originalMessage = messageChainOf(PlainText("a")),
kind = messageSourceKind,
fromId = 1230001,
targetId = 0, // the referenced message was actually sending from friend 1230001 to bot.
botId = bot.id,
)
), PlainText("s")
)
}.doDecoderChecks()
}
@Test
fun `decode referencing online outgoing private message in friend`() {
buildCodingChecks {
targetFriend()
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
origSeqs = intArrayOf(49858),
senderUin = 1230002,
time = 1653329998,
flag = 1,
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "b",
),
),
),
pbReserve = "18 C3 94 C4 B3 84 80 80 80 01".hexToBytes(),
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "s",
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 5E".hexToBytes(),
),
),
)
message(
QuoteReply(
OfflineMessageSourceImplData(
ids = intArrayOf(49858),
internalIds = intArrayOf(1181813315),
time = 1653329998,
originalMessage = messageChainOf(PlainText("b")),
kind = messageSourceKind,
fromId = 1230002, // bot id
targetId = 0, // the referenced message was actually sending from bot to the friend 1230001.
botId = bot.id,
)
), PlainText("s")
)
}.doDecoderChecks()
}
@Test
fun `encode referencing offline private message in friend`() {
buildCodingChecks {
targetFriend()
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
origSeqs = intArrayOf(-31257),
senderUin = 1230001,
time = 1653326514,
flag = 1,
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
),
// mirai's OfflineMessageSource has no enough information to create 'srcMsg'
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "s",
),
),
)
message(
QuoteReply(
OfflineMessageSourceImplData(
ids = intArrayOf(-31257),
internalIds = intArrayOf(1860746670),
time = 1653326514,
originalMessage = messageChainOf(PlainText("a")),
kind = messageSourceKind,
fromId = 1230001,
targetId = 1994701021,
botId = bot.id,
)
), PlainText("s")
)
}.doEncoderChecks()
}
init {
bot.addFriend(1230001)
}
private val onlineIncomingFriendMessage: MessageChain = runCoroutineInPlace {
net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg(
msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead(
fromUin = 1230001,
toUin = 1230002,
msgType = 166,
c2cCmd = 11,
msgSeq = 31222,
msgTime = 1653328003,
msgUid = 72057595832827069,
wseqInC2cMsghead = 31222,
),
msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody(
richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText(
attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr(
codePage = 0,
time = 1653328002,
random = 1794899133,
size = 9,
effect = 0,
charSet = 134,
pitchAndFamily = 0,
fontName = "Helvetica",
),
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F".hexToBytes(),
),
),
),
),
),
).toMessageChainOnline(bot)
}
@Test
fun `encode referencing online incoming private message in friend`() {
buildCodingChecks {
targetFriend()
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg(
origSeqs = intArrayOf(31222),
senderUin = 1230001,
time = 1653328003,
flag = 1,
elems = mutableListOf(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "a",
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
), // Don't worry about this empty Elem, it's from official
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags(
pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F".hexToBytes(),
),
),
),
pbReserve = "18 BD F9 EF D7 06".hexToBytes(),
// srcMsg is available for online source
srcMsg = "0A 20 08 B1 89 4B 10 B2 89 4B 18 A6 01 20 0B 28 F6 F3 01 30 83 91 AF 94 06 38 BD F9 EF D7 06 E0 01 01 1A 2D 0A 2B 12 05 0A 03 0A 01 61 12 00 12 1C AA 02 19 9A 01 16 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F 12 02 4A 00".hexToBytes(),
toUin = 1230002,
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "s",
),
)
)
message(onlineIncomingFriendMessage.quote(), PlainText("s"))
}.doEncoderChecks()
} }
private fun CodingChecksBuilder.targetGroup() { private fun CodingChecksBuilder.targetGroup() {
@ -121,7 +440,7 @@ internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() {
} }
private fun CodingChecksBuilder.targetFriend() { private fun CodingChecksBuilder.targetFriend() {
target(bot.addFriend(1)) target(bot.getFriendOrFail(1230001))
} }

View File

@ -13,6 +13,7 @@ package net.mamoe.mirai.internal.network.framework
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.MockBot
import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
@ -31,6 +32,8 @@ import net.mamoe.mirai.internal.utils.subLogger
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import network.framework.components.TestEventDispatcherImpl import network.framework.components.TestEventDispatcherImpl
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import kotlin.math.absoluteValue
import kotlin.random.Random
import kotlin.test.assertEquals import kotlin.test.assertEquals
/** /**
@ -41,9 +44,13 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT
protected open fun createNetworkHandlerContext() = TestNetworkHandlerContext(bot, logger, components) protected open fun createNetworkHandlerContext() = TestNetworkHandlerContext(bot, logger, components)
protected open fun createNetworkHandler() = TestNetworkHandler(bot, createNetworkHandlerContext()) protected open fun createNetworkHandler() = TestNetworkHandler(bot, createNetworkHandlerContext())
protected val bot: QQAndroidBot = MockBot { protected open fun createAccount() = BotAccount(Random.nextLong().absoluteValue.mod(1000L), "pwd")
nhProvider = { createNetworkHandler() }
additionalComponentsProvider = { this@AbstractMockNetworkHandlerTest.components } protected val bot: QQAndroidBot by lazy {
MockBot(createAccount()) {
nhProvider = { createNetworkHandler() }
additionalComponentsProvider = { this@AbstractMockNetworkHandlerTest.components }
}
} }
protected val logger = MiraiLogger.Factory.create(Bot::class, "test") protected val logger = MiraiLogger.Factory.create(Bot::class, "test")
protected val components = ConcurrentComponentStorage().apply { protected val components = ConcurrentComponentStorage().apply {

View File

@ -37,11 +37,8 @@ import kotlin.test.assertTrue
internal class MessageReceiptTest : AbstractTest(), GroupExtensions { internal class MessageReceiptTest : AbstractTest(), GroupExtensions {
private val bot = MockBot() private val bot = MockBot()
/**
* This test is very ugly, but we cannot do anything else.
*/ // We need #1304
@Test @Test
fun `refine ForwardMessageInternal for MessageReceipt`() = runBlockingUnit { fun `refine ForwardMessageInternal for MessageReceipt's original MessageChain`() = runBlockingUnit {
val group = bot.addGroup(123, 2) val group = bot.addGroup(123, 2)
val forward = buildForwardMessage(group) { val forward = buildForwardMessage(group) {
@ -52,7 +49,7 @@ internal class MessageReceiptTest : AbstractTest(), GroupExtensions {
val facade = MessageProtocolFacade.INSTANCE.copy() val facade = MessageProtocolFacade.INSTANCE.copy()
assertTrue { assertTrue {
facade.preprocessorPipeline.replaceProcessor( facade.outgoingPipeline.replaceProcessor(
{ it is GeneralMessageSenderProtocol.GeneralMessageSender }, { it is GeneralMessageSenderProtocol.GeneralMessageSender },
OutgoingMessageProcessorAdapter(object : OutgoingMessageSender { OutgoingMessageProcessorAdapter(object : OutgoingMessageSender {
override suspend fun OutgoingMessagePipelineContext.process() { override suspend fun OutgoingMessagePipelineContext.process() {

View File

@ -18,9 +18,9 @@ import net.mamoe.mirai.internal.MockBot
import net.mamoe.mirai.internal.getMiraiImpl import net.mamoe.mirai.internal.getMiraiImpl
import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer
import net.mamoe.mirai.internal.message.RefinableMessage import net.mamoe.mirai.internal.message.RefinableMessage
import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.internal.message.RefineContext
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
@ -326,7 +326,7 @@ private fun sourceStub(
private suspend fun testRecursiveRefine(list: List<ImMsgBody.Elem>, expected: MessageChain, isLight: Boolean) { private suspend fun testRecursiveRefine(list: List<ImMsgBody.Elem>, expected: MessageChain, isLight: Boolean) {
val actual = buildMessageChain { val actual = buildMessageChain {
ReceiveMessageTransformer.joinToMessageChain(list, 0, MessageSourceKind.GROUP, bot, this) MessageProtocolFacade.decode(list, 0, MessageSourceKind.GROUP, bot, this, null)
}.let { c -> }.let { c ->
if (isLight) { if (isLight) {
c.refineLight(bot) c.refineLight(bot)