mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-01 13:11:34 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
35f06b3b1d
@ -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,
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user