All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.message.protocol.impl.RichMessageProtocol.kt Maven / Gradle / Ivy

There is a newer version: 2.16.0
Show newest version
/*
 * 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.io.core.toByteArray
import net.mamoe.mirai.internal.message.data.ForwardMessageInternal
import net.mamoe.mirai.internal.message.data.LightAppInternal
import net.mamoe.mirai.internal.message.data.LongMessageInternal
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.collectGeneralFlags
import net.mamoe.mirai.internal.message.runWithBugReport
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import net.mamoe.mirai.utils.unzip
import net.mamoe.mirai.utils.zip

/**
 * Handles:
 * - [RichMessage]
 * - [LongMessageInternal]
 * - [ServiceMessage]
 * - [ForwardMessage]
 */
internal class RichMessageProtocol : MessageProtocol() {
    companion object {
        val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
    }

    override fun ProcessorCollector.collectProcessorsImpl() {
        add(RichMsgDecoder())
        add(LightAppDecoder())

        add(Encoder())
    }

    private class Encoder : MessageEncoder {
        override suspend fun MessageEncoderContext.process(data: RichMessage) {
            markAsConsumed()
            val content = data.content.toByteArray().zip()
            var longTextResId: String? = null
            when (data) {
                is ForwardMessageInternal -> {
                    collect(
                        ImMsgBody.Elem(
                            richMsg = ImMsgBody.RichMsg(
                                serviceId = data.serviceId, // ok
                                template1 = byteArrayOf(1) + content
                            )
                        )
                    )
                    // transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
                }
                is LongMessageInternal -> {
                    collect(
                        ImMsgBody.Elem(
                            richMsg = ImMsgBody.RichMsg(
                                serviceId = data.serviceId, // ok
                                template1 = byteArrayOf(1) + content
                            )
                        )
                    )
                    processAlso(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
                    longTextResId = data.resId
                }
                is LightApp -> collect(
                    ImMsgBody.Elem(
                        lightApp = ImMsgBody.LightAppElem(
                            data = byteArrayOf(1) + content
                        )
                    )
                )
                else -> collect(
                    ImMsgBody.Elem(
                        richMsg = ImMsgBody.RichMsg(
                            serviceId = when (data) {
                                is ServiceMessage -> data.serviceId
                                else -> error("unsupported RichMessage: ${data::class.simpleName}")
                            },
                            template1 = byteArrayOf(1) + content
                        )
                    )
                )
            }

            collectGeneralFlags {
                if (longTextResId != null) {
                    ImMsgBody.Elem(
                        generalFlags = ImMsgBody.GeneralFlags(
                            longTextFlag = 1,
                            longTextResid = longTextResId,
                            pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()
                        )
                    )
                } else {
                    // 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
                    ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_RICH_MESSAGE))
                }
            }
        }

        private companion object {
            private val PB_RESERVE_FOR_RICH_MESSAGE =
                "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes()
        }
    }

    private class LightAppDecoder : MessageDecoder {
        override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
            val lightApp = data.lightApp ?: return
            markAsConsumed()

            val content = runWithBugReport("解析 lightApp",
                { "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) {
                when (lightApp.data[0].toInt()) {
                    0 -> lightApp.data.decodeToString(startIndex = 1)
                    1 -> lightApp.data.unzip(1).decodeToString()
                    else -> error("unknown compression flag=${lightApp.data[0]}")
                }
            }

            collect(LightAppInternal(content))
        }

    }

    private class RichMsgDecoder : MessageDecoder {
        override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
            if (data.richMsg == null) return

            val richMsg = data.richMsg

            val content = runWithBugReport("解析 richMsg", { richMsg.template1.toUHexString() }) {
                when (richMsg.template1[0].toInt()) {
                    0 -> richMsg.template1.decodeToString(startIndex = 1)
                    1 -> richMsg.template1.unzip(1).decodeToString()
                    else -> error("unknown compression flag=${richMsg.template1[0]}")
                }
            }

            fun findStringProperty(name: String): String {
                return content.substringAfter("$name=\"", "").substringBefore("\"", "")
            }

            val serviceId = when (val sid = richMsg.serviceId) {
                0 -> {
                    val serviceIdStr = findStringProperty("serviceID")
                    if (serviceIdStr.isEmpty() || serviceIdStr.isBlank()) {
                        0
                    } else {
                        serviceIdStr.toIntOrNull() ?: 0
                    }
                }
                else -> sid
            }
            when (serviceId) {
                // 5: 使用微博长图转换功能分享到QQ群
                /*
                            
                             */
                /**
                 * json?
                 */
                1 -> @Suppress("DEPRECATION_ERROR")
                collect(SimpleServiceMessage(1, content))
                /**
                 * [LongMessageInternal], [ForwardMessage]
                 */
                35 -> {

                    val resId = findStringProperty("m_resid")
                    val fileName = findStringProperty("m_fileName").takeIf { it.isNotEmpty() }

                    val msg = if (resId.isEmpty()) {
                        // Nested ForwardMessage
                        if (fileName != null && findStringProperty("action") == "viewMultiMsg") {
                            ForwardMessageInternal(content, null, fileName)
                        } else {
                            SimpleServiceMessage(35, content)
                        }
                    } else when (findStringProperty("multiMsgFlag").toIntOrNull()) {
                        1 -> LongMessageInternal(content, resId)
                        0 -> ForwardMessageInternal(content, resId, fileName)
                        else -> {
                            // from PC QQ
                            if (findStringProperty("action") == "viewMultiMsg") {
                                ForwardMessageInternal(content, resId, fileName)
                            } else {
                                SimpleServiceMessage(35, content)
                            }
                        }
                    }

                    collect(msg)
                }

                // 104 新群员入群的消息
                else -> {
                    collect(SimpleServiceMessage(serviceId, content))
                }
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy