Merge remote-tracking branch 'origin/master'

This commit is contained in:
Him188 2020-02-12 23:11:11 +08:00
commit 35f06b3b1d
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,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() } var logger: MiraiConsoleLogger = DefaultLogger
PluginManager.loadPlugins() var path: String = System.getProperty("user.dir")
defaultCommands()
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() PluginManager.disableAllPlugins()
}) }
}
/**
fun defaultCommands() { * Defaults Commands are recommend to be replaced by plugin provided commands
class LoginCommand : Command( */
"login" object DefaultCommands {
) { class DefaultLoginCommand : Command(
override fun onCommand(args: List<String>): Boolean { "login"
if (args.size < 2) { ) {
println("\"/login qqnumber qqpassword \" to login a bot") override fun onCommand(args: List<String>): Boolean {
println("\"/login qq号 qq密码 \" 来登录一个BOT") if (args.size < 2) {
return false println("\"/login qqnumber qqpassword \" to login a bot")
} println("\"/login qq号 qq密码 \" 来登录一个BOT")
val qqNumber = args[0].toLong() return false
val qqPassword = args[1] }
println("login...") val qqNumber = args[0].toLong()
runBlocking { 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())
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() { fun main() {
val fullCommand = readLine() MiraiConsole.start()
if (fullCommand != null && fullCommand.startsWith("/")) { Runtime.getRuntime().addShutdownHook(thread(start = false) {
if (!CommandManager.runCommand(fullCommand)) { MiraiConsole.stop()
println("unknown command $fullCommand") })
println("未知指令 $fullCommand")
}
}
processNextCommandLine();
} }

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>
}
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
}
} }
}
fun getInt(key: String): Int { @Suppress("IMPLICIT_CAST_TO_ANY")
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt() 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 getFloat(key: String): Float { inline operator fun <reified T> ConfigSection.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat() 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 { interface ConfigSection : Config {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong() override fun getConfigSection(key: String): ConfigSection {
}
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)
} }