Smart Config

This commit is contained in:
jiahua.liu 2020-02-12 22:28:59 +08:00
parent e072c5b10e
commit df114f6958
6 changed files with 309 additions and 98 deletions

View File

@ -24,6 +24,10 @@ object MiraiHttpAPIServer {
SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同 SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同
} }
fun setAuthKey(key: String) {
SessionManager.authKey = key
}
@UseExperimental(KtorExperimentalAPI::class) @UseExperimental(KtorExperimentalAPI::class)
fun start( fun start(
port: Int = 8080, port: Int = 8080,

View File

@ -25,6 +25,7 @@ fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies { dependencies {
api(project(":mirai-core")) api(project(":mirai-core"))
api(project(":mirai-core-qqandroid")) api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main")) runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
api(kotlin("serialization")) api(kotlin("serialization"))

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.plugin import net.mamoe.mirai.plugin.PluginManager
object CommandManager { object CommandManager {
private val registeredCommand: MutableMap<String, Command> = mutableMapOf() 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 { fun runCommand(fullCommand: String): Boolean {
val blocks = fullCommand.split(" ") val blocks = fullCommand.split(" ")
val commandHead = blocks[0].replace("/", "") val commandHead = blocks[0].replace("/", "")
@ -43,12 +50,12 @@ object CommandManager {
return true return true
} }
} }
abstract class Command( abstract class Command(
val name: String, 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 return true
} }
} }

View File

@ -9,39 +9,57 @@
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.plugin.Command import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.plugin.CommandManager 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 net.mamoe.mirai.plugin.PluginManager
import java.io.File
import kotlin.concurrent.thread import kotlin.concurrent.thread
val bots = mutableMapOf<Long, Bot>() object MiraiConsole {
val bots
get() = Bot.instances
fun main() { val pluginManager: PluginManager
println("loading Mirai in console environments") get() = PluginManager
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() } var logger: MiraiConsoleLogger = DefaultLogger
PluginManager.loadPlugins() var path: String = System.getProperty("user.dir")
defaultCommands()
Runtime.getRuntime().addShutdownHook(thread(start = false) { val version = " 0.13"
PluginManager.disableAllPlugins() 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( * Defaults Commands are recommend to be replaced by plugin provided commands
*/
object DefaultCommands {
class DefaultLoginCommand : Command(
"login" "login"
) { ) {
override fun onCommand(args: List<String>): Boolean { override fun onCommand(args: List<String>): Boolean {
@ -53,29 +71,72 @@ fun defaultCommands() {
val qqNumber = args[0].toLong() val qqNumber = args[0].toLong()
val qqPassword = args[1] val qqPassword = args[1]
println("login...") println("login...")
runBlocking {
try { try {
Bot(qqNumber, qqPassword).also { runBlocking {
it.login() Bot(qqNumber, qqPassword).alsoLogin()
bots[qqNumber] = it
} }
} catch (e: Exception) { } catch (e: Exception) {
println("$qqNumber login failed") println("$qqNumber login failed")
} }
}
return true return true
} }
} }
CommandManager.register(LoginCommand()) }
object CommandListener {
fun start() {
thread {
processNextCommandLine()
}
} }
tailrec fun processNextCommandLine() { tailrec fun processNextCommandLine() {
val fullCommand = readLine() val fullCommand = readLine()
if (fullCommand != null && fullCommand.startsWith("/")) { if (fullCommand != null && fullCommand.startsWith("/")) {
if (!CommandManager.runCommand(fullCommand)) { if (!CommandManager.runCommand(fullCommand)) {
println("unknown command $fullCommand") logger("unknown command $fullCommand")
println("未知指令 $fullCommand") logger("未知指令 $fullCommand")
} }
} }
processNextCommandLine(); 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() {
}
}
}
fun main() {
MiraiConsole.start()
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
})
}

View File

@ -9,55 +9,205 @@
package net.mamoe.mirai.plugin package net.mamoe.mirai.plugin
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable 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 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
*/
fun getString(key: String): String { interface Config {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString() 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>
} }
fun getInt(key: String): Int { inline fun <reified T : Any> Config.withDefault(crossinline defaultValue: () -> T): ReadWriteProperty<Any, T> {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt() return object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (!this@withDefault.exist(property.name)) {
return defaultValue.invoke()
}
return getValue(thisRef, property)
} }
fun getFloat(key: String): Float { override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat() this@withDefault[property.name] = value
}
}
} }
fun getDouble(key: String): Double { @Suppress("IMPLICIT_CAST_TO_ANY")
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toDouble() 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
} }
fun getLong(key: String): Long { inline operator fun <reified T> ConfigSection.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong() this[property.name] = value!!
} }
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 return (get(key) ?: error("ConfigSection does not contain $key ")) as ConfigSection
} }
fun getStringList(key: String): List<String> { 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<String> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString() } 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() } 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() } 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() } 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() } 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)
}
}
}

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.plugin package net.mamoe.mirai.plugin
import Command
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -66,32 +67,18 @@ abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope {
this.onEnable() this.onEnable()
} }
@UnstableDefault /**
fun loadConfig(fileName: String = "config.json"): ConfigSection { * TODO: support all config types
var content = File(dataFolder.name + "/" + fileName) */
.also {
if (!it.exists()) it.createNewFile()
}.readText()
if (content == "") { @UnstableDefault
content = "{}" fun loadConfig(fileName: String): Config {
} return JsonConfig.load(File(fileName))
return Json.parse(
ConfigSection.serializer(),
content
)
} }
@UnstableDefault @UnstableDefault
fun saveConfig(config: ConfigSection, fileName: String = "config.json") { fun saveConfig(config: Config, fileName: String = "config.json") {
val content = Json.stringify( JsonConfig.save(file = File(fileName), config = config as JsonConfig)
ConfigSection.serializer(),
config
)
File(dataFolder.name + "/" + fileName)
.also {
if (!it.exists()) it.createNewFile()
}.writeText(content)
} }