Public API stabilization:

- Redesign MiraiConsoleFrontEndDescription
- Redesign MiraiConsoleImplementation
- Rework path-relevant properties
- Add docs
This commit is contained in:
Him188 2020-08-22 19:43:07 +08:00
parent bcd93ab34e
commit 218fb2bdcc
23 changed files with 442 additions and 282 deletions

View File

@ -12,6 +12,7 @@
package net.mamoe.mirai.console package net.mamoe.mirai.console
import com.vdurmont.semver4j.Semver
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
@ -24,8 +25,8 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File import java.nio.file.Path
import java.util.* import java.time.Instant
/** /**
@ -36,19 +37,20 @@ import java.util.*
*/ */
public interface MiraiConsole : CoroutineScope { public interface MiraiConsole : CoroutineScope {
/** /**
* Console 运行路径 * Console 运行根目录, 由前端决定确切路径.
*
* 所有子模块都会在这个目录之下创建子目录.
*/ */
public val rootDir: File public val rootPath: Path
/** /**
* Console 前端接口 * Console 主日志.
*/ *
@ConsoleExperimentalAPI * **实现细节**: 这个 [MiraiLogger] [MiraiLogger.identity] 通常为 `main`
public val frontEnd: MiraiConsoleFrontEnd *
* **注意**: 插件不应该在任何时刻使用它.
/**
* 与前端交互所使用的 Logger
*/ */
@ConsoleInternalAPI
public val mainLogger: MiraiLogger public val mainLogger: MiraiLogger
/** /**
@ -58,13 +60,22 @@ public interface MiraiConsole : CoroutineScope {
*/ */
public val builtInPluginLoaders: List<PluginLoader<*, *>> public val builtInPluginLoaders: List<PluginLoader<*, *>>
public val buildDate: Date /**
* Console 后端构建时间
*/
public val buildDate: Instant
public val version: String /**
* Console 后端版本号
*/
public val version: Semver
@ConsoleExperimentalAPI @ConsoleExperimentalAPI
public val pluginCenter: PluginCenter public val pluginCenter: PluginCenter
/**
* 创建一个 logger
*/
@ConsoleExperimentalAPI @ConsoleExperimentalAPI
public fun newLogger(identity: String?): MiraiLogger public fun newLogger(identity: String?): MiraiLogger
@ -88,8 +99,9 @@ public interface MiraiConsole : CoroutineScope {
public fun addBot(id: Long, password: String, configuration: BotConfiguration.() -> Unit = {}): Bot = public fun addBot(id: Long, password: String, configuration: BotConfiguration.() -> Unit = {}): Bot =
Bot(id, password) { Bot(id, password) {
fileBasedDeviceInfo() fileBasedDeviceInfo()
this.loginSolver = frontEnd.createLoginSolver()
redirectNetworkLogToDirectory() redirectNetworkLogToDirectory()
this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this)
configuration() configuration()
} }
} }

View File

@ -1,53 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger
/**
* 只需要实现一个这个传入 MiraiConsole 就可以绑定 UI 层与 Console
*
* 需要保证线程安全
*/
@ConsoleExperimentalAPI
@ConsoleFrontEndImplementation
public interface MiraiConsoleFrontEnd {
/**
* 名称
*/
public val name: String
/**
* 版本
*/
public val version: String
public fun loggerFor(identity: String?): MiraiLogger
/**
* UI 层接受一个新的bot
* */
public fun pushBot(
bot: Bot
)
/**
* UI 层提供一个输入, 相当于 [readLine]
*/
public suspend fun requestInput(hint: String): String
/**
* UI 层创建一个 [LoginSolver]
*/
public fun createLoginSolver(): LoginSolver
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console
import com.vdurmont.semver4j.Semver
/**
* 有关前端实现的信息
*/
public interface MiraiConsoleFrontEndDescription {
/**
* 此前端实现的名称
*/
public val name: String
/**
* 此前端实现的提供者
*/
public val vendor: String
/**
* 此前端实现的名称
*/
public val version: Semver
/**
* 返回显示在 [MiraiConsole] 启动时的信息
*/
public fun render(): String = "Frontend ${name}: version ${version}, provided by $vendor"
}

View File

@ -13,6 +13,7 @@ package net.mamoe.mirai.console
import kotlinx.atomicfu.locks.withLock import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage
@ -20,8 +21,11 @@ import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -51,15 +55,16 @@ public interface MiraiConsoleImplementation : CoroutineScope {
public override val coroutineContext: CoroutineContext public override val coroutineContext: CoroutineContext
/** /**
* Console 运行路径 * Console 运行根目录
* @see MiraiConsole.rootPath
*/ */
public val rootDir: File public val rootPath: Path
/** /**
* Console 前端接口 * Console 前端接口
*/ */
@ConsoleExperimentalAPI @ConsoleExperimentalAPI
public val frontEnd: MiraiConsoleFrontEnd public val frontEndDescription: MiraiConsoleFrontEndDescription
/** /**
* 与前端交互所使用的 Logger * 与前端交互所使用的 Logger
@ -79,6 +84,28 @@ public interface MiraiConsoleImplementation : CoroutineScope {
public val configStorageForJarPluginLoader: PluginDataStorage public val configStorageForJarPluginLoader: PluginDataStorage
public val dataStorageForBuiltIns: PluginDataStorage public val dataStorageForBuiltIns: PluginDataStorage
/**
* @see ConsoleInput 的实现
*/
public val consoleInput: ConsoleInput
/**
* 创建一个 [LoginSolver]
*
* **调用备注**: 此函数通常在构造 [Bot] 实例时调用.
*
* @param requesterBot 请求者 [Bot.id]
* @param configuration 请求者 [Bot.configuration]
*
* @see LoginSolver.Default
*/
public fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver
/**
* 创建一个 logger
*/
public fun newLogger(identity: String?): MiraiLogger
public companion object { public companion object {
internal lateinit var instance: MiraiConsoleImplementation internal lateinit var instance: MiraiConsoleImplementation
private val initLock = ReentrantLock() private val initLock = ReentrantLock()

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManagerImpl.allRegisteredCommands import net.mamoe.mirai.console.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.command.CommandManagerImpl.register import net.mamoe.mirai.console.command.CommandManagerImpl.register
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.message.nextMessageOrNull
import net.mamoe.mirai.utils.secondsToMillis import net.mamoe.mirai.utils.secondsToMillis
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -79,6 +80,7 @@ public object BuiltInCommands {
}.fold( }.fold(
onSuccess = { sendMessage("mirai-console stopped successfully.") }, onSuccess = { sendMessage("mirai-console stopped successfully.") },
onFailure = { onFailure = {
@OptIn(ConsoleInternalAPI::class)
MiraiConsole.mainLogger.error(it) MiraiConsole.mainLogger.error(it)
sendMessage(it.localizedMessage ?: it.message ?: it.toString()) sendMessage(it.localizedMessage ?: it.message ?: it.toString())
} }

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import java.io.File import java.io.File
import java.nio.file.Path
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -99,7 +100,7 @@ public interface MultiFilePluginDataStorage : PluginDataStorage {
/** /**
* 存放 [PluginData] 的目录. * 存放 [PluginData] 的目录.
*/ */
public val directory: File public val directoryPath: Path
public companion object { public companion object {
/** /**
@ -109,7 +110,11 @@ public interface MultiFilePluginDataStorage : PluginDataStorage {
*/ */
@JvmStatic @JvmStatic
@JvmName("create") @JvmName("create")
public operator fun invoke(directory: File): MultiFilePluginDataStorage = public operator fun invoke(directory: Path): MultiFilePluginDataStorage =
MultiFilePluginDataStorageImpl(directory) MultiFilePluginDataStorageImpl(directory)
} }
} }
@get:JvmSynthetic
public inline val MultiFilePluginDataStorage.directory: File
get() = this.directoryPath.toFile()

View File

@ -9,10 +9,13 @@
package net.mamoe.mirai.console.internal package net.mamoe.mirai.console.internal
import java.util.* import com.vdurmont.semver4j.Semver
import java.time.Instant
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
@JvmStatic @JvmStatic
val buildDate: Date = Date(1597935352287L) // 2020-08-20 22:55:52 val buildDate: Instant = Instant.ofEpochMilli(1597935352287L) // 2020-08-20 22:55:52
const val version: String = "1.0-M2-1"
@JvmStatic
val version: Semver = Semver("1.0-M2-1")
} }

View File

@ -11,12 +11,13 @@
package net.mamoe.mirai.console.internal package net.mamoe.mirai.console.internal
import com.vdurmont.semver4j.Semver
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.IllegalMiraiConsoleImplementationError import net.mamoe.mirai.console.IllegalMiraiConsoleImplementationError
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEnd import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.Command.Companion.primaryName
@ -30,12 +31,12 @@ import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.*
import java.io.File import java.nio.file.Path
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.time.Instant
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -45,36 +46,39 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
MiraiConsole { MiraiConsole {
override val pluginCenter: PluginCenter get() = CuiPluginCenter override val pluginCenter: PluginCenter get() = CuiPluginCenter
private val instance: MiraiConsoleImplementation get() = MiraiConsoleImplementation.instance private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
override val buildDate: Date get() = MiraiConsoleBuildConstants.buildDate override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
override val version: String get() = MiraiConsoleBuildConstants.version override val version: Semver by MiraiConsoleBuildConstants::version
override val rootDir: File get() = instance.rootDir override val rootPath: Path by instance::rootPath
override val frontEnd: MiraiConsoleFrontEnd get() = instance.frontEnd override val frontEndDescription: MiraiConsoleFrontEndDescription by instance::frontEndDescription
@ConsoleExperimentalAPI @OptIn(ConsoleInternalAPI::class)
override val mainLogger: MiraiLogger override val mainLogger: MiraiLogger by instance::mainLogger
get() = instance.mainLogger override val coroutineContext: CoroutineContext by instance::coroutineContext
override val coroutineContext: CoroutineContext get() = instance.coroutineContext override val builtInPluginLoaders: List<PluginLoader<*, *>> by instance::builtInPluginLoaders
override val builtInPluginLoaders: List<PluginLoader<*, *>> get() = instance.builtInPluginLoaders override val consoleCommandSender: ConsoleCommandSender by instance::consoleCommandSender
override val consoleCommandSender: ConsoleCommandSender get() = instance.consoleCommandSender
override val dataStorageForJarPluginLoader: PluginDataStorage get() = instance.dataStorageForJarPluginLoader override val dataStorageForJarPluginLoader: PluginDataStorage by instance::dataStorageForJarPluginLoader
override val configStorageForJarPluginLoader: PluginDataStorage get() = instance.configStorageForJarPluginLoader override val configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader
override val dataStorageForBuiltIns: PluginDataStorage get() = instance.dataStorageForBuiltIns override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
override val consoleInput: ConsoleInput by instance::consoleInput
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
instance.createLoginSolver(requesterBot, configuration)
init { init {
DefaultLogger = { identity -> this.newLogger(identity) } DefaultLogger = this::newLogger
} }
@ConsoleExperimentalAPI @ConsoleExperimentalAPI
override fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity) override fun newLogger(identity: String?): MiraiLogger = instance.newLogger(identity)
@OptIn(ConsoleExperimentalAPI::class) @OptIn(ConsoleExperimentalAPI::class)
internal fun doStart() { internal fun doStart() {
val buildDateFormatted = SimpleDateFormat("yyyy-MM-dd").format(buildDate) val buildDateFormatted = SimpleDateFormat("yyyy-MM-dd").format(buildDate)
mainLogger.info { "Starting mirai-console..." } mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." } mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { "Frontend ${frontEnd.name}: version $version." } mainLogger.info { frontEndDescription.render() }
if (coroutineContext[Job] == null) { if (coroutineContext[Job] == null) {
throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")

View File

@ -14,14 +14,15 @@ import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.Yaml
import java.io.File import java.io.File
import java.nio.file.Path
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Suppress("RedundantVisibilityModifier") // might be public in the future @Suppress("RedundantVisibilityModifier") // might be public in the future
internal open class MultiFilePluginDataStorageImpl( internal open class MultiFilePluginDataStorageImpl(
public final override val directory: File public final override val directoryPath: Path
) : PluginDataStorage, MultiFilePluginDataStorage { ) : PluginDataStorage, MultiFilePluginDataStorage {
init { init {
directory.mkdir() directoryPath.mkdir()
} }
public override fun <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): T = public override fun <T : PluginData> load(holder: PluginDataHolder, dataClass: Class<T>): T =
@ -53,17 +54,17 @@ internal open class MultiFilePluginDataStorageImpl(
protected open fun getPluginDataFile(holder: PluginDataHolder, clazz: KClass<*>): File = with(clazz) { protected open fun getPluginDataFile(holder: PluginDataHolder, clazz: KClass<*>): File = with(clazz) {
val name = findASerialName() val name = findASerialName()
val dir = File(directory, holder.name) val dir = directoryPath.resolve(holder.name)
if (dir.isFile) { if (dir.isFile) {
error("Target directory ${dir.path} for holder $holder is occupied by a file therefore data $qualifiedNameOrTip can't be saved.") error("Target directory $dir for holder $holder is occupied by a file therefore data $qualifiedNameOrTip can't be saved.")
} }
dir.mkdir() dir.mkdir()
val file = File(directory, name) val file = directoryPath.resolve(name)
if (file.isDirectory) { if (file.isDirectory) {
error("Target file $file is occupied by a directory therefore data $qualifiedNameOrTip can't be saved.") error("Target file $file is occupied by a directory therefore data $qualifiedNameOrTip can't be saved.")
} }
return file return file.toFile()
} }
@ConsoleExperimentalAPI @ConsoleExperimentalAPI
@ -78,4 +79,8 @@ internal open class MultiFilePluginDataStorageImpl(
file.writeText(Yaml.default.encodeToString(pluginData.updaterSerializer, Unit)) file.writeText(Yaml.default.encodeToString(pluginData.updaterSerializer, Unit))
} }
} }
} }
internal fun Path.mkdir(): Boolean = this.toFile().mkdir()
internal val Path.isFile: Boolean get() = this.toFile().isFile
internal val Path.isDirectory: Boolean get() = this.toFile().isDirectory

View File

@ -13,6 +13,7 @@ import kotlinx.atomicfu.AtomicLong
import kotlinx.atomicfu.locks.withLock import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
@ -20,8 +21,8 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.safeLoader import net.mamoe.mirai.console.plugin.safeLoader
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@ -60,11 +61,8 @@ internal abstract class JvmPluginInternal(
private var firstRun = true private var firstRun = true
final override val dataFolder: File by lazy { final override val dataFolderPath: Path by lazy {
File( PluginManager.pluginsPath.resolve(description.name).apply { mkdir() }
PluginManager.pluginsDataFolder,
description.name
).apply { mkdir() }
} }
internal fun internalOnDisable() { internal fun internalOnDisable() {

View File

@ -16,14 +16,15 @@ import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.cast
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.plugin.* import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.info
import java.io.File import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
internal object PluginManagerImpl : PluginManager { internal object PluginManagerImpl : PluginManager {
override val pluginsDir = File(MiraiConsole.rootDir, "plugins").apply { mkdir() } override val pluginsPath: Path = MiraiConsole.rootPath.resolve("plugins").apply { mkdir() }
override val pluginsDataFolder = File(MiraiConsole.rootDir, "data").apply { mkdir() } override val pluginsDataPath = MiraiConsole.rootPath.resolve("data").apply { mkdir() }
@Suppress("ObjectPropertyName") @Suppress("ObjectPropertyName")
private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf() private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf()
@ -45,15 +46,15 @@ internal object PluginManagerImpl : PluginManager {
?.getDescription(this) ?.getDescription(this)
?: error("Plugin is unloaded") ?: error("Plugin is unloaded")
override fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean = loadersLock.withLock { override fun PluginLoader<*, *>.register(): Boolean = loadersLock.withLock {
if (_pluginLoaders.any { it::class == loader }) { if (_pluginLoaders.any { it::class == this }) {
return false return false
} }
_pluginLoaders.add(loader) _pluginLoaders.add(this)
} }
override fun unregisterPluginLoader(loader: PluginLoader<*, *>) = loadersLock.withLock { override fun PluginLoader<*, *>.unregister() = loadersLock.withLock {
_pluginLoaders.remove(loader) _pluginLoaders.remove(this)
} }
init { init {

View File

@ -0,0 +1,15 @@
package net.mamoe.mirai.console.internal.util
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.requestInput
@Suppress("unused")
internal object ConsoleInputImpl : ConsoleInput {
private val inputLock = Mutex()
override suspend fun requestInput(hint: String): String =
inputLock.withLock { MiraiConsole.requestInput(hint) }
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.plugin
// TODO: 2020/8/22 Document PluginMissingDependencyException
public class PluginMissingDependencyException : PluginResolutionException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
// TODO: 2020/8/22 Document PluginResolutionException
public open class PluginResolutionException : Exception {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -14,11 +14,12 @@ package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File import java.io.File
import java.nio.file.Path
/** /**
* 表示一个 mirai-console 插件. * 表示一个 mirai-console 插件.
* *
* @see PluginDescription 插件描述 * @see PluginDescription 插件描述 需由 [PluginLoader] 帮助提供[PluginLoader.description]
* @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件 * @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件
* @see PluginFileExtensions 支持文件系统存储的扩展 * @see PluginFileExtensions 支持文件系统存储的扩展
* *
@ -71,13 +72,33 @@ public interface PluginFileExtensions {
/** /**
* 数据目录 * 数据目录
*/ */
public val dataFolder: File public val dataFolderPath: Path
/** /**
* 从数据目录获取一个文件, 若不存在则创建文件. * 从数据目录获取一个文件.
* @see dataFolderPath
*/ */
@JvmDefault @JvmDefault
public fun file(relativePath: String): File = File(dataFolder, relativePath).apply { createNewFile() } public fun resolveDataFile(relativePath: String): File = dataFolderPath.resolve(relativePath).toFile()
// TODO: 2020/7/11 add `fun path(...): Path` ? /**
* 从数据目录获取一个文件.
* @see dataFolderPath
*/
@JvmDefault
public fun resolveDataPath(relativePath: String): Path = dataFolderPath.resolve(relativePath)
/**
* 从数据目录获取一个文件.
* @see dataFolderPath
*/
@JvmDefault
public fun resolveDataFile(relativePath: Path): File = dataFolderPath.resolve(relativePath).toFile()
/**
* 从数据目录获取一个文件路径.
* @see dataFolderPath
*/
@JvmDefault
public fun resolveDataPath(relativePath: Path): Path = dataFolderPath.resolve(relativePath)
} }

View File

@ -21,18 +21,35 @@ import java.io.File
* *
* 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护. * 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护.
* *
* ### 内建加载器
* - [JarPluginLoader] Jar 插件加载器
*
* ### 扩展加载器
* 插件被允许扩展一个加载器 可通过 [PluginManager.registerPluginLoader]
*
* @see JarPluginLoader Jar 插件加载器 * @see JarPluginLoader Jar 插件加载器
* @see PluginManager.registerPluginLoader 注册一个扩展的插件加载器
*/ */
public interface PluginLoader<P : Plugin, D : PluginDescription> { public interface PluginLoader<P : Plugin, D : PluginDescription> {
/** /**
* 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表. 此函数只会被调用一次 * 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表.
*
* console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
*
* **实现细节:** 此函数只*应该* console 启动时被调用一次. 但取决于前端实现不同, 或可能由于被一些插件需要, 此函数也可能会被多次调用.
*/ */
public fun listPlugins(): List<D> public fun listPlugins(): List<D>
/** /**
* 获取此插件的描述 * 获取此插件的描述.
*
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
*
* 若在 console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件.
* *
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等). * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
*
* @see PluginDescription 插件描述
*/ */
@get:JvmName("getPluginDescription") @get:JvmName("getPluginDescription")
@get:Throws(PluginLoadException::class) @get:Throws(PluginLoadException::class)
@ -41,11 +58,19 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
/** /**
* 加载一个插件 (实例), 但不 [启用][enable] . 返回加载成功的主类实例 * 加载一个插件 (实例), 但不 [启用][enable] . 返回加载成功的主类实例
* *
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
*
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
*/ */
@Throws(PluginLoadException::class) @Throws(PluginLoadException::class)
public fun load(description: D): P public fun load(description: D): P
/**
* 启用这个插件.
*
* **实现约定**: 若插件已经启用, 抛出
*/
public fun enable(plugin: P) public fun enable(plugin: P)
public fun disable(plugin: P) public fun disable(plugin: P)
} }
@ -62,7 +87,7 @@ public open class PluginLoadException : RuntimeException {
} }
/** /**
* '/plugins' 目录中的插件的加载器. 每个加载器需绑定一个后缀. * ['/plugins'][PluginManager.pluginsPath] 目录中的插件的加载器. 每个加载器需绑定一个后缀.
* *
* @see AbstractFilePluginLoader 默认基础实现 * @see AbstractFilePluginLoader 默认基础实现
* @see JarPluginLoader 内建的 Jar (JVM) 插件加载器. * @see JarPluginLoader 内建的 Jar (JVM) 插件加载器.
@ -75,13 +100,16 @@ public interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoa
} }
/** /**
* [FilePluginLoader] 的默认基础实现 * [FilePluginLoader] 的默认基础实现.
*
* @see FilePluginLoader
*/ */
public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>( public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
public override val fileSuffix: String public override val fileSuffix: String
) : FilePluginLoader<P, D> { ) : FilePluginLoader<P, D> {
private fun pluginsFilesSequence(): Sequence<File> = private fun pluginsFilesSequence(): Sequence<File> =
PluginManager.pluginsDir.walk().filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) } PluginManager.pluginsPath.toFile().walk()
.filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
/** /**
* 读取扫描到的后缀与 [fileSuffix] 相同的文件中的 [PluginDescription] * 读取扫描到的后缀与 [fileSuffix] 相同的文件中的 [PluginDescription]

View File

@ -14,41 +14,74 @@ package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import java.io.File import java.io.File
import java.nio.file.Path
/** /**
* 插件管理器. * 插件管理器.
*
* [PluginManager] 管理所有 [插件加载器][PluginLoader], 储存对所有插件的引用 ([plugins]), 但不直接与 [插件实例][Plugin] 交互.
*
* 有关 [插件加载][PluginLoader.load], [插件启用][PluginLoader.enable] 等操作都由 [PluginLoader] 完成.
* [PluginManager] 仅作为一个联系所有 [插件加载器][PluginLoader], 使它们互相合作的桥梁.
*
* 若要主动加载一个插件, 请获取相应插件的 [PluginLoader], 然后使用 [PluginLoader.enable]
*
* @see Plugin 插件
* @see PluginLoader 插件加载器
*/ */
public interface PluginManager { public interface PluginManager {
/** /**
* `$rootDir/plugins` * 插件自身存放路径. 由前端决定具体路径.
*
* **实现细节**: terminal 前端实现为 `$rootPath/plugins`
*
* @see pluginsFolder [File] 类型
*/ */
public val pluginsDir: File public val pluginsPath: Path
/** /**
* `$rootDir/data` * 插件数据存放路径
*
* **实现细节**: terminal 前端实现为 `$rootPath/data`
*
* @see pluginsDataFolder [File] 类型
*/ */
public val pluginsDataFolder: File public val pluginsDataPath: Path
/** /**
* 已加载的插件列表 * 已加载的插件列表
*
* @return 只读列表
*/ */
public val plugins: List<Plugin> public val plugins: List<Plugin>
/** /**
* 内建的插件加载器列表. [MiraiConsole] 初始化. * 内建的插件加载器列表. [MiraiConsole] 初始化.
* *
* @return 不可变的 list. * @return 只读列表
*/ */
public val builtInLoaders: List<PluginLoader<*, *>> public val builtInLoaders: List<PluginLoader<*, *>>
/** /**
* 由插件创建的 [PluginLoader] * 由插件创建的 [PluginLoader]
*
* @return 只读列表
*/ */
public val pluginLoaders: List<PluginLoader<*, *>> public val pluginLoaders: List<PluginLoader<*, *>>
public fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean /**
* 注册一个扩展的插件加载器
*
* @see PluginLoader 插件加载器
*/
public fun PluginLoader<*, *>.register(): Boolean
public fun unregisterPluginLoader(loader: PluginLoader<*, *>): Boolean /**
* 取消注册一个扩展的插件加载器
*
* @see PluginLoader 插件加载器
*/
public fun PluginLoader<*, *>.unregister(): Boolean
/** /**
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription] * 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription]
@ -56,26 +89,23 @@ public interface PluginManager {
public val Plugin.description: PluginDescription public val Plugin.description: PluginDescription
public companion object INSTANCE : PluginManager by PluginManagerImpl { public companion object INSTANCE : PluginManager by PluginManagerImpl {
override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description } // due to Kotlin's bug
public override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description }
public override fun PluginLoader<*, *>.register(): Boolean = PluginManagerImpl.run { register() }
public override fun PluginLoader<*, *>.unregister(): Boolean = PluginManagerImpl.run { unregister() }
} }
} }
@JvmSynthetic /**
public inline fun PluginLoader<*, *>.register(): Boolean = PluginManager.registerPluginLoader(this) * @see PluginManager.pluginsPath
*/
@get:JvmSynthetic
public inline val PluginManager.pluginsFolder: File
get() = pluginsPath.toFile()
@JvmSynthetic /**
public inline fun PluginLoader<*, *>.unregister(): Boolean = PluginManager.unregisterPluginLoader(this) * @see PluginManager.pluginsDataPath
*/
public class PluginMissingDependencyException : PluginResolutionException { @get:JvmSynthetic
public constructor() : super() public inline val PluginManager.pluginsDataFolder: File
public constructor(message: String?) : super(message) get() = pluginsDataPath.toFile()
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
public open class PluginResolutionException : Exception {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -22,21 +22,53 @@ import java.io.File
/** /**
* 插件描述 * 插件描述.
*
* @see Plugin
*/ */
public interface PluginDescription { public interface PluginDescription {
/**
* 插件类型. 将会决定加载顺序
*
* @see PluginKind
*/
public val kind: PluginKind public val kind: PluginKind
/**
* 插件名称.
*/
public val name: String public val name: String
/**
* 插件作者, 允许为空
*/
public val author: String public val author: String
/**
* 插件版本.
*
* 语法参考: ([语义化版本 2.0.0](https://semver.org/lang/zh-CN/))
*
* @see Semver 语义化版本
*/
public val version: Semver public val version: Semver
/**
* 插件信息, 允许为空
*/
public val info: String public val info: String
/** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */ /**
* 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件
*
* @see PluginDependency
*/
public val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> public val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency>
} }
/** 插件类型 */ /**
* 插件类型
*/
@Serializable(with = PluginKind.AsStringSerializer::class) @Serializable(with = PluginKind.AsStringSerializer::class)
public enum class PluginKind { public enum class PluginKind {
/** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */ /** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */
@ -55,7 +87,11 @@ public enum class PluginKind {
) )
} }
/** 插件的一个依赖的信息 */ /**
* 插件的一个依赖的信息
*
* @see PluginDescription.dependencies
*/
@Serializable(with = PluginDependency.SmartSerializer::class) @Serializable(with = PluginDependency.SmartSerializer::class)
public data class PluginDependency( public data class PluginDependency(
/** 依赖插件名 */ /** 依赖插件名 */

View File

@ -13,10 +13,9 @@
package net.mamoe.mirai.console.util package net.mamoe.mirai.console.util
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.util.ConsoleInputImpl
/** /**
* Console 输入. 由于 console 接管了 [stdin][System. in], [readLine] 等操作需要在这里进行. * Console 输入. 由于 console 接管了 [stdin][System. in], [readLine] 等操作需要在这里进行.
@ -28,16 +27,9 @@ public interface ConsoleInput {
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun requestInput(hint: String): String public suspend fun requestInput(hint: String): String
public companion object INSTANCE : ConsoleInput by ConsoleInputImpl { public companion object INSTANCE : ConsoleInput by ConsoleInputImpl
@JvmSynthetic
public suspend inline fun MiraiConsole.requestInput(hint: String): String = ConsoleInput.requestInput(hint)
}
} }
@Suppress("unused") // don't move into INSTANCE, Compilation error
internal object ConsoleInputImpl : ConsoleInput { @JvmSynthetic
private val inputLock = Mutex() public suspend inline fun MiraiConsole.requestInput(hint: String): String = ConsoleInput.requestInput(hint)
override suspend fun requestInput(hint: String): String =
inputLock.withLock { MiraiConsole.frontEnd.requestInput(hint) }
}

View File

@ -13,7 +13,6 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender
@ -22,13 +21,15 @@ import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.plugin.DeferredPluginLoader import net.mamoe.mirai.console.plugin.DeferredPluginLoader
import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlatformLogger import java.nio.file.Path
import java.io.File
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -37,22 +38,34 @@ import kotlin.test.assertNotNull
@OptIn(ConsoleInternalAPI::class) @OptIn(ConsoleInternalAPI::class)
fun initTestEnvironment() { fun initTestEnvironment() {
object : MiraiConsoleImplementation { object : MiraiConsoleImplementation {
override val rootDir: File = createTempDir() override val rootPath: Path = createTempDir().toPath()
override val frontEnd: MiraiConsoleFrontEnd = object : MiraiConsoleFrontEnd {
override val name: String get() = "Test" @ConsoleExperimentalAPI
override val version: String get() = "1.0.0" override val frontEndDescription: MiraiConsoleFrontEndDescription
override fun loggerFor(identity: String?): MiraiLogger = PlatformLogger(identity) get() = TODO("Not yet implemented")
override fun pushBot(bot: Bot) = println("pushBot: $bot")
override suspend fun requestInput(hint: String): String = readLine()!!
override fun createLoginSolver(): LoginSolver = LoginSolver.Default
}
override val mainLogger: MiraiLogger = DefaultLogger("main") override val mainLogger: MiraiLogger = DefaultLogger("main")
override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JarPluginLoader }) override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JarPluginLoader })
override val consoleCommandSender: ConsoleCommandSender = object : ConsoleCommandSender() { override val consoleCommandSender: ConsoleCommandSender = object : ConsoleCommandSender() {
override suspend fun sendMessage(message: Message) = println(message) override suspend fun sendMessage(message: Message) = println(message)
} }
override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage() override val dataStorageForJarPluginLoader: PluginDataStorage get() = MemoryPluginDataStorage()
override val configStorageForJarPluginLoader: PluginDataStorage
get() = TODO("Not yet implemented")
override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage() override val dataStorageForBuiltIns: PluginDataStorage get() = MemoryPluginDataStorage()
override val consoleInput: ConsoleInput = object : ConsoleInput {
override suspend fun requestInput(hint: String): String {
println(hint)
return readLine() ?: error("No stdin")
}
}
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
LoginSolver.Default
override fun newLogger(identity: String?): MiraiLogger {
return DefaultLogger(identity)
}
override val coroutineContext: CoroutineContext = SupervisorJob() override val coroutineContext: CoroutineContext = SupervisorJob()
}.start() }.start()
CommandManager CommandManager

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.CommandExecuteStatus
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.console.util.requestInput
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import java.util.* import java.util.*
@ -50,11 +51,9 @@ internal fun startupConsoleThread() {
val consoleLogger = DefaultLogger("console") val consoleLogger = DefaultLogger("console")
while (isActive) { while (isActive) {
try { try {
val next = MiraiConsoleFrontEndPure.requestInput("").let { val next = MiraiConsole.requestInput("").let {
when { when {
it.startsWith(CommandManager.commandPrefix) -> { it.startsWith(CommandManager.commandPrefix) -> it
it
}
it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName
else -> CommandManager.commandPrefix + it else -> CommandManager.commandPrefix + it
} }
@ -71,7 +70,7 @@ internal fun startupConsoleThread() {
result.exception?.printStackTrace() result.exception?.printStackTrace()
} }
CommandExecuteStatus.COMMAND_NOT_FOUND -> { CommandExecuteStatus.COMMAND_NOT_FOUND -> {
consoleLogger.warning("Unknown command: ${result.commandName}") consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助")
} }
CommandExecuteStatus.PERMISSION_DENIED -> { CommandExecuteStatus.PERMISSION_DENIED -> {
consoleLogger.warning("Permission denied.") consoleLogger.warning("Permission denied.")

View File

@ -22,20 +22,13 @@ package net.mamoe.mirai.console.pure
//import net.mamoe.mirai.console.command.CommandManager //import net.mamoe.mirai.console.command.CommandManager
//import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd //import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd
import io.ktor.utils.io.concurrent.* import com.vdurmont.semver4j.Semver
import kotlinx.coroutines.Dispatchers import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleFrontEnd
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.DefaultLoginSolver
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlatformLogger import net.mamoe.mirai.utils.PlatformLogger
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ConcurrentHashMap
private val ANSI_RESET = Ansi().reset().toString() private val ANSI_RESET = Ansi().reset().toString()
@ -53,25 +46,7 @@ internal val LoggerCreator: (identity: String?) -> MiraiLogger = {
*/ */
@ConsoleInternalAPI @ConsoleInternalAPI
@Suppress("unused") @Suppress("unused")
object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { object MiraiConsoleFrontEndPure : MiraiConsoleFrontEndDescription {
private val globalLogger = LoggerCreator("Mirai")
private val cachedLoggers = ConcurrentHashMap<String, MiraiLogger>()
// companion object {
// ANSI color codes
const val COLOR_RED = "\u001b[38;5;196m"
const val COLOR_CYAN = "\u001b[38;5;87m"
const val COLOR_GREEN = "\u001b[38;5;82m"
// use a dark yellow(more like orange) instead of light one to save Solarized-light users
const val COLOR_YELLOW = "\u001b[38;5;220m"
const val COLOR_GREY = "\u001b[38;5;244m"
const val COLOR_BLUE = "\u001b[38;5;27m"
const val COLOR_NAVY = "\u001b[38;5;24m" // navy uniform blue
const val COLOR_PINK = "\u001b[38;5;207m"
const val COLOR_RESET = "\u001b[39;49m"
// }
internal val sdf by ThreadLocal.withInitial { internal val sdf by ThreadLocal.withInitial {
// SimpleDateFormat not thread safe. // SimpleDateFormat not thread safe.
SimpleDateFormat("HH:mm:ss") SimpleDateFormat("HH:mm:ss")
@ -81,32 +56,9 @@ object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd {
return this.get() return this.get()
} }
override val name: String override val name: String get() = "Pure"
get() = "Pure" override val version: Semver get() = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
override val version: String override val vendor: String get() = "Mamoe Technologies"
get() = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
override fun loggerFor(identity: String?): MiraiLogger {
identity?.apply {
return cachedLoggers.computeIfAbsent(this, LoggerCreator)
}
return globalLogger
}
override fun pushBot(bot: Bot) {
}
override suspend fun requestInput(hint: String): String {
return ConsoleUtils.miraiLineReader(hint)
}
override fun createLoginSolver(): LoginSolver {
return DefaultLoginSolver(
input = suspend {
requestInput("LOGIN> ")
}
)
}
} }

View File

@ -22,10 +22,12 @@
package net.mamoe.mirai.console.pure package net.mamoe.mirai.console.pure
import com.vdurmont.semver4j.Semver
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.ConsoleFrontEndImplementation
import net.mamoe.mirai.console.MiraiConsoleFrontEnd import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
@ -33,9 +35,15 @@ import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.plugin.DeferredPluginLoader import net.mamoe.mirai.console.plugin.DeferredPluginLoader
import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLoginSolver
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File import java.nio.file.Path
import java.nio.file.Paths
import java.util.* import java.util.*
/** /**
@ -44,20 +52,43 @@ import java.util.*
* @see MiraiConsoleFrontEndPure 前端实现 * @see MiraiConsoleFrontEndPure 前端实现
* @see MiraiConsolePureLoader CLI 入口点 * @see MiraiConsolePureLoader CLI 入口点
*/ */
class MiraiConsoleImplementationPure internal class MiraiConsoleImplementationPure
@JvmOverloads constructor( @JvmOverloads constructor(
override val rootDir: File = File("."), override val rootPath: Path = Paths.get("."),
override val builtInPluginLoaders: List<PluginLoader<*, *>> = Collections.unmodifiableList( override val builtInPluginLoaders: List<PluginLoader<*, *>> = Collections.unmodifiableList(
listOf(DeferredPluginLoader { JarPluginLoader }) listOf(DeferredPluginLoader { JarPluginLoader })
), ),
override val frontEnd: MiraiConsoleFrontEnd = MiraiConsoleFrontEndPure, override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl,
override val mainLogger: MiraiLogger = frontEnd.loggerFor("main"),
override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl, override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl,
override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(File(rootDir, "data")), override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(File(rootDir, "data")) override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config"))
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) { ) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) {
init { override val mainLogger: MiraiLogger by lazy {
rootDir.mkdir() MiraiConsole.newLogger("main")
require(rootDir.isDirectory) { "rootDir ${rootDir.absolutePath} is not a directory" }
} }
override val consoleInput: ConsoleInput get() = ConsoleInputImpl
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver {
return DefaultLoginSolver(input = { requestInput("LOGIN> ") })
}
override fun newLogger(identity: String?): MiraiLogger = LoggerCreator(identity)
init {
with(rootPath.toFile()) {
mkdir()
require(isDirectory) { "rootDir $absolutePath is not a directory" }
}
}
}
private object ConsoleInputImpl : ConsoleInput {
override suspend fun requestInput(hint: String): String = ConsoleUtils.miraiLineReader(hint)
}
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
override val name: String get() = "Pure"
override val vendor: String get() = "Mamoe Technologies"
override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
} }

View File

@ -20,18 +20,9 @@
package net.mamoe.mirai.console.pure package net.mamoe.mirai.console.pure
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.CommandPermission
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.content import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
@ -44,29 +35,12 @@ object MiraiConsolePureLoader {
@JvmStatic @JvmStatic
fun main(args: Array<String>?) { fun main(args: Array<String>?) {
startup() startup()
YellowCommand.register()
runBlocking { MiraiConsole.addBot(1994701021, "Asd123456789asd").alsoLogin() }
}
}
object YellowCommand : SimpleCommand(
net.mamoe.mirai.console.command.ConsoleCommandOwner, "", "sleep",
prefixOptional = true,
description = "睡一个人",
permission = CommandPermission.Any
) {
@Handler
suspend fun CommandSender.handle(target: Member) {
target.mute(1)
sendMessage("${this.name} 睡了 ${target.nameCardOrNick}")
} }
} }
internal fun startup() { internal fun startup() {
DefaultLogger = { MiraiConsoleFrontEndPure.loggerFor(it) }
overrideSTD()
MiraiConsoleImplementationPure().start() MiraiConsoleImplementationPure().start()
overrideSTD()
startupConsoleThread() startupConsoleThread()
} }