Review: Permission, PermissionService;

Lots of improvements;
Rename Permissible to Permittee, rename PermissibleIdentifier to PermitteeId;
Add docs for Permission system;
Remove ExperimentalPermission annotations on some targets
This commit is contained in:
Him188 2020-09-12 18:51:40 +08:00
parent c7cde8e790
commit 7b8e9cc1c6
22 changed files with 744 additions and 774 deletions

View File

@ -12,22 +12,20 @@ package net.mamoe.mirai.console.command
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.permission.*
import net.mamoe.mirai.console.permission.PermissionService.Companion.denyPermission
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
import net.mamoe.mirai.console.permission.PermissionService.Companion.getGrantedPermissions
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.message.nextMessageOrNull
import net.mamoe.mirai.utils.secondsToMillis
@ -37,20 +35,26 @@ import kotlin.system.exitProcess
@ConsoleExperimentalApi
@Suppress("EXPOSED_SUPER_INTERFACE")
public interface BuiltInCommand : Command, BuiltInCommandInternal
public interface BuiltInCommand : Command
// for identification
internal interface BuiltInCommandInternal : Command
internal interface BuiltInCommandInternal : Command, BuiltInCommand
/**
* 内建指令列表
*/
@ConsoleExperimentalApi
@Suppress("unused")
@OptIn(ExperimentalPermission::class)
public object BuiltInCommands {
@ConsoleExperimentalApi
public val rootPermission: Permission by lazy {
PermissionService.INSTANCE.register(
PermissionId("console", "*"),
"The parent of any built-in commands"
)
}
public val all: Array<out Command> by lazy {
internal val all: Array<out Command> by lazy {
this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray()
}
@ -63,8 +67,8 @@ public object BuiltInCommands {
public object HelpCommand : SimpleCommand(
ConsoleCommandOwner, "help",
description = "Command list",
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
parentPermission = rootPermission,
), BuiltInCommandInternal {
@Handler
public suspend fun CommandSender.handle() {
sendMessage(
@ -83,8 +87,8 @@ public object BuiltInCommands {
public object StopCommand : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "Stop the whole world.",
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
parentPermission = rootPermission,
), BuiltInCommandInternal {
private val closingLock = Mutex()
@ -94,16 +98,16 @@ public object BuiltInCommands {
closingLock.withLock {
sendMessage("Stopping mirai-console")
kotlin.runCatching {
ignoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
runIgnoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
}.fold(
onSuccess = {
ignoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
},
onFailure = {
if (it is CancellationException) return@fold
@OptIn(ConsoleInternalApi::class)
MiraiConsole.mainLogger.error("Exception in stop", it)
ignoreException<EventCancelledException> {
runIgnoreException<EventCancelledException> {
sendMessage(
it.localizedMessage ?: it.message ?: it.toString()
)
@ -119,8 +123,8 @@ public object BuiltInCommands {
public object LoginCommand : SimpleCommand(
ConsoleCommandOwner, "login", "登录",
description = "Log in a bot account.",
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
parentPermission = rootPermission,
), BuiltInCommandInternal {
@Handler
public suspend fun CommandSender.handle(id: Long, password: String) {
kotlin.runCatching {
@ -149,31 +153,37 @@ public object BuiltInCommands {
ConsoleCommandOwner, "permission", "权限", "perm",
description = "Manage permissions",
overrideContext = buildCommandArgumentContext {
PermissibleIdentifier::class with PermissibleIdentifierArgumentParser
PermitteeId::class with PermissibleIdentifierArgumentParser
Permission::class with PermissionIdArgumentParser.map { id ->
kotlin.runCatching {
id.findCorrespondingPermissionOrFail()
}.getOrElse { illegalArgument("指令不存在: $id", it) }
}
},
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
parentPermission = rootPermission,
), BuiltInCommandInternal {
// TODO: 2020/9/10 improve Permission command
@SubCommand
public suspend fun CommandSender.grant(target: PermissibleIdentifier, permission: Permission) {
@SubCommand("permit", "grant", "add")
public suspend fun CommandSender.permit(target: PermitteeId, permission: Permission) {
target.grantPermission(permission)
sendMessage("OK")
}
@SubCommand
public suspend fun CommandSender.deny(target: PermissibleIdentifier, permission: Permission) {
target.denyPermission(permission)
@SubCommand("cancel", "deny", "remove")
public suspend fun CommandSender.cancel(target: PermitteeId, permission: Permission) {
target.denyPermission(permission, false)
sendMessage("OK")
}
@SubCommand("grantedPermissions", "gp")
public suspend fun CommandSender.grantedPermissions(target: PermissibleIdentifier) {
val grantedPermissions = target.getGrantedPermissions()
@SubCommand("cancelAll", "denyAll", "removeAll")
public suspend fun CommandSender.cancelAll(target: PermitteeId, permission: Permission) {
target.denyPermission(permission, true)
sendMessage("OK")
}
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
public suspend fun CommandSender.permittedPermissions(target: PermitteeId) {
val grantedPermissions = target.getPermittedPermissions()
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
}
@ -182,380 +192,4 @@ public object BuiltInCommands {
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() })
}
}
}
internal inline fun <reified E : Throwable, R> ignoreException(block: () -> R): R? {
try {
return block()
} catch (e: Throwable) {
if (e is E) return null
throw e
}
}
internal inline fun <reified E : Throwable> ignoreException(block: () -> Unit): Unit? {
try {
return block()
} catch (e: Throwable) {
if (e is E) return null
throw e
}
}
internal fun ContactOrBot.render(): String {
return when (this) {
is Bot -> "Bot $nick($id)"
is Group -> "Group $name($id)"
is Friend -> "Friend $nick($id)"
is Member -> "Friend $nameCardOrNick($id)"
else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}")
}
}
/*
/**
* Some defaults commands are recommend to be replaced by plugin provided commands
*/
internal object DefaultCommands {
internal val commandPrefix = "mirai.command.prefix".property() ?: "/"
private suspend fun CommandSender.login(account: Long, password: String) {
MiraiConsole.logger("[Bot Login]", 0, "login...")
try {
MiraiConsole.frontEnd.prePushBot(account)
val bot = Bot(account, password) {
fileBasedDeviceInfo(MiraiConsole.path + "/device.json")
this.loginSolver = MiraiConsole.frontEnd.createLoginSolver()
this.botLoggerSupplier = {
SimpleLogger("BOT $account]") { _, message, e ->
MiraiConsole.logger("[BOT $account]", account, message)
if (e != null) {
MiraiConsole.logger("[NETWORK ERROR]", account, e)//因为在一页 所以可以不打QQ
}
}
}
this.networkLoggerSupplier = {
SimpleLogger("BOT $account") { _, message, e ->
MiraiConsole.logger("[NETWORK]", account, message)//因为在一页 所以可以不打QQ
if (e != null) {
MiraiConsole.logger("[NETWORK ERROR]", account, e)//因为在一页 所以可以不打QQ
}
}
}
}
bot.login()
MiraiConsole.subscribeMessages {
startsWith(commandPrefix) { message ->
if (this.bot != bot) return@startsWith
if (bot.checkManager(this.sender.id)) {
val sender = if (this is GroupMessageEvent) {
GroupContactCommandSender(bot,this.sender, this.subject)
} else {
ContactCommandSender(bot,this.subject)
}
CommandManager.runCommand(
sender, message
)
}
}
}
sendMessage("$account login successes")
MiraiConsole.frontEnd.pushBot(bot)
} catch (e: Exception) {
sendMessage("$account login failed -> " + e.message)
}
}
private fun String.property(): String? = System.getProperty(this)
@JvmSynthetic
internal fun tryLoginAuto() {
// For java -Dmirai.account=10086 -Dmirai.password=Password -jar mirai-console-wrapper-X.jar
val account = ("mirai.account".property() ?: return).toLong()
val password = "mirai.password".property() ?: "mirai.passphrase".property() ?: "mirai.passwd".property()
if (password == null) {
MiraiConsole.logger.invoke(
SimpleLogger.LogPriority.ERROR, "[AUTO LOGIN]", account,
"Find the account to be logged in, but no password specified"
)
return
}
GlobalScope.launch {
ConsoleCommandSender.login(account, password)
}
}
operator fun invoke() {
registerConsoleCommands {
name = "manager"
description = "Add a manager"
onCommand { it ->
if (this !is ConsoleCommandSender) {
sendMessage("请在后台使用该指令")
return@onCommand false
}
if (it.size < 2) {
MiraiConsole.logger("[Bot Manager]", 0, "/manager add [bot ID] [Manager ID]")
MiraiConsole.logger("[Bot Manager]", 0, "/manager remove [bot ID] [Manager ID]")
MiraiConsole.logger("[Bot Manager]", 0, "/manager list [bot ID]")
return@onCommand true
}
val botId = try {
it[1].toLong()
} catch (e: Exception) {
MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个Bot的ID")
return@onCommand false
}
val bot = MiraiConsole.getBotOrNull(botId)
if (bot == null) {
MiraiConsole.logger("[Bot Manager]", 0, "$botId 没有在Console中登陆")
return@onCommand false
}
when (it[0]) {
"add" -> {
if (it.size < 3) {
MiraiConsole.logger("[Bot Manager]", 0, "/manager add [bot ID] [Manager ID]")
return@onCommand true
}
val adminID = try {
it[2].toLong()
} catch (e: Exception) {
MiraiConsole.logger("[Bot Manager]", 0, it[2] + " 不是一个ID")
return@onCommand false
}
if (bot.addManager(adminID)) {
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "增加成功")
} else {
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "已经是一个manager了")
}
}
"remove" -> {
if (it.size < 3) {
MiraiConsole.logger("[Bot Manager]", 0, "/manager remove [bot ID] [Manager ID]")
return@onCommand true
}
val adminID = try {
it[2].toLong()
} catch (e: Exception) {
MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个ID")
return@onCommand false
}
if (!bot.checkManager(adminID)) {
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "本身不是一个Manager")
return@onCommand true
}
bot.removeManager(adminID)
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "移除成功")
}
"list" -> {
bot.managers.forEach {
MiraiConsole.logger("[Bot Manager]", 0, " -> $it")
}
}
}
return@onCommand true
}
}
registerConsoleCommands {
name = "login"
description = "机器人登录"
onCommand {
if (this !is ConsoleCommandSender) {
sendMessage("请在后台使用该指令")
return@onCommand false
}
if (it.size < 2) {
MiraiConsole.logger("\"/login qq password \" to login a bot")
MiraiConsole.logger("\"/login qq号 qq密码 \" 来登录一个BOT")
return@onCommand false
}
val qqNumber = it[0].toLong()
val qqPassword = it[1]
login(qqNumber, qqPassword)
true
}
}
registerConsoleCommands {
name = "status"
description = "获取状态"
onCommand { args ->
when (args.size) {
0 -> {
sendMessage("当前有" + botInstances.size + "个BOT在线")
}
1 -> {
val bot = args[0]
var find = false
botInstances.forEach {
if (it.id.toString().contains(bot)) {
find = true
appendMessage(
"" + it.id + ": 在线中; 好友数量:" + it.friends.size + "; 群组数量:" + it.groups.size
)
}
}
if (!find) {
sendMessage("没有找到BOT$bot")
}
}
}
true
}
}
registerConsoleCommands {
name = "say"
description = "聊天功能演示"
onCommand {
if (it.size < 2) {
MiraiConsole.logger("say [好友qq号或者群号] [测试消息] //将默认使用第一个BOT")
MiraiConsole.logger("say [bot号] [好友qq号或者群号] [测试消息]")
return@onCommand false
}
val bot: Bot? = if (it.size == 2) {
if (botInstances.isEmpty()) {
MiraiConsole.logger("还没有BOT登录")
return@onCommand false
}
botInstances[0]
} else {
MiraiConsole.getBotOrNull(it[0].toLong())
}
if (bot == null) {
MiraiConsole.logger("没有找到BOT")
return@onCommand false
}
val target = it[it.size - 2].toLong()
val message = it[it.size - 1]
try {
val contact = bot.getFriendOrNull(target) ?: bot.getGroup(target)
contact.sendMessage(message)
MiraiConsole.logger("消息已推送")
} catch (e: NoSuchElementException) {
MiraiConsole.logger("没有找到群或好友 号码为${target}")
return@onCommand false
}
true
}
}
registerConsoleCommands {
name = "plugins"
alias = listOf("plugin")
description = "获取插件列表"
onCommand {
PluginManager.getAllPluginDescriptions().let { descriptions ->
descriptions.forEach {
appendMessage("\t" + it.name + " v" + it.version + " by " + it.author + " " + it.info)
}
appendMessage("加载了" + descriptions.size + "个插件")
true
}
}
}
registerConsoleCommands {
name = "command"
alias = listOf("commands", "help", "helps")
description = "获取指令列表"
onCommand {
CommandManager.commands.toSet().let { commands ->
var size = 0
appendMessage("")//\n
commands.forEach {
++size
appendMessage("-> " + it.name + " :" + it.description)
}
appendMessage("""共有${size}条指令""")
}
true
}
}
registerConsoleCommands {
name = "about"
description = "About Mirai-Console"
onCommand {
appendMessage("v${MiraiConsole.version} ${MiraiConsole.build} is still in testing stage, major features are available")
appendMessage("now running under ${MiraiConsole.path}")
appendMessage("在Github中获取项目最新进展: https://github.com/mamoe/mirai")
appendMessage("Mirai为开源项目请自觉遵守开源项目协议")
appendMessage("Powered by Mamoe Technologies and contributors")
true
}
}
registerConsoleCommands {
name = "reload"
alias = listOf("reloadPlugins")
description = "重新加载全部插件"
onCommand {
PluginManager.reloadPlugins()
sendMessage("重新加载完成")
true
}
}
registerConsoleCommands {
name = "install"
description = "Install plugin from PluginCenter"
usage = "/install [plugin-name] to install plugin or /install [page-num] to show list "
onCommand { args ->
val center = MiraiConsole.frontEnd.pluginCenter
suspend fun showPage(num: Int) {
sendMessage("正在连接 " + center.name)
val list = center.fetchPlugin(num)
if (list.isEmpty()) {
sendMessage("页码过大")
return
}
sendMessage("显示插件列表第 $num")
appendMessage("\n")
list.values.forEach {
appendMessage("=> " + it.name + " ;作者: " + it.author + " ;介绍: " + it.description)
}
sendMessage("使用 /install ${num + 1} 查看下一页")
}
suspend fun installPlugin(name: String) {
sendMessage("正在连接 " + center.name)
val plugin = center.findPlugin(name)
if (plugin == null) {
sendMessage("插件未找到, 请注意大小写")
return
}
sendMessage("正在安装 " + plugin.name)
try {
center.downloadPlugin(name) {}
sendMessage("安装 " + plugin.name + " 成功, 请重启服务器以更新")
} catch (e: Exception) {
sendMessage("安装 " + plugin.name + " 失败, " + (e.message ?: "未知原因"))
}
}
if (args.isEmpty()) {
showPage(1)
} else {
val arg = args[0]
val id = arg.toIntOrNull() ?: 0
if (id > 0) {
showPage(id)
} else {
installPlugin(arg)
}
}
true
}
}
}
}
*/
}

View File

@ -54,7 +54,6 @@ public interface Command {
/**
* 指令权限
*/
@ExperimentalPermission
public val permission: Permission
/**
@ -85,13 +84,13 @@ public interface Command {
@JvmStatic
public val Command.primaryName: String
get() = names[0]
@JvmSynthetic
public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
sender.run { onCommand(args) }
}
}
@JvmSynthetic
public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
sender.run { onCommand(args) }
/**
* [Command] 的基础实现
*

View File

@ -32,10 +32,10 @@ import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.data.castOrNull
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
import net.mamoe.mirai.console.permission.AbstractPermissibleIdentifier
import net.mamoe.mirai.console.permission.AbstractPermitteeId
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.permission.Permittee
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
@ -137,7 +137,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
* @see asCommandSender
*/
@OptIn(ExperimentalPermission::class)
public interface CommandSender : CoroutineScope, Permissible {
public interface CommandSender : CoroutineScope, Permittee {
/**
* 与这个 [CommandSender] 相关的 [Bot].
* 当通过控制台执行时为 `null`.
@ -504,6 +504,9 @@ public fun CommandSender.getBotOrNull(): Bot? {
/**
* 控制台指令执行者. 代表由控制台执行指令
*
* 控制台拥有一切指令的执行权限.
*
* @see INSTANCE
*/
// 前端实现
@ -515,7 +518,7 @@ public abstract class ConsoleCommandSender @ConsoleFrontEndImplementation constr
public final override fun toString(): String = NAME
@ExperimentalPermission
public final override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.Console
public final override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console
public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope {
public const val NAME: String = "ConsoleCommandSender"
@ -607,13 +610,13 @@ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractComma
* @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
*/
public open class FriendCommandSender internal constructor(
public final override val user: Friend
public final override val user: Friend,
) : AbstractUserCommandSender(), CoroutineScope by user.childScope("FriendCommandSender") {
public override val subject: Contact get() = user
public override fun toString(): String = "FriendCommandSender($user)"
@ExperimentalPermission
public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactFriend(user.id)
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactFriend(user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = sendMessage(PlainText(message))
@ -627,7 +630,7 @@ public open class FriendCommandSender internal constructor(
* @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
*/
public open class MemberCommandSender internal constructor(
public final override val user: Member
public final override val user: Member,
) : AbstractUserCommandSender(),
GroupAwareCommandSender,
CoroutineScope by user.childScope("MemberCommandSender") {
@ -636,7 +639,7 @@ public open class MemberCommandSender internal constructor(
public override fun toString(): String = "MemberCommandSender($user)"
@ExperimentalPermission
public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactMember(group.id, user.id)
public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactMember(group.id, user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Group> = sendMessage(PlainText(message))
@ -650,7 +653,7 @@ public open class MemberCommandSender internal constructor(
* @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
*/
public open class TempCommandSender internal constructor(
public final override val user: Member
public final override val user: Member,
) : AbstractUserCommandSender(),
GroupAwareCommandSender,
CoroutineScope by user.childScope("TempCommandSender") {
@ -659,8 +662,8 @@ public open class TempCommandSender internal constructor(
public override fun toString(): String = "TempCommandSender($user)"
@ExperimentalPermission
public override val identifier: PermissibleIdentifier =
AbstractPermissibleIdentifier.ExactTemp(user.group.id, user.id)
public override val permitteeId: PermitteeId =
AbstractPermitteeId.ExactTemp(user.group.id, user.id)
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Member> = sendMessage(PlainText(message))
@ -695,7 +698,7 @@ public interface CommandSenderOnMessage<T : MessageEvent> :
* @see FriendCommandSender 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式
*/
public class FriendCommandSenderOnMessage internal constructor(
public override val fromEvent: FriendMessageEvent
public override val fromEvent: FriendMessageEvent,
) : FriendCommandSender(fromEvent.sender),
CommandSenderOnMessage<FriendMessageEvent>,
MessageEventExtensions<User, Contact> by fromEvent {
@ -708,7 +711,7 @@ public class FriendCommandSenderOnMessage internal constructor(
* @see MemberCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式
*/
public class MemberCommandSenderOnMessage internal constructor(
public override val fromEvent: GroupMessageEvent
public override val fromEvent: GroupMessageEvent,
) : MemberCommandSender(fromEvent.sender),
CommandSenderOnMessage<GroupMessageEvent>,
MessageEventExtensions<User, Contact> by fromEvent {
@ -721,7 +724,7 @@ public class MemberCommandSenderOnMessage internal constructor(
* @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式
*/
public class TempCommandSenderOnMessage internal constructor(
public override val fromEvent: TempMessageEvent
public override val fromEvent: TempMessageEvent,
) : TempCommandSender(fromEvent.sender),
CommandSenderOnMessage<TempMessageEvent>,
MessageEventExtensions<User, Contact> by fromEvent {

View File

@ -17,8 +17,8 @@ import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.command.description.CommandArgumentContext.ParserPair
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissibleIdentifier
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermitteeId
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.contact.*
import kotlin.internal.LowPriorityInOverloadResolution
@ -85,7 +85,7 @@ public interface CommandArgumentContext {
Bot::class with ExistingBotArgumentParser
PermissionId::class with PermissionIdArgumentParser
PermissibleIdentifier::class with PermissibleIdentifierArgumentParser
PermitteeId::class with PermissibleIdentifierArgumentParser
})
}

View File

@ -13,10 +13,10 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
import net.mamoe.mirai.console.internal.command.fuzzySearchMember
import net.mamoe.mirai.console.permission.AbstractPermissibleIdentifier
import net.mamoe.mirai.console.permission.AbstractPermitteeId
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.console.permission.PermitteeId
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.getGroupOrNull
@ -318,17 +318,17 @@ public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
}
@ExperimentalPermission
public object PermissibleIdentifierArgumentParser : CommandArgumentParser<PermissibleIdentifier> {
override fun parse(raw: String, sender: CommandSender): PermissibleIdentifier {
return if (raw == "~") sender.identifier
else kotlin.runCatching { AbstractPermissibleIdentifier.parseFromString(raw) }.getOrElse {
public object PermissibleIdentifierArgumentParser : CommandArgumentParser<PermitteeId> {
override fun parse(raw: String, sender: CommandSender): PermitteeId {
return if (raw == "~") sender.permitteeId
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
illegalArgument("无法解析 $raw 为 PermissibleIdentifier")
}
}
override fun parse(raw: MessageContent, sender: CommandSender): PermissibleIdentifier {
override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId {
if (raw is At) {
return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).identifier
return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId
}
return super.parse(raw, sender)
}

View File

@ -65,7 +65,7 @@ public class ScopedComponentStorage(
}
/**
* 注册一个扩展
* 注册一个 [PermissionService]
*/
@ExperimentalPermission
public fun contributePermissionService(

View File

@ -36,9 +36,9 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
import net.mamoe.mirai.console.internal.data.castOrNull
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.autoHexToBytes
import net.mamoe.mirai.console.permission.BuiltInPermissionService
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission

View File

@ -7,10 +7,11 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.permission
package net.mamoe.mirai.console.internal.permission
import net.mamoe.mirai.console.data.PluginDataExtensions
import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grantedWith
import net.mamoe.mirai.console.permission.*
import net.mamoe.mirai.console.permission.PermitteeId.Companion.hasChild
/**
*
@ -18,7 +19,7 @@ import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grante
@ExperimentalPermission
internal abstract class AbstractConcurrentPermissionService<P : Permission> : PermissionService<P> {
protected abstract val permissions: MutableMap<PermissionId, P>
protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>>
protected abstract fun createPermission(
id: PermissionId,
@ -31,26 +32,28 @@ internal abstract class AbstractConcurrentPermissionService<P : Permission> : Pe
override fun register(id: PermissionId, description: String, parent: Permission): P {
val instance = createPermission(id, description, parent)
val old = permissions.putIfAbsent(id, instance)
if (old != null) throw DuplicatedPermissionRegistrationException(instance, old)
if (old != null) throw PermissionRegistryConflictException(instance, old)
return instance
}
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P) {
override fun permit(permitteeId: PermitteeId, permission: P) {
val id = permission.id
grantedPermissionsMap[id].add(permissibleIdentifier)
grantedPermissionsMap[id].add(permitteeId)
}
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) {
grantedPermissionsMap[permission.id].remove(permissibleIdentifier)
override fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) {
if (recursive) {
grantedPermissionsMap[permission.id]
} else grantedPermissionsMap[permission.id].remove(permitteeId)
}
override fun getRegisteredPermissions(): Sequence<P> = permissions.values.asSequence()
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P> = sequence<P> {
override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P> = sequence<P> {
for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) {
val granted =
if (permissibleIdentifiers.isEmpty()) false
else permissibleIdentifiers.any { permissibleIdentifier.grantedWith(it) }
else permissibleIdentifiers.any { permitteeId.hasChild(it) }
if (granted) get(permissionIdentifier)?.let { yield(it) }
}

View File

@ -7,20 +7,33 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.permission
package net.mamoe.mirai.console.internal.permission
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.PluginDataExtensions
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.permission.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
@Suppress("unused") // don't pollute top-level
@OptIn(ExperimentalPermission::class)
internal fun PermissionService<*>.checkType(permissionType: KClass<out Permission>): PermissionService<Permission> {
require(this.permissionType.isSuperclassOf(permissionType)) {
"Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " +
"Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get"
}
@Suppress("UNCHECKED_CAST")
return this as PermissionService<Permission>
}
@ExperimentalPermission
internal object AllGrantPermissionService : PermissionService<PermissionImpl> {
internal object AllPermitPermissionService : PermissionService<PermissionImpl> {
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class
override val rootPermission: PermissionImpl get() = RootPermissionImpl.also { all[it.id] = it }
@ -28,26 +41,26 @@ internal object AllGrantPermissionService : PermissionService<PermissionImpl> {
override fun register(
id: PermissionId,
description: String,
parent: Permission
parent: Permission,
): PermissionImpl {
val new = PermissionImpl(id, description, parent)
val old = all.putIfAbsent(id, new)
if (old != null) throw DuplicatedPermissionRegistrationException(new, old)
if (old != null) throw PermissionRegistryConflictException(new, old)
return new
}
override fun get(id: PermissionId): PermissionImpl? = all[id]
override fun getRegisteredPermissions(): Sequence<PermissionImpl> = all.values.asSequence()
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<PermissionImpl> =
override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<PermissionImpl> =
all.values.asSequence()
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) {
}
override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean =
override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean =
true
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) {
}
}
@ -65,26 +78,26 @@ internal object AllDenyPermissionService : PermissionService<PermissionImpl> {
override fun register(
id: PermissionId,
description: String,
parent: Permission
parent: Permission,
): PermissionImpl {
val new = PermissionImpl(id, description, parent)
val old = all.putIfAbsent(id, new)
if (old != null) throw DuplicatedPermissionRegistrationException(new, old)
if (old != null) throw PermissionRegistryConflictException(new, old)
return new
}
override fun get(id: PermissionId): PermissionImpl? = all[id]
override fun getRegisteredPermissions(): Sequence<PermissionImpl> = all.values.asSequence()
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<PermissionImpl> =
override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<PermissionImpl> =
emptySequence()
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) {
}
override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean =
override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean =
false
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) {
override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) {
}
}
@ -99,8 +112,8 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
override val rootPermission: PermissionImpl = RootPermissionImpl.also { permissions[it.id] = it }
@Suppress("UNCHECKED_CAST")
override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>>
get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>>
override fun createPermission(id: PermissionId, description: String, parent: Permission): PermissionImpl =
PermissionImpl(id, description, parent)
@ -112,10 +125,10 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
@ExperimentalPermission
internal class ConcurrentSaveData private constructor(
public override val saveName: String,
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
) : AutoSavePluginConfig() {
public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermissibleIdentifier>>
by value<MutableMap<PermissionId, MutableSet<AbstractPermissibleIdentifier>>>(ConcurrentHashMap())
public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermitteeId>>
by value<MutableMap<PermissionId, MutableSet<AbstractPermitteeId>>>(ConcurrentHashMap())
.withDefault { CopyOnWriteArraySet() }
public companion object {

View File

@ -0,0 +1,80 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.permission
import net.mamoe.mirai.console.permission.AbstractPermitteeId
import net.mamoe.mirai.console.permission.AbstractPermitteeId.*
import net.mamoe.mirai.console.permission.ExperimentalPermission
@OptIn(ExperimentalPermission::class)
internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
val str = string.trim { it.isWhitespace() }.toLowerCase()
if (str == "console") return Console
if (str.isNotEmpty()) {
when (str[0]) {
'g' -> {
val arg = str.substring(1)
if (arg == "*") return AnyGroup
else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it }
}
'f' -> {
val arg = str.substring(1)
if (arg == "*") return AnyFriend
else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it }
}
'u' -> {
val arg = str.substring(1)
if (arg == "*") return AnyUser
else arg.toLongOrNull()?.let(::ExactUser)?.let { return it }
}
'c' -> {
val arg = str.substring(1)
if (arg == "*") return AnyContact
}
'm' -> kotlin.run {
val arg = str.substring(1)
if (arg == "*") return AnyMemberFromAnyGroup
else {
val components = arg.split('.')
if (components.size == 2) {
val groupId = components[0].toLongOrNull() ?: return@run
if (components[1] == "*") return AnyMember(groupId)
else {
val memberId = components[1].toLongOrNull() ?: return@run
return ExactMember(groupId, memberId)
}
}
}
}
't' -> kotlin.run {
val arg = str.substring(1)
if (arg == "*") return AnyTempFromAnyGroup
else {
val components = arg.split('.')
if (components.size == 2) {
val groupId = components[0].toLongOrNull() ?: return@run
if (components[1] == "*") return AnyTemp(groupId)
else {
val memberId = components[1].toLongOrNull() ?: return@run
return ExactTemp(groupId, memberId)
}
}
}
}
}
}
error("Cannot deserialize '$str' as AbstractPermissibleIdentifier")
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmName("CommonUtils")
package net.mamoe.mirai.console.internal.util
internal inline fun <reified E : Throwable, R> runIgnoreException(block: () -> R): R? {
try {
return block()
} catch (e: Throwable) {
if (e is E) return null
throw e
}
}
internal inline fun <reified E : Throwable> runIgnoreException(block: () -> Unit): Unit? {
try {
return block()
} catch (e: Throwable) {
if (e is E) return null
throw e
}
}

View File

@ -1,186 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.permission
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.internal.data.map
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/**
*/
@ExperimentalPermission("Classname is subject to change")
public interface PermissibleIdentifier {
public val parents: Array<out PermissibleIdentifier>
public companion object {
@ExperimentalPermission
public fun PermissibleIdentifier.grantedWith(with: PermissibleIdentifier): Boolean {
return allParentsWithSelf().any { it == with }
}
private fun PermissibleIdentifier.allParentsWithSelf(): Sequence<PermissibleIdentifier> {
return sequence {
yield(this@allParentsWithSelf)
yieldAll(parents.asSequence().flatMap { it.allParentsWithSelf() })
}
}
}
}
@Serializable(with = AbstractPermissibleIdentifier.AsStringSerializer::class)
@ExperimentalPermission
public sealed class AbstractPermissibleIdentifier(
public final override vararg val parents: PermissibleIdentifier
) : PermissibleIdentifier {
public companion object {
@JvmStatic
public fun parseFromString(string: String): AbstractPermissibleIdentifier {
val str = string.trim { it.isWhitespace() }.toLowerCase()
if (str == "console") return Console
if (str.isNotEmpty()) {
when (str[0]) {
'g' -> {
val arg = str.substring(1)
if (arg == "*") return AnyGroup
else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it }
}
'f' -> {
val arg = str.substring(1)
if (arg == "*") return AnyFriend
else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it }
}
'u' -> {
val arg = str.substring(1)
if (arg == "*") return AnyUser
else arg.toLongOrNull()?.let(::ExactUser)?.let { return it }
}
'c' -> {
val arg = str.substring(1)
if (arg == "*") return AnyContact
}
'm' -> kotlin.run {
val arg = str.substring(1)
if (arg == "*") return AnyMemberFromAnyGroup
else {
val components = arg.split('.')
if (components.size == 2) {
val groupId = components[0].toLongOrNull() ?: return@run
if (components[1] == "*") return AnyMember(groupId)
else {
val memberId = components[1].toLongOrNull() ?: return@run
return ExactMember(groupId, memberId)
}
}
}
}
't' -> kotlin.run {
val arg = str.substring(1)
if (arg == "*") return AnyTempFromAnyGroup
else {
val components = arg.split('.')
if (components.size == 2) {
val groupId = components[0].toLongOrNull() ?: return@run
if (components[1] == "*") return AnyTemp(groupId)
else {
val memberId = components[1].toLongOrNull() ?: return@run
return ExactTemp(groupId, memberId)
}
}
}
}
}
}
error("Cannot deserialize '$str' as AbstractPermissibleIdentifier")
}
}
@ConsoleExperimentalApi
public object AsStringSerializer : KSerializer<AbstractPermissibleIdentifier> by String.serializer().map(
serializer = { it.toString() },
deserializer = d@{ str -> parseFromString(str) }
)
public object AnyGroup : AbstractPermissibleIdentifier(AnyContact) {
override fun toString(): String = "g*"
}
public data class ExactGroup(public val groupId: Long) : AbstractPermissibleIdentifier(AnyGroup) {
override fun toString(): String = "g$groupId"
}
public data class AnyMember(public val groupId: Long) : AbstractPermissibleIdentifier(AnyMemberFromAnyGroup) {
override fun toString(): String = "m$groupId.*"
}
public object AnyMemberFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "m*"
}
public object AnyTempFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "t*"
}
public data class ExactMember(
public val groupId: Long,
public val memberId: Long
) : AbstractPermissibleIdentifier(AnyMember(groupId), ExactUser(memberId)) {
override fun toString(): String = "m$groupId.$memberId"
}
public object AnyFriend : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "f*"
}
public data class ExactFriend(
public val id: Long
) : AbstractPermissibleIdentifier(ExactUser(id)) {
override fun toString(): String = "f$id"
}
public data class AnyTemp(
public val groupId: Long,
) : AbstractPermissibleIdentifier(AnyUser, AnyMember(groupId)) {
override fun toString(): String = "t$groupId.*"
}
public data class ExactTemp(
public val groupId: Long,
public val memberId: Long
) : AbstractPermissibleIdentifier(ExactUser(groupId), ExactMember(groupId, memberId)) {
override fun toString(): String = "t$groupId.$memberId"
}
public object AnyUser : AbstractPermissibleIdentifier(AnyContact) {
override fun toString(): String = "u*"
}
public data class ExactUser(
public val id: Long
) : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "u$id"
}
public object AnyContact : AbstractPermissibleIdentifier() {
override fun toString(): String = "*"
}
public object Console : AbstractPermissibleIdentifier() {
override fun toString(): String = "console"
}
}

View File

@ -9,49 +9,75 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command
/**
* 一个权限节点.
* 一个权限.
*
* [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
*
* 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
* **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
*
* ### 获取 [Permission]
*
* #### 根权限
* [RootPermission] 是所有权限的父权限.
*
* #### 指令的权限
* 每个指令都拥有一个 [Command.permission].
*
* [BuiltInCommands.rootPermission] 为所有内建指令的权限.
*
* #### 手动申请权限
* [PermissionService.register]
*/
@ExperimentalPermission
public interface Permission {
/**
* 唯一识别 ID. 所有权限的 [id] 都互不相同.
*
* @see PermissionService.get [id] 获取已注册的 [Permission]
* @see PermissionId
*/
public val id: PermissionId
/**
* 描述信息. 描述信息在注册权限时强制提供.
*/
public val description: String
/**
* 父权限.
*
* [RootPermission] parent 为自身
*/
public val parent: Permission
public companion object {
/**
* 根权限. 是所有权限的父权限.
*
* Java 用户使用.
*
* @see RootPermission 推荐 Kotlin 用户使用.
*/
@JvmStatic
public fun getRootPermission(): Permission = PermissionService.INSTANCE.rootPermission
/**
* 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent` ... 直到 [Permission.parent] 为它自己.
*/
@get:JvmStatic
public val Permission.parentsWithSelf: Sequence<Permission>
get() = generateSequence(this) { p ->
p.parent.takeIf { parent -> parent != p }
}
}
}
/**
* 所有权限的父权限.
* 根权限. 所有权限的父权限.
*/
@get:JvmName("getRootPermission")
@ExperimentalPermission
@get:JvmSynthetic
public val RootPermission: Permission
get() = PermissionService.INSTANCE.rootPermission
/**
* 所有内建指令的权限
*/
@ExperimentalPermission
public val RootConsoleBuiltInPermission: Permission by lazy {
PermissionService.INSTANCE.register(
PermissionId("console", "*"),
"The parent of any built-in commands"
)
}
@ConsoleExperimentalApi
@ExperimentalPermission
public fun Permission.parentsWithSelfSequence(): Sequence<Permission> =
generateSequence(this) { p ->
p.parent.takeIf { parent -> parent != p }
}
get() = PermissionService.INSTANCE.rootPermission

View File

@ -11,19 +11,19 @@ package net.mamoe.mirai.console.permission
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.internal.data.map
/**
* 表示一个 [权限][Permission] 的唯一 ID.
*
* [PermissionId] [Permission] 唯一对应.
*/
@Serializable(with = PermissionId.AsStringSerializer::class)
@ExperimentalPermission
@Serializable(with = PermissionId.PermissionIdAsStringSerializer::class)
public data class PermissionId(
public val namespace: String,
public val id: String
public val id: String,
) {
init {
require(!namespace.contains(':')) {
@ -34,21 +34,30 @@ public data class PermissionId(
}
}
@Serializer(forClass = PermissionId::class)
public object AsClassSerializer
public object AsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
serializer = { it.namespace + ":" + it.id },
deserializer = { it.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }
)
public override fun toString(): String {
return "$namespace:$id"
}
/**
* 返回 `$namespace:$id`
*/
public override fun toString(): String = "$namespace:$id"
public companion object {
public fun parseFromString(string: String): PermissionId =
string.split(':').let { (namespace, id) -> PermissionId(namespace, id) }
/**
* `$namespace:$id` 解析 [PermissionId].
*
* @throws IllegalArgumentException 在解析失败时抛出.
*/
@JvmStatic
public fun parseFromString(string: String): PermissionId {
return kotlin.runCatching {
string.split(':').let { (namespace, id) -> PermissionId(namespace, id) }
}.getOrElse {
throw IllegalArgumentException("Could not parse PermissionId from '$string'", it)
}
}
}
}

View File

@ -9,8 +9,6 @@
package net.mamoe.mirai.console.permission
@ExperimentalPermission
public interface PermissionIdNamespace {
@ExperimentalPermission
public fun permissionId(id: String): PermissionId
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.permission
import kotlin.annotation.AnnotationTarget.*
/**
* 表示一个应该由 [权限服务][PermissionService] 实现的类.
*
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段.
* 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们.
*/
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@MustBeDocumented
internal annotation class PermissionImplementation

View File

@ -1,15 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.permission
@ExperimentalPermission
public open class PermissionNotFoundException public constructor(
public val id: PermissionId
) : Exception(id.toString())

View File

@ -11,8 +11,10 @@
package net.mamoe.mirai.console.permission
@ExperimentalPermission
public class DuplicatedPermissionRegistrationException(
newInstance: Permission,
public val existingInstance: Permission
) : Exception("Duplicated Permission registry. new: $newInstance, existing: $existingInstance")
/**
* @see PermissionService.register
*/
public class PermissionRegistryConflictException(
public val newInstance: Permission,
public val existingInstance: Permission,
) : Exception("Conflicted Permission registry. new: $newInstance, existing: $existingInstance")

View File

@ -12,13 +12,18 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.internal.permission.checkType
import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
/**
* 权限服务. 用于承载 Console 的权限系统.
*
* ### 可扩展
* 权限服务可由插件扩展并覆盖默认实现.
*
* [PermissionServiceProvider]
*/
@ExperimentalPermission
public interface PermissionService<P : Permission> {
@ExperimentalPermission
public val permissionType: KClass<P>
@ -29,12 +34,25 @@ public interface PermissionService<P : Permission> {
public operator fun get(id: PermissionId): P?
public fun getRegisteredPermissions(): Sequence<P>
public fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P>
public fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: P): Boolean {
/**
* 获取 [PermitteeId] 和其父标识的所有被授予的所有直接和间接的权限列表
*/
public fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P>
/**
* 判断 [permission] [permission] 的权限.
*
* 返回 `true` 的意义:
* - 通常意义: [permitteeId] 拥有 [permission] '能力'
* - 实现意义: [permitteeId] 自身或任意父标识 [PermissionService] 被授予高于或等于 [permission] 的权限
*
* @see Companion.testPermission 接收 [Permittee] 参数的扩展
*/
public fun testPermission(permitteeId: PermitteeId, permission: P): Boolean {
val permissionId = permission.id
val all = this[permissionId]?.parentsWithSelfSequence() ?: return false
return getGrantedPermissions(permissibleIdentifier).any { p ->
val all = this[permissionId]?.parentsWithSelf ?: return false
return getPermittedPermissions(permitteeId).any { p ->
all.any { p.id == it.id }
}
}
@ -42,7 +60,12 @@ public interface PermissionService<P : Permission> {
///////////////////////////////////////////////////////////////////////////
@Throws(DuplicatedPermissionRegistrationException::class)
/**
* 申请并注册一个权限 [Permission].
*
* @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出.
*/
@Throws(PermissionRegistryConflictException::class)
public fun register(
id: PermissionId,
description: String,
@ -51,8 +74,28 @@ public interface PermissionService<P : Permission> {
///////////////////////////////////////////////////////////////////////////
public fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P)
public fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P)
/**
* 授予 [permitteeId] [permission] 权限
*
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
*
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
*/
public fun permit(permitteeId: PermitteeId, permission: P)
/**
* 撤销 [permitteeId] [permission] 授权
*
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
*
* @param recursive `true` 时递归撤销所有子权限.
* 例如, [permission] "*:*",
* recursive `true` 时撤销全部权限 (因为所有权限都是 "*:*" 的子权限);
* 而为 `false` 时仅撤销 "*:*" 本身, 而不会影响子权限.
*
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
*/
public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean)
public companion object {
internal var instanceField: PermissionService<*>? = null
@ -63,7 +106,7 @@ public interface PermissionService<P : Permission> {
get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.")
public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P =
get(id) ?: throw PermissionNotFoundException(id)
get(id) ?: throw NoSuchElementException("Permission not found: $id")
internal fun PermissionService<*>.allocatePermissionIdForPlugin(name: String, id: String) =
PermissionId("plugin.${name.toLowerCase()}", id.toLowerCase())
@ -72,81 +115,68 @@ public interface PermissionService<P : Permission> {
public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this)
public fun PermissibleIdentifier.grantPermission(permission: Permission) {
INSTANCE.checkType(permission::class).grant(this, permission)
public fun PermitteeId.grantPermission(permission: Permission) {
INSTANCE.checkType(permission::class).permit(this, permission)
}
public fun PermissibleIdentifier.grantPermission(permissionId: PermissionId) {
public fun PermitteeId.grantPermission(permissionId: PermissionId) {
grantPermission(permissionId.findCorrespondingPermissionOrFail())
}
public fun PermissibleIdentifier.denyPermission(permission: Permission) {
INSTANCE.checkType(permission::class).deny(this, permission)
public fun PermitteeId.denyPermission(permission: Permission, recursive: Boolean) {
INSTANCE.checkType(permission::class).cancel(this, permission, recursive)
}
public fun PermissibleIdentifier.denyPermission(permissionId: PermissionId) {
denyPermission(permissionId.findCorrespondingPermissionOrFail())
public fun PermitteeId.denyPermission(permissionId: PermissionId, recursive: Boolean) {
denyPermission(permissionId.findCorrespondingPermissionOrFail(), recursive)
}
public fun Permissible.hasPermission(permission: Permission): Boolean =
public fun Permittee.hasPermission(permission: Permission): Boolean =
permission.testPermission(this@hasPermission)
public fun PermissibleIdentifier.hasPermission(permission: Permission): Boolean =
public fun PermitteeId.hasPermission(permission: Permission): Boolean =
permission.testPermission(this@hasPermission)
public fun PermissibleIdentifier.hasPermission(permissionId: PermissionId): Boolean {
public fun PermitteeId.hasPermission(permissionId: PermissionId): Boolean {
val instance = permissionId.findCorrespondingPermissionOrFail()
return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance)
}
public fun Permissible.hasPermission(permissionId: PermissionId): Boolean =
public fun Permittee.hasPermission(permissionId: PermissionId): Boolean =
permissionId.testPermission(this@hasPermission)
public fun Permissible.getGrantedPermissions(): Sequence<Permission> =
INSTANCE.getGrantedPermissions(this@getGrantedPermissions.identifier)
public fun Permittee.getPermittedPermissions(): Sequence<Permission> =
INSTANCE.getPermittedPermissions(this@getPermittedPermissions.permitteeId)
public fun Permissible.grantPermission(vararg permissions: Permission) {
public fun Permittee.grantPermission(vararg permissions: Permission) {
for (permission in permissions) {
INSTANCE.checkType(permission::class).grant(this.identifier, permission)
INSTANCE.checkType(permission::class).permit(this.permitteeId, permission)
}
}
public fun Permissible.denyPermission(vararg permissions: Permission) {
public fun Permittee.denyPermission(vararg permissions: Permission, recursive: Boolean) {
for (permission in permissions) {
INSTANCE.checkType(permission::class).deny(this.identifier, permission)
INSTANCE.checkType(permission::class).cancel(this.permitteeId, permission, recursive)
}
}
public fun PermissibleIdentifier.getGrantedPermissions(): Sequence<Permission> =
INSTANCE.getGrantedPermissions(this@getGrantedPermissions)
public fun PermitteeId.getPermittedPermissions(): Sequence<Permission> =
INSTANCE.getPermittedPermissions(this@getPermittedPermissions)
public fun Permission.testPermission(permissible: Permissible): Boolean =
INSTANCE.checkType(this::class).testPermission(permissible.identifier, this@testPermission)
public fun Permission.testPermission(permittee: Permittee): Boolean =
INSTANCE.checkType(this::class).testPermission(permittee.permitteeId, this@testPermission)
public fun Permission.testPermission(permissibleIdentifier: PermissibleIdentifier): Boolean =
INSTANCE.checkType(this::class).testPermission(permissibleIdentifier, this@testPermission)
public fun Permission.testPermission(permitteeId: PermitteeId): Boolean =
INSTANCE.checkType(this::class).testPermission(permitteeId, this@testPermission)
public fun PermissionId.testPermission(permissible: Permissible): Boolean {
public fun PermissionId.testPermission(permittee: Permittee): Boolean {
val p = INSTANCE[this] ?: return false
return p.testPermission(permissible)
return p.testPermission(permittee)
}
public fun PermissionId.testPermission(permissible: PermissibleIdentifier): Boolean {
public fun PermissionId.testPermission(permissible: PermitteeId): Boolean {
val p = INSTANCE[this] ?: return false
return p.testPermission(permissible)
}
}
}
@OptIn(ExperimentalPermission::class)
internal fun PermissionService<*>.checkType(permissionType: KClass<out Permission>): PermissionService<Permission> {
return PermissionService.INSTANCE.run {
require(this.permissionType.isSuperclassOf(permissionType)) {
"Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " +
"Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get"
}
@Suppress("UNCHECKED_CAST")
this as PermissionService<Permission>
}
}
}

View File

@ -14,15 +14,15 @@ package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.command.CommandSender
/**
* 拥有权限的对象.
* 被赋予权限的对象, '被许可人'.
*
* 典型的实例为 [CommandSender]
* 被许可人自身不持有拥有的权限列表, 而是拥有 [PermitteeId], 标识自己的身份, [权限服务][PermissionService] 处理.
*
* 注意: 请不要自主实现 [Permissible]
* **注意**: 请不要自主实现 [Permittee]
*
* @see CommandSender
*/
@ExperimentalPermission
public interface Permissible {
public val identifier: PermissibleIdentifier
@PermissionImplementation
public interface Permittee {
public val permitteeId: PermitteeId
}

View File

@ -0,0 +1,307 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.permission
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.internal.data.map
import net.mamoe.mirai.console.internal.permission.parseFromStringImpl
import net.mamoe.mirai.console.permission.AbstractPermitteeId.AnyMember
import net.mamoe.mirai.console.permission.AbstractPermitteeId.ExactMember
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.User
/**
* [被许可人][Permittee] 的标识符
*
* 一个这样的标识符即可代表特定的单个 [Permittee], 也可以表示多个同类 [Permittee].
*
* ### 获取 [PermitteeId]
* 总是通过 [Permittee.permitteeId].
*/
@PermissionImplementation
public interface PermitteeId {
/**
* 直接父 [PermitteeId]. 在检查权限时会首先检查自己, 再递归检查父类.
*
* @see allParentsWithSelf
* @see allParents
*/
public val directParents: Array<out PermitteeId>
/**
* 转换为字符串表示. 用于权限服务识别和指令的解析.
*/
public fun asString(): String
public companion object {
/**
* [this] [this] 的任意一个直接或间接父 [PermitteeId.asString] `this.asString` 相同时返回 `true`
*/
@JvmStatic
public fun PermitteeId.hasChild(child: PermitteeId): Boolean {
return allParentsWithSelf.any { it.asString() == child.asString() } // asString is for compatibility issue with external implementations
}
/**
* 获取所有直接或间接父类的 [PermitteeId].
*/
@get:JvmStatic
public val PermitteeId.allParentsWithSelf: Sequence<PermitteeId>
get() = sequence {
yield(this@allParentsWithSelf)
yieldAll(allParents)
}
/**
* 获取所有直接或间接父类的 [PermitteeId], 返回包含 `this` + 这些父类 [Sequence]
*/
@get:JvmStatic
public val PermitteeId.allParents: Sequence<PermitteeId>
get() = directParents.asSequence().flatMap { it.allParentsWithSelf }
}
}
/**
* 内建的 [PermitteeId].
*
* - 若指令 A 的权限被授予给 [AnyMember], 那么一个 [ExactMember] 可以执行这个指令.
*
* ```
* Console AnyContact
*
* |
* +---------------------------+------------------------+
* | |
* AnyUser AnyGroup
*
* | |
* +--------------+---------------------+ |
* | | | |
* AnyFriend | AnyMemberFromAnyGroup |
* | |
* | | | |
* | | +--------+--------------+ |
* | | | | |
* | | | AnyTempFromAnyGroup |
* | | | |
* | | AnyMember | |
* | | | |
* | ExactUser | | ExactGroup
* | | |
* | | | | |
* +------------+ +----------+ |
* | | |
* ExactFriend ExactMember |
* |
* | |
* +-----------------------+
* |
* |
* ExactTemp
* ```
*/
@Serializable(with = AbstractPermitteeId.AsStringSerializer::class)
public sealed class AbstractPermitteeId(
public final override vararg val directParents: PermitteeId,
) : PermitteeId {
public final override fun toString(): String = asString()
public companion object {
/**
* [AbstractPermitteeId.asString] 解析 [AbstractPermitteeId]
*/
@JvmStatic
public fun parseFromString(string: String): AbstractPermitteeId = parseFromStringImpl(string)
}
/**
* 使用 [asString] 序列化 [AbstractPermitteeId]
*/
@ConsoleExperimentalApi
public object AsStringSerializer : KSerializer<AbstractPermitteeId> by String.serializer().map(
serializer = AbstractPermitteeId::asString,
deserializer = ::parseFromString
)
/**
* 表示任何群对象. (不是指群成员, 而是指这个 '群')
*
* - **直接父标识符**: [AnyContact]
* - **间接父标识符**:
* - 字符串表示: "g*"
*
* @see AnyMember
*/
public object AnyGroup : AbstractPermitteeId(AnyContact) {
override fun asString(): String = "g*"
}
/**
* 表示一个群
*
* - **直接父标识符**: [AnyGroup]
* - **间接父标识符**: [AnyContact]
* - 字符串表示: "g$groupId"
*/
public data class ExactGroup(public val groupId: Long) : AbstractPermitteeId(AnyGroup) {
override fun asString(): String = "g$groupId"
}
/**
* 表示来自一个群的任意一个成员
*
* - **直接父标识符**: [AnyMemberFromAnyGroup]
* - **间接父标识符**: [AnyUser], [AnyContact]
* - 字符串表示: "m$groupId.*"
*/
public data class AnyMember(public val groupId: Long) : AbstractPermitteeId(AnyMemberFromAnyGroup) {
override fun asString(): String = "m$groupId.*"
}
/**
* 表示来自任意群的任意一个成员
*
* - **直接父标识符**: [AnyUser]
* - **间接父标识符**: [AnyContact]
* - 字符串表示: "m*"
*/
public object AnyMemberFromAnyGroup : AbstractPermitteeId(AnyUser) {
override fun asString(): String = "m*"
}
/**
* 表示唯一的一个群成员
*
* - **直接父标识符**: [AnyMember], [ExactUser]
* - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact]
* - 字符串表示: "m$groupId.$memberId"
*/
public data class ExactMember(
public val groupId: Long,
public val memberId: Long,
) : AbstractPermitteeId(AnyMember(groupId), ExactUser(memberId)) {
override fun asString(): String = "m$groupId.$memberId"
}
/**
* 表示任何好友
*
* - **直接父标识符**: [AnyUser]
* - **间接父标识符**: [AnyContact]
* - 字符串表示: "f*"
*/
public object AnyFriend : AbstractPermitteeId(AnyUser) {
override fun asString(): String = "f*"
}
/**
* 表示唯一的一个好友
*
* - **直接父标识符**: [ExactUser]
* - **间接父标识符**: [AnyUser], [AnyContact]
* - 字符串表示: "f$id"
*/
public data class ExactFriend(
public val id: Long,
) : AbstractPermitteeId(ExactUser(id)) {
override fun asString(): String = "f$id"
}
/**
* 表示任何一个通过一个群 *在临时会话发送消息的* [群成员][Member]
*
* - **直接父标识符**: [AnyMember], [AnyTempFromAnyGroup]
* - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact]
* - 字符串表示: "t$groupId.*"
*/
public data class AnyTemp(
public val groupId: Long,
) : AbstractPermitteeId(AnyMember(groupId), AnyTempFromAnyGroup) {
override fun asString(): String = "t$groupId.*"
}
/**
* 表示任何一个 *在临时会话发送消息的* [群成员][Member]
*
* - **直接父标识符**: [AnyUser]
* - **间接父标识符**: [AnyContact]
* - 字符串表示: "t*"
*/
public object AnyTempFromAnyGroup : AbstractPermitteeId(AnyUser) {
override fun asString(): String = "t*"
}
/**
* 表示唯一的一个 *在临时会话发送消息的* [群成员][Member]
*
* - **直接父标识符**: [ExactMember]
* - **间接父标识符**: [AnyUser], [AnyMember], [ExactUser], [AnyContact]
* - 字符串表示: "t$groupId.$memberId"
*/
public data class ExactTemp(
public val groupId: Long,
public val memberId: Long,
) : AbstractPermitteeId(ExactMember(groupId, memberId)) {
override fun asString(): String = "t$groupId.$memberId"
}
/**
* 表示任何 [用户][User]
*
* - **直接父标识符**: [AnyContact]
* - **间接父标识符**:
* - 字符串表示: "u*"
*/
public object AnyUser : AbstractPermitteeId(AnyContact) {
override fun asString(): String = "u*"
}
/**
* 表示任何 [用户][User]
*
* - **直接父标识符**: [AnyUser]
* - **间接父标识符**: [AnyContact]
* - 字符串表示: "u$id"
*/
public data class ExactUser(
public val id: Long,
) : AbstractPermitteeId(AnyUser) {
override fun asString(): String = "u$id"
}
/**
* 表示任何 [联系对象][Contact]
*
* - **直接父标识符**:
* - **间接父标识符**:
* - 字符串表示: "*"
*/
public object AnyContact : AbstractPermitteeId() {
override fun asString(): String = "*"
}
/**
* 表示控制台
*
* - **直接父标识符**:
* - **间接父标识符**:
* - 字符串表示: "console"
*/
public object Console : AbstractPermitteeId() {
override fun asString(): String = "console"
}
}

View File

@ -12,8 +12,8 @@
package net.mamoe.mirai.console.util
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.contact.*
/**
* 为简化操作提供的一些工具
@ -32,7 +32,7 @@ public object ContactUtils {
@JvmOverloads
@JvmStatic
@ConsoleExperimentalApi
public fun Bot.getContact(id: Long, includeMembers: Boolean = false): Contact {
public fun Bot.getContact(id: Long, includeMembers: Boolean = true): Contact {
return getContactOrNull(id, includeMembers)
?: throw NoSuchElementException("Contact $id not found for bot ${this.id}")
}
@ -45,7 +45,7 @@ public object ContactUtils {
@JvmOverloads
@JvmStatic
@ConsoleExperimentalApi
public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = false): Contact? {
public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = true): Contact? {
return getFriendOrGroupOrNull(id) ?: kotlin.run {
if (includeMembers) {
groups.asSequence().flatMap { it.members.asSequence() }.firstOrNull { it.id == id }
@ -77,4 +77,18 @@ public object ContactUtils {
return this.friends.getOrNull(id) ?: this.groups.getOrNull(id)
}
/**
* [ContactOrBot] 输出为字符串表示.
*/
@JvmName("renderContactOrName")
@JvmStatic
public fun ContactOrBot.render(): String {
return when (this) {
is Bot -> "Bot $nick($id)"
is Group -> "Group $name($id)"
is Friend -> "Friend $nick($id)"
is Member -> "Member $nameCardOrNick(${group.id}.$id)"
else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}")
}
}
}