2
0
jiahua.liu 6 жил өмнө
parent
commit
df114f6958

+ 4 - 0
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,

+ 1 - 0
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"))

+ 11 - 3
mirai-console/src/main/kotlin/net/mamoe/mirai/plugin/Command.kt → 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<String, Command> = mutableMapOf()
@@ -25,6 +25,13 @@ object CommandManager {
         }
     }
 
+    fun unregister(command: Command) {
+        val allNames = mutableListOf<String>(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<String> = listOf()
+    val alias: List<String> = listOf(),
+    val description: String = ""
 ) {
     /**
      * 最高优先级监听器
@@ -58,3 +65,4 @@ abstract class Command(
         return true
     }
 }
+

+ 112 - 51
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<Long, Bot>()
+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")
-
-    thread { processNextCommandLine() }
-
-    PluginManager.loadPlugins()
-    defaultCommands()
+    val pluginManager: PluginManager
+        get() = PluginManager
 
-    Runtime.getRuntime().addShutdownHook(thread(start = false) {
-        PluginManager.disableAllPlugins()
-    })
-}
+    var logger: MiraiConsoleLogger = DefaultLogger
 
+    var path: String = System.getProperty("user.dir")
 
-fun defaultCommands() {
-    class LoginCommand : Command(
-        "login"
-    ) {
-        override fun onCommand(args: List<String>): 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 {
+    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()
+    }
+
+    /**
+     * Defaults Commands are recommend to be replaced by plugin provided commands
+     */
+    object DefaultCommands {
+        class DefaultLoginCommand : Command(
+            "login"
+        ) {
+            override fun onCommand(args: List<String>): 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())
-}
 
-tailrec fun processNextCommandLine() {
-    val fullCommand = readLine()
-    if (fullCommand != null && fullCommand.startsWith("/")) {
-        if (!CommandManager.runCommand(fullCommand)) {
-            println("unknown command $fullCommand")
-            println("未知指令 $fullCommand")
+    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() {
+
         }
     }
-    processNextCommandLine();
 }
+
+fun main() {
+    MiraiConsole.start()
+    Runtime.getRuntime().addShutdownHook(thread(start = false) {
+        MiraiConsole.stop()
+    })
+}
+

+ 164 - 14
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<String, Any>() {
+/**
+ * TODO: support all config types
+ */
+
+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<String>
+    fun getIntList(key: String): List<Int>
+    fun getFloatList(key: String): List<Float>
+    fun getDoubleList(key: String): List<Double>
+    fun getLongList(key: String): List<Long>
+    operator fun set(key: String, value: Any)
+    operator fun get(key: String): Any?
+    fun exist(key: String): Boolean
+    fun asMap(): Map<String, Any>
+}
+
+inline fun <reified T : Any> Config.withDefault(crossinline defaultValue: () -> T): ReadWriteProperty<Any, T> {
+    return object : ReadWriteProperty<Any, T> {
+        override fun getValue(thisRef: Any, property: KProperty<*>): T {
+            if ([email protected](property.name)) {
+                return defaultValue.invoke()
+            }
+            return getValue(thisRef, property)
+        }
+
+        override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
+            this@withDefault[property.name] = value
+        }
+    }
+}
+
+@Suppress("IMPLICIT_CAST_TO_ANY")
+inline operator fun <reified T> 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
+}
+
+inline operator fun <reified T> ConfigSection.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+    this[property.name] = value!!
+}
+
+
+interface ConfigSection : Config {
+    override fun getConfigSection(key: String): ConfigSection {
+        return (get(key) ?: error("ConfigSection does not contain $key ")) as ConfigSection
+    }
 
-    fun getString(key: String): String {
+    override fun getString(key: String): String {
         return (get(key) ?: error("ConfigSection does not contain $key ")).toString()
     }
 
-    fun getInt(key: String): Int {
+    override fun getInt(key: String): Int {
         return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt()
     }
 
-    fun getFloat(key: String): Float {
+    override fun getFloat(key: String): Float {
         return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat()
     }
 
-    fun getDouble(key: String): Double {
+    override fun getDouble(key: String): Double {
         return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toDouble()
     }
 
-    fun getLong(key: String): Long {
+    override fun getLong(key: String): Long {
         return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong()
     }
 
-    fun getConfigSection(key: String): ConfigSection {
-        return (get(key) ?: error("ConfigSection does not contain $key ")) as ConfigSection
+    override fun getList(key: String): List<*> {
+        return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>)
     }
 
-    fun getStringList(key: String): List<String> {
+    override fun getStringList(key: String): List<String> {
         return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString() }
     }
 
-    fun getIntList(key: String): List<Int> {
+    override fun getIntList(key: String): List<Int> {
         return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toInt() }
     }
 
-    fun getFloatList(key: String): List<Float> {
+    override fun getFloatList(key: String): List<Float> {
         return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toFloat() }
     }
 
-    fun getDoubleList(key: String): List<Double> {
+    override fun getDoubleList(key: String): List<Double> {
         return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toDouble() }
     }
 
-    fun getLongList(key: String): List<Long> {
+    override fun getLongList(key: String): List<Long> {
         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<String, Any>(), 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<String, Any> {
+        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)
+        }
+    }
 }

+ 9 - 22
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()
     }
 
+    /**
+     * TODO: support all config types
+     */
+
     @UnstableDefault
-    fun loadConfig(fileName: String = "config.json"): ConfigSection {
-        var content = File(dataFolder.name + "/" + fileName)
-            .also {
-                if (!it.exists()) it.createNewFile()
-            }.readText()
-
-        if (content == "") {
-            content = "{}"
-        }
-        return Json.parse(
-            ConfigSection.serializer(),
-            content
-        )
+    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)
     }