mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-25 11:40:16 +08:00
Merge remote-tracking branch 'origin/master' into dependencies
This commit is contained in:
commit
1901cbec5e
@ -45,12 +45,12 @@ kotlin {
|
||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi")
|
||||
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
|
||||
useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "unused")
|
||||
@file:OptIn(ConsoleInternalAPI::class)
|
||||
@file:OptIn(ConsoleInternalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console
|
||||
|
||||
@ -18,15 +18,15 @@ import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.extension.foldExtensions
|
||||
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
@ -60,15 +60,15 @@ public interface MiraiConsole : CoroutineScope {
|
||||
*
|
||||
* **注意**: 插件不应该在任何时刻使用它.
|
||||
*/
|
||||
@ConsoleInternalAPI
|
||||
@ConsoleInternalApi
|
||||
public val mainLogger: MiraiLogger
|
||||
|
||||
/**
|
||||
* 内建加载器列表, 一般需要包含 [JarPluginLoader].
|
||||
* 内建加载器列表, 一般需要包含 [JvmPluginLoader].
|
||||
*
|
||||
* @return 不可变 [List] ([java.util.Collections.unmodifiableList])
|
||||
*/
|
||||
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
||||
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
|
||||
|
||||
/**
|
||||
* 此 Console 后端构建时间
|
||||
@ -80,13 +80,14 @@ public interface MiraiConsole : CoroutineScope {
|
||||
*/
|
||||
public val version: Semver
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public val pluginCenter: PluginCenter
|
||||
|
||||
/**
|
||||
* 创建一个 logger
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun createLogger(identity: String?): MiraiLogger
|
||||
|
||||
public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge {
|
||||
@ -106,7 +107,7 @@ public interface MiraiConsole : CoroutineScope {
|
||||
* @see BotConfigurationAlterer ExtensionPoint
|
||||
*/
|
||||
// don't static
|
||||
@ConsoleExperimentalAPI("This is a low-level API and might be removed in the future.")
|
||||
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
|
||||
public fun addBot(id: Long, password: String, configuration: BotConfiguration.() -> Unit = {}): Bot =
|
||||
addBotImpl(id, password, configuration)
|
||||
|
||||
@ -118,7 +119,7 @@ public interface MiraiConsole : CoroutineScope {
|
||||
* @see Bot.botInstances 获取现有 [Bot] 实例列表
|
||||
* @see BotConfigurationAlterer ExtensionPoint
|
||||
*/
|
||||
@ConsoleExperimentalAPI("This is a low-level API and might be removed in the future.")
|
||||
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
|
||||
public fun addBot(id: Long, password: ByteArray, configuration: BotConfiguration.() -> Unit = {}): Bot =
|
||||
addBotImpl(id, password, configuration)
|
||||
|
||||
@ -133,8 +134,10 @@ public interface MiraiConsole : CoroutineScope {
|
||||
configuration()
|
||||
}
|
||||
|
||||
config = BotConfigurationAlterer.foldExtensions(config) { acc, extension ->
|
||||
extension.alterConfiguration(id, acc)
|
||||
config = GlobalComponentStorage.run {
|
||||
BotConfigurationAlterer.foldExtensions(config) { acc, extension ->
|
||||
extension.alterConfiguration(id, acc)
|
||||
}
|
||||
}
|
||||
|
||||
return when (password) {
|
||||
|
@ -18,8 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
@ -37,8 +37,8 @@ import kotlin.coroutines.CoroutineContext
|
||||
*
|
||||
* 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用.
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
@MustBeDocumented
|
||||
public annotation class ConsoleFrontEndImplementation
|
||||
@ -72,11 +72,11 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
public val frontEndDescription: MiraiConsoleFrontEndDescription
|
||||
|
||||
/**
|
||||
* 内建加载器列表, 一般需要包含 [JarPluginLoader].
|
||||
* 内建加载器列表, 一般需要包含 [JvmPluginLoader].
|
||||
*
|
||||
* @return 不可变的 [List], [Collections.unmodifiableList]
|
||||
*/
|
||||
public val builtInPluginLoaders: List<PluginLoader<*, *>>
|
||||
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
|
||||
|
||||
/**
|
||||
* 由 Kotlin 用户实现
|
||||
@ -118,18 +118,39 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
withContext(Dispatchers.IO) { sendMessageJ(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* [ConsoleCommandSender]
|
||||
*/
|
||||
public val consoleCommandSender: ConsoleCommandSenderImpl
|
||||
|
||||
public val dataStorageForJarPluginLoader: PluginDataStorage
|
||||
public val configStorageForJarPluginLoader: PluginDataStorage
|
||||
public val dataStorageForJvmPluginLoader: PluginDataStorage
|
||||
public val configStorageForJvmPluginLoader: PluginDataStorage
|
||||
public val dataStorageForBuiltIns: PluginDataStorage
|
||||
public val configStorageForBuiltIns: PluginDataStorage
|
||||
|
||||
/**
|
||||
* @see ConsoleInput 的实现
|
||||
* @see JConsoleInput
|
||||
*/
|
||||
public val consoleInput: ConsoleInput
|
||||
|
||||
/**
|
||||
* 供 Java 用户实现 [ConsoleInput]
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@ConsoleFrontEndImplementation
|
||||
public interface JConsoleInput : ConsoleInput {
|
||||
/**
|
||||
* @see ConsoleInput.requestInput
|
||||
*/
|
||||
@JvmName("requestInput")
|
||||
public fun requestInputJ(hint: String): String
|
||||
|
||||
override suspend fun requestInput(hint: String): String {
|
||||
return withContext(Dispatchers.IO) { requestInputJ(hint) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 [LoginSolver]
|
||||
*
|
||||
|
@ -12,22 +12,22 @@ package net.mamoe.mirai.console.command
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.alsoLogin
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.permission.*
|
||||
import net.mamoe.mirai.console.internal.util.runIgnoreException
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.denyPermission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.getGrantedPermissions
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.message.nextMessageOrNull
|
||||
import net.mamoe.mirai.utils.secondsToMillis
|
||||
@ -35,22 +35,27 @@ import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
@Suppress("EXPOSED_SUPER_INTERFACE")
|
||||
public interface BuiltInCommand : Command, BuiltInCommandInternal
|
||||
public interface BuiltInCommand : Command
|
||||
|
||||
// for identification
|
||||
internal interface BuiltInCommandInternal : Command
|
||||
internal interface BuiltInCommandInternal : Command, BuiltInCommand
|
||||
|
||||
/**
|
||||
* 内建指令列表
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@Suppress("unused")
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public object BuiltInCommands {
|
||||
@ConsoleExperimentalApi
|
||||
public val parentPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
ConsoleCommandOwner.permissionId("*"),
|
||||
"The parent of any built-in commands"
|
||||
)
|
||||
}
|
||||
|
||||
public val all: Array<out Command> by lazy {
|
||||
internal val all: Array<out Command> by lazy {
|
||||
this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray()
|
||||
}
|
||||
|
||||
@ -63,8 +68,7 @@ public object BuiltInCommands {
|
||||
public object HelpCommand : SimpleCommand(
|
||||
ConsoleCommandOwner, "help",
|
||||
description = "Command list",
|
||||
parentPermission = RootConsoleBuiltInPermission,
|
||||
), BuiltInCommand {
|
||||
), BuiltInCommandInternal {
|
||||
@Handler
|
||||
public suspend fun CommandSender.handle() {
|
||||
sendMessage(
|
||||
@ -83,8 +87,7 @@ public object BuiltInCommands {
|
||||
public object StopCommand : SimpleCommand(
|
||||
ConsoleCommandOwner, "stop", "shutdown", "exit",
|
||||
description = "Stop the whole world.",
|
||||
parentPermission = RootConsoleBuiltInPermission,
|
||||
), BuiltInCommand {
|
||||
), BuiltInCommandInternal {
|
||||
|
||||
private val closingLock = Mutex()
|
||||
|
||||
@ -94,16 +97,16 @@ public object BuiltInCommands {
|
||||
closingLock.withLock {
|
||||
sendMessage("Stopping mirai-console")
|
||||
kotlin.runCatching {
|
||||
ignoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
|
||||
runIgnoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
ignoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
||||
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
||||
},
|
||||
onFailure = {
|
||||
if (it is CancellationException) return@fold
|
||||
@OptIn(ConsoleInternalAPI::class)
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
MiraiConsole.mainLogger.error("Exception in stop", it)
|
||||
ignoreException<EventCancelledException> {
|
||||
runIgnoreException<EventCancelledException> {
|
||||
sendMessage(
|
||||
it.localizedMessage ?: it.message ?: it.toString()
|
||||
)
|
||||
@ -119,8 +122,7 @@ public object BuiltInCommands {
|
||||
public object LoginCommand : SimpleCommand(
|
||||
ConsoleCommandOwner, "login", "登录",
|
||||
description = "Log in a bot account.",
|
||||
parentPermission = RootConsoleBuiltInPermission,
|
||||
), BuiltInCommand {
|
||||
), BuiltInCommandInternal {
|
||||
@Handler
|
||||
public suspend fun CommandSender.handle(id: Long, password: String) {
|
||||
kotlin.runCatching {
|
||||
@ -144,36 +146,40 @@ public object BuiltInCommands {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public object PermissionCommand : CompositeCommand(
|
||||
ConsoleCommandOwner, "permission", "权限", "perm",
|
||||
description = "Manage permissions",
|
||||
overrideContext = buildCommandArgumentContext {
|
||||
PermissibleIdentifier::class with PermissibleIdentifierArgumentParser
|
||||
PermitteeId::class with PermitteeIdArgumentParser
|
||||
Permission::class with PermissionIdArgumentParser.map { id ->
|
||||
kotlin.runCatching {
|
||||
id.findCorrespondingPermissionOrFail()
|
||||
}.getOrElse { illegalArgument("指令不存在: $id", it) }
|
||||
}
|
||||
},
|
||||
parentPermission = RootConsoleBuiltInPermission,
|
||||
), BuiltInCommand {
|
||||
), BuiltInCommandInternal {
|
||||
// TODO: 2020/9/10 improve Permission command
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.grant(target: PermissibleIdentifier, permission: Permission) {
|
||||
@SubCommand("permit", "grant", "add")
|
||||
public suspend fun CommandSender.permit(target: PermitteeId, permission: Permission) {
|
||||
target.grantPermission(permission)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.deny(target: PermissibleIdentifier, permission: Permission) {
|
||||
target.denyPermission(permission)
|
||||
@SubCommand("cancel", "deny", "remove")
|
||||
public suspend fun CommandSender.cancel(target: PermitteeId, permission: Permission) {
|
||||
target.denyPermission(permission, false)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand("grantedPermissions", "gp")
|
||||
public suspend fun CommandSender.grantedPermissions(target: PermissibleIdentifier) {
|
||||
val grantedPermissions = target.getGrantedPermissions()
|
||||
@SubCommand("cancelAll", "denyAll", "removeAll")
|
||||
public suspend fun CommandSender.cancelAll(target: PermitteeId, permission: Permission) {
|
||||
target.denyPermission(permission, true)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
|
||||
public suspend fun CommandSender.permittedPermissions(target: PermitteeId) {
|
||||
val grantedPermissions = target.getPermittedPermissions()
|
||||
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
|
||||
}
|
||||
|
||||
@ -182,380 +188,4 @@ public object BuiltInCommands {
|
||||
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified E : Throwable, R> ignoreException(block: () -> R): R? {
|
||||
try {
|
||||
return block()
|
||||
} catch (e: Throwable) {
|
||||
if (e is E) return null
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified E : Throwable> ignoreException(block: () -> Unit): Unit? {
|
||||
try {
|
||||
return block()
|
||||
} catch (e: Throwable) {
|
||||
if (e is E) return null
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ContactOrBot.render(): String {
|
||||
return when (this) {
|
||||
is Bot -> "Bot $nick($id)"
|
||||
is Group -> "Group $name($id)"
|
||||
is Friend -> "Friend $nick($id)"
|
||||
is Member -> "Friend $nameCardOrNick($id)"
|
||||
else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/**
|
||||
* Some defaults commands are recommend to be replaced by plugin provided commands
|
||||
*/
|
||||
internal object DefaultCommands {
|
||||
internal val commandPrefix = "mirai.command.prefix".property() ?: "/"
|
||||
private suspend fun CommandSender.login(account: Long, password: String) {
|
||||
MiraiConsole.logger("[Bot Login]", 0, "login...")
|
||||
try {
|
||||
MiraiConsole.frontEnd.prePushBot(account)
|
||||
val bot = Bot(account, password) {
|
||||
fileBasedDeviceInfo(MiraiConsole.path + "/device.json")
|
||||
this.loginSolver = MiraiConsole.frontEnd.createLoginSolver()
|
||||
this.botLoggerSupplier = {
|
||||
SimpleLogger("BOT $account]") { _, message, e ->
|
||||
MiraiConsole.logger("[BOT $account]", account, message)
|
||||
if (e != null) {
|
||||
MiraiConsole.logger("[NETWORK ERROR]", account, e)//因为在一页 所以可以不打QQ
|
||||
}
|
||||
}
|
||||
}
|
||||
this.networkLoggerSupplier = {
|
||||
SimpleLogger("BOT $account") { _, message, e ->
|
||||
MiraiConsole.logger("[NETWORK]", account, message)//因为在一页 所以可以不打QQ
|
||||
if (e != null) {
|
||||
MiraiConsole.logger("[NETWORK ERROR]", account, e)//因为在一页 所以可以不打QQ
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bot.login()
|
||||
MiraiConsole.subscribeMessages {
|
||||
startsWith(commandPrefix) { message ->
|
||||
if (this.bot != bot) return@startsWith
|
||||
|
||||
if (bot.checkManager(this.sender.id)) {
|
||||
val sender = if (this is GroupMessageEvent) {
|
||||
GroupContactCommandSender(bot,this.sender, this.subject)
|
||||
} else {
|
||||
ContactCommandSender(bot,this.subject)
|
||||
}
|
||||
CommandManager.runCommand(
|
||||
sender, message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
sendMessage("$account login successes")
|
||||
MiraiConsole.frontEnd.pushBot(bot)
|
||||
} catch (e: Exception) {
|
||||
sendMessage("$account login failed -> " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.property(): String? = System.getProperty(this)
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun tryLoginAuto() {
|
||||
// For java -Dmirai.account=10086 -Dmirai.password=Password -jar mirai-console-wrapper-X.jar
|
||||
val account = ("mirai.account".property() ?: return).toLong()
|
||||
val password = "mirai.password".property() ?: "mirai.passphrase".property() ?: "mirai.passwd".property()
|
||||
if (password == null) {
|
||||
MiraiConsole.logger.invoke(
|
||||
SimpleLogger.LogPriority.ERROR, "[AUTO LOGIN]", account,
|
||||
"Find the account to be logged in, but no password specified"
|
||||
)
|
||||
return
|
||||
}
|
||||
GlobalScope.launch {
|
||||
ConsoleCommandSender.login(account, password)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun invoke() {
|
||||
registerConsoleCommands {
|
||||
name = "manager"
|
||||
description = "Add a manager"
|
||||
onCommand { it ->
|
||||
if (this !is ConsoleCommandSender) {
|
||||
sendMessage("请在后台使用该指令")
|
||||
return@onCommand false
|
||||
}
|
||||
if (it.size < 2) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, "/manager add [bot ID] [Manager ID]")
|
||||
MiraiConsole.logger("[Bot Manager]", 0, "/manager remove [bot ID] [Manager ID]")
|
||||
MiraiConsole.logger("[Bot Manager]", 0, "/manager list [bot ID]")
|
||||
return@onCommand true
|
||||
}
|
||||
val botId = try {
|
||||
it[1].toLong()
|
||||
} catch (e: Exception) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个Bot的ID")
|
||||
return@onCommand false
|
||||
}
|
||||
val bot = MiraiConsole.getBotOrNull(botId)
|
||||
if (bot == null) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, "$botId 没有在Console中登陆")
|
||||
return@onCommand false
|
||||
}
|
||||
when (it[0]) {
|
||||
"add" -> {
|
||||
if (it.size < 3) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, "/manager add [bot ID] [Manager ID]")
|
||||
return@onCommand true
|
||||
}
|
||||
val adminID = try {
|
||||
it[2].toLong()
|
||||
} catch (e: Exception) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[2] + " 不是一个ID")
|
||||
return@onCommand false
|
||||
}
|
||||
if (bot.addManager(adminID)) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "增加成功")
|
||||
} else {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "已经是一个manager了")
|
||||
}
|
||||
}
|
||||
"remove" -> {
|
||||
if (it.size < 3) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, "/manager remove [bot ID] [Manager ID]")
|
||||
return@onCommand true
|
||||
}
|
||||
val adminID = try {
|
||||
it[2].toLong()
|
||||
} catch (e: Exception) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个ID")
|
||||
return@onCommand false
|
||||
}
|
||||
if (!bot.checkManager(adminID)) {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "本身不是一个Manager")
|
||||
return@onCommand true
|
||||
}
|
||||
bot.removeManager(adminID)
|
||||
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "移除成功")
|
||||
}
|
||||
"list" -> {
|
||||
bot.managers.forEach {
|
||||
MiraiConsole.logger("[Bot Manager]", 0, " -> $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
return@onCommand true
|
||||
}
|
||||
}
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "login"
|
||||
description = "机器人登录"
|
||||
onCommand {
|
||||
if (this !is ConsoleCommandSender) {
|
||||
sendMessage("请在后台使用该指令")
|
||||
return@onCommand false
|
||||
}
|
||||
if (it.size < 2) {
|
||||
MiraiConsole.logger("\"/login qq password \" to login a bot")
|
||||
MiraiConsole.logger("\"/login qq号 qq密码 \" 来登录一个BOT")
|
||||
return@onCommand false
|
||||
}
|
||||
val qqNumber = it[0].toLong()
|
||||
val qqPassword = it[1]
|
||||
login(qqNumber, qqPassword)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "status"
|
||||
description = "获取状态"
|
||||
onCommand { args ->
|
||||
when (args.size) {
|
||||
0 -> {
|
||||
sendMessage("当前有" + botInstances.size + "个BOT在线")
|
||||
}
|
||||
1 -> {
|
||||
val bot = args[0]
|
||||
var find = false
|
||||
botInstances.forEach {
|
||||
if (it.id.toString().contains(bot)) {
|
||||
find = true
|
||||
appendMessage(
|
||||
"" + it.id + ": 在线中; 好友数量:" + it.friends.size + "; 群组数量:" + it.groups.size
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!find) {
|
||||
sendMessage("没有找到BOT$bot")
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "say"
|
||||
description = "聊天功能演示"
|
||||
onCommand {
|
||||
if (it.size < 2) {
|
||||
MiraiConsole.logger("say [好友qq号或者群号] [测试消息] //将默认使用第一个BOT")
|
||||
MiraiConsole.logger("say [bot号] [好友qq号或者群号] [测试消息]")
|
||||
return@onCommand false
|
||||
}
|
||||
val bot: Bot? = if (it.size == 2) {
|
||||
if (botInstances.isEmpty()) {
|
||||
MiraiConsole.logger("还没有BOT登录")
|
||||
return@onCommand false
|
||||
}
|
||||
botInstances[0]
|
||||
} else {
|
||||
MiraiConsole.getBotOrNull(it[0].toLong())
|
||||
}
|
||||
if (bot == null) {
|
||||
MiraiConsole.logger("没有找到BOT")
|
||||
return@onCommand false
|
||||
}
|
||||
val target = it[it.size - 2].toLong()
|
||||
val message = it[it.size - 1]
|
||||
try {
|
||||
val contact = bot.getFriendOrNull(target) ?: bot.getGroup(target)
|
||||
contact.sendMessage(message)
|
||||
MiraiConsole.logger("消息已推送")
|
||||
} catch (e: NoSuchElementException) {
|
||||
MiraiConsole.logger("没有找到群或好友 号码为${target}")
|
||||
return@onCommand false
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "plugins"
|
||||
alias = listOf("plugin")
|
||||
description = "获取插件列表"
|
||||
onCommand {
|
||||
PluginManager.getAllPluginDescriptions().let { descriptions ->
|
||||
descriptions.forEach {
|
||||
appendMessage("\t" + it.name + " v" + it.version + " by " + it.author + " " + it.info)
|
||||
}
|
||||
appendMessage("加载了" + descriptions.size + "个插件")
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "command"
|
||||
alias = listOf("commands", "help", "helps")
|
||||
description = "获取指令列表"
|
||||
onCommand {
|
||||
CommandManager.commands.toSet().let { commands ->
|
||||
var size = 0
|
||||
appendMessage("")//\n
|
||||
commands.forEach {
|
||||
++size
|
||||
appendMessage("-> " + it.name + " :" + it.description)
|
||||
}
|
||||
appendMessage("""共有${size}条指令""")
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "about"
|
||||
description = "About Mirai-Console"
|
||||
onCommand {
|
||||
appendMessage("v${MiraiConsole.version} ${MiraiConsole.build} is still in testing stage, major features are available")
|
||||
appendMessage("now running under ${MiraiConsole.path}")
|
||||
appendMessage("在Github中获取项目最新进展: https://github.com/mamoe/mirai")
|
||||
appendMessage("Mirai为开源项目,请自觉遵守开源项目协议")
|
||||
appendMessage("Powered by Mamoe Technologies and contributors")
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "reload"
|
||||
alias = listOf("reloadPlugins")
|
||||
description = "重新加载全部插件"
|
||||
onCommand {
|
||||
PluginManager.reloadPlugins()
|
||||
sendMessage("重新加载完成")
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
registerConsoleCommands {
|
||||
name = "install"
|
||||
description = "Install plugin from PluginCenter"
|
||||
usage = "/install [plugin-name] to install plugin or /install [page-num] to show list "
|
||||
onCommand { args ->
|
||||
|
||||
val center = MiraiConsole.frontEnd.pluginCenter
|
||||
|
||||
suspend fun showPage(num: Int) {
|
||||
sendMessage("正在连接 " + center.name)
|
||||
val list = center.fetchPlugin(num)
|
||||
if (list.isEmpty()) {
|
||||
sendMessage("页码过大")
|
||||
return
|
||||
}
|
||||
sendMessage("显示插件列表第 $num 页")
|
||||
appendMessage("\n")
|
||||
list.values.forEach {
|
||||
appendMessage("=> " + it.name + " ;作者: " + it.author + " ;介绍: " + it.description)
|
||||
}
|
||||
sendMessage("使用 /install ${num + 1} 查看下一页")
|
||||
}
|
||||
|
||||
suspend fun installPlugin(name: String) {
|
||||
sendMessage("正在连接 " + center.name)
|
||||
val plugin = center.findPlugin(name)
|
||||
if (plugin == null) {
|
||||
sendMessage("插件未找到, 请注意大小写")
|
||||
return
|
||||
}
|
||||
sendMessage("正在安装 " + plugin.name)
|
||||
try {
|
||||
center.downloadPlugin(name) {}
|
||||
sendMessage("安装 " + plugin.name + " 成功, 请重启服务器以更新")
|
||||
} catch (e: Exception) {
|
||||
sendMessage("安装 " + plugin.name + " 失败, " + (e.message ?: "未知原因"))
|
||||
}
|
||||
}
|
||||
|
||||
if (args.isEmpty()) {
|
||||
showPage(1)
|
||||
} else {
|
||||
val arg = args[0]
|
||||
|
||||
val id = arg.toIntOrNull() ?: 0
|
||||
if (id > 0) {
|
||||
showPage(id)
|
||||
} else {
|
||||
installPlugin(arg)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
@ -17,7 +17,6 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.java.JCommand
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.internal.command.isValidSubName
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
@ -42,19 +41,18 @@ public interface Command {
|
||||
public val names: Array<out String>
|
||||
|
||||
/**
|
||||
* 用法说明, 用于发送给用户. 一般 [usage] 包含 [description].
|
||||
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
|
||||
*/
|
||||
public val usage: String
|
||||
|
||||
/**
|
||||
* 指令描述, 用于显示在 [BuiltInCommands.Help]
|
||||
* 指令描述, 用于显示在 [BuiltInCommands.HelpCommand]
|
||||
*/
|
||||
public val description: String
|
||||
|
||||
/**
|
||||
* 指令权限
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
public val permission: Permission
|
||||
|
||||
/**
|
||||
@ -90,7 +88,7 @@ public interface Command {
|
||||
|
||||
@JvmSynthetic
|
||||
public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
|
||||
sender.run { onCommand(args) }
|
||||
sender.onCommand(args)
|
||||
|
||||
/**
|
||||
* [Command] 的基础实现
|
||||
@ -100,7 +98,6 @@ public suspend inline fun Command.onCommand(sender: CommandSender, args: Message
|
||||
* @see RawCommand
|
||||
*/
|
||||
public abstract class AbstractCommand
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
@JvmOverloads constructor(
|
||||
/** 指令拥有者. */
|
||||
public override val owner: CommandOwner,
|
||||
@ -108,7 +105,7 @@ public abstract class AbstractCommand
|
||||
description: String = "<no description available>",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
public override val prefixOptional: Boolean = false
|
||||
public override val prefixOptional: Boolean = false,
|
||||
) : Command {
|
||||
public override val description: String = description.trimIndent()
|
||||
public override val names: Array<out String> =
|
||||
@ -116,6 +113,5 @@ public abstract class AbstractCommand
|
||||
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
|
||||
}.toTypedArray()
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
}
|
@ -104,7 +104,7 @@ public interface CommandManager {
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean = true
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult
|
||||
|
||||
/**
|
||||
@ -120,7 +120,7 @@ public interface CommandManager {
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean = true
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = executeCommand(PlainText(message).asMessageChain(), checkPermission)
|
||||
|
||||
/**
|
||||
@ -132,7 +132,7 @@ public interface CommandManager {
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
checkPermission: Boolean = true
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult
|
||||
|
||||
/**
|
||||
@ -145,7 +145,7 @@ public interface CommandManager {
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String = "",
|
||||
checkPermission: Boolean = true
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
|
||||
|
||||
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||
@ -165,24 +165,48 @@ public interface CommandManager {
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult =
|
||||
CommandManagerImpl.run { execute(sender, arguments = arguments, checkPermission = checkPermission) }
|
||||
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String,
|
||||
checkPermission: Boolean
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult = CommandManagerImpl.run { execute(sender, arguments, checkPermission) }
|
||||
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see execute 获取更多信息
|
||||
*/
|
||||
public suspend fun CommandSender.execute(
|
||||
command: Command,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult {
|
||||
return command.execute(this, arguments, checkPermission)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see execute 获取更多信息
|
||||
*/
|
||||
public suspend fun CommandSender.execute(
|
||||
command: Command,
|
||||
arguments: String,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult {
|
||||
return command.execute(this, arguments, checkPermission)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,25 +10,23 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
|
||||
import net.mamoe.mirai.console.permission.*
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermissionIdNamespace
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
|
||||
/**
|
||||
* 指令的所有者. 目前仅作为标识作用.
|
||||
* 指令的所有者.
|
||||
*
|
||||
* @see CommandManager.unregisterAllCommands 取消注册所有属于一个 [CommandOwner] 的指令
|
||||
* @see CommandManager.registeredCommands 获取已经注册了的属于一个 [CommandOwner] 的指令列表.
|
||||
*
|
||||
* @see JvmPlugin 是一个 [CommandOwner]
|
||||
*/
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public interface CommandOwner : PermissionIdNamespace {
|
||||
/**
|
||||
* 此 [PermissionIdNamespace] 拥有的指令都默认将 [parentPermission] 作为父权限.
|
||||
*
|
||||
* TODO document
|
||||
* 在构造指令时, [Command.permission] 默认会使用 [parentPermission] 作为 [Permission.parent]
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
public val parentPermission: Permission
|
||||
}
|
||||
|
||||
@ -36,10 +34,7 @@ public interface CommandOwner : PermissionIdNamespace {
|
||||
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
|
||||
*/
|
||||
internal object ConsoleCommandOwner : CommandOwner {
|
||||
@ExperimentalPermission
|
||||
override val parentPermission: Permission
|
||||
get() = RootPermission
|
||||
override val parentPermission: Permission get() = BuiltInCommands.parentPermission
|
||||
|
||||
@ExperimentalPermission
|
||||
override fun permissionId(id: String): PermissionId = PermissionId("console", id)
|
||||
override fun permissionId(name: String): PermissionId = PermissionId("console", "command.$name")
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
|
||||
@file:Suppress(
|
||||
"NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME", "FunctionName", "SuspendFunctionOnCoroutineScope",
|
||||
"unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER"
|
||||
"unused"
|
||||
)
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
@ -19,24 +19,21 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.ConsoleFrontEndImplementation
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender.INSTANCE
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
|
||||
import net.mamoe.mirai.console.permission.AbstractPermissibleIdentifier
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permissible
|
||||
import net.mamoe.mirai.console.permission.PermissibleIdentifier
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
||||
import net.mamoe.mirai.console.permission.Permittee
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
|
||||
import net.mamoe.mirai.console.util.MessageScope
|
||||
@ -47,7 +44,6 @@ import net.mamoe.mirai.message.data.PlainText
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
|
||||
/**
|
||||
* 指令发送者.
|
||||
@ -136,8 +132,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
|
||||
* @see toCommandSender
|
||||
* @see asCommandSender
|
||||
*/
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public interface CommandSender : CoroutineScope, Permissible {
|
||||
public interface CommandSender : CoroutineScope, Permittee {
|
||||
/**
|
||||
* 与这个 [CommandSender] 相关的 [Bot].
|
||||
* 当通过控制台执行时为 `null`.
|
||||
@ -180,7 +175,7 @@ public interface CommandSender : CoroutineScope, Permissible {
|
||||
@JvmBlockingBridge
|
||||
public suspend fun sendMessage(message: String): MessageReceipt<Contact>?
|
||||
|
||||
@ConsoleExperimentalAPI("This is unstable and might get changed")
|
||||
@ConsoleExperimentalApi("This is unstable and might get changed")
|
||||
public suspend fun catchExecutionException(e: Throwable)
|
||||
|
||||
public companion object {
|
||||
@ -282,7 +277,7 @@ public sealed class AbstractCommandSender : CommandSender, CoroutineScope {
|
||||
public abstract override val user: User?
|
||||
public abstract override fun toString(): String
|
||||
|
||||
@ConsoleExperimentalAPI("This is unstable and might get changed")
|
||||
@ConsoleExperimentalApi("This is unstable and might get changed")
|
||||
override suspend fun catchExecutionException(e: Throwable) {
|
||||
if (this is CommandSenderOnMessage<*>) {
|
||||
val cause = e.rootCauseOrSelf
|
||||
@ -441,7 +436,6 @@ public fun CommandSender.isNotUser(): Boolean {
|
||||
* @return [ifIsConsole] 或 [ifIsUser] 执行结果.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline fun <R> CommandSender.fold(
|
||||
ifIsConsole: ConsoleCommandSender.() -> R,
|
||||
ifIsUser: UserCommandSender.() -> R,
|
||||
@ -464,12 +458,12 @@ public inline fun <R> CommandSender.fold(
|
||||
*
|
||||
* ### 实验性 API
|
||||
*
|
||||
* 这是预览版本 API. 如果你对 [UserCommandSender.fold] 有建议, 请在 [mamoe/mirai-console/issues](https://github.com/mamoe/mirai-console/issues/new) 提交.
|
||||
* 这是预览版本 API. 如果你对 [UserCommandSender.foldContext] 有建议, 请在 [mamoe/mirai-console/issues](https://github.com/mamoe/mirai-console/issues/new) 提交.
|
||||
*
|
||||
* @return [inGroup] 或 [inPrivate] 执行结果.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public inline fun <R> UserCommandSender.foldContext(
|
||||
inGroup: MemberCommandSender.() -> R,
|
||||
inPrivate: UserCommandSender.() -> R,
|
||||
@ -504,31 +498,30 @@ public fun CommandSender.getBotOrNull(): Bot? {
|
||||
|
||||
/**
|
||||
* 控制台指令执行者. 代表由控制台执行指令
|
||||
* @see INSTANCE
|
||||
*
|
||||
* 控制台拥有一切指令的执行权限.
|
||||
*/
|
||||
// 前端实现
|
||||
public abstract class ConsoleCommandSender @ConsoleFrontEndImplementation constructor() : AbstractCommandSender() {
|
||||
public final override val bot: Nothing? get() = null
|
||||
public final override val subject: Nothing? get() = null
|
||||
public final override val user: Nothing? get() = null
|
||||
public final override val name: String get() = NAME
|
||||
public final override fun toString(): String = NAME
|
||||
public object ConsoleCommandSender : AbstractCommandSender() {
|
||||
public const val NAME: String = "ConsoleCommandSender"
|
||||
|
||||
@ExperimentalPermission
|
||||
public final override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.Console
|
||||
public override val bot: Nothing? get() = null
|
||||
public override val subject: Nothing? get() = null
|
||||
public override val user: Nothing? get() = null
|
||||
public override val name: String get() = NAME
|
||||
public override fun toString(): String = NAME
|
||||
|
||||
public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope {
|
||||
public const val NAME: String = "ConsoleCommandSender"
|
||||
public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) }
|
||||
public override suspend fun sendMessage(message: Message): Nothing? {
|
||||
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
|
||||
return null
|
||||
}
|
||||
public override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console
|
||||
|
||||
public override suspend fun sendMessage(message: String): MessageReceipt<User>? {
|
||||
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
|
||||
return null
|
||||
}
|
||||
public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) }
|
||||
public override suspend fun sendMessage(message: Message): Nothing? {
|
||||
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
|
||||
return null
|
||||
}
|
||||
|
||||
public override suspend fun sendMessage(message: String): MessageReceipt<User>? {
|
||||
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -607,13 +600,12 @@ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractComma
|
||||
* @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
|
||||
*/
|
||||
public open class FriendCommandSender internal constructor(
|
||||
public final override val user: Friend
|
||||
public final override val user: Friend,
|
||||
) : AbstractUserCommandSender(), CoroutineScope by user.childScope("FriendCommandSender") {
|
||||
public override val subject: Contact get() = user
|
||||
public override fun toString(): String = "FriendCommandSender($user)"
|
||||
|
||||
@ExperimentalPermission
|
||||
public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactFriend(user.id)
|
||||
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactFriend(user.id)
|
||||
|
||||
@JvmBlockingBridge
|
||||
public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = sendMessage(PlainText(message))
|
||||
@ -627,7 +619,7 @@ public open class FriendCommandSender internal constructor(
|
||||
* @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
|
||||
*/
|
||||
public open class MemberCommandSender internal constructor(
|
||||
public final override val user: Member
|
||||
public final override val user: Member,
|
||||
) : AbstractUserCommandSender(),
|
||||
GroupAwareCommandSender,
|
||||
CoroutineScope by user.childScope("MemberCommandSender") {
|
||||
@ -635,8 +627,7 @@ public open class MemberCommandSender internal constructor(
|
||||
public override val subject: Group get() = group
|
||||
public override fun toString(): String = "MemberCommandSender($user)"
|
||||
|
||||
@ExperimentalPermission
|
||||
public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactMember(group.id, user.id)
|
||||
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactMember(group.id, user.id)
|
||||
|
||||
@JvmBlockingBridge
|
||||
public override suspend fun sendMessage(message: String): MessageReceipt<Group> = sendMessage(PlainText(message))
|
||||
@ -650,7 +641,7 @@ public open class MemberCommandSender internal constructor(
|
||||
* @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
|
||||
*/
|
||||
public open class TempCommandSender internal constructor(
|
||||
public final override val user: Member
|
||||
public final override val user: Member,
|
||||
) : AbstractUserCommandSender(),
|
||||
GroupAwareCommandSender,
|
||||
CoroutineScope by user.childScope("TempCommandSender") {
|
||||
@ -658,9 +649,8 @@ public open class TempCommandSender internal constructor(
|
||||
public override val subject: Contact get() = group
|
||||
public override fun toString(): String = "TempCommandSender($user)"
|
||||
|
||||
@ExperimentalPermission
|
||||
public override val identifier: PermissibleIdentifier =
|
||||
AbstractPermissibleIdentifier.ExactTemp(user.group.id, user.id)
|
||||
public override val permitteeId: PermitteeId =
|
||||
AbstractPermitteeId.ExactTemp(user.group.id, user.id)
|
||||
|
||||
@JvmBlockingBridge
|
||||
public override suspend fun sendMessage(message: String): MessageReceipt<Member> = sendMessage(PlainText(message))
|
||||
@ -695,7 +685,7 @@ public interface CommandSenderOnMessage<T : MessageEvent> :
|
||||
* @see FriendCommandSender 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式
|
||||
*/
|
||||
public class FriendCommandSenderOnMessage internal constructor(
|
||||
public override val fromEvent: FriendMessageEvent
|
||||
public override val fromEvent: FriendMessageEvent,
|
||||
) : FriendCommandSender(fromEvent.sender),
|
||||
CommandSenderOnMessage<FriendMessageEvent>,
|
||||
MessageEventExtensions<User, Contact> by fromEvent {
|
||||
@ -708,7 +698,7 @@ public class FriendCommandSenderOnMessage internal constructor(
|
||||
* @see MemberCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式
|
||||
*/
|
||||
public class MemberCommandSenderOnMessage internal constructor(
|
||||
public override val fromEvent: GroupMessageEvent
|
||||
public override val fromEvent: GroupMessageEvent,
|
||||
) : MemberCommandSender(fromEvent.sender),
|
||||
CommandSenderOnMessage<GroupMessageEvent>,
|
||||
MessageEventExtensions<User, Contact> by fromEvent {
|
||||
@ -721,7 +711,7 @@ public class MemberCommandSenderOnMessage internal constructor(
|
||||
* @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式
|
||||
*/
|
||||
public class TempCommandSenderOnMessage internal constructor(
|
||||
public override val fromEvent: TempMessageEvent
|
||||
public override val fromEvent: TempMessageEvent,
|
||||
) : TempCommandSender(fromEvent.sender),
|
||||
CommandSenderOnMessage<TempMessageEvent>,
|
||||
MessageEventExtensions<User, Contact> by fromEvent {
|
||||
|
@ -20,9 +20,7 @@ package net.mamoe.mirai.console.command
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
@ -81,14 +79,13 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
*
|
||||
* @see buildCommandArgumentContext
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public abstract class CompositeCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||
public abstract class CompositeCommand(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "no description available",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
|
@ -15,7 +15,6 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.java.JRawCommand
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
|
||||
@ -30,7 +29,7 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
* @see SimpleCommand 简单指令
|
||||
* @see CompositeCommand 复合指令
|
||||
*/
|
||||
public abstract class RawCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||
public abstract class RawCommand(
|
||||
/**
|
||||
* 指令拥有者.
|
||||
* @see CommandOwner
|
||||
@ -45,9 +44,8 @@ public abstract class RawCommand @OptIn(ExperimentalPermission::class) construct
|
||||
/** 指令父权限 */
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
public override val prefixOptional: Boolean = false
|
||||
public override val prefixOptional: Boolean = false,
|
||||
) : Command {
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,6 @@ import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
|
||||
@ -49,13 +48,13 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
* @see JSimpleCommand Java 实现
|
||||
* @see [CommandManager.executeCommand]
|
||||
*/
|
||||
public abstract class SimpleCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||
public abstract class SimpleCommand(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "no description available",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
|
@ -16,7 +16,9 @@ import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext.ParserPair
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.contact.*
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
@ -43,7 +45,7 @@ public interface CommandArgumentContext {
|
||||
*/
|
||||
public data class ParserPair<T : Any>(
|
||||
val klass: KClass<T>,
|
||||
val parser: CommandArgumentParser<T>
|
||||
val parser: CommandArgumentParser<T>,
|
||||
)
|
||||
|
||||
public operator fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>?
|
||||
@ -79,6 +81,9 @@ public interface CommandArgumentContext {
|
||||
Group::class with ExistingGroupArgumentParser
|
||||
Friend::class with ExistingFriendArgumentParser
|
||||
Bot::class with ExistingBotArgumentParser
|
||||
|
||||
PermissionId::class with PermissionIdArgumentParser
|
||||
PermitteeId::class with PermitteeIdArgumentParser
|
||||
})
|
||||
}
|
||||
|
||||
@ -134,7 +139,7 @@ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>):
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public class SimpleCommandArgumentContext(
|
||||
public val list: List<ParserPair<*>>
|
||||
public val list: List<ParserPair<*>>,
|
||||
) : CommandArgumentContext {
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
|
||||
(this.list.firstOrNull { klass == it.klass }?.parser
|
||||
@ -208,7 +213,7 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline infix fun <T : Any> KClass<T>.with(
|
||||
crossinline parser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T
|
||||
crossinline parser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T,
|
||||
): CommandArgumentContextBuilder {
|
||||
add(ParserPair(this, object : CommandArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
@ -221,7 +226,7 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline infix fun <T : Any> KClass<T>.with(
|
||||
crossinline parser: CommandArgumentParser<T>.(s: String) -> T
|
||||
crossinline parser: CommandArgumentParser<T>.(s: String) -> T,
|
||||
): CommandArgumentContextBuilder {
|
||||
add(ParserPair(this, object : CommandArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
|
||||
@ -238,10 +243,10 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
@JvmSynthetic
|
||||
public inline infix fun <reified T : Any> add(
|
||||
crossinline parser: CommandArgumentParser<*>.(s: String) -> T
|
||||
crossinline parser: CommandArgumentParser<*>.(s: String) -> T,
|
||||
): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
|
||||
}
|
||||
@ -249,11 +254,11 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline infix fun <reified T : Any> add(
|
||||
crossinline parser: CommandArgumentParser<*>.(s: String, sender: CommandSender) -> T
|
||||
crossinline parser: CommandArgumentParser<*>.(s: String, sender: CommandSender) -> T,
|
||||
): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
}
|
||||
|
@ -13,10 +13,9 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
|
||||
import net.mamoe.mirai.console.internal.command.fuzzySearchMember
|
||||
import net.mamoe.mirai.console.permission.AbstractPermissibleIdentifier
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.PermissibleIdentifier
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.getFriendOrNull
|
||||
import net.mamoe.mirai.getGroupOrNull
|
||||
@ -308,7 +307,6 @@ public object ExistingMemberArgumentParser : InternalCommandArgumentParserExtens
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalPermission
|
||||
public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
|
||||
override fun parse(raw: String, sender: CommandSender): PermissionId {
|
||||
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse {
|
||||
@ -317,17 +315,17 @@ public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalPermission
|
||||
public object PermissibleIdentifierArgumentParser : CommandArgumentParser<PermissibleIdentifier> {
|
||||
override fun parse(raw: String, sender: CommandSender): PermissibleIdentifier {
|
||||
return kotlin.runCatching { AbstractPermissibleIdentifier.parseFromString(raw) }.getOrElse {
|
||||
public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> {
|
||||
override fun parse(raw: String, sender: CommandSender): PermitteeId {
|
||||
return if (raw == "~") sender.permitteeId
|
||||
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
|
||||
illegalArgument("无法解析 $raw 为 PermissibleIdentifier")
|
||||
}
|
||||
}
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): PermissibleIdentifier {
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId {
|
||||
if (raw is At) {
|
||||
return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).identifier
|
||||
return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId
|
||||
}
|
||||
return super.parse(raw, sender)
|
||||
}
|
||||
|
@ -14,9 +14,7 @@ import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
|
||||
/**
|
||||
* 复合指令. 指令注册时候会通过反射构造指令解析器.
|
||||
@ -68,8 +66,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
*
|
||||
* @see buildCommandArgumentContext
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public abstract class JCompositeCommand @OptIn(ExperimentalPermission::class)
|
||||
public abstract class JCompositeCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
@ -79,7 +76,6 @@ public abstract class JCompositeCommand @OptIn(ExperimentalPermission::class)
|
||||
public final override var description: String = "<no descriptions given>"
|
||||
protected set
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public final override var permission: Permission = super.permission
|
||||
protected set
|
||||
|
||||
|
@ -14,7 +14,6 @@ import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
@ -45,7 +44,7 @@ import net.mamoe.mirai.message.data.SingleMessage
|
||||
*
|
||||
* @see JRawCommand
|
||||
*/
|
||||
public abstract class JRawCommand @OptIn(ExperimentalPermission::class)
|
||||
public abstract class JRawCommand
|
||||
@JvmOverloads constructor(
|
||||
/**
|
||||
* 指令拥有者.
|
||||
@ -65,7 +64,6 @@ public abstract class JRawCommand @OptIn(ExperimentalPermission::class)
|
||||
protected set
|
||||
|
||||
/** 指令权限 */
|
||||
@ExperimentalPermission
|
||||
public final override var permission: Permission = createOrFindCommandPermission(parentPermission)
|
||||
protected set
|
||||
|
||||
|
@ -14,7 +14,6 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
/**
|
||||
@ -40,7 +39,7 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
* @see SimpleCommand
|
||||
* @see [CommandManager.executeCommand]
|
||||
*/
|
||||
public abstract class JSimpleCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||
public abstract class JSimpleCommand(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
basePermission: Permission,
|
||||
@ -48,7 +47,6 @@ public abstract class JSimpleCommand @OptIn(ExperimentalPermission::class) const
|
||||
public override var description: String = super.description
|
||||
protected set
|
||||
|
||||
@ExperimentalPermission
|
||||
public override var permission: Permission = super.permission
|
||||
protected set
|
||||
public override var prefixOptional: Boolean = super.prefixOptional
|
||||
|
@ -38,8 +38,6 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
|
||||
|
||||
/**
|
||||
* 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用
|
||||
*
|
||||
* @suppress 注意, 这是实验性 API.
|
||||
*/
|
||||
public final override val updaterSerializer: KSerializer<Unit>
|
||||
get() = super.updaterSerializer
|
||||
|
@ -16,7 +16,7 @@ import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.plugin.updateWhen
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
/**
|
||||
@ -38,7 +38,7 @@ public open class AutoSavePluginData private constructor(
|
||||
public constructor() : this(null)
|
||||
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
|
||||
check(owner is AutoSavePluginDataHolder) { "owner must be AutoSavePluginDataHolder for AutoSavePluginData" }
|
||||
|
||||
@ -89,7 +89,7 @@ public open class AutoSavePluginData private constructor(
|
||||
/**
|
||||
* @return `true` 时, 一段时间后, 即使无属性改变, 也会进行保存.
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
protected open fun shouldPerformAutoSaveWheneverChanged(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package net.mamoe.mirai.console.data
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 可以持有相关 [AutoSavePluginData] 的对象.
|
||||
@ -13,7 +13,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
*
|
||||
* @see net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope {
|
||||
/**
|
||||
* [AutoSavePluginData] 每次自动保存时间间隔
|
||||
@ -27,6 +27,6 @@ public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope {
|
||||
* @see LongRange Java 用户使用 [LongRange] 的构造器创建
|
||||
* @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, 如 `3000..50000`
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public val autoSaveIntervalMillis: LongRange
|
||||
}
|
@ -21,8 +21,7 @@ import kotlinx.serialization.KSerializer
|
||||
import net.mamoe.mirai.console.data.java.JAutoSavePluginData
|
||||
import net.mamoe.mirai.console.internal.data.*
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
@ -95,7 +94,7 @@ import kotlin.reflect.full.findAnnotation
|
||||
* newList.add(1) // 不会添加到 MyPluginData.nestedMap 中, 因为 `mutableListOf` 创建的 MutableList 被非引用地添加进了 MyPluginData.nestedMap
|
||||
* ```
|
||||
*
|
||||
* 一个解决方案是对 [SerializerAwareValue] 做映射或相关修改. 如 [PluginDataExtensions]。
|
||||
* 一个解决方案是对 [SerializerAwareValue] 做映射或相关修改. 如 [PluginDataExtensions].
|
||||
*
|
||||
* 要查看详细的解释,请查看 [docs/PluginData.md](https://github.com/mamoe/mirai-console/blob/master/docs/PluginData.md)
|
||||
*
|
||||
@ -117,7 +116,7 @@ public interface PluginData {
|
||||
/**
|
||||
* 这个 [PluginData] 保存时使用的名称. 默认通过 [ValueName] 获取, 否则使用 [类全名][KClass.qualifiedName] (即 [Class.getCanonicalName])
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public val saveName: String
|
||||
get() {
|
||||
val clazz = this::class
|
||||
@ -129,7 +128,7 @@ public interface PluginData {
|
||||
/**
|
||||
* 由 [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点.
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public data class ValueNode<T>(
|
||||
/**
|
||||
* 节点名称.
|
||||
@ -178,8 +177,6 @@ public interface PluginData {
|
||||
|
||||
/**
|
||||
* 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用
|
||||
*
|
||||
* @suppress 注意, 这是实验性 API.
|
||||
*/
|
||||
public val updaterSerializer: KSerializer<Unit>
|
||||
|
||||
@ -191,7 +188,7 @@ public interface PluginData {
|
||||
/**
|
||||
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun onInit(owner: PluginDataHolder, storage: PluginDataStorage)
|
||||
}
|
||||
|
||||
@ -346,6 +343,6 @@ internal fun <T> PluginData.valueImpl(type: KType, classifier: KClass<*>): Seria
|
||||
* - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的类
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun <T> PluginData.valueFromKType(type: KType, default: T): SerializerAwareValue<T> =
|
||||
(valueFromKTypeImpl(type) as SerializerAwareValue<Any?>).apply { this.value = default } as SerializerAwareValue<T>
|
||||
|
@ -4,7 +4,7 @@ package net.mamoe.mirai.console.data
|
||||
|
||||
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
|
||||
import net.mamoe.mirai.console.internal.data.ShadowMap
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
|
||||
/**
|
||||
@ -12,7 +12,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
|
||||
*/
|
||||
public object PluginDataExtensions {
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public open class NotNullMap<K, V> internal constructor(
|
||||
private val delegate: Map<K, V>
|
||||
) : Map<K, V> by delegate {
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 可以持有相关 [PluginData] 实例的对象, 作为 [PluginData] 实例的拥有者.
|
||||
@ -21,12 +21,12 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
*
|
||||
* @see AutoSavePluginDataHolder 支持自动保存
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PluginDataHolder {
|
||||
/**
|
||||
* 保存时使用的分类名
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public val dataHolderName: String
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,9 @@ package net.mamoe.mirai.console.data
|
||||
|
||||
import net.mamoe.mirai.console.internal.data.MemoryPluginDataStorageImpl
|
||||
import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -28,17 +28,20 @@ import java.nio.file.Path
|
||||
*
|
||||
* 此为较低层的 API, 一般插件开发者不会接触.
|
||||
*
|
||||
* [JarPluginLoader] 实现一个 [PluginDataStorage], 用于管理所有 [JvmPlugin] 的 [PluginData] 实例.
|
||||
* [JvmPluginLoader] 实现一个 [PluginDataStorage], 用于管理所有 [JvmPlugin] 的 [PluginData] 实例.
|
||||
*
|
||||
* ### 实现 [PluginDataStorage]
|
||||
* 无特殊需求. 实现两个成员函数即可. 可参考内建 [MultiFilePluginDataStorageImpl]
|
||||
*
|
||||
* @see PluginDataHolder
|
||||
* @see JarPluginLoader.dataStorage
|
||||
* @see JvmPluginLoader.dataStorage
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PluginDataStorage {
|
||||
/**
|
||||
* 读取一个实例. 并为 [instance] [设置 [PluginDataStorage]][PluginData.onInit]
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun load(holder: PluginDataHolder, instance: PluginData)
|
||||
|
||||
/**
|
||||
@ -46,7 +49,7 @@ public interface PluginDataStorage {
|
||||
*
|
||||
* **实现细节**: 调用 [PluginData.updaterSerializer]
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun store(holder: PluginDataHolder, instance: PluginData)
|
||||
|
||||
/*
|
||||
@ -96,7 +99,7 @@ public companion object {
|
||||
* 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失.
|
||||
* @see PluginDataStorage
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface MemoryPluginDataStorage : PluginDataStorage {
|
||||
public companion object {
|
||||
/**
|
||||
@ -112,7 +115,7 @@ public interface MemoryPluginDataStorage : PluginDataStorage {
|
||||
/**
|
||||
* 用多个文件存储 [PluginData] 实例的 [PluginDataStorage].
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface MultiFilePluginDataStorage : PluginDataStorage {
|
||||
/**
|
||||
* 存放 [PluginData] 的目录.
|
||||
|
@ -16,7 +16,7 @@ import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.StringFormat
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.internal.data.setValueBySerializer
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
@ -212,7 +212,7 @@ public interface StringValue : PrimitiveValue<String>
|
||||
/**
|
||||
* 复合数据类型实现
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface CompositeValue<T> : Value<T>
|
||||
|
||||
|
||||
@ -227,7 +227,7 @@ public interface ListValue<E> : CompositeValue<List<E>>
|
||||
*
|
||||
* @param E 不是基础数据类型
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface CompositeListValue<E> : ListValue<E>
|
||||
|
||||
/**
|
||||
@ -235,16 +235,16 @@ public interface CompositeListValue<E> : ListValue<E>
|
||||
*
|
||||
* @param E 是基础类型
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveListValue<E> : ListValue<E>
|
||||
|
||||
|
||||
//// region PrimitiveListValue CODEGEN ////
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveIntListValue : PrimitiveListValue<Int>
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveLongListValue : PrimitiveListValue<Long>
|
||||
// TODO + codegen
|
||||
|
||||
@ -255,30 +255,30 @@ public interface PrimitiveLongListValue : PrimitiveListValue<Long>
|
||||
* @see [CompositeSetValue]
|
||||
* @see [PrimitiveSetValue]
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface SetValue<E> : CompositeValue<Set<E>>
|
||||
|
||||
/**
|
||||
* 复合数据类型 [Set]
|
||||
* @param E 是基础数据类型
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface CompositeSetValue<E> : SetValue<E>
|
||||
|
||||
/**
|
||||
* 基础数据类型 [Set]
|
||||
* @param E 是基础数据类型
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveSetValue<E> : SetValue<E>
|
||||
|
||||
|
||||
//// region PrimitiveSetValue CODEGEN ////
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveIntSetValue : PrimitiveSetValue<Int>
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveLongSetValue : PrimitiveSetValue<Long>
|
||||
// TODO + codegen
|
||||
|
||||
@ -289,27 +289,27 @@ public interface PrimitiveLongSetValue : PrimitiveSetValue<Long>
|
||||
* @see [CompositeMapValue]
|
||||
* @see [PrimitiveMapValue]
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface MapValue<K, V> : CompositeValue<Map<K, V>>
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface CompositeMapValue<K, V> : MapValue<K, V>
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveMapValue<K, V> : MapValue<K, V>
|
||||
|
||||
|
||||
//// region PrimitiveMapValue CODEGEN ////
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveIntIntMapValue : PrimitiveMapValue<Int, Int>
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface PrimitiveIntLongMapValue : PrimitiveMapValue<Int, Long>
|
||||
// TODO + codegen
|
||||
|
||||
//// endregion PrimitiveSetValue CODEGEN ////
|
||||
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public interface ReferenceValue<T> : Value<T>
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.extension
|
||||
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
|
||||
|
||||
/**
|
||||
* 组件容器, 容纳 [Plugin] 注册的 [Extension].
|
||||
*
|
||||
* @see Extension
|
||||
* @see JvmPlugin.onLoad
|
||||
*/
|
||||
public interface ComponentStorage {
|
||||
public fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
extensionInstance: T,
|
||||
)
|
||||
|
||||
public fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
lazyInstance: () -> T,
|
||||
)
|
||||
}
|
||||
|
@ -12,18 +12,36 @@ package net.mamoe.mirai.console.extension
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
|
||||
|
||||
/**
|
||||
* 表示一个扩展.
|
||||
*
|
||||
* ### 获取扩展
|
||||
* Console 不允许插件获取自己或其他插件注册的扩展
|
||||
*
|
||||
* ### 注册扩展
|
||||
* 插件仅能在 [JvmPlugin.onLoad] 阶段注册扩展
|
||||
*
|
||||
* ```kotlin
|
||||
* object MyPlugin : KotlinPlugin( /* ... */ ) {
|
||||
* fun PluginComponentStorage.onLoad() {
|
||||
* contributePermissionService { /* ... */ }
|
||||
* contributePluginLoader { /* ... */ }
|
||||
* contribute(ExtensionPoint) { /* ... */ }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see ComponentStorage
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public interface Extension
|
||||
|
||||
/**
|
||||
* 增加一些函数 (方法)的扩展
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public interface FunctionExtension : Extension
|
||||
|
||||
/**
|
||||
@ -33,7 +51,6 @@ public interface FunctionExtension : Extension
|
||||
*
|
||||
* @see PermissionServiceProvider
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public interface SingletonExtension<T> : Extension {
|
||||
public val instance: T
|
||||
}
|
||||
@ -43,7 +60,6 @@ public interface SingletonExtension<T> : Extension {
|
||||
*
|
||||
* @see PluginLoaderProvider
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public interface InstanceExtension<T> : Extension {
|
||||
public val instance: T
|
||||
}
|
||||
|
@ -7,13 +7,16 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.extension
|
||||
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
public data class ExtensionRegistry<T>(
|
||||
public val plugin: Plugin,
|
||||
public val extension: T
|
||||
)
|
||||
/**
|
||||
* 在调用一个 extension 时遇到的异常.
|
||||
*/
|
||||
public open class ExtensionException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
@ -11,146 +11,20 @@
|
||||
|
||||
package net.mamoe.mirai.console.extension
|
||||
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
/**
|
||||
* 由 [Extension] 的 `companion` 实现.
|
||||
*/
|
||||
public interface ExtensionPoint<T : Extension> {
|
||||
public val type: KClass<T>
|
||||
|
||||
public fun registerExtension(plugin: Plugin, extension: T)
|
||||
public fun getExtensions(): Set<ExtensionRegistry<T>>
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
@JvmSynthetic
|
||||
@ConsoleExperimentalAPI
|
||||
public inline fun <reified T : Extension> ExtensionPoint<*>.isFor(exactType: Boolean = false): Boolean {
|
||||
return if (exactType) {
|
||||
T::class == type
|
||||
} else T::class.isSubclassOf(type)
|
||||
}
|
||||
}
|
||||
public val extensionType: KClass<T>
|
||||
}
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T> {
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
public fun <T : SingletonExtension<*>> SingletonExtensionPoint<T>.findSingleton(): T? {
|
||||
return SingletonExtensionSelector.selectSingleton(type, this.getExtensions())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示一个扩展点
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public open class AbstractExtensionPoint<T : Extension>(
|
||||
@ConsoleExperimentalAPI
|
||||
public override val type: KClass<T>
|
||||
) : ExtensionPoint<T> {
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
allExtensionPoints.add(this)
|
||||
}
|
||||
|
||||
private val instances: MutableSet<ExtensionRegistry<T>> = CopyOnWriteArraySet()
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
public override fun registerExtension(plugin: Plugin, extension: T) {
|
||||
// require(plugin.isEnabled) { "Plugin $plugin must be enabled before registering an extension." }
|
||||
requireNotNull(extension::class.qualifiedName) { "Extension must not be an anonymous object" }
|
||||
instances.add(ExtensionRegistry(plugin, extension))
|
||||
}
|
||||
|
||||
public override fun getExtensions(): Set<ExtensionRegistry<T>> = Collections.unmodifiableSet(instances)
|
||||
|
||||
internal companion object {
|
||||
@ConsoleExperimentalAPI
|
||||
internal val allExtensionPoints: MutableList<AbstractExtensionPoint<*>> = mutableListOf()
|
||||
}
|
||||
}
|
||||
|
||||
public override val extensionType: KClass<T>,
|
||||
) : ExtensionPoint<T>
|
||||
|
||||
/**
|
||||
* 在调用一个 extension 时遇到的异常.
|
||||
*
|
||||
* @see PluginLoader.load
|
||||
* @see PluginLoader.enable
|
||||
* @see PluginLoader.disable
|
||||
* @see PluginLoader.description
|
||||
* 表示一个 [SingletonExtension] 的 [ExtensionPoint]
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public open class ExtensionException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
||||
|
||||
internal inline fun <T : Extension> AbstractExtensionPoint<out T>.withExtensions(block: T.() -> Unit) {
|
||||
return withExtensions { _ -> block() }
|
||||
}
|
||||
|
||||
@LowPriorityInOverloadResolution
|
||||
internal inline fun <T : Extension> AbstractExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin) -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block)
|
||||
}
|
||||
for ((plugin, extension) in this.getExtensions()) {
|
||||
kotlin.runCatching {
|
||||
block.invoke(extension, plugin)
|
||||
}.getOrElse { throwable ->
|
||||
throwExtensionException(extension, plugin, throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <T : Extension, E> AbstractExtensionPoint<out T>.foldExtensions(
|
||||
initial: E,
|
||||
block: (acc: E, extension: T) -> E
|
||||
): E {
|
||||
contract {
|
||||
callsInPlace(block)
|
||||
}
|
||||
var e: E = initial
|
||||
for ((plugin, extension) in this.getExtensions()) {
|
||||
kotlin.runCatching {
|
||||
e = block.invoke(e, extension)
|
||||
}.getOrElse { throwable ->
|
||||
throwExtensionException(extension, plugin, throwable)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
internal fun <T : Extension> AbstractExtensionPoint<out T>.throwExtensionException(
|
||||
extension: T,
|
||||
plugin: Plugin,
|
||||
throwable: Throwable
|
||||
) {
|
||||
throw ExtensionException(
|
||||
"Exception while executing extension ${extension.kClassQualifiedNameOrTip} provided by plugin '${plugin.name}', registered for ${this.type.qualifiedName}",
|
||||
throwable
|
||||
)
|
||||
}
|
||||
|
||||
internal inline fun <T : Extension> AbstractExtensionPoint<T>.useExtensions(block: (extension: T) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
|
||||
@LowPriorityInOverloadResolution
|
||||
internal inline fun <T : Extension> AbstractExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T>
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.extension
|
||||
|
||||
import net.mamoe.mirai.console.extensions.*
|
||||
import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
/**
|
||||
* 添加一些扩展给 [Plugin] 的 [ComponentStorage].
|
||||
*
|
||||
* 所有扩展都会以 'lazy' 形式注册, 由 Console 在不同的启动阶段分别初始化各类扩展.
|
||||
*/
|
||||
@Suppress("EXPOSED_SUPER_CLASS", "unused", "MemberVisibilityCanBePrivate")
|
||||
public class PluginComponentStorage(
|
||||
@JvmField
|
||||
internal val plugin: Plugin,
|
||||
) : AbstractConcurrentComponentStorage() {
|
||||
/**
|
||||
* 注册一个扩展
|
||||
*/
|
||||
public fun <E : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<E>,
|
||||
lazyInstance: () -> E,
|
||||
): Unit = contribute(extensionPoint, plugin, lazyInstance)
|
||||
|
||||
/**
|
||||
* 注册一个扩展
|
||||
*/
|
||||
public inline fun <reified E : Extension> contribute(
|
||||
noinline lazyInstance: () -> E,
|
||||
) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(contribute(
|
||||
(E::class.companionObjectInstance as? ExtensionPoint<E>
|
||||
?: error("Companion object of ${E::class.qualifiedName} is not an ExtensionPoint")),
|
||||
lazyInstance
|
||||
))
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// FunctionExtension
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** 注册一个 [SingletonExtensionSelector] */
|
||||
public fun contributeSingletonExtensionSelector(lazyInstance: () -> SingletonExtensionSelector): Unit =
|
||||
contribute(SingletonExtensionSelector, plugin, lazyInstance)
|
||||
|
||||
/** 注册一个 [BotConfigurationAlterer] */
|
||||
public fun contributeBotConfigurationAlterer(instance: BotConfigurationAlterer): Unit =
|
||||
contribute(BotConfigurationAlterer, plugin, lazyInstance = { instance })
|
||||
|
||||
/** 注册一个 [PostStartupExtension] */
|
||||
public fun contributePostStartupExtension(instance: PostStartupExtension): Unit =
|
||||
contribute(PostStartupExtension, plugin, lazyInstance = { instance })
|
||||
|
||||
/** 注册一个 [PostStartupExtension] */
|
||||
public fun runAfterStartup(block: () -> Unit): Unit = contributePostStartupExtension(block)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// InstanceExtensions & SingletonExtensions
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** 注册一个 [PermissionServiceProvider] */
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePermissionService(
|
||||
lazyInstance: () -> PermissionService<*>,
|
||||
): Unit = contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance))
|
||||
|
||||
/** 注册一个 [PermissionServiceProvider] */
|
||||
@JvmName("contributePermissionServiceProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePermissionService(
|
||||
lazyProvider: () -> PermissionServiceProvider,
|
||||
): Unit = contribute(PermissionServiceProvider, plugin, lazyProvider)
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
/** 注册一个 [PluginLoaderProvider] */
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePluginLoader(lazyInstance: () -> PluginLoader<*, *>): Unit =
|
||||
contribute(PluginLoaderProvider, plugin, LazyPluginLoaderProviderImpl(lazyInstance))
|
||||
|
||||
/** 注册一个 [PluginLoaderProvider] */
|
||||
@JvmName("contributePluginLoaderProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePluginLoader(lazyProvider: () -> PluginLoaderProvider): Unit =
|
||||
contribute(PluginLoaderProvider, plugin, lazyProvider)
|
||||
}
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 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.extensions
|
||||
@ -12,16 +21,15 @@ import net.mamoe.mirai.utils.BotConfiguration
|
||||
*
|
||||
* @see MiraiConsole.addBot
|
||||
*/
|
||||
public interface BotConfigurationAlterer : FunctionExtension {
|
||||
public fun interface BotConfigurationAlterer : FunctionExtension {
|
||||
|
||||
/**
|
||||
* 修改 [configuration], 返回修改完成的 [BotConfiguration]
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun alterConfiguration(
|
||||
botId: Long,
|
||||
configuration: BotConfiguration
|
||||
): BotConfiguration = configuration
|
||||
configuration: BotConfiguration,
|
||||
): BotConfiguration
|
||||
|
||||
public companion object ExtensionPoint :
|
||||
AbstractExtensionPoint<BotConfigurationAlterer>(BotConfigurationAlterer::class)
|
||||
|
@ -1,22 +1,38 @@
|
||||
/*
|
||||
* 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.extensions
|
||||
|
||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.SingletonExtension
|
||||
import net.mamoe.mirai.console.extension.SingletonExtensionPoint
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
|
||||
|
||||
/**
|
||||
* [权限服务][PermissionService] 提供器.
|
||||
*
|
||||
* 当插件注册 [PermissionService] 后, 默认会使用插件的 [PermissionService].
|
||||
*
|
||||
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 和 [PluginLoadPriority.ON_EXTENSIONS] 插件提供
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class),
|
||||
SingletonExtensionPoint<PermissionServiceProvider>
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PermissionServiceProvider
|
||||
*/
|
||||
public class PermissionServiceProviderImpl(override val instance: PermissionService<*>) : PermissionServiceProvider
|
||||
|
||||
/**
|
||||
* @see PermissionServiceProvider
|
||||
*/
|
||||
public class LazyPermissionServiceProviderImpl(initializer: () -> PermissionService<*>) : PermissionServiceProvider {
|
||||
override val instance: PermissionService<*> by lazy(initializer)
|
||||
}
|
@ -1,15 +1,30 @@
|
||||
/*
|
||||
* 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.extensions
|
||||
|
||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.Extension
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
|
||||
/**
|
||||
* 提供扩展 [PluginLoader]
|
||||
*
|
||||
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供
|
||||
* @see Extension
|
||||
*/
|
||||
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
|
||||
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)
|
||||
}
|
||||
|
||||
public class PluginLoaderProviderImpl(override val instance: PluginLoader<*, *>) : PluginLoaderProvider
|
||||
|
||||
public class LazyPluginLoaderProviderImpl(initializer: () -> PluginLoader<*, *>) : PluginLoaderProvider {
|
||||
override val instance: PluginLoader<*, *> by lazy(initializer)
|
||||
}
|
@ -16,8 +16,6 @@ import net.mamoe.mirai.console.extension.FunctionExtension
|
||||
* 在 Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化.
|
||||
*
|
||||
* 这些扩展只会, 且一定会被调用正好一次.
|
||||
*
|
||||
* 此扩展可由所有插件提供
|
||||
*/
|
||||
public fun interface PostStartupExtension : FunctionExtension {
|
||||
/**
|
||||
|
@ -10,9 +10,14 @@
|
||||
package net.mamoe.mirai.console.extensions
|
||||
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.extension.*
|
||||
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
|
||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.Extension
|
||||
import net.mamoe.mirai.console.extension.FunctionExtension
|
||||
import net.mamoe.mirai.console.extension.SingletonExtension
|
||||
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.extension.ExtensionRegistry
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.utils.info
|
||||
import kotlin.reflect.KClass
|
||||
@ -21,21 +26,32 @@ import kotlin.reflect.KClass
|
||||
* 用于同时拥有多个 [SingletonExtension] 时选择一个实例.
|
||||
*
|
||||
* 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器.
|
||||
*
|
||||
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供
|
||||
*/
|
||||
public interface SingletonExtensionSelector : FunctionExtension {
|
||||
public data class Registry<T : Extension>(
|
||||
val plugin: Plugin,
|
||||
val extension: T,
|
||||
)
|
||||
|
||||
/**
|
||||
* @return null 表示使用 builtin
|
||||
*/
|
||||
public fun <T : Extension> selectSingleton(
|
||||
extensionType: KClass<T>,
|
||||
candidates: Collection<ExtensionRegistry<T>>
|
||||
candidates: Collection<Registry<T>>,
|
||||
): T?
|
||||
|
||||
public companion object ExtensionPoint :
|
||||
AbstractExtensionPoint<SingletonExtensionSelector>(SingletonExtensionSelector::class) {
|
||||
internal val instance: SingletonExtensionSelector by lazy {
|
||||
val instances = SingletonExtensionSelector.getExtensions()
|
||||
when {
|
||||
|
||||
private var instanceField: SingletonExtensionSelector? = null
|
||||
|
||||
internal val instance: SingletonExtensionSelector get() = instanceField ?: error("")
|
||||
|
||||
internal fun init() {
|
||||
check(instanceField == null) { "Internal error: reinitialize SingletonExtensionSelector" }
|
||||
val instances = GlobalComponentStorage.run { SingletonExtensionSelector.getExtensions() }
|
||||
instanceField = when {
|
||||
instances.isEmpty() -> BuiltInSingletonExtensionSelector
|
||||
instances.size == 1 -> {
|
||||
instances.single().also { (plugin, ext) ->
|
||||
@ -50,8 +66,14 @@ public interface SingletonExtensionSelector : FunctionExtension {
|
||||
|
||||
internal fun <T : Extension> selectSingleton(
|
||||
extensionType: KClass<T>,
|
||||
candidates: Collection<ExtensionRegistry<T>>
|
||||
candidates: Collection<ExtensionRegistry<T>>,
|
||||
): T? =
|
||||
instance.selectSingleton(extensionType, candidates)
|
||||
instance.selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) })
|
||||
|
||||
|
||||
internal fun <T : Extension> SingletonExtensionSelector.selectSingleton(
|
||||
extensionType: KClass<T>,
|
||||
candidates: Collection<ExtensionRegistry<T>>,
|
||||
): T? = selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) })
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ import java.time.Instant
|
||||
|
||||
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
|
||||
@JvmStatic
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1599005996)
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1599934775)
|
||||
|
||||
@JvmStatic
|
||||
val version: Semver = Semver("1.0-M4-dev-3", Semver.SemverType.LOOSE)
|
||||
val version: Semver = Semver("1.0-M4", Semver.SemverType.LOOSE)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:OptIn(ConsoleExperimentalAPI::class)
|
||||
@file:OptIn(ConsoleExperimentalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.internal
|
||||
|
||||
@ -27,24 +27,26 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.extension.useExtensions
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.extensions.PostStartupExtension
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
|
||||
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
|
||||
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
||||
import net.mamoe.mirai.console.internal.util.autoHexToBytes
|
||||
import net.mamoe.mirai.console.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
|
||||
import net.mamoe.mirai.console.permission.RootPermission
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.nio.file.Path
|
||||
@ -58,6 +60,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
/**
|
||||
* [MiraiConsole] 公开 API 与前端实现的连接桥.
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
|
||||
MiraiConsole {
|
||||
override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet")
|
||||
@ -72,11 +75,11 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
createLogger("main")
|
||||
}
|
||||
override val coroutineContext: CoroutineContext by instance::coroutineContext
|
||||
override val builtInPluginLoaders: List<PluginLoader<*, *>> by instance::builtInPluginLoaders
|
||||
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> by instance::builtInPluginLoaders
|
||||
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl by instance::consoleCommandSender
|
||||
|
||||
override val dataStorageForJarPluginLoader: PluginDataStorage by instance::dataStorageForJarPluginLoader
|
||||
override val configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader
|
||||
override val dataStorageForJvmPluginLoader: PluginDataStorage by instance::dataStorageForJvmPluginLoader
|
||||
override val configStorageForJvmPluginLoader: PluginDataStorage by instance::configStorageForJvmPluginLoader
|
||||
override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
|
||||
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
|
||||
override val consoleInput: ConsoleInput by instance::consoleInput
|
||||
@ -90,7 +93,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
|
||||
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
|
||||
|
||||
@OptIn(ConsoleExperimentalAPI::class, ExperimentalPermission::class)
|
||||
@Suppress("RemoveRedundantBackticks")
|
||||
internal fun doStart() {
|
||||
phase `greeting`@{
|
||||
@ -124,35 +126,48 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
ConsoleDataScope.reloadAll()
|
||||
}
|
||||
|
||||
val pluginLoadSession: PluginManagerImpl.PluginLoadSession
|
||||
|
||||
phase `load BEFORE_EXTENSIONS plugins`@{
|
||||
phase `initialize all plugins`@{
|
||||
PluginManager // init
|
||||
|
||||
mainLogger.verbose { "Loading PluginLoader provider plugins..." }
|
||||
PluginManagerImpl.loadEnablePluginProviderPlugins()
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
|
||||
mainLogger.verbose { "Loading JVM plugins..." }
|
||||
PluginManagerImpl.loadAllPluginsUsingBuiltInLoaders()
|
||||
PluginManagerImpl.initExternalPluginLoaders().let { count ->
|
||||
mainLogger.verbose { "$count external PluginLoader(s) found. " }
|
||||
if (count != 0) {
|
||||
mainLogger.verbose { "Loading external plugins..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phase `load all plugins`@{
|
||||
PluginManagerImpl.loadPlugins(PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider())
|
||||
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
|
||||
}
|
||||
|
||||
phase `collect extensions`@{
|
||||
for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) {
|
||||
resolvedPlugin.castOrNull<AbstractJvmPlugin>()?.let {
|
||||
GlobalComponentStorage.mergeWith(it.componentStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phase `load SingletonExtensionSelector`@{
|
||||
SingletonExtensionSelector.init()
|
||||
val instance = SingletonExtensionSelector.instance
|
||||
if (instance is BuiltInSingletonExtensionSelector) {
|
||||
ConsoleDataScope.addAndReloadConfig(instance.config)
|
||||
}
|
||||
}
|
||||
|
||||
phase `load ON_EXTENSIONS plugins`@{
|
||||
mainLogger.verbose { "Scanning high-priority extension and normal plugins..." }
|
||||
pluginLoadSession = PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider()
|
||||
mainLogger.verbose { "${pluginLoadSession.allKindsOfPlugins.size} plugin(s) found." }
|
||||
|
||||
mainLogger.verbose { "Loading Extension provider plugins..." }
|
||||
PluginManagerImpl.loadEnableHighPriorityExtensionPlugins(pluginLoadSession)
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
|
||||
}
|
||||
|
||||
phase `load PermissionService`@{
|
||||
mainLogger.verbose { "Loading PermissionService..." }
|
||||
|
||||
PermissionService.instanceField = GlobalComponentStorage.run {
|
||||
PermissionServiceProvider.findSingletonInstance(BuiltInPermissionService)
|
||||
}
|
||||
|
||||
PermissionService.INSTANCE.let { ps ->
|
||||
if (ps is BuiltInPermissionService) {
|
||||
ConsoleDataScope.addAndReloadConfig(ps.config)
|
||||
@ -171,13 +186,13 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
CommandManagerImpl.commandListener // start
|
||||
}
|
||||
|
||||
phase `load AFTER_EXTENSION plugins`@{
|
||||
mainLogger.verbose { "Loading normal plugins..." }
|
||||
val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession)
|
||||
mainLogger.verbose { "$count normal plugin(s) loaded." }
|
||||
}
|
||||
phase `enable plugins`@{
|
||||
mainLogger.verbose { "Enabling plugins..." }
|
||||
|
||||
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) loaded." }
|
||||
PluginManagerImpl.enableAllLoadedPlugins()
|
||||
|
||||
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." }
|
||||
}
|
||||
|
||||
phase `auto-login bots`@{
|
||||
runBlocking {
|
||||
@ -198,16 +213,19 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
}
|
||||
}
|
||||
|
||||
PostStartupExtension.useExtensions { it() }
|
||||
GlobalComponentStorage.run {
|
||||
PostStartupExtension.useExtensions { it() }
|
||||
}
|
||||
|
||||
mainLogger.info { "mirai-console started successfully." }
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class MiraiIsCool
|
||||
private annotation class ILoveOmaeKumikoForever
|
||||
|
||||
@MiraiIsCool
|
||||
@ILoveOmaeKumikoForever
|
||||
private inline fun phase(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
|
@ -99,7 +99,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
}
|
||||
|
||||
override fun Command.register(override: Boolean): Boolean {
|
||||
if (this is CompositeCommand) this.subCommands // init
|
||||
if (this is CompositeCommand) this.subCommands // init lazy
|
||||
this.permission // init lazy
|
||||
this.names // init lazy
|
||||
this.description // init lazy
|
||||
this.usage // init lazy
|
||||
|
||||
modifyLock.withLock {
|
||||
if (!override) {
|
||||
|
@ -16,7 +16,6 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.message.data.*
|
||||
@ -46,13 +45,13 @@ internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
baseCommand.names
|
||||
}
|
||||
|
||||
internal abstract class AbstractReflectionCommand @OptIn(ExperimentalPermission::class)
|
||||
internal abstract class AbstractReflectionCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
names: Array<out String>,
|
||||
description: String = "<no description available>",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false
|
||||
prefixOptional: Boolean = false,
|
||||
) : Command, AbstractCommand(
|
||||
owner,
|
||||
names = names,
|
||||
@ -120,27 +119,26 @@ internal abstract class AbstractReflectionCommand @OptIn(ExperimentalPermission:
|
||||
}
|
||||
}
|
||||
|
||||
internal class DefaultSubCommandDescriptor @OptIn(ExperimentalPermission::class) constructor(
|
||||
internal class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: Permission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit,
|
||||
)
|
||||
|
||||
internal inner class SubCommandDescriptor @OptIn(ExperimentalPermission::class) constructor(
|
||||
internal inner class SubCommandDescriptor(
|
||||
val names: Array<out String>,
|
||||
val params: Array<CommandParameter<*>>,
|
||||
val description: String,
|
||||
val permission: Permission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
|
||||
val context: CommandArgumentContext
|
||||
val context: CommandArgumentContext,
|
||||
) {
|
||||
val usage: String = createUsage(this@AbstractReflectionCommand)
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
internal suspend fun parseAndExecute(
|
||||
sender: CommandSender,
|
||||
argsWithSubCommandNameNotRemoved: MessageChain,
|
||||
removeSubName: Boolean
|
||||
removeSubName: Boolean,
|
||||
) {
|
||||
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) 1 else 0)
|
||||
if (!this.permission.testPermission(sender)) {
|
||||
@ -251,7 +249,6 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm
|
||||
appendLine()
|
||||
}.trimEnd()
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
internal fun AbstractReflectionCommand.createSubCommand(
|
||||
function: KFunction<*>,
|
||||
context: CommandArgumentContext
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
|
||||
@JvmSynthetic
|
||||
@Throws(CommandExecutionException::class)
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
command: Command,
|
||||
args: MessageChain,
|
||||
commandName: String,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
if (checkPermission && !command.permission.testPermission(this)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, commandName)
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
command.onCommand(this, args)
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
return CommandExecuteResult.Success(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
args = args
|
||||
)
|
||||
},
|
||||
onFailure = {
|
||||
return CommandExecuteResult.ExecutionFailed(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
exception = it,
|
||||
args = args
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
val command =
|
||||
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
|
||||
val args = messages.flattenCommandComponents()
|
||||
|
||||
return executeCommandInternal(command, args.drop(1).asMessageChain(), commandName, checkPermission)
|
||||
}
|
@ -9,16 +9,12 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -56,56 +52,6 @@ internal fun String.fuzzyMatchWith(target: String): Double {
|
||||
return match.toDouble() / (longerLength + (shorterLength - match))
|
||||
}
|
||||
|
||||
internal inline fun <T : Any> Collection<T>.fuzzySearch(
|
||||
target: String,
|
||||
crossinline index: (T) -> String
|
||||
): T? {
|
||||
var maxElement: T? = null
|
||||
var max = 0.0
|
||||
|
||||
for (t in this) {
|
||||
val r = index(t).fuzzyMatchWith(target)
|
||||
if (r > max) {
|
||||
maxElement = t
|
||||
max = r
|
||||
}
|
||||
}
|
||||
|
||||
if (max >= 0.7) {
|
||||
return maxElement
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 模糊搜索一个List中index最接近target的东西
|
||||
* 并且确保target是唯一的
|
||||
* 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null
|
||||
*/
|
||||
internal inline fun <T : Any> Collection<T>.fuzzySearchOnly(
|
||||
target: String,
|
||||
index: (T) -> String
|
||||
): T? {
|
||||
var potential: T? = null
|
||||
var rate = 0.0
|
||||
var collide = 0
|
||||
this.forEach {
|
||||
with(index(it).fuzzyMatchWith(target)) {
|
||||
if (this > rate) {
|
||||
rate = this
|
||||
potential = it
|
||||
}
|
||||
if (this == 1.0) {
|
||||
collide++
|
||||
}
|
||||
if (collide > 1) {
|
||||
return null//collide
|
||||
}
|
||||
}
|
||||
}
|
||||
return potential
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return candidates
|
||||
@ -141,58 +87,9 @@ internal fun Group.fuzzySearchMember(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
internal fun Command.createOrFindCommandPermission(parent: Permission): Permission {
|
||||
val id = owner.permissionId(primaryName)
|
||||
val id = owner.permissionId("command.$primaryName")
|
||||
return PermissionService.INSTANCE[id] ?: PermissionService.INSTANCE.register(id, description, parent)
|
||||
}
|
||||
|
||||
//// internal
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
@JvmSynthetic
|
||||
@Throws(CommandExecutionException::class)
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
command: Command,
|
||||
args: MessageChain,
|
||||
commandName: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
if (checkPermission && !command.permission.testPermission(this)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, commandName)
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
command.onCommand(this, args)
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
return CommandExecuteResult.Success(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
args = args
|
||||
)
|
||||
},
|
||||
onFailure = {
|
||||
return CommandExecuteResult.ExecutionFailed(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
exception = it,
|
||||
args = args
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
val command =
|
||||
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
|
||||
val args = messages.flattenCommandComponents()
|
||||
|
||||
return executeCommandInternal(command, args.drop(1).asMessageChain(), commandName, checkPermission)
|
||||
}
|
||||
|
@ -14,8 +14,7 @@ import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.PluginDataHolder
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.SilentLogger
|
||||
import net.mamoe.mirai.utils.debug
|
||||
@ -63,9 +62,8 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
return file.toFile().also { it.createNewFile() }
|
||||
}
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
val yaml =/* if (instance.saveName == "PermissionService") Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
|
@ -1,11 +1,10 @@
|
||||
package net.mamoe.mirai.console.internal.extensions
|
||||
package net.mamoe.mirai.console.internal.extension
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.extension.Extension
|
||||
import net.mamoe.mirai.console.extension.ExtensionRegistry
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedName
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
@ -25,7 +24,7 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
|
||||
|
||||
override fun <T : Extension> selectSingleton(
|
||||
extensionType: KClass<T>,
|
||||
candidates: Collection<ExtensionRegistry<T>>
|
||||
candidates: Collection<SingletonExtensionSelector.Registry<T>>,
|
||||
): T? = when {
|
||||
candidates.isEmpty() -> null
|
||||
candidates.size == 1 -> candidates.single().extension
|
||||
@ -42,14 +41,14 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
|
||||
|
||||
private fun <T : Extension> promptForSelectionAndSave(
|
||||
extensionType: KClass<T>,
|
||||
candidates: Collection<ExtensionRegistry<T>>
|
||||
candidates: Collection<SingletonExtensionSelector.Registry<T>>,
|
||||
) = promptForManualSelection(extensionType, candidates)
|
||||
.also { config.value[extensionType.qualifiedName!!] = it.extension.kClassQualifiedName!! }.extension
|
||||
|
||||
private fun <T : Any> promptForManualSelection(
|
||||
private fun <T : Extension> promptForManualSelection(
|
||||
extensionType: KClass<T>,
|
||||
candidates: Collection<ExtensionRegistry<T>>
|
||||
): ExtensionRegistry<T> {
|
||||
candidates: Collection<SingletonExtensionSelector.Registry<T>>,
|
||||
): SingletonExtensionSelector.Registry<T> {
|
||||
MiraiConsole.mainLogger.info { "There are multiple ${extensionType.simpleName}s, please select one:" }
|
||||
|
||||
val candidatesList = candidates.toList()
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.internal.extension
|
||||
|
||||
import net.mamoe.mirai.console.extension.*
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal object GlobalComponentStorage : AbstractConcurrentComponentStorage()
|
||||
internal interface ExtensionRegistry<out E : Extension> {
|
||||
val plugin: Plugin
|
||||
val extension: E
|
||||
|
||||
operator fun component1(): Plugin {
|
||||
return this.plugin
|
||||
}
|
||||
|
||||
operator fun component2(): E {
|
||||
return this.extension
|
||||
}
|
||||
}
|
||||
|
||||
internal class LazyExtensionRegistry<out E : Extension>(
|
||||
override val plugin: Plugin,
|
||||
initializer: () -> E,
|
||||
) : ExtensionRegistry<E> {
|
||||
override val extension: E by lazy { initializer() }
|
||||
}
|
||||
|
||||
internal data class DataExtensionRegistry<out E : Extension>(
|
||||
override val plugin: Plugin,
|
||||
override val extension: E,
|
||||
) : ExtensionRegistry<E>
|
||||
|
||||
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
|
||||
return instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>>
|
||||
}
|
||||
|
||||
internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
|
||||
for ((ep, list) in another.instances) {
|
||||
for (extensionRegistry in list) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
ep as ExtensionPoint<Extension>
|
||||
this.contribute(ep, extensionRegistry.plugin, lazyInstance = { extensionRegistry.extension })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.() -> Unit) {
|
||||
return withExtensions { _ -> block() }
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin) -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block)
|
||||
}
|
||||
for ((plugin, extension) in this.getExtensions()) {
|
||||
kotlin.runCatching {
|
||||
block.invoke(extension, plugin)
|
||||
}.getOrElse { throwable ->
|
||||
throwExtensionException(extension, plugin, throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified E : SingletonExtension<*>> ExtensionPoint<out E>.findSingleton(builtin: E): E =
|
||||
findSingleton(E::class, builtin)
|
||||
|
||||
internal fun <E : SingletonExtension<*>> ExtensionPoint<out E>.findSingleton(type: KClass<E>, builtin: E): E {
|
||||
val candidates = this.getExtensions()
|
||||
return when (candidates.size) {
|
||||
0 -> builtin
|
||||
1 -> candidates.single().extension
|
||||
else -> SingletonExtensionSelector.instance.selectSingleton(type, candidates) ?: builtin
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified E : SingletonExtension<T>, T> ExtensionPoint<out E>.findSingletonInstance(builtin: T): T =
|
||||
findSingletonInstance(E::class, builtin)
|
||||
|
||||
internal fun <E : SingletonExtension<T>, T> ExtensionPoint<out E>.findSingletonInstance(
|
||||
type: KClass<E>,
|
||||
builtin: T,
|
||||
): T {
|
||||
val candidates = this.getExtensions()
|
||||
return when (candidates.size) {
|
||||
0 -> builtin
|
||||
1 -> candidates.single().extension.instance
|
||||
else -> SingletonExtensionSelector.instance.selectSingleton(type, candidates)?.instance ?: builtin
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <T : Extension, E> ExtensionPoint<out T>.foldExtensions(
|
||||
initial: E,
|
||||
block: (acc: E, extension: T) -> E,
|
||||
): E {
|
||||
contract {
|
||||
callsInPlace(block)
|
||||
}
|
||||
var e: E = initial
|
||||
for ((plugin, extension) in this.getExtensions()) {
|
||||
kotlin.runCatching {
|
||||
e = block.invoke(e, extension)
|
||||
}.getOrElse { throwable ->
|
||||
throwExtensionException(extension, plugin, throwable)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
internal fun <T : Extension> ExtensionPoint<out T>.throwExtensionException(
|
||||
extension: T,
|
||||
plugin: Plugin,
|
||||
throwable: Throwable,
|
||||
) {
|
||||
throw ExtensionException(
|
||||
"Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin.name}', registered for '${this.extensionType.qualifiedName}'",
|
||||
throwable
|
||||
)
|
||||
}
|
||||
|
||||
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
|
||||
val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
|
||||
override fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
extensionInstance: T,
|
||||
) {
|
||||
instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance))
|
||||
}
|
||||
|
||||
override fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
lazyInstance: () -> T,
|
||||
) {
|
||||
instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance))
|
||||
}
|
||||
}
|
@ -7,18 +7,15 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
package net.mamoe.mirai.console.internal.permission
|
||||
|
||||
import net.mamoe.mirai.console.data.PluginDataExtensions
|
||||
import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grantedWith
|
||||
import net.mamoe.mirai.console.permission.*
|
||||
import net.mamoe.mirai.console.permission.PermitteeId.Companion.hasChild
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
internal abstract class AbstractConcurrentPermissionService<P : Permission> : PermissionService<P> {
|
||||
protected abstract val permissions: MutableMap<PermissionId, P>
|
||||
protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||
protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>>
|
||||
|
||||
protected abstract fun createPermission(
|
||||
id: PermissionId,
|
||||
@ -31,26 +28,28 @@ internal abstract class AbstractConcurrentPermissionService<P : Permission> : Pe
|
||||
override fun register(id: PermissionId, description: String, parent: Permission): P {
|
||||
val instance = createPermission(id, description, parent)
|
||||
val old = permissions.putIfAbsent(id, instance)
|
||||
if (old != null) throw DuplicatedPermissionRegistrationException(instance, old)
|
||||
if (old != null) throw PermissionRegistryConflictException(instance, old)
|
||||
return instance
|
||||
}
|
||||
|
||||
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P) {
|
||||
override fun permit(permitteeId: PermitteeId, permission: P) {
|
||||
val id = permission.id
|
||||
grantedPermissionsMap[id].add(permissibleIdentifier)
|
||||
grantedPermissionsMap[id].add(permitteeId)
|
||||
}
|
||||
|
||||
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) {
|
||||
grantedPermissionsMap[permission.id].remove(permissibleIdentifier)
|
||||
override fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) {
|
||||
if (recursive) {
|
||||
grantedPermissionsMap[permission.id]
|
||||
} else grantedPermissionsMap[permission.id].remove(permitteeId)
|
||||
}
|
||||
|
||||
override fun getRegisteredPermissions(): Sequence<P> = permissions.values.asSequence()
|
||||
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P> = sequence<P> {
|
||||
override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P> = sequence<P> {
|
||||
for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) {
|
||||
|
||||
val granted =
|
||||
if (permissibleIdentifiers.isEmpty()) false
|
||||
else permissibleIdentifiers.any { permissibleIdentifier.grantedWith(it) }
|
||||
else permissibleIdentifiers.any { permitteeId.hasChild(it) }
|
||||
|
||||
if (granted) get(permissionIdentifier)?.let { yield(it) }
|
||||
}
|
@ -7,20 +7,31 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
package net.mamoe.mirai.console.internal.permission
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.PluginDataExtensions
|
||||
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.permission.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
|
||||
@Suppress("unused") // don't pollute top-level
|
||||
internal fun PermissionService<*>.checkType(permissionType: KClass<out Permission>): PermissionService<Permission> {
|
||||
require(this.permissionType.isSuperclassOf(permissionType)) {
|
||||
"Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " +
|
||||
"Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get"
|
||||
}
|
||||
|
||||
@ExperimentalPermission
|
||||
internal object AllGrantPermissionService : PermissionService<PermissionImpl> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this as PermissionService<Permission>
|
||||
}
|
||||
|
||||
internal object AllPermitPermissionService : PermissionService<PermissionImpl> {
|
||||
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
|
||||
override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class
|
||||
override val rootPermission: PermissionImpl get() = RootPermissionImpl.also { all[it.id] = it }
|
||||
@ -28,34 +39,32 @@ internal object AllGrantPermissionService : PermissionService<PermissionImpl> {
|
||||
override fun register(
|
||||
id: PermissionId,
|
||||
description: String,
|
||||
parent: Permission
|
||||
parent: Permission,
|
||||
): PermissionImpl {
|
||||
val new = PermissionImpl(id, description, parent)
|
||||
val old = all.putIfAbsent(id, new)
|
||||
if (old != null) throw DuplicatedPermissionRegistrationException(new, old)
|
||||
if (old != null) throw PermissionRegistryConflictException(new, old)
|
||||
return new
|
||||
}
|
||||
|
||||
override fun get(id: PermissionId): PermissionImpl? = all[id]
|
||||
override fun getRegisteredPermissions(): Sequence<PermissionImpl> = all.values.asSequence()
|
||||
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<PermissionImpl> =
|
||||
override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<PermissionImpl> =
|
||||
all.values.asSequence()
|
||||
|
||||
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||
override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) {
|
||||
}
|
||||
|
||||
override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean =
|
||||
override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean =
|
||||
true
|
||||
|
||||
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||
override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
private val RootPermissionImpl = PermissionImpl(PermissionId("*", "*"), "The root permission").also { it.parent = it }
|
||||
|
||||
@ExperimentalPermission
|
||||
internal object AllDenyPermissionService : PermissionService<PermissionImpl> {
|
||||
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
|
||||
override val permissionType: KClass<PermissionImpl>
|
||||
@ -65,42 +74,40 @@ internal object AllDenyPermissionService : PermissionService<PermissionImpl> {
|
||||
override fun register(
|
||||
id: PermissionId,
|
||||
description: String,
|
||||
parent: Permission
|
||||
parent: Permission,
|
||||
): PermissionImpl {
|
||||
val new = PermissionImpl(id, description, parent)
|
||||
val old = all.putIfAbsent(id, new)
|
||||
if (old != null) throw DuplicatedPermissionRegistrationException(new, old)
|
||||
if (old != null) throw PermissionRegistryConflictException(new, old)
|
||||
return new
|
||||
}
|
||||
|
||||
override fun get(id: PermissionId): PermissionImpl? = all[id]
|
||||
override fun getRegisteredPermissions(): Sequence<PermissionImpl> = all.values.asSequence()
|
||||
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<PermissionImpl> =
|
||||
override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<PermissionImpl> =
|
||||
emptySequence()
|
||||
|
||||
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||
override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) {
|
||||
}
|
||||
|
||||
override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean =
|
||||
override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean =
|
||||
false
|
||||
|
||||
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||
override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalPermission
|
||||
internal object BuiltInPermissionService : AbstractConcurrentPermissionService<PermissionImpl>(),
|
||||
PermissionService<PermissionImpl> {
|
||||
|
||||
@ExperimentalPermission
|
||||
override val permissionType: KClass<PermissionImpl>
|
||||
get() = PermissionImpl::class
|
||||
override val permissions: ConcurrentHashMap<PermissionId, PermissionImpl> = ConcurrentHashMap()
|
||||
override val rootPermission: PermissionImpl = RootPermissionImpl.also { permissions[it.id] = it }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||
get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||
override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>>
|
||||
get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>>
|
||||
|
||||
override fun createPermission(id: PermissionId, description: String, parent: Permission): PermissionImpl =
|
||||
PermissionImpl(id, description, parent)
|
||||
@ -109,13 +116,12 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
|
||||
ConcurrentSaveData("PermissionService")
|
||||
|
||||
@Suppress("RedundantVisibilityModifier")
|
||||
@ExperimentalPermission
|
||||
internal class ConcurrentSaveData private constructor(
|
||||
public override val saveName: String,
|
||||
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
|
||||
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
|
||||
) : AutoSavePluginConfig() {
|
||||
public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermissibleIdentifier>>
|
||||
by value<MutableMap<PermissionId, MutableSet<AbstractPermissibleIdentifier>>>(ConcurrentHashMap())
|
||||
public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermitteeId>>
|
||||
by value<MutableMap<PermissionId, MutableSet<AbstractPermitteeId>>>(ConcurrentHashMap())
|
||||
.withDefault { CopyOnWriteArraySet() }
|
||||
|
||||
public companion object {
|
||||
@ -132,7 +138,6 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
|
||||
* [Permission] 的简单实现
|
||||
*/
|
||||
@Serializable
|
||||
@ExperimentalPermission
|
||||
internal data class PermissionImpl @Deprecated("Only for Root") constructor(
|
||||
override val id: PermissionId,
|
||||
override val description: String,
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.internal.permission
|
||||
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId.*
|
||||
|
||||
internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
|
||||
val str = string.trim { it.isWhitespace() }.toLowerCase()
|
||||
if (str == "console") return Console
|
||||
if (str.isNotEmpty()) {
|
||||
when (str[0]) {
|
||||
'g' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyGroup
|
||||
else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it }
|
||||
}
|
||||
'f' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyFriend
|
||||
else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it }
|
||||
}
|
||||
'u' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyUser
|
||||
else arg.toLongOrNull()?.let(::ExactUser)?.let { return it }
|
||||
}
|
||||
'c' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyContact
|
||||
}
|
||||
'm' -> kotlin.run {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyMemberFromAnyGroup
|
||||
else {
|
||||
val components = arg.split('.')
|
||||
|
||||
if (components.size == 2) {
|
||||
val groupId = components[0].toLongOrNull() ?: return@run
|
||||
|
||||
if (components[1] == "*") return AnyMember(groupId)
|
||||
else {
|
||||
val memberId = components[1].toLongOrNull() ?: return@run
|
||||
return ExactMember(groupId, memberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
't' -> kotlin.run {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyTempFromAnyGroup
|
||||
else {
|
||||
val components = arg.split('.')
|
||||
|
||||
if (components.size == 2) {
|
||||
val groupId = components[0].toLongOrNull() ?: return@run
|
||||
|
||||
if (components[1] == "*") return AnyTemp(groupId)
|
||||
else {
|
||||
val memberId = components[1].toLongOrNull() ?: return@run
|
||||
return ExactTemp(groupId, memberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error("Cannot deserialize '$str' as AbstractPermissibleIdentifier")
|
||||
}
|
@ -17,36 +17,35 @@ import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.util.PluginServiceHelper.findServices
|
||||
import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices
|
||||
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.jvm.*
|
||||
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
internal object JarPluginLoaderImpl :
|
||||
internal object BuiltInJvmPluginLoaderImpl :
|
||||
AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
|
||||
CoroutineScope by MiraiConsole.childScope("JarPluginLoader", CoroutineExceptionHandler { _, throwable ->
|
||||
JarPluginLoaderImpl.logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
||||
CoroutineScope by MiraiConsole.childScope("JvmPluginLoader", CoroutineExceptionHandler { _, throwable ->
|
||||
BuiltInJvmPluginLoaderImpl.logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
|
||||
}),
|
||||
JarPluginLoader {
|
||||
JvmPluginLoader {
|
||||
|
||||
override val configStorage: PluginDataStorage
|
||||
get() = MiraiConsoleImplementationBridge.configStorageForJarPluginLoader
|
||||
get() = MiraiConsoleImplementationBridge.configStorageForJvmPluginLoader
|
||||
|
||||
@JvmStatic
|
||||
internal val logger: MiraiLogger = MiraiConsole.createLogger(JarPluginLoader::class.simpleName!!)
|
||||
internal val logger: MiraiLogger = MiraiConsole.createLogger(JvmPluginLoader::class.simpleName!!)
|
||||
|
||||
override val dataStorage: PluginDataStorage
|
||||
get() = MiraiConsoleImplementationBridge.dataStorageForJarPluginLoader
|
||||
get() = MiraiConsoleImplementationBridge.dataStorageForJvmPluginLoader
|
||||
|
||||
internal val classLoaders: ConcurrentLinkedQueue<JvmPluginClassLoader> = ConcurrentLinkedQueue()
|
||||
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter
|
||||
override val JvmPlugin.description: JvmPluginDescription
|
||||
get() = this.description
|
||||
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description
|
||||
|
||||
private val pluginFileToInstanceMap: MutableMap<File, JvmPlugin> = ConcurrentHashMap()
|
||||
|
||||
@ -98,10 +97,10 @@ internal object JarPluginLoaderImpl :
|
||||
@Throws(PluginLoadException::class)
|
||||
override fun load(plugin: JvmPlugin) {
|
||||
ensureActive()
|
||||
|
||||
runCatching {
|
||||
if (plugin is JvmPluginInternal) {
|
||||
plugin.internalOnLoad()
|
||||
} else plugin.onLoad()
|
||||
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" }
|
||||
plugin.internalOnLoad(plugin.componentStorage)
|
||||
}.getOrElse {
|
||||
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
|
||||
}
|
@ -14,17 +14,18 @@ import kotlinx.atomicfu.locks.withLock
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.runCatchingLog
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.internal.data.mkdir
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.allocatePermissionIdForPlugin
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
||||
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.NamedSupervisorJob
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
@ -41,10 +42,12 @@ internal val <T> T.job: Job where T : CoroutineScope, T : Plugin get() = this.co
|
||||
*/
|
||||
@PublishedApi
|
||||
internal abstract class JvmPluginInternal(
|
||||
parentCoroutineContext: CoroutineContext
|
||||
parentCoroutineContext: CoroutineContext,
|
||||
) : JvmPlugin, CoroutineScope {
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
@Suppress("LeakingThis")
|
||||
internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this)
|
||||
|
||||
final override val parentPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(name, "*"),
|
||||
@ -58,17 +61,10 @@ internal abstract class JvmPluginInternal(
|
||||
final override fun getResourceAsStream(path: String): InputStream? =
|
||||
resourceContainerDelegate.getResourceAsStream(path)
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
override fun permissionId(id: String): PermissionId {
|
||||
return PermissionId(description.name, id)
|
||||
}
|
||||
|
||||
// region JvmPlugin
|
||||
final override val logger: MiraiLogger by lazy {
|
||||
JarPluginLoaderImpl.logger.runCatchingLog {
|
||||
MiraiConsole.createLogger(
|
||||
"Plugin ${this.description.name}"
|
||||
)
|
||||
BuiltInJvmPluginLoaderImpl.logger.runCatchingLog {
|
||||
MiraiConsole.createLogger(this.description.name)
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
@ -106,8 +102,8 @@ internal abstract class JvmPluginInternal(
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
internal fun internalOnLoad() { // propagate exceptions
|
||||
onLoad()
|
||||
internal fun internalOnLoad(componentStorage: PluginComponentStorage) {
|
||||
onLoad(componentStorage)
|
||||
}
|
||||
|
||||
internal fun internalOnEnable(): Boolean {
|
||||
@ -135,6 +131,7 @@ internal abstract class JvmPluginInternal(
|
||||
// for future use
|
||||
@Suppress("PropertyName")
|
||||
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
|
||||
this as AbstractJvmPlugin
|
||||
CoroutineName("Plugin $dataHolderName")
|
||||
}
|
||||
|
||||
@ -149,12 +146,12 @@ internal abstract class JvmPluginInternal(
|
||||
.plus(parentCoroutineContext)
|
||||
.plus(
|
||||
NamedSupervisorJob(
|
||||
"Plugin $dataHolderName",
|
||||
parentCoroutineContext[Job] ?: JarPluginLoaderImpl.coroutineContext[Job]!!
|
||||
"Plugin ${(this as AbstractJvmPlugin).dataHolderName}",
|
||||
parentCoroutineContext[Job] ?: BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!
|
||||
)
|
||||
)
|
||||
.also {
|
||||
JarPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion {
|
||||
BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion {
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
|
@ -11,24 +11,26 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal.plugin
|
||||
|
||||
import kotlinx.atomicfu.locks.withLock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.extension.useExtensions
|
||||
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
||||
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.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
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.CopyOnWriteArrayList
|
||||
|
||||
internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsole.childScope("PluginManager") {
|
||||
|
||||
@ -41,40 +43,31 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
|
||||
@Suppress("ObjectPropertyName")
|
||||
private val _pluginLoaders: MutableList<PluginLoader<*, *>> by lazy {
|
||||
MiraiConsole.builtInPluginLoaders.toMutableList()
|
||||
builtInLoaders.toMutableList()
|
||||
}
|
||||
private val loadersLock: ReentrantLock = ReentrantLock()
|
||||
|
||||
private val logger = MiraiConsole.createLogger("plugin")
|
||||
|
||||
@JvmField
|
||||
internal val resolvedPlugins: MutableList<Plugin> = mutableListOf()
|
||||
internal val resolvedPlugins: MutableList<Plugin> =
|
||||
CopyOnWriteArrayList() // write operations are mostly performed on init
|
||||
override val plugins: List<Plugin>
|
||||
get() = resolvedPlugins.toList()
|
||||
override val builtInLoaders: List<PluginLoader<*, *>>
|
||||
get() = MiraiConsole.builtInPluginLoaders
|
||||
override val builtInLoaders: List<PluginLoader<*, *>> by lazy {
|
||||
MiraiConsole.builtInPluginLoaders.map { it.value }
|
||||
}
|
||||
override val pluginLoaders: List<PluginLoader<*, *>>
|
||||
get() = _pluginLoaders.toList()
|
||||
|
||||
override val Plugin.description: PluginDescription
|
||||
get() = if (this is JvmPlugin) {
|
||||
this.safeLoader.getDescription(this)
|
||||
this.safeLoader.getPluginDescription(this)
|
||||
} else resolvedPlugins.firstOrNull { it == this }
|
||||
?.loader?.cast<PluginLoader<Plugin, PluginDescription>>()
|
||||
?.getDescription(this)
|
||||
?.getPluginDescription(this)
|
||||
?: error("Plugin is unloaded")
|
||||
|
||||
|
||||
override fun PluginLoader<*, *>.register(): Boolean = loadersLock.withLock {
|
||||
if (_pluginLoaders.any { it::class == this::class }) {
|
||||
return false
|
||||
}
|
||||
_pluginLoaders.add(this)
|
||||
}
|
||||
|
||||
override fun PluginLoader<*, *>.unregister() = loadersLock.withLock {
|
||||
_pluginLoaders.remove(this)
|
||||
}
|
||||
|
||||
init {
|
||||
MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion {
|
||||
plugins.forEach { it.disable() }
|
||||
@ -89,10 +82,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
resolvedPlugins.add(plugin)
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
logger.info { "Successfully loaded plugin ${plugin.description.name}" }
|
||||
logger.info { "Successfully loaded plugin ${getPluginDescription(plugin).name}" }
|
||||
},
|
||||
onFailure = {
|
||||
logger.info { "Cannot load plugin ${plugin.description.name}" }
|
||||
logger.info { "Cannot load plugin ${getPluginDescription(plugin).name}" }
|
||||
throw it
|
||||
}
|
||||
)
|
||||
@ -114,59 +107,65 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
}
|
||||
|
||||
internal class PluginLoadSession(
|
||||
val allKindsOfPlugins: List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>>
|
||||
val allKindsOfPlugins: List<PluginDescriptionWithLoader>,
|
||||
)
|
||||
|
||||
// Phase #2
|
||||
internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession {
|
||||
return PluginLoadSession(loadersLock.withLock { _pluginLoaders.listAllPlugins() })
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Phase #0:
|
||||
// - initialize all plugins using builtin loaders
|
||||
// - sort by dependencies
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 使用 [builtInLoaders] 寻找所有插件, 并初始化其主类.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Throws(PluginMissingDependencyException::class)
|
||||
private fun findAndSortAllPluginsUsingBuiltInLoaders(): List<PluginDescriptionWithLoader> {
|
||||
val allDescriptions =
|
||||
builtInLoaders.listAndSortAllPlugins()
|
||||
.asSequence()
|
||||
.onEach { (_, descriptions) ->
|
||||
descriptions.let(PluginManagerImpl::checkPluginDescription)
|
||||
}
|
||||
|
||||
return allDescriptions.toList().sortByDependencies()
|
||||
}
|
||||
|
||||
// Phase #0
|
||||
internal fun loadEnablePluginProviderPlugins() {
|
||||
loadAndEnableLoaderProvidersUsingBuiltInLoaders()
|
||||
}
|
||||
|
||||
// Phase #3
|
||||
internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int {
|
||||
loadersLock.withLock {
|
||||
session.allKindsOfPlugins.flatMap { it.second }
|
||||
.filter { it.loadPriority == PluginLoadPriority.ON_EXTENSIONS }
|
||||
.sortByDependencies()
|
||||
.also { it.loadAndEnableAllInOrder() }
|
||||
.let { return it.size }
|
||||
internal fun loadAllPluginsUsingBuiltInLoaders() {
|
||||
for ((l, _, p) in findAndSortAllPluginsUsingBuiltInLoaders()) {
|
||||
l.loadPluginNoEnable(p)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase #4
|
||||
internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int {
|
||||
loadersLock.withLock {
|
||||
session.allKindsOfPlugins.flatMap { it.second }
|
||||
.filter { it.loadPriority == PluginLoadPriority.AFTER_EXTENSIONS }
|
||||
.sortByDependencies()
|
||||
.also { it.loadAndEnableAllInOrder() }
|
||||
.let { return it.size }
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Phase #1:
|
||||
// - load PluginLoaderProvider
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Phase #1
|
||||
internal fun loadPluginLoaderProvidedByPlugins() {
|
||||
loadersLock.withLock {
|
||||
internal fun initExternalPluginLoaders(): Int {
|
||||
var count = 0
|
||||
GlobalComponentStorage.run {
|
||||
PluginLoaderProvider.useExtensions { ext, plugin ->
|
||||
logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" }
|
||||
_pluginLoaders.add(ext.instance)
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Phase #2
|
||||
internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession {
|
||||
return PluginLoadSession(_pluginLoaders.filterNot { builtInLoaders.contains(it) }.listAndSortAllPlugins())
|
||||
}
|
||||
|
||||
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
|
||||
this.forEach { (loader, _, plugin) ->
|
||||
loader.loadPluginNoEnable(plugin)
|
||||
}
|
||||
this.forEach { (loader, _, plugin) ->
|
||||
loader.enablePlugin(plugin)
|
||||
}
|
||||
internal fun loadPlugins(session: PluginLoadSession) {
|
||||
session.allKindsOfPlugins.forEach { it.loader.load(it.plugin) }
|
||||
}
|
||||
|
||||
internal fun enableAllLoadedPlugins() {
|
||||
resolvedPlugins.forEach { it.enable() }
|
||||
}
|
||||
|
||||
@kotlin.jvm.Throws(PluginLoadException::class)
|
||||
@ -178,38 +177,25 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Throws(PluginMissingDependencyException::class)
|
||||
private fun loadAndEnableLoaderProvidersUsingBuiltInLoaders(): List<PluginDescriptionWithLoader> {
|
||||
val allDescriptions =
|
||||
builtInLoaders.listAllPlugins()
|
||||
.asSequence()
|
||||
.onEach { (loader, descriptions) ->
|
||||
loader as PluginLoader<Plugin, PluginDescription>
|
||||
|
||||
descriptions.forEach(PluginManagerImpl::checkPluginDescription)
|
||||
descriptions.filter { it.loadPriority == PluginLoadPriority.BEFORE_EXTENSIONS }.sortByDependencies()
|
||||
.loadAndEnableAllInOrder()
|
||||
}
|
||||
.flatMap { it.second.asSequence() }
|
||||
|
||||
return allDescriptions.toList()
|
||||
}
|
||||
|
||||
private fun List<PluginLoader<*, *>>.listAllPlugins(): List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>> {
|
||||
return associateWith { loader ->
|
||||
private fun List<PluginLoader<*, *>>.listAndSortAllPlugins(): List<PluginDescriptionWithLoader> {
|
||||
return flatMap { loader ->
|
||||
loader.listPlugins().map { plugin -> plugin.description.wrapWith(loader, plugin) }
|
||||
}.toList()
|
||||
}.sortByDependencies()
|
||||
}
|
||||
|
||||
@Throws(PluginMissingDependencyException::class)
|
||||
private fun <D : PluginDescription> List<D>.sortByDependencies(): List<D> {
|
||||
val resolved = ArrayList<D>(this.size)
|
||||
|
||||
fun D.canBeLoad(): Boolean = this.dependencies.all { it.isOptional || it in resolved }
|
||||
fun D.canBeLoad(): Boolean = this.dependencies.all { dependency ->
|
||||
val target = resolved.findDependency(dependency)
|
||||
if (target == null) {
|
||||
dependency.isOptional
|
||||
} else {
|
||||
target.checkSatisfies(dependency, this@canBeLoad)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun List<D>.consumeLoadable(): List<D> {
|
||||
val (canBeLoad, cannotBeLoad) = this.partition { it.canBeLoad() }
|
||||
@ -218,9 +204,9 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
}
|
||||
|
||||
fun Collection<PluginDependency>.filterIsMissing(): List<PluginDependency> =
|
||||
this.filterNot { it.isOptional || it in resolved }
|
||||
this.filterNot { it.isOptional || resolved.findDependency(it) != null }
|
||||
|
||||
tailrec fun List<D>.doSort() {
|
||||
fun List<D>.doSort() {
|
||||
if (this.isEmpty()) return
|
||||
|
||||
val beforeSize = this.size
|
||||
@ -228,8 +214,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
check(resultPlugins.size < beforeSize) {
|
||||
throw PluginMissingDependencyException(resultPlugins.joinToString("\n") { badPlugin ->
|
||||
"Cannot load plugin ${badPlugin.name}, missing dependencies: ${
|
||||
badPlugin.dependencies.filterIsMissing()
|
||||
.joinToString()
|
||||
badPlugin.dependencies.filterIsMissing().joinToString()
|
||||
}"
|
||||
})
|
||||
}
|
||||
@ -239,14 +224,12 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
this.doSort()
|
||||
return resolved
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
internal data class PluginDescriptionWithLoader(
|
||||
@JvmField val loader: PluginLoader<Plugin, PluginDescription>, // easier type
|
||||
@JvmField val delegate: PluginDescription,
|
||||
@JvmField val plugin: Plugin
|
||||
@JvmField val plugin: Plugin,
|
||||
) : PluginDescription by delegate
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -259,5 +242,13 @@ internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>, plugin: Plug
|
||||
loader as PluginLoader<Plugin, PluginDescription>, this, plugin
|
||||
)
|
||||
|
||||
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
|
||||
any { it.id == dependency.id }
|
||||
internal fun List<PluginDescription>.findDependency(dependency: PluginDependency): PluginDescription? {
|
||||
return find { it.id.equals(dependency.id, ignoreCase = true) }
|
||||
}
|
||||
|
||||
internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) {
|
||||
val requirement = dependency.versionRequirement
|
||||
if (requirement != null && this.version !in requirement) {
|
||||
throw PluginLoadException("Plugin '${plugin.id}' ('${plugin.id}') requires '${dependency.id}' with version $requirement while the resolved is ${this.version}")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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:JvmName("CommonUtils")
|
||||
|
||||
package net.mamoe.mirai.console.internal.util
|
||||
|
||||
internal inline fun <reified E : Throwable, R> runIgnoreException(block: () -> R): R? {
|
||||
try {
|
||||
return block()
|
||||
} catch (e: Throwable) {
|
||||
if (e is E) return null
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified E : Throwable> runIgnoreException(block: () -> Unit): Unit? {
|
||||
try {
|
||||
return block()
|
||||
} catch (e: Throwable) {
|
||||
if (e is E) return null
|
||||
throw e
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ package net.mamoe.mirai.console.internal.util
|
||||
|
||||
import net.mamoe.mirai.console.internal.data.cast
|
||||
import net.mamoe.mirai.console.internal.data.createInstanceOrNull
|
||||
import net.mamoe.mirai.console.internal.plugin.JarPluginLoaderImpl
|
||||
import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl
|
||||
import java.io.InputStream
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
@ -65,7 +65,7 @@ internal object PluginServiceHelper {
|
||||
|
||||
fun <T : Any> loadAllServicesFromMemoryAndPluginClassLoaders(service: KClass<T>): List<T> {
|
||||
val list = ServiceLoader.load(service.java, this::class.java.classLoader).toList()
|
||||
return list + JarPluginLoaderImpl.classLoaders.flatMap { it.findServices(service).loadAllServices() }
|
||||
return list + BuiltInJvmPluginLoaderImpl.classLoaders.flatMap { it.findServices(service).loadAllServices() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
|
||||
/**
|
||||
*/
|
||||
@ExperimentalPermission("Classname is subject to change")
|
||||
public interface PermissibleIdentifier {
|
||||
public val parents: Array<out PermissibleIdentifier>
|
||||
|
||||
public companion object {
|
||||
@ExperimentalPermission
|
||||
public fun PermissibleIdentifier.grantedWith(with: PermissibleIdentifier): Boolean {
|
||||
return allParentsWithSelf().any { it == with }
|
||||
}
|
||||
|
||||
private fun PermissibleIdentifier.allParentsWithSelf(): Sequence<PermissibleIdentifier> {
|
||||
return sequence {
|
||||
yield(this@allParentsWithSelf)
|
||||
yieldAll(parents.asSequence())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable(with = AbstractPermissibleIdentifier.AsStringSerializer::class)
|
||||
@ExperimentalPermission
|
||||
public sealed class AbstractPermissibleIdentifier(
|
||||
public final override vararg val parents: PermissibleIdentifier
|
||||
) : PermissibleIdentifier {
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
public fun parseFromString(string: String): AbstractPermissibleIdentifier {
|
||||
val str = string.trim { it.isWhitespace() }.toLowerCase()
|
||||
if (str == "console") return Console
|
||||
if (str.isNotEmpty()) {
|
||||
when (str[0]) {
|
||||
'g' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyGroup
|
||||
else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it }
|
||||
}
|
||||
'f' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyFriend
|
||||
else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it }
|
||||
}
|
||||
'u' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyUser
|
||||
else arg.toLongOrNull()?.let(::ExactUser)?.let { return it }
|
||||
}
|
||||
'c' -> {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyContact
|
||||
}
|
||||
'm' -> kotlin.run {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyMemberFromAnyGroup
|
||||
else {
|
||||
val components = arg.split('.')
|
||||
|
||||
if (components.size == 2) {
|
||||
val groupId = components[0].toLongOrNull() ?: return@run
|
||||
|
||||
if (components[1] == "*") return AnyMember(groupId)
|
||||
else {
|
||||
val memberId = components[1].toLongOrNull() ?: return@run
|
||||
return ExactMember(groupId, memberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
't' -> kotlin.run {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyTempFromAnyGroup
|
||||
else {
|
||||
val components = arg.split('.')
|
||||
|
||||
if (components.size == 2) {
|
||||
val groupId = components[0].toLongOrNull() ?: return@run
|
||||
|
||||
if (components[1] == "*") return AnyTemp(groupId)
|
||||
else {
|
||||
val memberId = components[1].toLongOrNull() ?: return@run
|
||||
return ExactTemp(groupId, memberId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
error("Cannot deserialize '$str' as AbstractPermissibleIdentifier")
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
public object AsStringSerializer : KSerializer<AbstractPermissibleIdentifier> by String.serializer().map(
|
||||
serializer = { it.toString() },
|
||||
|
||||
deserializer = d@{ str -> parseFromString(str) }
|
||||
)
|
||||
|
||||
public object AnyGroup : AbstractPermissibleIdentifier(AnyContact) {
|
||||
override fun toString(): String = "g*"
|
||||
}
|
||||
|
||||
public data class ExactGroup(public val groupId: Long) : AbstractPermissibleIdentifier(AnyGroup) {
|
||||
override fun toString(): String = "g$groupId"
|
||||
}
|
||||
|
||||
public data class AnyMember(public val groupId: Long) : AbstractPermissibleIdentifier(AnyMemberFromAnyGroup) {
|
||||
override fun toString(): String = "m$groupId.*"
|
||||
}
|
||||
|
||||
public object AnyMemberFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
|
||||
override fun toString(): String = "m*"
|
||||
}
|
||||
|
||||
public object AnyTempFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
|
||||
override fun toString(): String = "t*"
|
||||
}
|
||||
|
||||
public data class ExactMember(
|
||||
public val groupId: Long,
|
||||
public val memberId: Long
|
||||
) : AbstractPermissibleIdentifier(AnyMember(groupId), ExactUser(memberId)) {
|
||||
override fun toString(): String = "m$groupId.$memberId"
|
||||
}
|
||||
|
||||
public object AnyFriend : AbstractPermissibleIdentifier(AnyUser) {
|
||||
override fun toString(): String = "f*"
|
||||
}
|
||||
|
||||
public data class ExactFriend(
|
||||
public val id: Long
|
||||
) : AbstractPermissibleIdentifier(ExactUser(id)) {
|
||||
override fun toString(): String = "f$id"
|
||||
}
|
||||
|
||||
public data class AnyTemp(
|
||||
public val groupId: Long,
|
||||
) : AbstractPermissibleIdentifier(AnyUser, AnyMember(groupId)) {
|
||||
override fun toString(): String = "t$groupId.*"
|
||||
}
|
||||
|
||||
public data class ExactTemp(
|
||||
public val groupId: Long,
|
||||
public val memberId: Long
|
||||
) : AbstractPermissibleIdentifier(ExactUser(groupId), ExactMember(groupId, memberId)) {
|
||||
override fun toString(): String = "t$groupId.$memberId"
|
||||
}
|
||||
|
||||
public object AnyUser : AbstractPermissibleIdentifier(AnyContact) {
|
||||
override fun toString(): String = "u*"
|
||||
}
|
||||
|
||||
public data class ExactUser(
|
||||
public val id: Long
|
||||
) : AbstractPermissibleIdentifier(AnyUser) {
|
||||
override fun toString(): String = "u$id"
|
||||
}
|
||||
|
||||
public object AnyContact : AbstractPermissibleIdentifier() {
|
||||
override fun toString(): String = "*"
|
||||
}
|
||||
|
||||
public object Console : AbstractPermissibleIdentifier() {
|
||||
override fun toString(): String = "console"
|
||||
}
|
||||
}
|
@ -9,49 +9,75 @@
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
|
||||
/**
|
||||
* 一个权限节点.
|
||||
* 一个权限.
|
||||
*
|
||||
* 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
|
||||
*
|
||||
* 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
|
||||
* **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
|
||||
*
|
||||
* ### 获取 [Permission]
|
||||
*
|
||||
* #### 根权限
|
||||
* [RootPermission] 是所有权限的父权限.
|
||||
*
|
||||
* #### 指令的权限
|
||||
* 每个指令都拥有一个 [Command.permission].
|
||||
*
|
||||
* [BuiltInCommands.parentPermission] 为所有内建指令的权限.
|
||||
*
|
||||
* #### 手动申请权限
|
||||
* [PermissionService.register]
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
public interface Permission {
|
||||
/**
|
||||
* 唯一识别 ID. 所有权限的 [id] 都互不相同.
|
||||
*
|
||||
* @see PermissionService.get 由 [id] 获取已注册的 [Permission]
|
||||
* @see PermissionId
|
||||
*/
|
||||
public val id: PermissionId
|
||||
|
||||
/**
|
||||
* 描述信息. 描述信息在注册权限时强制提供.
|
||||
*/
|
||||
public val description: String
|
||||
|
||||
/**
|
||||
* 父权限.
|
||||
*
|
||||
* [RootPermission] 的 parent 为自身
|
||||
*/
|
||||
public val parent: Permission
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 根权限. 是所有权限的父权限.
|
||||
*
|
||||
* 供 Java 用户使用.
|
||||
*
|
||||
* @see RootPermission 推荐 Kotlin 用户使用.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun getRootPermission(): Permission = PermissionService.INSTANCE.rootPermission
|
||||
|
||||
/**
|
||||
* 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent` ... 直到 [Permission.parent] 为它自己.
|
||||
*/
|
||||
@get:JvmStatic
|
||||
public val Permission.parentsWithSelf: Sequence<Permission>
|
||||
get() = generateSequence(this) { p ->
|
||||
p.parent.takeIf { parent -> parent != p }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有权限的父权限.
|
||||
* 根权限. 是所有权限的父权限. 权限 ID 为 "*:*"
|
||||
*/
|
||||
@get:JvmName("getRootPermission")
|
||||
@ExperimentalPermission
|
||||
@get:JvmSynthetic
|
||||
public val RootPermission: Permission
|
||||
get() = PermissionService.INSTANCE.rootPermission
|
||||
|
||||
/**
|
||||
* 所有内建指令的权限
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
public val RootConsoleBuiltInPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
PermissionId("console", "*"),
|
||||
"The parent of any built-in commands"
|
||||
)
|
||||
}
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ExperimentalPermission
|
||||
public fun Permission.parentsWithSelfSequence(): Sequence<Permission> =
|
||||
generateSequence(this) { p ->
|
||||
p.parent.takeIf { parent -> parent != p }
|
||||
}
|
||||
get() = PermissionService.INSTANCE.rootPermission
|
@ -11,44 +11,56 @@ package net.mamoe.mirai.console.permission
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
|
||||
|
||||
/**
|
||||
* 表示一个 [权限][Permission] 的唯一 ID.
|
||||
*
|
||||
* [PermissionId] 与 [Permission] 唯一对应.
|
||||
*
|
||||
* ### 字符串表示
|
||||
* `"$namespace:$name"`. 如 "console:command.stop", "*:*"
|
||||
*/
|
||||
@Serializable(with = PermissionId.AsStringSerializer::class)
|
||||
@ExperimentalPermission
|
||||
@Serializable(with = PermissionId.PermissionIdAsStringSerializer::class)
|
||||
public data class PermissionId(
|
||||
public val namespace: String,
|
||||
public val id: String
|
||||
public val name: String,
|
||||
) {
|
||||
init {
|
||||
require(!namespace.contains(':')) {
|
||||
"':' is not allowed in namespace"
|
||||
}
|
||||
require(!id.contains(':')) {
|
||||
require(!name.contains(':')) {
|
||||
"':' is not allowed in id"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializer(forClass = PermissionId::class)
|
||||
public object AsClassSerializer
|
||||
|
||||
public object AsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
|
||||
serializer = { it.namespace + ":" + it.id },
|
||||
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
|
||||
serializer = { it.namespace + ":" + it.name },
|
||||
deserializer = { it.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }
|
||||
)
|
||||
|
||||
public override fun toString(): String {
|
||||
return "$namespace:$id"
|
||||
}
|
||||
/**
|
||||
* 返回 `$namespace:$id`
|
||||
*/
|
||||
public override fun toString(): String = "$namespace:$name"
|
||||
|
||||
public companion object {
|
||||
public fun parseFromString(string: String): PermissionId =
|
||||
string.split(':').let { (namespace, id) -> PermissionId(namespace, id) }
|
||||
/**
|
||||
* 由 `$namespace:$id` 解析 [PermissionId].
|
||||
*
|
||||
* @throws IllegalArgumentException 在解析失败时抛出.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun parseFromString(string: String): PermissionId {
|
||||
return kotlin.runCatching {
|
||||
string.split(':').let { (namespace, id) -> PermissionId(namespace, id) }
|
||||
}.getOrElse {
|
||||
throw IllegalArgumentException("Could not parse PermissionId from '$string'", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,12 @@
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
@ExperimentalPermission
|
||||
/**
|
||||
* [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace].
|
||||
*/
|
||||
public interface PermissionIdNamespace {
|
||||
@ExperimentalPermission
|
||||
public fun permissionId(id: String): PermissionId
|
||||
/**
|
||||
* 创建一个此命名空间下的 [PermitteeId]
|
||||
*/
|
||||
public fun permissionId(name: String): PermissionId
|
||||
}
|
@ -12,14 +12,12 @@ package net.mamoe.mirai.console.permission
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 标记一个实验性的权限系统 API.
|
||||
* 表示一个应该由权限插件实现的类.
|
||||
*
|
||||
* 权限系统是在 1.0-M4 引入的一个实验性系统, 目前不具有 API 稳定性.
|
||||
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段.
|
||||
* 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
@MustBeDocumented
|
||||
public annotation class ExperimentalPermission(
|
||||
val message: String = ""
|
||||
)
|
||||
internal annotation class PermissionImplementation
|
@ -1,15 +0,0 @@
|
||||
/*
|
||||
* 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.permission
|
||||
|
||||
@ExperimentalPermission
|
||||
public open class PermissionNotFoundException public constructor(
|
||||
public val id: PermissionId
|
||||
) : Exception(id.toString())
|
@ -11,8 +11,10 @@
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
@ExperimentalPermission
|
||||
public class DuplicatedPermissionRegistrationException(
|
||||
newInstance: Permission,
|
||||
public val existingInstance: Permission
|
||||
) : Exception("Duplicated Permission registry. new: $newInstance, existing: $existingInstance")
|
||||
/**
|
||||
* @see PermissionService.register
|
||||
*/
|
||||
public class PermissionRegistryConflictException(
|
||||
public val newInstance: Permission,
|
||||
public val existingInstance: Permission,
|
||||
) : Exception("Conflicted Permission registry. new: $newInstance, existing: $existingInstance")
|
@ -11,31 +11,61 @@
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import net.mamoe.mirai.console.extension.SingletonExtensionPoint.Companion.findSingleton
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.internal.permission.checkType
|
||||
import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
|
||||
/**
|
||||
* 权限服务. 用于承载 Console 的权限系统.
|
||||
*
|
||||
* ### 可扩展
|
||||
* 权限服务可由插件扩展并覆盖默认实现.
|
||||
*
|
||||
* [PermissionServiceProvider]
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
@PermissionImplementation
|
||||
public interface PermissionService<P : Permission> {
|
||||
@ExperimentalPermission
|
||||
/**
|
||||
* [P] 的类型
|
||||
*/
|
||||
public val permissionType: KClass<P>
|
||||
|
||||
/**
|
||||
* [RootPermission] 的实现
|
||||
*/
|
||||
public val rootPermission: P
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 获取一个已经 [注册][register] 了的 [P]
|
||||
*/
|
||||
public operator fun get(id: PermissionId): P?
|
||||
|
||||
/**
|
||||
* 获取所有已注册的指令列表. 应保证线程安全.
|
||||
*/
|
||||
public fun getRegisteredPermissions(): Sequence<P>
|
||||
public fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P>
|
||||
|
||||
public fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: P): Boolean {
|
||||
/**
|
||||
* 获取 [PermitteeId] 和其父标识的所有被授予的所有直接和间接的权限列表
|
||||
*/
|
||||
public fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P>
|
||||
|
||||
/**
|
||||
* 判断 [permission] 对 [permission] 的权限.
|
||||
*
|
||||
* 返回 `true` 的意义:
|
||||
* - 通常意义: [permitteeId] 拥有 [permission] 的 '能力'
|
||||
* - 实现意义: [permitteeId] 自身或任意父标识 [PermissionService] 被授予高于或等于 [permission] 的权限
|
||||
*
|
||||
* @see Companion.testPermission 接收 [Permittee] 参数的扩展
|
||||
*/
|
||||
public fun testPermission(permitteeId: PermitteeId, permission: P): Boolean {
|
||||
val permissionId = permission.id
|
||||
val all = this[permissionId]?.parentsWithSelfSequence() ?: return false
|
||||
return getGrantedPermissions(permissibleIdentifier).any { p ->
|
||||
val all = this[permissionId]?.parentsWithSelf ?: return false
|
||||
return getPermittedPermissions(permitteeId).any { p ->
|
||||
all.any { p.id == it.id }
|
||||
}
|
||||
}
|
||||
@ -43,27 +73,53 @@ public interface PermissionService<P : Permission> {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Throws(DuplicatedPermissionRegistrationException::class)
|
||||
/**
|
||||
* 申请并注册一个权限 [Permission].
|
||||
*
|
||||
* @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出.
|
||||
*/
|
||||
@Throws(PermissionRegistryConflictException::class)
|
||||
public fun register(
|
||||
id: PermissionId,
|
||||
description: String,
|
||||
parent: Permission = RootPermission
|
||||
parent: Permission = RootPermission,
|
||||
): P
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P)
|
||||
public fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P)
|
||||
/**
|
||||
* 授予 [permitteeId] 以 [permission] 权限
|
||||
*
|
||||
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
|
||||
*
|
||||
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
|
||||
*/
|
||||
public fun permit(permitteeId: PermitteeId, permission: P)
|
||||
|
||||
/**
|
||||
* 撤销 [permitteeId] 的 [permission] 授权
|
||||
*
|
||||
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
|
||||
*
|
||||
* @param recursive `true` 时递归撤销所有子权限.
|
||||
* 例如, 若 [permission] 为 "*:*",
|
||||
* recursive 为 `true` 时撤销全部权限 (因为所有权限都是 "*:*" 的子权限);
|
||||
* 而为 `false` 时仅撤销 "*:*" 本身, 而不会影响子权限.
|
||||
*
|
||||
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
|
||||
*/
|
||||
public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean)
|
||||
|
||||
public companion object {
|
||||
internal var instanceField: PermissionService<*>? = null
|
||||
|
||||
@get:JvmName("getInstance")
|
||||
@JvmStatic
|
||||
public val INSTANCE: PermissionService<out Permission> by lazy {
|
||||
PermissionServiceProvider.findSingleton()?.instance ?: BuiltInPermissionService
|
||||
}
|
||||
public val INSTANCE: PermissionService<out Permission>
|
||||
get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.")
|
||||
|
||||
public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P =
|
||||
get(id) ?: throw PermissionNotFoundException(id)
|
||||
get(id) ?: throw NoSuchElementException("Permission not found: $id")
|
||||
|
||||
internal fun PermissionService<*>.allocatePermissionIdForPlugin(name: String, id: String) =
|
||||
PermissionId("plugin.${name.toLowerCase()}", id.toLowerCase())
|
||||
@ -72,81 +128,68 @@ public interface PermissionService<P : Permission> {
|
||||
|
||||
public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this)
|
||||
|
||||
public fun PermissibleIdentifier.grantPermission(permission: Permission) {
|
||||
INSTANCE.checkType(permission::class).grant(this, permission)
|
||||
public fun PermitteeId.grantPermission(permission: Permission) {
|
||||
INSTANCE.checkType(permission::class).permit(this, permission)
|
||||
}
|
||||
|
||||
public fun PermissibleIdentifier.grantPermission(permissionId: PermissionId) {
|
||||
public fun PermitteeId.grantPermission(permissionId: PermissionId) {
|
||||
grantPermission(permissionId.findCorrespondingPermissionOrFail())
|
||||
}
|
||||
|
||||
public fun PermissibleIdentifier.denyPermission(permission: Permission) {
|
||||
INSTANCE.checkType(permission::class).deny(this, permission)
|
||||
public fun PermitteeId.denyPermission(permission: Permission, recursive: Boolean) {
|
||||
INSTANCE.checkType(permission::class).cancel(this, permission, recursive)
|
||||
}
|
||||
|
||||
public fun PermissibleIdentifier.denyPermission(permissionId: PermissionId) {
|
||||
denyPermission(permissionId.findCorrespondingPermissionOrFail())
|
||||
public fun PermitteeId.denyPermission(permissionId: PermissionId, recursive: Boolean) {
|
||||
denyPermission(permissionId.findCorrespondingPermissionOrFail(), recursive)
|
||||
}
|
||||
|
||||
public fun Permissible.hasPermission(permission: Permission): Boolean =
|
||||
public fun Permittee.hasPermission(permission: Permission): Boolean =
|
||||
permission.testPermission(this@hasPermission)
|
||||
|
||||
public fun PermissibleIdentifier.hasPermission(permission: Permission): Boolean =
|
||||
public fun PermitteeId.hasPermission(permission: Permission): Boolean =
|
||||
permission.testPermission(this@hasPermission)
|
||||
|
||||
public fun PermissibleIdentifier.hasPermission(permissionId: PermissionId): Boolean {
|
||||
public fun PermitteeId.hasPermission(permissionId: PermissionId): Boolean {
|
||||
val instance = permissionId.findCorrespondingPermissionOrFail()
|
||||
return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance)
|
||||
}
|
||||
|
||||
public fun Permissible.hasPermission(permissionId: PermissionId): Boolean =
|
||||
public fun Permittee.hasPermission(permissionId: PermissionId): Boolean =
|
||||
permissionId.testPermission(this@hasPermission)
|
||||
|
||||
public fun Permissible.getGrantedPermissions(): Sequence<Permission> =
|
||||
INSTANCE.getGrantedPermissions(this@getGrantedPermissions.identifier)
|
||||
public fun Permittee.getPermittedPermissions(): Sequence<Permission> =
|
||||
INSTANCE.getPermittedPermissions(this@getPermittedPermissions.permitteeId)
|
||||
|
||||
public fun Permissible.grantPermission(vararg permissions: Permission) {
|
||||
public fun Permittee.grantPermission(vararg permissions: Permission) {
|
||||
for (permission in permissions) {
|
||||
INSTANCE.checkType(permission::class).grant(this.identifier, permission)
|
||||
INSTANCE.checkType(permission::class).permit(this.permitteeId, permission)
|
||||
}
|
||||
}
|
||||
|
||||
public fun Permissible.denyPermission(vararg permissions: Permission) {
|
||||
public fun Permittee.denyPermission(vararg permissions: Permission, recursive: Boolean) {
|
||||
for (permission in permissions) {
|
||||
INSTANCE.checkType(permission::class).deny(this.identifier, permission)
|
||||
INSTANCE.checkType(permission::class).cancel(this.permitteeId, permission, recursive)
|
||||
}
|
||||
}
|
||||
|
||||
public fun PermissibleIdentifier.getGrantedPermissions(): Sequence<Permission> =
|
||||
INSTANCE.getGrantedPermissions(this@getGrantedPermissions)
|
||||
public fun PermitteeId.getPermittedPermissions(): Sequence<Permission> =
|
||||
INSTANCE.getPermittedPermissions(this@getPermittedPermissions)
|
||||
|
||||
public fun Permission.testPermission(permissible: Permissible): Boolean =
|
||||
INSTANCE.checkType(this::class).testPermission(permissible.identifier, this@testPermission)
|
||||
public fun Permission.testPermission(permittee: Permittee): Boolean =
|
||||
INSTANCE.checkType(this::class).testPermission(permittee.permitteeId, this@testPermission)
|
||||
|
||||
public fun Permission.testPermission(permissibleIdentifier: PermissibleIdentifier): Boolean =
|
||||
INSTANCE.checkType(this::class).testPermission(permissibleIdentifier, this@testPermission)
|
||||
public fun Permission.testPermission(permitteeId: PermitteeId): Boolean =
|
||||
INSTANCE.checkType(this::class).testPermission(permitteeId, this@testPermission)
|
||||
|
||||
public fun PermissionId.testPermission(permissible: Permissible): Boolean {
|
||||
public fun PermissionId.testPermission(permittee: Permittee): Boolean {
|
||||
val p = INSTANCE[this] ?: return false
|
||||
return p.testPermission(permissible)
|
||||
return p.testPermission(permittee)
|
||||
}
|
||||
|
||||
public fun PermissionId.testPermission(permissible: PermissibleIdentifier): Boolean {
|
||||
public fun PermissionId.testPermission(permissible: PermitteeId): Boolean {
|
||||
val p = INSTANCE[this] ?: return false
|
||||
return p.testPermission(permissible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
internal fun PermissionService<*>.checkType(permissionType: KClass<out Permission>): PermissionService<Permission> {
|
||||
return PermissionService.INSTANCE.run {
|
||||
require(this.permissionType.isSuperclassOf(permissionType)) {
|
||||
"Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " +
|
||||
"Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get"
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this as PermissionService<Permission>
|
||||
}
|
||||
}
|
||||
}
|
@ -14,15 +14,15 @@ package net.mamoe.mirai.console.permission
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
|
||||
/**
|
||||
* 可拥有权限的对象.
|
||||
* 可被赋予权限的对象, 即 '被许可人'.
|
||||
*
|
||||
* 典型的实例为 [CommandSender]
|
||||
* 被许可人自身不持有拥有的权限列表, 而是拥有 [PermitteeId], 标识自己的身份, 供 [权限服务][PermissionService] 处理.
|
||||
*
|
||||
* 注意: 请不要自主实现 [Permissible]
|
||||
* **注意**: 请不要自主实现 [Permittee]
|
||||
*
|
||||
* @see CommandSender
|
||||
*/
|
||||
@ExperimentalPermission
|
||||
public interface Permissible {
|
||||
public val identifier: PermissibleIdentifier
|
||||
@PermissionImplementation
|
||||
public interface Permittee {
|
||||
public val permitteeId: PermitteeId
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.internal.permission.parseFromStringImpl
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId.*
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.User
|
||||
|
||||
/**
|
||||
* [被许可人][Permittee] 的标识符
|
||||
*
|
||||
* 一个这样的标识符即可代表特定的单个 [Permittee], 也可以表示多个同类 [Permittee].
|
||||
*
|
||||
* ### 获取 [PermitteeId]
|
||||
* 总是通过 [Permittee.permitteeId].
|
||||
*/
|
||||
@PermissionImplementation
|
||||
public interface PermitteeId {
|
||||
/**
|
||||
* 直接父 [PermitteeId]. 在检查权限时会首先检查自己, 再递归检查父类.
|
||||
*
|
||||
* @see allParentsWithSelf
|
||||
* @see allParents
|
||||
*/
|
||||
public val directParents: Array<out PermitteeId>
|
||||
|
||||
/**
|
||||
* 转换为字符串表示. 用于权限服务识别和指令的解析.
|
||||
*/
|
||||
public fun asString(): String
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 当 [this] 或 [this] 的任意一个直接或间接父 [PermitteeId.asString] 与 `this.asString` 相同时返回 `true`
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun PermitteeId.hasChild(child: PermitteeId): Boolean {
|
||||
return allParentsWithSelf.any { it.asString() == child.asString() } // asString is for compatibility issue with external implementations
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有直接或间接父类的 [PermitteeId].
|
||||
*/
|
||||
@get:JvmStatic
|
||||
public val PermitteeId.allParentsWithSelf: Sequence<PermitteeId>
|
||||
get() = sequence {
|
||||
yield(this@allParentsWithSelf)
|
||||
yieldAll(allParents)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有直接或间接父类的 [PermitteeId], 返回包含 `this` + 这些父类 的 [Sequence]
|
||||
*/
|
||||
@get:JvmStatic
|
||||
public val PermitteeId.allParents: Sequence<PermitteeId>
|
||||
get() = directParents.asSequence().flatMap { it.allParentsWithSelf }
|
||||
|
||||
/**
|
||||
* 创建 [AbstractPermitteeId.ExactUser]
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
public val User.permitteeId: ExactUser
|
||||
get() = ExactUser(id)
|
||||
|
||||
/**
|
||||
* 创建 [AbstractPermitteeId.ExactMember]
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
public val Member.permitteeId: ExactMember
|
||||
get() = ExactMember(group.id, id)
|
||||
|
||||
/**
|
||||
* 创建 [AbstractPermitteeId.ExactGroup]
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
public val Group.permitteeId: ExactGroup
|
||||
get() = ExactGroup(id)
|
||||
|
||||
/**
|
||||
* 创建 [AbstractPermitteeId.ExactTemp]
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
public val Member.permitteeIdOnTemp: ExactTemp
|
||||
get() = ExactTemp(group.id, id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内建的 [PermitteeId].
|
||||
*
|
||||
* - 若指令 A 的权限被授予给 [AnyMember], 那么一个 [ExactMember] 可以执行这个指令.
|
||||
*
|
||||
* ```
|
||||
* Console AnyContact
|
||||
* ↑
|
||||
* |
|
||||
* +---------------------------+------------------------+
|
||||
* | |
|
||||
* AnyUser AnyGroup
|
||||
* ↑ ↑
|
||||
* | |
|
||||
* +--------------+---------------------+ |
|
||||
* | | | |
|
||||
* AnyFriend | AnyMemberFromAnyGroup |
|
||||
* ↑ | ↑ |
|
||||
* | | | |
|
||||
* | | +--------+--------------+ |
|
||||
* | | | | |
|
||||
* | | | AnyTempFromAnyGroup |
|
||||
* | | | ↑ |
|
||||
* | | AnyMember | |
|
||||
* | | ↑ | |
|
||||
* | ExactUser | | ExactGroup
|
||||
* | ↑ ↑ | |
|
||||
* | | | | |
|
||||
* +------------+ +----------+ |
|
||||
* | | |
|
||||
* ExactFriend ExactMember |
|
||||
* ↑ |
|
||||
* | |
|
||||
* +-----------------------+
|
||||
* |
|
||||
* |
|
||||
* ExactTemp
|
||||
* ```
|
||||
*/
|
||||
@Serializable(with = AbstractPermitteeId.AsStringSerializer::class)
|
||||
public sealed class AbstractPermitteeId(
|
||||
public final override vararg val directParents: PermitteeId,
|
||||
) : PermitteeId {
|
||||
public final override fun toString(): String = asString()
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 由 [AbstractPermitteeId.asString] 解析 [AbstractPermitteeId]
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun parseFromString(string: String): AbstractPermitteeId = parseFromStringImpl(string)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [asString] 序列化 [AbstractPermitteeId]
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public object AsStringSerializer : KSerializer<AbstractPermitteeId> by String.serializer().map(
|
||||
serializer = AbstractPermitteeId::asString,
|
||||
deserializer = ::parseFromString
|
||||
)
|
||||
|
||||
/**
|
||||
* 表示任何群对象. (不是指群成员, 而是指这个 '群')
|
||||
*
|
||||
* - **直接父标识符**: [AnyContact]
|
||||
* - **间接父标识符**: 无
|
||||
* - 字符串表示: "g*"
|
||||
*
|
||||
* @see AnyMember
|
||||
*/
|
||||
public object AnyGroup : AbstractPermitteeId(AnyContact) {
|
||||
override fun asString(): String = "g*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示一个群
|
||||
*
|
||||
* - **直接父标识符**: [AnyGroup]
|
||||
* - **间接父标识符**: [AnyContact]
|
||||
* - 字符串表示: "g$groupId"
|
||||
*/
|
||||
public data class ExactGroup(public val groupId: Long) : AbstractPermitteeId(AnyGroup) {
|
||||
override fun asString(): String = "g$groupId"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示来自一个群的任意一个成员
|
||||
*
|
||||
* - **直接父标识符**: [AnyMemberFromAnyGroup]
|
||||
* - **间接父标识符**: [AnyUser], [AnyContact]
|
||||
* - 字符串表示: "m$groupId.*"
|
||||
*/
|
||||
public data class AnyMember(public val groupId: Long) : AbstractPermitteeId(AnyMemberFromAnyGroup) {
|
||||
override fun asString(): String = "m$groupId.*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示来自任意群的任意一个成员
|
||||
*
|
||||
* - **直接父标识符**: [AnyUser]
|
||||
* - **间接父标识符**: [AnyContact]
|
||||
* - 字符串表示: "m*"
|
||||
*/
|
||||
public object AnyMemberFromAnyGroup : AbstractPermitteeId(AnyUser) {
|
||||
override fun asString(): String = "m*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示唯一的一个群成员
|
||||
*
|
||||
* - **直接父标识符**: [AnyMember], [ExactUser]
|
||||
* - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact]
|
||||
* - 字符串表示: "m$groupId.$memberId"
|
||||
*/
|
||||
public data class ExactMember(
|
||||
public val groupId: Long,
|
||||
public val memberId: Long,
|
||||
) : AbstractPermitteeId(AnyMember(groupId), ExactUser(memberId)) {
|
||||
override fun asString(): String = "m$groupId.$memberId"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示任何好友
|
||||
*
|
||||
* - **直接父标识符**: [AnyUser]
|
||||
* - **间接父标识符**: [AnyContact]
|
||||
* - 字符串表示: "f*"
|
||||
*/
|
||||
public object AnyFriend : AbstractPermitteeId(AnyUser) {
|
||||
override fun asString(): String = "f*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示唯一的一个好友
|
||||
*
|
||||
* - **直接父标识符**: [ExactUser]
|
||||
* - **间接父标识符**: [AnyUser], [AnyContact]
|
||||
* - 字符串表示: "f$id"
|
||||
*/
|
||||
public data class ExactFriend(
|
||||
public val id: Long,
|
||||
) : AbstractPermitteeId(ExactUser(id)) {
|
||||
override fun asString(): String = "f$id"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示任何一个通过一个群 *在临时会话发送消息的* [群成员][Member]
|
||||
*
|
||||
* - **直接父标识符**: [AnyMember], [AnyTempFromAnyGroup]
|
||||
* - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact]
|
||||
* - 字符串表示: "t$groupId.*"
|
||||
*/
|
||||
public data class AnyTemp(
|
||||
public val groupId: Long,
|
||||
) : AbstractPermitteeId(AnyMember(groupId), AnyTempFromAnyGroup) {
|
||||
override fun asString(): String = "t$groupId.*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示任何一个 *在临时会话发送消息的* [群成员][Member]
|
||||
*
|
||||
* - **直接父标识符**: [AnyUser]
|
||||
* - **间接父标识符**: [AnyContact]
|
||||
* - 字符串表示: "t*"
|
||||
*/
|
||||
public object AnyTempFromAnyGroup : AbstractPermitteeId(AnyUser) {
|
||||
override fun asString(): String = "t*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示唯一的一个 *在临时会话发送消息的* [群成员][Member]
|
||||
*
|
||||
* - **直接父标识符**: [ExactMember]
|
||||
* - **间接父标识符**: [AnyUser], [AnyMember], [ExactUser], [AnyContact]
|
||||
* - 字符串表示: "t$groupId.$memberId"
|
||||
*/
|
||||
public data class ExactTemp(
|
||||
public val groupId: Long,
|
||||
public val memberId: Long,
|
||||
) : AbstractPermitteeId(ExactMember(groupId, memberId)) {
|
||||
override fun asString(): String = "t$groupId.$memberId"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 表示任何 [用户][User]
|
||||
*
|
||||
* - **直接父标识符**: [AnyContact]
|
||||
* - **间接父标识符**: 无
|
||||
* - 字符串表示: "u*"
|
||||
*/
|
||||
public object AnyUser : AbstractPermitteeId(AnyContact) {
|
||||
override fun asString(): String = "u*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示任何 [用户][User]
|
||||
*
|
||||
* - **直接父标识符**: [AnyUser]
|
||||
* - **间接父标识符**: [AnyContact]
|
||||
* - 字符串表示: "u$id"
|
||||
*/
|
||||
public data class ExactUser(
|
||||
public val id: Long,
|
||||
) : AbstractPermitteeId(AnyUser) {
|
||||
override fun asString(): String = "u$id"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示任何 [联系对象][Contact]
|
||||
*
|
||||
* - **直接父标识符**: 无
|
||||
* - **间接父标识符**: 无
|
||||
* - 字符串表示: "*"
|
||||
*/
|
||||
public object AnyContact : AbstractPermitteeId() {
|
||||
override fun asString(): String = "*"
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示控制台
|
||||
*
|
||||
* - **直接父标识符**: 无
|
||||
* - **间接父标识符**: 无
|
||||
* - 字符串表示: "console"
|
||||
*/
|
||||
public object Console : AbstractPermitteeId() {
|
||||
override fun asString(): String = "console"
|
||||
}
|
||||
}
|
@ -18,8 +18,8 @@ import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
|
||||
/**
|
||||
* 表示一个 mirai-console 插件.
|
||||
@ -52,7 +52,7 @@ public interface Plugin : CommandOwner {
|
||||
/**
|
||||
* 获取 [PluginDescription]
|
||||
*/
|
||||
public inline val Plugin.description: PluginDescription get() = this.safeLoader.getDescription(this)
|
||||
public inline val Plugin.description: PluginDescription get() = this.safeLoader.getPluginDescription(this)
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.name`]
|
||||
@ -64,11 +64,6 @@ public inline val Plugin.name: String get() = this.description.name
|
||||
*/
|
||||
public inline val Plugin.version: Semver get() = this.description.version
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.loadPriority]
|
||||
*/
|
||||
public inline val Plugin.loadPriority: PluginLoadPriority get() = this.description.loadPriority
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.info]
|
||||
*/
|
||||
|
@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.console.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 插件加载器.
|
||||
*
|
||||
* 插件加载器只实现寻找插件列表, 加载插件, 启用插件, 关闭插件这四个功能.
|
||||
*
|
||||
* 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护.
|
||||
*
|
||||
* ## 内建加载器
|
||||
* - [JarPluginLoader] Jar 插件加载器
|
||||
*
|
||||
* ## 扩展加载器
|
||||
* 插件被允许扩展一个加载器.
|
||||
* Console 使用 [ServiceLoader] 加载 [PluginLoader] 的实例.
|
||||
* 插件也可通过 [PluginManager.register] 手动注册, 然而这是不推荐的.
|
||||
*
|
||||
* ### 实现扩展加载器
|
||||
* 直接实现接口 [PluginLoader] 或 [FilePluginLoader], 并添加 [ServiceLoader] 相关资源文件即可.
|
||||
*
|
||||
* @see JarPluginLoader Jar 插件加载器
|
||||
* @see PluginManager.register 注册一个扩展的插件加载器
|
||||
*/
|
||||
public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
/**
|
||||
* 扫描并返回可以被加载的插件的列表.
|
||||
*
|
||||
* 这些插件都应处于还未被加载的状态.
|
||||
*
|
||||
* 在 console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
|
||||
*
|
||||
* **实现细节:** 此函数*只应该*在 console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用.
|
||||
*/
|
||||
public fun listPlugins(): List<P>
|
||||
|
||||
/**
|
||||
* 获取此插件的描述.
|
||||
*
|
||||
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
*
|
||||
* 若在 console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
|
||||
*
|
||||
* @see PluginDescription 插件描述
|
||||
* @see getDescription 无 receiver, 接受参数的版本.
|
||||
*/
|
||||
@get:JvmName("getPluginDescription")
|
||||
@get:Throws(PluginLoadException::class)
|
||||
public val P.description: D // Java signature: `public D getDescription(P)`
|
||||
|
||||
/**
|
||||
* 加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例
|
||||
*
|
||||
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
*/
|
||||
@Throws(PluginLoadException::class)
|
||||
public fun load(plugin: P)
|
||||
|
||||
/**
|
||||
* 启用这个插件.
|
||||
*
|
||||
* **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
*
|
||||
* @see PluginManager.enable
|
||||
*/
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun enable(plugin: P)
|
||||
|
||||
/**
|
||||
* 禁用这个插件.
|
||||
*
|
||||
* **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
*
|
||||
* @see PluginManager.disable
|
||||
*/
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun disable(plugin: P)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此插件的描述.
|
||||
*
|
||||
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
*
|
||||
* 若在 console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
|
||||
*
|
||||
* @see PluginDescription 插件描述
|
||||
* @see PluginLoader.description
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@JvmSynthetic
|
||||
public inline fun <D : PluginDescription, P : Plugin> PluginLoader<in P, out D>.getDescription(plugin: P): D =
|
||||
plugin.description
|
||||
|
||||
/**
|
||||
* 在加载插件过程中遇到的意料之中的问题.
|
||||
*
|
||||
* @see PluginLoader.load
|
||||
* @see PluginLoader.enable
|
||||
* @see PluginLoader.disable
|
||||
* @see PluginLoader.description
|
||||
*/
|
||||
public open class PluginLoadException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* ['/plugins'][PluginManager.pluginsPath] 目录中的插件的加载器. 每个加载器需绑定一个后缀.
|
||||
*
|
||||
* @see AbstractFilePluginLoader 默认基础实现
|
||||
* @see JarPluginLoader 内建的 Jar (JVM) 插件加载器.
|
||||
*/
|
||||
public interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> {
|
||||
/**
|
||||
* 所支持的插件文件后缀, 含 '.'. 如 [JarPluginLoader] 为 ".jar"
|
||||
*/
|
||||
public val fileSuffix: String
|
||||
}
|
||||
|
||||
/**
|
||||
* [FilePluginLoader] 的默认基础实现.
|
||||
*
|
||||
* @see FilePluginLoader
|
||||
*/
|
||||
public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
|
||||
/**
|
||||
* 所支持的插件文件后缀, 含 '.'. 如 [JarPluginLoader] 为 ".jar"
|
||||
*/
|
||||
public override val fileSuffix: String
|
||||
) : FilePluginLoader<P, D> {
|
||||
private fun pluginsFilesSequence(): Sequence<File> =
|
||||
PluginManager.pluginsFolder.listFiles().orEmpty().asSequence()
|
||||
.filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
|
||||
|
||||
/**
|
||||
* 读取扫描到的后缀与 [fileSuffix] 相同的文件中的插件实例, 但不 [加载][PluginLoader.load]
|
||||
*/
|
||||
protected abstract fun Sequence<File>.extractPlugins(): List<P>
|
||||
|
||||
public final override fun listPlugins(): List<P> = pluginsFilesSequence().extractPlugins()
|
||||
}
|
||||
|
||||
|
||||
// Not yet decided to make public API
|
||||
internal class DeferredPluginLoader<P : Plugin, D : PluginDescription>(
|
||||
initializer: () -> PluginLoader<P, D>
|
||||
) : PluginLoader<P, D> {
|
||||
private val instance by lazy(initializer)
|
||||
|
||||
override fun listPlugins(): List<P> = instance.run { listPlugins() }
|
||||
override val P.description: D get() = instance.run { description }
|
||||
override fun load(plugin: P) = instance.load(plugin)
|
||||
override fun enable(plugin: P) = instance.enable(plugin)
|
||||
override fun disable(plugin: P) = instance.disable(plugin)
|
||||
}
|
@ -14,10 +14,9 @@ package net.mamoe.mirai.console.plugin
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 插件管理器.
|
||||
@ -104,23 +103,7 @@ public interface PluginManager {
|
||||
public val pluginLoaders: List<PluginLoader<*, *>>
|
||||
|
||||
/**
|
||||
* 手动注册一个扩展的插件加载器. 在启动时会通过 [ServiceLoader] 加载, 但也可以手动注册.
|
||||
*
|
||||
* @see PluginLoader 插件加载器
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public fun PluginLoader<*, *>.register(): Boolean
|
||||
|
||||
/**
|
||||
* 取消注册一个扩展的插件加载器
|
||||
*
|
||||
* @see PluginLoader 插件加载器
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public fun PluginLoader<*, *>.unregister(): Boolean
|
||||
|
||||
/**
|
||||
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription]
|
||||
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getPluginDescription]
|
||||
*/
|
||||
public val Plugin.description: PluginDescription
|
||||
|
||||
@ -158,8 +141,6 @@ public interface PluginManager {
|
||||
public companion object INSTANCE : PluginManager by PluginManagerImpl {
|
||||
// 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() }
|
||||
public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() }
|
||||
public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() }
|
||||
public override fun Plugin.load(): Unit = PluginManagerImpl.run { load() }
|
||||
|
@ -11,14 +11,17 @@ package net.mamoe.mirai.console.plugin.center
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import java.io.File
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
/**
|
||||
* 插件中心, 计划实现中
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public interface PluginCenter {
|
||||
|
||||
@Serializable
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public data class PluginInsight(
|
||||
val name: String,
|
||||
val version: String,
|
||||
@ -29,10 +32,10 @@ public interface PluginCenter {
|
||||
val author: String,
|
||||
val description: String,
|
||||
val tags: List<String>,
|
||||
val commands: List<String>
|
||||
val commands: List<String>,
|
||||
)
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
@Serializable
|
||||
public data class PluginInfo(
|
||||
val name: String,
|
||||
@ -48,7 +51,7 @@ public interface PluginCenter {
|
||||
val usage: String,
|
||||
val vcs: String,
|
||||
val commands: List<String>,
|
||||
val changeLog: List<String>
|
||||
val changeLog: List<String>,
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,21 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
/**
|
||||
* 在检查到非法 [PluginDescription] 时抛出.
|
||||
*
|
||||
* @see PluginDescription.checkPluginDescription
|
||||
*/
|
||||
public class IllegalPluginDescriptionException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
|
||||
/**
|
||||
* 插件的一个依赖的信息.
|
||||
*
|
||||
@ -28,27 +26,27 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
*
|
||||
* 版本遵循 [语义化版本 2.0 规范](https://semver.org/lang/zh-CN/),
|
||||
*
|
||||
* 允许 [Apache Ivy 风格版本号表示](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html)
|
||||
* ### 示例
|
||||
* `Requirement.buildIvy("[1.0, 2.0)")`
|
||||
*/
|
||||
public val version: Semver? = null,
|
||||
public val versionRequirement: VersionRequirement? = null,
|
||||
/**
|
||||
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
|
||||
*/
|
||||
public val isOptional: Boolean = false
|
||||
public val isOptional: Boolean = false,
|
||||
) {
|
||||
init {
|
||||
kotlin.runCatching {
|
||||
PluginDescription.checkPluginId(id)
|
||||
}.getOrElse {
|
||||
throw IllegalArgumentException(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
public constructor(name: String, isOptional: Boolean = false) : this(
|
||||
name, null, isOptional
|
||||
)
|
||||
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
public constructor(name: String, version: String, isOptional: Boolean) : this(
|
||||
name,
|
||||
Semver(version, Semver.SemverType.IVY),
|
||||
isOptional
|
||||
)
|
||||
}
|
@ -11,7 +11,6 @@ package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||
|
||||
|
||||
/**
|
||||
@ -21,51 +20,45 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||
*/
|
||||
public interface PluginDescription {
|
||||
/**
|
||||
* 插件类型. 将会决定加载顺序
|
||||
*
|
||||
* @see PluginLoadPriority
|
||||
*/
|
||||
public val loadPriority: PluginLoadPriority
|
||||
|
||||
/**
|
||||
* 插件 ID, 必须全英文, 仅允许英文字母, '-', '_', '.'.
|
||||
* 插件 ID. 用于指令权限等一些内部处理
|
||||
*
|
||||
* - 仅允许英文字母, '-', '_', '.'. 在内部操作处理时不区分大小写.
|
||||
* - 类似于 Java 包名, 插件 ID 需要 '域名.名称' 格式, 如 `net.mamoe.mirai.example-plugin`
|
||||
* - 域名和名称都是必须的
|
||||
* - '.' 不允许位于首位或末尾
|
||||
* - '-' 和 '_' 仅允许存在于两个英文字母之间
|
||||
* - 数字和符号都不允许位于首位
|
||||
* - '-' 和 '_' 仅允许存在于两个英文字母之间. 不推荐使用 '_'. 请参照 `org.example.mirai.plugin.my-example-plugin` 格式.
|
||||
*
|
||||
* 可使用 [ID_REGEX] 检验格式合法性.
|
||||
*
|
||||
* ID 在插件发布后就应该保持不变, 以便其他插件添加依赖.
|
||||
*
|
||||
* 插件 ID 的域名和名称都不能完全是以下其中一个 ([FORBIDDEN_ID_WORDS]).
|
||||
* 插件 ID 的域名和名称都不能完全是以下其中一个 ([FORBIDDEN_ID_NAMES]).
|
||||
* - "console"
|
||||
* - "main"
|
||||
* - "plugin"
|
||||
* - "config"
|
||||
* - "data"
|
||||
*
|
||||
* ### 示例
|
||||
* - 合法 `net.mamoe.mirai.example-plugin`
|
||||
* - 非法 `.example-plugin`
|
||||
*
|
||||
* ID 用于指令权限等一些内部处理
|
||||
*
|
||||
* @see FORBIDDEN_ID_LETTERS
|
||||
* @see FORBIDDEN_ID_WORDS
|
||||
* @see ID_REGEX
|
||||
* @see FORBIDDEN_ID_NAMES
|
||||
*/
|
||||
public val id: String
|
||||
|
||||
/**
|
||||
* 插件名称. 允许中文, 允许各类符号.
|
||||
* 插件名称用于展示给用户,仅取决于 `PluginDescription` 提供的 `name`,与主类类名等其他信息无关.
|
||||
*
|
||||
* 插件名称不能完全是以下其中一种 ([FORBIDDEN_ID_WORDS]).
|
||||
* 名称允许中文, 允许各类符号,但不能完全是以下其中一种(忽略大小写)([FORBIDDEN_ID_NAMES]):
|
||||
* - console
|
||||
* - main
|
||||
* - plugin
|
||||
* - config
|
||||
* - data
|
||||
*
|
||||
* 插件名称用于显示给用户.
|
||||
*
|
||||
* @see FORBIDDEN_ID_LETTERS
|
||||
* @see FORBIDDEN_ID_WORDS
|
||||
* @see FORBIDDEN_ID_NAMES
|
||||
*/
|
||||
public val name: String
|
||||
|
||||
@ -87,7 +80,7 @@ public interface PluginDescription {
|
||||
* - `1.0.0-M2-1`
|
||||
* - `1` (尽管非常不建议这么做)
|
||||
*
|
||||
* 非法版本号实例:
|
||||
* 非法版本号示例:
|
||||
* - `DEBUG-1`
|
||||
* - `-1.0`
|
||||
* - `v1.0` (不允许 "v")
|
||||
@ -110,8 +103,22 @@ public interface PluginDescription {
|
||||
public val dependencies: Set<PluginDependency>
|
||||
|
||||
public companion object {
|
||||
public val FORBIDDEN_ID_LETTERS: Array<String> = "~!@#$%^&*()+/*<>{}|[]\\?".map(Char::toString).toTypedArray()
|
||||
public val FORBIDDEN_ID_WORDS: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
|
||||
/**
|
||||
* [PluginDescription.id] 的合法 [Regex].
|
||||
*
|
||||
* - Group 1: 域名
|
||||
* - Group 2: 名称
|
||||
*
|
||||
* @see PluginDescription.id
|
||||
*/
|
||||
public val ID_REGEX: Regex = Regex("""([a-zA-Z]+(?:\.[a-zA-Z0-9]+)*)\.([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)""")
|
||||
|
||||
/**
|
||||
* 在 [PluginDescription.id] 和 [PluginDescription.name] 中禁止用的完全匹配名称列表.
|
||||
*
|
||||
* @see PluginDescription.id
|
||||
*/
|
||||
public val FORBIDDEN_ID_NAMES: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
|
||||
|
||||
/**
|
||||
* 依次检查 [PluginDescription] 的 [PluginDescription.id], [PluginDescription.name], [PluginDescription.dependencies] 的合法性
|
||||
@ -126,57 +133,62 @@ public interface PluginDescription {
|
||||
checkDependencies(instance.id, instance.dependencies)
|
||||
}.getOrElse {
|
||||
throw IllegalPluginDescriptionException(
|
||||
"Illegal description. Plugin ${instance.name} (${instance.id})",
|
||||
"Illegal PluginDescription. Plugin ${instance.name} (${instance.id})",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 [PluginDescription.id] 的合法性.
|
||||
* 检查 [PluginDescription.id] 的合法性. 忽略大小写.
|
||||
*
|
||||
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||
*/
|
||||
@Throws(IllegalPluginDescriptionException::class)
|
||||
public fun checkPluginId(id: String) {
|
||||
if (id.isBlank()) throw IllegalPluginDescriptionException("Plugin id cannot be blank")
|
||||
if (id.count { it == '.' } < 2) throw IllegalPluginDescriptionException("'$id' is illegal. Plugin id must consist of both domain and name. ")
|
||||
if (id.none { it == '.' }) throw IllegalPluginDescriptionException("'$id' is illegal. Plugin id must consist of both domain and name. ")
|
||||
|
||||
FORBIDDEN_ID_LETTERS.firstOrNull { it in id }?.let { illegal ->
|
||||
throw IllegalPluginDescriptionException("Plugin id contains illegal char: $illegal.")
|
||||
val lowercaseId = id.toLowerCase()
|
||||
|
||||
if (ID_REGEX.matchEntire(id) == null) {
|
||||
throw IllegalPluginDescriptionException("Plugin does not match regex '${ID_REGEX.pattern}'.")
|
||||
}
|
||||
|
||||
val idSections = id.split('.')
|
||||
FORBIDDEN_ID_WORDS.firstOrNull { it in idSections }?.let { illegal ->
|
||||
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal ->
|
||||
throw IllegalPluginDescriptionException("Plugin id contains illegal word: '$illegal'.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 [PluginDescription.name] 的合法性.
|
||||
* 检查 [PluginDescription.name] 的合法性. 忽略大小写.
|
||||
*
|
||||
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||
*/
|
||||
@Throws(IllegalPluginDescriptionException::class)
|
||||
public fun checkPluginName(name: String) {
|
||||
if (name.isBlank()) throw IllegalPluginDescriptionException("Plugin name cannot be blank")
|
||||
FORBIDDEN_ID_WORDS.firstOrNull { it in name }?.let { illegal ->
|
||||
val lowercaseName = name.toLowerCase()
|
||||
FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal ->
|
||||
throw IllegalPluginDescriptionException("Plugin name is illegal: '$illegal'.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 [PluginDescription.dependencies] 的合法性.
|
||||
* 检查 [PluginDescription.dependencies] 的合法性. 忽略大小写.
|
||||
*
|
||||
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||
*/
|
||||
@Throws(IllegalPluginDescriptionException::class)
|
||||
public fun checkDependencies(pluginId: String, dependencies: Set<PluginDependency>) {
|
||||
if (dependencies.distinctBy { it.id }.size != dependencies.size)
|
||||
throw PluginLoadException("Duplicated dependency detected: A plugin cannot depend on different versions of dependencies of the same id")
|
||||
val lowercaseId = pluginId.toLowerCase()
|
||||
val lowercaseDependencies = dependencies.mapTo(LinkedHashSet(dependencies.size)) { it.id.toLowerCase() }
|
||||
|
||||
if (dependencies.any { it.id == pluginId })
|
||||
throw PluginLoadException("Recursive dependency detected: A plugin cannot depend on itself")
|
||||
if (lowercaseDependencies.size != dependencies.size)
|
||||
throw IllegalPluginDescriptionException("Duplicated dependency detected: A plugin cannot depend on different versions of dependencies of the same id")
|
||||
|
||||
if (lowercaseDependencies.any { it == lowercaseId })
|
||||
throw IllegalPluginDescriptionException("Recursive dependency detected: A plugin cannot depend on itself")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.extension.Extension
|
||||
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority.*
|
||||
|
||||
/**
|
||||
* 插件类型.
|
||||
*
|
||||
* 插件类型将影响加载顺序: [BEFORE_EXTENSIONS] -> [ON_EXTENSIONS] -> [AFTER_EXTENSIONS].
|
||||
*
|
||||
* 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [BEFORE_EXTENSIONS] 不允许依赖一个 [AFTER_EXTENSIONS] 类型的插件.
|
||||
*/
|
||||
public enum class PluginLoadPriority {
|
||||
/**
|
||||
* 表示此插件最早被加载. 在 Console 启动时的第一初始化阶段就会加载这些插件.
|
||||
*
|
||||
* 一般只有提供 [PluginLoaderProvider] 或 [SingletonExtensionSelector] 的插件才需要在此阶段加载.
|
||||
*/
|
||||
BEFORE_EXTENSIONS,
|
||||
|
||||
/**
|
||||
* 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [AFTER_EXTENSIONS] 类型插件前加载
|
||||
*
|
||||
* 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. 如 [PermissionServiceProvider].
|
||||
*
|
||||
* 一些普通的 [Extension], 如 [BotConfigurationAlterer], 也可以使用 [AFTER_EXTENSIONS] 类型插件注册.
|
||||
*/
|
||||
ON_EXTENSIONS,
|
||||
|
||||
/**
|
||||
* 表示此插件为一个通常的插件, 在扩展处理完毕后加载.
|
||||
*/
|
||||
AFTER_EXTENSIONS;
|
||||
|
||||
public object AsStringSerializer : KSerializer<PluginLoadPriority> by String.serializer().map(
|
||||
serializer = { it.name },
|
||||
deserializer = { str ->
|
||||
values().firstOrNull {
|
||||
it.name.equals(str, ignoreCase = true)
|
||||
} ?: AFTER_EXTENSIONS
|
||||
}
|
||||
)
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Requirement
|
||||
import com.vdurmont.semver4j.Semver
|
||||
|
||||
public sealed class VersionRequirement {
|
||||
public abstract operator fun contains(version: Semver): Boolean
|
||||
public fun contains(version: String): Boolean = contains(Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
public class Exact
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
constructor(
|
||||
version: Semver,
|
||||
) : VersionRequirement() {
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
public val version: Semver = version.toStrict()
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor(version: String) : this(Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
override fun contains(version: Semver): Boolean = this.version.isEquivalentTo(version.toStrict())
|
||||
}
|
||||
|
||||
public data class MatchesNpmPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildNPM(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict())
|
||||
}
|
||||
|
||||
public data class MatchesIvyPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildIvy(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict())
|
||||
}
|
||||
|
||||
|
||||
public data class MatchesCocoapodsPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildCocoapods(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict())
|
||||
}
|
||||
|
||||
public abstract class Custom : VersionRequirement()
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
public class InRange(
|
||||
begin: Semver,
|
||||
public val beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
public val endInclusive: Boolean,
|
||||
) : VersionRequirement() {
|
||||
public val end: Semver = end.toStrict()
|
||||
public val begin: Semver = begin.toStrict()
|
||||
|
||||
public constructor(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
) : this(Semver(begin, Semver.SemverType.LOOSE), beginInclusive, end, endInclusive)
|
||||
|
||||
public constructor(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
) : this(Semver(begin, Semver.SemverType.LOOSE),
|
||||
beginInclusive,
|
||||
Semver(end, Semver.SemverType.LOOSE),
|
||||
endInclusive)
|
||||
|
||||
public constructor(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
) : this(begin, beginInclusive, Semver(end, Semver.SemverType.LOOSE), endInclusive)
|
||||
|
||||
override fun contains(version: Semver): Boolean {
|
||||
val strict = version.toStrict()
|
||||
return if (beginInclusive) {
|
||||
strict.isGreaterThanOrEqualTo(begin)
|
||||
} else {
|
||||
strict.isGreaterThan(begin)
|
||||
} && if (endInclusive) {
|
||||
strict.isLowerThanOrEqualTo(end)
|
||||
} else {
|
||||
strict.isLowerThan(end)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return buildString {
|
||||
append(if (beginInclusive) "[" else "(")
|
||||
append(begin)
|
||||
append(",")
|
||||
append(end)
|
||||
append(if (endInclusive) "]" else ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused", "DeprecatedCallableAddReplaceWith")
|
||||
public class Builder {
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun exact(version: Semver): VersionRequirement = Exact(version)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun exact(version: String): VersionRequirement = Exact(version)
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun custom(checker: (version: Semver) -> Boolean): VersionRequirement {
|
||||
return object : Custom() {
|
||||
override fun contains(version: Semver): Boolean = checker(version)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.NPM
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun npmPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesNpmPattern(versionPattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.IVY
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun ivyPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesIvyPattern(versionPattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.COCOAPODS
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun cocoapodsPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesCocoapodsPattern(versionPattern)
|
||||
}
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun Semver.rangeTo(endInclusive: Semver): VersionRequirement {
|
||||
return InRange(this, true, endInclusive, true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun Semver.rangeTo(endInclusive: String): VersionRequirement {
|
||||
return InRange(this, true, Semver(endInclusive, Semver.SemverType.LOOSE), true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun String.rangeTo(endInclusive: String): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE),
|
||||
true,
|
||||
Semver(endInclusive, Semver.SemverType.LOOSE),
|
||||
true)
|
||||
}
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun String.rangeTo(endInclusive: Semver): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE), true, endInclusive, true)
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun Semver.until(endExclusive: Semver): VersionRequirement {
|
||||
return InRange(this, true, endExclusive, false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun Semver.until(endExclusive: String): VersionRequirement {
|
||||
return InRange(this, true, Semver(endExclusive, Semver.SemverType.LOOSE), false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun String.until(endExclusive: String): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE),
|
||||
true,
|
||||
Semver(endExclusive, Semver.SemverType.LOOSE),
|
||||
false)
|
||||
}
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun String.until(endExclusive: Semver): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE), true, endExclusive, false)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class ILoveKafuuChinoForever
|
||||
}
|
||||
}
|
@ -7,30 +7,70 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS")
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
||||
import net.mamoe.mirai.console.data.PluginConfig
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.minutesToMillis
|
||||
import net.mamoe.mirai.utils.secondsToMillis
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* [JavaPlugin] 和 [KotlinPlugin] 的父类
|
||||
* [JavaPlugin] 和 [KotlinPlugin] 的父类. 所有 [JvmPlugin] 都应该拥有此类作为直接或间接父类.
|
||||
*
|
||||
* @see JavaPlugin
|
||||
* @see KotlinPlugin
|
||||
*/
|
||||
public abstract class AbstractJvmPlugin @JvmOverloads constructor(
|
||||
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) {
|
||||
@ConsoleExperimentalAPI
|
||||
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
) : JvmPlugin, JvmPluginInternal(parentCoroutineContext), AutoSavePluginDataHolder {
|
||||
@ConsoleExperimentalApi
|
||||
public final override val dataHolderName: String
|
||||
get() = this.description.name
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
public final override val loader: JvmPluginLoader get() = super<JvmPluginInternal>.loader
|
||||
|
||||
public final override fun permissionId(name: String): PermissionId = PermissionId(description.id, "command.$name")
|
||||
|
||||
/**
|
||||
* 重载 [PluginData]
|
||||
*
|
||||
* @see reloadPluginData
|
||||
*/
|
||||
@JvmName("reloadPluginData")
|
||||
public fun <T : PluginData> T.reload(): Unit = loader.dataStorage.load(this@AbstractJvmPlugin, this)
|
||||
|
||||
/**
|
||||
* 重载 [PluginConfig]
|
||||
*
|
||||
* @see reloadPluginConfig
|
||||
*/
|
||||
@JvmName("reloadPluginConfig")
|
||||
public fun <T : PluginConfig> T.reload(): Unit = loader.configStorage.load(this@AbstractJvmPlugin, this)
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToMillis
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载一个 [PluginData]
|
||||
*
|
||||
* @see AbstractJvmPlugin.reload
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun AbstractJvmPlugin.reloadPluginData(instance: PluginData): Unit = this.run { instance.reload() }
|
||||
|
||||
/**
|
||||
* 重载一个 [PluginConfig]
|
||||
*
|
||||
* @see AbstractJvmPlugin.reload
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun AbstractJvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() }
|
@ -18,23 +18,17 @@
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
||||
import net.mamoe.mirai.console.data.PluginConfig
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.permission.PermissionIdNamespace
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginFileExtensions
|
||||
import net.mamoe.mirai.console.plugin.ResourceContainer
|
||||
import net.mamoe.mirai.console.plugin.getDescription
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
|
||||
|
||||
/**
|
||||
* Java, Kotlin 或其他 JVM 平台插件
|
||||
*
|
||||
* 有关 [JvmPlugin] 相关实现方法,请参考
|
||||
*
|
||||
* @see AbstractJvmPlugin 默认实现
|
||||
*
|
||||
* @see JavaPlugin Java 插件
|
||||
@ -43,73 +37,42 @@ import net.mamoe.mirai.utils.MiraiLogger
|
||||
* @see JvmPlugin 支持文件系统扩展
|
||||
* @see ResourceContainer 支持资源获取 (如 Jar 中的资源文件)
|
||||
*/
|
||||
@OptIn(ExperimentalPermission::class)
|
||||
public interface JvmPlugin : Plugin, CoroutineScope,
|
||||
PluginFileExtensions, ResourceContainer, AutoSavePluginDataHolder, PermissionIdNamespace {
|
||||
PluginFileExtensions, ResourceContainer, PermissionIdNamespace {
|
||||
|
||||
/** 日志 */
|
||||
public val logger: MiraiLogger
|
||||
|
||||
/** 插件描述 */
|
||||
public val description: JvmPluginDescription get() = loader.getDescription(this)
|
||||
public val description: JvmPluginDescription
|
||||
|
||||
/** 所属插件加载器实例 */
|
||||
@JvmDefault
|
||||
public override val loader: JarPluginLoader
|
||||
get() = JarPluginLoader
|
||||
|
||||
/**
|
||||
* 重载 [PluginData]
|
||||
*
|
||||
* @see reloadPluginData
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmName("reloadPluginData")
|
||||
public fun <T : PluginData> T.reload(): Unit = loader.dataStorage.load(this@JvmPlugin, this)
|
||||
|
||||
/**
|
||||
* 重载 [PluginConfig]
|
||||
*
|
||||
* @see reloadPluginConfig
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmName("reloadPluginConfig")
|
||||
public fun <T : PluginConfig> T.reload(): Unit = loader.configStorage.load(this@JvmPlugin, this)
|
||||
// `final` in AbstractJvmPlugin
|
||||
public override val loader: JvmPluginLoader get() = JvmPluginLoader
|
||||
|
||||
/**
|
||||
* 在插件被加载时调用. 只会被调用一次.
|
||||
*
|
||||
* 在 [onLoad] 时可注册扩展 [PluginComponentStorage.contribute]
|
||||
*
|
||||
* @see PluginComponentStorage 查看更多信息
|
||||
*
|
||||
* @receiver 组件容器
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun onLoad() {
|
||||
}
|
||||
public fun PluginComponentStorage.onLoad() {}
|
||||
|
||||
/**
|
||||
* 在插件被启用时调用, 可能会被调用多次
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun onEnable() {
|
||||
}
|
||||
public fun onEnable() {}
|
||||
|
||||
/**
|
||||
* 在插件被关闭时调用, 可能会被调用多次
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun onDisable() {
|
||||
public fun onDisable() {}
|
||||
|
||||
public companion object {
|
||||
@JvmSynthetic
|
||||
public inline fun JvmPlugin.onLoad(storage: PluginComponentStorage): Unit = storage.onLoad()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载一个 [PluginData]
|
||||
*
|
||||
* @see JvmPlugin.reload
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun JvmPlugin.reloadPluginData(instance: PluginData): Unit = this.run { instance.reload() }
|
||||
|
||||
/**
|
||||
* 重载一个 [PluginConfig]
|
||||
*
|
||||
* @see JvmPlugin.reload
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun JvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() }
|
||||
}
|
@ -7,21 +7,22 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_member")
|
||||
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_member", "DeprecatedCallableAddReplaceWith")
|
||||
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import net.mamoe.mirai.console.plugin.description.VersionRequirement
|
||||
|
||||
/**
|
||||
* JVM 插件的描述. 通常作为 `plugin.yml`
|
||||
*
|
||||
* 请不要自行实现 [JvmPluginDescription] 接口. 它不具有继承稳定性.
|
||||
*
|
||||
* 要查看相关约束, 参考 [PluginDescription]
|
||||
*
|
||||
* @see SimpleJvmPluginDescription
|
||||
* @see JvmPluginDescriptionBuilder
|
||||
*/
|
||||
@ -33,21 +34,43 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public operator fun invoke(
|
||||
name: String,
|
||||
/**
|
||||
* @see [PluginDescription.id]
|
||||
*/
|
||||
id: String,
|
||||
/**
|
||||
* @see [PluginDescription.version]
|
||||
*/
|
||||
version: String,
|
||||
block: JvmPluginDescriptionBuilder.() -> Unit = {}
|
||||
): JvmPluginDescription = JvmPluginDescriptionBuilder(name, version).apply(block).build()
|
||||
/**
|
||||
* @see [PluginDescription.name]
|
||||
*/
|
||||
name: String = id,
|
||||
block: JvmPluginDescriptionBuilder.() -> Unit = {},
|
||||
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
|
||||
|
||||
/**
|
||||
* 构建 [JvmPluginDescription]
|
||||
* @see JvmPluginDescriptionBuilder
|
||||
*/
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@JvmSynthetic
|
||||
public operator fun invoke(
|
||||
name: String,
|
||||
/**
|
||||
* @see [PluginDescription.id]
|
||||
*/
|
||||
id: String,
|
||||
/**
|
||||
* @see [PluginDescription.version]
|
||||
*/
|
||||
version: Semver,
|
||||
block: JvmPluginDescriptionBuilder.() -> Unit = {}
|
||||
): JvmPluginDescription = JvmPluginDescriptionBuilder(name, version).apply(block).build()
|
||||
/**
|
||||
* @see [PluginDescription.name]
|
||||
*/
|
||||
name: String = id,
|
||||
block: JvmPluginDescriptionBuilder.() -> Unit = {},
|
||||
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,25 +95,29 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
*
|
||||
* @see [JvmPluginDescription.invoke]
|
||||
*/
|
||||
public class JvmPluginDescriptionBuilder(
|
||||
public class JvmPluginDescriptionBuilder
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
constructor(
|
||||
private var id: String,
|
||||
private var version: Semver,
|
||||
) {
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor(name: String, version: String) : this(name, Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
private var name: String = id
|
||||
private var author: String = ""
|
||||
private var info: String = ""
|
||||
private var dependencies: MutableSet<PluginDependency> = mutableSetOf()
|
||||
private var loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun name(value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() }
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun version(value: String): JvmPluginDescriptionBuilder =
|
||||
apply { this.version = Semver(value, Semver.SemverType.LOOSE) }
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun version(value: Semver): JvmPluginDescriptionBuilder = apply { this.version = value }
|
||||
|
||||
@ -103,59 +130,91 @@ public class JvmPluginDescriptionBuilder(
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun kind(value: PluginLoadPriority): JvmPluginDescriptionBuilder = apply { this.loadPriority = value }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun normalPlugin(): JvmPluginDescriptionBuilder =
|
||||
apply { this.loadPriority = PluginLoadPriority.AFTER_EXTENSIONS }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun loaderProviderPlugin(): JvmPluginDescriptionBuilder =
|
||||
apply { this.loadPriority = PluginLoadPriority.BEFORE_EXTENSIONS }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun highPriorityExtensionsPlugin(): JvmPluginDescriptionBuilder =
|
||||
apply { this.loadPriority = PluginLoadPriority.ON_EXTENSIONS }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
version: String? = null,
|
||||
isOptional: Boolean = false
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
if (version == null) this.dependencies.add(PluginDependency(pluginId, version, isOptional))
|
||||
else this.dependencies.add(PluginDependency(pluginId, version, isOptional))
|
||||
}
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun setDependencies(
|
||||
value: Set<PluginDependency>
|
||||
value: Set<PluginDependency>,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies = value.toMutableSet()
|
||||
}
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
vararg dependencies: PluginDependency
|
||||
vararg dependencies: PluginDependency,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
for (dependency in dependencies) {
|
||||
this.dependencies.add(dependency)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
version: Semver? = null,
|
||||
isOptional: Boolean = false
|
||||
): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, version, isOptional)) }
|
||||
isOptional: Boolean = false,
|
||||
versionRequirement: VersionRequirement,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
|
||||
}
|
||||
|
||||
/**
|
||||
* isOptional = false
|
||||
*
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
versionRequirement: VersionRequirement,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, false))
|
||||
}
|
||||
|
||||
/**
|
||||
* 无版本要求
|
||||
*
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, null, isOptional))
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:
|
||||
*
|
||||
* ```
|
||||
* dependsOn("org.example.test-plugin") { "1.0.0".."1.2.0" }
|
||||
* dependsOn("org.example.test-plugin") { npmPattern("1.x || >=2.5.0 || 5.0.0 - 7.2.3") }
|
||||
* dependsOn("org.example.test-plugin") { ivyPattern("[1.0,2.0[") }
|
||||
* dependsOn("org.example.test-plugin") { custom { it.toString() == "1.0.0" } }
|
||||
* ```
|
||||
*
|
||||
* @see PluginDependency
|
||||
* @see VersionRequirement.Builder
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
versionRequirement: VersionRequirement.Builder.() -> VersionRequirement,
|
||||
): JvmPluginDescriptionBuilder =
|
||||
apply {
|
||||
this.dependencies.add(PluginDependency(pluginId,
|
||||
VersionRequirement.Builder().run(versionRequirement),
|
||||
isOptional))
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public fun build(): JvmPluginDescription =
|
||||
SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
|
||||
SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5
|
||||
@ -194,7 +253,6 @@ public data class SimpleJvmPluginDescription
|
||||
public override val author: String = "",
|
||||
public override val info: String = "",
|
||||
public override val dependencies: Set<PluginDependency> = setOf(),
|
||||
public override val loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS,
|
||||
) : JvmPluginDescription {
|
||||
|
||||
@Deprecated(
|
||||
@ -216,51 +274,9 @@ public data class SimpleJvmPluginDescription
|
||||
author: String = "",
|
||||
info: String = "",
|
||||
dependencies: Set<PluginDependency> = setOf(),
|
||||
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS,
|
||||
) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies, loadPriority)
|
||||
) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies)
|
||||
|
||||
init {
|
||||
require(!name.contains(':')) { "':' is forbidden in plugin name" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Deprecated(
|
||||
"JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.",
|
||||
replaceWith = ReplaceWith(
|
||||
"SimpleJvmPluginDescription(name, version, author, info, dependencies, kind)",
|
||||
"net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription"
|
||||
),
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
@LowPriorityInOverloadResolution
|
||||
@Suppress("DEPRECATION_ERROR", "FunctionName")
|
||||
public fun JvmPluginDescription(
|
||||
name: String,
|
||||
version: Semver,
|
||||
id: String = name,
|
||||
author: String = "",
|
||||
info: String = "",
|
||||
dependencies: Set<PluginDependency> = setOf(),
|
||||
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
|
||||
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
|
||||
|
||||
@Deprecated(
|
||||
"JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.",
|
||||
replaceWith = ReplaceWith(
|
||||
"SimpleJvmPluginDescription(name, version, author, info, dependencies, kind)",
|
||||
"net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription"
|
||||
),
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
@LowPriorityInOverloadResolution
|
||||
@Suppress("DEPRECATION_ERROR", "FunctionName")
|
||||
public fun JvmPluginDescription(
|
||||
name: String,
|
||||
version: String,
|
||||
id: String = name,
|
||||
author: String = "",
|
||||
info: String = "",
|
||||
dependencies: Set<PluginDependency> = setOf(),
|
||||
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
|
||||
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
|
||||
}
|
@ -11,30 +11,31 @@ package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.internal.plugin.JarPluginLoaderImpl
|
||||
import net.mamoe.mirai.console.plugin.FilePluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl
|
||||
import net.mamoe.mirai.console.plugin.loader.FilePluginLoader
|
||||
|
||||
/**
|
||||
* 内建的 Jar (JVM) 插件加载器
|
||||
*/
|
||||
@ConsoleExperimentalAPI("classname might change")
|
||||
public interface JarPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
|
||||
public interface JvmPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
|
||||
/**
|
||||
* [JvmPlugin.reloadPluginData] 默认使用的实例
|
||||
* ".jar"
|
||||
*/
|
||||
public override val fileSuffix: String
|
||||
|
||||
/**
|
||||
* [AbstractJvmPlugin.reloadPluginData] 默认使用的实例
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public val dataStorage: PluginDataStorage
|
||||
|
||||
/**
|
||||
* [JvmPlugin.reloadPluginData] 默认使用的实例
|
||||
* [AbstractJvmPlugin.reloadPluginData] 默认使用的实例
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
public val configStorage: PluginDataStorage
|
||||
|
||||
public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl {
|
||||
public companion object BuiltIn : JvmPluginLoader by BuiltInJvmPluginLoaderImpl {
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
override val JvmPlugin.description: JvmPluginDescription
|
||||
get() = JarPluginLoaderImpl.run { description }
|
||||
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription =
|
||||
BuiltInJvmPluginLoaderImpl.run { plugin.description }
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ import kotlin.coroutines.EmptyCoroutineContext
|
||||
/**
|
||||
* Kotlin 插件的父类.
|
||||
*
|
||||
* 必须通过 "plugin.yml" 指定主类并由 [JarPluginLoader] 加载.
|
||||
* 必须通过 "plugin.yml" 指定主类并由 [JvmPluginLoader] 加载.
|
||||
*/
|
||||
public abstract class KotlinPlugin @JvmOverloads constructor(
|
||||
public final override val description: JvmPluginDescription,
|
||||
|
@ -0,0 +1,43 @@
|
||||
package net.mamoe.mirai.console.plugin.loader
|
||||
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* ['/plugins'][PluginManager.pluginsPath] 目录中的插件的加载器. 每个加载器需绑定一个后缀.
|
||||
*
|
||||
* @see AbstractFilePluginLoader 默认基础实现
|
||||
* @see JvmPluginLoader 内建的 Jar (JVM) 插件加载器.
|
||||
*/
|
||||
public interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> {
|
||||
/**
|
||||
* 所支持的插件文件后缀, 含 '.', 不区分大小写. 如 [JvmPluginLoader] 为 ".jar"
|
||||
*/
|
||||
public val fileSuffix: String
|
||||
}
|
||||
|
||||
/**
|
||||
* [FilePluginLoader] 的默认基础实现.
|
||||
*
|
||||
* @see FilePluginLoader
|
||||
*/
|
||||
public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
|
||||
/**
|
||||
* 所支持的插件文件后缀, 含 '.', 不区分大小写. 如 [JvmPluginLoader] 为 ".jar"
|
||||
*/
|
||||
public override val fileSuffix: String,
|
||||
) : FilePluginLoader<P, D> {
|
||||
private fun pluginsFilesSequence(): Sequence<File> =
|
||||
PluginManager.pluginsFolder.listFiles().orEmpty().asSequence()
|
||||
.filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
|
||||
|
||||
/**
|
||||
* 读取扫描到的后缀与 [fileSuffix] 相同的文件中的插件实例, 但不 [加载][PluginLoader.load]
|
||||
*/
|
||||
protected abstract fun Sequence<File>.extractPlugins(): List<P>
|
||||
|
||||
public final override fun listPlugins(): List<P> = pluginsFilesSequence().extractPlugins()
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.plugin.loader
|
||||
|
||||
/**
|
||||
* 在加载插件过程中遇到的意料之中的问题.
|
||||
*
|
||||
* @see PluginLoader.load
|
||||
* @see PluginLoader.enable
|
||||
* @see PluginLoader.disable
|
||||
* @see PluginLoader.getPluginDescription
|
||||
*/
|
||||
public open class PluginLoadException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.console.plugin.loader
|
||||
|
||||
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
|
||||
/**
|
||||
* 插件加载器.
|
||||
*
|
||||
* 插件加载器只实现寻找插件列表, 加载插件, 启用插件, 关闭插件这四个功能.
|
||||
*
|
||||
* 一个插件要在何时被加载,依赖如何处理,[PluginLoader] 都无需关心.
|
||||
*
|
||||
* 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护.
|
||||
*
|
||||
* ## 内建加载器
|
||||
* - [JvmPluginLoader] Jar 插件加载器
|
||||
*
|
||||
* ## 扩展加载器
|
||||
* 插件被允许扩展一个加载器.
|
||||
*
|
||||
* ### 实现扩展加载器
|
||||
* 直接实现接口 [PluginLoader] 或 [FilePluginLoader], 并注册 [PluginLoaderProvider]
|
||||
*
|
||||
* @see JvmPluginLoader Jar 插件加载器
|
||||
*/
|
||||
public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
/**
|
||||
* 扫描并返回可以被加载的插件的列表.
|
||||
*
|
||||
* 这些插件都应处于还未被加载的状态.
|
||||
*
|
||||
* 在 Console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
|
||||
*
|
||||
* **实现细节:** 此函数*只应该*在 Console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用.
|
||||
*/
|
||||
public fun listPlugins(): List<P>
|
||||
|
||||
/**
|
||||
* 获取此插件的描述.
|
||||
*
|
||||
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
*
|
||||
* 若在 Console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
|
||||
*
|
||||
* @see PluginDescription 插件描述
|
||||
* @see getPluginDescription 无 receiver, 接受参数的版本.
|
||||
*/
|
||||
@Throws(PluginLoadException::class)
|
||||
public fun getPluginDescription(plugin: P): D // Java signature: `public D getDescription(P)`
|
||||
|
||||
/**
|
||||
* 主动加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例
|
||||
*
|
||||
* **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enable], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException].
|
||||
*
|
||||
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
|
||||
*/
|
||||
@Throws(PluginLoadException::class)
|
||||
public fun load(plugin: P)
|
||||
|
||||
/**
|
||||
* 主动启用这个插件.
|
||||
*
|
||||
* **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enable], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException].
|
||||
*
|
||||
* **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
|
||||
*
|
||||
* @see PluginManager.enable
|
||||
*/
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun enable(plugin: P)
|
||||
|
||||
/**
|
||||
* 主动禁用这个插件.
|
||||
*
|
||||
* **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
*
|
||||
* @see PluginManager.disable
|
||||
*/
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun disable(plugin: P)
|
||||
}
|
@ -20,7 +20,7 @@ import kotlin.annotation.AnnotationTarget.*
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||
@Target(PROPERTY, FUNCTION, TYPE, CLASS)
|
||||
internal annotation class JavaFriendlyAPI
|
||||
internal annotation class JavaFriendlyApi
|
||||
|
||||
/**
|
||||
* 标记为一个仅供 mirai-console 内部使用的 API.
|
||||
@ -32,8 +32,8 @@ internal annotation class JavaFriendlyAPI
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY)
|
||||
@MustBeDocumented
|
||||
public annotation class ConsoleInternalAPI(
|
||||
val message: String = ""
|
||||
public annotation class ConsoleInternalApi(
|
||||
val message: String = "",
|
||||
)
|
||||
|
||||
/**
|
||||
@ -46,6 +46,6 @@ public annotation class ConsoleInternalAPI(
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
@MustBeDocumented
|
||||
public annotation class ConsoleExperimentalAPI(
|
||||
val message: String = ""
|
||||
public annotation class ConsoleExperimentalApi(
|
||||
val message: String = "",
|
||||
)
|
@ -12,13 +12,13 @@
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.contact.*
|
||||
|
||||
/**
|
||||
* 为简化操作提供的一些工具
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public object ContactUtils {
|
||||
/**
|
||||
* 获取一个 [Bot] 的好友, 群, 或群员.
|
||||
@ -31,8 +31,8 @@ public object ContactUtils {
|
||||
*/
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
public fun Bot.getContact(id: Long, includeMembers: Boolean = false): Contact {
|
||||
@ConsoleExperimentalApi
|
||||
public fun Bot.getContact(id: Long, includeMembers: Boolean = true): Contact {
|
||||
return getContactOrNull(id, includeMembers)
|
||||
?: throw NoSuchElementException("Contact $id not found for bot ${this.id}")
|
||||
}
|
||||
@ -44,8 +44,8 @@ public object ContactUtils {
|
||||
*/
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = false): Contact? {
|
||||
@ConsoleExperimentalApi
|
||||
public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = true): Contact? {
|
||||
return getFriendOrGroupOrNull(id) ?: kotlin.run {
|
||||
if (includeMembers) {
|
||||
groups.asSequence().flatMap { it.members.asSequence() }.firstOrNull { it.id == id }
|
||||
@ -60,7 +60,7 @@ public object ContactUtils {
|
||||
* 访问顺序为 [Bot.friends] -> [Bot.groups]
|
||||
*/
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun Bot.getFriendOrGroup(id: Long): Contact {
|
||||
return getFriendOrGroupOrNull(id)
|
||||
?: throw NoSuchElementException("Friend or Group $id not found for bot ${this.id}")
|
||||
@ -72,9 +72,23 @@ public object ContactUtils {
|
||||
* 访问顺序为 [Bot.friends] -> [Bot.groups]
|
||||
*/
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun Bot.getFriendOrGroupOrNull(id: Long): Contact? {
|
||||
return this.friends.getOrNull(id) ?: this.groups.getOrNull(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 [ContactOrBot] 输出为字符串表示.
|
||||
*/
|
||||
@JvmName("renderContactOrName")
|
||||
@JvmStatic
|
||||
public fun ContactOrBot.render(): String {
|
||||
return when (this) {
|
||||
is Bot -> "Bot $nick($id)"
|
||||
is Group -> "Group $name($id)"
|
||||
is Friend -> "Friend $nick($id)"
|
||||
is Member -> "Member $nameCardOrNick(${group.id}.$id)"
|
||||
else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}")
|
||||
}
|
||||
}
|
||||
}
|
@ -15,15 +15,15 @@ import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public object CoroutineScopeUtils {
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun CoroutineContext.overrideWithSupervisorJob(name: String? = null): CoroutineContext =
|
||||
this + NamedSupervisorJob(name ?: "<unnamed>", this[Job])
|
||||
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun CoroutineScope.childScope(
|
||||
name: String? = null,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
@ -31,7 +31,7 @@ public object CoroutineScopeUtils {
|
||||
CoroutineScope(this.childScopeContext(name, context))
|
||||
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public fun CoroutineScope.childScopeContext(
|
||||
name: String? = null,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
@ -42,7 +42,7 @@ public object CoroutineScopeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public class NamedSupervisorJob @JvmOverloads constructor(
|
||||
private val name: String,
|
||||
parent: Job? = null
|
||||
|
@ -104,7 +104,7 @@ public interface MessageScope {
|
||||
*
|
||||
* @suppress 此 API 不稳定, 可能在任何时间被修改
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
public val realTarget: Any?
|
||||
|
||||
/**
|
||||
@ -526,8 +526,8 @@ internal fun MessageScope.asSequence(): Sequence<MessageScope> {
|
||||
}
|
||||
|
||||
private class CombinedScope(
|
||||
private val first: MessageScope,
|
||||
private val second: MessageScope
|
||||
val first: MessageScope,
|
||||
val second: MessageScope,
|
||||
) : MessageScope {
|
||||
override val realTarget: Any? get() = null
|
||||
|
||||
@ -549,7 +549,7 @@ private class CombinedScope(
|
||||
}
|
||||
|
||||
private class CommandSenderAsMessageScope(
|
||||
private val sender: CommandSender
|
||||
private val sender: CommandSender,
|
||||
) : MessageScope {
|
||||
override val realTarget: Any?
|
||||
get() = sender.user ?: sender // ConsoleCommandSender
|
||||
@ -564,7 +564,7 @@ private class CommandSenderAsMessageScope(
|
||||
}
|
||||
|
||||
private class ContactAsMessageScope(
|
||||
private val sender: Contact
|
||||
private val sender: Contact,
|
||||
) : MessageScope {
|
||||
override val realTarget: Any?
|
||||
get() = sender
|
||||
|
@ -15,12 +15,11 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.data.MemoryPluginDataStorage
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
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.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.LoginSolver
|
||||
@ -32,12 +31,12 @@ import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(ConsoleInternalAPI::class)
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
fun initTestEnvironment() {
|
||||
object : MiraiConsoleImplementation {
|
||||
override val rootPath: Path = createTempDir().toPath()
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
override val frontEndDescription: MiraiConsoleFrontEndDescription
|
||||
get() = object : MiraiConsoleFrontEndDescription {
|
||||
override val name: String
|
||||
@ -48,7 +47,7 @@ fun initTestEnvironment() {
|
||||
get() = Semver("1.0.0")
|
||||
|
||||
}
|
||||
override val builtInPluginLoaders: List<PluginLoader<*, *>> = listOf(DeferredPluginLoader { JarPluginLoader })
|
||||
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader })
|
||||
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl =
|
||||
object : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
|
||||
override suspend fun sendMessage(message: Message) {
|
||||
@ -59,8 +58,8 @@ fun initTestEnvironment() {
|
||||
println(message)
|
||||
}
|
||||
}
|
||||
override val dataStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
|
||||
override val configStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
|
||||
override val dataStorageForJvmPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
|
||||
override val configStorageForJvmPluginLoader: PluginDataStorage = MemoryPluginDataStorage()
|
||||
override val dataStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage()
|
||||
override val configStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage()
|
||||
|
||||
|
@ -10,22 +10,13 @@
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertSame
|
||||
|
||||
@OptIn(ConsoleInternalAPI::class)
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
internal class PluginDataTest {
|
||||
|
||||
object MyPlugin : KotlinPlugin(
|
||||
JvmPluginDescription(
|
||||
"1", "2"
|
||||
)
|
||||
)
|
||||
|
||||
class MyPluginData : AutoSavePluginData() {
|
||||
var int by value(1)
|
||||
val map: MutableMap<String, String> by value()
|
||||
|
@ -7,18 +7,9 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
object Versions {
|
||||
const val core = "1.2.2"
|
||||
const val console = "1.0-M4-dev-5"
|
||||
const val core = "1.2.3"
|
||||
const val console = "1.0-M4"
|
||||
const val consoleGraphical = "0.0.7"
|
||||
const val consoleTerminal = "0.1.0"
|
||||
const val consolePure = console
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
|
||||
[`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
|
||||
[`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
|
||||
[`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
|
||||
[`JvmPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt
|
||||
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
||||
[`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
|
||||
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
|
||||
|
@ -1,2 +1,37 @@
|
||||
# Mirai Console Backend - Extensions
|
||||
|
||||
Mirai Console 拥有灵活的 Extensions API,支持扩展 Console 的一些服务。
|
||||
|
||||
Extensions 属于插件开发的进阶内容。
|
||||
|
||||
[`Extension`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/Extension.kt
|
||||
[`ExtensionPoint`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt
|
||||
[`PluginComponentStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/PluginComponentStorage.kt
|
||||
[`ComponentStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt
|
||||
|
||||
## [扩展][`Extension`]
|
||||
|
||||
### [组件容器][`ComponentStorage`]
|
||||
|
||||
容纳插件注册的 [扩展][`Extension`]。
|
||||
|
||||
### 注册扩展
|
||||
|
||||
插件仅能在 [`onLoad`](Plugins.md#加载) 阶段注册扩展。
|
||||
|
||||
示例:
|
||||
|
||||
```kotlin
|
||||
object MyPlugin : KotlinPlugin( /* ... */ ) {
|
||||
fun PluginComponentStorage.onLoad() {
|
||||
contributePermissionService { /* ... */ }
|
||||
contributePluginLoader { /* ... */ }
|
||||
contribute(ExtensionPoint) { /* ... */ }
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 可用扩展
|
||||
|
||||
查看 [extensions](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/)。每个文件对应一个扩展。
|
@ -1 +1,35 @@
|
||||
#
|
||||
# Mirai Console Frontend
|
||||
|
||||
Mirai Console 前端开发文档。
|
||||
|
||||
[`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
|
||||
|
||||
## 实现前端
|
||||
|
||||
### 添加编译器设置
|
||||
|
||||
在 `build.gradle` 或 `build.gradle.kts` 添加:
|
||||
```kotlin
|
||||
kotlin {
|
||||
sourceSets.all {
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
此后就可以使用 `net.mamoe.mirai.console.ConsoleFrontEndImplementation` 标记的所有 API。
|
||||
|
||||
|
||||
### 实现 Mirai Console
|
||||
|
||||
[`MiraiConsole`] 是后端的公开对象,由 [MiraiConsoleImplementationBridge](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt) 代理,与前端链接。
|
||||
|
||||
前端需要实现 [MiraiConsoleImplementation.kt](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt)。
|
||||
|
||||
由于实现前端需要一定的技术能力,相信实现者都能理解源码内注释。
|
||||
|
||||
### 启动 Mirai Console
|
||||
|
||||
通过 `public fun MiraiConsoleImplementation.start()`。
|
||||
|
||||
[MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt#L161)
|
@ -0,0 +1,141 @@
|
||||
# Mirai Console Backend - Permissions
|
||||
|
||||
权限系统。
|
||||
|
||||
[`PermissionService`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt
|
||||
[`Permission`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt
|
||||
[`RootPermission`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt#L82
|
||||
[`PermissionId`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt
|
||||
[`PermissionIdNamespace`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt
|
||||
[`Permittee`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permittee.kt
|
||||
[`PermitteeId`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt
|
||||
[`AbstractPermitteeId`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt#L77
|
||||
[`CommandSender`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt
|
||||
|
||||
## 权限
|
||||
|
||||
每个权限都由 [`Permission`] 对象表示。
|
||||
|
||||
一个 [`Permission`] 拥有这些信息:
|
||||
```kotlin
|
||||
interface Permission {
|
||||
val id: PermissionId // 唯一识别 ID
|
||||
val description: String // 描述信息
|
||||
val parent: Permission // 父权限
|
||||
}
|
||||
```
|
||||
|
||||
「权限」表示的意义是 “做一项工作的能力”。如 “执行指令 /stop”,“操作数据库” 都叫作权限。
|
||||
|
||||
[`Permission`] 对象由 Console 内置或者由特定权限插件实现。其他插件不能实现 [`Permission`] 接口。
|
||||
|
||||
### 权限 ID
|
||||
|
||||
```kotlin
|
||||
data class PermissionId(
|
||||
val namespace: String, // 命名空间
|
||||
val name: String, // 名称
|
||||
)
|
||||
```
|
||||
|
||||
[`PermissionId`] 是 [`Permission`] 的唯一标识符。知道 [`PermissionId`] 就可以获取到对应的 [`Permission`]。
|
||||
|
||||
字符串表示为 "$namespace:$name",如 "console:command.stop", "*:*"
|
||||
|
||||
#### 命名空间
|
||||
|
||||
命名空间(“namespace”)用于限定权限的创建者,避免冲突。
|
||||
|
||||
一些常见命名空间:
|
||||
|
||||
| 用途 | 命名空间 |
|
||||
|:-------------|:------------|
|
||||
| 根权限 | `"*"` |
|
||||
| Console 内置 | `"console"` |
|
||||
| ID 为 A 的插件 | `"A"` |
|
||||
|
||||
#### 名称
|
||||
|
||||
名称则表示特定的含义。如一个指令,某一项操作等。
|
||||
|
||||
一些常见名称:
|
||||
|
||||
| 用途 | 名称 |
|
||||
|:--------------------------|:--------------|
|
||||
| 根权限 | `"*"` |
|
||||
| Console 内置的名为 A 的指令 | `"command.A"` |
|
||||
| ID 为 A 的插件的名为 B 的指令 | `"command.B"` |
|
||||
|
||||
#### 根权限
|
||||
|
||||
[`RootPermission`] 是所有权限的父权限。其 ID 为 "*:*"
|
||||
|
||||
## 被许可人
|
||||
|
||||
```kotlin
|
||||
interface Permittee {
|
||||
val permitteeId: PermitteeId
|
||||
}
|
||||
```
|
||||
|
||||
[`Permittee`] 表示一个可被赋予权限的对象,即 '被许可人'。
|
||||
|
||||
[`CommandSender`] 实现 [`Permittee`]。
|
||||
|
||||
被许可人自身不持有拥有的权限列表,而是拥有 [`PermitteeId`],标识自己的身份,供 [权限服务][`PermissionService`] 处理。
|
||||
|
||||
**注意**:请不要自主实现 [`Permittee`]。
|
||||
|
||||
### 被许可人 ID
|
||||
|
||||
```kotlin
|
||||
interface PermitteeId {
|
||||
val directParents: Array<out PermitteeId> // 直接父对象
|
||||
fun asString(): String // 转换为字符串表示
|
||||
}
|
||||
````
|
||||
|
||||
[`PermitteeId`] 是被许可人的标识符。
|
||||
|
||||
一个这样的标识符即可代表特定的单个 [`Permittee`], 也可以表示多个同类 [`Permittee`].
|
||||
|
||||
#### `directParents`
|
||||
[`PermitteeId`] 允许拥有多个父对象。在检查权限时会首先检查自己, 再递归检查父类。
|
||||
|
||||
#### 衍生类型
|
||||
|
||||
[`PermitteeId`] 的实现通常是 [`AbstractPermitteeId`]
|
||||
|
||||
在 [`AbstractPermitteeId`] 查看其子类。
|
||||
|
||||
### 获取被许可人
|
||||
|
||||
通常使用 `CommandSender.permitteeId`。
|
||||
也可以直接构造 [`AbstractPermitteeId`] 的子类。或者在 Kotlin 使用扩展 `User.permitteeId`
|
||||
|
||||
## 权限服务
|
||||
|
||||
[`PermissionService`] 承载权限的授权和管理。Console 内置一个实现,而权限服务可以由插件提供(见 [扩展](Extensions.md))。
|
||||
|
||||
### 判断权限
|
||||
|
||||
在 Kotlin,在该有扩展的对象上 Console 都为它们实现了扩展。可以使用:
|
||||
```kotlin
|
||||
fun Permittee.hasPermission(Permission): Boolean
|
||||
fun Permission.testPermission(Permittee): Boolean
|
||||
fun PermitteeId.hasPermission(Permission): Boolean
|
||||
fun PermissionId.testPermission(Permittee): Boolean
|
||||
fun Permittee.hasPermission(PermissionId): Boolean
|
||||
fun Permission.testPermission(PermitteeId): Boolean
|
||||
// ...
|
||||
```
|
||||
|
||||
请查看 [`PermissionService`] 中的伴生对象。
|
||||
|
||||
### 注册权限
|
||||
|
||||
每一条指令都会默认自动创建一个权限。
|
||||
|
||||
如果希望手动注册一个其他用途的权限,使用 `PermissionService.register`。
|
||||
|
||||
**注意**:权限只能在插件 [启用](Plugins.md#启用) 之后才能注册。否则会得到一个异常。
|
@ -2,9 +2,9 @@
|
||||
|
||||
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
|
||||
[`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
|
||||
[`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
|
||||
[`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
|
||||
[`JvmPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt
|
||||
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
||||
[`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
|
||||
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
|
||||
@ -35,7 +35,6 @@
|
||||
[`RawCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt
|
||||
[`CommandManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
|
||||
|
||||
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
|
||||
[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
|
||||
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
|
||||
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
|
||||
[`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
|
||||
[`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
|
||||
[`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
|
||||
[`JvmPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader.kt
|
||||
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
||||
[`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
|
||||
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
|
||||
@ -59,11 +59,11 @@ interface Plugin : CommandOwner { // CommandOwner 是空的 interface
|
||||
}
|
||||
```
|
||||
|
||||
[`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [实现 PluginLoader](PluginLoader.md))。
|
||||
[`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [扩展 - PluginLoader](Extensions.md))。
|
||||
|
||||
## JVM 平台插件接口 - [`JvmPlugin`]
|
||||
|
||||
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JarPluginLoader`] 加载)。
|
||||
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
|
||||
Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。
|
||||
|
||||
### 主类和描述
|
||||
@ -83,8 +83,7 @@ Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。
|
||||
- 插件自身的版本要求遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 合格的版本例如: `1.0.0`, `1.0`, `1.0-M1`, `1.0-pre-1`
|
||||
- 插件依赖的版本遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 同时支持 [Apache Ivy 风格表示方法](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html).
|
||||
|
||||
##### 插件名
|
||||
插件名仅取决于 `PluginDescription` 提供的 `name`,与主类类名等其他信息无关。
|
||||
有关描述的详细信息可在开发时查看源码内文档。
|
||||
|
||||
#### 实现 Kotlin 插件主类
|
||||
|
||||
@ -120,7 +119,7 @@ public final class JExample extends JavaPlugin {
|
||||
public static final JExample INSTANCE = new JExample(); // 可以像 Kotlin 一样静态初始化单例
|
||||
private JExample() {
|
||||
super(new JvmPluginDescriptionBuilder(
|
||||
"JExample", // name
|
||||
"org.example.test-plugin", // name
|
||||
"1.0.0" // version
|
||||
)
|
||||
// .author("...")
|
||||
@ -140,7 +139,7 @@ public final class JExample extends JavaPlugin {
|
||||
}
|
||||
public JExample() { // 此时必须 public
|
||||
super(new JvmPluginDescriptionBuilder(
|
||||
"JExample", // name
|
||||
"org.example.test-plugin", // id
|
||||
"1.0.0" // version
|
||||
)
|
||||
// .author("...")
|
||||
@ -174,9 +173,9 @@ Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务
|
||||
|
||||
#### 加载
|
||||
|
||||
[`JarPluginLoader`] 调用插件的 `onLoad()`,在 `onLoad()` 正常返回后插件被认为成功加载。
|
||||
[`JvmPluginLoader`] 调用插件的 `onLoad()`,在 `onLoad()` 正常返回后插件被认为成功加载。
|
||||
|
||||
由于 `onLoad()` 只会被初始化一次,插件可以在该方法内进行一些*一次性*的*初始化*任务。
|
||||
由于 `onLoad()` 只会被初始化一次,插件可以在该方法内进行一些*一次性*的*初始化*任务,如 [注册扩展](Extensions.md#注册扩展)。
|
||||
|
||||
**在 `onLoad()` 时插件并未处于启用状态,此时插件不能进行监听事件,加载配置等操作。**
|
||||
|
||||
@ -184,13 +183,13 @@ Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务
|
||||
|
||||
#### 启用
|
||||
|
||||
[`JarPluginLoader`] 调用插件的 `onEnable()`,意为启用一个插件。
|
||||
[`JvmPluginLoader`] 调用插件的 `onEnable()`,意为启用一个插件。
|
||||
|
||||
此时插件可以启动所有协程,事件监听,和其他任务。**但这些任务都应该拥有生命周期管理,详见 [任务生命周期管理](#任务生命周期管理)。**
|
||||
|
||||
#### 禁用
|
||||
|
||||
[`JarPluginLoader`] 调用插件的 `onDisable()`,意为禁用一个插件。
|
||||
[`JvmPluginLoader`] 调用插件的 `onDisable()`,意为禁用一个插件。
|
||||
|
||||
插件的任何类和对象都不会被卸载。「禁用」仅表示停止关闭所有正在进行的任务,保存所有数据,停止处理将来的数据。
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
## 目录
|
||||
|
||||
- **[准备工作](#准备工作)**
|
||||
- **[启动 Console](#Run.md)**
|
||||
- **[启动 Console](Run.md)**
|
||||
|
||||
### 后端插件开发基础
|
||||
|
||||
@ -17,8 +17,6 @@
|
||||
### 后端插件开发进阶
|
||||
|
||||
- 扩展 - [Extension 模块和扩展点](Extensions.md)
|
||||
- 扩展 - [实现 PluginLoader](PluginLoader.md)
|
||||
- 扩展 - [实现 PermissionService](PermissionService.md)
|
||||
|
||||
### 实现前端
|
||||
- [FrontEnd](FrontEnd.md)
|
||||
@ -29,10 +27,9 @@
|
||||
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
|
||||
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
||||
[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
|
||||
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
|
||||
[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
|
||||
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
|
||||
[`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
|
||||
|
||||
## 准备工作
|
||||
|
38
docs/Run.md
38
docs/Run.md
@ -7,7 +7,7 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
## 独立启动
|
||||
|
||||
### 环境
|
||||
- JDK 11
|
||||
- JRE 11+ / JDK 11+
|
||||
|
||||
### 准备文件
|
||||
|
||||
@ -23,7 +23,37 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
|
||||
mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。
|
||||
|
||||
1. 在 [mirai-repo] 下载如下三个模块的最新版本文件:
|
||||
- []
|
||||
1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`):
|
||||
- mirai-core-qqandroid
|
||||
- mirai-console
|
||||
- mirai-console-pure
|
||||
|
||||
[mirai-repo]: https://github.com/project-mirai/mirai-repo/
|
||||
2. 创建一个新的文件, 名为 `start-mirai-console.bat`/`start-mirai-console.ps1`/`start-mirai-console.sh`
|
||||
|
||||
Windows CMD:
|
||||
```shell script
|
||||
@echo off
|
||||
title Mirai Console
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader %*
|
||||
pause
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
```shell script
|
||||
$Host.UI.RawUI.WindowTitle = "Mirai Console"
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $args
|
||||
pause
|
||||
```
|
||||
|
||||
Linux:
|
||||
```shell script
|
||||
#!/usr/bin/env bash
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $*
|
||||
```
|
||||
|
||||
然后就可以开始使用 mirai-console 了
|
||||
|
||||
### mirai-console-pure 前端参数
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-pure 支持的启动参数
|
||||
|
||||
[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow
|
||||
|
@ -23,7 +23,7 @@ kotlin {
|
||||
languageSettings.progressiveMode = true
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalAPI")
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi")
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
/*
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.FIELD,
|
||||
AnnotationTarget.CONSTRUCTOR
|
||||
)
|
||||
@MustBeDocumented
|
||||
annotation class ConsolePureExperimentalApi
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
public object ConsolePureSettings {
|
||||
@JvmField
|
||||
var setupAnsi: Boolean = System.getProperty("os.name")
|
||||
.toLowerCase()
|
||||
.contains("windows") // Just for Windows
|
||||
|
||||
@JvmField
|
||||
var noConsole: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var noAnsi: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var noConsoleSafeReading: Boolean = false
|
||||
|
||||
@JvmField
|
||||
var noConsoleReadingReplacement: String = ""
|
||||
}
|
@ -20,20 +20,23 @@ import net.mamoe.mirai.console.command.CommandExecuteStatus
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
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 org.jline.reader.UserInterruptException
|
||||
|
||||
val consoleLogger by lazy { DefaultLogger("console") }
|
||||
|
||||
@OptIn(ConsoleInternalAPI::class)
|
||||
@OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class)
|
||||
internal fun startupConsoleThread() {
|
||||
if (ConsolePureSettings.noConsole) return
|
||||
|
||||
MiraiConsole.launch(CoroutineName("Input")) {
|
||||
while (true) {
|
||||
try {
|
||||
val next = MiraiConsole.requestInput("").let {
|
||||
when {
|
||||
it.isBlank() -> it
|
||||
it.startsWith(CommandManager.commandPrefix) -> it
|
||||
it == "?" -> CommandManager.commandPrefix + BuiltInCommands.HelpCommand.primaryName
|
||||
else -> CommandManager.commandPrefix + it
|
||||
|
@ -17,7 +17,7 @@
|
||||
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
|
||||
"EXPOSED_SUPER_CLASS"
|
||||
)
|
||||
@file:OptIn(ConsoleInternalAPI::class, ConsoleFrontEndImplementation::class)
|
||||
@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
@ -30,13 +30,14 @@ import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.plugin.DeferredPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.pure.noconsole.AllEmptyLineReader
|
||||
import net.mamoe.mirai.console.pure.noconsole.NoConsole
|
||||
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.console.util.NamedSupervisorJob
|
||||
import net.mamoe.mirai.utils.*
|
||||
import org.fusesource.jansi.Ansi
|
||||
@ -51,26 +52,23 @@ import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* mirai-console-pure 后端实现
|
||||
*
|
||||
* @see MiraiConsolePureLoader CLI 入口点
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
class MiraiConsoleImplementationPure
|
||||
@JvmOverloads constructor(
|
||||
override val rootPath: Path = Paths.get("."),
|
||||
override val builtInPluginLoaders: List<PluginLoader<*, *>> = Collections.unmodifiableList(
|
||||
listOf(DeferredPluginLoader { JarPluginLoader })
|
||||
),
|
||||
override val rootPath: Path = Paths.get(".").toAbsolutePath(),
|
||||
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }),
|
||||
override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl,
|
||||
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplPure,
|
||||
override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
|
||||
override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
|
||||
override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
|
||||
override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
|
||||
override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config"))
|
||||
override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
|
||||
override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
|
||||
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
|
||||
NamedSupervisorJob("MiraiConsoleImplementationPure") +
|
||||
CoroutineExceptionHandler { coroutineContext, throwable ->
|
||||
@ -119,6 +117,8 @@ private object ConsoleInputImpl : ConsoleInput {
|
||||
}
|
||||
|
||||
val lineReader: LineReader by lazy {
|
||||
if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader
|
||||
|
||||
LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.completer(NullCompleter())
|
||||
@ -126,6 +126,8 @@ val lineReader: LineReader by lazy {
|
||||
}
|
||||
|
||||
val terminal: Terminal = run {
|
||||
if (ConsolePureSettings.noConsole) return@run NoConsole
|
||||
|
||||
val dumb = System.getProperty("java.class.path")
|
||||
.contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
"INVISIBLE_GETTER",
|
||||
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER",
|
||||
)
|
||||
@file:OptIn(ConsoleInternalAPI::class)
|
||||
@file:OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
@ -26,13 +26,15 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||
import net.mamoe.mirai.console.pure.noconsole.SystemOutputPrintStream
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.minutesToMillis
|
||||
import java.io.PrintStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* mirai-console-pure CLI 入口点
|
||||
@ -40,6 +42,7 @@ import java.io.PrintStream
|
||||
object MiraiConsolePureLoader {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
parse(args, exitProcess = true)
|
||||
startAsDaemon()
|
||||
try {
|
||||
runBlocking {
|
||||
@ -50,8 +53,99 @@ object MiraiConsolePureLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
fun printHelpMessage() {
|
||||
val help = listOf(
|
||||
"" to "Mirai-Console[Pure FrontEnd] v" + kotlin.runCatching {
|
||||
net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
|
||||
}.getOrElse { "<unknown>" },
|
||||
"" to "",
|
||||
"--help" to "显示此帮助",
|
||||
"" to "",
|
||||
"--no-console" to "使用无终端操作环境",
|
||||
"--dont-setup-terminal-ansi" to
|
||||
"[NoConsole] [Windows Only] 不进行ansi console初始化工作",
|
||||
"--no-ansi" to "[NoConsole] 禁用 ansi",
|
||||
"--safe-reading" to
|
||||
"[NoConsole] 如果启动此选项, console在获取用户输入的时候会获得一个安全的替换符\n" +
|
||||
" 如果不启动, 将会直接 error",
|
||||
"--reading-replacement <string>" to
|
||||
"[NoConsole] Console尝试读取命令的替换符, 默认是空字符串\n" +
|
||||
" 使用此选项会自动开启 --safe-reading",
|
||||
)
|
||||
val prefixPlaceholder = String(CharArray(
|
||||
help.maxOfOrNull { it.first.length }!! + 3
|
||||
) { ' ' })
|
||||
|
||||
fun printOption(optionName: String, value: String) {
|
||||
if (optionName == "") {
|
||||
println(value)
|
||||
return
|
||||
}
|
||||
print(optionName)
|
||||
print(prefixPlaceholder.substring(optionName.length))
|
||||
val lines = value.split('\n').iterator()
|
||||
if (lines.hasNext()) println(lines.next())
|
||||
lines.forEach { line ->
|
||||
print(prefixPlaceholder)
|
||||
println(line)
|
||||
}
|
||||
}
|
||||
help.forEach { (optionName, value) ->
|
||||
printOption(optionName, value)
|
||||
}
|
||||
}
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
fun parse(args: Array<String>, exitProcess: Boolean = false) {
|
||||
val iterator = args.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
when (val option = iterator.next()) {
|
||||
"--help" -> {
|
||||
printHelpMessage()
|
||||
if (exitProcess) exitProcess(0)
|
||||
return
|
||||
}
|
||||
"--no-console" -> {
|
||||
ConsolePureSettings.noConsole = true
|
||||
}
|
||||
"--dont-setup-terminal-ansi" -> {
|
||||
ConsolePureSettings.setupAnsi = false
|
||||
}
|
||||
"--no-ansi" -> {
|
||||
ConsolePureSettings.noAnsi = true
|
||||
ConsolePureSettings.setupAnsi = false
|
||||
}
|
||||
"--reading-replacement" -> {
|
||||
ConsolePureSettings.noConsoleSafeReading = true
|
||||
if (iterator.hasNext()) {
|
||||
ConsolePureSettings.noConsoleReadingReplacement = iterator.next()
|
||||
} else {
|
||||
println("Bad option `--reading-replacement`")
|
||||
println("Usage: --reading-replacement <string>")
|
||||
if (exitProcess)
|
||||
exitProcess(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
"--safe-reading" -> {
|
||||
ConsolePureSettings.noConsoleSafeReading = true
|
||||
}
|
||||
else -> {
|
||||
println("Unknown option `$option`")
|
||||
printHelpMessage()
|
||||
if (exitProcess)
|
||||
exitProcess(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ConsolePureSettings.noConsole)
|
||||
SystemOutputPrintStream // Setup Output Channel
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {
|
||||
instance.start()
|
||||
overrideSTD()
|
||||
@ -61,10 +155,10 @@ object MiraiConsolePureLoader {
|
||||
|
||||
internal object ConsoleDataHolder : AutoSavePluginDataHolder,
|
||||
CoroutineScope by MiraiConsole.childScope("ConsoleDataHolder") {
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
|
||||
|
||||
@ConsoleExperimentalAPI
|
||||
@ConsoleExperimentalApi
|
||||
override val dataHolderName: String
|
||||
get() = "Pure"
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user