Commit 35f06b3b authored by Him188's avatar Him188

Merge remote-tracking branch 'origin/master'

parents fec3702b 240183d5
...@@ -24,6 +24,10 @@ object MiraiHttpAPIServer { ...@@ -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,
......
...@@ -25,6 +25,7 @@ fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" ...@@ -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"))
......
...@@ -7,7 +7,7 @@ ...@@ -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 { ...@@ -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 { ...@@ -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( ...@@ -58,3 +65,4 @@ abstract class Command(
return true return true
} }
} }
...@@ -9,73 +9,134 @@ ...@@ -9,73 +9,134 @@
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() }
PluginManager.loadPlugins()
defaultCommands()
Runtime.getRuntime().addShutdownHook(thread(start = false) { var logger: MiraiConsoleLogger = DefaultLogger
PluginManager.disableAllPlugins()
})
}
var path: String = System.getProperty("user.dir")
fun defaultCommands() { val version = " 0.13"
class LoginCommand : Command( val build = "Beta"
"login"
) { fun start() {
override fun onCommand(args: List<String>): Boolean { logger("Mirai-console v${version} $build is still in testing stage, majority feature is available")
if (args.size < 2) { logger("Mirai-console v${version} $build 还处于测试阶段, 大部分功能可用")
println("\"/login qqnumber qqpassword \" to login a bot") logger()
println("\"/login qq号 qq密码 \" 来登录一个BOT") logger("Mirai-console now running under " + System.getProperty("user.dir"))
return false logger("Mirai-console 正在 " + System.getProperty("user.dir") + "下运行")
} logger()
val qqNumber = args[0].toLong() logger("Get news in github: https://github.com/mamoe/mirai")
val qqPassword = args[1] logger("在Github中获取项目最新进展: https://github.com/mamoe/mirai")
println("login...") logger("Mirai为开源项目,请自觉遵守开源项目协议")
runBlocking { 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 { 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())
}
tailrec fun processNextCommandLine() { object CommandListener {
val fullCommand = readLine() fun start() {
if (fullCommand != null && fullCommand.startsWith("/")) { thread {
if (!CommandManager.runCommand(fullCommand)) { processNextCommandLine()
println("unknown command $fullCommand") }
println("未知指令 $fullCommand") }
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()
})
}
...@@ -9,55 +9,205 @@ ...@@ -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
*/
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 (!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
}
}
}
@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() 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() 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() 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() 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() return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong()
} }
fun getConfigSection(key: String): ConfigSection { override fun getList(key: String): List<*> {
return (get(key) ?: error("ConfigSection does not contain $key ")) as ConfigSection 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() } 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)
}
}
} }
\ No newline at end of file
...@@ -9,6 +9,7 @@ ...@@ -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 { ...@@ -66,32 +67,18 @@ abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope {
this.onEnable() this.onEnable()
} }
/**
* TODO: support all config types
*/
@UnstableDefault @UnstableDefault
fun loadConfig(fileName: String = "config.json"): ConfigSection { fun loadConfig(fileName: String): Config {
var content = File(dataFolder.name + "/" + fileName) return JsonConfig.load(File(fileName))
.also {
if (!it.exists()) it.createNewFile()
}.readText()
if (content == "") {
content = "{}"
}
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)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment