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

com.simbot.component.mirai.utils.kt Maven / Gradle / Ivy

There is a newer version: 1.11.0-1.17-Final
Show newest version
/*
 * Copyright (c) 2020. ForteScarlet All rights reserved.
 * Project  component-mirai (Codes other than Mirai)
 * File     utils.kt (Codes other than Mirai)
 *
 * You can contact the author through the following channels:
 * github https://github.com/ForteScarlet
 * gitee  https://gitee.com/ForteScarlet
 * email  [email protected]
 * QQ     1149159218
 *
 * The Mirai code is copyrighted by mamoe-mirai
 * you can see mirai at https://github.com/mamoe/mirai
 *
 *
 */

package com.simbot.component.mirai

import cn.hutool.core.io.FileUtil
import com.simplerobot.modules.utils.*
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.AtAll
import net.mamoe.mirai.message.uploadImage
import java.net.URL
import java.util.function.BiFunction


/**
 * 发送消息之前,会将Message通过此处进行处理。
 * 此处可解析部分CQ码并转化为Message
 * 然后发送此消息
 */
suspend fun  C.sendMsg(msg: String): MessageReceipt {
    return this.sendMessage(msg.toWholeMessage(this))
}


/**
 * 字符串解析为[Message]
 * 一般解析其中的CQ码
 * 等核心支持CAT码中转后转化为CAT码
 */
fun String.toWholeMessage(contact: Contact): Message {
    // 切割,解析CQ码并拼接最终结果
   return KQCodeUtils.split(this).asSequence().map {
       if(it.trim().startsWith("[CQ:")){
           // 如果是CQ码,转化为KQCode并进行处理
           KQCode.of(it).toMessage(contact)
       }else{
           it.toMessage()
       }
   }.reduce { acc, msg -> acc + msg }
}

/**
 * KQCode转化为Message对象
 */
fun KQCode.toMessage(contact: Contact): Message {
    // 判断类型,有些东西有可能并不存在与CQ码规范中,例如XML
    return when(this.type) {
        //region CQ码解析为Message
        //region at
        "at" -> {
            val id = this["qq"] ?: this["at"] ?: throw CQCodeParamNullPointerException("at", "qq", "at")
            if(id == "all") {
                AtAll
            } else {
                when(contact) {
                    is Member -> {
                        At(contact)
                    }
                    is Friend -> {
                        "@${contact.nick}".toMessage()
                    }
                    is Group -> {
                        At(contact[id.toLong()])
                    }
                    else -> "@$id".toMessage()
                }
            }
        }
        //endregion

        //region face
        "face" ->  Face((this["id"] ?: this["face"])!!.toInt())
        //endregion


        //region image
        "image" -> {
            // image 类型的CQ码,参数一般是file, destruct
            val file = this["file"] ?: this["image"] ?: throw CQCodeParamNullPointerException("image", "file", "image")

            // file文件,可能是本地的或者网络的
            val image: Image = if(file.startsWith("http")){
                // 网络图片 阻塞上传
                runBlocking {
                    contact.uploadImage(URL(file)).also {  ImageCache[file] = it }
                }
            }else{
                // 先查询缓存中有没有这个东西
                // 本地文件
               ImageCache[file] ?: runBlocking {
//                   contact.uploadImage(File(file)).also { ImageCache[file] = it }
                   contact.uploadImage(FileUtil.file(file)).also { ImageCache[file] = it }
               }
            }
            // 如果是闪照则转化
            return if(this["destruct"] == "true"){
                image.flash()
            }else{
                image
            }
        }
        //endregion

        //region record
        "voice", "record" -> {
            // 似乎暂不支持,不过可转发
            "[语音]".toMessage()
        }
        //endregion


        //region rps 猜拳
        "rps" -> {
            // 似乎也不支持猜拳
            "[猜拳]".toMessage()
        }
        //endregion

        //region dice 骰子
        "dice" -> {
            // 似乎也..
            "[骰子]".toMessage()
        }
        //endregion

        //region shake 戳一戳
        "shake" -> {
            // 戳一戳进行扩展,可多解析'type'参数与'id'参数。
            // 如果没有type,直接返回戳一戳
            val type = this["type"]?.toInt() ?: return PokeMessage.Poke
            val id = this["id"]?.toInt() ?: -1

            // 尝试寻找对应的Poke,找不到则返回戳一戳
            return PokeMessage.values.find { it.type == type && it.id == id } ?: PokeMessage.Poke
        }
        //endregion

        //region anonymous
        "anonymous" -> {
            // 匿名消息,不进行解析
            EmptyMessageChain
        }
        //endregion

        //region music
        "music" -> {
            // 音乐,就是分享,应该归类于xml
            // 参数:"type", "id", "style*"
            // 或者:"type", "url", "audio", "title", "content*", "image*"
            val type = this["type"]
            // TODO 解析music
            "[${type}音乐]".toMessage()
        }
        //endregion

        //region share
        "share" -> {
            // 分享
            // 参数:"url", "title", "content*", "image*"
            val type = this["url"]
            val title = this["title"]
            // TODO 解析share
            "$title: $type".toMessage()
        }
        //endregion

        //region emoji
        "emoji" -> {
            // emoji, 基本用不到
            val id = this["id"] ?: ""
            "emoji($id)".toMessage()
        }
        //endregion


        //region location
        "location" -> {
            // 地点 "lat", "lon", "title", "content"
            val lat = this["lat"] ?: ""
            val lon = this["lon"] ?: ""
            val title = this["title"] ?: ""
            val content = this["content"] ?: ""
            "位置($lat,$lon)[$title]:$content".toMessage()
        }
        //endregion

        //region sign
        "sign" ->  "[签到]".toMessage()

        //endregion

        //region show
        "show" -> EmptyMessageChain
        //endregion


        //region contact
        "contact" -> {
            // TODO 联系人分享 可改成xml
            // ype一般可能是qq或者group
            // [CQ:contact,id=1234546,type=qq]
            val id = this["id"] ?: return EmptyMessageChain

            val typeName = when(this["type"]){
                "qq" -> "好友分享"
                "group" -> "群聊分享"
                else -> "其他分享"
            }
            "$typeName: $id".toMessage()
        }
        //endregion

        //region xml message
        //对XML类型的CQ码做解析
        "xml" -> {
            val xmlCode = this
            // 解析的参数
//            val action = this["action"] ?: "plugin"
            val flag: Int = this["flag"]?.toInt() ?: 3
            val url = this["url"] ?: ""
            val sourceName = this["sourceName"] ?: ""
            val sourceIconURL = this["sourceIconURL"] ?: ""

            // 构建xml
            return buildXmlMessage(60) {
                // action
                xmlCode["action"]?.also { this.action = it }
                // 一般为点击这条消息后跳转的链接
                xmlCode["actionData"]?.also { this.actionData = it }
                /*
                   摘要, 在官方客户端内消息列表中显示
                 */
                xmlCode["brief"]?.also { this.brief = it }
                xmlCode["flag"]?.also { this.flag = it.toInt() }
                xmlCode["url"]?.also { this.url = it }
                // sourceName 好像是名称
                xmlCode["sourceName"]?.also { this.sourceName = it }
                // sourceIconURL 好像是图标
                xmlCode["sourceIconURL"]?.also { this.sourceIconURL = it }

                // builder
//                val keys = xmlCode.params.keys

                this.item {
                    xmlCode["bg"]?.also { this.bg = it.toInt() }
                    xmlCode["layout"]?.also { this.layout = it.toInt() }
                    // picture(coverUrl: String)
                    xmlCode["picture_coverUrl"]?.also { this.picture(it) }
                    // summary(text: String, color: String = "#000000")
                    xmlCode["summary_text"]?.also {
                        val color: String = xmlCode["summary_color"] ?: "#000000"
                        this.summary(it, color)
                    }
                    // title(text: String, size: Int = 25, color: String = "#000000")
                    xmlCode["title_text"]?.also {
                        val size: Int = xmlCode["title_size"]?.toInt() ?: 25
                        val color: String = xmlCode["title_color"] ?: "#000000"
                        this.title(it, size, color)
                    }

                }

            }
        }
        //endregion


        //region lightApp小程序 & json
        // 一般都是json消息
        "app","json" -> {
            val content: String = this["content"] ?: "{}"
            LightApp(content)
        }
        //endregion


        //region rich 或 service, 对应serviceMessage。
        "rich", "service" -> {
            val content: String = this["content"] ?: "{}"
            // 如果没有serviceId,认为其为lightApp
            val serviceId: Int = this["serviceId"]?.toInt() ?: return LightApp(content)
            ServiceMessage(serviceId, content)
        }
        //endregion

        //region quote
        // 引用回复
        "quote" -> {
            val key = this["id"] ?: this["quote"] ?: throw CQCodeParamNullPointerException("quote", "id")
            val source = RecallCache.get(key, contact.bot.id) ?: return EmptyMessageChain
            QuoteReply(source)
        }
        //endregion



        //region else
        else -> {
            val handler = CQCodeParsingHandler[this.type]
            return if(handler != null){
                handler(this, contact)
            }else{
                this.toString().toMessage()
            }
        }
        //endregion
        //endregion


    }


}

/**
 * 转化函数
 */
@FunctionalInterface
interface CQCodeHandler: (KQCode, Contact) -> Message, BiFunction {
    @JvmDefault
    override fun apply(code: KQCode, contact: Contact): Message = this.invoke(code, contact)
}


/**
 * 可注册的额外解析器
 */
object CQCodeParsingHandler {

    /** 注册额外的解析器 */
    private val otherHandler: MutableMap by lazy { mutableMapOf() }

    /**
     * get
     */
    operator fun get(cqType: String) = otherHandler[cqType]

    /**
     * set, same as [registerHandler]
     */
    internal operator fun set(cqType: String, handler: CQCodeHandler) {
        registerHandler(cqType, handler)
    }

    /**
     * 注册一个处理器。
     * @param cqType 要解析的类型
     * @param handler 解析器
     */
    @JvmStatic
    fun registerHandler(cqType: String, handler: CQCodeHandler) {
        if (otherHandler.containsKey(cqType)) {
            throw CQCodeParseHandlerRegisterException("failed.existed", cqType)
        }else{
            otherHandler[cqType] = handler
        }
    }

    /**
     * 获取所有处理器
     */
    @JvmStatic
    fun handlers(): Map = otherHandler.toMap()

}




/**
 * mirai码格式化兼容工具
 */
object MiraiCodeFormatUtils {

    /**
     * 字符串替换,替换消息中的`[mirai:`字符串为`[CQ:`
     */
    @JvmStatic
    fun mi2cq(msg: MessageChain?): String? {
        if(msg == null){
            return null
        }

        // 携带mirai码的字符串
        return msg.asSequence().map { it.toCqString() }.joinToString("")
    }


    fun SingleMessage.toCqString(): String {
        if(this is MessageSource){
            return ""
        }
        val kqCode: KQCode = when(this){
            // voice, 转化为record类型的cq码
            is Voice -> {
                val voiceMq = MQCodeUtils.toMqCode(this.toString())
                val voiceKq = voiceMq.toKQCode().mutable()
                voiceKq.type = "record"
                voiceKq["url"] = this.url
                voiceKq["fileName"] = this.fileName
                voiceKq
            }

            // image, 追加file、url
            is Image -> {
                // 缓存image
                ImageCache[this.imageId] = this
                val imageMq = MQCodeUtils.toMqCode(this.toString())
                val imageKq = imageMq.toKQCode().mutable()
                imageKq["url"] = runBlocking { queryUrl() }
                if(this is FlashImage){
                    imageKq["destruct"] = "true"
                }
                imageKq
            }

            is At -> {
                val atMq = MQCodeUtils.toMqCode(this.toString())
                val atKq = atMq.toKQCode().mutable()
                atKq["display"] = this.display
                atKq["target"] = this.target.toString()
                atKq
            }

            // at all
            is AtAll -> com.simplerobot.modules.utils.AtAll


            // face -> id
            is Face -> MQCodeUtils.toMqCode(this.toString()).toKQCode()

            // poke message, get id & type
            is PokeMessage -> {
                val pokeMq = MQCodeUtils.toMqCode(this.toString())
                val pokeKq = pokeMq.toKQCode().mutable()
                pokeKq["type"] = this.type.toString()
                pokeKq["id"] = this.id.toString()
                pokeKq
            }

            // 引用
            is QuoteReply -> {
                val quoteMq = MQCodeUtils.toMqCode(this.toString())
                val quoteKq = quoteMq.toKQCode().mutable()
                quoteKq["id"] = this.source.toCacheKey()
                quoteKq["qq"] = this.source.fromId.toString()
                quoteKq
            }

            // 富文本
            is RichMessage -> when(this) {
                // app
                is LightApp -> {
                    val code = MutableKQCode("app")
                    code["content"] = this.content
                    code
                }
                // service message
                is ServiceMessage -> {
                    val code = MutableKQCode("service")
                    code["content"] = this.content
                    code["serviceId"] = this.serviceId.toString()
                    code
                }
                else -> {
                    val string = this.toString()
                    return if(string.trim().startsWith("[mirai:")){
                        MQCodeUtils.toMqCode(string).toKQCode().toString()
                    }else string
                }
            }

            // 其他东西,不做特殊处理
            else -> {
                val string = this.toString()
                return if(string.trim().startsWith("[mirai:")){
                    MQCodeUtils.toMqCode(string).toKQCode().toString()
                }else string
            }
        }
        return kqCode.toString()
    }



}








© 2015 - 2025 Weber Informatics LLC | Privacy Policy