Integrate new MessageProtocol with existing code

This commit is contained in:
Him188 2022-04-28 16:00:07 +01:00
parent c47779c726
commit a89f6aeaef
41 changed files with 934 additions and 476 deletions

View File

@ -0,0 +1,23 @@
<!--
~ 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
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RunMessageDecodingRecorderKt" type="JetRunConfigurationType"
nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="net.mamoe.mirai.internal.bootstrap.RunMessageDecodingRecorderKt"/>
<module name="mirai.mirai-core.jvmTest"/>
<shortenClasspath name="NONE"/>
<option name="VM_PARAMETERS"
value="-Dmirai.debug.network.state.observer.logging=true -Dmirai.debug.network.show.all.components=true -Dkotlinx.coroutines.debug=on -Dmirai.debug.network.show.packet.details=true"/>
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/run"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
</configuration>
</component>

View File

@ -1,10 +1,10 @@
/* /*
* Copyright 2019-2021 Mamoe Technologies and contributors. * Copyright 2019-2022 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* *
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
@file:JvmMultifileClass @file:JvmMultifileClass

View File

@ -17,10 +17,18 @@ import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageC
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
import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext.Companion.BOT
import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext.Companion.GROUP_ID
import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext.Companion.MESSAGE_SOURCE_KIND
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.source.* import net.mamoe.mirai.internal.message.source.*
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
import net.mamoe.mirai.internal.utils.runCoroutineInPlace
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.buildTypeSafeMap
import net.mamoe.mirai.utils.toLongUnsigned import net.mamoe.mirai.utils.toLongUnsigned
/** /**
@ -147,68 +155,16 @@ internal object ReceiveMessageTransformer {
bot: Bot, bot: Bot,
builder: MessageChainBuilder, builder: MessageChainBuilder,
) { ) {
// ProtoBuf.encodeToHexString(elements).soutv("join") val pipeline = MessageProtocolFacade.decoderPipeline
// (this._miraiContentToString().soutv())
for (element in elements) { val attributes = buildTypeSafeMap {
transformElement(element, groupIdOrZero, messageSourceKind, bot, builder) set(BOT, bot)
when { set(MESSAGE_SOURCE_KIND, messageSourceKind)
element.richMsg != null -> { set(GROUP_ID, groupIdOrZero)
// removed
}
}
} }
}
private fun transformElement( runCoroutineInPlace {
element: ImMsgBody.Elem, elements.forEach { builder.addAll(pipeline.process(it, attributes)) }
groupIdOrZero: Long,
messageSourceKind: MessageSourceKind,
bot: Bot,
builder: MessageChainBuilder,
) {
when {
element.srcMsg != null -> {
// removed
}
element.notOnlineImage != null -> {
// removed
}
element.customFace != null -> {
// removed
}
element.face != null -> {
// removed
}
element.text != null -> {
// removed
}
element.marketFace != null -> {
// removed
}
element.lightApp != null -> {
// removed
}
element.customElem != null -> {
// removed
}
element.commonElem != null -> {
// removed
}
element.transElemInfo != null -> {
// removed
}
element.elemFlags2 != null
|| element.extraInfo != null
|| element.generalFlags != null
|| element.anonGroupMsg != null
-> {
// ignore
}
else -> {
// removed
// println(it._miraiContentToString())
}
} }
} }
@ -315,27 +271,6 @@ internal object ReceiveMessageTransformer {
return builder.asMessageChain() return builder.asMessageChain()
} }
private fun decodeText(text: ImMsgBody.Text, list: MessageChainBuilder) {
// removed
}
private fun decodeSrcMsg(
srcMsg: ImMsgBody.SourceMsg,
list: MessageChainBuilder,
bot: Bot,
messageSourceKind: MessageSourceKind,
groupIdOrZero: Long,
) {
// removed
}
private fun decodeLightApp(
lightApp: ImMsgBody.LightAppElem,
list: MessageChainBuilder,
) {
// removed
}
fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl( fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl(
filename = fileName.decodeToString(), filename = fileName.decodeToString(),
fileMd5 = fileMd5, fileMd5 = fileMd5,

View File

@ -9,27 +9,16 @@
package net.mamoe.mirai.internal.message package net.mamoe.mirai.internal.message
import net.mamoe.mirai.contact.AnonymousMember
import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.message.protocol.MessageEncoderContext
import net.mamoe.mirai.internal.message.data.MarketFaceImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl
import net.mamoe.mirai.internal.message.flags.InternalFlagOnlyMessage
import net.mamoe.mirai.internal.message.image.OfflineFriendImage
import net.mamoe.mirai.internal.message.image.OfflineGroupImage
import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl
import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl
import net.mamoe.mirai.internal.message.source.MessageSourceInternal
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.internal.utils.runCoroutineInPlace
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510 import net.mamoe.mirai.utils.buildTypeSafeMap
internal val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
internal val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。")
internal val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。")
internal val UNSUPPORTED_VOICE_MESSAGE_PLAIN = PlainText("收到语音消息你需要升级到最新版QQ才能接收升级地址https://im.qq.com") internal val UNSUPPORTED_VOICE_MESSAGE_PLAIN = PlainText("收到语音消息你需要升级到最新版QQ才能接收升级地址https://im.qq.com")
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@ -38,138 +27,21 @@ internal fun MessageChain.toRichTextElems(
withGeneralFlags: Boolean, withGeneralFlags: Boolean,
isForward: Boolean = false, isForward: Boolean = false,
): MutableList<ImMsgBody.Elem> { ): MutableList<ImMsgBody.Elem> {
val forGroup = messageTarget is Group val originalMessage = this
val elements = ArrayList<ImMsgBody.Elem>(this.size) val pipeline = MessageProtocolFacade.encoderPipeline
var longTextResId: String? = null val attributes = buildTypeSafeMap {
set(MessageEncoderContext.CONTACT, messageTarget)
fun transformOneMessage(currentMessage: Message) { set(MessageEncoderContext.ORIGINAL_MESSAGE, originalMessage)
if (currentMessage is RichMessage) { set(MessageEncoderContext.ADD_GENERAL_FLAGS, withGeneralFlags)
// removed set(MessageEncoderContext.IS_FORWARD, isForward)
}
when (currentMessage) {
is PlainText -> {
// removed
}
is CustomMessage -> {
// removed
}
is At -> {
// removed
}
is PokeMessage -> {
// removed
}
is OfflineGroupImage -> {
// removed
}
is OnlineGroupImageImpl -> {
// removed
}
is OnlineFriendImageImpl -> {
// removed
}
is OfflineFriendImage -> {
// removed
}
is FlashImage -> {
// removed
}
is AtAll -> {
// removed
}
is Face -> {
// removed
}
is QuoteReply -> { // transformed
}
is Dice -> {
// removed
}
is MarketFace -> {
// removed
}
is VipFace -> {
// removed
}
is PttMessage -> {
// removed
}
is MusicShare -> {
// removed
}
is ForwardMessage,
is MessageSource, // mirai metadata only
is RichMessage, // already transformed above
-> {
}
is InternalFlagOnlyMessage, is ShowImageFlag -> {
// ignore
}
is UnsupportedMessageImpl -> {
// removed
}
else -> {
// unrecognized types are ignored
// error("unsupported message type: ${currentMessage::class.simpleName}")
}
}
} }
if (this.anyIsInstance<QuoteReply>()) { val builder = ArrayList<ImMsgBody.Elem>(originalMessage.size)
when (val source = this[QuoteReply]!!.source) {
is MessageSourceInternal -> { runCoroutineInPlace {
elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) originalMessage.forEach { builder.addAll(pipeline.process(it, attributes)) }
if (forGroup) {
if (source is OnlineMessageSource.Incoming.FromGroup) {
val sender0 = source.sender
if (sender0 !is AnonymousMember)
transformOneMessage(At(sender0))
// transformOneMessage(PlainText(" "))
// removed by https://github.com/mamoe/mirai/issues/524
// 发送 QuoteReply 消息时无可避免的产生多余空格 #524
}
}
}
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}. Don't implement your own MessageSource.")
}
} }
this.forEach(::transformOneMessage) return builder
if (withGeneralFlags) {
when {
longTextResId != null -> {
// removed
}
this.anyIsInstance<MarketFaceImpl>() -> {
// removed
}
this.anyIsInstance<RichMessage>() -> {
// removed
}
this.anyIsInstance<FlashImage>() -> {
// removed
}
this.anyIsInstance<PttMessage>() -> {
// removed
}
else -> {
// removed
}
}
}
return elements
} }
@Suppress("SpellCheckingInspection")
internal val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()

View File

@ -0,0 +1,54 @@
/*
* 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
import net.mamoe.mirai.Bot
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker
import net.mamoe.mirai.internal.pipeline.Processor
import net.mamoe.mirai.internal.pipeline.ProcessorPipeline
import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.utils.TypeKey
import kotlin.coroutines.RestrictsSuspension
internal interface MessageDecoderPipeline : ProcessorPipeline<MessageDecoderProcessor, ImMsgBody.Elem, Message>
@RestrictsSuspension // Implementor can only call `MessageDecoderContext.process` and `processAlso` so there will be no suspension point
internal interface MessageDecoderContext : ProcessorPipelineContext<ImMsgBody.Elem, Message> {
companion object {
val BOT = TypeKey<Bot>("bot")
val MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind")
val GROUP_ID = TypeKey<Long>("groupId") // zero if not group
}
}
internal interface MessageDecoder : PipelineConsumptionMarker {
suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem)
}
/**
* Adapter for [MessageDecoder] to be used as [Processor].
*/
internal class MessageDecoderProcessor(
private val decoder: MessageDecoder,
) : Processor<MessageDecoderContext, ImMsgBody.Elem> {
override suspend fun process(context: MessageDecoderContext, data: ImMsgBody.Elem) {
@Suppress("ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL")
decoder.run { context.process(data) }
// TODO: 2022/4/27 handle exceptions
}
override fun toString(): String {
return "MessageDecoderProcessor(decoder=$decoder)"
}
}

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.internal.message.protocol
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.pipeline.AbstractProcessorPipeline
import net.mamoe.mirai.internal.pipeline.PipelineConfiguration
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.TypeSafeMap import net.mamoe.mirai.utils.TypeSafeMap
@ -25,6 +26,7 @@ private val defaultTraceLogging: MiraiLogger by lazy {
internal open class MessageDecoderPipelineImpl : internal open class MessageDecoderPipelineImpl :
AbstractProcessorPipeline<MessageDecoderProcessor, MessageDecoderContext, ImMsgBody.Elem, Message>( AbstractProcessorPipeline<MessageDecoderProcessor, MessageDecoderContext, ImMsgBody.Elem, Message>(
PipelineConfiguration(stopWhenConsumed = true),
defaultTraceLogging defaultTraceLogging
), ),
MessageDecoderPipeline { MessageDecoderPipeline {

View File

@ -0,0 +1,79 @@
/*
* 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
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker
import net.mamoe.mirai.internal.pipeline.Processor
import net.mamoe.mirai.internal.pipeline.ProcessorPipeline
import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.TypeKey
import net.mamoe.mirai.utils.uncheckedCast
import kotlin.reflect.KClass
internal interface MessageEncoderPipeline :
ProcessorPipeline<MessageEncoderProcessor<*>, SingleMessage, ImMsgBody.Elem> {
}
internal interface MessageEncoderContext : ProcessorPipelineContext<SingleMessage, ImMsgBody.Elem> {
/**
* General flags that should be appended to the end of the result.
*
* Do not update this property directly, but call [collectGeneralFlags].
*/
var generalFlags: ImMsgBody.Elem
companion object {
val ADD_GENERAL_FLAGS = TypeKey<Boolean>("addGeneralFlags")
val MessageEncoderContext.addGeneralFlags get() = attributes[ADD_GENERAL_FLAGS]
/**
* Override default generalFlags if needed
*/
inline fun MessageEncoderContext.collectGeneralFlags(block: () -> ImMsgBody.Elem) {
if (addGeneralFlags) {
generalFlags = block()
}
}
val CONTACT = TypeKey<ContactOrBot?>("contactOrBot")
val MessageEncoderContext.contact get() = attributes[CONTACT]
val ORIGINAL_MESSAGE = TypeKey<MessageChain>("originalMessage")
val MessageEncoderContext.originalMessage get() = attributes[ORIGINAL_MESSAGE]
val IS_FORWARD = TypeKey<Boolean>("isForward")
val MessageEncoderContext.isForward get() = attributes[IS_FORWARD]
}
}
internal fun interface MessageEncoder<T : SingleMessage> : PipelineConsumptionMarker {
suspend fun MessageEncoderContext.process(data: T)
}
/**
* Adapter for [MessageEncoder] to be used as [Processor].
*/
internal class MessageEncoderProcessor<T : SingleMessage>(
private val encoder: MessageEncoder<T>,
private val elementType: KClass<T>,
) : Processor<MessageEncoderContext, SingleMessage> {
override suspend fun process(context: MessageEncoderContext, data: SingleMessage) {
if (elementType.isInstance(data)) {
encoder.run { context.process(data.uncheckedCast()) }
// TODO: 2022/4/27 handle exceptions
}
}
}

View File

@ -9,9 +9,9 @@
package net.mamoe.mirai.internal.message.protocol package net.mamoe.mirai.internal.message.protocol
import net.mamoe.mirai.internal.message.PB_RESERVE_FOR_ELSE
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.pipeline.AbstractProcessorPipeline
import net.mamoe.mirai.internal.pipeline.PipelineConfiguration
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
@ -22,6 +22,7 @@ private val defaultTraceLogging: MiraiLogger by lazy {
internal open class MessageEncoderPipelineImpl : internal open class MessageEncoderPipelineImpl :
AbstractProcessorPipeline<MessageEncoderProcessor<*>, MessageEncoderContext, SingleMessage, ImMsgBody.Elem>( AbstractProcessorPipeline<MessageEncoderProcessor<*>, MessageEncoderContext, SingleMessage, ImMsgBody.Elem>(
PipelineConfiguration(stopWhenConsumed = true),
defaultTraceLogging defaultTraceLogging
), ),
MessageEncoderPipeline { MessageEncoderPipeline {
@ -34,4 +35,8 @@ internal open class MessageEncoderPipelineImpl :
} }
override fun createContext(attributes: TypeSafeMap): MessageEncoderContext = MessageEncoderContextImpl(attributes) override fun createContext(attributes: TypeSafeMap): MessageEncoderContext = MessageEncoderContextImpl(attributes)
private companion object {
private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes()
}
} }

View File

@ -9,33 +9,12 @@
package net.mamoe.mirai.internal.message.protocol package net.mamoe.mirai.internal.message.protocol
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker
import net.mamoe.mirai.internal.pipeline.Processor
import net.mamoe.mirai.internal.pipeline.ProcessorPipeline
import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.TypeKey
import net.mamoe.mirai.utils.uncheckedCast
import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
internal abstract class ProcessorCollector { // Loaded by ServiceLoader
inline fun <reified T : SingleMessage> add(encoder: MessageEncoder<T>) = add(encoder, T::class)
abstract fun <T : SingleMessage> add(encoder: MessageEncoder<T>, elementType: KClass<T>)
abstract fun add(decoder: MessageDecoder)
}
internal abstract class MessageProtocol( internal abstract class MessageProtocol(
private val priority: UInt = 1000u // the higher, the prior it being called val priority: UInt = PRIORITY_CONTENT // the higher, the prior it being called
) { ) {
fun collectProcessors(processorCollector: ProcessorCollector) { fun collectProcessors(processorCollector: ProcessorCollector) {
processorCollector.collectProcessorsImpl() processorCollector.collectProcessorsImpl()
@ -46,124 +25,36 @@ internal abstract class MessageProtocol(
companion object { companion object {
const val PRIORITY_METADATA: UInt = 10000u const val PRIORITY_METADATA: UInt = 10000u
const val PRIORITY_CONTENT: UInt = 1000u const val PRIORITY_CONTENT: UInt = 1000u
const val PRIORITY_IGNORE: UInt = 500u
const val PRIORITY_UNSUPPORTED: UInt = 100u const val PRIORITY_UNSUPPORTED: UInt = 100u
} }
}
internal object MessageProtocols { object PriorityComparator : Comparator<MessageProtocol> {
val instances: List<MessageProtocol> = initialize() override fun compare(o1: MessageProtocol, o2: MessageProtocol): Int {
private fun initialize(): List<MessageProtocol> { // Do not use o1.compareTo
val encoderPipeline = MessageEncoderPipelineImpl() // > Task :mirai-core:checkAndroidApiLevel
val decoderPipeline = MessageDecoderPipelineImpl() // > /Users/runner/work/mirai/mirai/mirai-core/build/classes/kotlin/android/main/net/mamoe/mirai/internal/message/protocol/MessageProtocol$PriorityComparator.class
// > Method compare(Lnet/mamoe/mirai/internal/message/protocol/MessageProtocol;Lnet/mamoe/mirai/internal/message/protocol/MessageProtocol;)I
// > Invoke method java/lang/Integer.compareUnsigned(II)I
// Couldn't access java/lang/Integer.compareUnsigned(II)I: java/lang/Integer.compareUnsigned(II)I since api level 26
val instances = ServiceLoader.load(MessageProtocol::class.java).iterator().asSequence().toList() return uintCompare(o1.priority.toInt(), o2.priority.toInt())
for (instance in instances) {
instance.collectProcessors(object : ProcessorCollector() {
override fun <T : SingleMessage> add(encoder: MessageEncoder<T>, elementType: KClass<T>) {
encoderPipeline.registerProcessor(MessageEncoderProcessor(encoder, elementType))
}
override fun add(decoder: MessageDecoder) {
decoderPipeline.registerProcessor(MessageDecoderProcessor(decoder))
}
})
} }
return instances private fun uintCompare(v1: Int, v2: Int): Int = (v1 xor Int.MIN_VALUE).compareTo(v2 xor Int.MIN_VALUE)
}
}
///////////////////////////////////////////////////////////////////////////
// decoders
///////////////////////////////////////////////////////////////////////////
internal interface MessageDecoderContext : ProcessorPipelineContext<ImMsgBody.Elem, Message> {
companion object {
val BOT = TypeKey<Bot>("bot")
val MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind")
val GROUP_ID = TypeKey<Long>("groupId") // zero if not group
} }
} }
internal interface MessageDecoder : PipelineConsumptionMarker { internal abstract class ProcessorCollector {
suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) inline fun <reified T : SingleMessage> add(encoder: MessageEncoder<T>) = add(encoder, T::class)
abstract fun <T : SingleMessage> add(encoder: MessageEncoder<T>, elementType: KClass<T>)
abstract fun add(decoder: MessageDecoder)
} }
/**
* Adapter for [MessageDecoder] to be used as [Processor].
*/
internal class MessageDecoderProcessor(
private val decoder: MessageDecoder
) : Processor<MessageDecoderContext, ImMsgBody.Elem> {
override suspend fun process(context: MessageDecoderContext, data: ImMsgBody.Elem) {
decoder.run { context.process(data) }
// TODO: 2022/4/27 handle exceptions
}
}
internal interface MessageDecoderPipeline : ProcessorPipeline<MessageDecoderProcessor, ImMsgBody.Elem, Message>
///////////////////////////////////////////////////////////////////////////
// encoders
///////////////////////////////////////////////////////////////////////////
internal interface MessageEncoderContext : ProcessorPipelineContext<SingleMessage, ImMsgBody.Elem> {
/**
* General flags that should be appended to the end of the result.
*
* Do not update this property directly, but call [collectGeneralFlags].
*/
var generalFlags: ImMsgBody.Elem
companion object {
val ADD_GENERAL_FLAGS = TypeKey<Boolean>("addGeneralFlags")
val MessageEncoderContext.addGeneralFlags get() = attributes[ADD_GENERAL_FLAGS]
/**
* Override default generalFlags if needed
*/
inline fun MessageEncoderContext.collectGeneralFlags(block: () -> ImMsgBody.Elem) {
if (addGeneralFlags) {
generalFlags = block()
}
}
val CONTACT = TypeKey<Contact>("contact")
val MessageEncoderContext.contact get() = attributes[CONTACT]
val ORIGINAL_MESSAGE = TypeKey<MessageChain>("originalMessage")
val MessageEncoderContext.originalMessage get() = attributes[ORIGINAL_MESSAGE]
val IS_FORWARD = TypeKey<Boolean>("isForward")
val MessageEncoderContext.isForward get() = attributes[IS_FORWARD]
}
}
internal fun interface MessageEncoder<T : SingleMessage> : PipelineConsumptionMarker {
suspend fun MessageEncoderContext.process(data: T)
}
/**
* Adapter for [MessageEncoder] to be used as [Processor].
*/
internal class MessageEncoderProcessor<T : SingleMessage>(
private val encoder: MessageEncoder<T>,
private val elementType: KClass<T>,
) : Processor<MessageEncoderContext, SingleMessage> {
override suspend fun process(context: MessageEncoderContext, data: SingleMessage) {
if (elementType.isInstance(data)) {
encoder.run { context.process(data.uncheckedCast()) }
// TODO: 2022/4/27 handle exceptions
}
}
}
internal interface MessageEncoderPipeline : ProcessorPipeline<MessageEncoderProcessor<*>, SingleMessage, ImMsgBody.Elem>
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// refiners // refiners

View File

@ -0,0 +1,49 @@
/*
* 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
import net.mamoe.mirai.message.data.SingleMessage
import java.util.*
import kotlin.reflect.KClass
internal interface MessageProtocolFacade {
val encoderPipeline: MessageEncoderPipeline
val decoderPipeline: MessageDecoderPipeline
val loaded: List<MessageProtocol>
companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl()
}
internal class MessageProtocolFacadeImpl : MessageProtocolFacade {
override val encoderPipeline: MessageEncoderPipeline = MessageEncoderPipelineImpl()
override val decoderPipeline: MessageDecoderPipeline = MessageDecoderPipelineImpl()
override val loaded: List<MessageProtocol> = initialize()
private fun initialize(): List<MessageProtocol> {
val instances = ServiceLoader.load(MessageProtocol::class.java).iterator().asSequence()
.toCollection(PriorityQueue(MessageProtocol.PriorityComparator.reversed()))
for (instance in instances) {
instance.collectProcessors(object : ProcessorCollector() {
override fun <T : SingleMessage> add(encoder: MessageEncoder<T>, elementType: KClass<T>) {
encoderPipeline.registerProcessor(MessageEncoderProcessor(encoder, elementType))
}
override fun add(decoder: MessageDecoder) {
decoderPipeline.registerProcessor(MessageDecoderProcessor(decoder))
}
})
}
return instances.toList()
}
}

View File

@ -9,7 +9,6 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.internal.message.MIRAI_CUSTOM_ELEM_TYPE
import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.message.protocol.*
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.CustomMessage import net.mamoe.mirai.message.data.CustomMessage
@ -24,6 +23,8 @@ internal class CustomMessageProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<CustomMessage> { private class Encoder : MessageEncoder<CustomMessage> {
override suspend fun MessageEncoderContext.process(data: CustomMessage) { override suspend fun MessageEncoderContext.process(data: CustomMessage) {
markAsConsumed()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
collect( collect(
ImMsgBody.Elem( ImMsgBody.Elem(
@ -37,12 +38,16 @@ internal class CustomMessageProtocol : MessageProtocol() {
) )
) )
} }
private companion object {
private val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510
}
} }
private class Decoder : MessageDecoder { private class Decoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
if (data.customElem == null) return if (data.customElem == null) return
markAsConsumed()
kotlin.runCatching { kotlin.runCatching {
data.customElem.data.read { data.customElem.data.read {
CustomMessage.load(this) CustomMessage.load(this)

View File

@ -28,6 +28,7 @@ internal class FaceProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<Face> { private class Encoder : MessageEncoder<Face> {
override suspend fun MessageEncoderContext.process(data: Face) { override suspend fun MessageEncoderContext.process(data: Face) {
markAsConsumed()
collect( collect(
if (data.id >= 260) { if (data.id >= 260) {
ImMsgBody.Elem(commonElem = data.toCommData()) ImMsgBody.Elem(commonElem = data.toCommData())
@ -67,6 +68,7 @@ internal class FaceProtocol : MessageProtocol() {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
val commonElem = data.commonElem ?: return val commonElem = data.commonElem ?: return
if (commonElem.serviceType != 33) return if (commonElem.serviceType != 33) return
markAsConsumed()
val proto = val proto =
commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer()) commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer())

View File

@ -31,6 +31,11 @@ internal class FileMessageProtocol : MessageProtocol() {
if (data.transElemInfo == null) return if (data.transElemInfo == null) return
if (data.transElemInfo.elemType != 24) return if (data.transElemInfo.elemType != 24) return
markAsConsumed()
processAlso(data)
process(data)
data.transElemInfo.elemValue.read { data.transElemInfo.elemValue.read {
// group file feed // group file feed
// 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63 // 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63

View File

@ -11,7 +11,6 @@ package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.User import net.mamoe.mirai.contact.User
import net.mamoe.mirai.internal.message.UNSUPPORTED_FLASH_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl
import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl
import net.mamoe.mirai.internal.message.image.friendImageId import net.mamoe.mirai.internal.message.image.friendImageId
@ -23,6 +22,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.FlashImage import net.mamoe.mirai.message.data.FlashImage
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.hexToBytes
internal class FlashImageProtocol : MessageProtocol() { internal class FlashImageProtocol : MessageProtocol() {
@ -36,6 +36,8 @@ internal class FlashImageProtocol : MessageProtocol() {
if (data.commonElem == null) return if (data.commonElem == null) return
if (data.commonElem.serviceType != 3) return if (data.commonElem.serviceType != 3) return
markAsConsumed()
val proto = val proto =
data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer()) data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer())
if (proto.flashTroopPic != null) { if (proto.flashTroopPic != null) {
@ -51,6 +53,8 @@ internal class FlashImageProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<FlashImage> { private class Encoder : MessageEncoder<FlashImage> {
override suspend fun MessageEncoderContext.process(data: FlashImage) { override suspend fun MessageEncoderContext.process(data: FlashImage) {
markAsConsumed()
collect(data.toJceData(contact)) collect(data.toJceData(contact))
processAlso(UNSUPPORTED_FLASH_MESSAGE_PLAIN) processAlso(UNSUPPORTED_FLASH_MESSAGE_PLAIN)
collectGeneralFlags { collectGeneralFlags {
@ -61,6 +65,7 @@ internal class FlashImageProtocol : MessageProtocol() {
private companion object { private companion object {
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes() private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes()
private val UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。")
private fun FlashImage.toJceData(messageTarget: ContactOrBot?): ImMsgBody.Elem { private fun FlashImage.toJceData(messageTarget: ContactOrBot?): ImMsgBody.Elem {
return if (messageTarget is User) { return if (messageTarget is User) {

View File

@ -1,29 +0,0 @@
/*
* 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.internal.message.protocol.MessageEncoder
import net.mamoe.mirai.internal.message.protocol.MessageEncoderContext
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.message.data.SingleMessage
internal class GeneralFlagsProtocol : MessageProtocol() {
override fun ProcessorCollector.collectProcessorsImpl() {
add(Encoder())
}
private class Encoder : MessageEncoder<SingleMessage> {
override suspend fun MessageEncoderContext.process(data: SingleMessage) {
}
}
}

View File

@ -17,18 +17,24 @@ import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.ShowImageFlag import net.mamoe.mirai.message.data.ShowImageFlag
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
internal class IgnoredMessagesProtocol : MessageProtocol() { internal class IgnoredMessagesProtocol : MessageProtocol(PRIORITY_IGNORE) {
override fun ProcessorCollector.collectProcessorsImpl() { override fun ProcessorCollector.collectProcessorsImpl() {
add(Encoder()) add(Encoder())
add(Decoder()) add(Decoder())
// 所有未处理的 Elem 都会变成 UnsupportedMessage 所有不用在这里处理
} }
private class Decoder : MessageDecoder { private class Decoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
when (data) { when {
data.elemFlags2 != null
|| data.extraInfo != null
|| data.generalFlags != null
|| data.anonGroupMsg != null
-> markAsConsumed()
} }
} }
} }
private class Encoder : MessageEncoder<SingleMessage> { private class Encoder : MessageEncoder<SingleMessage> {

View File

@ -29,15 +29,22 @@ internal class ImageProtocol : MessageProtocol() {
private class ImageDecoder : MessageDecoder { private class ImageDecoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
if (data.notOnlineImage != null) collect(OnlineFriendImageImpl(data.notOnlineImage)) markAsConsumed()
if (data.customFace != null) { when {
collect(OnlineGroupImageImpl(data.customFace)) data.notOnlineImage != null -> {
data.customFace.pbReserve.let { collect(OnlineFriendImageImpl(data.notOnlineImage))
if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) { }
collect(ShowImageFlag) data.customFace != null -> {
collect(OnlineGroupImageImpl(data.customFace))
data.customFace.pbReserve.let {
if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) {
collect(ShowImageFlag)
}
} }
} }
else -> {
markNotConsumed()
}
} }
} }
@ -45,6 +52,8 @@ internal class ImageProtocol : MessageProtocol() {
private class ImageEncoder : MessageEncoder<AbstractImage> { private class ImageEncoder : MessageEncoder<AbstractImage> {
override suspend fun MessageEncoderContext.process(data: AbstractImage) { override suspend fun MessageEncoderContext.process(data: AbstractImage) {
markAsConsumed()
when (data) { when (data) {
is OfflineGroupImage -> { is OfflineGroupImage -> {
if (contact is User) { if (contact is User) {

View File

@ -7,24 +7,6 @@
* 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
*/
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.internal.message.data.MarketFaceImpl import net.mamoe.mirai.internal.message.data.MarketFaceImpl
@ -62,6 +44,7 @@ internal class MarketFaceProtocol : MessageProtocol() {
private class DiceEncoder : MessageEncoder<Dice> { private class DiceEncoder : MessageEncoder<Dice> {
override suspend fun MessageEncoderContext.process(data: Dice) { override suspend fun MessageEncoderContext.process(data: Dice) {
markAsConsumed()
processAlso(MarketFaceImpl(data.toJceStruct())) processAlso(MarketFaceImpl(data.toJceStruct()))
} }
} }

View File

@ -17,10 +17,13 @@ import net.mamoe.mirai.message.data.content
internal class MusicShareProtocol : MessageProtocol() { internal class MusicShareProtocol : MessageProtocol() {
override fun ProcessorCollector.collectProcessorsImpl() { override fun ProcessorCollector.collectProcessorsImpl() {
add(Encoder())
// add(Decoder())
} }
private class Encoder : MessageEncoder<MusicShare> { private class Encoder : MessageEncoder<MusicShare> {
override suspend fun MessageEncoderContext.process(data: MusicShare) { override suspend fun MessageEncoderContext.process(data: MusicShare) {
markAsConsumed()
// 只有在 QuoteReply 的 source 里才会进行 MusicShare 转换, 因此可以转 PT. // 只有在 QuoteReply 的 source 里才会进行 MusicShare 转换, 因此可以转 PT.
// 发送消息时会被特殊处理 // 发送消息时会被特殊处理
processAlso(PlainText(data.content)) processAlso(PlainText(data.content))

View File

@ -9,15 +9,19 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import net.mamoe.mirai.internal.message.UNSUPPORTED_POKE_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.message.protocol.*
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
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.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.PokeMessage import net.mamoe.mirai.message.data.PokeMessage
internal class PokeMessageProtocol : MessageProtocol() { internal class PokeMessageProtocol : MessageProtocol() {
companion object {
val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。")
}
override fun ProcessorCollector.collectProcessorsImpl() { override fun ProcessorCollector.collectProcessorsImpl() {
add(Encoder()) add(Encoder())
add(Decoder()) add(Decoder())
@ -25,6 +29,7 @@ internal class PokeMessageProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<PokeMessage> { private class Encoder : MessageEncoder<PokeMessage> {
override suspend fun MessageEncoderContext.process(data: PokeMessage) { override suspend fun MessageEncoderContext.process(data: PokeMessage) {
markAsConsumed()
collect( collect(
ImMsgBody.Elem( ImMsgBody.Elem(
commonElem = ImMsgBody.CommonElem( commonElem = ImMsgBody.CommonElem(
@ -41,13 +46,13 @@ internal class PokeMessageProtocol : MessageProtocol() {
) )
processAlso(UNSUPPORTED_POKE_MESSAGE_PLAIN) processAlso(UNSUPPORTED_POKE_MESSAGE_PLAIN)
} }
} }
private class Decoder : MessageDecoder { private class Decoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
if (data.commonElem == null) return if (data.commonElem == null) return
if (data.commonElem.serviceType != 2) return if (data.commonElem.serviceType != 2) return
markAsConsumed()
val proto = val proto =
data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer()) data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer())

View File

@ -26,6 +26,7 @@ internal class PttMessageProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<PttMessage> { private class Encoder : MessageEncoder<PttMessage> {
override suspend fun MessageEncoderContext.process(data: PttMessage) { override suspend fun MessageEncoderContext.process(data: PttMessage) {
markAsConsumed()
collect( collect(
ImMsgBody.Elem( ImMsgBody.Elem(
extraInfo = ImMsgBody.ExtraInfo(flags = 16, groupMask = 1) extraInfo = ImMsgBody.ExtraInfo(flags = 16, groupMask = 1)

View File

@ -32,11 +32,16 @@ internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) {
private class Decoder : MessageDecoder { private class Decoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
if (data.srcMsg == null) return if (data.srcMsg == null) return
OfflineMessageSourceImplData( markAsConsumed()
data.srcMsg, collect(
attributes[BOT], QuoteReply(
attributes[MESSAGE_SOURCE_KIND], OfflineMessageSourceImplData(
attributes[GROUP_ID] data.srcMsg,
attributes[BOT],
attributes[MESSAGE_SOURCE_KIND],
attributes[GROUP_ID]
)
)
) )
} }
@ -45,6 +50,7 @@ internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) {
private class Encoder : MessageEncoder<QuoteReply> { private class Encoder : MessageEncoder<QuoteReply> {
override suspend fun MessageEncoderContext.process(data: QuoteReply) { override suspend fun MessageEncoderContext.process(data: QuoteReply) {
val source = data.source as? MessageSourceInternal ?: return val source = data.source as? MessageSourceInternal ?: return
markAsConsumed()
collect(ImMsgBody.Elem(srcMsg = source.toJceData())) collect(ImMsgBody.Elem(srcMsg = source.toJceData()))
if (contact is Group) { if (contact is Group) {
if (source is OnlineMessageSource.Incoming.FromGroup) { if (source is OnlineMessageSource.Incoming.FromGroup) {

View File

@ -10,7 +10,6 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.internal.message.UNSUPPORTED_MERGED_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.data.ForwardMessageInternal
import net.mamoe.mirai.internal.message.data.LightAppInternal import net.mamoe.mirai.internal.message.data.LightAppInternal
import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.LongMessageInternal
@ -32,6 +31,10 @@ import net.mamoe.mirai.utils.zip
* - [ForwardMessage] * - [ForwardMessage]
*/ */
internal class RichMessageProtocol : MessageProtocol() { internal class RichMessageProtocol : MessageProtocol() {
companion object {
val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
}
override fun ProcessorCollector.collectProcessorsImpl() { override fun ProcessorCollector.collectProcessorsImpl() {
add(RichMsgDecoder()) add(RichMsgDecoder())
add(LightAppDecoder()) add(LightAppDecoder())
@ -41,6 +44,7 @@ internal class RichMessageProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<RichMessage> { private class Encoder : MessageEncoder<RichMessage> {
override suspend fun MessageEncoderContext.process(data: RichMessage) { override suspend fun MessageEncoderContext.process(data: RichMessage) {
markAsConsumed()
val content = data.content.toByteArray().zip() val content = data.content.toByteArray().zip()
var longTextResId: String? = null var longTextResId: String? = null
when (data) { when (data) {
@ -92,7 +96,7 @@ internal class RichMessageProtocol : MessageProtocol() {
ImMsgBody.Elem( ImMsgBody.Elem(
generalFlags = ImMsgBody.GeneralFlags( generalFlags = ImMsgBody.GeneralFlags(
longTextFlag = 1, longTextFlag = 1,
longTextResid = longTextResId!!, longTextResid = longTextResId,
pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes() pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
) )
) )
@ -112,6 +116,7 @@ internal class RichMessageProtocol : MessageProtocol() {
private class LightAppDecoder : MessageDecoder { private class LightAppDecoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
val lightApp = data.lightApp ?: return val lightApp = data.lightApp ?: return
markAsConsumed()
val content = runWithBugReport("解析 lightApp", val content = runWithBugReport("解析 lightApp",
{ "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) { { "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) {

View File

@ -40,6 +40,7 @@ internal class TextProtocol : MessageProtocol() {
private class Decoder : MessageDecoder { private class Decoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
val text = data.text ?: return val text = data.text ?: return
markAsConsumed()
if (text.attr6Buf.isEmpty()) { if (text.attr6Buf.isEmpty()) {
collect(PlainText(text.str)) collect(PlainText(text.str))
} else { } else {
@ -59,16 +60,18 @@ internal class TextProtocol : MessageProtocol() {
private class PlainTextEncoder : MessageEncoder<PlainText> { private class PlainTextEncoder : MessageEncoder<PlainText> {
override suspend fun MessageEncoderContext.process(data: PlainText) { override suspend fun MessageEncoderContext.process(data: PlainText) {
markAsConsumed()
collect(ImMsgBody.Elem(text = ImMsgBody.Text(str = data.content))) collect(ImMsgBody.Elem(text = ImMsgBody.Text(str = data.content)))
} }
} }
private class AtEncoder : MessageEncoder<At> { private class AtEncoder : MessageEncoder<At> {
override suspend fun MessageEncoderContext.process(data: At) { override suspend fun MessageEncoderContext.process(data: At) {
markAsConsumed()
collected += ImMsgBody.Elem( collected += ImMsgBody.Elem(
text = data.toJceData( text = data.toJceData(
attributes[CONTACT].safeCast(), attributes[CONTACT].safeCast(),
originalMessage[MessageSource], originalMessage.sourceOrNull,
isForward, isForward,
) )
) )
@ -148,6 +151,7 @@ internal class TextProtocol : MessageProtocol() {
private class AtAllEncoder : MessageEncoder<AtAll> { private class AtAllEncoder : MessageEncoder<AtAll> {
override suspend fun MessageEncoderContext.process(data: AtAll) { override suspend fun MessageEncoderContext.process(data: AtAll) {
markAsConsumed()
collect(jceData) collect(jceData)
} }

View File

@ -13,7 +13,7 @@ import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl
import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.message.protocol.*
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
internal class UnsupportedMessageProtocol : MessageProtocol(priority = 100u) { internal class UnsupportedMessageProtocol : MessageProtocol(priority = PRIORITY_UNSUPPORTED) {
override fun ProcessorCollector.collectProcessorsImpl() { override fun ProcessorCollector.collectProcessorsImpl() {
add(Decoder()) add(Decoder())
add(Encoder()) add(Encoder())
@ -21,13 +21,16 @@ internal class UnsupportedMessageProtocol : MessageProtocol(priority = 100u) {
private class Decoder : MessageDecoder { private class Decoder : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
val struct = UnsupportedMessageImpl(data).takeIf { it.struct.isNotEmpty() } ?: return markAsConsumed()
val struct = UnsupportedMessageImpl(data)
if (struct.struct.isEmpty()) return
collect(struct) collect(struct)
} }
} }
private class Encoder : MessageEncoder<UnsupportedMessageImpl> { private class Encoder : MessageEncoder<UnsupportedMessageImpl> {
override suspend fun MessageEncoderContext.process(data: UnsupportedMessageImpl) { override suspend fun MessageEncoderContext.process(data: UnsupportedMessageImpl) {
markAsConsumed()
collect(data.structElem) collect(data.structElem)
} }
} }

View File

@ -24,6 +24,7 @@ internal class VipFaceProtocol : MessageProtocol() {
private class Encoder : MessageEncoder<VipFace> { private class Encoder : MessageEncoder<VipFace> {
override suspend fun MessageEncoderContext.process(data: VipFace) { override suspend fun MessageEncoderContext.process(data: VipFace) {
markAsConsumed()
processAlso(PlainText(data.contentToString())) processAlso(PlainText(data.contentToString()))
} }
} }
@ -32,6 +33,7 @@ internal class VipFaceProtocol : MessageProtocol() {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
if (data.commonElem == null) return if (data.commonElem == null) return
if (data.commonElem.serviceType != 23) return if (data.commonElem.serviceType != 23) return
markAsConsumed()
val proto = val proto =
data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer()) data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer())

View File

@ -1,20 +1,23 @@
/* /*
* Copyright 2019-2021 Mamoe Technologies and contributors. * Copyright 2019-2022 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* *
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
package net.mamoe.mirai.internal.network.component package net.mamoe.mirai.internal.network.component
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import kotlin.reflect.* import kotlin.reflect.*
import kotlin.reflect.full.allSupertypes import kotlin.reflect.full.allSupertypes
/** /**
* A key for specific component [T]. Components are not polymorphic. * A key for specific component [T]. Components are not polymorphic.
* *
* Most components locate in `net.mamoe.mirai.internal.network.components` while some like [MessageProtocolFacade] don't.
*
* @param T is a type hint. * @param T is a type hint.
*/ */
internal interface ComponentKey<T : Any> { internal interface ComponentKey<T : Any> {

View File

@ -28,10 +28,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushTransMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushTransMsg
import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.network.toPacket
import net.mamoe.mirai.internal.pipeline.AbstractProcessorPipeline import net.mamoe.mirai.internal.pipeline.*
import net.mamoe.mirai.internal.pipeline.Processor
import net.mamoe.mirai.internal.pipeline.ProcessorPipeline
import net.mamoe.mirai.internal.pipeline.ProcessorPipelineContext
import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.ProtocolStruct
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -82,7 +79,9 @@ internal open class NoticeProcessorPipelineImpl protected constructor(
private val bot: QQAndroidBot, private val bot: QQAndroidBot,
traceLogging: MiraiLogger = defaultTraceLogging, traceLogging: MiraiLogger = defaultTraceLogging,
) : NoticeProcessorPipeline, ) : NoticeProcessorPipeline,
AbstractProcessorPipeline<NoticeProcessor, NoticePipelineContext, ProtocolStruct, Packet>(traceLogging) { AbstractProcessorPipeline<NoticeProcessor, NoticePipelineContext, ProtocolStruct, Packet>(
PipelineConfiguration(stopWhenConsumed = false), traceLogging
) {
open inner class ContextImpl( open inner class ContextImpl(
attributes: TypeSafeMap, attributes: TypeSafeMap,

View File

@ -1,10 +1,10 @@
/* /*
* Copyright 2019-2021 Mamoe Technologies and contributors. * Copyright 2019-2022 Mamoe Technologies and contributors.
* *
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
* *
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/dev/LICENSE
*/ */
package net.mamoe.mirai.internal.network.protocol.data.proto package net.mamoe.mirai.internal.network.protocol.data.proto
@ -14,6 +14,7 @@ import kotlinx.serialization.protobuf.ProtoIntegerType
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import kotlinx.serialization.protobuf.ProtoType import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.ProtoBuf
import net.mamoe.mirai.internal.utils.structureToString
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
@Serializable @Serializable
@ -380,7 +381,11 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(51) @JvmField val lightApp: LightAppElem? = null, @ProtoNumber(51) @JvmField val lightApp: LightAppElem? = null,
@ProtoNumber(52) @JvmField val eimInfo: EIMInfo? = null, @ProtoNumber(52) @JvmField val eimInfo: EIMInfo? = null,
@ProtoNumber(53) @JvmField val commonElem: CommonElem? = null, @ProtoNumber(53) @JvmField val commonElem: CommonElem? = null,
) : ProtoBuf ) : ProtoBuf {
override fun toString(): String {
return this.structureToString()
}
}
@Serializable @Serializable
internal class ElemFlags( internal class ElemFlags(

View File

@ -49,6 +49,10 @@ internal value class MutableProcessResult<R>(
internal interface PipelineConsumptionMarker internal interface PipelineConsumptionMarker
internal interface ProcessorPipelineContext<D, R> { internal interface ProcessorPipelineContext<D, R> {
/**
* Child processes ([processAlso]) will inherit [attributes] from its parent, while any other properties from the context will not.
*/
val attributes: TypeSafeMap val attributes: TypeSafeMap
val collected: MutableProcessResult<R> val collected: MutableProcessResult<R>
@ -135,11 +139,16 @@ internal abstract class AbstractProcessorPipelineContext<D, R>(
} }
} }
internal class PipelineConfiguration(
var stopWhenConsumed: Boolean
)
internal abstract class AbstractProcessorPipeline<P : Processor<C, D>, C : ProcessorPipelineContext<D, R>, D, R> internal abstract class AbstractProcessorPipeline<P : Processor<C, D>, C : ProcessorPipelineContext<D, R>, D, R>
protected constructor( protected constructor(
val configuration: PipelineConfiguration,
val traceLogging: MiraiLogger, val traceLogging: MiraiLogger,
) : ProcessorPipeline<P, D, R> { ) : ProcessorPipeline<P, D, R> {
constructor() : this(SilentLogger) constructor(configuration: PipelineConfiguration) : this(configuration, SilentLogger)
/** /**
* Must be ordered * Must be ordered
@ -199,6 +208,12 @@ protected constructor(
}, success=${result.isSuccess}, consumed=${context.isConsumed}, diff=$diffPackets" }, success=${result.isSuccess}, consumed=${context.isConsumed}, diff=$diffPackets"
} }
} }
if (context.isConsumed && configuration.stopWhenConsumed) {
traceLogging.info { "stopWhenConsumed=true, stopped." }
break
}
} }
return context.collected.data return context.collected.data
} }

View File

@ -0,0 +1,58 @@
/*
* 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.utils
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.Continuation
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
/**
* Runs the [coroutine] directly in current thread, **expecting no suspension**.
*/
internal fun <R> runCoroutineInPlace(coroutine: suspend () -> R): R {
var lateResult: CompletableDeferred<R>? = null
val result = coroutine.startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) { r ->
val deferred: CompletableDeferred<R>? = lateResult
@Suppress("KotlinConstantConditions")
if (deferred != null) {
r.fold(onSuccess = { deferred.complete(it) }, onFailure = { deferred.completeExceptionally(it) })
} else {
if (logger.isErrorEnabled) {
logger.error(IllegalStateException("runCoroutineInPlace reached an unexpected state: coroutine did not finish. ()"))
}
}
})
if (result != COROUTINE_SUSPENDED) {
@Suppress("UNCHECKED_CAST")
return result as R
}
lateResult = CompletableDeferred()
if (logger.isErrorEnabled) {
logger.error(IllegalStateException("runCoroutineInPlace reached an unexpected state: coroutine did not finish."))
}
return runBlocking { lateResult.await() }
}
private val myStubFailure = Exception()
private class RunCoroutineInPlace
private val logger by lazy {
MiraiLogger.Factory.create(RunCoroutineInPlace::class)
}

View File

@ -0,0 +1,24 @@
#
# 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
#
net.mamoe.mirai.internal.message.protocol.impl.CustomMessageProtocol
net.mamoe.mirai.internal.message.protocol.impl.FaceProtocol
net.mamoe.mirai.internal.message.protocol.impl.FileMessageProtocol
net.mamoe.mirai.internal.message.protocol.impl.FlashImageProtocol
net.mamoe.mirai.internal.message.protocol.impl.IgnoredMessagesProtocol
net.mamoe.mirai.internal.message.protocol.impl.ImageProtocol
net.mamoe.mirai.internal.message.protocol.impl.MarketFaceProtocol
net.mamoe.mirai.internal.message.protocol.impl.MusicShareProtocol
net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol
net.mamoe.mirai.internal.message.protocol.impl.PttMessageProtocol
net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol
net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol
net.mamoe.mirai.internal.message.protocol.impl.TextProtocol
net.mamoe.mirai.internal.message.protocol.impl.UnsupportedMessageProtocol
net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol

View File

@ -12,6 +12,8 @@ package net.mamoe.mirai.internal.message
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
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
import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN
import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@ -0,0 +1,42 @@
/*
* 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
import net.mamoe.mirai.internal.test.AbstractTest
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
internal class MessageProtocolFacadeTest : AbstractTest() {
@Test
fun `can load`() {
assertEquals(
"""
QuoteReplyProtocol
CustomMessageProtocol
FileMessageProtocol
FlashImageProtocol
FaceProtocol
ImageProtocol
MarketFaceProtocol
MusicShareProtocol
PokeMessageProtocol
IgnoredMessagesProtocol
PttMessageProtocol
RichMessageProtocol
TextProtocol
UnsupportedMessageProtocol
VipFaceProtocol
""".trimIndent(),
MessageProtocolFacadeImpl().loaded.joinToString("\n") { it::class.simpleName.toString() }
)
}
}

View File

@ -9,16 +9,29 @@
package net.mamoe.mirai.internal.message.protocol.impl package net.mamoe.mirai.internal.message.protocol.impl
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.contact.inferMessageSourceKind
import net.mamoe.mirai.internal.message.protocol.* import net.mamoe.mirai.internal.message.protocol.*
import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest 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.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.internal.notice.processors.GroupExtensions
import net.mamoe.mirai.message.data.MessageChain 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 org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import kotlin.test.asserter import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest() { internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions {
protected abstract val protocol: MessageProtocol
protected var defaultTarget: ContactOrBot? = null
private var decoderLoggerEnabled = false private var decoderLoggerEnabled = false
private var encoderLoggerEnabled = false private var encoderLoggerEnabled = false
@ -50,45 +63,39 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
protocol: MessageProtocol, protocol: MessageProtocol,
encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem> encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem>
) { ) {
assertEquals( asserter.assertEquals(
expectedStruct, expectedStruct,
facadeOf(protocol).encode(), facadeOf(protocol).encode(),
message = "Failed to check single Protocol" message = "Failed to check single Protocol"
) )
assertEquals( asserter.assertEquals(
expectedStruct, expectedStruct,
MessageProtocolFacade.INSTANCE.encode(), MessageProtocolFacade.INSTANCE.encode(),
message = "Failed to check with all protocols" message = "Failed to check with all protocols"
) )
} }
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") var asserter: EqualityAsserter = EqualityAsserter.OrdinaryThenStructural
private fun <@kotlin.internal.OnlyInputTypes T> assertEquals(
expected: List<T>, fun useOrdinaryEquality() {
actual: List<T>, asserter = EqualityAsserter.Ordinary
message: String? = null }
) {
if (expected.size == 1 && actual.size == 1) { fun useStructuralEquality() {
asserter.assertEquals(message, expected.single().structureToString(), actual.single().structureToString()) asserter = EqualityAsserter.Structural
} else {
asserter.assertEquals(
message,
expected.joinToString { it.structureToString() },
actual.joinToString { it.structureToString() })
}
} }
protected fun doDecoderChecks( protected fun doDecoderChecks(
expectedChain: MessageChain, expectedChain: MessageChain,
protocol: MessageProtocol, protocol: MessageProtocol = this.protocol,
decode: MessageProtocolFacade.() -> MessageChain decode: MessageProtocolFacade.() -> MessageChain
) { ) {
assertEquals( asserter.assertEquals(
expectedChain.toList(), expectedChain.toList(),
facadeOf(protocol).decode().toList(), facadeOf(protocol).decode().toList(),
message = "Failed to check single Protocol" message = "Failed to check single Protocol"
) )
assertEquals( asserter.assertEquals(
expectedChain.toList(), expectedChain.toList(),
MessageProtocolFacade.INSTANCE.decode().toList(), MessageProtocolFacade.INSTANCE.decode().toList(),
message = "Failed to check with all protocols" message = "Failed to check with all protocols"
@ -96,8 +103,98 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
} }
protected fun doEncoderChecks( protected fun doEncoderChecks(
expectedStruct: ImMsgBody.Elem, vararg expectedStruct: ImMsgBody.Elem,
protocol: MessageProtocol, protocol: MessageProtocol = this.protocol,
encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem> encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem>
): Unit = doEncoderChecks(mutableListOf(expectedStruct), protocol, encode) ): Unit = doEncoderChecks(expectedStruct.toList(), protocol, encode)
inner class ChecksBuilder {
var elems: MutableList<ImMsgBody.Elem> = mutableListOf()
var messages: MessageChainBuilder = MessageChainBuilder()
var groupIdOrZero: Long = 0
var messageSourceKind: MessageSourceKind = MessageSourceKind.GROUP
var target: ContactOrBot? = defaultTarget
var withGeneralFlags = true
var isForward = false
fun elem(vararg elem: ImMsgBody.Elem) {
elems.addAll(elem)
}
fun message(vararg message: SingleMessage) {
messages.addAll(message)
}
fun target(target: ContactOrBot?) {
this.target = target
if (target != null) {
messageSourceKind = target.inferMessageSourceKind()
}
if (target is Group) {
groupIdOrZero = target.id
}
}
fun forward() {
this.isForward = true
}
fun build() = ChecksConfiguration(
elems.toList(),
messages.build(),
groupIdOrZero,
messageSourceKind,
target,
withGeneralFlags,
isForward
)
}
class ChecksConfiguration(
val elems: List<ImMsgBody.Elem>,
val messageChain: MessageChain,
val groupIdOrZero: Long,
val messageSourceKind: MessageSourceKind,
val target: ContactOrBot?,
val withGeneralFlags: Boolean,
val isForward: Boolean,
)
@Suppress("DeferredIsResult")
protected fun buildChecks(
builderAction: ChecksBuilder.() -> Unit,
): Deferred<ChecksConfiguration> { // IDE will warn you if you forget to call .do
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return CompletableDeferred(ChecksBuilder().apply(builderAction).build())
}
@OptIn(ExperimentalCoroutinesApi::class)
protected open fun Deferred<ChecksConfiguration>.doEncoderChecks() {
val config = this.getCompleted()
doEncoderChecks(config.elems, protocol) {
encode(
config.messageChain,
config.target,
withGeneralFlags = config.withGeneralFlags,
isForward = config.isForward
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
protected open fun Deferred<ChecksConfiguration>.doDecoderChecks() {
val config = this.getCompleted()
doDecoderChecks(config.messageChain, protocol) {
decode(config.elems, config.groupIdOrZero, config.messageSourceKind, bot)
}
}
protected open fun Deferred<ChecksConfiguration>.doBothChecks() {
doEncoderChecks()
doDecoderChecks()
}
} }

View File

@ -0,0 +1,95 @@
/*
* 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.internal.utils.structureToString
import net.mamoe.mirai.internal.utils.structureToStringIfAvailable
import kotlin.test.assertNotNull
import kotlin.test.asserter
internal interface EqualityAsserter {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
fun <@kotlin.internal.OnlyInputTypes T> assertEquals(
expected: List<T>,
actual: List<T>,
message: String? = null
)
object Ordinary : EqualityAsserter {
override fun <T> assertEquals(expected: List<T>, actual: List<T>, message: String?) {
if (expected.size == actual.size) {
if (expected.size == 1 && expected.singleOrNull() == actual.singleOrNull()) {
return asserter.assertEquals(message, expected.single(), actual.single())
}
if (expected.zip(actual).all { (e, a) -> e == a }) return
asserter.assertEquals(message, expected, actual)
} else {
asserter.assertEquals(message, expected, actual)
}
}
}
object Structural : EqualityAsserter {
override fun <T> assertEquals(expected: List<T>, actual: List<T>, message: String?) {
if (expected.size == 1 && actual.size == 1) {
val e = expected.single()
val a = actual.single()
if (a == null || e == null) {
asserter.assertEquals(
"[Null] $message",
structureToStringOrOrdinaryString(e),
structureToStringOrOrdinaryString(a)
)
assertNotNull(a, message)
assertNotNull(e, message)
}
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
if (!e!!::class.isInstance(a) && !a!!::class.isInstance(e)) {
asserter.assertEquals(
"[Incompatible type] $message",
structureToStringOrOrdinaryString(e),
structureToStringOrOrdinaryString(a)
)
return
}
asserter.assertEquals(
message,
structureToStringOrOrdinaryString(e),
structureToStringOrOrdinaryString(a)
)
} else {
asserter.assertEquals(
message,
expected.joinToString { structureToStringOrOrdinaryString(it) },
actual.joinToString { structureToStringOrOrdinaryString(it) })
}
}
private fun <T> structureToStringOrOrdinaryString(it: T): String =
it.structureToString().ifBlank {
it.structureToStringIfAvailable() ?: error("structureToString is not available")
}
}
object OrdinaryThenStructural : EqualityAsserter {
override fun <T> assertEquals(expected: List<T>, actual: List<T>, message: String?) {
try {
Ordinary.assertEquals(expected, actual, message)
return
} catch (e: AssertionError) {
Structural.assertEquals(expected, actual, message)
return
}
}
}
}

View File

@ -9,6 +9,7 @@
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.message.data.Face import net.mamoe.mirai.message.data.Face
import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.message.data.messageChainOf
@ -16,6 +17,7 @@ import net.mamoe.mirai.utils.hexToBytes
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
internal class FaceProtocolTest : AbstractMessageProtocolTest() { internal class FaceProtocolTest : AbstractMessageProtocolTest() {
override val protocol: MessageProtocol = FaceProtocol()
@Test @Test
fun `can encode`() { fun `can encode`() {
@ -27,7 +29,6 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
buf = "00 01 00 04 52 CC F5 D0".hexToBytes(), buf = "00 01 00 04 52 CC F5 D0".hexToBytes(),
), ),
), ),
FaceProtocol()
) { ) {
encode( encode(
messageChainOf(Face(Face.PIE_ZUI)), messageChainOf(Face(Face.PIE_ZUI)),
@ -40,7 +41,6 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
fun `can decode`() { fun `can decode`() {
doDecoderChecks( doDecoderChecks(
messageChainOf(Face(Face.YIN_XIAN)), messageChainOf(Face(Face.YIN_XIAN)),
FaceProtocol()
) { ) {
decode( decode(
listOf( listOf(
@ -58,4 +58,5 @@ internal class FaceProtocolTest : AbstractMessageProtocolTest() {
} }
} }
} }

View File

@ -0,0 +1,118 @@
/*
* 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.message.data.At
import net.mamoe.mirai.message.data.AtAll
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.utils.hexToBytes
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
internal class TextProtocolTest : AbstractMessageProtocolTest() {
override val protocol = TextProtocol()
@BeforeEach
fun `init group`() {
defaultTarget = bot.addGroup(123, 1230003).apply {
addMember(1230003, "user3", MemberPermission.OWNER)
}
}
@Test
fun `test PlainText`() {
buildChecks {
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "hello",
),
)
)
message(PlainText("hello"))
}.doBothChecks()
}
@Test
fun `test AtAll`() {
buildChecks {
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "@全体成员",
attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes(),
),
)
)
message(AtAll)
}.doBothChecks()
}
@Test
fun `AtAll auto append spaces`() {
buildChecks {
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "@全体成员",
attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes(),
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "Hi",
),
),
)
message(AtAll, PlainText("Hi"))
}.doEncoderChecks()
}
@Test
fun `test At`() {
buildChecks {
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "@user3",
attr6Buf = "00 01 00 00 00 06 00 00 12 C4 B3 00 00".hexToBytes(),
),
)
)
message(At(1230003))
}.doBothChecks()
}
@Test
fun `At auto append spaces`() {
buildChecks {
elem(
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = "@user3",
attr6Buf = "00 01 00 00 00 06 00 00 12 C4 B3 00 00".hexToBytes(),
),
),
net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem(
text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text(
str = " ",
),
),
)
message(At(1230003))
message(PlainText(" "))
target(bot.addGroup(123, 1230003).apply {
addMember(1230003, "user3", MemberPermission.OWNER)
})
}.doBothChecks()
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.testFramework.message.protocol
import net.mamoe.mirai.internal.message.protocol.MessageDecoder
import net.mamoe.mirai.internal.message.protocol.MessageDecoderContext
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer
import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer.Companion.generateAndDesensitize
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.debug
internal class MessageDecodingRecorder(
private val logger: MiraiLogger = MiraiLogger.Factory.create(MessageDecodingRecorder::class)
) : MessageDecoder {
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
logger.debug {
"\n" + ValueDescAnalyzer.generateAndDesensitize(data)
}
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.bootstrap
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.internal.asQQAndroidBot
import net.mamoe.mirai.internal.message.protocol.MessageDecoderProcessor
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer
import net.mamoe.mirai.internal.testFramework.message.protocol.MessageDecodingRecorder
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.readResource
import net.mamoe.yamlkt.Yaml
import kotlin.concurrent.thread
suspend fun main() {
Runtime.getRuntime().addShutdownHook(thread(start = false) {
Bot.instances.forEach {
it.close()
}
})
Desensitizer.local.desensitize("") // verify rules
val account = Yaml.decodeFromString(LocalAccount.serializer(), readResource("local.account.yml"))
val bot = BotFactory.newBot(account.id, account.password) {
enableContactCache()
fileBasedDeviceInfo("local.device.json")
protocol = BotConfiguration.MiraiProtocol.ANDROID_PHONE
}.asQQAndroidBot()
MessageProtocolFacade.decoderPipeline.registerBefore(MessageDecoderProcessor(MessageDecodingRecorder()))
bot.login()
bot.join()
}

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 许可证的约束, 可以在以下链接找到该许可证. * 此源代码的使用受 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. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.