From df114f695838f50f179438a5b11a6059e1fbc3e6 Mon Sep 17 00:00:00 2001 From: "jiahua.liu" Date: Wed, 12 Feb 2020 22:28:59 +0800 Subject: [PATCH] Smart Config --- .../mirai/api/http/MiraiHttpAPIServer.kt | 4 + mirai-console/build.gradle.kts | 1 + .../{net/mamoe/mirai/plugin => }/Command.kt | 14 +- mirai-console/src/main/kotlin/MiraiConsole.kt | 161 +++++++++----- .../net/mamoe/mirai/plugin/ConfigSection.kt | 196 ++++++++++++++++-- .../net/mamoe/mirai/plugin/PluginBase.kt | 31 +-- 6 files changed, 309 insertions(+), 98 deletions(-) rename mirai-console/src/main/kotlin/{net/mamoe/mirai/plugin => }/Command.kt (83%) diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt index 0d8f70094..afb6e6556 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt @@ -24,6 +24,10 @@ object MiraiHttpAPIServer { SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同 } + fun setAuthKey(key: String) { + SessionManager.authKey = key + } + @UseExperimental(KtorExperimentalAPI::class) fun start( port: Int = 8080, diff --git a/mirai-console/build.gradle.kts b/mirai-console/build.gradle.kts index 1170fcdf2..34c4dc570 100644 --- a/mirai-console/build.gradle.kts +++ b/mirai-console/build.gradle.kts @@ -25,6 +25,7 @@ fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" dependencies { api(project(":mirai-core")) api(project(":mirai-core-qqandroid")) + api(project(":mirai-api-http")) runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main")) runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) api(kotlin("serialization")) diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/Command.kt b/mirai-console/src/main/kotlin/Command.kt similarity index 83% rename from mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/Command.kt rename to mirai-console/src/main/kotlin/Command.kt index 1153bfd95..226f61102 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/Command.kt +++ b/mirai-console/src/main/kotlin/Command.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.plugin +import net.mamoe.mirai.plugin.PluginManager object CommandManager { private val registeredCommand: MutableMap = mutableMapOf() @@ -25,6 +25,13 @@ object CommandManager { } } + fun unregister(command: Command) { + val allNames = mutableListOf(command.name).also { it.addAll(command.alias) } + allNames.forEach { + registeredCommand.remove(it) + } + } + fun runCommand(fullCommand: String): Boolean { val blocks = fullCommand.split(" ") val commandHead = blocks[0].replace("/", "") @@ -43,12 +50,12 @@ object CommandManager { return true } - } abstract class Command( val name: String, - val alias: List = listOf() + val alias: List = listOf(), + val description: String = "" ) { /** * 最高优先级监听器 @@ -58,3 +65,4 @@ abstract class Command( return true } } + diff --git a/mirai-console/src/main/kotlin/MiraiConsole.kt b/mirai-console/src/main/kotlin/MiraiConsole.kt index 9058fed37..46382223e 100644 --- a/mirai-console/src/main/kotlin/MiraiConsole.kt +++ b/mirai-console/src/main/kotlin/MiraiConsole.kt @@ -9,73 +9,134 @@ import kotlinx.coroutines.runBlocking import net.mamoe.mirai.Bot -import net.mamoe.mirai.plugin.Command -import net.mamoe.mirai.plugin.CommandManager +import net.mamoe.mirai.alsoLogin +import net.mamoe.mirai.api.http.generateSessionKey +import net.mamoe.mirai.plugin.JsonConfig +import net.mamoe.mirai.plugin.PluginBase import net.mamoe.mirai.plugin.PluginManager +import java.io.File import kotlin.concurrent.thread -val bots = mutableMapOf() +object MiraiConsole { + val bots + get() = Bot.instances -fun main() { - println("loading Mirai in console environments") - println("正在控制台环境中启动Mirai ") - println() - println("Mirai-console is still in testing stage, some feature is not available") - println("Mirai-console 还处于测试阶段, 部分功能不可用") - println() - println("Mirai-console now running on " + System.getProperty("user.dir")) - println("Mirai-console 正在 " + System.getProperty("user.dir") + " 运行") - println() - println("\"/login qqnumber qqpassword \" to login a bot") - println("\"/login qq号 qq密码 \" 来登陆一个BOT") + val pluginManager: PluginManager + get() = PluginManager - thread { processNextCommandLine() } + var logger: MiraiConsoleLogger = DefaultLogger - PluginManager.loadPlugins() - defaultCommands() + var path: String = System.getProperty("user.dir") - Runtime.getRuntime().addShutdownHook(thread(start = false) { + val version = " 0.13" + val build = "Beta" + + fun start() { + logger("Mirai-console v${version} $build is still in testing stage, majority feature is available") + logger("Mirai-console v${version} $build 还处于测试阶段, 大部分功能可用") + logger() + logger("Mirai-console now running under " + System.getProperty("user.dir")) + logger("Mirai-console 正在 " + System.getProperty("user.dir") + "下运行") + logger() + logger("Get news in github: https://github.com/mamoe/mirai") + logger("在Github中获取项目最新进展: https://github.com/mamoe/mirai") + logger("Mirai为开源项目,请自觉遵守开源项目协议") + logger("Powered by Mamoe Technology") + logger() + logger("\"/login qqnumber qqpassword \" to login a bot") + logger("\"/login qq号 qq密码 \" 来登陆一个BOT") + + CommandManager.register(DefaultCommands.DefaultLoginCommand()) + pluginManager.loadPlugins() + CommandListener.start() + } + + fun stop() { PluginManager.disableAllPlugins() - }) -} + } - -fun defaultCommands() { - class LoginCommand : Command( - "login" - ) { - override fun onCommand(args: List): Boolean { - if (args.size < 2) { - println("\"/login qqnumber qqpassword \" to login a bot") - println("\"/login qq号 qq密码 \" 来登录一个BOT") - return false - } - val qqNumber = args[0].toLong() - val qqPassword = args[1] - println("login...") - runBlocking { + /** + * Defaults Commands are recommend to be replaced by plugin provided commands + */ + object DefaultCommands { + class DefaultLoginCommand : Command( + "login" + ) { + override fun onCommand(args: List): Boolean { + if (args.size < 2) { + println("\"/login qqnumber qqpassword \" to login a bot") + println("\"/login qq号 qq密码 \" 来登录一个BOT") + return false + } + val qqNumber = args[0].toLong() + val qqPassword = args[1] + println("login...") try { - Bot(qqNumber, qqPassword).also { - it.login() - bots[qqNumber] = it + runBlocking { + Bot(qqNumber, qqPassword).alsoLogin() } } catch (e: Exception) { println("$qqNumber login failed") } + return true } - return true } } - CommandManager.register(LoginCommand()) + + object CommandListener { + fun start() { + thread { + processNextCommandLine() + } + } + + tailrec fun processNextCommandLine() { + val fullCommand = readLine() + if (fullCommand != null && fullCommand.startsWith("/")) { + if (!CommandManager.runCommand(fullCommand)) { + logger("unknown command $fullCommand") + logger("未知指令 $fullCommand") + } + } + processNextCommandLine(); + } + } + + interface MiraiConsoleLogger { + operator fun invoke(any: Any? = null) + } + + object DefaultLogger : MiraiConsoleLogger { + override fun invoke(any: Any?) { + println("[Mirai${version} $build]: " + any?.toString()) + } + } + + object MiraiProperties { + var HTTP_API_ENABLE: Boolean = true + var HTTP_API_PORT: Short = 8080 + var HTTP_API_AUTH_KEY: String = "" + private val file = File(path + "/mirai.json".replace("//", "/")) + private lateinit var config: JsonConfig + fun load() { + if (!file.exists()) { + HTTP_API_AUTH_KEY = "INITKEY" + generateSessionKey() + save() + return + } + config = PluginBase + } + + fun save() { + + } + } } -tailrec fun processNextCommandLine() { - val fullCommand = readLine() - if (fullCommand != null && fullCommand.startsWith("/")) { - if (!CommandManager.runCommand(fullCommand)) { - println("unknown command $fullCommand") - println("未知指令 $fullCommand") - } - } - processNextCommandLine(); +fun main() { + MiraiConsole.start() + Runtime.getRuntime().addShutdownHook(thread(start = false) { + MiraiConsole.stop() + }) } + diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/ConfigSection.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/ConfigSection.kt index 6d0705c2d..48e9c19d7 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/ConfigSection.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/ConfigSection.kt @@ -9,55 +9,205 @@ package net.mamoe.mirai.plugin +import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.UnstableDefault +import kotlinx.serialization.json.Json +import java.io.File import java.util.concurrent.ConcurrentHashMap +import kotlin.properties.Delegates +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import kotlin.reflect.full.isSubclassOf -@Serializable -class ConfigSection() : ConcurrentHashMap() { +/** + * TODO: support all config types + */ - fun getString(key: String): String { - return (get(key) ?: error("ConfigSection does not contain $key ")).toString() +interface Config { + fun getConfigSection(key: String): ConfigSection + fun getString(key: String): String + fun getInt(key: String): Int + fun getFloat(key: String): Float + fun getDouble(key: String): Double + fun getLong(key: String): Long + fun getList(key: String): List<*> + fun getStringList(key: String): List + fun getIntList(key: String): List + fun getFloatList(key: String): List + fun getDoubleList(key: String): List + fun getLongList(key: String): List + operator fun set(key: String, value: Any) + operator fun get(key: String): Any? + fun exist(key: String): Boolean + fun asMap(): Map +} + +inline fun Config.withDefault(crossinline defaultValue: () -> T): ReadWriteProperty { + return object : ReadWriteProperty { + override fun getValue(thisRef: Any, property: KProperty<*>): T { + if (!this@withDefault.exist(property.name)) { + return defaultValue.invoke() + } + return getValue(thisRef, property) + } + + override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { + this@withDefault[property.name] = value + } } +} - fun getInt(key: String): Int { - return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt() - } +@Suppress("IMPLICIT_CAST_TO_ANY") +inline operator fun ConfigSection.getValue(thisRef: Any?, property: KProperty<*>): T { + return when (T::class) { + String::class -> this.getString(property.name) + Int::class -> this.getInt(property.name) + Float::class -> this.getFloat(property.name) + Double::class -> this.getDouble(property.name) + Long::class -> this.getLong(property.name) + else -> when { + T::class.isSubclassOf(ConfigSection::class) -> this.getConfigSection(property.name) + T::class == List::class || T::class == MutableList::class -> { + val list = this.getList(property.name) + return if (list.isEmpty()) { + list + } else { + when (list[0]!!::class) { + String::class -> getStringList(property.name) + Int::class -> getIntList(property.name) + Float::class -> getFloatList(property.name) + Double::class -> getDoubleList(property.name) + Long::class -> getLongList(property.name) + else -> { + error("unsupported type") + } + } + } as T + } + else -> { + error("unsupported type") + } + } + } as T +} - fun getFloat(key: String): Float { - return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat() - } +inline operator fun ConfigSection.setValue(thisRef: Any?, property: KProperty<*>, value: T) { + this[property.name] = value!! +} - fun getDouble(key: String): Double { - return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toDouble() - } - fun getLong(key: String): Long { - return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong() - } - - fun getConfigSection(key: String): ConfigSection { +interface ConfigSection : Config { + override fun getConfigSection(key: String): ConfigSection { return (get(key) ?: error("ConfigSection does not contain $key ")) as ConfigSection } - fun getStringList(key: String): List { + override fun getString(key: String): String { + return (get(key) ?: error("ConfigSection does not contain $key ")).toString() + } + + override fun getInt(key: String): Int { + return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt() + } + + override fun getFloat(key: String): Float { + return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat() + } + + override fun getDouble(key: String): Double { + return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toDouble() + } + + override fun getLong(key: String): Long { + return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong() + } + + override fun getList(key: String): List<*> { + return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>) + } + + override fun getStringList(key: String): List { return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString() } } - fun getIntList(key: String): List { + override fun getIntList(key: String): List { return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toInt() } } - fun getFloatList(key: String): List { + override fun getFloatList(key: String): List { return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toFloat() } } - fun getDoubleList(key: String): List { + override fun getDoubleList(key: String): List { return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toDouble() } } - fun getLongList(key: String): List { + override fun getLongList(key: String): List { return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toLong() } } + override operator fun set(key: String, value: Any) { + this[key] = value + } +} + +@Serializable +open class ConfigSectionImpl() : ConcurrentHashMap(), ConfigSection { + override operator fun get(key: String): Any? { + return super.get(key) + } + + override fun exist(key: String): Boolean { + return containsKey(key) + } + + override fun asMap(): Map { + return this + } +} + + +interface FileConfig { + +} + +@Serializable +abstract class FileConfigImpl internal constructor() : ConfigSectionImpl(), FileConfig { + +} + +@Serializable +class JsonConfig internal constructor() : FileConfigImpl() { + + companion object { + @UnstableDefault + fun load(file: File): Config { + require(file.extension.toLowerCase() == "json") + val content = file.apply { + if (!this.exists()) this.createNewFile() + }.readText() + + if (content.isEmpty() || content.isBlank()) { + return JsonConfig() + } + return Json.parse( + JsonConfig.serializer(), + content + ) + } + + @UnstableDefault + fun save(file: File, config: JsonConfig) { + require(file.extension.toLowerCase() == "json") + val content = Json.stringify( + JsonConfig.serializer(), + config + ) + file.apply { + if (!this.exists()) this.createNewFile() + }.writeText(content) + } + } } \ No newline at end of file diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/PluginBase.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/PluginBase.kt index 1a0b100b0..0975192e3 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/PluginBase.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/PluginBase.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.plugin +import Command import kotlinx.coroutines.* import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json @@ -66,32 +67,18 @@ abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope { this.onEnable() } - @UnstableDefault - fun loadConfig(fileName: String = "config.json"): ConfigSection { - var content = File(dataFolder.name + "/" + fileName) - .also { - if (!it.exists()) it.createNewFile() - }.readText() + /** + * TODO: support all config types + */ - if (content == "") { - content = "{}" - } - return Json.parse( - ConfigSection.serializer(), - content - ) + @UnstableDefault + fun loadConfig(fileName: String): Config { + return JsonConfig.load(File(fileName)) } @UnstableDefault - fun saveConfig(config: ConfigSection, fileName: String = "config.json") { - val content = Json.stringify( - ConfigSection.serializer(), - config - ) - File(dataFolder.name + "/" + fileName) - .also { - if (!it.exists()) it.createNewFile() - }.writeText(content) + fun saveConfig(config: Config, fileName: String = "config.json") { + JsonConfig.save(file = File(fileName), config = config as JsonConfig) }