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相同的方法生成, 但意义不同
}
fun setAuthKey(key: String) {
SessionManager.authKey = key
}
@UseExperimental(KtorExperimentalAPI::class)
fun start(
port: Int = 8080,

View File

@ -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"))

View File

@ -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
}
}

View File

@ -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")
val pluginManager: PluginManager
get() = PluginManager
thread { processNextCommandLine() }
var logger: MiraiConsoleLogger = DefaultLogger
PluginManager.loadPlugins()
defaultCommands()
var path: String = System.getProperty("user.dir")
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()
})
}
}
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 {
/**
* 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())
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() {
val fullCommand = readLine()
if (fullCommand != null && fullCommand.startsWith("/")) {
if (!CommandManager.runCommand(fullCommand)) {
println("unknown command $fullCommand")
println("未知指令 $fullCommand")
}
}
processNextCommandLine();
fun main() {
MiraiConsole.start()
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
})
}

View File

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

View File

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