Merge remote-tracking branch 'origin/master' into no-console

# Conflicts:
#	frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt
#	frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt
#	frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt
This commit is contained in:
Karlatemp 2020-09-12 19:37:56 +08:00
commit 9a1a6dddfd
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
74 changed files with 1464 additions and 1558 deletions

View File

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

View File

@ -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.extension.GlobalComponentStorage
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.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.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,7 +60,7 @@ public interface MiraiConsole : CoroutineScope {
*
* **注意**: 插件不应该在任何时刻使用它.
*/
@ConsoleInternalAPI
@ConsoleInternalApi
public val mainLogger: MiraiLogger
/**
@ -80,13 +80,13 @@ 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 +106,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 +118,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 +133,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) {

View File

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

View File

@ -12,22 +12,23 @@ 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.PermissionId
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 +36,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(
PermissionId("console", "*"),
"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 +69,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 +88,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 +98,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 +123,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 +147,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 +189,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
}
}
}
}
*/
}

View File

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

View File

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

View File

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

View File

@ -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
@ -32,11 +32,10 @@ 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 +46,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 +134,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 +177,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 +279,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 +438,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 +460,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,6 +500,9 @@ public fun CommandSender.getBotOrNull(): Bot? {
/**
* 控制台指令执行者. 代表由控制台执行指令
*
* 控制台拥有一切指令的执行权限.
*
* @see INSTANCE
*/
// 前端实现
@ -514,8 +513,7 @@ public abstract class ConsoleCommandSender @ConsoleFrontEndImplementation constr
public final override val name: String get() = NAME
public final override fun toString(): String = NAME
@ExperimentalPermission
public final override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.Console
public final override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console
public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope {
public const val NAME: String = "ConsoleCommandSender"
@ -607,13 +605,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 +624,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 +632,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 +646,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 +654,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 +690,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 +703,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 +716,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 {

View File

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

View File

@ -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) }
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -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>(
/**
* 节点名称.
@ -191,7 +190,7 @@ public interface PluginData {
/**
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public fun onInit(owner: PluginDataHolder, storage: PluginDataStorage)
}
@ -346,6 +345,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>

View File

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

View File

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

View File

@ -15,7 +15,7 @@ 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.util.ConsoleExperimentalApi
import java.io.File
import java.nio.file.Path
@ -33,12 +33,12 @@ import java.nio.file.Path
* @see PluginDataHolder
* @see JarPluginLoader.dataStorage
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface PluginDataStorage {
/**
* 读取一个实例. 并为 [instance] [设置 [PluginDataStorage]][PluginData.onInit]
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public fun load(holder: PluginDataHolder, instance: PluginData)
/**
@ -46,7 +46,7 @@ public interface PluginDataStorage {
*
* **实现细节**: 调用 [PluginData.updaterSerializer]
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public fun store(holder: PluginDataHolder, instance: PluginData)
/*
@ -96,7 +96,7 @@ public companion object {
* 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失.
* @see PluginDataStorage
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface MemoryPluginDataStorage : PluginDataStorage {
public companion object {
/**
@ -112,7 +112,7 @@ public interface MemoryPluginDataStorage : PluginDataStorage {
/**
* 用多个文件存储 [PluginData] 实例的 [PluginDataStorage].
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface MultiFilePluginDataStorage : PluginDataStorage {
/**
* 存放 [PluginData] 的目录.

View File

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

View File

@ -0,0 +1,216 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.extensions.LazyPermissionServiceProviderImpl
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
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.permission.PermissionService
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.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.companionObjectInstance
/**
* 组件容器, 容纳 [Plugin] 注册的 [Extension].
*/
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,
)
}
@Suppress("EXPOSED_SUPER_CLASS")
public class ScopedComponentStorage(
@JvmField
internal val plugin: Plugin,
) : AbstractConcurrentComponentStorage() {
/**
* 注册一个扩展
*/
public fun <E : Extension> contribute(
extensionPoint: ExtensionPoint<E>,
lazyInstance: () -> E,
) {
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
))
}
/**
* 注册一个 [PermissionService]
*/
public fun contributePermissionService(
lazyInstance: () -> PermissionService<*>,
) {
contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance))
}
}
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() }
}
@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.type.qualifiedName}",
throwable
)
}
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T) -> Unit): Unit =
withExtensions(block)
@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))
}
}

View File

@ -12,18 +12,19 @@ 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.util.ConsoleExperimentalApi
/**
* 表示一个扩展.
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface Extension
/**
* 增加一些函数 (方法)的扩展
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface FunctionExtension : Extension
/**
@ -33,7 +34,7 @@ public interface FunctionExtension : Extension
*
* @see PermissionServiceProvider
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface SingletonExtension<T> : Extension {
public val instance: T
}
@ -43,7 +44,7 @@ public interface SingletonExtension<T> : Extension {
*
* @see PluginLoaderProvider
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public interface InstanceExtension<T> : Extension {
public val instance: T
}

View File

@ -11,30 +11,19 @@
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 net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
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
@ConsoleExperimentalApi
public inline fun <reified T : Extension> ExtensionPoint<*>.isFor(exactType: Boolean = false): Boolean {
return if (exactType) {
T::class == type
@ -43,46 +32,17 @@ public interface ExtensionPoint<T : Extension> {
}
}
@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 interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T>
/**
* 表示一个扩展点
*/
@ConsoleExperimentalAPI
@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()
}
}
@ConsoleExperimentalApi
public override val type: KClass<T>,
) : ExtensionPoint<T>
/**
@ -93,64 +53,10 @@ public open class AbstractExtensionPoint<T : Extension>(
* @see PluginLoader.disable
* @see PluginLoader.description
*/
@ConsoleExperimentalAPI
@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)
}

View File

@ -1,19 +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.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
)

View File

@ -3,20 +3,21 @@ 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>
}
public class PermissionServiceProviderImpl(override val instance: PermissionService<*>) : PermissionServiceProvider
public class LazyPermissionServiceProviderImpl(initializer: () -> PermissionService<*>) : PermissionServiceProvider {
override val instance: PermissionService<*> by lazy(initializer)
}

View File

@ -3,12 +3,9 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
/**
* 提供扩展 [PluginLoader]
*
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供
*/
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)

View File

@ -12,8 +12,9 @@ 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.plugin.Plugin
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.info
import kotlin.reflect.KClass
@ -21,21 +22,33 @@ import kotlin.reflect.KClass
* 用于同时拥有多个 [SingletonExtension] 时选择一个实例.
*
* 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器.
*
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供
*/
@ConsoleExperimentalApi
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 +63,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) })
}
}

View File

@ -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.extension.GlobalComponentStorage
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.data.castOrNull
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
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.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.utils.*
import java.nio.file.Path
@ -90,7 +92,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 +125,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 +185,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,7 +212,9 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
}
}
PostStartupExtension.useExtensions { it() }
GlobalComponentStorage.run {
PostStartupExtension.useExtensions { it() }
}
mainLogger.info { "mirai-console started successfully." }
}

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ 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()

View File

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

View File

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

View File

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

View File

@ -84,10 +84,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)
}

View File

@ -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.ScopedComponentStorage
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: ScopedComponentStorage = ScopedComponentStorage(this)
final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register(
PermissionService.INSTANCE.allocatePermissionIdForPlugin(name, "*"),
@ -58,11 +61,6 @@ 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 {
@ -106,8 +104,8 @@ internal abstract class JvmPluginInternal(
}
@Throws(Throwable::class)
internal fun internalOnLoad() { // propagate exceptions
onLoad()
internal fun internalOnLoad(componentStorage: ScopedComponentStorage) {
onLoad(componentStorage)
}
internal fun internalOnEnable(): Boolean {
@ -135,6 +133,7 @@ internal abstract class JvmPluginInternal(
// for future use
@Suppress("PropertyName")
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
this as AbstractJvmPlugin
CoroutineName("Plugin $dataHolderName")
}
@ -149,7 +148,7 @@ internal abstract class JvmPluginInternal(
.plus(parentCoroutineContext)
.plus(
NamedSupervisorJob(
"Plugin $dataHolderName",
"Plugin ${(this as AbstractJvmPlugin).dataHolderName}",
parentCoroutineContext[Job] ?: JarPluginLoaderImpl.coroutineContext[Job]!!
)
)

View File

@ -11,24 +11,22 @@
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.extension.GlobalComponentStorage
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.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.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") {
@ -43,11 +41,11 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
private val _pluginLoaders: MutableList<PluginLoader<*, *>> by lazy {
MiraiConsole.builtInPluginLoaders.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<*, *>>
@ -64,17 +62,6 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
?: 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() }
@ -114,59 +101,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.load(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,31 +171,10 @@ 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)
@ -220,7 +192,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
fun Collection<PluginDependency>.filterIsMissing(): List<PluginDependency> =
this.filterNot { it.isOptional || it in resolved }
tailrec fun List<D>.doSort() {
fun List<D>.doSort() {
if (this.isEmpty()) return
val beforeSize = this.size
@ -239,14 +211,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")

View File

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

View File

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

View File

@ -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 }
}
}
}
/**
* 所有权限的父权限.
* 根权限. 所有权限的父权限.
*/
@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

View File

@ -11,19 +11,19 @@ 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] 唯一对应.
*/
@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 id: String,
) {
init {
require(!namespace.contains(':')) {
@ -34,21 +34,30 @@ public data class PermissionId(
}
}
@Serializer(forClass = PermissionId::class)
public object AsClassSerializer
public object AsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
serializer = { it.namespace + ":" + it.id },
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:$id"
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)
}
}
}
}

View File

@ -9,8 +9,12 @@
package net.mamoe.mirai.console.permission
@ExperimentalPermission
/**
* [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace].
*/
public interface PermissionIdNamespace {
@ExperimentalPermission
/**
* 创建一个此命名空间下的 [PermitteeId]
*/
public fun permissionId(id: String): PermissionId
}

View File

@ -0,0 +1,23 @@
/*
* 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
import kotlin.annotation.AnnotationTarget.*
/**
* 表示一个应该由 [权限服务][PermissionService] 实现的类.
*
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段.
* 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们.
*/
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@MustBeDocumented
internal annotation class PermissionImplementation

View File

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

View File

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

View File

@ -11,31 +11,60 @@
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
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 +72,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 +127,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>
}
}
}

View File

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

View File

@ -0,0 +1,307 @@
/*
* 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.AnyMember
import net.mamoe.mirai.console.permission.AbstractPermitteeId.ExactMember
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.contact.Contact
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 }
}
}
/**
* 内建的 [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"
}
}

View File

@ -18,7 +18,6 @@ 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
/**
@ -64,11 +63,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]
*/

View File

@ -13,7 +13,6 @@ 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
@ -31,14 +30,11 @@ import java.util.*
*
* ## 扩展加载器
* 插件被允许扩展一个加载器.
* Console 使用 [ServiceLoader] 加载 [PluginLoader] 的实例.
* 插件也可通过 [PluginManager.register] 手动注册, 然而这是不推荐的.
*
* ### 实现扩展加载器
* 直接实现接口 [PluginLoader] [FilePluginLoader], 并添加 [ServiceLoader] 相关资源文件即可.
*
* @see JarPluginLoader Jar 插件加载器
* @see PluginManager.register 注册一个扩展的插件加载器
*/
public interface PluginLoader<P : Plugin, D : PluginDescription> {
/**

View File

@ -14,10 +14,8 @@ 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 java.io.File
import java.nio.file.Path
import java.util.*
/**
* 插件管理器.
@ -103,22 +101,6 @@ 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]
*/
@ -158,8 +140,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() }

View File

@ -11,14 +11,14 @@ 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,
@ -32,7 +32,7 @@ public interface PluginCenter {
val commands: List<String>
)
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
@Serializable
public data class PluginInfo(
val name: String,

View File

@ -20,13 +20,6 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
* @see Plugin
*/
public interface PluginDescription {
/**
* 插件类型. 将会决定加载顺序
*
* @see PluginLoadPriority
*/
public val loadPriority: PluginLoadPriority
/**
* 插件 ID, 必须全英文, 仅允许英文字母, '-', '_', '.'.
*

View File

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

View File

@ -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: JarPluginLoader get() = super<JvmPluginInternal>.loader
public final override fun permissionId(id: String): PermissionId = PermissionId(description.id, id)
/**
* 重载 [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() }

View File

@ -13,23 +13,23 @@ 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.util.ConsoleExperimentalApi
/**
* 内建的 Jar (JVM) 插件加载器
*/
@ConsoleExperimentalAPI("classname might change")
@ConsoleExperimentalApi("classname might change")
public interface JarPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
/**
* [JvmPlugin.reloadPluginData] 默认使用的实例
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public val dataStorage: PluginDataStorage
/**
* [JvmPlugin.reloadPluginData] 默认使用的实例
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
public val configStorage: PluginDataStorage
public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl {

View File

@ -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.ScopedComponentStorage
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,40 @@ 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: JarPluginLoader get() = JarPluginLoader
/**
* 在插件被加载时调用. 只会被调用一次.
*
* [onLoad] 时可注册扩展 [ScopedComponentStorage.contribute]
*
* @receiver 组件容器
*/
@JvmDefault
public fun onLoad() {
}
public fun @ParameterName("storage") ScopedComponentStorage.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: ScopedComponentStorage): 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() }
}

View File

@ -14,7 +14,6 @@ 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
/**
@ -22,6 +21,8 @@ import kotlin.internal.LowPriorityInOverloadResolution
*
* 请不要自行实现 [JvmPluginDescription] 接口. 它不具有继承稳定性.
*
* 要查看相关约束, 参考 [PluginDescription]
*
* @see SimpleJvmPluginDescription
* @see JvmPluginDescriptionBuilder
*/
@ -82,7 +83,6 @@ public class JvmPluginDescriptionBuilder(
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() }
@ -103,21 +103,6 @@ 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,
@ -154,7 +139,7 @@ public class JvmPluginDescriptionBuilder(
@Suppress("DEPRECATION_ERROR")
public fun build(): JvmPluginDescription =
SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
@Retention(AnnotationRetention.SOURCE)
@DslMarker
@ -194,7 +179,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,8 +200,7 @@ 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" }
@ -242,8 +225,7 @@ public fun JvmPluginDescription(
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
@Deprecated(
"JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.",
@ -262,5 +244,4 @@ public fun JvmPluginDescription(
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies)

View File

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

View File

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

View File

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

View File

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

View File

@ -18,9 +18,9 @@ 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.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 +32,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

View File

@ -12,12 +12,12 @@ 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(

View File

@ -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-dev-8"
const val consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0"
const val consolePure = console

View File

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

View File

@ -20,14 +20,14 @@ 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, ConsolePureExperimentalAPI::class)
@OptIn(ConsoleInternalApi::class, ConsolePureExperimentalAPI::class)
internal fun startupConsoleThread() {
if (ConsolePureSettings.noConsole) return
@ -36,6 +36,7 @@ internal fun startupConsoleThread() {
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

View File

@ -17,7 +17,7 @@
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
"EXPOSED_SUPER_CLASS"
)
@file:OptIn(ConsoleInternalAPI::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalAPI::class)
@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalAPI::class)
package net.mamoe.mirai.console.pure
@ -34,11 +34,11 @@ 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.pure.ConsoleInputImpl.requestInput
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.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.console.util.NamedSupervisorJob
import net.mamoe.mirai.utils.*
import org.fusesource.jansi.Ansi
@ -60,7 +60,7 @@ import java.util.*
*
* @see MiraiConsolePureLoader CLI 入口点
*/
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
class MiraiConsoleImplementationPure
@JvmOverloads constructor(
override val rootPath: Path = Paths.get("."),

View File

@ -15,7 +15,7 @@
"INVISIBLE_GETTER",
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER",
)
@file:OptIn(ConsoleInternalAPI::class, ConsolePureExperimentalAPI::class)
@file:OptIn(ConsoleInternalApi::class, ConsolePureExperimentalAPI::class)
package net.mamoe.mirai.console.pure
@ -27,8 +27,8 @@ 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.pure.noconsole.SystemOutputPrintStream
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
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
@ -145,7 +145,7 @@ object MiraiConsolePureLoader {
}
@Suppress("MemberVisibilityCanBePrivate")
@ConsoleExperimentalAPI
@ConsoleExperimentalApi
fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {
instance.start()
overrideSTD()
@ -155,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"
}