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

love.forte.simbot.bot.BotVerifyInfoConfiguration.kt Maven / Gradle / Ivy

There is a newer version: 3.0.0.preview.0.4
Show newest version
/*
 *
 *  * Copyright (c) 2021. ForteScarlet All rights reserved.
 *  * Project  simple-robot
 *  * File     MiraiAvatar.kt
 *  *
 *  * You can contact the author through the following channels:
 *  * github https://github.com/ForteScarlet
 *  * gitee  https://gitee.com/ForteScarlet
 *  * email  [email protected]
 *  * QQ     1149159218
 *
 */

package love.forte.simbot.bot

import love.forte.common.utils.scanner.ResourcesScanner
import love.forte.simbot.bot.BotVerifyInfoConfiguration.Companion.ACTION_NAME_KEY
import love.forte.simbot.utils.newInputStream
import love.forte.simbot.utils.useJarBufferedReader
import org.slf4j.LoggerFactory
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.*
import kotlin.io.path.*


/**
 *
 * bot验证信息配置管理器。
 *
 * 是一个配置类接口,其内部记录本次启动所读取到的所有 **配置** 的bot验证信息。
 * 其数据是静态的,在启动的时候读取所有配置记录后不再更新,因此与后续追加、移除的bot信息无关。
 *
 * 其中包括了 `simbot.core.bots` 中的配置以及 `simbot.core.bot-resource-type` 中指定类型的配置。
 *
 * `simbot.core.bot-resource-type` 指定扫描模式后,[BotVerifyInfoConfiguration] 会去扫描 `simbot-bots` 目录下的所有 `.bot` 文件。
 * `.bot` 文件是一种 properties 格式文件,其中记录bot的各项信息。
 *
 *
 * @author ForteScarlet
 * @since 2.1.0
 */
public interface BotVerifyInfoConfiguration {

    companion object {
        const val PATH_DIR = "simbot-bots"
        const val ACTION_NAME_KEY = "action_name"
    }

    /**
     * Bot资源获取的类型。
     */
    val botResourceType: BotResourceType

    /**
     * 已配置的bot信息列表。可能为空。
     */
    val configuredBotVerifyInfos: List

}


/**
 * 基础的 [BotVerifyInfoConfiguration] 配置类实现。
 *
 * 通过 action_name 来决定是否被加载。
 *
 */
public class SimpleBotVerifyInfoConfiguration(
    override val botResourceType: BotResourceType,
    actionBots: List = listOf(ALL_ACTION_KEY),
    codeAlias: Array = CODE_ALIAS,
    verificationAlias: Array = VERIFICATION_ALIAS,
    other: List = emptyList(),
) : BotVerifyInfoConfiguration {

    private companion object {
        private val LOGGER = LoggerFactory.getLogger(BotVerifyInfoConfiguration::class.java)
        private const val ALL_ACTION_KEY = "*"
    }

    override val configuredBotVerifyInfos: List

    init {
        fun fromFile(): List {
            val root = Path(BotVerifyInfoConfiguration.PATH_DIR)
            val collection = mutableListOf()

            if (!root.exists()) {
                LOGGER.debug("Cannot read bots configure by file: The directory '${BotVerifyInfoConfiguration.PATH_DIR}' does not exist, skip.")
                return emptyList()
            }

            Files.walkFileTree(root, FileVisitorByExtension("bot", collection) { p ->
                LOGGER.debug("Bot verify info by {}", p.name)
                Properties().apply {
                    p.reader(Charsets.UTF_8).use(::load)
                }
            })

            return collection
        }

        fun fromResource(): List {
            return runCatching {
                val loader = Thread.currentThread().contextClassLoader
                ResourcesScanner(loader).scan(BotVerifyInfoConfiguration.PATH_DIR) { uri ->
                    uri.toASCIIString().endsWith("bot")
                }.collection.map { uri ->
                    LOGGER.debug("Bot verify info by {}", uri.toASCIIString())
                    Properties().apply {
                        val asciiString = uri.toASCIIString()
                        loader.getResource(asciiString)?.newInputStream()?.reader(Charsets.UTF_8)?.use(::load)
                            ?: kotlin.runCatching {
                                LOGGER.debug("Uri stream null: {}, try use url", asciiString)
                                uri.toURL().useJarBufferedReader(Charsets.UTF_8, ::load)
                                // uri.toURL().openStream().reader(Charsets.UTF_8).use(::load)
                            }.getOrElse { e1 ->
                                throw IllegalStateException("Uri stream read failed: $uri", e1)
                            }


                    }
                }
            }.getOrElse { e ->
                LOGGER.debug("Cannot read bots configure by resource, skip. info : {}", e.localizedMessage)
                LOGGER.debug("Details: $e", e)
                emptyList()
            }
        }


        LOGGER.debug("Bot resource Type: {}", botResourceType)

        val propertiesList = when (botResourceType) {
            BotResourceType.NONE -> emptyList()
            BotResourceType.FILE -> fromFile()
            BotResourceType.RESOURCE -> fromResource()
            BotResourceType.BOTH -> fromFile() + fromResource()
            BotResourceType.FILE_FIRST -> fromFile().ifEmpty {
                LOGGER.debug("Bot verify info read file first but not found, try resource.")
                fromResource()
            }
            BotResourceType.RESOURCE_FIRST -> fromResource().ifEmpty {
                LOGGER.debug("Bot verify info read resource first but not found, try file.")
                fromFile()
            }
        }

        val infos: List = propertiesList.map { p ->
            pairBotVerifyInfo(p,
                codeAlias,
                verificationAlias)
        }

        val actionBotsSet = actionBots.toSet()

        configuredBotVerifyInfos = ((
                if (ALL_ACTION_KEY in actionBotsSet) infos
                else infos.filter { i ->
                    i[ACTION_NAME_KEY]?.let { actionName -> actionName in actionBotsSet } ?: kotlin.run {
                        LOGGER.warn("Bot(code=${i.code})'s config property '$ACTION_NAME_KEY' is null, but your action bots config properties is not ignore or '$ALL_ACTION_KEY', so this bot will always be loaded.")
                        true
                    }
                }) + other).distinctBy { info ->
            info.code
        }


    }


}

/**
 * 根据扩展名获取文件的文件访问器。
 */
internal class FileVisitorByExtension(
    private val extension: String,
    private val collection: MutableList,
    private val mapper: (path: Path) -> T,
) : SimpleFileVisitor() {

    override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult {
        requireNotNull(file)
        if (file.extension == extension) {
            collection.add(mapper(file))
        }

        return FileVisitResult.CONTINUE
    }
}


/**
 * 加载 `simbot-bots` 路径资源的时候,使用的文件、资源匹配原则。
 */
public enum class BotResourceType {
    /**
     * 只查找文件目录
     */
    FILE,

    /**
     * 只查找资源目录
     */
    RESOURCE,

    /**
     * 两者都查找且进行合并
     */
    BOTH,

    /**
     * 优先文件目录,如果没有任何结果则查询资源目录。
     */
    FILE_FIRST,

    /**
     * 优先资源目录,如果没有任何结果则查询文件目录。
     */
    RESOURCE_FIRST,

    /**
     * 不去获取bot配置信息。
     */
    NONE
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy