mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Merge remote-tracking branch 'origin/master' into dependencies
This commit is contained in:
commit
c42fd3a8a6
@ -6,7 +6,6 @@ import java.time.Instant
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
kotlin("kapt")
|
|
||||||
id("java")
|
id("java")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
id("com.jfrog.bintray")
|
id("com.jfrog.bintray")
|
||||||
@ -81,9 +80,9 @@ dependencies {
|
|||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||||
|
|
||||||
|
|
||||||
val autoService = "1.0-rc7"
|
// val autoService = "1.0-rc7"
|
||||||
kapt("com.google.auto.service", "auto-service", autoService)
|
// kapt("com.google.auto.service", "auto-service", autoService)
|
||||||
compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
// compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.apply {
|
ext.apply {
|
||||||
|
@ -9,27 +9,30 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.command
|
package net.mamoe.mirai.console.command
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.alsoLogin
|
import net.mamoe.mirai.alsoLogin
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||||
|
import net.mamoe.mirai.console.command.description.PermissibleIdentifierArgumentParser
|
||||||
|
import net.mamoe.mirai.console.command.description.PermissionIdArgumentParser
|
||||||
|
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
|
||||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
|
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||||
import net.mamoe.mirai.console.util.BotManager.INSTANCE.addManager
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
import net.mamoe.mirai.console.util.BotManager.INSTANCE.managers
|
import net.mamoe.mirai.console.permission.PermissibleIdentifier
|
||||||
import net.mamoe.mirai.console.util.BotManager.INSTANCE.removeManager
|
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.getGrantedPermissions
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.event.events.EventCancelledException
|
import net.mamoe.mirai.event.events.EventCancelledException
|
||||||
import net.mamoe.mirai.getFriendOrNull
|
|
||||||
import net.mamoe.mirai.message.nextMessageOrNull
|
import net.mamoe.mirai.message.nextMessageOrNull
|
||||||
import net.mamoe.mirai.utils.secondsToMillis
|
import net.mamoe.mirai.utils.secondsToMillis
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -60,33 +63,6 @@ public object BuiltInCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Managers : CompositeCommand(
|
|
||||||
ConsoleCommandOwner, "managers",
|
|
||||||
description = "Manage the managers for each bot",
|
|
||||||
permission = CommandPermission.Console or CommandPermission.Manager
|
|
||||||
), BuiltInCommand {
|
|
||||||
@Permission(CommandPermission.Console::class)
|
|
||||||
@SubCommand
|
|
||||||
public suspend fun CommandSender.add(target: User) {
|
|
||||||
target.bot.addManager(target.id)
|
|
||||||
sendMessage("已成功添加 ${target.render()} 为 ${target.bot.render()} 的管理员")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Permission(CommandPermission.Console::class)
|
|
||||||
@SubCommand
|
|
||||||
public suspend fun CommandSender.remove(target: User) {
|
|
||||||
target.bot.removeManager(target.id)
|
|
||||||
sendMessage("已成功取消 ${target.render()} 对 ${target.bot.render()} 的管理员权限")
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubCommand
|
|
||||||
public suspend fun CommandSender.list(bot: Bot) {
|
|
||||||
sendMessage("$bot 的管理员列表:\n" + bot.managers.joinToString("\n") {
|
|
||||||
bot.getFriendOrNull(it)?.render() ?: it.toString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Help : SimpleCommand(
|
public object Help : SimpleCommand(
|
||||||
ConsoleCommandOwner, "help",
|
ConsoleCommandOwner, "help",
|
||||||
description = "Command list"
|
description = "Command list"
|
||||||
@ -119,14 +95,15 @@ public object BuiltInCommands {
|
|||||||
closingLock.withLock {
|
closingLock.withLock {
|
||||||
sendMessage("Stopping mirai-console")
|
sendMessage("Stopping mirai-console")
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
MiraiConsole.job.cancelAndJoin()
|
ignoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
|
||||||
}.fold(
|
}.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
ignoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
ignoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
|
if (it is CancellationException) return@fold
|
||||||
@OptIn(ConsoleInternalAPI::class)
|
@OptIn(ConsoleInternalAPI::class)
|
||||||
MiraiConsole.mainLogger.error(it)
|
MiraiConsole.mainLogger.error("Exception in stop", it)
|
||||||
ignoreException<EventCancelledException> {
|
ignoreException<EventCancelledException> {
|
||||||
sendMessage(
|
sendMessage(
|
||||||
it.localizedMessage ?: it.message ?: it.toString()
|
it.localizedMessage ?: it.message ?: it.toString()
|
||||||
@ -141,7 +118,7 @@ public object BuiltInCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public object Login : SimpleCommand(
|
public object Login : SimpleCommand(
|
||||||
ConsoleCommandOwner, "login",
|
ConsoleCommandOwner, "login", "登录",
|
||||||
description = "Log in a bot account."
|
description = "Log in a bot account."
|
||||||
), BuiltInCommand {
|
), BuiltInCommand {
|
||||||
@Handler
|
@Handler
|
||||||
@ -166,6 +143,40 @@ public object BuiltInCommands {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
public object Permission : CompositeCommand(
|
||||||
|
ConsoleCommandOwner, "permission", "权限",
|
||||||
|
description = "Manage permissions",
|
||||||
|
overrideContext = buildCommandArgumentContext {
|
||||||
|
PermissibleIdentifier::class with PermissibleIdentifierArgumentParser
|
||||||
|
PermissionId::class with PermissionIdArgumentParser
|
||||||
|
}
|
||||||
|
), BuiltInCommand {
|
||||||
|
// TODO: 2020/9/10 improve Permission command
|
||||||
|
@SubCommand
|
||||||
|
public suspend fun CommandSender.grant(target: PermissibleIdentifier, permission: PermissionId) {
|
||||||
|
target.grantPermission(permission)
|
||||||
|
sendMessage("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand
|
||||||
|
public suspend fun CommandSender.deny(target: PermissibleIdentifier, permission: PermissionId) {
|
||||||
|
target.denyPermission(permission)
|
||||||
|
sendMessage("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand("grantedPermissions", "gp")
|
||||||
|
public suspend fun CommandSender.grantedPermissions(target: PermissibleIdentifier) {
|
||||||
|
val grantedPermissions = target.getGrantedPermissions()
|
||||||
|
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand("listPermissions", "lp")
|
||||||
|
public suspend fun CommandSender.listPermissions() {
|
||||||
|
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal inline fun <reified E : Throwable, R> ignoreException(block: () -> R): R? {
|
internal inline fun <reified E : Throwable, R> ignoreException(block: () -> R): R? {
|
||||||
|
@ -15,7 +15,11 @@ import net.mamoe.kjbb.JvmBlockingBridge
|
|||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||||
import net.mamoe.mirai.console.command.java.JCommand
|
import net.mamoe.mirai.console.command.java.JCommand
|
||||||
|
import net.mamoe.mirai.console.internal.command.createCommandPermission
|
||||||
import net.mamoe.mirai.console.internal.command.isValidSubName
|
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.console.permission.PermissionId
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
import net.mamoe.mirai.message.data.SingleMessage
|
import net.mamoe.mirai.message.data.SingleMessage
|
||||||
|
|
||||||
@ -51,7 +55,8 @@ public interface Command {
|
|||||||
/**
|
/**
|
||||||
* 指令权限
|
* 指令权限
|
||||||
*/
|
*/
|
||||||
public val permission: CommandPermission
|
@ExperimentalPermission
|
||||||
|
public val permission: Permission
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选
|
* 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选
|
||||||
@ -95,13 +100,14 @@ public suspend inline fun Command.onCommand(sender: CommandSender, args: Message
|
|||||||
* @see CompositeCommand
|
* @see CompositeCommand
|
||||||
* @see RawCommand
|
* @see RawCommand
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractCommand @JvmOverloads constructor(
|
public abstract class AbstractCommand
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
@JvmOverloads constructor(
|
||||||
/** 指令拥有者. */
|
/** 指令拥有者. */
|
||||||
public override val owner: CommandOwner,
|
public override val owner: CommandOwner,
|
||||||
vararg names: String,
|
vararg names: String,
|
||||||
description: String = "<no description available>",
|
description: String = "<no description available>",
|
||||||
/** 指令权限 */
|
parentPermission: PermissionId = owner.basePermission,
|
||||||
public override val permission: CommandPermission = CommandPermission.Default,
|
|
||||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||||
public override val prefixOptional: Boolean = false
|
public override val prefixOptional: Boolean = false
|
||||||
) : Command {
|
) : Command {
|
||||||
@ -111,4 +117,6 @@ public abstract class AbstractCommand @JvmOverloads constructor(
|
|||||||
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
|
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
public override val permission: Permission by lazy { createCommandPermission(parentPermission) }
|
||||||
}
|
}
|
@ -10,6 +10,10 @@
|
|||||||
package net.mamoe.mirai.console.command
|
package net.mamoe.mirai.console.command
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionId
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionIdNamespace
|
||||||
|
import net.mamoe.mirai.console.permission.RootPermission
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,9 +24,25 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
|||||||
*
|
*
|
||||||
* @see JvmPlugin 是一个 [CommandOwner]
|
* @see JvmPlugin 是一个 [CommandOwner]
|
||||||
*/
|
*/
|
||||||
public interface CommandOwner
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
public interface CommandOwner : PermissionIdNamespace {
|
||||||
|
/**
|
||||||
|
* 此 [PermissionIdNamespace] 拥有的指令都默认将 [basePermission] 作为父权限.
|
||||||
|
*
|
||||||
|
* TODO document
|
||||||
|
*/
|
||||||
|
@ExperimentalPermission
|
||||||
|
public val basePermission: PermissionId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
|
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
|
||||||
*/
|
*/
|
||||||
internal object ConsoleCommandOwner : CommandOwner
|
internal object ConsoleCommandOwner : CommandOwner {
|
||||||
|
@ExperimentalPermission
|
||||||
|
override val basePermission: PermissionId
|
||||||
|
get() = RootPermission.id
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
override fun permissionId(id: String): PermissionId = PermissionId("console", id)
|
||||||
|
}
|
@ -1,143 +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", "NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
|
|
||||||
|
|
||||||
package net.mamoe.mirai.console.command
|
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
|
||||||
import net.mamoe.mirai.console.internal.command.AndCommandPermissionImpl
|
|
||||||
import net.mamoe.mirai.console.internal.command.OrCommandPermissionImpl
|
|
||||||
import net.mamoe.mirai.console.util.BotManager.INSTANCE.isManager
|
|
||||||
import net.mamoe.mirai.contact.isAdministrator
|
|
||||||
import net.mamoe.mirai.contact.isOperator
|
|
||||||
import net.mamoe.mirai.contact.isOwner
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指令权限.
|
|
||||||
*
|
|
||||||
* 在 [CommandManager.executeCommand] 时将会检查权限.
|
|
||||||
*
|
|
||||||
* @see Command.permission 从指令获取权限
|
|
||||||
*/
|
|
||||||
public fun interface CommandPermission {
|
|
||||||
/**
|
|
||||||
* 判断 [this] 是否拥有这个指令的权限
|
|
||||||
*
|
|
||||||
* @see CommandSender.hasPermission
|
|
||||||
* @see CommandPermission.testPermission
|
|
||||||
*/
|
|
||||||
public fun CommandSender.hasPermission(): Boolean
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 满足两个权限其中一个即可使用指令
|
|
||||||
*/ // no extension for Java
|
|
||||||
public infix fun or(another: CommandPermission): CommandPermission = OrCommandPermissionImpl(this, another)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同时拥有两个权限才能使用指令
|
|
||||||
*/ // no extension for Java
|
|
||||||
public infix fun and(another: CommandPermission): CommandPermission = AndCommandPermissionImpl(this, another)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 任何人都可以使用这个指令
|
|
||||||
*/
|
|
||||||
public object Any : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 任何人都不能使用这个指令. 指令只能通过调用 [Command.onCommand] 执行.
|
|
||||||
*/
|
|
||||||
public object None : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 来自任何 [Bot] 的任何一个管理员或群主都可以使用这个指令
|
|
||||||
*/
|
|
||||||
public object Operator : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean {
|
|
||||||
return this is MemberCommandSender && this.user.isOperator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 来自任何 [Bot] 的任何一个群主都可以使用这个指令
|
|
||||||
*/
|
|
||||||
public object GroupOwner : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean {
|
|
||||||
return this is MemberCommandSender && this.user.isOwner()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 管理员 (不包含群主) 可以使用这个指令
|
|
||||||
*/
|
|
||||||
public object GroupAdmin : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean {
|
|
||||||
return this is MemberCommandSender && this.user.isAdministrator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 任何 [Bot] 的 manager 都可以使用这个指令
|
|
||||||
*/
|
|
||||||
public object Manager : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean {
|
|
||||||
return this is MemberCommandSender && this.user.isManager
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅控制台能使用和这个指令
|
|
||||||
*/
|
|
||||||
public object Console : CommandPermission {
|
|
||||||
public override fun CommandSender.hasPermission(): Boolean = this is ConsoleCommandSender
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认权限.
|
|
||||||
*
|
|
||||||
* @return [Manager] or [Console]
|
|
||||||
*/
|
|
||||||
public object Default : CommandPermission by (Manager or Console)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断 [this] 是否拥有权限 [permission]
|
|
||||||
*
|
|
||||||
* @see CommandSender.hasPermission
|
|
||||||
* @see CommandPermission.testPermission
|
|
||||||
* @see CommandPermission.hasPermission
|
|
||||||
*/
|
|
||||||
public inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
|
|
||||||
permission.run { this@hasPermission.hasPermission() }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断 [sender] 是否拥有权限 [this]
|
|
||||||
*
|
|
||||||
* @see CommandSender.hasPermission
|
|
||||||
* @see CommandPermission.testPermission
|
|
||||||
* @see CommandPermission.hasPermission
|
|
||||||
*/
|
|
||||||
public inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断 [sender] 是否拥有权限 [Command.permission]
|
|
||||||
*
|
|
||||||
* @see CommandSender.hasPermission
|
|
||||||
* @see CommandPermission.testPermission
|
|
||||||
* @see CommandPermission.hasPermission
|
|
||||||
*/
|
|
||||||
public inline fun Command.testPermission(sender: CommandSender): Boolean = sender.hasPermission(this.permission)
|
|
@ -32,6 +32,10 @@ import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
|||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||||
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
|
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.util.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
|
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
|
||||||
@ -132,7 +136,8 @@ import kotlin.internal.LowPriorityInOverloadResolution
|
|||||||
* @see toCommandSender
|
* @see toCommandSender
|
||||||
* @see asCommandSender
|
* @see asCommandSender
|
||||||
*/
|
*/
|
||||||
public interface CommandSender : CoroutineScope {
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
public interface CommandSender : CoroutineScope, Permissible {
|
||||||
/**
|
/**
|
||||||
* 与这个 [CommandSender] 相关的 [Bot].
|
* 与这个 [CommandSender] 相关的 [Bot].
|
||||||
* 当通过控制台执行时为 `null`.
|
* 当通过控制台执行时为 `null`.
|
||||||
@ -509,6 +514,9 @@ public abstract class ConsoleCommandSender @ConsoleFrontEndImplementation constr
|
|||||||
public final override val name: String get() = NAME
|
public final override val name: String get() = NAME
|
||||||
public final override fun toString(): String = NAME
|
public final override fun toString(): String = NAME
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public final override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.Console
|
||||||
|
|
||||||
public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope {
|
public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope {
|
||||||
public const val NAME: String = "ConsoleCommandSender"
|
public const val NAME: String = "ConsoleCommandSender"
|
||||||
public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) }
|
public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) }
|
||||||
@ -604,6 +612,9 @@ public open class FriendCommandSender internal constructor(
|
|||||||
public override val subject: Contact get() = user
|
public override val subject: Contact get() = user
|
||||||
public override fun toString(): String = "FriendCommandSender($user)"
|
public override fun toString(): String = "FriendCommandSender($user)"
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactFriend(user.id)
|
||||||
|
|
||||||
@JvmBlockingBridge
|
@JvmBlockingBridge
|
||||||
public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = sendMessage(PlainText(message))
|
public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = sendMessage(PlainText(message))
|
||||||
|
|
||||||
@ -620,10 +631,13 @@ public open class MemberCommandSender internal constructor(
|
|||||||
) : AbstractUserCommandSender(),
|
) : AbstractUserCommandSender(),
|
||||||
GroupAwareCommandSender,
|
GroupAwareCommandSender,
|
||||||
CoroutineScope by user.childScope("MemberCommandSender") {
|
CoroutineScope by user.childScope("MemberCommandSender") {
|
||||||
public override val group: Group get() = user.group
|
public final override val group: Group get() = user.group
|
||||||
public override val subject: Group get() = group
|
public override val subject: Group get() = group
|
||||||
public override fun toString(): String = "MemberCommandSender($user)"
|
public override fun toString(): String = "MemberCommandSender($user)"
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactMember(group.id, user.id)
|
||||||
|
|
||||||
@JvmBlockingBridge
|
@JvmBlockingBridge
|
||||||
public override suspend fun sendMessage(message: String): MessageReceipt<Group> = sendMessage(PlainText(message))
|
public override suspend fun sendMessage(message: String): MessageReceipt<Group> = sendMessage(PlainText(message))
|
||||||
|
|
||||||
@ -644,6 +658,10 @@ public open class TempCommandSender internal constructor(
|
|||||||
public override val subject: Contact get() = group
|
public override val subject: Contact get() = group
|
||||||
public override fun toString(): String = "TempCommandSender($user)"
|
public override fun toString(): String = "TempCommandSender($user)"
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public override val identifier: PermissibleIdentifier =
|
||||||
|
AbstractPermissibleIdentifier.ExactTemp(user.group.id, user.id)
|
||||||
|
|
||||||
@JvmBlockingBridge
|
@JvmBlockingBridge
|
||||||
public override suspend fun sendMessage(message: String): MessageReceipt<Member> = sendMessage(PlainText(message))
|
public override suspend fun sendMessage(message: String): MessageReceipt<Member> = sendMessage(PlainText(message))
|
||||||
|
|
||||||
|
@ -20,11 +20,12 @@ package net.mamoe.mirai.console.command
|
|||||||
import net.mamoe.mirai.console.command.description.*
|
import net.mamoe.mirai.console.command.description.*
|
||||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
||||||
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionId
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 复合指令. 指令注册时候会通过反射构造指令解析器.
|
* 复合指令. 指令注册时候会通过反射构造指令解析器.
|
||||||
@ -81,14 +82,14 @@ import kotlin.reflect.KClass
|
|||||||
* @see buildCommandArgumentContext
|
* @see buildCommandArgumentContext
|
||||||
*/
|
*/
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public abstract class CompositeCommand(
|
public abstract class CompositeCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
vararg names: String,
|
vararg names: String,
|
||||||
description: String = "no description available",
|
description: String = "no description available",
|
||||||
permission: CommandPermission = CommandPermission.Default,
|
parentPermission: PermissionId = owner.basePermission,
|
||||||
prefixOptional: Boolean = false,
|
prefixOptional: Boolean = false,
|
||||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
|
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
|
||||||
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
|
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
|
||||||
CommandArgumentContextAware {
|
CommandArgumentContextAware {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,7 +113,8 @@ public abstract class CompositeCommand(
|
|||||||
/** 指定子指令要求的权限 */
|
/** 指定子指令要求的权限 */
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target(FUNCTION)
|
@Target(FUNCTION)
|
||||||
protected annotation class Permission(val value: KClass<out CommandPermission>)
|
@ExperimentalPermission
|
||||||
|
protected annotation class Permission(val value: String)
|
||||||
|
|
||||||
/** 指令描述 */
|
/** 指令描述 */
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
|
@ -14,6 +14,10 @@ package net.mamoe.mirai.console.command
|
|||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||||
import net.mamoe.mirai.console.command.java.JRawCommand
|
import net.mamoe.mirai.console.command.java.JRawCommand
|
||||||
|
import net.mamoe.mirai.console.internal.command.createCommandPermission
|
||||||
|
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.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +31,7 @@ import net.mamoe.mirai.message.data.MessageChain
|
|||||||
* @see SimpleCommand 简单指令
|
* @see SimpleCommand 简单指令
|
||||||
* @see CompositeCommand 复合指令
|
* @see CompositeCommand 复合指令
|
||||||
*/
|
*/
|
||||||
public abstract class RawCommand(
|
public abstract class RawCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||||
/**
|
/**
|
||||||
* 指令拥有者.
|
* 指令拥有者.
|
||||||
* @see CommandOwner
|
* @see CommandOwner
|
||||||
@ -39,11 +43,14 @@ public abstract class RawCommand(
|
|||||||
public override val usage: String = "<no usages given>",
|
public override val usage: String = "<no usages given>",
|
||||||
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
||||||
public override val description: String = "<no descriptions given>",
|
public override val description: String = "<no descriptions given>",
|
||||||
/** 指令权限 */
|
/** 指令父权限 */
|
||||||
public override val permission: CommandPermission = CommandPermission.Default,
|
parentPermission: PermissionId = owner.basePermission,
|
||||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||||
public override val prefixOptional: Boolean = false
|
public override val prefixOptional: Boolean = false
|
||||||
) : Command {
|
) : Command {
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
public override val permission: Permission by lazy { createCommandPermission(parentPermission) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在指令被执行时调用.
|
* 在指令被执行时调用.
|
||||||
*
|
*
|
||||||
|
@ -22,6 +22,8 @@ import net.mamoe.mirai.console.command.description.*
|
|||||||
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
||||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
||||||
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionId
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,14 +49,14 @@ import net.mamoe.mirai.message.data.MessageChain
|
|||||||
* @see JSimpleCommand Java 实现
|
* @see JSimpleCommand Java 实现
|
||||||
* @see [CommandManager.executeCommand]
|
* @see [CommandManager.executeCommand]
|
||||||
*/
|
*/
|
||||||
public abstract class SimpleCommand(
|
public abstract class SimpleCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
vararg names: String,
|
vararg names: String,
|
||||||
description: String = "no description available",
|
description: String = "no description available",
|
||||||
permission: CommandPermission = CommandPermission.Default,
|
basePermission: PermissionId = owner.basePermission,
|
||||||
prefixOptional: Boolean = false,
|
prefixOptional: Boolean = false,
|
||||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
|
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
|
||||||
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
|
) : Command, AbstractReflectionCommand(owner, names, description, basePermission, prefixOptional),
|
||||||
CommandArgumentContextAware {
|
CommandArgumentContextAware {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,12 @@ package net.mamoe.mirai.console.command.description
|
|||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.console.command.*
|
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.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.PermissionId
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.getFriendOrNull
|
import net.mamoe.mirai.getFriendOrNull
|
||||||
import net.mamoe.mirai.getGroupOrNull
|
import net.mamoe.mirai.getGroupOrNull
|
||||||
@ -303,6 +308,31 @@ 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 {
|
||||||
|
illegalArgument("无法解析 $raw 为 PermissionId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public object PermissibleIdentifierArgumentParser : CommandArgumentParser<PermissibleIdentifier> {
|
||||||
|
override fun parse(raw: String, sender: CommandSender): PermissibleIdentifier {
|
||||||
|
return kotlin.runCatching { AbstractPermissibleIdentifier.parseFromString(raw) }.getOrElse {
|
||||||
|
illegalArgument("无法解析 $raw 为 PermissionId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parse(raw: MessageContent, sender: CommandSender): PermissibleIdentifier {
|
||||||
|
if (raw is At) {
|
||||||
|
return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).identifier
|
||||||
|
}
|
||||||
|
return super.parse(raw, sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArgumentParser<T> {
|
internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArgumentParser<T> {
|
||||||
fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
||||||
|
|
||||||
|
@ -9,8 +9,13 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.command.java
|
package net.mamoe.mirai.console.command.java
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||||
|
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.command.description.buildCommandArgumentContext
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionId
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,16 +69,18 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
|||||||
* @see buildCommandArgumentContext
|
* @see buildCommandArgumentContext
|
||||||
*/
|
*/
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public abstract class JCompositeCommand(
|
public abstract class JCompositeCommand @OptIn(ExperimentalPermission::class)
|
||||||
|
@JvmOverloads constructor(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
vararg names: String
|
vararg names: String,
|
||||||
) : CompositeCommand(owner, *names) {
|
parentPermission: PermissionId = owner.basePermission,
|
||||||
|
) : CompositeCommand(owner, *names, parentPermission = parentPermission) {
|
||||||
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
||||||
public final override var description: String = "<no descriptions given>"
|
public final override var description: String = "<no descriptions given>"
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/** 指令权限 */
|
@OptIn(ExperimentalPermission::class)
|
||||||
public final override var permission: CommandPermission = CommandPermission.Default
|
public final override var permission: net.mamoe.mirai.console.permission.Permission = super.permission
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||||
|
@ -13,6 +13,10 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.*
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||||
|
import net.mamoe.mirai.console.internal.command.createCommandPermission
|
||||||
|
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.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
import net.mamoe.mirai.message.data.SingleMessage
|
import net.mamoe.mirai.message.data.SingleMessage
|
||||||
|
|
||||||
@ -42,14 +46,16 @@ import net.mamoe.mirai.message.data.SingleMessage
|
|||||||
*
|
*
|
||||||
* @see JRawCommand
|
* @see JRawCommand
|
||||||
*/
|
*/
|
||||||
public abstract class JRawCommand(
|
public abstract class JRawCommand @OptIn(ExperimentalPermission::class)
|
||||||
|
@JvmOverloads constructor(
|
||||||
/**
|
/**
|
||||||
* 指令拥有者.
|
* 指令拥有者.
|
||||||
* @see CommandOwner
|
* @see CommandOwner
|
||||||
*/
|
*/
|
||||||
public override val owner: CommandOwner,
|
public override val owner: CommandOwner,
|
||||||
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
|
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
|
||||||
public override vararg val names: String
|
public override vararg val names: String,
|
||||||
|
parentPermission: PermissionId = owner.basePermission,
|
||||||
) : Command {
|
) : Command {
|
||||||
/** 用法说明, 用于发送给用户 */
|
/** 用法说明, 用于发送给用户 */
|
||||||
public override var usage: String = "<no usages given>"
|
public override var usage: String = "<no usages given>"
|
||||||
@ -60,7 +66,8 @@ public abstract class JRawCommand(
|
|||||||
protected set
|
protected set
|
||||||
|
|
||||||
/** 指令权限 */
|
/** 指令权限 */
|
||||||
public final override var permission: CommandPermission = CommandPermission.Default
|
@ExperimentalPermission
|
||||||
|
public final override var permission: Permission = createCommandPermission(parentPermission)
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||||
|
@ -12,9 +12,11 @@ package net.mamoe.mirai.console.command.java
|
|||||||
import net.mamoe.mirai.console.command.CommandManager
|
import net.mamoe.mirai.console.command.CommandManager
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||||
import net.mamoe.mirai.console.command.CommandOwner
|
import net.mamoe.mirai.console.command.CommandOwner
|
||||||
import net.mamoe.mirai.console.command.CommandPermission
|
|
||||||
import net.mamoe.mirai.console.command.SimpleCommand
|
import net.mamoe.mirai.console.command.SimpleCommand
|
||||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java 实现:
|
* Java 实现:
|
||||||
@ -39,13 +41,16 @@ import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
|||||||
* @see SimpleCommand
|
* @see SimpleCommand
|
||||||
* @see [CommandManager.executeCommand]
|
* @see [CommandManager.executeCommand]
|
||||||
*/
|
*/
|
||||||
public abstract class JSimpleCommand(
|
public abstract class JSimpleCommand @OptIn(ExperimentalPermission::class) constructor(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
vararg names: String
|
vararg names: String,
|
||||||
) : SimpleCommand(owner, *names) {
|
basePermission: PermissionId,
|
||||||
|
) : SimpleCommand(owner, *names, basePermission = basePermission) {
|
||||||
public override var description: String = super.description
|
public override var description: String = super.description
|
||||||
protected set
|
protected set
|
||||||
public override var permission: CommandPermission = super.permission
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public override var permission: Permission = super.permission
|
||||||
protected set
|
protected set
|
||||||
public override var prefixOptional: Boolean = super.prefixOptional
|
public override var prefixOptional: Boolean = super.prefixOptional
|
||||||
protected set
|
protected set
|
||||||
|
@ -1,15 +1,58 @@
|
|||||||
@file:Suppress("unused", "INAPPLICABLE_JVM_NAME")
|
@file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.data
|
package net.mamoe.mirai.console.data
|
||||||
|
|
||||||
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
|
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
|
||||||
import net.mamoe.mirai.console.internal.data.ShadowMap
|
import net.mamoe.mirai.console.internal.data.ShadowMap
|
||||||
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
|
import kotlin.internal.LowPriorityInOverloadResolution
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [PluginData] 相关一些扩展
|
* [PluginData] 相关一些扩展
|
||||||
*/
|
*/
|
||||||
public object PluginDataExtensions {
|
public object PluginDataExtensions {
|
||||||
|
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
|
public open class NotNullMap<K, V> internal constructor(
|
||||||
|
private val delegate: Map<K, V>
|
||||||
|
) : Map<K, V> by delegate {
|
||||||
|
override fun get(key: K): V =
|
||||||
|
delegate[key] ?: error("Internal error: delegate[key] returned null for NotNullMap.get")
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"getOrDefault on NotNullMap always returns the value in the map, and defaultValue will never be returned.",
|
||||||
|
level = DeprecationLevel.WARNING,
|
||||||
|
replaceWith = ReplaceWith("this.get(key)")
|
||||||
|
)
|
||||||
|
override fun getOrDefault(key: K, defaultValue: V): V {
|
||||||
|
return super.getOrDefault(key, defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") // as designed
|
||||||
|
public class NotNullMutableMap<K, V> internal constructor(
|
||||||
|
private val delegate: MutableMap<K, V>
|
||||||
|
) : MutableMap<K, V> by delegate, NotNullMap<K, V>(delegate) {
|
||||||
|
override fun get(key: K): V =
|
||||||
|
delegate[key] ?: error("Internal error: delegate[key] returned null for NotNullMutableMap.get")
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"getOrDefault on NotNullMutableMap always returns the value in the map, and defaultValue will never be returned.",
|
||||||
|
level = DeprecationLevel.WARNING,
|
||||||
|
replaceWith = ReplaceWith("this.get(key)")
|
||||||
|
)
|
||||||
|
override fun getOrDefault(key: K, defaultValue: V): V {
|
||||||
|
return super<MutableMap>.getOrDefault(key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"putIfAbsent on NotNullMutableMap always does nothing.",
|
||||||
|
level = DeprecationLevel.WARNING,
|
||||||
|
replaceWith = ReplaceWith("")
|
||||||
|
)
|
||||||
|
override fun putIfAbsent(key: K, value: V): Nothing? = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [LinkedHashMap], 再从 [this] 中取出链接自动保存的 [LinkedHashMap]. ([MutableMap.getOrPut] 的替代)
|
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [LinkedHashMap], 再从 [this] 中取出链接自动保存的 [LinkedHashMap]. ([MutableMap.getOrPut] 的替代)
|
||||||
*
|
*
|
||||||
@ -17,7 +60,7 @@ public object PluginDataExtensions {
|
|||||||
*/
|
*/
|
||||||
@JvmName("withEmptyDefaultMapImmutable")
|
@JvmName("withEmptyDefaultMapImmutable")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>> {
|
public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, Map<InnerE, InnerV>>> {
|
||||||
return this.withDefault { LinkedHashMap() }
|
return this.withDefault { LinkedHashMap() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +70,7 @@ public object PluginDataExtensions {
|
|||||||
*/
|
*/
|
||||||
@JvmName("withEmptyDefaultMap")
|
@JvmName("withEmptyDefaultMap")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, MutableMap<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, MutableMap<InnerE, InnerV>>> {
|
public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, MutableMap<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableMap<InnerE, InnerV>>> {
|
||||||
return this.withDefault { LinkedHashMap() }
|
return this.withDefault { LinkedHashMap() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +81,7 @@ public object PluginDataExtensions {
|
|||||||
*/
|
*/
|
||||||
@JvmName("withEmptyDefaultListImmutable")
|
@JvmName("withEmptyDefaultListImmutable")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun <K, E> SerializerAwareValue<MutableMap<K, List<E>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, List<E>>> {
|
public fun <K, E> SerializerAwareValue<MutableMap<K, List<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, List<E>>> {
|
||||||
return this.withDefault { ArrayList() }
|
return this.withDefault { ArrayList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +91,7 @@ public object PluginDataExtensions {
|
|||||||
*/
|
*/
|
||||||
@JvmName("withEmptyDefaultList")
|
@JvmName("withEmptyDefaultList")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun <K, E> SerializerAwareValue<MutableMap<K, MutableList<E>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, MutableList<E>>> {
|
public fun <K, E> SerializerAwareValue<MutableMap<K, MutableList<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableList<E>>> {
|
||||||
return this.withDefault { ArrayList() }
|
return this.withDefault { ArrayList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +102,7 @@ public object PluginDataExtensions {
|
|||||||
*/
|
*/
|
||||||
@JvmName("withEmptyDefaultSetImmutable")
|
@JvmName("withEmptyDefaultSetImmutable")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun <K, E> SerializerAwareValue<MutableMap<K, Set<E>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, Set<E>>> {
|
public fun <K, E> SerializerAwareValue<MutableMap<K, Set<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, Set<E>>> {
|
||||||
return this.withDefault { LinkedHashSet() }
|
return this.withDefault { LinkedHashSet() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +112,7 @@ public object PluginDataExtensions {
|
|||||||
*/
|
*/
|
||||||
@JvmName("withEmptyDefaultSet")
|
@JvmName("withEmptyDefaultSet")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun <K, E> SerializerAwareValue<MutableMap<K, MutableSet<E>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, MutableSet<E>>> {
|
public fun <K, E> SerializerAwareValue<MutableMap<K, MutableSet<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableSet<E>>> {
|
||||||
return this.withDefault { LinkedHashSet() }
|
return this.withDefault { LinkedHashSet() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,15 +121,47 @@ public object PluginDataExtensions {
|
|||||||
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
|
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@JvmName("withDefaultMapImmutableNotNull")
|
||||||
|
public fun <K, V : Any> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<NotNullMap<K, V>> {
|
||||||
|
@Suppress("UNCHECKED_CAST") // magic
|
||||||
|
return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<NotNullMap<K, V>>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@LowPriorityInOverloadResolution
|
||||||
@JvmName("withDefaultMapImmutable")
|
@JvmName("withDefaultMapImmutable")
|
||||||
public fun <K, V> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<Map<K, V>> {
|
public fun <K, V> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<Map<K, V>> {
|
||||||
@Suppress("UNCHECKED_CAST") // magic
|
@Suppress("UNCHECKED_CAST") // magic
|
||||||
return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<Map<K, V>>
|
return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<Map<K, V>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmName("withDefaultMapNotNull")
|
||||||
|
public fun <K, V : Any> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<NotNullMutableMap<K, V>> {
|
||||||
|
val origin = this
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return SerializableValue(
|
||||||
|
object : CompositeMapValue<K, V> {
|
||||||
|
private val instance = NotNullMutableMap(createDelegateInstance(origin, defaultValueComputer))
|
||||||
|
|
||||||
|
override var value: Map<K, V>
|
||||||
|
get() = instance
|
||||||
|
set(value) {
|
||||||
|
origin.value = value as MutableMap<K, V> // erased cast
|
||||||
|
}
|
||||||
|
} as Value<NotNullMutableMap<K, V>>, // erased cast
|
||||||
|
this.serializer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
|
* 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值
|
||||||
*/
|
*/
|
||||||
|
@LowPriorityInOverloadResolution
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmName("withDefaultMap")
|
@JvmName("withDefaultMap")
|
||||||
public fun <K, V> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<MutableMap<K, V>> {
|
public fun <K, V> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<MutableMap<K, V>> {
|
||||||
@ -95,7 +170,22 @@ public object PluginDataExtensions {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return SerializableValue(
|
return SerializableValue(
|
||||||
object : CompositeMapValue<K, V> {
|
object : CompositeMapValue<K, V> {
|
||||||
private val instance = object : MutableMap<K, V>, AbstractMap<K, V>() {
|
private val instance = createDelegateInstance(origin, defaultValueComputer)
|
||||||
|
override var value: Map<K, V>
|
||||||
|
get() = instance
|
||||||
|
set(value) {
|
||||||
|
origin.value = value as MutableMap<K, V> // erased cast
|
||||||
|
}
|
||||||
|
} as Value<MutableMap<K, V>>, // erased cast
|
||||||
|
this.serializer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <K, V> createDelegateInstance(
|
||||||
|
origin: SerializerAwareValue<MutableMap<K, V>>,
|
||||||
|
defaultValueComputer: (K) -> V,
|
||||||
|
): MutableMap<K, V> {
|
||||||
|
return object : MutableMap<K, V>, AbstractMap<K, V>() {
|
||||||
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = origin.value.entries
|
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = origin.value.entries
|
||||||
override val keys: MutableSet<K> get() = origin.value.keys
|
override val keys: MutableSet<K> get() = origin.value.keys
|
||||||
override val values: MutableCollection<V> get() = origin.value.values
|
override val values: MutableCollection<V> get() = origin.value.values
|
||||||
@ -112,18 +202,37 @@ public object PluginDataExtensions {
|
|||||||
return origin.value[key]
|
return origin.value[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var value: Map<K, V>
|
|
||||||
|
/**
|
||||||
|
* 替换 [MutableMap] 的 key
|
||||||
|
*/
|
||||||
|
@JvmName("mapKeysNotNull")
|
||||||
|
@JvmStatic
|
||||||
|
public fun <OldK, NewK, V : Any> SerializerAwareValue<NotNullMutableMap<OldK, V>>.mapKeys(
|
||||||
|
oldToNew: (OldK) -> NewK,
|
||||||
|
newToOld: (NewK) -> OldK,
|
||||||
|
): SerializerAwareValue<NotNullMutableMap<NewK, V>> {
|
||||||
|
val origin = this
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return SerializableValue(
|
||||||
|
object : CompositeMapValue<NewK, V> {
|
||||||
|
private val instance =
|
||||||
|
NotNullMutableMap(ShadowMap({ origin.value }, oldToNew, newToOld, { it }, { it }))
|
||||||
|
|
||||||
|
override var value: Map<NewK, V>
|
||||||
get() = instance
|
get() = instance
|
||||||
set(value) {
|
set(value) {
|
||||||
origin.value = value as MutableMap<K, V> // erased cast
|
origin.value =
|
||||||
|
value.mapKeysTo(NotNullMutableMap(LinkedHashMap())) { it.key.let(newToOld) } // erased cast
|
||||||
}
|
}
|
||||||
} as Value<MutableMap<K, V>>, // erased cast
|
} as Value<NotNullMutableMap<NewK, V>>, // erased cast
|
||||||
this.serializer
|
this.serializer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 替换 [MutableMap] 的 key
|
* 替换 [MutableMap] 的 key
|
||||||
*/
|
*/
|
||||||
@ -177,4 +286,72 @@ public object PluginDataExtensions {
|
|||||||
this.serializer
|
this.serializer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换 [Map] 的 key
|
||||||
|
*/
|
||||||
|
@JvmName("mapKeysImmutableNotNull")
|
||||||
|
@JvmStatic
|
||||||
|
public fun <OldK, NewK, V : Any> SerializerAwareValue<NotNullMap<OldK, V>>.mapKeys(
|
||||||
|
oldToNew: (OldK) -> NewK,
|
||||||
|
newToOld: (NewK) -> OldK,
|
||||||
|
): SerializerAwareValue<NotNullMap<NewK, V>> {
|
||||||
|
val origin = this
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return SerializableValue(
|
||||||
|
object : CompositeMapValue<NewK, V> {
|
||||||
|
// casting Map to MutableMap is OK here, as we don't call mutable functions
|
||||||
|
private val instance =
|
||||||
|
NotNullMap(ShadowMap({ origin.value as MutableMap<OldK, V> }, oldToNew, newToOld, { it }, { it }))
|
||||||
|
|
||||||
|
override var value: Map<NewK, V>
|
||||||
|
get() = instance
|
||||||
|
set(value) {
|
||||||
|
origin.value =
|
||||||
|
value.mapKeysTo(NotNullMutableMap(LinkedHashMap())) { it.key.let(newToOld) } // erased cast
|
||||||
|
}
|
||||||
|
} as Value<NotNullMap<NewK, V>>, // erased cast
|
||||||
|
this.serializer
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,6 +58,26 @@ public class SerializableValue<T>(
|
|||||||
) : Value<T> by delegate, SerializerAwareValue<T> {
|
) : Value<T> by delegate, SerializerAwareValue<T> {
|
||||||
public override fun toString(): String = delegate.toString()
|
public override fun toString(): String = delegate.toString()
|
||||||
|
|
||||||
|
public override fun equals(other: Any?): Boolean {
|
||||||
|
if (other === this) return true
|
||||||
|
if (other?.javaClass != this.javaClass) return false
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
other as SerializableValue<T>
|
||||||
|
if (other.delegate != this.delegate) return false
|
||||||
|
// if (other.serializer != this.serializer) return false
|
||||||
|
// TODO: 2020/9/9 serializers should be checked here, but it will cause incomparable issue when putting a SerializableValue as a Key
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
@Suppress("UnnecessaryVariable", "CanBeVal")
|
||||||
|
var result = delegate.hashCode()
|
||||||
|
// result = 31 * result + serializer.hashCode()
|
||||||
|
// TODO: 2020/9/9 serializers should be checked here, but it will cause incomparable issue when putting a SerializableValue as a Key
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmName("create")
|
@JvmName("create")
|
||||||
|
@ -13,9 +13,15 @@ import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
|||||||
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示一个扩展.
|
||||||
|
*/
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public interface Extension
|
public interface Extension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加一些函数 (方法)的扩展
|
||||||
|
*/
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public interface FunctionExtension : Extension
|
public interface FunctionExtension : Extension
|
||||||
|
|
||||||
|
@ -11,39 +11,77 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.extension
|
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.internal.data.kClassQualifiedNameOrTip
|
||||||
import net.mamoe.mirai.console.plugin.Plugin
|
import net.mamoe.mirai.console.plugin.Plugin
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.name
|
import net.mamoe.mirai.console.plugin.name
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.CopyOnWriteArraySet
|
import java.util.concurrent.CopyOnWriteArraySet
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
import kotlin.internal.LowPriorityInOverloadResolution
|
import kotlin.internal.LowPriorityInOverloadResolution
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
|
@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
|
||||||
|
public inline fun <reified T : Extension> ExtensionPoint<*>.isFor(exactType: Boolean = false): Boolean {
|
||||||
|
return if (exactType) {
|
||||||
|
T::class == type
|
||||||
|
} else T::class.isSubclassOf(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
@ConsoleExperimentalAPI
|
||||||
public open class AbstractExtensionPoint<T : Extension>(
|
public open class AbstractExtensionPoint<T : Extension>(
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public val type: KClass<T>
|
public override val type: KClass<T>
|
||||||
) {
|
) : ExtensionPoint<T> {
|
||||||
|
init {
|
||||||
@ConsoleExperimentalAPI
|
@Suppress("LeakingThis")
|
||||||
public data class ExtensionRegistry<T>(
|
allExtensionPoints.add(this)
|
||||||
public val plugin: Plugin,
|
}
|
||||||
public val extension: T
|
|
||||||
)
|
|
||||||
|
|
||||||
private val instances: MutableSet<ExtensionRegistry<T>> = CopyOnWriteArraySet()
|
private val instances: MutableSet<ExtensionRegistry<T>> = CopyOnWriteArraySet()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public fun registerExtension(plugin: Plugin, extension: T) {
|
public override fun registerExtension(plugin: Plugin, extension: T) {
|
||||||
require(plugin.isEnabled) { "Plugin $plugin must be enabled before registering an extension." }
|
// 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))
|
instances.add(ExtensionRegistry(plugin, extension))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
public override fun getExtensions(): Set<ExtensionRegistry<T>> = Collections.unmodifiableSet(instances)
|
||||||
internal fun getExtensions(): Set<ExtensionRegistry<T>> = instances
|
|
||||||
|
internal companion object {
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
|
internal val allExtensionPoints: MutableList<AbstractExtensionPoint<*>> = mutableListOf()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -105,7 +143,7 @@ internal fun <T : Extension> AbstractExtensionPoint<out T>.throwExtensionExcepti
|
|||||||
throwable: Throwable
|
throwable: Throwable
|
||||||
) {
|
) {
|
||||||
throw ExtensionException(
|
throw ExtensionException(
|
||||||
"Exception while executing extension ${extension.kClassQualifiedNameOrTip} from ${plugin.name}, registered for ${this.type.qualifiedName}",
|
"Exception while executing extension ${extension.kClassQualifiedNameOrTip} provided by plugin '${plugin.name}', registered for ${this.type.qualifiedName}",
|
||||||
throwable
|
throwable
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
)
|
@ -5,7 +5,6 @@ package net.mamoe.mirai.console.extensions
|
|||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||||
import net.mamoe.mirai.console.extension.FunctionExtension
|
import net.mamoe.mirai.console.extension.FunctionExtension
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
|
||||||
import net.mamoe.mirai.utils.BotConfiguration
|
import net.mamoe.mirai.utils.BotConfiguration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,7 +12,6 @@ import net.mamoe.mirai.utils.BotConfiguration
|
|||||||
*
|
*
|
||||||
* @see MiraiConsole.addBot
|
* @see MiraiConsole.addBot
|
||||||
*/
|
*/
|
||||||
@ConsoleExperimentalAPI
|
|
||||||
public interface BotConfigurationAlterer : FunctionExtension {
|
public interface BotConfigurationAlterer : FunctionExtension {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,11 +2,19 @@ package net.mamoe.mirai.console.extensions
|
|||||||
|
|
||||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||||
import net.mamoe.mirai.console.extension.SingletonExtension
|
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.permission.PermissionService
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import net.mamoe.mirai.console.plugin.description.PluginKind
|
||||||
|
|
||||||
@ConsoleExperimentalAPI
|
/**
|
||||||
public interface PermissionServiceProvider : SingletonExtension<PermissionService> {
|
* [权限服务][PermissionService] 提供器.
|
||||||
|
*
|
||||||
|
* 此扩展可由 [PluginKind.LOADER] 和 [PluginKind.HIGH_PRIORITY_EXTENSIONS] 插件提供
|
||||||
|
*/
|
||||||
|
@ExperimentalPermission
|
||||||
|
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
|
||||||
public companion object ExtensionPoint :
|
public companion object ExtensionPoint :
|
||||||
AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class)
|
AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class),
|
||||||
|
SingletonExtensionPoint<PermissionServiceProvider>
|
||||||
}
|
}
|
@ -3,9 +3,12 @@ package net.mamoe.mirai.console.extensions
|
|||||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
|
import net.mamoe.mirai.console.plugin.description.PluginKind
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提供扩展 [PluginLoader]
|
* 提供扩展 [PluginLoader]
|
||||||
|
*
|
||||||
|
* 此扩展可由 [PluginKind.LOADER] 插件提供
|
||||||
*/
|
*/
|
||||||
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
|
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
|
||||||
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)
|
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.extensions
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||||
|
import net.mamoe.mirai.console.extension.FunctionExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化.
|
||||||
|
*
|
||||||
|
* 这些扩展只会, 且一定会被调用正好一次.
|
||||||
|
*
|
||||||
|
* 此扩展可由所有插件提供
|
||||||
|
*/
|
||||||
|
public fun interface PostStartupExtension : FunctionExtension {
|
||||||
|
/**
|
||||||
|
* 将在 Console 主线程执行.
|
||||||
|
*/
|
||||||
|
public operator fun invoke()
|
||||||
|
|
||||||
|
public companion object ExtensionPoint : AbstractExtensionPoint<PostStartupExtension>(PostStartupExtension::class)
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.extensions
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
|
import net.mamoe.mirai.console.extension.*
|
||||||
|
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
|
||||||
|
import net.mamoe.mirai.console.plugin.description.PluginKind
|
||||||
|
import net.mamoe.mirai.console.plugin.name
|
||||||
|
import net.mamoe.mirai.utils.info
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于同时拥有多个 [SingletonExtension] 时选择一个实例.
|
||||||
|
*
|
||||||
|
* 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器.
|
||||||
|
*
|
||||||
|
* 此扩展可由 [PluginKind.LOADER] 和 [PluginKind.HIGH_PRIORITY_EXTENSIONS] 插件提供
|
||||||
|
*/
|
||||||
|
public interface SingletonExtensionSelector : FunctionExtension {
|
||||||
|
|
||||||
|
public fun <T : Extension> selectSingleton(
|
||||||
|
extensionType: KClass<T>,
|
||||||
|
candidates: Collection<ExtensionRegistry<T>>
|
||||||
|
): T?
|
||||||
|
|
||||||
|
public companion object ExtensionPoint :
|
||||||
|
AbstractExtensionPoint<SingletonExtensionSelector>(SingletonExtensionSelector::class) {
|
||||||
|
internal val instance: SingletonExtensionSelector by lazy {
|
||||||
|
val instances = SingletonExtensionSelector.getExtensions()
|
||||||
|
when {
|
||||||
|
instances.isEmpty() -> BuiltInSingletonExtensionSelector
|
||||||
|
instances.size == 1 -> {
|
||||||
|
instances.single().also { (plugin, ext) ->
|
||||||
|
MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin.name}" }
|
||||||
|
}.extension
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p.name}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun <T : Extension> selectSingleton(
|
||||||
|
extensionType: KClass<T>,
|
||||||
|
candidates: Collection<ExtensionRegistry<T>>
|
||||||
|
): T? =
|
||||||
|
instance.selectSingleton(extensionType, candidates)
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,9 @@ import com.vdurmont.semver4j.Semver
|
|||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
|
import net.mamoe.mirai.alsoLogin
|
||||||
import net.mamoe.mirai.console.MalformedMiraiConsoleImplementationError
|
import net.mamoe.mirai.console.MalformedMiraiConsoleImplementationError
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
|
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
|
||||||
@ -23,12 +25,20 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation
|
|||||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||||
import net.mamoe.mirai.console.command.CommandManager
|
import net.mamoe.mirai.console.command.CommandManager
|
||||||
|
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||||
|
import net.mamoe.mirai.console.extension.useExtensions
|
||||||
|
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.command.CommandManagerImpl
|
||||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
|
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.builtins.ConsoleDataScope
|
||||||
import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter
|
|
||||||
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
||||||
|
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.PluginLoader
|
||||||
import net.mamoe.mirai.console.plugin.PluginManager
|
import net.mamoe.mirai.console.plugin.PluginManager
|
||||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||||
@ -39,6 +49,8 @@ import java.nio.file.Path
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +58,7 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
*/
|
*/
|
||||||
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
|
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
|
||||||
MiraiConsole {
|
MiraiConsole {
|
||||||
override val pluginCenter: PluginCenter get() = CuiPluginCenter
|
override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet")
|
||||||
|
|
||||||
private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
|
private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
|
||||||
override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
|
override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
|
||||||
@ -76,14 +88,19 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
|||||||
|
|
||||||
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
|
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
|
||||||
|
|
||||||
@OptIn(ConsoleExperimentalAPI::class)
|
@OptIn(ConsoleExperimentalAPI::class, ExperimentalPermission::class)
|
||||||
|
@Suppress("RemoveRedundantBackticks")
|
||||||
internal fun doStart() {
|
internal fun doStart() {
|
||||||
|
phase `greeting`@{
|
||||||
val buildDateFormatted =
|
val buildDateFormatted =
|
||||||
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||||
|
|
||||||
mainLogger.info { "Starting mirai-console..." }
|
mainLogger.info { "Starting mirai-console..." }
|
||||||
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
|
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
|
||||||
mainLogger.info { frontEndDescription.render() }
|
mainLogger.info { frontEndDescription.render() }
|
||||||
|
}
|
||||||
|
|
||||||
|
phase `check coroutineContext`@{
|
||||||
if (coroutineContext[Job] == null) {
|
if (coroutineContext[Job] == null) {
|
||||||
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
|
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
|
||||||
}
|
}
|
||||||
@ -94,31 +111,93 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
|||||||
MiraiConsole.job.invokeOnCompletion {
|
MiraiConsole.job.invokeOnCompletion {
|
||||||
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
|
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mainLogger.info { "Reloading configurations..." }
|
ConsoleInput
|
||||||
|
|
||||||
|
// start
|
||||||
|
|
||||||
|
phase `load configurations`@{
|
||||||
|
mainLogger.verbose { "Loading configurations..." }
|
||||||
ConsoleDataScope.reloadAll()
|
ConsoleDataScope.reloadAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
val pluginLoadSession: PluginManagerImpl.PluginLoadSession
|
||||||
|
|
||||||
|
phase `load plugins`@{
|
||||||
|
PluginManager // init
|
||||||
|
|
||||||
|
mainLogger.verbose { "Loading PluginLoader provider plugins..." }
|
||||||
|
PluginManagerImpl.loadEnablePluginProviderPlugins()
|
||||||
|
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
|
||||||
|
|
||||||
|
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." }
|
||||||
|
}
|
||||||
|
|
||||||
|
SingletonExtensionSelector.instance // init
|
||||||
|
|
||||||
|
phase `load PermissionService`@{
|
||||||
|
mainLogger.verbose { "Loading PermissionService..." }
|
||||||
|
PermissionService.INSTANCE.let { ps ->
|
||||||
|
if (ps is BuiltInPermissionService) {
|
||||||
|
ConsoleDataScope.addAndReloadConfig(ps.config)
|
||||||
|
mainLogger.verbose { "Reloaded PermissionService settings." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleCommandSender.grantPermission(RootPermission)
|
||||||
|
}
|
||||||
|
|
||||||
|
phase `prepare commands`@{
|
||||||
|
mainLogger.verbose { "Loading built-in commands..." }
|
||||||
BuiltInCommands.registerAll()
|
BuiltInCommands.registerAll()
|
||||||
mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
|
mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
|
||||||
CommandManager
|
CommandManager
|
||||||
CommandManagerImpl.commandListener // start
|
CommandManagerImpl.commandListener // start
|
||||||
|
}
|
||||||
|
|
||||||
mainLogger.info { "Loading plugins..." }
|
phase `load normal plugins`@{
|
||||||
PluginManager
|
mainLogger.verbose { "Loading normal plugins..." }
|
||||||
PluginManagerImpl.loadEnablePlugins()
|
val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession)
|
||||||
mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." }
|
mainLogger.verbose { "$count normal plugin(s) loaded." }
|
||||||
mainLogger.info { "mirai-console started successfully." }
|
}
|
||||||
|
|
||||||
|
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) loaded." }
|
||||||
|
|
||||||
|
phase `auto-login bots`@{
|
||||||
|
runBlocking {
|
||||||
for ((id, password) in AutoLoginConfig.plainPasswords) {
|
for ((id, password) in AutoLoginConfig.plainPasswords) {
|
||||||
mainLogger.info { "Auto-login $id" }
|
mainLogger.info { "Auto-login $id" }
|
||||||
MiraiConsole.addBot(id, password)
|
MiraiConsole.addBot(id, password).alsoLogin()
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((id, password) in AutoLoginConfig.md5Passwords) {
|
for ((id, password) in AutoLoginConfig.md5Passwords) {
|
||||||
mainLogger.info { "Auto-login $id" }
|
mainLogger.info { "Auto-login $id" }
|
||||||
MiraiConsole.addBot(id, password)
|
MiraiConsole.addBot(id, password).alsoLogin()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only for initialize
|
PostStartupExtension.useExtensions { it() }
|
||||||
|
|
||||||
|
mainLogger.info { "mirai-console started successfully." }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@DslMarker
|
||||||
|
private annotation class MiraiIsCool
|
||||||
|
|
||||||
|
@MiraiIsCool
|
||||||
|
private inline fun phase(block: () -> Unit) {
|
||||||
|
contract {
|
||||||
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
block()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -66,7 +66,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
|||||||
|
|
||||||
when (val result = sender.executeCommand(message)) {
|
when (val result = sender.executeCommand(message)) {
|
||||||
is CommandExecuteResult.PermissionDenied -> {
|
is CommandExecuteResult.PermissionDenied -> {
|
||||||
if (!result.command.prefixOptional) {
|
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
|
||||||
sender.sendMessage("权限不足")
|
sender.sendMessage("权限不足")
|
||||||
intercept()
|
intercept()
|
||||||
}
|
}
|
||||||
|
@ -1,32 +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.internal.command
|
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.CommandPermission
|
|
||||||
import net.mamoe.mirai.console.command.CommandSender
|
|
||||||
import net.mamoe.mirai.console.command.hasPermission
|
|
||||||
|
|
||||||
internal class OrCommandPermissionImpl(
|
|
||||||
private val first: CommandPermission,
|
|
||||||
private val second: CommandPermission
|
|
||||||
) : CommandPermission {
|
|
||||||
override fun CommandSender.hasPermission(): Boolean {
|
|
||||||
return this.hasPermission(first) || this.hasPermission(second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class AndCommandPermissionImpl(
|
|
||||||
private val first: CommandPermission,
|
|
||||||
private val second: CommandPermission
|
|
||||||
) : CommandPermission {
|
|
||||||
override fun CommandSender.hasPermission(): Boolean {
|
|
||||||
return this.hasPermission(first) && this.hasPermission(second)
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,11 +16,19 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
|||||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||||
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
|
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
|
||||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
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.PermissionId
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionService
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
import kotlin.reflect.KAnnotatedElement
|
import kotlin.reflect.KAnnotatedElement
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KFunction
|
import kotlin.reflect.KFunction
|
||||||
import kotlin.reflect.full.*
|
import kotlin.reflect.full.callSuspend
|
||||||
|
import kotlin.reflect.full.declaredFunctions
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
internal object CompositeCommandSubCommandAnnotationResolver :
|
internal object CompositeCommandSubCommandAnnotationResolver :
|
||||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
||||||
@ -40,17 +48,18 @@ internal object SimpleCommandSubCommandAnnotationResolver :
|
|||||||
baseCommand.names
|
baseCommand.names
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
|
internal abstract class AbstractReflectionCommand @OptIn(ExperimentalPermission::class)
|
||||||
|
@JvmOverloads constructor(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
names: Array<out String>,
|
names: Array<out String>,
|
||||||
description: String = "<no description available>",
|
description: String = "<no description available>",
|
||||||
permission: CommandPermission = CommandPermission.Default,
|
parentPermission: PermissionId = owner.basePermission,
|
||||||
prefixOptional: Boolean = false
|
prefixOptional: Boolean = false
|
||||||
) : Command, AbstractCommand(
|
) : Command, AbstractCommand(
|
||||||
owner,
|
owner,
|
||||||
names = names,
|
names = names,
|
||||||
description = description,
|
description = description,
|
||||||
permission = permission,
|
parentPermission = parentPermission,
|
||||||
prefixOptional = prefixOptional
|
prefixOptional = prefixOptional
|
||||||
), CommandArgumentContextAware {
|
), CommandArgumentContextAware {
|
||||||
internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||||
@ -70,7 +79,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
|
|||||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||||
DefaultSubCommandDescriptor(
|
DefaultSubCommandDescriptor(
|
||||||
"",
|
"",
|
||||||
permission,
|
createCommandPermission(parentPermission),
|
||||||
onCommand = { sender: CommandSender, args: MessageChain ->
|
onCommand = { sender: CommandSender, args: MessageChain ->
|
||||||
sender.onDefault(args)
|
sender.onDefault(args)
|
||||||
}
|
}
|
||||||
@ -113,21 +122,23 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultSubCommandDescriptor(
|
internal class DefaultSubCommandDescriptor @OptIn(ExperimentalPermission::class) constructor(
|
||||||
val description: String,
|
val description: String,
|
||||||
val permission: CommandPermission,
|
val permission: Permission,
|
||||||
val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit
|
val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
internal inner class SubCommandDescriptor(
|
internal inner class SubCommandDescriptor @OptIn(ExperimentalPermission::class) constructor(
|
||||||
val names: Array<out String>,
|
val names: Array<out String>,
|
||||||
val params: Array<CommandParameter<*>>,
|
val params: Array<CommandParameter<*>>,
|
||||||
val description: String,
|
val description: String,
|
||||||
val permission: CommandPermission,
|
val permission: Permission,
|
||||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
|
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
|
||||||
val context: CommandArgumentContext
|
val context: CommandArgumentContext
|
||||||
) {
|
) {
|
||||||
val usage: String = createUsage(this@AbstractReflectionCommand)
|
val usage: String = createUsage(this@AbstractReflectionCommand)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
internal suspend fun parseAndExecute(
|
internal suspend fun parseAndExecute(
|
||||||
sender: CommandSender,
|
sender: CommandSender,
|
||||||
argsWithSubCommandNameNotRemoved: MessageChain,
|
argsWithSubCommandNameNotRemoved: MessageChain,
|
||||||
@ -209,10 +220,6 @@ internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
|
|||||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||||
findAnnotation<T>() != null
|
findAnnotation<T>() != null
|
||||||
|
|
||||||
internal inline fun <T : Any> KClass<out T>.getInstance(): T {
|
|
||||||
return this.objectInstance ?: this.createInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
||||||
|
|
||||||
internal fun Array<AbstractReflectionCommand.SubCommandDescriptor>.createUsage(baseCommand: AbstractReflectionCommand): String =
|
internal fun Array<AbstractReflectionCommand.SubCommandDescriptor>.createUsage(baseCommand: AbstractReflectionCommand): String =
|
||||||
@ -227,7 +234,7 @@ internal fun Array<AbstractReflectionCommand.SubCommandDescriptor>.createUsage(b
|
|||||||
|
|
||||||
internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseCommand: AbstractReflectionCommand): String =
|
internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseCommand: AbstractReflectionCommand): String =
|
||||||
buildString {
|
buildString {
|
||||||
if (!baseCommand.prefixOptional) {
|
if (baseCommand.prefixOptional) {
|
||||||
append("(")
|
append("(")
|
||||||
append(CommandManager.commandPrefix)
|
append(CommandManager.commandPrefix)
|
||||||
append(")")
|
append(")")
|
||||||
@ -246,6 +253,7 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm
|
|||||||
appendLine()
|
appendLine()
|
||||||
}.trimEnd()
|
}.trimEnd()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
internal fun AbstractReflectionCommand.createSubCommand(
|
internal fun AbstractReflectionCommand.createSubCommand(
|
||||||
function: KFunction<*>,
|
function: KFunction<*>,
|
||||||
context: CommandArgumentContext
|
context: CommandArgumentContext
|
||||||
@ -322,8 +330,8 @@ internal fun AbstractReflectionCommand.createSubCommand(
|
|||||||
return SubCommandDescriptor(
|
return SubCommandDescriptor(
|
||||||
commandName,
|
commandName,
|
||||||
params,
|
params,
|
||||||
subDescription,
|
subDescription, // overridePermission?.value
|
||||||
overridePermission?.value?.getInstance() ?: permission,
|
overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
||||||
onCommand = { sender: CommandSender, args: Array<out Any> ->
|
onCommand = { sender: CommandSender, args: Array<out Any> ->
|
||||||
val result = if (notStatic) {
|
val result = if (notStatic) {
|
||||||
if (hasSenderParam) {
|
if (hasSenderParam) {
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
package net.mamoe.mirai.console.internal.command
|
package net.mamoe.mirai.console.internal.command
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.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.PermissionId
|
||||||
|
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.Group
|
||||||
import net.mamoe.mirai.contact.Member
|
import net.mamoe.mirai.contact.Member
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
@ -136,12 +142,14 @@ internal fun Group.fuzzySearchMember(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
internal fun Command.createCommandPermission(parent: PermissionId): Permission {
|
||||||
|
return PermissionService.INSTANCE.register(owner.permissionId(primaryName), description, parent)
|
||||||
|
}
|
||||||
|
|
||||||
//// internal
|
//// internal
|
||||||
|
|
||||||
@JvmSynthetic
|
@OptIn(ExperimentalPermission::class)
|
||||||
internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Array(size - n) { this[n + it] }
|
|
||||||
|
|
||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
@Throws(CommandExecutionException::class)
|
@Throws(CommandExecutionException::class)
|
||||||
internal suspend fun CommandSender.executeCommandInternal(
|
internal suspend fun CommandSender.executeCommandInternal(
|
||||||
@ -150,7 +158,7 @@ internal suspend fun CommandSender.executeCommandInternal(
|
|||||||
commandName: String,
|
commandName: String,
|
||||||
checkPermission: Boolean
|
checkPermission: Boolean
|
||||||
): CommandExecuteResult {
|
): CommandExecuteResult {
|
||||||
if (checkPermission && !command.testPermission(this)) {
|
if (checkPermission && !command.permission.testPermission(this)) {
|
||||||
return CommandExecuteResult.PermissionDenied(command, commandName)
|
return CommandExecuteResult.PermissionDenied(command, commandName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import net.mamoe.mirai.console.data.PluginData
|
|||||||
import net.mamoe.mirai.console.data.PluginDataHolder
|
import net.mamoe.mirai.console.data.PluginDataHolder
|
||||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
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.MiraiLogger
|
||||||
import net.mamoe.mirai.utils.SilentLogger
|
import net.mamoe.mirai.utils.SilentLogger
|
||||||
@ -54,7 +55,7 @@ internal open class MultiFilePluginDataStorageImpl(
|
|||||||
}
|
}
|
||||||
dir.mkdir()
|
dir.mkdir()
|
||||||
|
|
||||||
val file = dir.resolve(name)
|
val file = dir.resolve("$name.yml")
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
|
error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
|
||||||
}
|
}
|
||||||
@ -64,7 +65,26 @@ internal open class MultiFilePluginDataStorageImpl(
|
|||||||
|
|
||||||
@ConsoleExperimentalAPI
|
@ConsoleExperimentalAPI
|
||||||
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
||||||
getPluginDataFile(holder, instance).writeText(Yaml.default.encodeToString(instance.updaterSerializer, Unit))
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
val yaml =/* if (instance.saveName == "PermissionService") Json {
|
||||||
|
prettyPrint = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
isLenient = true
|
||||||
|
allowStructuredMapKeys = true
|
||||||
|
} /*Yaml(
|
||||||
|
configuration = YamlConfiguration(
|
||||||
|
mapSerialization = YamlConfiguration.MapSerialization.FLOW_MAP,
|
||||||
|
listSerialization = YamlConfiguration.ListSerialization.FLOW_SEQUENCE,
|
||||||
|
classSerialization = YamlConfiguration.MapSerialization.FLOW_MAP
|
||||||
|
)
|
||||||
|
)*/ else */Yaml.default
|
||||||
|
getPluginDataFile(holder, instance).writeText(
|
||||||
|
kotlin.runCatching {
|
||||||
|
yaml.encodeToString(instance.updaterSerializer, Unit)
|
||||||
|
}.getOrElse {
|
||||||
|
throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it)
|
||||||
|
}
|
||||||
|
)
|
||||||
logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" }
|
logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,4 +196,21 @@ internal class LazyReferenceValueImpl<T> : ReferenceValue<T>, AbstractValueImpl<
|
|||||||
initialied = true
|
initialied = true
|
||||||
valueField = value
|
valueField = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return valueField.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other === this) return true
|
||||||
|
if (other?.javaClass != this.javaClass) return false
|
||||||
|
|
||||||
|
other as LazyReferenceValueImpl<*>
|
||||||
|
if (other.valueField != valueField) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return valueField?.hashCode() ?: 0
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,51 +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("MemberVisibilityCanBePrivate")
|
|
||||||
|
|
||||||
package net.mamoe.mirai.console.internal.data.builtins
|
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
|
||||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
|
||||||
import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys
|
|
||||||
import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault
|
|
||||||
import net.mamoe.mirai.console.data.ValueDescription
|
|
||||||
import net.mamoe.mirai.console.data.value
|
|
||||||
import net.mamoe.mirai.console.util.BotManager
|
|
||||||
import net.mamoe.mirai.contact.User
|
|
||||||
|
|
||||||
internal object BotManagerImpl : BotManager {
|
|
||||||
override val User.isManager: Boolean get() = this.id in ManagersConfig[this.bot]
|
|
||||||
|
|
||||||
override fun Bot.removeManager(id: Long): Boolean {
|
|
||||||
return ManagersConfig[this].remove(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val Bot.managers: List<Long>
|
|
||||||
get() = ManagersConfig[this].toList()
|
|
||||||
|
|
||||||
override fun Bot.addManager(id: Long): Boolean {
|
|
||||||
return ManagersConfig[this].add(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object ManagersConfig : AutoSavePluginConfig() {
|
|
||||||
override val saveName: String
|
|
||||||
get() = "Managers"
|
|
||||||
|
|
||||||
@ValueDescription(
|
|
||||||
"""
|
|
||||||
管理员列表
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
private val managers by value<MutableMap<Long, MutableSet<Long>>>().withEmptyDefault()
|
|
||||||
.mapKeys(Bot::getInstance, Bot::id)
|
|
||||||
|
|
||||||
internal operator fun get(bot: Bot): MutableSet<Long> = managers[bot]!!
|
|
||||||
}
|
|
@ -21,8 +21,13 @@ import net.mamoe.mirai.utils.minutesToMillis
|
|||||||
|
|
||||||
|
|
||||||
internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("ConsoleDataScope") {
|
internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("ConsoleDataScope") {
|
||||||
private val data: Array<out PluginData> = arrayOf()
|
private val data: List<PluginData> = mutableListOf()
|
||||||
private val configs: Array<out PluginConfig> = arrayOf(ManagersConfig, AutoLoginConfig)
|
private val configs: MutableList<PluginConfig> = mutableListOf(AutoLoginConfig)
|
||||||
|
|
||||||
|
fun addAndReloadConfig(config: PluginConfig) {
|
||||||
|
configs.add(config)
|
||||||
|
ConsoleBuiltInPluginConfigStorage.load(ConsoleBuiltInPluginConfigHolder, config)
|
||||||
|
}
|
||||||
|
|
||||||
fun reloadAll() {
|
fun reloadAll() {
|
||||||
data.forEach { dt ->
|
data.forEach { dt ->
|
||||||
@ -37,13 +42,13 @@ internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("Co
|
|||||||
internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
|
internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder,
|
||||||
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginDataHolder") {
|
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginDataHolder") {
|
||||||
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
|
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
|
||||||
override val dataHolderName: String get() = "ConsoleBuiltIns"
|
override val dataHolderName: String get() = "Console"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder,
|
internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder,
|
||||||
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginConfigHolder") {
|
CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginConfigHolder") {
|
||||||
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
|
override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis
|
||||||
override val dataHolderName: String get() = "ConsoleBuiltIns"
|
override val dataHolderName: String get() = "Console"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object ConsoleBuiltInPluginDataStorage :
|
internal object ConsoleBuiltInPluginDataStorage :
|
||||||
|
@ -47,7 +47,7 @@ internal fun serializerMirai(type: KType): KSerializer<Any?> {
|
|||||||
typeArguments.isEmpty() -> rootClass.serializer()
|
typeArguments.isEmpty() -> rootClass.serializer()
|
||||||
else -> {
|
else -> {
|
||||||
val serializers = typeArguments
|
val serializers = typeArguments
|
||||||
.map(::serializer)
|
.map(::serializerMirai)
|
||||||
// Array is not supported, see KT-32839
|
// Array is not supported, see KT-32839
|
||||||
when (rootClass) {
|
when (rootClass) {
|
||||||
List::class, MutableList::class, ArrayList::class -> ListSerializer(serializers[0])
|
List::class, MutableList::class, ArrayList::class -> ListSerializer(serializers[0])
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package net.mamoe.mirai.console.internal.extensions
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
|
import net.mamoe.mirai.console.data.AutoSavePluginData
|
||||||
|
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
|
||||||
|
import net.mamoe.mirai.console.util.ConsoleInput
|
||||||
|
import net.mamoe.mirai.utils.info
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
|
||||||
|
|
||||||
|
private val config: SaveData = SaveData()
|
||||||
|
|
||||||
|
private class SaveData : AutoSavePluginData() {
|
||||||
|
override val saveName: String get() = "ExtensionSelector"
|
||||||
|
|
||||||
|
val value: MutableMap<String, String> by value()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Extension> selectSingleton(
|
||||||
|
extensionType: KClass<T>,
|
||||||
|
candidates: Collection<ExtensionRegistry<T>>
|
||||||
|
): T? = when {
|
||||||
|
candidates.isEmpty() -> null
|
||||||
|
candidates.size == 1 -> candidates.single().extension
|
||||||
|
else -> kotlin.run {
|
||||||
|
val target = config.value[extensionType.qualifiedName!!]
|
||||||
|
?: return promptForSelectionAndSave(extensionType, candidates)
|
||||||
|
|
||||||
|
val found = candidates.firstOrNull { it.extension::class.qualifiedName == target }?.extension
|
||||||
|
?: return promptForSelectionAndSave(extensionType, candidates)
|
||||||
|
|
||||||
|
found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Extension> promptForSelectionAndSave(
|
||||||
|
extensionType: KClass<T>,
|
||||||
|
candidates: Collection<ExtensionRegistry<T>>
|
||||||
|
) = promptForManualSelection(extensionType, candidates)
|
||||||
|
.also { config.value[extensionType.qualifiedName!!] = it.extension.kClassQualifiedName!! }.extension
|
||||||
|
|
||||||
|
private fun <T : Any> promptForManualSelection(
|
||||||
|
extensionType: KClass<T>,
|
||||||
|
candidates: Collection<ExtensionRegistry<T>>
|
||||||
|
): ExtensionRegistry<T> {
|
||||||
|
MiraiConsole.mainLogger.info { "There are multiple ${extensionType.simpleName}s, please select one:" }
|
||||||
|
|
||||||
|
val candidatesList = candidates.toList()
|
||||||
|
|
||||||
|
for ((index, candidate) in candidatesList.withIndex()) {
|
||||||
|
MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin.name}'" }
|
||||||
|
}
|
||||||
|
|
||||||
|
MiraiConsole.mainLogger.info { "Please choose a number from 1 to ${candidatesList.count()}" }
|
||||||
|
|
||||||
|
val choiceStr = runBlocking { ConsoleInput.requestInput("Your choice: ") }
|
||||||
|
|
||||||
|
val choice = choiceStr.toIntOrNull() ?: error("Bad choice")
|
||||||
|
|
||||||
|
return candidatesList[choice - 1]
|
||||||
|
}
|
||||||
|
}
|
@ -1,111 +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:OptIn(ConsoleExperimentalAPI::class)
|
|
||||||
|
|
||||||
package net.mamoe.mirai.console.internal.plugin
|
|
||||||
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.util.*
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
|
||||||
import net.mamoe.mirai.console.util.retryCatching
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
internal val json = Json {
|
|
||||||
isLenient = true
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(KtorExperimentalAPI::class)
|
|
||||||
internal val Http = HttpClient(CIO)
|
|
||||||
|
|
||||||
internal object CuiPluginCenter : PluginCenter {
|
|
||||||
|
|
||||||
var plugins: List<PluginCenter.PluginInsight>? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一页 10 个 pageMinNum=1
|
|
||||||
*/
|
|
||||||
override suspend fun fetchPlugin(page: Int): Map<String, PluginCenter.PluginInsight> {
|
|
||||||
check(page > 0)
|
|
||||||
val startIndex = (page - 1) * 10
|
|
||||||
val endIndex = startIndex + 9
|
|
||||||
val map = mutableMapOf<String, PluginCenter.PluginInsight>()
|
|
||||||
(startIndex until endIndex).forEach { index ->
|
|
||||||
val plugins = plugins ?: kotlin.run {
|
|
||||||
refresh()
|
|
||||||
plugins
|
|
||||||
} ?: return mapOf()
|
|
||||||
|
|
||||||
if (index >= plugins.size) {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
map[name] = plugins[index]
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findPlugin(name: String): PluginCenter.PluginInfo? {
|
|
||||||
val result = retryCatching(3) {
|
|
||||||
Http.get<String>("https://miraiapi.jasonczc.cn/getPluginDetailedInfo?name=$name")
|
|
||||||
}.getOrElse { return null }
|
|
||||||
if (result == "err:not found") return null
|
|
||||||
|
|
||||||
return json.decodeFromString(PluginCenter.PluginInfo.serializer(), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun refresh() {
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Result(
|
|
||||||
val success: Boolean,
|
|
||||||
val result: List<PluginCenter.PluginInsight>
|
|
||||||
)
|
|
||||||
|
|
||||||
val result = json.decodeFromString(Result.serializer(), Http.get("https://miraiapi.jasonczc.cn/getPluginList"))
|
|
||||||
|
|
||||||
check(result.success) { "Failed to fetch plugin list from Cui Cloud" }
|
|
||||||
plugins = result.result
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <T : Any> T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File {
|
|
||||||
TODO()
|
|
||||||
/*
|
|
||||||
val info = findPlugin(name) ?: error("Plugin Not Found")
|
|
||||||
val targetFile = File(PluginManager.pluginsPath, "$name-" + info.version + ".jar")
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
tryNTimes {
|
|
||||||
val con =
|
|
||||||
URL("https://pan.jasonczc.cn/?/mirai/plugins/$name/$name-" + info.version + ".mp4").openConnection() as HttpURLConnection
|
|
||||||
val input = con.inputStream
|
|
||||||
val size = con.contentLength
|
|
||||||
var totalDownload = 0F
|
|
||||||
val outputStream = FileOutputStream(targetFile)
|
|
||||||
var len: Int
|
|
||||||
val buff = ByteArray(1024)
|
|
||||||
while (input.read(buff).also { len = it } != -1) {
|
|
||||||
totalDownload += len
|
|
||||||
outputStream.write(buff, 0, len)
|
|
||||||
progressListener.invoke(this@downloadPlugin, totalDownload / size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return targetFile
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
override val name: String get() = "崔云"
|
|
||||||
}
|
|
||||||
|
|
@ -22,7 +22,6 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
|
|||||||
import net.mamoe.mirai.console.plugin.jvm.*
|
import net.mamoe.mirai.console.plugin.jvm.*
|
||||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import net.mamoe.mirai.utils.info
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@ -74,8 +73,8 @@ internal object JarPluginLoaderImpl :
|
|||||||
JvmPluginClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader, classLoaders)
|
JvmPluginClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader, classLoaders)
|
||||||
}.onEach { (_, classLoader) ->
|
}.onEach { (_, classLoader) ->
|
||||||
classLoaders.add(classLoader)
|
classLoaders.add(classLoader)
|
||||||
}.asSequence().findAllInstances().onEach { loaded ->
|
}.asSequence().findAllInstances().onEach {
|
||||||
logger.info { "Successfully initialized JvmPlugin ${loaded}." }
|
//logger.verbose { "Successfully initialized JvmPlugin ${loaded}." }
|
||||||
}.onEach { (file, plugin) ->
|
}.onEach { (file, plugin) ->
|
||||||
pluginFileToInstanceMap[file] = plugin
|
pluginFileToInstanceMap[file] = plugin
|
||||||
} + pluginFileToInstanceMap.asSequence()
|
} + pluginFileToInstanceMap.asSequence()
|
||||||
@ -98,9 +97,13 @@ internal object JarPluginLoaderImpl :
|
|||||||
override fun enable(plugin: JvmPlugin) {
|
override fun enable(plugin: JvmPlugin) {
|
||||||
if (plugin.isEnabled) return
|
if (plugin.isEnabled) return
|
||||||
ensureActive()
|
ensureActive()
|
||||||
|
runCatching {
|
||||||
if (plugin is JvmPluginInternal) {
|
if (plugin is JvmPluginInternal) {
|
||||||
plugin.internalOnEnable()
|
plugin.internalOnEnable()
|
||||||
} else plugin.onEnable()
|
} else plugin.onEnable()
|
||||||
|
}.getOrElse {
|
||||||
|
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disable(plugin: JvmPlugin) {
|
override fun disable(plugin: JvmPlugin) {
|
||||||
|
@ -15,11 +15,16 @@ import kotlinx.coroutines.*
|
|||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.data.runCatchingLog
|
import net.mamoe.mirai.console.data.runCatchingLog
|
||||||
import net.mamoe.mirai.console.internal.data.mkdir
|
import net.mamoe.mirai.console.internal.data.mkdir
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
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.Plugin
|
||||||
import net.mamoe.mirai.console.plugin.PluginManager
|
import net.mamoe.mirai.console.plugin.PluginManager
|
||||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
||||||
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
|
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
|
||||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||||
|
import net.mamoe.mirai.console.plugin.name
|
||||||
import net.mamoe.mirai.console.util.NamedSupervisorJob
|
import net.mamoe.mirai.console.util.NamedSupervisorJob
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -38,10 +43,24 @@ internal abstract class JvmPluginInternal(
|
|||||||
parentCoroutineContext: CoroutineContext
|
parentCoroutineContext: CoroutineContext
|
||||||
) : JvmPlugin, CoroutineScope {
|
) : JvmPlugin, CoroutineScope {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
final override val basePermission: PermissionId by lazy {
|
||||||
|
PermissionService.INSTANCE.register(
|
||||||
|
PermissionService.INSTANCE.allocatePermissionIdForPlugin(name, "*"),
|
||||||
|
"The base permission"
|
||||||
|
).id
|
||||||
|
}
|
||||||
|
|
||||||
final override var isEnabled: Boolean = false
|
final override var isEnabled: Boolean = false
|
||||||
|
|
||||||
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
|
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
|
||||||
override fun getResourceAsStream(path: String): InputStream? = resourceContainerDelegate.getResourceAsStream(path)
|
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
|
// region JvmPlugin
|
||||||
final override val logger: MiraiLogger by lazy {
|
final override val logger: MiraiLogger by lazy {
|
||||||
@ -62,11 +81,11 @@ internal abstract class JvmPluginInternal(
|
|||||||
dataFolderPath.toFile()
|
dataFolderPath.toFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val configFolderPath: Path by lazy {
|
final override val configFolderPath: Path by lazy {
|
||||||
PluginManager.pluginsConfigPath.resolve(description.name).apply { mkdir() }
|
PluginManager.pluginsConfigPath.resolve(description.name).apply { mkdir() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val configFolder: File by lazy {
|
final override val configFolder: File by lazy {
|
||||||
configFolderPath.toFile()
|
configFolderPath.toFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +110,7 @@ internal abstract class JvmPluginInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun internalOnEnable(): Boolean {
|
internal fun internalOnEnable(): Boolean {
|
||||||
|
basePermission
|
||||||
if (!firstRun) refreshCoroutineContext()
|
if (!firstRun) refreshCoroutineContext()
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
onEnable()
|
onEnable()
|
||||||
@ -116,6 +136,7 @@ internal abstract class JvmPluginInternal(
|
|||||||
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
|
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
|
||||||
CoroutineName("Plugin $dataHolderName")
|
CoroutineName("Plugin $dataHolderName")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
internal val coroutineContextInitializer = {
|
internal val coroutineContextInitializer = {
|
||||||
CoroutineExceptionHandler { context, throwable ->
|
CoroutineExceptionHandler { context, throwable ->
|
||||||
|
@ -113,64 +113,84 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
internal class PluginLoadSession(
|
||||||
* STEPS:
|
val allKindsOfPlugins: List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>>
|
||||||
* 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件
|
)
|
||||||
* 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件
|
|
||||||
* 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表.
|
// Phase #2
|
||||||
* 4. 解决依赖并排序
|
internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession {
|
||||||
* 5. 依次 [PluginLoader.load]
|
return PluginLoadSession(loadersLock.withLock { _pluginLoaders.listAllPlugins() })
|
||||||
* 但不 [PluginLoader.enable]
|
}
|
||||||
*
|
|
||||||
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
|
// Phase #0
|
||||||
*/
|
internal fun loadEnablePluginProviderPlugins() {
|
||||||
@Suppress("UNCHECKED_CAST")
|
loadAndEnableLoaderProvidersUsingBuiltInLoaders()
|
||||||
@Throws(PluginMissingDependencyException::class)
|
}
|
||||||
internal fun loadEnablePlugins() {
|
|
||||||
loadAndEnableLoaderProviders()
|
// Phase #3
|
||||||
loadPluginLoaderProvidedByPlugins()
|
internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int {
|
||||||
loadersLock.withLock {
|
loadersLock.withLock {
|
||||||
_pluginLoaders.listAllPlugins().flatMap { it.second }
|
session.allKindsOfPlugins.flatMap { it.second }
|
||||||
.also {
|
.filter { it.kind == PluginKind.HIGH_PRIORITY_EXTENSIONS }
|
||||||
logger.debug("All plugins: ${it.joinToString { (_, desc, _) -> desc.name }}")
|
|
||||||
}
|
|
||||||
.sortByDependencies()
|
.sortByDependencies()
|
||||||
.also {
|
.also { it.loadAndEnableAllInOrder() }
|
||||||
logger.debug("Sorted plugins: ${it.joinToString { (_, desc, _) -> desc.name }}")
|
.let { return it.size }
|
||||||
}
|
|
||||||
.loadAndEnableAllInOrder()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadPluginLoaderProvidedByPlugins() {
|
// Phase #4
|
||||||
|
internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int {
|
||||||
loadersLock.withLock {
|
loadersLock.withLock {
|
||||||
PluginLoaderProvider.useExtensions {
|
session.allKindsOfPlugins.flatMap { it.second }
|
||||||
logger.info { "Loaded PluginLoader ${it.instance} from $" }
|
.filter { it.kind == PluginKind.NORMAL }
|
||||||
_pluginLoaders.add(it.instance)
|
.sortByDependencies()
|
||||||
|
.also { it.loadAndEnableAllInOrder() }
|
||||||
|
.let { return it.size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase #1
|
||||||
|
internal fun loadPluginLoaderProvidedByPlugins() {
|
||||||
|
loadersLock.withLock {
|
||||||
|
PluginLoaderProvider.useExtensions { ext, plugin ->
|
||||||
|
logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" }
|
||||||
|
_pluginLoaders.add(ext.instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
|
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
|
||||||
return this.forEach { (loader, _, plugin) ->
|
this.forEach { (loader, _, plugin) ->
|
||||||
loader.loadPluginNoEnable(plugin)
|
loader.loadPluginNoEnable(plugin)
|
||||||
|
}
|
||||||
|
this.forEach { (loader, _, plugin) ->
|
||||||
loader.enablePlugin(plugin)
|
loader.enablePlugin(plugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlin.jvm.Throws(PluginLoadException::class)
|
||||||
|
internal fun checkPluginDescription(description: PluginDescription) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
PluginDescription.checkPluginDescription(description)
|
||||||
|
}.getOrElse {
|
||||||
|
throw PluginLoadException("PluginDescription check failed.", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
|
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@Throws(PluginMissingDependencyException::class)
|
@Throws(PluginMissingDependencyException::class)
|
||||||
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
|
private fun loadAndEnableLoaderProvidersUsingBuiltInLoaders(): List<PluginDescriptionWithLoader> {
|
||||||
val allDescriptions =
|
val allDescriptions =
|
||||||
builtInLoaders.listAllPlugins()
|
builtInLoaders.listAllPlugins()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.onEach { (loader, descriptions) ->
|
.onEach { (loader, descriptions) ->
|
||||||
loader as PluginLoader<Plugin, PluginDescription>
|
loader as PluginLoader<Plugin, PluginDescription>
|
||||||
|
|
||||||
|
descriptions.forEach(PluginManagerImpl::checkPluginDescription)
|
||||||
descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies().loadAndEnableAllInOrder()
|
descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies().loadAndEnableAllInOrder()
|
||||||
}
|
}
|
||||||
.flatMap { it.second.asSequence() }
|
.flatMap { it.second.asSequence() }
|
||||||
@ -196,7 +216,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
|||||||
return cannotBeLoad
|
return cannotBeLoad
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<PluginDependency>.filterIsMissing(): List<PluginDependency> =
|
fun Collection<PluginDependency>.filterIsMissing(): List<PluginDependency> =
|
||||||
this.filterNot { it.isOptional || it in resolved }
|
this.filterNot { it.isOptional || it in resolved }
|
||||||
|
|
||||||
tailrec fun List<D>.doSort() {
|
tailrec fun List<D>.doSort() {
|
||||||
@ -239,4 +259,4 @@ internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>, plugin: Plug
|
|||||||
)
|
)
|
||||||
|
|
||||||
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
|
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
|
||||||
any { it.name == dependency.name }
|
any { it.id == dependency.id }
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grantedWith
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ExperimentalPermission
|
||||||
|
public abstract class AbstractConcurrentPermissionService<P : Permission> : PermissionService<P> {
|
||||||
|
protected abstract val permissions: MutableMap<PermissionId, P>
|
||||||
|
protected abstract val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||||
|
|
||||||
|
protected abstract fun createPermission(
|
||||||
|
id: PermissionId,
|
||||||
|
description: String,
|
||||||
|
base: PermissionId = RootPermission.id
|
||||||
|
): P
|
||||||
|
|
||||||
|
override fun get(id: PermissionId): P? = permissions[id]
|
||||||
|
|
||||||
|
override fun register(id: PermissionId, description: String, base: PermissionId): P {
|
||||||
|
grantedPermissionsMap[id] = CopyOnWriteArrayList() // mutations are not quite often performed
|
||||||
|
val instance = createPermission(id, description, base)
|
||||||
|
val old = permissions.putIfAbsent(id, instance)
|
||||||
|
if (old != null) throw DuplicatedPermissionRegistrationException(instance, old)
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P) {
|
||||||
|
val id = permission.id
|
||||||
|
grantedPermissionsMap[id]?.add(permissibleIdentifier)
|
||||||
|
?: error("Bad PermissionService implementation: grantedPermissionsMap[id] is null.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) {
|
||||||
|
grantedPermissionsMap[permission.id]?.remove(permissibleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRegisteredPermissions(): Sequence<P> = permissions.values.asSequence()
|
||||||
|
public override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P> = sequence<P> {
|
||||||
|
for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) {
|
||||||
|
|
||||||
|
val granted =
|
||||||
|
if (permissibleIdentifiers.isEmpty()) false
|
||||||
|
else permissibleIdentifiers.any { permissibleIdentifier.grantedWith(it) }
|
||||||
|
|
||||||
|
if (granted) get(permissionIdentifier)?.let { yield(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||||
|
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
|
||||||
|
import net.mamoe.mirai.console.data.value
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.createType
|
||||||
|
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public object AllGrantPermissionService : PermissionService<PermissionImpl> {
|
||||||
|
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
|
||||||
|
override val permissionType: KClass<PermissionImpl>
|
||||||
|
get() = PermissionImpl::class
|
||||||
|
|
||||||
|
override fun register(
|
||||||
|
id: PermissionId,
|
||||||
|
description: String,
|
||||||
|
base: PermissionId
|
||||||
|
): PermissionImpl {
|
||||||
|
val new = PermissionImpl(id, description, base)
|
||||||
|
val old = all.putIfAbsent(id, new)
|
||||||
|
if (old != null) throw DuplicatedPermissionRegistrationException(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> =
|
||||||
|
all.values.asSequence()
|
||||||
|
|
||||||
|
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean =
|
||||||
|
true
|
||||||
|
|
||||||
|
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public object AllDenyPermissionService : PermissionService<PermissionImpl> {
|
||||||
|
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
|
||||||
|
override val permissionType: KClass<PermissionImpl>
|
||||||
|
get() = PermissionImpl::class
|
||||||
|
|
||||||
|
override fun register(
|
||||||
|
id: PermissionId,
|
||||||
|
description: String,
|
||||||
|
base: PermissionId
|
||||||
|
): PermissionImpl {
|
||||||
|
val new = PermissionImpl(id, description, base)
|
||||||
|
val old = all.putIfAbsent(id, new)
|
||||||
|
if (old != null) throw DuplicatedPermissionRegistrationException(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> =
|
||||||
|
emptySequence()
|
||||||
|
|
||||||
|
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean =
|
||||||
|
false
|
||||||
|
|
||||||
|
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
internal object BuiltInPermissionService : AbstractConcurrentPermissionService<PermissionImpl>(),
|
||||||
|
PermissionService<PermissionImpl> {
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
override val permissionType: KClass<PermissionImpl>
|
||||||
|
get() = PermissionImpl::class
|
||||||
|
override val permissions: MutableMap<PermissionId, PermissionImpl> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||||
|
get() = config.grantedPermissionMap as MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||||
|
|
||||||
|
override fun createPermission(id: PermissionId, description: String, base: PermissionId): PermissionImpl =
|
||||||
|
PermissionImpl(id, description, base)
|
||||||
|
|
||||||
|
internal val config: ConcurrentSaveData<PermissionImpl> =
|
||||||
|
ConcurrentSaveData(
|
||||||
|
PermissionImpl::class.createType(),
|
||||||
|
"PermissionService",
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("RedundantVisibilityModifier")
|
||||||
|
@ExperimentalPermission
|
||||||
|
internal class ConcurrentSaveData<P : Permission> private constructor(
|
||||||
|
permissionType: KType,
|
||||||
|
public override val saveName: String,
|
||||||
|
// delegate: PluginConfig,
|
||||||
|
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
|
||||||
|
) : AutoSavePluginConfig() {
|
||||||
|
public val grantedPermissionMap: MutableMap<PermissionId, MutableList<AbstractPermissibleIdentifier>>
|
||||||
|
by value<MutableMap<PermissionId, MutableList<AbstractPermissibleIdentifier>>>(ConcurrentHashMap())
|
||||||
|
.withDefault { CopyOnWriteArrayList() }
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
@JvmStatic
|
||||||
|
public operator fun <P : Permission> invoke(
|
||||||
|
permissionType: KType,
|
||||||
|
saveName: String,
|
||||||
|
// delegate: PluginConfig,
|
||||||
|
): ConcurrentSaveData<P> = ConcurrentSaveData(permissionType, saveName, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "CanBeParameter")
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.permission
|
||||||
|
|
||||||
|
@ExperimentalPermission
|
||||||
|
public class DuplicatedPermissionRegistrationException(
|
||||||
|
newInstance: Permission,
|
||||||
|
public val existingInstance: Permission
|
||||||
|
) : Exception("Duplicated Permission registry. new: $newInstance, existing: $existingInstance")
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记一个实验性的权限系统 API.
|
||||||
|
*
|
||||||
|
* 权限系统是在 1.0-M4 引入的一个实验性系统, 目前不具有 API 稳定性.
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||||
|
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||||
|
@MustBeDocumented
|
||||||
|
public annotation class ExperimentalPermission(
|
||||||
|
val message: String = ""
|
||||||
|
)
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Mamoe Technologies and contributors.
|
||||||
|
*
|
||||||
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||||
|
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||||
|
*
|
||||||
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("NOTHING_TO_INLINE", "unused", "MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
|
package net.mamoe.mirai.console.permission
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.command.CommandSender
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可拥有权限的对象.
|
||||||
|
*
|
||||||
|
* 典型的实例为 [CommandSender]
|
||||||
|
*
|
||||||
|
* 注意: 请不要自主实现 [Permissible]
|
||||||
|
*
|
||||||
|
* @see CommandSender
|
||||||
|
*/
|
||||||
|
@ExperimentalPermission
|
||||||
|
public interface Permissible {
|
||||||
|
public val identifier: PermissibleIdentifier
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
objects.find { it.toString() == str }?.let { return it as AbstractPermissibleIdentifier }
|
||||||
|
for ((regex, block) in regexes) {
|
||||||
|
val result = regex.find(str) ?: continue
|
||||||
|
if (result.range.last != str.lastIndex) continue
|
||||||
|
if (result.range.first != 0) continue
|
||||||
|
return result.destructured.run(block)
|
||||||
|
}
|
||||||
|
error("Cannot deserialize '$str' as AbstractPermissibleIdentifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val objects by lazy {
|
||||||
|
// https://youtrack.jetbrains.com/issue/KT-41782
|
||||||
|
AbstractPermissibleIdentifier::class.nestedClasses.mapNotNull { it.objectInstance }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val regexes: List<Pair<Regex, (matchGroup: MatchResult.Destructured) -> AbstractPermissibleIdentifier>> =
|
||||||
|
listOf(
|
||||||
|
Regex("""ExactGroup\(\s*([0-9]+)\s*\)""") to { (id) -> ExactGroup(id.toLong()) },
|
||||||
|
Regex("""ExactFriend\(\s*([0-9]+)\s*\)""") to { (id) -> ExactFriend(id.toLong()) },
|
||||||
|
Regex("""ExactUser\(\s*([0-9]+)\s*\)""") to { (id) -> ExactUser(id.toLong()) },
|
||||||
|
Regex("""AnyMember\(\s*([0-9]+)\s*\)""") to { (id) -> AnyMember(id.toLong()) },
|
||||||
|
Regex("""ExactMember\(\s*([0-9]+)\s*([0-9]+)\s*\)""") to { (a, b) ->
|
||||||
|
ExactMember(
|
||||||
|
a.toLong(),
|
||||||
|
b.toLong()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Regex("""ExactTemp\(\s*([0-9]+)\s*([0-9]+)\s*\)""") to { (a, b) -> ExactTemp(a.toLong(), b.toLong()) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = "AnyGroup"
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class ExactGroup(public val groupId: Long) : AbstractPermissibleIdentifier(AnyGroup)
|
||||||
|
|
||||||
|
public data class AnyMember(public val groupId: Long) : AbstractPermissibleIdentifier(AnyMemberFromAnyGroup)
|
||||||
|
|
||||||
|
public object AnyMemberFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
|
||||||
|
override fun toString(): String = "AnyMemberFromAnyGroup"
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class ExactMember(
|
||||||
|
public val groupId: Long,
|
||||||
|
public val memberId: Long
|
||||||
|
) : AbstractPermissibleIdentifier(AnyMember(groupId), ExactUser(memberId))
|
||||||
|
|
||||||
|
public object AnyFriend : AbstractPermissibleIdentifier(AnyUser) {
|
||||||
|
override fun toString(): String = "AnyFriend"
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class ExactFriend(
|
||||||
|
public val id: Long
|
||||||
|
) : AbstractPermissibleIdentifier(ExactUser(id)) {
|
||||||
|
override fun toString(): String = "ExactFriend"
|
||||||
|
}
|
||||||
|
|
||||||
|
public object AnyTemp : AbstractPermissibleIdentifier(AnyUser) {
|
||||||
|
override fun toString(): String = "AnyTemp"
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class ExactTemp(
|
||||||
|
public val groupId: Long,
|
||||||
|
public val id: Long
|
||||||
|
) : AbstractPermissibleIdentifier(ExactUser(groupId)) // TODO: 2020/9/8 ExactMember ?
|
||||||
|
|
||||||
|
public object AnyUser : AbstractPermissibleIdentifier(AnyContact) {
|
||||||
|
override fun toString(): String = "AnyUser"
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class ExactUser(
|
||||||
|
public val id: Long
|
||||||
|
) : AbstractPermissibleIdentifier(AnyUser)
|
||||||
|
|
||||||
|
public object AnyContact : AbstractPermissibleIdentifier() {
|
||||||
|
override fun toString(): String = "AnyContact"
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Console : AbstractPermissibleIdentifier() {
|
||||||
|
override fun toString(): String = "Console"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlinx.serialization.Serializable
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermission
|
||||||
|
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个权限节点.
|
||||||
|
*
|
||||||
|
* 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
|
||||||
|
*
|
||||||
|
* 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
|
||||||
|
*/
|
||||||
|
@ExperimentalPermission
|
||||||
|
public interface Permission {
|
||||||
|
public val id: PermissionId
|
||||||
|
public val description: String
|
||||||
|
public val parentId: PermissionId
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
|
private val ROOT_PERMISSION_ID = PermissionId("*", "*")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有权限的父权限.
|
||||||
|
*/
|
||||||
|
@get:JvmName("getRootPermission")
|
||||||
|
@ExperimentalPermission
|
||||||
|
public val RootPermission: Permission by lazy {
|
||||||
|
PermissionService.INSTANCE.register(
|
||||||
|
ROOT_PERMISSION_ID,
|
||||||
|
"The parent of any permission",
|
||||||
|
ROOT_PERMISSION_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConsoleExperimentalAPI
|
||||||
|
@ExperimentalPermission
|
||||||
|
public fun Permission.parentsWithSelfSequence(): Sequence<Permission> =
|
||||||
|
generateSequence(this) { p ->
|
||||||
|
p.parentId.findCorrespondingPermission()?.takeIf { parent -> parent != p }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Permission] 的简单实现
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@ExperimentalPermission
|
||||||
|
public class PermissionImpl(
|
||||||
|
override val id: PermissionId,
|
||||||
|
override val description: String,
|
||||||
|
override val parentId: PermissionId = RootPermission.id
|
||||||
|
) : Permission
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Serializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import net.mamoe.mirai.console.internal.data.map
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [PermissionId] 与 [Permission] 唯一对应.
|
||||||
|
*/
|
||||||
|
@Serializable(with = PermissionId.AsStringSerializer::class)
|
||||||
|
@ExperimentalPermission
|
||||||
|
public data class PermissionId(
|
||||||
|
public val namespace: String,
|
||||||
|
public val id: String
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(!namespace.contains(':')) {
|
||||||
|
"':' is not allowed in namespace"
|
||||||
|
}
|
||||||
|
require(!id.contains(':')) {
|
||||||
|
"':' is not allowed in id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializer(forClass = PermissionId::class)
|
||||||
|
public object AsClassSerializer
|
||||||
|
|
||||||
|
public object AsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
|
||||||
|
serializer = { it.namespace + ":" + it.id },
|
||||||
|
deserializer = { it.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }
|
||||||
|
)
|
||||||
|
|
||||||
|
public override fun toString(): String {
|
||||||
|
return "$namespace:$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public fun parseFromString(string: String): PermissionId =
|
||||||
|
string.split(':').let { (namespace, id) -> PermissionId(namespace, id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* 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 interface PermissionIdNamespace {
|
||||||
|
@ExperimentalPermission
|
||||||
|
public fun permissionId(id: String): PermissionId
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* 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())
|
@ -7,28 +7,145 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("NOTHING_TO_INLINE", "unused", "MemberVisibilityCanBePrivate")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.permission
|
package net.mamoe.mirai.console.permission
|
||||||
|
|
||||||
import net.mamoe.mirai.console.extension.useExtensions
|
import net.mamoe.mirai.console.extension.SingletonExtensionPoint.Companion.findSingleton
|
||||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSuperclassOf
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [PermissionServiceProvider]
|
* [PermissionServiceProvider]
|
||||||
*/
|
*/
|
||||||
@ConsoleExperimentalAPI
|
@ExperimentalPermission
|
||||||
public interface PermissionService {
|
public interface PermissionService<P : Permission> {
|
||||||
|
@ExperimentalPermission
|
||||||
|
public val permissionType: KClass<P>
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public companion object : PermissionService {
|
public operator fun get(id: PermissionId): P?
|
||||||
private val instance by lazy {
|
|
||||||
PermissionServiceProvider.useExtensions { }
|
public fun getRegisteredPermissions(): Sequence<P>
|
||||||
|
public fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P>
|
||||||
|
|
||||||
|
public fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: P): Boolean {
|
||||||
|
val permissionId = permission.id
|
||||||
|
val all = this[permissionId]?.parentsWithSelfSequence() ?: return false
|
||||||
|
return getGrantedPermissions(permissibleIdentifier).any { p ->
|
||||||
|
all.any { p.id == it.id }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Throws(DuplicatedPermissionRegistrationException::class)
|
||||||
|
public fun register(
|
||||||
|
id: PermissionId,
|
||||||
|
description: String,
|
||||||
|
base: PermissionId = RootPermission.id
|
||||||
|
): P
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P)
|
||||||
|
public fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P)
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
@get:JvmName("getInstance")
|
||||||
|
@JvmStatic
|
||||||
|
public val INSTANCE: PermissionService<out Permission> by lazy {
|
||||||
|
PermissionServiceProvider.findSingleton()?.instance ?: BuiltInPermissionService
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P =
|
||||||
|
get(id) ?: throw PermissionNotFoundException(id)
|
||||||
|
|
||||||
|
internal fun PermissionService<*>.allocatePermissionIdForPlugin(name: String, id: String) =
|
||||||
|
PermissionId("plugin.${name.toLowerCase()}", id.toLowerCase())
|
||||||
|
|
||||||
|
public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this]
|
||||||
|
|
||||||
|
public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this)
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.grantPermission(permission: Permission) {
|
||||||
|
INSTANCE.checkType(permission::class).grant(this, permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.grantPermission(permissionId: PermissionId) {
|
||||||
|
grantPermission(permissionId.findCorrespondingPermissionOrFail())
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.denyPermission(permission: Permission) {
|
||||||
|
INSTANCE.checkType(permission::class).deny(this, permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.denyPermission(permissionId: PermissionId) {
|
||||||
|
denyPermission(permissionId.findCorrespondingPermissionOrFail())
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Permissible.hasPermission(permission: Permission): Boolean =
|
||||||
|
permission.testPermission(this@hasPermission)
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.hasPermission(permission: Permission): Boolean =
|
||||||
|
permission.testPermission(this@hasPermission)
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.hasPermission(permissionId: PermissionId): Boolean {
|
||||||
|
val instance = permissionId.findCorrespondingPermissionOrFail()
|
||||||
|
return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Permissible.hasPermission(permissionId: PermissionId): Boolean =
|
||||||
|
permissionId.testPermission(this@hasPermission)
|
||||||
|
|
||||||
|
public fun Permissible.getGrantedPermissions(): Sequence<Permission> =
|
||||||
|
INSTANCE.getGrantedPermissions(this@getGrantedPermissions.identifier)
|
||||||
|
|
||||||
|
public fun Permissible.grantPermission(vararg permissions: Permission) {
|
||||||
|
for (permission in permissions) {
|
||||||
|
INSTANCE.checkType(permission::class).grant(this.identifier, permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Permissible.denyPermission(vararg permissions: Permission) {
|
||||||
|
for (permission in permissions) {
|
||||||
|
INSTANCE.checkType(permission::class).deny(this.identifier, permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun PermissibleIdentifier.getGrantedPermissions(): Sequence<Permission> =
|
||||||
|
INSTANCE.getGrantedPermissions(this@getGrantedPermissions)
|
||||||
|
|
||||||
|
public fun Permission.testPermission(permissible: Permissible): Boolean =
|
||||||
|
INSTANCE.checkType(this::class).testPermission(permissible.identifier, this@testPermission)
|
||||||
|
|
||||||
|
public fun Permission.testPermission(permissibleIdentifier: PermissibleIdentifier): Boolean =
|
||||||
|
INSTANCE.checkType(this::class).testPermission(permissibleIdentifier, this@testPermission)
|
||||||
|
|
||||||
|
public fun PermissionId.testPermission(permissible: Permissible): Boolean {
|
||||||
|
val p = INSTANCE[this] ?: return false
|
||||||
|
return p.testPermission(permissible)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun PermissionId.testPermission(permissible: PermissibleIdentifier): Boolean {
|
||||||
|
val p = INSTANCE[this] ?: return false
|
||||||
|
return p.testPermission(permissible)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface proprietary
|
@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"
|
||||||
|
}
|
||||||
|
|
||||||
@ConsoleExperimentalAPI
|
@Suppress("UNCHECKED_CAST")
|
||||||
public interface Permission
|
this as PermissionService<Permission>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -82,4 +82,4 @@ public inline val Plugin.author: String get() = this.description.author
|
|||||||
/**
|
/**
|
||||||
* 获取 [PluginDescription.dependencies]
|
* 获取 [PluginDescription.dependencies]
|
||||||
*/
|
*/
|
||||||
public inline val Plugin.dependencies: List<PluginDependency> get() = this.description.dependencies
|
public inline val Plugin.dependencies: Set<PluginDependency> get() = this.description.dependencies
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.mamoe.mirai.console.plugin.description
|
||||||
|
|
||||||
|
public class IllegalPluginDescriptionException : 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)
|
||||||
|
}
|
@ -18,9 +18,11 @@ import com.vdurmont.semver4j.Semver
|
|||||||
*
|
*
|
||||||
* @see PluginDescription.dependencies
|
* @see PluginDescription.dependencies
|
||||||
*/
|
*/
|
||||||
public data class PluginDependency(
|
public data class PluginDependency @JvmOverloads constructor(
|
||||||
/** 依赖插件名 */
|
/**
|
||||||
public val name: String,
|
* 依赖插件 ID, [PluginDescription.id]
|
||||||
|
*/
|
||||||
|
public val id: String,
|
||||||
/**
|
/**
|
||||||
* 依赖版本号. 为 null 时则为不限制版本.
|
* 依赖版本号. 为 null 时则为不限制版本.
|
||||||
*
|
*
|
||||||
@ -34,6 +36,16 @@ public data class PluginDependency(
|
|||||||
*/
|
*/
|
||||||
public val isOptional: Boolean = false
|
public val isOptional: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* @see PluginDependency
|
||||||
|
*/
|
||||||
|
public constructor(name: String, isOptional: Boolean = false) : this(
|
||||||
|
name, null, isOptional
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see PluginDependency
|
||||||
|
*/
|
||||||
public constructor(name: String, version: String, isOptional: Boolean) : this(
|
public constructor(name: String, version: String, isOptional: Boolean) : this(
|
||||||
name,
|
name,
|
||||||
Semver(version, Semver.SemverType.IVY),
|
Semver(version, Semver.SemverType.IVY),
|
||||||
|
@ -11,6 +11,7 @@ package net.mamoe.mirai.console.plugin.description
|
|||||||
|
|
||||||
import com.vdurmont.semver4j.Semver
|
import com.vdurmont.semver4j.Semver
|
||||||
import net.mamoe.mirai.console.plugin.Plugin
|
import net.mamoe.mirai.console.plugin.Plugin
|
||||||
|
import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +28,44 @@ public interface PluginDescription {
|
|||||||
public val kind: PluginKind
|
public val kind: PluginKind
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件名称. 不允许存在 ":"
|
* 插件 ID, 必须全英文, 仅允许英文字母, '-', '_', '.'.
|
||||||
|
*
|
||||||
|
* - 类似于 Java 包名, 插件 ID 需要 '域名.名称' 格式, 如 `net.mamoe.mirai.example-plugin`
|
||||||
|
* - 域名和名称都是必须的
|
||||||
|
* - '.' 不允许位于首位或末尾
|
||||||
|
* - '-' 和 '_' 仅允许存在于两个英文字母之间
|
||||||
|
*
|
||||||
|
* ID 在插件发布后就应该保持不变, 以便其他插件添加依赖.
|
||||||
|
*
|
||||||
|
* 插件 ID 的域名和名称都不能完全是以下其中一个 ([FORBIDDEN_ID_WORDS]).
|
||||||
|
* - "console"
|
||||||
|
* - "main"
|
||||||
|
* - "plugin"
|
||||||
|
* - "config"
|
||||||
|
* - "data"
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ID 用于指令权限等一些内部处理
|
||||||
|
*
|
||||||
|
* @see FORBIDDEN_ID_LETTERS
|
||||||
|
* @see FORBIDDEN_ID_WORDS
|
||||||
|
*/
|
||||||
|
public val id: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件名称. 允许中文, 允许各类符号.
|
||||||
|
*
|
||||||
|
* 插件名称不能完全是以下其中一种 ([FORBIDDEN_ID_WORDS]).
|
||||||
|
* - console
|
||||||
|
* - main
|
||||||
|
* - plugin
|
||||||
|
* - config
|
||||||
|
* - data
|
||||||
|
*
|
||||||
|
* 插件名称用于显示给用户.
|
||||||
|
*
|
||||||
|
* @see FORBIDDEN_ID_LETTERS
|
||||||
|
* @see FORBIDDEN_ID_WORDS
|
||||||
*/
|
*/
|
||||||
public val name: String
|
public val name: String
|
||||||
|
|
||||||
@ -39,7 +77,21 @@ public interface PluginDescription {
|
|||||||
/**
|
/**
|
||||||
* 插件版本.
|
* 插件版本.
|
||||||
*
|
*
|
||||||
* 语法参考: ([语义化版本 2.0.0](https://semver.org/lang/zh-CN/))
|
* 语法参考: ([语义化版本 2.0.0](https://semver.org/lang/zh-CN/)).
|
||||||
|
*
|
||||||
|
* 合法的版本号示例:
|
||||||
|
* - `1.0.0`
|
||||||
|
* - `1.0`
|
||||||
|
* - `1.0-M1`
|
||||||
|
* - `1.0.0-M1`
|
||||||
|
* - `1.0.0-M2-1`
|
||||||
|
* - `1` (尽管非常不建议这么做)
|
||||||
|
*
|
||||||
|
* 非法版本号实例:
|
||||||
|
* - `DEBUG-1`
|
||||||
|
* - `-1.0`
|
||||||
|
* - `v1.0` (不允许 "v")
|
||||||
|
* - `V1.0` (不允许 "V")
|
||||||
*
|
*
|
||||||
* @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本.
|
* @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本.
|
||||||
*/
|
*/
|
||||||
@ -55,6 +107,77 @@ public interface PluginDescription {
|
|||||||
*
|
*
|
||||||
* @see PluginDependency
|
* @see PluginDependency
|
||||||
*/
|
*/
|
||||||
public val dependencies: List<PluginDependency>
|
public val dependencies: Set<PluginDependency>
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public val FORBIDDEN_ID_LETTERS: Array<String> = "~!@#$%^&*()+/*<>{}|[]\\?".map(Char::toString).toTypedArray()
|
||||||
|
public val FORBIDDEN_ID_WORDS: Array<String> = arrayOf("main", "console", "plugin", "config", "data")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 依次检查 [PluginDescription] 的 [PluginDescription.id], [PluginDescription.name], [PluginDescription.dependencies] 的合法性
|
||||||
|
*
|
||||||
|
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalPluginDescriptionException::class)
|
||||||
|
public fun checkPluginDescription(instance: PluginDescription) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
checkPluginId(instance.id)
|
||||||
|
checkPluginName(instance.name)
|
||||||
|
checkDependencies(instance.id, instance.dependencies)
|
||||||
|
}.getOrElse {
|
||||||
|
throw IllegalPluginDescriptionException(
|
||||||
|
"Illegal description. Plugin ${instance.name} (${instance.id})",
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 [PluginDescription.id] 的合法性.
|
||||||
|
*
|
||||||
|
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalPluginDescriptionException::class)
|
||||||
|
public fun checkPluginId(id: String) {
|
||||||
|
if (id.isBlank()) throw IllegalPluginDescriptionException("Plugin id cannot be blank")
|
||||||
|
if (id.count { it == '.' } < 2) throw IllegalPluginDescriptionException("'$id' is illegal. Plugin id must consist of both domain and name. ")
|
||||||
|
|
||||||
|
FORBIDDEN_ID_LETTERS.firstOrNull { it in id }?.let { illegal ->
|
||||||
|
throw IllegalPluginDescriptionException("Plugin id contains illegal char: $illegal.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val idSections = id.split('.')
|
||||||
|
FORBIDDEN_ID_WORDS.firstOrNull { it in idSections }?.let { illegal ->
|
||||||
|
throw IllegalPluginDescriptionException("Plugin id contains illegal word: '$illegal'.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 [PluginDescription.name] 的合法性.
|
||||||
|
*
|
||||||
|
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalPluginDescriptionException::class)
|
||||||
|
public fun checkPluginName(name: String) {
|
||||||
|
if (name.isBlank()) throw IllegalPluginDescriptionException("Plugin name cannot be blank")
|
||||||
|
FORBIDDEN_ID_WORDS.firstOrNull { it in name }?.let { illegal ->
|
||||||
|
throw IllegalPluginDescriptionException("Plugin name is illegal: '$illegal'.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 [PluginDescription.dependencies] 的合法性.
|
||||||
|
*
|
||||||
|
* @throws IllegalPluginDescriptionException 当不合法时抛出.
|
||||||
|
*/
|
||||||
|
@Throws(IllegalPluginDescriptionException::class)
|
||||||
|
public fun checkDependencies(pluginId: String, dependencies: Set<PluginDependency>) {
|
||||||
|
if (dependencies.distinctBy { it.id }.size != dependencies.size)
|
||||||
|
throw PluginLoadException("Duplicated dependency detected: A plugin cannot depend on different versions of dependencies of the same id")
|
||||||
|
|
||||||
|
if (dependencies.any { it.id == pluginId })
|
||||||
|
throw PluginLoadException("Recursive dependency detected: A plugin cannot depend on itself")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,19 +10,34 @@
|
|||||||
package net.mamoe.mirai.console.plugin.description
|
package net.mamoe.mirai.console.plugin.description
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
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.internal.data.map
|
import net.mamoe.mirai.console.internal.data.map
|
||||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||||
|
import net.mamoe.mirai.console.plugin.description.PluginKind.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件类型
|
* 插件类型.
|
||||||
|
*
|
||||||
|
* 插件类型将影响加载顺序: [LOADER] -> [HIGH_PRIORITY_EXTENSIONS] -> [NORMAL].
|
||||||
|
*
|
||||||
|
* 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [LOADER] 不允许依赖一个 [NORMAL] 类型的插件.
|
||||||
*/
|
*/
|
||||||
@Serializable(with = PluginKind.AsStringSerializer::class)
|
|
||||||
public enum class PluginKind {
|
public enum class PluginKind {
|
||||||
/** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */
|
/** 表示此插件提供一个 [PluginLoader], 也可以同时提供其他 [Extension] 应最早被加载 */
|
||||||
LOADER,
|
LOADER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [NORMAL] 类型插件前加载
|
||||||
|
*
|
||||||
|
* 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. 如 [PermissionServiceProvider].
|
||||||
|
*
|
||||||
|
* 一些普通的 [Extension], 如 [BotConfigurationAlterer], 也可以使用 [NORMAL] 类型插件注册.
|
||||||
|
*/
|
||||||
|
HIGH_PRIORITY_EXTENSIONS,
|
||||||
|
|
||||||
/** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */
|
/** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */
|
||||||
NORMAL;
|
NORMAL;
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
||||||
import net.mamoe.mirai.console.data.PluginConfig
|
import net.mamoe.mirai.console.data.PluginConfig
|
||||||
import net.mamoe.mirai.console.data.PluginData
|
import net.mamoe.mirai.console.data.PluginData
|
||||||
|
import net.mamoe.mirai.console.permission.ExperimentalPermission
|
||||||
|
import net.mamoe.mirai.console.permission.PermissionIdNamespace
|
||||||
import net.mamoe.mirai.console.plugin.Plugin
|
import net.mamoe.mirai.console.plugin.Plugin
|
||||||
import net.mamoe.mirai.console.plugin.PluginFileExtensions
|
import net.mamoe.mirai.console.plugin.PluginFileExtensions
|
||||||
import net.mamoe.mirai.console.plugin.ResourceContainer
|
import net.mamoe.mirai.console.plugin.ResourceContainer
|
||||||
@ -41,8 +43,9 @@ import net.mamoe.mirai.utils.MiraiLogger
|
|||||||
* @see JvmPlugin 支持文件系统扩展
|
* @see JvmPlugin 支持文件系统扩展
|
||||||
* @see ResourceContainer 支持资源获取 (如 Jar 中的资源文件)
|
* @see ResourceContainer 支持资源获取 (如 Jar 中的资源文件)
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalPermission::class)
|
||||||
public interface JvmPlugin : Plugin, CoroutineScope,
|
public interface JvmPlugin : Plugin, CoroutineScope,
|
||||||
PluginFileExtensions, ResourceContainer, AutoSavePluginDataHolder {
|
PluginFileExtensions, ResourceContainer, AutoSavePluginDataHolder, PermissionIdNamespace {
|
||||||
|
|
||||||
/** 日志 */
|
/** 日志 */
|
||||||
public val logger: MiraiLogger
|
public val logger: MiraiLogger
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_member")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.plugin.jvm
|
package net.mamoe.mirai.console.plugin.jvm
|
||||||
|
|
||||||
@ -15,37 +15,250 @@ import com.vdurmont.semver4j.Semver
|
|||||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||||
import net.mamoe.mirai.console.plugin.description.PluginKind
|
import net.mamoe.mirai.console.plugin.description.PluginKind
|
||||||
|
import kotlin.internal.LowPriorityInOverloadResolution
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JVM 插件的描述. 通常作为 `plugin.yml`
|
* JVM 插件的描述. 通常作为 `plugin.yml`
|
||||||
|
*
|
||||||
|
* 请不要自行实现 [JvmPluginDescription] 接口. 它不具有继承稳定性.
|
||||||
|
*
|
||||||
* @see SimpleJvmPluginDescription
|
* @see SimpleJvmPluginDescription
|
||||||
|
* @see JvmPluginDescriptionBuilder
|
||||||
*/
|
*/
|
||||||
public interface JvmPluginDescription : PluginDescription
|
public interface JvmPluginDescription : PluginDescription {
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* 构建 [JvmPluginDescription]
|
||||||
|
* @see JvmPluginDescriptionBuilder
|
||||||
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
|
public operator fun invoke(
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
block: JvmPluginDescriptionBuilder.() -> Unit = {}
|
||||||
|
): JvmPluginDescription = JvmPluginDescriptionBuilder(name, version).apply(block).build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 [JvmPluginDescription]
|
||||||
|
* @see JvmPluginDescriptionBuilder
|
||||||
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
|
public operator fun invoke(
|
||||||
|
name: String,
|
||||||
|
version: Semver,
|
||||||
|
block: JvmPluginDescriptionBuilder.() -> Unit = {}
|
||||||
|
): JvmPluginDescription = JvmPluginDescriptionBuilder(name, version).apply(block).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* [JvmPluginDescription] 构建器.
|
||||||
|
*
|
||||||
|
* #### Kotlin Example
|
||||||
|
* ```
|
||||||
|
* val desc = JvmPluginDescription("org.example.example-plugin", "1.0.0") {
|
||||||
|
* info("This is an example plugin")
|
||||||
|
* dependsOn("org.example.another-plugin")
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* #### Java Example
|
||||||
|
* ```
|
||||||
|
* JvmPluginDescription desc = new JvmPluginDescriptionBuilder("org.example.example-plugin", "1.0.0")
|
||||||
|
* .info("This is an example plugin")
|
||||||
|
* .dependsOn("org.example.another-plugin")
|
||||||
|
* .build()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see [JvmPluginDescription.invoke]
|
||||||
|
*/
|
||||||
|
public class JvmPluginDescriptionBuilder(
|
||||||
|
private var id: String,
|
||||||
|
private var version: Semver,
|
||||||
|
) {
|
||||||
|
public constructor(name: String, version: String) : this(name, Semver(version, Semver.SemverType.LOOSE))
|
||||||
|
|
||||||
|
private var name: String = id
|
||||||
|
private var author: String = ""
|
||||||
|
private var info: String = ""
|
||||||
|
private var dependencies: MutableSet<PluginDependency> = mutableSetOf()
|
||||||
|
private var kind: PluginKind = PluginKind.NORMAL
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun name(value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun version(value: String): JvmPluginDescriptionBuilder =
|
||||||
|
apply { this.version = Semver(value, Semver.SemverType.LOOSE) }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun version(value: Semver): JvmPluginDescriptionBuilder = apply { this.version = value }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun id(value: String): JvmPluginDescriptionBuilder = apply { this.id = value.trim() }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun author(value: String): JvmPluginDescriptionBuilder = apply { this.author = value.trim() }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun kind(value: PluginKind): JvmPluginDescriptionBuilder = apply { this.kind = value }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun normalPlugin(): JvmPluginDescriptionBuilder = apply { this.kind = PluginKind.NORMAL }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun loaderProviderPlugin(): JvmPluginDescriptionBuilder = apply { this.kind = PluginKind.LOADER }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun highPriorityExtensionsPlugin(): JvmPluginDescriptionBuilder =
|
||||||
|
apply { this.kind = PluginKind.HIGH_PRIORITY_EXTENSIONS }
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun dependsOn(
|
||||||
|
pluginId: String,
|
||||||
|
version: String? = null,
|
||||||
|
isOptional: Boolean = false
|
||||||
|
): JvmPluginDescriptionBuilder = apply {
|
||||||
|
if (version == null) this.dependencies.add(PluginDependency(pluginId, version, isOptional))
|
||||||
|
else this.dependencies.add(PluginDependency(pluginId, version, isOptional))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun setDependencies(
|
||||||
|
value: Set<PluginDependency>
|
||||||
|
): JvmPluginDescriptionBuilder = apply {
|
||||||
|
this.dependencies = value.toMutableSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun dependsOn(
|
||||||
|
vararg dependencies: PluginDependency
|
||||||
|
): JvmPluginDescriptionBuilder = apply {
|
||||||
|
for (dependency in dependencies) {
|
||||||
|
this.dependencies.add(dependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ILoveKuriyamaMiraiForever
|
||||||
|
public fun dependsOn(
|
||||||
|
pluginId: String,
|
||||||
|
version: Semver? = null,
|
||||||
|
isOptional: Boolean = false
|
||||||
|
): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, version, isOptional)) }
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION_ERROR")
|
||||||
|
public fun build(): JvmPluginDescription =
|
||||||
|
SimpleJvmPluginDescription(name, version, id, author, info, dependencies, kind)
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@DslMarker
|
||||||
|
private annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor 推荐使用带名称的参数, 而不要按位置摆放.
|
||||||
|
*
|
||||||
* @see JvmPluginDescription
|
* @see JvmPluginDescription
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(
|
||||||
|
"""
|
||||||
|
将在 1.0-RC 删除. 请使用 JvmPluginDescription.
|
||||||
|
""",
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
"JvmPluginDescription",
|
||||||
|
"net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription"
|
||||||
|
),
|
||||||
|
level = DeprecationLevel.ERROR
|
||||||
|
)
|
||||||
public data class SimpleJvmPluginDescription
|
public data class SimpleJvmPluginDescription
|
||||||
|
@Deprecated(
|
||||||
|
"""
|
||||||
|
构造器不稳定, 将在 1.0-RC 删除. 请使用 JvmPluginDescriptionBuilder.
|
||||||
|
""",
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
"JvmPluginDescription(name, version) {}",
|
||||||
|
"net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription.Companion.invoke"
|
||||||
|
),
|
||||||
|
level = DeprecationLevel.ERROR
|
||||||
|
)
|
||||||
@JvmOverloads public constructor(
|
@JvmOverloads public constructor(
|
||||||
public override val name: String,
|
public override val name: String,
|
||||||
public override val version: Semver,
|
public override val version: Semver,
|
||||||
|
public override val id: String = name,
|
||||||
public override val author: String = "",
|
public override val author: String = "",
|
||||||
public override val info: String = "",
|
public override val info: String = "",
|
||||||
public override val dependencies: List<PluginDependency> = listOf(),
|
public override val dependencies: Set<PluginDependency> = setOf(),
|
||||||
public override val kind: PluginKind = PluginKind.NORMAL,
|
public override val kind: PluginKind = PluginKind.NORMAL,
|
||||||
) : JvmPluginDescription {
|
) : JvmPluginDescription {
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"""
|
||||||
|
构造器不稳定, 将在 1.0-RC 删除. 请使用 JvmPluginDescriptionBuilder.
|
||||||
|
""",
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
"JvmPluginDescription.invoke(name, version) {}",
|
||||||
|
"net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription.Companion.invoke"
|
||||||
|
),
|
||||||
|
level = DeprecationLevel.ERROR
|
||||||
|
)
|
||||||
|
@Suppress("DEPRECATION_ERROR")
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
public constructor(
|
public constructor(
|
||||||
name: String,
|
name: String,
|
||||||
version: String,
|
version: String,
|
||||||
|
id: String = name,
|
||||||
author: String = "",
|
author: String = "",
|
||||||
info: String = "",
|
info: String = "",
|
||||||
dependencies: List<PluginDependency> = listOf(),
|
dependencies: Set<PluginDependency> = setOf(),
|
||||||
kind: PluginKind = PluginKind.NORMAL,
|
kind: PluginKind = PluginKind.NORMAL,
|
||||||
) : this(name, Semver(version, Semver.SemverType.LOOSE), author, info, dependencies, kind)
|
) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies, kind)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(!name.contains(':')) { "':' is forbidden in plugin name" }
|
require(!name.contains(':')) { "':' is forbidden in plugin name" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.",
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
"SimpleJvmPluginDescription(name, version, author, info, dependencies, kind)",
|
||||||
|
"net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription"
|
||||||
|
),
|
||||||
|
level = DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
@LowPriorityInOverloadResolution
|
||||||
|
@Suppress("DEPRECATION_ERROR", "FunctionName")
|
||||||
|
public fun JvmPluginDescription(
|
||||||
|
name: String,
|
||||||
|
version: Semver,
|
||||||
|
id: String = name,
|
||||||
|
author: String = "",
|
||||||
|
info: String = "",
|
||||||
|
dependencies: Set<PluginDependency> = setOf(),
|
||||||
|
kind: PluginKind = PluginKind.NORMAL
|
||||||
|
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, kind)
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.",
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
"SimpleJvmPluginDescription(name, version, author, info, dependencies, kind)",
|
||||||
|
"net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription"
|
||||||
|
),
|
||||||
|
level = DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
@LowPriorityInOverloadResolution
|
||||||
|
@Suppress("DEPRECATION_ERROR", "FunctionName")
|
||||||
|
public fun JvmPluginDescription(
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
id: String = name,
|
||||||
|
author: String = "",
|
||||||
|
info: String = "",
|
||||||
|
dependencies: Set<PluginDependency> = setOf(),
|
||||||
|
kind: PluginKind = PluginKind.NORMAL
|
||||||
|
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, kind)
|
||||||
|
@ -1,36 +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("NOTHING_TO_INLINE")
|
|
||||||
@file:JvmMultifileClass
|
|
||||||
@file:JvmName("ConsoleUtils")
|
|
||||||
|
|
||||||
package net.mamoe.mirai.console.util
|
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
|
||||||
import net.mamoe.mirai.console.internal.data.builtins.BotManagerImpl
|
|
||||||
import net.mamoe.mirai.contact.User
|
|
||||||
|
|
||||||
public interface BotManager {
|
|
||||||
/**
|
|
||||||
* 判断此用户是否为 console 管理员
|
|
||||||
*/
|
|
||||||
public val User.isManager: Boolean
|
|
||||||
public val Bot.managers: List<Long>
|
|
||||||
|
|
||||||
public fun Bot.removeManager(id: Long): Boolean
|
|
||||||
public fun Bot.addManager(id: Long): Boolean
|
|
||||||
|
|
||||||
public companion object INSTANCE : BotManager { // kotlin import handler doesn't recognize delegation.
|
|
||||||
override fun Bot.addManager(id: Long): Boolean = BotManagerImpl.run { addManager(id) }
|
|
||||||
override fun Bot.removeManager(id: Long): Boolean = BotManagerImpl.run { removeManager(id) }
|
|
||||||
override val User.isManager: Boolean get() = BotManagerImpl.run { isManager }
|
|
||||||
override val Bot.managers: List<Long> get() = BotManagerImpl.run { managers }
|
|
||||||
}
|
|
||||||
}
|
|
@ -189,7 +189,7 @@ internal class TestCommand {
|
|||||||
|
|
||||||
val composite = object : CompositeCommand(
|
val composite = object : CompositeCommand(
|
||||||
ConsoleCommandOwner,
|
ConsoleCommandOwner,
|
||||||
"test",
|
"test22",
|
||||||
overrideContext = buildCommandArgumentContext {
|
overrideContext = buildCommandArgumentContext {
|
||||||
add(object : CommandArgumentParser<MyClass> {
|
add(object : CommandArgumentParser<MyClass> {
|
||||||
override fun parse(raw: String, sender: CommandSender): MyClass {
|
override fun parse(raw: String, sender: CommandSender): MyClass {
|
||||||
@ -234,7 +234,7 @@ internal class TestCommand {
|
|||||||
|
|
||||||
simple.withRegistration {
|
simple.withRegistration {
|
||||||
// assertEquals("xxx", withTesting { simple.execute(sender, "xxx") })
|
// assertEquals("xxx", withTesting { simple.execute(sender, "xxx") })
|
||||||
assertEquals("xxx", withTesting { println(sender.executeCommand("/test xxx")) })
|
assertEquals("xxx", withTesting { assertSuccess(sender.executeCommand("/test xxx")) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
package net.mamoe.mirai.console.data
|
package net.mamoe.mirai.console.data
|
||||||
|
|
||||||
import kotlinx.serialization.json.Json
|
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.plugin.jvm.KotlinPlugin
|
||||||
import net.mamoe.mirai.console.plugin.jvm.SimpleJvmPluginDescription
|
|
||||||
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
import net.mamoe.mirai.console.util.ConsoleInternalAPI
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -21,7 +21,7 @@ import kotlin.test.assertSame
|
|||||||
internal class PluginDataTest {
|
internal class PluginDataTest {
|
||||||
|
|
||||||
object MyPlugin : KotlinPlugin(
|
object MyPlugin : KotlinPlugin(
|
||||||
SimpleJvmPluginDescription(
|
JvmPluginDescription(
|
||||||
"1", "2"
|
"1", "2"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package net.mamoe.mirai.console.permission
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class PermissionsBasicsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parentsWithSelfSequence() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
const val core = "1.2.2"
|
const val core = "1.2.2"
|
||||||
const val console = "1.0-M4-dev-4"
|
const val console = "1.0-M4-dev-5"
|
||||||
const val consoleGraphical = "0.0.7"
|
const val consoleGraphical = "0.0.7"
|
||||||
const val consoleTerminal = "0.1.0"
|
const val consoleTerminal = "0.1.0"
|
||||||
const val consolePure = console
|
const val consolePure = console
|
||||||
|
@ -2,6 +2,39 @@
|
|||||||
|
|
||||||
欢迎来到 mirai-console 开发文档!
|
欢迎来到 mirai-console 开发文档!
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- **[准备工作](#准备工作)**
|
||||||
|
- **[启动 Console](#Run.md)**
|
||||||
|
|
||||||
|
### 后端插件开发基础
|
||||||
|
|
||||||
|
- 插件 - [Plugin 模块](Plugins.md)
|
||||||
|
- 指令 - [Command 模块](Commands.md)
|
||||||
|
- 存储 - [PluginData 模块](PluginData.md)
|
||||||
|
- 权限 - [Permission 模块](Permissions.md)
|
||||||
|
|
||||||
|
### 后端插件开发进阶
|
||||||
|
|
||||||
|
- 扩展 - [Extension 模块和扩展点](Extensions.md)
|
||||||
|
- 扩展 - [实现 PluginLoader](PluginLoader.md)
|
||||||
|
- 扩展 - [实现 PermissionService](PermissionService.md)
|
||||||
|
|
||||||
|
### 实现前端
|
||||||
|
- [FrontEnd](FrontEnd.md)
|
||||||
|
|
||||||
|
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
|
||||||
|
[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
|
||||||
|
[`PluginData`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt
|
||||||
|
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
|
||||||
|
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
||||||
|
[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
|
||||||
|
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
|
||||||
|
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
|
||||||
|
[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
|
||||||
|
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
|
||||||
|
[`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
|
||||||
|
|
||||||
## 准备工作
|
## 准备工作
|
||||||
***如果跳过本节内容,你很可能遇到无法解决的问题。***
|
***如果跳过本节内容,你很可能遇到无法解决的问题。***
|
||||||
|
|
||||||
@ -43,42 +76,12 @@
|
|||||||
- 对于 Java 使用者,请阅读 [Java 用户的使用指南](#java-用户的使用指南),[在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数](#在-java-使用-mirai-console-中的-kotlin-suspend-函数)
|
- 对于 Java 使用者,请阅读 [Java 用户的使用指南](#java-用户的使用指南),[在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数](#在-java-使用-mirai-console-中的-kotlin-suspend-函数)
|
||||||
- 对于 Kotlin 使用者,请熟知 [Kotlin `1.4` 版本带来的新特性](#mirai-console-使用的-kotlin-14-版本的新特性)
|
- 对于 Kotlin 使用者,请熟知 [Kotlin `1.4` 版本带来的新特性](#mirai-console-使用的-kotlin-14-版本的新特性)
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
### 后端插件开发基础
|
|
||||||
|
|
||||||
- 插件 - [Plugin 模块](Plugins.md)
|
|
||||||
- 指令 - [Command 模块](Commands.md)
|
|
||||||
- 存储 - [PluginData 模块](PluginData.md)
|
|
||||||
- 权限 - [Permission 模块](Permissions.md)
|
|
||||||
|
|
||||||
### 后端插件开发进阶
|
|
||||||
|
|
||||||
- 扩展 - [Extension 模块和扩展点](Extensions.md)
|
|
||||||
- 扩展 - [实现 PluginLoader](PluginLoader.md)
|
|
||||||
- 扩展 - [实现 PermissionService](PermissionService.md)
|
|
||||||
|
|
||||||
### 实现前端
|
|
||||||
- [FrontEnd](FrontEnd.md)
|
|
||||||
|
|
||||||
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
|
|
||||||
[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
|
|
||||||
[`PluginData`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt
|
|
||||||
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
|
|
||||||
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
|
||||||
[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
|
|
||||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
|
|
||||||
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
|
|
||||||
[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
|
|
||||||
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
|
|
||||||
[`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
|
|
||||||
|
|
||||||
|
|
||||||
## 附录
|
## 附录
|
||||||
|
|
||||||
### Java 用户的使用指南
|
### Java 用户的使用指南
|
||||||
|
|
||||||
- Java 中的「方法」在 Kotlin 中均被成为「函数」。
|
- Java 中的「方法」在 Kotlin 中均被称为「函数」。
|
||||||
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}`
|
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}`
|
||||||
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)`
|
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)`
|
||||||
|
|
||||||
|
29
docs/Run.md
Normal file
29
docs/Run.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Mirai Console - Run
|
||||||
|
|
||||||
|
Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||||
|
|
||||||
|
## 使用第三方工具自动启动
|
||||||
|
|
||||||
|
## 独立启动
|
||||||
|
|
||||||
|
### 环境
|
||||||
|
- JDK 11
|
||||||
|
|
||||||
|
### 准备文件
|
||||||
|
|
||||||
|
要启动 Mirai Console,你需要:
|
||||||
|
- mirai-core-qqandroid
|
||||||
|
- mirai-console 后端
|
||||||
|
- mirai-console 任一前端
|
||||||
|
- 相关依赖
|
||||||
|
|
||||||
|
只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 pure 前端可用。
|
||||||
|
|
||||||
|
### 启动 mirai-console-pure 前端
|
||||||
|
|
||||||
|
mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。
|
||||||
|
|
||||||
|
1. 在 [mirai-repo] 下载如下三个模块的最新版本文件:
|
||||||
|
- []
|
||||||
|
|
||||||
|
[mirai-repo]: https://github.com/project-mirai/mirai-repo/
|
@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
kotlin("kapt")
|
|
||||||
id("java")
|
id("java")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
id("com.jfrog.bintray")
|
id("com.jfrog.bintray")
|
||||||
@ -46,10 +45,10 @@ dependencies {
|
|||||||
testApi(project(":mirai-console"))
|
testApi(project(":mirai-console"))
|
||||||
|
|
||||||
|
|
||||||
val autoService = "1.0-rc7"
|
// val autoService = "1.0-rc7"
|
||||||
kapt("com.google.auto.service", "auto-service", autoService)
|
// kapt("com.google.auto.service", "auto-service", autoService)
|
||||||
compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
// compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||||
testCompileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
// testCompileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.apply {
|
ext.apply {
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
package net.mamoe.mirai.console.pure
|
package net.mamoe.mirai.console.pure
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
@ -28,7 +29,7 @@ val consoleLogger by lazy { DefaultLogger("console") }
|
|||||||
|
|
||||||
@OptIn(ConsoleInternalAPI::class)
|
@OptIn(ConsoleInternalAPI::class)
|
||||||
internal fun startupConsoleThread() {
|
internal fun startupConsoleThread() {
|
||||||
MiraiConsole.launch {
|
MiraiConsole.launch(CoroutineName("Input")) {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
val next = MiraiConsole.requestInput("").let {
|
val next = MiraiConsole.requestInput("").let {
|
||||||
|
@ -74,6 +74,9 @@ class MiraiConsoleImplementationPure
|
|||||||
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
|
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
|
||||||
NamedSupervisorJob("MiraiConsoleImplementationPure") +
|
NamedSupervisorJob("MiraiConsoleImplementationPure") +
|
||||||
CoroutineExceptionHandler { coroutineContext, throwable ->
|
CoroutineExceptionHandler { coroutineContext, throwable ->
|
||||||
|
if (throwable is CancellationException) {
|
||||||
|
return@CoroutineExceptionHandler
|
||||||
|
}
|
||||||
val coroutineName = coroutineContext[CoroutineName]?.name ?: "<unnamed>"
|
val coroutineName = coroutineContext[CoroutineName]?.name ?: "<unnamed>"
|
||||||
MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable)
|
MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable)
|
||||||
}) {
|
}) {
|
||||||
|
@ -92,11 +92,11 @@ internal object ConsoleCommandSenderImplPure : MiraiConsoleImplementation.Consol
|
|||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
lineReader.printAbove(message)
|
lineReader.printAbove(message)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
consoleLogger.error(it)
|
consoleLogger.error("Exception while ConsoleCommandSenderImplPure.sendMessage", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sendMessage(message: Message) {
|
override suspend fun sendMessage(message: Message) {
|
||||||
return sendMessage(message.contentToString())
|
return sendMessage(message.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +0,0 @@
|
|||||||
### Mirai Console Terminal
|
|
||||||
支持windows/mac/linux
|
|
||||||
在terminal环境下的Console, 由控制台富文本实现简易UI
|
|
||||||
优点: 可以在linux环境下运行/简洁使用效率高
|
|
||||||
缺点: 需要有略微的terminal知识
|
|
||||||
所使用插件系统与graphical版本一致 可以来回切换
|
|
@ -1,49 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("kotlinx-serialization")
|
|
||||||
id("kotlin")
|
|
||||||
id("java")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
apply(plugin = "com.github.johnrengelman.shadow")
|
|
||||||
|
|
||||||
version = Versions.Mirai.console
|
|
||||||
|
|
||||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
|
||||||
manifest {
|
|
||||||
attributes["Main-Class"] = "net.mamoe.mirai.console.MiraiConsoleTerminalLoader"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
all {
|
|
||||||
|
|
||||||
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
|
|
||||||
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
|
|
||||||
languageSettings.progressiveMode = true
|
|
||||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
|
||||||
api(project(":mirai-console"))
|
|
||||||
api(group = "com.googlecode.lanterna", name = "lanterna", version = "3.0.2")
|
|
||||||
}
|
|
||||||
val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks
|
|
||||||
compileKotlin.kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
val compileTestKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks
|
|
||||||
compileTestKotlin.kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
tasks.withType(JavaCompile::class.java) {
|
|
||||||
options.encoding = "UTF8"
|
|
||||||
}
|
|
@ -1,727 +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("MemberVisibilityCanBePrivate")
|
|
||||||
|
|
||||||
package net.mamoe.mirai.console
|
|
||||||
|
|
||||||
import com.googlecode.lanterna.SGR
|
|
||||||
import com.googlecode.lanterna.TerminalSize
|
|
||||||
import com.googlecode.lanterna.TextColor
|
|
||||||
import com.googlecode.lanterna.graphics.TextGraphics
|
|
||||||
import com.googlecode.lanterna.input.KeyStroke
|
|
||||||
import com.googlecode.lanterna.input.KeyType
|
|
||||||
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
|
|
||||||
import com.googlecode.lanterna.terminal.Terminal
|
|
||||||
import com.googlecode.lanterna.terminal.swing.SwingTerminal
|
|
||||||
import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.io.ByteWriteChannel
|
|
||||||
import kotlinx.coroutines.io.close
|
|
||||||
import kotlinx.coroutines.io.jvm.nio.copyTo
|
|
||||||
import kotlinx.coroutines.io.reader
|
|
||||||
import kotlinx.io.core.use
|
|
||||||
import net.mamoe.mirai.Bot
|
|
||||||
import net.mamoe.mirai.console.MiraiConsoleTerminalFrontEnd.LoggerDrawer.cleanPage
|
|
||||||
import net.mamoe.mirai.console.MiraiConsoleTerminalFrontEnd.LoggerDrawer.drawLog
|
|
||||||
import net.mamoe.mirai.console.MiraiConsoleTerminalFrontEnd.LoggerDrawer.redrawLogs
|
|
||||||
import net.mamoe.mirai.console.command.CommandManager
|
|
||||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
|
||||||
import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd
|
|
||||||
import net.mamoe.mirai.utils.LoginSolver
|
|
||||||
import net.mamoe.mirai.utils.SimpleLogger.LogPriority
|
|
||||||
import java.awt.Image
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import java.io.File
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.io.PrintStream
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque
|
|
||||||
import javax.imageio.ImageIO
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 此文件不推荐任何人看
|
|
||||||
* 可能导致
|
|
||||||
* 1:心肌梗死
|
|
||||||
* 2:呼吸困难
|
|
||||||
* 3:想要重写但是发现改任何一个看似不合理的地方都会崩
|
|
||||||
*
|
|
||||||
* @author NaturalHG
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
val String.actualLength: Int get() = this.sumBy { if (it.isChineseChar) 2 else 1 }
|
|
||||||
|
|
||||||
|
|
||||||
fun String.getSubStringIndexByActualLength(widthMax: Int): Int {
|
|
||||||
return this.sumBy { if (it.isChineseChar) 2 else 1 }.coerceAtMost(widthMax).coerceAtLeast(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Char.isChineseChar: Boolean
|
|
||||||
get() {
|
|
||||||
return this.toString().isChineseChar
|
|
||||||
}
|
|
||||||
|
|
||||||
val String.isChineseChar: Boolean
|
|
||||||
get() {
|
|
||||||
return this.matches(Regex("[\u4e00-\u9fa5]"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object MiraiConsoleTerminalFrontEnd : MiraiConsoleFrontEnd {
|
|
||||||
const val cacheLogSize = 50
|
|
||||||
var mainTitle = "Mirai Console v0.01 Core v0.15"
|
|
||||||
|
|
||||||
override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) {
|
|
||||||
mainTitle = "Mirai Console(Terminal) $consoleVersion $consoleBuild Core $coreVersion"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pushLog(identity: Long, message: String) {
|
|
||||||
log[identity]!!.push(message)
|
|
||||||
if (identity == screens[currentScreenId]) {
|
|
||||||
drawLog(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改interface之后用来暂时占位
|
|
||||||
override fun pushLog(priority: LogPriority, identityStr: String, identity: Long, message: String) {
|
|
||||||
this.pushLog(identity, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun prePushBot(identity: Long) {
|
|
||||||
log[identity] = LimitLinkedQueue(cacheLogSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pushBot(bot: Bot) {
|
|
||||||
botAdminCount[bot.id] = 0
|
|
||||||
screens.add(bot.id)
|
|
||||||
drawFrame(this.getScreenName(currentScreenId))
|
|
||||||
if (terminal is SwingTerminalFrame) {
|
|
||||||
terminal.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
var requesting = false
|
|
||||||
private var requestResult: String? = null
|
|
||||||
override suspend fun requestInput(hint:String): String {
|
|
||||||
if(hint.isNotEmpty()){
|
|
||||||
this.pushLog(0, hint)
|
|
||||||
}
|
|
||||||
requesting = true
|
|
||||||
while (requesting) {
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
return requestResult!!
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun provideInput(input: String) {
|
|
||||||
if (requesting) {
|
|
||||||
requestResult = input
|
|
||||||
requesting = false
|
|
||||||
} else {
|
|
||||||
CommandManager.runCommand(ConsoleCommandSender, commandBuilder.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun pushBotAdminStatus(identity: Long, admins: List<Long>) {
|
|
||||||
botAdminCount[identity] = admins.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createLoginSolver(): LoginSolver {
|
|
||||||
return object : LoginSolver() {
|
|
||||||
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
|
|
||||||
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
tempFile.createNewFile()
|
|
||||||
pushLog(0, "[Login Solver]需要图片验证码登录, 验证码为 4 字母")
|
|
||||||
try {
|
|
||||||
tempFile.writeChannel().apply {
|
|
||||||
writeFully(ByteBuffer.wrap(data))
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
pushLog(0, "请查看文件 ${tempFile.absolutePath}")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error("[Login Solver]验证码无法保存[Error0001]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var toLog: String
|
|
||||||
tempFile.inputStream().use {
|
|
||||||
val img = ImageIO.read(it)
|
|
||||||
toLog += img?.createCharImg((terminal.terminalSize.columns / 1.5).toInt()) ?: "无法创建字符图片. 请查看文件\n"
|
|
||||||
}
|
|
||||||
return requestInput("$toLog[Login Solver]请输验证码. ${tempFile.absolutePath}")
|
|
||||||
.takeUnless { it.isEmpty() || it.length != 4 }
|
|
||||||
.also {
|
|
||||||
pushLog(0, "[Login Solver]正在提交[$it]中...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
|
|
||||||
pushLog(0, "[Login Solver]需要滑动验证码")
|
|
||||||
pushLog(0, "[Login Solver]请在任意浏览器中打开以下链接并完成验证码. ")
|
|
||||||
pushLog(0, url)
|
|
||||||
return requestInput("[Login Solver]完成后请输入任意字符 ").also {
|
|
||||||
pushLog(0, "[Login Solver]正在提交中")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
|
|
||||||
pushLog(0, "[Login Solver]需要进行账户安全认证")
|
|
||||||
pushLog(0, "[Login Solver]该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
|
|
||||||
pushLog(0, "[Login Solver]完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
|
|
||||||
pushLog(0, "[Login Solver]请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
|
|
||||||
pushLog(0, "[Login Solver]这步操作将在后续的版本中优化")
|
|
||||||
pushLog(0, url)
|
|
||||||
return requestInput("").also {
|
|
||||||
pushLog(0, "[Login Solver]正在提交中...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val log = ConcurrentHashMap<Long, LimitLinkedQueue<String>>().also {
|
|
||||||
it[0L] = LimitLinkedQueue(cacheLogSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val botAdminCount = ConcurrentHashMap<Long, Int>()
|
|
||||||
|
|
||||||
private val screens = mutableListOf(0L)
|
|
||||||
private var currentScreenId = 0
|
|
||||||
|
|
||||||
|
|
||||||
lateinit var terminal: Terminal
|
|
||||||
lateinit var textGraphics: TextGraphics
|
|
||||||
|
|
||||||
private var hasStart = false
|
|
||||||
private lateinit var internalPrinter: PrintStream
|
|
||||||
fun start() {
|
|
||||||
if (hasStart) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
internalPrinter = System.out
|
|
||||||
|
|
||||||
|
|
||||||
hasStart = true
|
|
||||||
val defaultTerminalFactory = DefaultTerminalFactory(internalPrinter, System.`in`, Charset.defaultCharset())
|
|
||||||
try {
|
|
||||||
terminal = defaultTerminalFactory.createTerminal()
|
|
||||||
terminal.enterPrivateMode()
|
|
||||||
terminal.clearScreen()
|
|
||||||
terminal.setCursorVisible(false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
try {
|
|
||||||
terminal = SwingTerminalFrame("Mirai Console")
|
|
||||||
terminal.enterPrivateMode()
|
|
||||||
terminal.clearScreen()
|
|
||||||
terminal.setCursorVisible(false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error("can not create terminal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
textGraphics = terminal.newTextGraphics()
|
|
||||||
|
|
||||||
/*
|
|
||||||
var lastRedrawTime = 0L
|
|
||||||
var lastNewWidth = 0
|
|
||||||
var lastNewHeight = 0
|
|
||||||
|
|
||||||
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
|
|
||||||
try {
|
|
||||||
if (lastNewHeight == newSize.rows
|
|
||||||
&&
|
|
||||||
lastNewWidth == newSize.columns
|
|
||||||
) {
|
|
||||||
return@TerminalResizeListener
|
|
||||||
}
|
|
||||||
lastNewHeight = newSize.rows
|
|
||||||
lastNewWidth = newSize.columns
|
|
||||||
terminal.clearScreen()
|
|
||||||
if(terminal !is SwingTerminalFrame) {
|
|
||||||
Thread.sleep(300)
|
|
||||||
}
|
|
||||||
update()
|
|
||||||
redrawCommand()
|
|
||||||
redrawLogs(log[screens[currentScreenId]]!!)
|
|
||||||
}catch (ignored:Exception){
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
*/
|
|
||||||
var lastJob: Job? = null
|
|
||||||
terminal.addResizeListener { _: Terminal, _: TerminalSize ->
|
|
||||||
lastJob = GlobalScope.launch {
|
|
||||||
try {
|
|
||||||
delay(300)
|
|
||||||
if (lastJob == coroutineContext[Job]) {
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
terminal.clearScreen()
|
|
||||||
//inited = false
|
|
||||||
update()
|
|
||||||
redrawCommand()
|
|
||||||
redrawLogs(log[screens[currentScreenId]]!!)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
pushLog(0, "[UI ERROR] ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (terminal !is SwingTerminalFrame) {
|
|
||||||
System.setOut(PrintStream(object : OutputStream() {
|
|
||||||
var builder = java.lang.StringBuilder()
|
|
||||||
override fun write(b: Int) {
|
|
||||||
with(b.toChar()) {
|
|
||||||
if (this == '\n') {
|
|
||||||
pushLog(0, builder.toString())
|
|
||||||
builder = java.lang.StringBuilder()
|
|
||||||
} else {
|
|
||||||
builder.append(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
System.setErr(System.out)
|
|
||||||
|
|
||||||
try {
|
|
||||||
update()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
pushLog(0, "[UI ERROR] ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val charList = listOf(',', '.', '/', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '!', ' ')
|
|
||||||
thread {
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
val keyStroke: KeyStroke = terminal.readInput()
|
|
||||||
|
|
||||||
when (keyStroke.keyType) {
|
|
||||||
KeyType.ArrowLeft -> {
|
|
||||||
currentScreenId =
|
|
||||||
getLeftScreenId()
|
|
||||||
clearRows(2)
|
|
||||||
cleanPage()
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
KeyType.ArrowRight -> {
|
|
||||||
currentScreenId =
|
|
||||||
getRightScreenId()
|
|
||||||
clearRows(2)
|
|
||||||
cleanPage()
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
KeyType.Enter -> {
|
|
||||||
provideInput(commandBuilder.toString())
|
|
||||||
emptyCommand()
|
|
||||||
}
|
|
||||||
KeyType.Escape -> {
|
|
||||||
exit()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
if (keyStroke.character != null) {
|
|
||||||
if (keyStroke.character.toInt() == 8) {
|
|
||||||
deleteCommandChar()
|
|
||||||
}
|
|
||||||
if (keyStroke.character.isLetterOrDigit() || charList.contains(keyStroke.character)) {
|
|
||||||
addCommandChar(keyStroke.character)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
pushLog(0, "[UI ERROR] ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLeftScreenId(): Int {
|
|
||||||
var newId = currentScreenId - 1
|
|
||||||
if (newId < 0) {
|
|
||||||
newId = screens.size - 1
|
|
||||||
}
|
|
||||||
return newId
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRightScreenId(): Int {
|
|
||||||
var newId = 1 + currentScreenId
|
|
||||||
if (newId >= screens.size) {
|
|
||||||
newId = 0
|
|
||||||
}
|
|
||||||
return newId
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getScreenName(id: Int): String {
|
|
||||||
return when (screens[id]) {
|
|
||||||
0L -> {
|
|
||||||
"Console Screen"
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
"Bot: ${screens[id]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun clearRows(row: Int) {
|
|
||||||
textGraphics.putString(
|
|
||||||
0, row, " ".repeat(
|
|
||||||
terminal.terminalSize.columns
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun drawFrame(
|
|
||||||
title: String
|
|
||||||
) {
|
|
||||||
val width = terminal.terminalSize.columns
|
|
||||||
val height = terminal.terminalSize.rows
|
|
||||||
terminal.setBackgroundColor(TextColor.ANSI.DEFAULT)
|
|
||||||
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.GREEN
|
|
||||||
textGraphics.putString((width - mainTitle.actualLength) / 2, 1, mainTitle, SGR.BOLD)
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.putString(2, 3, "-".repeat(width - 4))
|
|
||||||
textGraphics.putString(2, 5, "-".repeat(width - 4))
|
|
||||||
textGraphics.putString(2, height - 4, "-".repeat(width - 4))
|
|
||||||
textGraphics.putString(2, height - 3, "|>>>")
|
|
||||||
textGraphics.putString(width - 3, height - 3, "|")
|
|
||||||
textGraphics.putString(2, height - 2, "-".repeat(width - 4))
|
|
||||||
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
val leftName =
|
|
||||||
getScreenName(getLeftScreenId())
|
|
||||||
// clearRows(2)
|
|
||||||
textGraphics.putString((width - title.actualLength) / 2 - "$leftName << ".length, 2, "$leftName << ")
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.YELLOW
|
|
||||||
textGraphics.putString((width - title.actualLength) / 2, 2, title, SGR.BOLD)
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
val rightName =
|
|
||||||
getScreenName(getRightScreenId())
|
|
||||||
textGraphics.putString((width + title.actualLength) / 2 + 1, 2, ">> $rightName")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun drawMainFrame(
|
|
||||||
onlineBotCount: Number
|
|
||||||
) {
|
|
||||||
drawFrame("Console Screen")
|
|
||||||
val width = terminal.terminalSize.columns
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
clearRows(4)
|
|
||||||
textGraphics.putString(2, 4, "|Online Bots: $onlineBotCount")
|
|
||||||
textGraphics.putString(
|
|
||||||
width - 2 - "Powered By Mamoe Technologies|".actualLength,
|
|
||||||
4,
|
|
||||||
"Powered By Mamoe Technologies|"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun drawBotFrame(
|
|
||||||
qq: Long,
|
|
||||||
adminCount: Number
|
|
||||||
) {
|
|
||||||
drawFrame("Bot: $qq")
|
|
||||||
val width = terminal.terminalSize.columns
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
clearRows(4)
|
|
||||||
textGraphics.putString(2, 4, "|Admins: $adminCount")
|
|
||||||
textGraphics.putString(width - 2 - "Add admins via commands|".actualLength, 4, "Add admins via commands|")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object LoggerDrawer {
|
|
||||||
var currentHeight = 6
|
|
||||||
|
|
||||||
fun drawLog(string: String, flush: Boolean = true) {
|
|
||||||
val maxHeight = terminal.terminalSize.rows - 4
|
|
||||||
val heightNeed = (string.actualLength / (terminal.terminalSize.columns - 6)) + 1
|
|
||||||
if (heightNeed - 1 > maxHeight) {
|
|
||||||
pushLog(0, "[UI ERROR]: 您的屏幕太小, 有一条超长LOG无法显示")
|
|
||||||
return//拒绝打印
|
|
||||||
}
|
|
||||||
if (currentHeight + heightNeed > maxHeight) {
|
|
||||||
cleanPage()//翻页
|
|
||||||
}
|
|
||||||
if (string.contains("\n")) {
|
|
||||||
string.split("\n").forEach { _ ->
|
|
||||||
drawLog(string, false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val width = terminal.terminalSize.columns - 6
|
|
||||||
var x = string
|
|
||||||
while (true) {
|
|
||||||
if (x == "") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val toWrite = if (x.actualLength > width) {
|
|
||||||
val index = x.getSubStringIndexByActualLength(width)
|
|
||||||
x.substring(0, index).also {
|
|
||||||
x = if (index < x.length) {
|
|
||||||
x.substring(index)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
x.also {
|
|
||||||
x = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.GREEN
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.putString(
|
|
||||||
3,
|
|
||||||
currentHeight, toWrite, SGR.ITALIC
|
|
||||||
)
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
++currentHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (flush && terminal is SwingTerminalFrame) {
|
|
||||||
terminal.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun cleanPage() {
|
|
||||||
for (index in 6 until terminal.terminalSize.rows - 4) {
|
|
||||||
clearRows(index)
|
|
||||||
}
|
|
||||||
currentHeight = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun redrawLogs(toDraw: Queue<String>) {
|
|
||||||
//this.cleanPage()
|
|
||||||
currentHeight = 6
|
|
||||||
var logsToDraw = 0
|
|
||||||
var vara = 0
|
|
||||||
val toPrint = mutableListOf<String>()
|
|
||||||
toDraw.forEach {
|
|
||||||
val heightNeed = (it.actualLength / (terminal.terminalSize.columns - 6)) + 1
|
|
||||||
vara += heightNeed
|
|
||||||
if (currentHeight + vara < terminal.terminalSize.rows - 4) {
|
|
||||||
logsToDraw++
|
|
||||||
toPrint.add(it)
|
|
||||||
} else {
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toPrint.reversed().forEach {
|
|
||||||
drawLog(it, false)
|
|
||||||
}
|
|
||||||
if (terminal is SwingTerminalFrame) {
|
|
||||||
terminal.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private var commandBuilder = StringBuilder()
|
|
||||||
private fun redrawCommand() {
|
|
||||||
val height = terminal.terminalSize.rows
|
|
||||||
val width = terminal.terminalSize.columns
|
|
||||||
clearRows(height - 3)
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
textGraphics.putString(2, height - 3, "|>>>")
|
|
||||||
textGraphics.putString(width - 3, height - 3, "|")
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.BLACK
|
|
||||||
textGraphics.putString(7, height - 3, commandBuilder.toString())
|
|
||||||
if (terminal is SwingTerminalFrame) {
|
|
||||||
terminal.flush()
|
|
||||||
}
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addCommandChar(
|
|
||||||
c: Char
|
|
||||||
) {
|
|
||||||
if (!requesting && commandBuilder.isEmpty() && c != '/') {
|
|
||||||
addCommandChar('/')
|
|
||||||
}
|
|
||||||
textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.BLACK
|
|
||||||
val height = terminal.terminalSize.rows
|
|
||||||
commandBuilder.append(c)
|
|
||||||
if (terminal is SwingTerminalFrame) {
|
|
||||||
redrawCommand()
|
|
||||||
} else {
|
|
||||||
textGraphics.putString(6 + commandBuilder.length, height - 3, c.toString())
|
|
||||||
}
|
|
||||||
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteCommandChar() {
|
|
||||||
if (commandBuilder.isNotEmpty()) {
|
|
||||||
commandBuilder = StringBuilder(commandBuilder.toString().substring(0, commandBuilder.length - 1))
|
|
||||||
}
|
|
||||||
val height = terminal.terminalSize.rows
|
|
||||||
if (terminal is SwingTerminalFrame) {
|
|
||||||
redrawCommand()
|
|
||||||
} else {
|
|
||||||
textGraphics.putString(7 + commandBuilder.length, height - 3, " ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private var lastEmpty: Job? = null
|
|
||||||
private fun emptyCommand() {
|
|
||||||
commandBuilder = StringBuilder()
|
|
||||||
if (terminal is SwingTerminal) {
|
|
||||||
redrawCommand()
|
|
||||||
terminal.flush()
|
|
||||||
} else {
|
|
||||||
lastEmpty = GlobalScope.launch {
|
|
||||||
try {
|
|
||||||
delay(100)
|
|
||||||
if (lastEmpty == coroutineContext[Job]) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
terminal.clearScreen()
|
|
||||||
}
|
|
||||||
//inited = false
|
|
||||||
update()
|
|
||||||
redrawCommand()
|
|
||||||
redrawLogs(log[screens[currentScreenId]]!!)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
pushLog(0, "[UI ERROR] ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun update() {
|
|
||||||
when (screens[currentScreenId]) {
|
|
||||||
0L -> {
|
|
||||||
drawMainFrame(screens.size - 1)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
drawBotFrame(
|
|
||||||
screens[currentScreenId],
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redrawLogs(log[screens[currentScreenId]]!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exit() {
|
|
||||||
try {
|
|
||||||
terminal.exitPrivateMode()
|
|
||||||
terminal.close()
|
|
||||||
exitProcess(0)
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LimitLinkedQueue<T>(
|
|
||||||
private val limit: Int = 50
|
|
||||||
) : ConcurrentLinkedDeque<T>() {
|
|
||||||
override fun push(e: T) {
|
|
||||||
if (size >= limit) {
|
|
||||||
this.pollLast()
|
|
||||||
}
|
|
||||||
return super.push(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author NaturalHG
|
|
||||||
*/
|
|
||||||
private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
|
|
||||||
val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
|
|
||||||
val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
|
|
||||||
val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
|
|
||||||
val g2d = image.createGraphics()
|
|
||||||
g2d.drawImage(tmp, 0, 0, null)
|
|
||||||
fun gray(rgb: Int): Int {
|
|
||||||
val r = rgb and 0xff0000 shr 16
|
|
||||||
val g = rgb and 0x00ff00 shr 8
|
|
||||||
val b = rgb and 0x0000ff
|
|
||||||
return (r * 30 + g * 59 + b * 11 + 50) / 100
|
|
||||||
}
|
|
||||||
|
|
||||||
fun grayCompare(g1: Int, g2: Int): Boolean =
|
|
||||||
kotlin.math.min(g1, g2).toDouble() / kotlin.math.max(g1, g2) >= ignoreRate
|
|
||||||
|
|
||||||
val background = gray(image.getRGB(0, 0))
|
|
||||||
|
|
||||||
return buildString(capacity = height) {
|
|
||||||
|
|
||||||
val lines = mutableListOf<StringBuilder>()
|
|
||||||
|
|
||||||
var minXPos = outputWidth
|
|
||||||
var maxXPos = 0
|
|
||||||
|
|
||||||
for (y in 0 until image.height) {
|
|
||||||
val builderLine = StringBuilder()
|
|
||||||
for (x in 0 until image.width) {
|
|
||||||
val gray = gray(image.getRGB(x, y))
|
|
||||||
if (grayCompare(gray, background)) {
|
|
||||||
builderLine.append(" ")
|
|
||||||
} else {
|
|
||||||
builderLine.append("#")
|
|
||||||
if (x < minXPos) {
|
|
||||||
minXPos = x
|
|
||||||
}
|
|
||||||
if (x > maxXPos) {
|
|
||||||
maxXPos = x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (builderLine.toString().isBlank()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines.add(builderLine)
|
|
||||||
}
|
|
||||||
for (line in lines) {
|
|
||||||
append(line.substring(minXPos, maxXPos)).append("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from Ktor CIO
|
|
||||||
private fun File.writeChannel(
|
|
||||||
coroutineContext: CoroutineContext = Dispatchers.IO
|
|
||||||
): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
RandomAccessFile(this@writeChannel, "rw").use { file ->
|
|
||||||
val copied = channel.copyTo(file.channel)
|
|
||||||
file.setLength(copied) // truncate tail that could remain from the previously written data
|
|
||||||
}
|
|
||||||
}.channel
|
|
@ -1,40 +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
|
|
||||||
|
|
||||||
import net.mamoe.mirai.console.pure.MiraiConsoleUIPure
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
class MiraiConsoleTerminalLoader {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
if (args.contains("pure") || args.contains("-pure") || System.getProperty(
|
|
||||||
"os.name",
|
|
||||||
""
|
|
||||||
).toLowerCase().contains("windows")
|
|
||||||
) {
|
|
||||||
println("[MiraiConsoleTerminalLoader]: 将以Pure[兼容模式]启动Console")
|
|
||||||
MiraiConsole.start(MiraiConsoleUIPure())
|
|
||||||
} else {
|
|
||||||
MiraiConsoleTerminalFrontEnd.start()
|
|
||||||
thread {
|
|
||||||
MiraiConsole.start(
|
|
||||||
MiraiConsoleTerminalFrontEnd
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Runtime.getRuntime().addShutdownHook(thread(start = false) {
|
|
||||||
MiraiConsole.stop()
|
|
||||||
MiraiConsoleTerminalFrontEnd.exit()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user