diff --git a/backend/codegen/README.MD b/backend/codegen/README.MD
new file mode 100644
index 000000000..6d5db89bc
--- /dev/null
+++ b/backend/codegen/README.MD
@@ -0,0 +1,87 @@
+# Mirai Console
+你可以在全平台运行Mirai高效率机器人框架
+### Mirai Console提供了6个版本以满足各种需要
+#### 所有版本的Mirai Console API相同 插件系统相同
+
+| 名字 | 介绍 |
+|:------------------------|:------------------------------|
+| Mirai-Console-Pure | 最纯净版, CLI环境, 通过标准输入与标准输出 交互 |
+| Mirai-Console-Terminal | (UNIX)Terminal环境 提供简洁的富文本控制台 |
+| Mirai-Console-Android | 安卓APP (TODO) |
+| Mirai-Console-Graphical | JavaFX的图形化界面 (.jar/.exe/.dmg) |
+| Mirai-Console-WebPanel | Web Panel操作(TODO) |
+| Mirai-Console-Ios | IOS APP (TODO) |
+
+
+### 如何选择版本
+1: Mirai-Console-Pure 兼容性最高, 在其他都表现不佳的时候请使用
+2: 以系统区分
+```kotlin
+ return when(operatingSystem){
+ WINDOWS -> listOf("Graphical","WebPanel","Pure")
+ MAC_OS -> listOf("Graphical","Terminal","WebPanel","Pure")
+ LINUX -> listOf("Terminal","Pure")
+ ANDROID -> listOf("Android","Pure","WebPanel")
+ IOS -> listOf("Ios")
+ else -> listOf("Pure")
+ }
+```
+3: 以策略区分
+```kotlin
+ return when(task){
+ 体验 -> listOf("Graphical","Terminal","WebPanel","Android","Pure")
+ 测试插件 -> listOf("Pure")
+ 调试插件 -> byOperatingSystem()
+ 稳定挂机 -> listOf("Terminal","Pure")
+ else -> listOf("Pure")
+ }
+```
+
+
+#### More Importantly, Mirai Console support Plugins, tells the bot what to do
+#### Mirai Console 支持插件系统, 你可以自己开发或使用公开的插件来逻辑化机器人, 如群管
+
+
+#### download 下载
+#### how to get/write plugins 如何获取/写插件
+
+
+
+### how to use(如何使用)
+#### how to run Mirai Console
+
+ - download mirai-console.jar
+ - open command line/terminal
+ - create a folder and put mirai-console.jar in
+ - cd that folder
+ - "java -jar mirai-console.jar"
+
+
+
+ - 下载mirai-console.jar
+ - 打开终端
+ - 在任何地方创建一个文件夹, 并放入mirai-console.jar
+ - 在终端中打开该文件夹"cd"
+ - 输入"java -jar mirai-console.jar"
+
+
+#### how to add plugins
+
+ - After first time of running mirai console
+ - /plugins/folder will be created next to mirai-console.jar
+ - put plugin(.jar) into /plugins/
+ - restart mirai console
+ - checking logger and check if the plugin is loaded successfully
+ - if the plugin has it own Config file, it normally appears in /plugins/{pluginName}/
+
+
+
+ - 在首次运行mirai console后
+ - mirai-console.jar 的同级会出现/plugins/文件夹
+ - 将插件(.jar)放入/plugins/文件夹
+ - 重启mirai console
+ - 在开启后检查日志, 是否成功加载
+ - 如该插件有配置文件, 配置文件一般会创建在/plugins/插件名字/ 文件夹下
+
+
+
diff --git a/backend/codegen/build.gradle.kts b/backend/codegen/build.gradle.kts
new file mode 100644
index 000000000..574abfe7f
--- /dev/null
+++ b/backend/codegen/build.gradle.kts
@@ -0,0 +1,41 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ id("kotlin")
+ kotlin("plugin.serialization")
+ id("java")
+}
+
+kotlin {
+ sourceSets {
+ all {
+ languageSettings.useExperimentalAnnotation("kotlin.Experimental")
+ languageSettings.useExperimentalAnnotation("kotlin.OptIn")
+ languageSettings.progressiveMode = true
+ languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
+ languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
+ languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
+ languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
+ }
+ }
+}
+
+dependencies {
+ api(kotlin("stdlib"))
+}
+
+val compileKotlin: KotlinCompile by tasks
+compileKotlin.kotlinOptions {
+ jvmTarget = "1.8"
+}
+val compileTestKotlin: KotlinCompile by tasks
+compileTestKotlin.kotlinOptions {
+ jvmTarget = "1.8"
+}
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+tasks.withType(JavaCompile::class.java) {
+ options.encoding = "UTF8"
+}
\ No newline at end of file
diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt
new file mode 100644
index 000000000..99269419a
--- /dev/null
+++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/SettingValueUseSiteCodegen.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.console.codegen
+
+import java.io.File
+
+
+fun main() {
+ println(File("").absolutePath) // default project base dir
+
+ File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Setting.kt").apply {
+ createNewFile()
+ }.writeText(buildString {
+ appendln(COPYRIGHT)
+ appendln()
+ appendln(PACKAGE)
+ appendln()
+ // appendln(IMPORTS)
+ // appendln()
+ // appendln()
+ appendln(DO_NOT_MODIFY)
+ appendln()
+ appendln()
+ appendln(genAllValueUseSite())
+ })
+}
+
+private val DO_NOT_MODIFY = """
+/**
+ * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.SettingValueUseSiteCodegen.kt
+ * !!! DO NOT MODIFY THIS FILE MANUALLY
+ */
+""".trimIndent()
+
+private val PACKAGE = """
+package net.mamoe.mirai.console.setting
+""".trimIndent()
+
+private val IMPORTS = """
+import kotlinx.serialization.builtins.*
+""".trimIndent()
+
+fun genAllValueUseSite(): String = buildString {
+ // PRIMITIVE
+ for (number in NUMBERS + OTHER_PRIMITIVES) {
+ appendln(genValueUseSite(number, number))
+ }
+
+ // PRIMITIVE ARRAYS
+ for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) {
+ appendln(genValueUseSite("${number}Array", "${number}Array"))
+ }
+
+ // TYPED ARRAYS
+ for (number in NUMBERS + OTHER_PRIMITIVES) {
+ appendln(genValueUseSite("Array<${number}>", "Typed${number}Array"))
+ }
+
+ // PRIMITIVE LISTS
+ for (number in NUMBERS + OTHER_PRIMITIVES) {
+ appendln(genValueUseSite("List<${number}>", "${number}List"))
+ }
+}
+
+fun genValueUseSite(kotlinTypeName: String, miraiValueName: String): String =
+ """
+ fun Setting.value(default: $kotlinTypeName): ${miraiValueName}Value = valueImpl(default)
+ """.trimIndent()
+
diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt
new file mode 100644
index 000000000..7d40123f1
--- /dev/null
+++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueImplCodegen.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+
+package net.mamoe.mirai.console.codegen
+
+import java.io.File
+
+
+fun main() {
+ println(File("").absolutePath) // default project base dir
+
+ File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt").apply {
+ createNewFile()
+ }.writeText(buildString {
+ appendln(COPYRIGHT)
+ appendln()
+ appendln(PACKAGE)
+ appendln()
+ appendln(IMPORTS)
+ appendln()
+ appendln()
+ appendln(DO_NOT_MODIFY)
+ appendln()
+ appendln()
+ appendln(genAllValueImpl())
+ })
+}
+
+private val DO_NOT_MODIFY = """
+/**
+ * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.ValueImplCodegen.kt
+ * !!! DO NOT MODIFY THIS FILE MANUALLY
+ */
+""".trimIndent()
+
+private val PACKAGE = """
+package net.mamoe.mirai.console.setting
+""".trimIndent()
+
+private val IMPORTS = """
+import kotlinx.serialization.builtins.*
+""".trimIndent()
+
+fun genAllValueImpl(): String = buildString {
+ // PRIMITIVE
+ for (number in NUMBERS + OTHER_PRIMITIVES) {
+ appendln(genValueImpl(number, number, "$number.serializer()", false))
+ }
+
+ // PRIMITIVE ARRAYS
+ for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) {
+ appendln(genValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true))
+ }
+
+ // TYPED ARRAYS
+ for (number in NUMBERS + OTHER_PRIMITIVES) {
+ appendln(genValueImpl("Array<${number}>", "Typed${number}Array", "ArraySerializer(${number}.serializer())", true))
+ }
+
+ // PRIMITIVE LISTS
+ for (number in NUMBERS + OTHER_PRIMITIVES) {
+ appendln(genValueImpl("List<${number}>", "${number}List", "ListSerializer(${number}.serializer())", false))
+ }
+}
+
+fun genValueImpl(kotlinTypeName: String, miraiValueName: String, serializer: String, isArray: Boolean): String =
+ """
+ internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value {
+ return object : ${miraiValueName}Value() {
+ private var internalValue: $kotlinTypeName = default
+ override var value: $kotlinTypeName
+ get() = internalValue
+ set(new) {
+ ${
+ if (isArray) """
+ if (!new.contentEquals(internalValue)) {
+ internalValue = new
+ onElementChanged(this)
+ }
+ """.trim()
+ else """
+ if (new != internalValue) {
+ internalValue = new
+ onElementChanged(this)
+ }
+ """.trim()
+ }
+ }
+ override val serializer = ${serializer}.bind(
+ getter = { internalValue },
+ setter = { internalValue = it }
+ )
+ }
+ }
+ """.trimIndent() + "\n"
+
diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt
new file mode 100644
index 000000000..4d75b33d2
--- /dev/null
+++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValuesCodegen.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+@file:Suppress("ClassName")
+
+package net.mamoe.mirai.console.codegen
+
+import org.intellij.lang.annotations.Language
+import java.io.File
+
+fun main() {
+ println(File("").absolutePath) // default project base dir
+
+ File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_Value.kt").apply {
+ createNewFile()
+ }.writeText(genPublicApi())
+}
+
+internal const val COPYRIGHT = """
+/*
+ * Copyright 2020 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/master/LICENSE
+ */
+"""
+
+internal val NUMBERS = listOf(
+ "Int",
+ "Short",
+ "Byte",
+ "Long",
+ "Float",
+ "Double"
+)
+
+internal val UNSIGNED_NUMBERS = listOf(
+ "UInt",
+ "UShort",
+ "UByte",
+ "ULong"
+)
+
+internal val OTHER_PRIMITIVES = listOf(
+ "Boolean",
+ "Char",
+ "String"
+)
+
+fun genPublicApi() = buildString {
+ fun appendln(@Language("kt") code: String){
+ this.appendln(code.trimIndent())
+ }
+
+ appendln(COPYRIGHT.trim())
+ appendln()
+ appendln(
+ """
+ package net.mamoe.mirai.console.setting
+
+ import kotlinx.serialization.KSerializer
+ import kotlin.properties.ReadWriteProperty
+ import kotlin.reflect.KProperty
+ """
+ )
+ appendln()
+ appendln(
+ """
+ /**
+ * !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt
+ * !!! for better performance
+ * !!! DO NOT MODIFY THIS FILE MANUALLY
+ */
+ """
+ )
+ appendln()
+
+ appendln(
+ """
+sealed class Value : ReadWriteProperty {
+ abstract var value: T
+
+ abstract val serializer: KSerializer
+ override fun getValue(thisRef: Setting, property: KProperty<*>): T = value
+ override fun setValue(thisRef: Setting, property: KProperty<*>, value: T) {
+ this.value = value
+ }
+}
+ """
+ )
+ appendln()
+
+ // PRIMITIVES
+
+ appendln(
+ """
+ sealed class PrimitiveValue : Value()
+
+ sealed class NumberValue : Value()
+ """
+ )
+
+ for (number in NUMBERS) {
+ val template = """
+ abstract class ${number}Value internal constructor() : NumberValue<${number}>()
+ """
+
+ appendln(template)
+ }
+
+ appendln()
+
+ for (number in OTHER_PRIMITIVES) {
+ val template = """
+ abstract class ${number}Value internal constructor() : PrimitiveValue<${number}>()
+ """
+
+ appendln(template)
+ }
+
+ appendln()
+
+ // ARRAYS
+
+ appendln(
+ """
+ // T can be primitive array or typed Array
+ sealed class ArrayValue : Value()
+ """
+ )
+
+ // PRIMITIVE ARRAYS
+ appendln(
+ """
+ sealed class PrimitiveArrayValue : ArrayValue()
+ """
+ )
+ appendln()
+
+ for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) {
+ val template = """
+ abstract class ${number}ArrayValue internal constructor() : PrimitiveArrayValue<${number}Array>(), Iterable<${number}> {
+ override fun iterator(): Iterator<${number}> = this.value.iterator()
+ }
+ """
+
+ appendln(template)
+ }
+
+ appendln()
+
+ // TYPED ARRAYS
+
+ appendln(
+ """
+ sealed class TypedPrimitiveArrayValue : ArrayValue>() , Iterable{
+ override fun iterator() = this.value.iterator()
+ }
+ """
+ )
+ appendln()
+
+ for (number in (NUMBERS + OTHER_PRIMITIVES)) {
+ val template = """
+ abstract class Typed${number}ArrayValue internal constructor() : TypedPrimitiveArrayValue<${number}>()
+ """
+
+ appendln(template)
+ }
+
+ appendln()
+
+ // TYPED LISTS
+
+ appendln(
+ """
+ sealed class ListValue : Value>(), Iterable{
+ override fun iterator() = this.value.iterator()
+ }
+ """
+ )
+
+ for (number in (NUMBERS + OTHER_PRIMITIVES)) {
+ val template = """
+ abstract class ${number}ListValue internal constructor() : ListValue<${number}>()
+ """
+
+ appendln(template)
+ }
+
+ appendln()
+}
\ No newline at end of file
diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts
index 124050c15..60572b5fd 100644
--- a/backend/mirai-console/build.gradle.kts
+++ b/backend/mirai-console/build.gradle.kts
@@ -32,7 +32,7 @@ dependencies {
compileAndRuntime("net.mamoe:mirai-core:${Versions.Mirai.core}")
compileAndRuntime(kotlin("stdlib"))
- api("moe.him188.yamlkt:yamlkt:0.2.0")
+ api("net.mamoe.yamlkt:yamlkt:0.2.0")
api("org.jsoup:jsoup:1.12.1")
diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java
deleted file mode 100644
index 0f1877e06..000000000
--- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/plugins/ConfigSectionFactory.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package net.mamoe.mirai.console.plugins;
-
-public class ConfigSectionFactory {
-
- public static ConfigSection create(){
- return ConfigSection.Companion.create();
- }
-
-}
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt
index faced21a0..60b041c17 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/center/CuiPluginCenter.kt
@@ -1,12 +1,10 @@
package net.mamoe.mirai.console.center
-import com.google.gson.JsonArray
-import com.google.gson.JsonObject
-import com.google.gson.JsonParser
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.get
import io.ktor.util.KtorExperimentalAPI
+import kotlinx.serialization.json.*
import net.mamoe.mirai.console.utils.retryCatching
import java.io.File
@@ -26,20 +24,20 @@ internal object CuiPluginCenter : PluginCenter {
if (plugins == null) {
refresh()
}
- if (it >= plugins!!.size()) {
+ if (it >= plugins!!.size) {
return@forEach
}
val info = plugins!![it]
- with(info.asJsonObject) {
- map[this.get("name").asString] = PluginCenter.PluginInsight(
- this.get("name")?.asString ?: "",
- this.get("version")?.asString ?: "",
- this.get("core")?.asString ?: "",
- this.get("console")?.asString ?: "",
- this.get("author")?.asString ?: "",
- this.get("description")?.asString ?: "",
- this.get("tags")?.asJsonArray?.map { it.asString } ?: arrayListOf(),
- this.get("commands")?.asJsonArray?.map { it.asString } ?: arrayListOf()
+ with(info.jsonObject) {
+ map[this["name"]!!.toString()] = PluginCenter.PluginInsight(
+ this["name"]?.primitive?.content ?: "",
+ this["version"]?.primitive?.content ?: "",
+ this["core"]?.primitive?.content ?: "",
+ this["console"]?.primitive?.content ?: "",
+ this["author"]?.primitive?.content ?: "",
+ this["description"]?.primitive?.content ?: "",
+ this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(),
+ this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf()
)
}
}
@@ -62,18 +60,18 @@ internal object CuiPluginCenter : PluginCenter {
return result.asJson().run {
PluginCenter.PluginInfo(
- this.get("name")?.asString ?: "",
- this.get("version")?.asString ?: "",
- this.get("core")?.asString ?: "",
- this.get("console")?.asString ?: "",
- this.get("tags")?.asJsonArray?.map { it.asString } ?: arrayListOf(),
- this.get("author")?.asString ?: "",
- this.get("contact")?.asString ?: "",
- this.get("description")?.asString ?: "",
- this.get("usage")?.asString ?: "",
- this.get("vsc")?.asString ?: "",
- this.get("commands")?.asJsonArray?.map { it.asString } ?: arrayListOf(),
- this.get("changeLog")?.asJsonArray?.map { it.asString } ?: arrayListOf()
+ this["name"]?.primitive?.content ?: "",
+ this["version"]?.primitive?.content ?: "",
+ this["core"]?.primitive?.content ?: "",
+ this["console"]?.primitive?.content ?: "",
+ this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(),
+ this["author"]?.primitive?.content ?: "",
+ this["contact"]?.primitive?.content ?: "",
+ this["description"]?.primitive?.content ?: "",
+ this["usage"]?.primitive?.content ?: "",
+ this["vsc"]?.primitive?.content ?: "",
+ this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(),
+ this["changeLog"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf()
)
}
@@ -82,10 +80,10 @@ internal object CuiPluginCenter : PluginCenter {
override suspend fun refresh() {
val results = Http.get("https://miraiapi.jasonczc.cn/getPluginList").asJson()
- if (!(results.has("success") && results["success"].asBoolean)) {
+ if (!(results.containsKey("success") && results["success"]?.boolean == true)) {
error("Failed to fetch plugin list from Cui Cloud")
}
- plugins = results.get("result").asJsonArray//先不解析
+ plugins = results["result"]?.jsonArray//先不解析
}
override suspend fun T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File {
@@ -118,8 +116,10 @@ internal object CuiPluginCenter : PluginCenter {
get() = "崔云"
+ private val json = Json(JsonConfiguration.Stable)
+
private fun String.asJson(): JsonObject {
- return JsonParser.parseString(this).asJsonObject
+ return json.parseJson(this).jsonObject
}
}
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt
deleted file mode 100644
index ba8225913..000000000
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/ConfigSection.kt
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright 2020 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/master/LICENSE
- */
-
-@file:Suppress("MemberVisibilityCanBePrivate")
-
-package net.mamoe.mirai.console.plugins
-
-import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
-import com.moandjiezana.toml.Toml
-import com.moandjiezana.toml.TomlWriter
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.UnstableDefault
-import net.mamoe.mirai.console.encodeToString
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import org.yaml.snakeyaml.Yaml
-import java.io.File
-import java.io.InputStream
-import java.util.*
-import java.util.concurrent.ConcurrentHashMap
-import kotlin.NoSuchElementException
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KClass
-import kotlin.reflect.KProperty
-import kotlin.reflect.full.isSubclassOf
-
-
-/**
- * 标注一个即将在将来版本删除的 API
- *
- * 目前 [Config] 的读写设计语义不明, list 和 map 读取效率低, 属性委托写入语义不明
- * 将在未来重写并变为 `ReadOnlyConfig` 和 `ReadWriteConfig`.
- * 并计划添加图形端配置绑定, 和带注释的序列化, 自动保存的配置文件, 与指令绑定的属性.
- */
-@RequiresOptIn("将在未来进行不兼容的修改", RequiresOptIn.Level.WARNING)
-annotation class ToBeRemoved
-
-/**
- * 可读可写配置文件.
- *
- * @suppress 注意: 配置文件正在进行不兼容的修改
- */
-interface Config {
- @ToBeRemoved
- 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 getBoolean(key: String): Boolean
-
- @ToBeRemoved
- fun getList(key: String): List<*>
-
-
- @ToBeRemoved
- fun getStringList(key: String): List
-
- @ToBeRemoved
- fun getIntList(key: String): List
-
- @ToBeRemoved
- fun getFloatList(key: String): List
-
- @ToBeRemoved
- fun getDoubleList(key: String): List
-
- @ToBeRemoved
- fun getLongList(key: String): List
-
- @ToBeRemoved
- fun getConfigSectionList(key: String): List
-
- operator fun set(key: String, value: Any)
- operator fun get(key: String): Any?
- operator fun contains(key: String): Boolean
-
- @ToBeRemoved
- fun exist(key: String): Boolean
-
- /**
- * 设置 key = value (如果value不存在则valueInitializer会被调用)
- * 之后返回当前key对应的值
- * */
- @ToBeRemoved
- fun setIfAbsent(key: String, value: T)
-
- @ToBeRemoved
- fun setIfAbsent(key: String, valueInitializer: Config.() -> T)
-
- @ToBeRemoved
- fun asMap(): Map
-
- @ToBeRemoved
- fun save()
-
- companion object {
- @ToBeRemoved
- fun load(fileName: String): Config {
- return load(
- File(
- fileName.replace(
- "//",
- "/"
- )
- )
- )
- }
-
- /**
- * create a read-write config
- * */
- @ToBeRemoved
- fun load(file: File): Config {
- if (!file.exists()) {
- file.createNewFile()
- }
- return when (file.extension.toLowerCase()) {
- "json" -> JsonConfig(file)
- "yml" -> YamlConfig(file)
- "yaml" -> YamlConfig(file)
- "mirai" -> YamlConfig(file)
- "ini" -> TomlConfig(file)
- "toml" -> TomlConfig(file)
- "properties" -> TomlConfig(file)
- "property" -> TomlConfig(file)
- "data" -> TomlConfig(file)
- else -> error("Unsupported file config type ${file.extension.toLowerCase()}")
- }
- }
-
- /**
- * create a read-only config
- */
- @ToBeRemoved
- fun load(content: String, type: String): Config {
- return when (type.toLowerCase()) {
- "json" -> JsonConfig(content)
- "yml" -> YamlConfig(content)
- "yaml" -> YamlConfig(content)
- "mirai" -> YamlConfig(content)
- "ini" -> TomlConfig(content)
- "toml" -> TomlConfig(content)
- "properties" -> TomlConfig(content)
- "property" -> TomlConfig(content)
- "data" -> TomlConfig(content)
- else -> error("Unsupported file config type $content")
- }
- }
-
- /**
- * create a read-only config
- */
- @ToBeRemoved
- fun load(inputStream: InputStream, type: String): Config {
- return load(inputStream.readBytes().encodeToString(), type)
- }
-
- }
-}
-
-
-@ToBeRemoved
-fun File.loadAsConfig(): Config {
- return Config.load(this)
-}
-
-/* 最简单的代理 */
-@ToBeRemoved
-inline operator fun Config.getValue(thisRef: Any?, property: KProperty<*>): T {
- return smartCast(property)
-}
-
-@ToBeRemoved
-inline operator fun Config.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
- this[property.name] = value
-}
-
-/* 带有默认值的代理 */
-@Suppress("unused")
-@ToBeRemoved
-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)) {//unsafe
- return this@withDefault.smartCast(property)
- }
- return defaultValue()
- }
-
- override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
- this@withDefault[property.name] = value
- }
- }
-}
-
-/* 带有默认值且如果为空会写入的代理 */
-@Suppress("unused")
-@ToBeRemoved
-inline fun Config.withDefaultWrite(
- noinline defaultValue: () -> T
-): WithDefaultWriteLoader {
- return WithDefaultWriteLoader(
- T::class,
- this,
- defaultValue,
- false
- )
-}
-
-/* 带有默认值且如果为空会写入保存的代理 */
-@ToBeRemoved
-inline fun Config.withDefaultWriteSave(
- noinline defaultValue: () -> T
-): WithDefaultWriteLoader {
- return WithDefaultWriteLoader(T::class, this, defaultValue, true)
-}
-
-@ToBeRemoved
-class WithDefaultWriteLoader(
- private val _class: KClass,
- private val config: Config,
- private val defaultValue: () -> T,
- private val save: Boolean
-) {
- operator fun provideDelegate(
- thisRef: Any,
- prop: KProperty<*>
- ): ReadWriteProperty {
- val defaultValue by lazy { defaultValue.invoke() }
- if (!config.contains(prop.name)) {
- config[prop.name] = defaultValue
- if (save) {
- config.save()
- }
- }
- return object : ReadWriteProperty {
- override fun getValue(thisRef: Any, property: KProperty<*>): T {
- if (config.exist(property.name)) {//unsafe
- return config.smartCastInternal(property.name, _class)
- }
- return defaultValue
- }
-
- override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
- config[property.name] = value
- }
- }
- }
-}
-
-@PublishedApi
-internal inline fun Config.smartCast(property: KProperty<*>): T {
- return smartCastInternal(property.name, T::class)
-}
-
-@OptIn(ToBeRemoved::class)
-@PublishedApi
-@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-internal fun Config.smartCastInternal(propertyName: String, _class: KClass): T {
- return when (_class) {
- String::class -> this.getString(propertyName)
- Int::class -> this.getInt(propertyName)
- Float::class -> this.getFloat(propertyName)
- Double::class -> this.getDouble(propertyName)
- Long::class -> this.getLong(propertyName)
- Boolean::class -> this.getBoolean(propertyName)
- else -> when {
- _class.isSubclassOf(ConfigSection::class) -> this.getConfigSection(propertyName)
- _class == List::class || _class == MutableList::class -> {
- val list = this.getList(propertyName)
- return if (list.isEmpty()) {
- list
- } else {
- when (list[0]!!::class) {
- String::class -> getStringList(propertyName)
- Int::class -> getIntList(propertyName)
- Float::class -> getFloatList(propertyName)
- Double::class -> getDoubleList(propertyName)
- Long::class -> getLongList(propertyName)
- //不去支持getConfigSectionList(propertyName)
- // LinkedHashMap::class -> getConfigSectionList(propertyName)//faster approach
- else -> {
- //if(list[0]!! is ConfigSection || list[0]!! is Map<*,*>){
- // getConfigSectionList(propertyName)
- //}else {
- error("unsupported type" + list[0]!!::class)
- //}
- }
- }
- } as T
- }
- else -> {
- error("unsupported type")
- }
- }
- } as T
-}
-
-
-@ToBeRemoved
-interface ConfigSection : Config, MutableMap {
- companion object {
- fun create(): ConfigSection {
- return ConfigSectionImpl()
- }
-
- fun new(): ConfigSection {
- return this.create()
- }
- }
-
- override fun getConfigSection(key: String): ConfigSection {
- val content = get(key) ?: throw NoSuchElementException(key)
- if (content is ConfigSection) {
- return content
- }
- @Suppress("UNCHECKED_CAST")
- return ConfigSectionDelegation(
- Collections.synchronizedMap(
- (get(key) ?: throw NoSuchElementException(key)) as LinkedHashMap
- )
- )
- }
-
- override fun getString(key: String): String {
- return (get(key) ?: throw NoSuchElementException(key)).toString()
- }
-
- override fun getInt(key: String): Int {
- return (get(key) ?: throw NoSuchElementException(key)).toString().toInt()
- }
-
- override fun getFloat(key: String): Float {
- return (get(key) ?: throw NoSuchElementException(key)).toString().toFloat()
- }
-
- override fun getBoolean(key: String): Boolean {
- return (get(key) ?: throw NoSuchElementException(key)).toString().toBoolean()
- }
-
- override fun getDouble(key: String): Double {
- return (get(key) ?: throw NoSuchElementException(key)).toString().toDouble()
- }
-
- override fun getLong(key: String): Long {
- return (get(key) ?: throw NoSuchElementException(key)).toString().toLong()
- }
-
- override fun getList(key: String): List<*> {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>)
- }
-
- override fun getStringList(key: String): List {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).filterNotNull().map { it.toString() }
- }
-
- override fun getIntList(key: String): List {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toInt() }
- }
-
- override fun getFloatList(key: String): List {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toFloat() }
- }
-
- override fun getDoubleList(key: String): List {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toDouble() }
- }
-
- override fun getLongList(key: String): List {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toLong() }
- }
-
- override fun getConfigSectionList(key: String): List {
- return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map {
- if (it is ConfigSection) {
- it
- } else {
- @Suppress("UNCHECKED_CAST")
- ConfigSectionDelegation(
- Collections.synchronizedMap(
- it as MutableMap
- )
- )
- }
- }
- }
-
- override fun exist(key: String): Boolean {
- return get(key) != null
- }
-
- override fun setIfAbsent(key: String, value: T) {
- putIfAbsent(key, value)
- }
-
- override fun setIfAbsent(key: String, valueInitializer: Config.() -> T) {
- if (this.exist(key)) {
- put(key, valueInitializer.invoke(this))
- }
- }
-}
-
-@OptIn(ToBeRemoved::class)
-internal inline fun ConfigSection.smartGet(key: String): T {
- return this.smartCastInternal(key, T::class)
-}
-
-@Serializable
-@ToBeRemoved
-open class ConfigSectionImpl : ConcurrentHashMap(),
-
- ConfigSection {
- override fun set(key: String, value: Any) {
- super.put(key, value)
- }
-
- override operator fun get(key: String): Any? {
- return super.get(key)
- }
-
- @Suppress("RedundantOverride")
- override fun contains(key: String): Boolean {
- return super.contains(key)
- }
-
- override fun exist(key: String): Boolean {
- return containsKey(key)
- }
-
- override fun asMap(): Map {
- return this
- }
-
- override fun save() {
-
- }
-}
-
-@ToBeRemoved
-open class ConfigSectionDelegation(
- private val delegate: MutableMap
-) : ConfigSection, MutableMap by delegate {
- override fun set(key: String, value: Any) {
- delegate[key] = value
- }
-
- override fun contains(key: String): Boolean {
- return delegate.containsKey(key)
- }
-
- override fun asMap(): Map {
- return delegate
- }
-
- override fun save() {
-
- }
-}
-
-
-@ToBeRemoved
-interface FileConfig : Config {
- fun deserialize(content: String): ConfigSection
-
- fun serialize(config: ConfigSection): String
-}
-
-
-@MiraiInternalAPI
-@ToBeRemoved
-abstract class FileConfigImpl internal constructor(
- private val rawContent: String
-) : FileConfig,
- ConfigSection {
-
- internal var file: File? = null
-
-
- @Suppress("unused")
- constructor(file: File) : this(file.readText()) {
- this.file = file
- }
-
-
- private val content by lazy {
- deserialize(rawContent)
- }
-
-
- override val size: Int get() = content.size
- override val entries: MutableSet> get() = content.entries
- override val keys: MutableSet get() = content.keys
- override val values: MutableCollection get() = content.values
- override fun containsKey(key: String): Boolean = content.containsKey(key)
- override fun containsValue(value: Any): Boolean = content.containsValue(value)
- override fun put(key: String, value: Any): Any? = content.put(key, value)
- override fun isEmpty(): Boolean = content.isEmpty()
- override fun putAll(from: Map) = content.putAll(from)
- override fun clear() = content.clear()
- override fun remove(key: String): Any? = content.remove(key)
-
- override fun save() {
- if (isReadOnly) {
- error("Config is readonly")
- }
- if (!((file?.exists())!!)) {
- file?.createNewFile()
- }
- file?.writeText(serialize(content))
- }
-
- val isReadOnly: Boolean get() = file == null
-
- override fun contains(key: String): Boolean {
- return content.contains(key)
- }
-
- override fun get(key: String): Any? {
- return content[key]
- }
-
- override fun set(key: String, value: Any) {
- content[key] = value
- }
-
- override fun asMap(): Map {
- return content.asMap()
- }
-
-}
-
-@ToBeRemoved
-@OptIn(MiraiInternalAPI::class)
-class JsonConfig internal constructor(
- content: String
-) : FileConfigImpl(content) {
- constructor(file: File) : this(file.readText()) {
- this.file = file
- }
-
- @UnstableDefault
- override fun deserialize(content: String): ConfigSection {
- if (content.isEmpty() || content.isBlank() || content == "{}") {
- return ConfigSectionImpl()
- }
- val gson = Gson()
- val typeRef = object : TypeToken