Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-05-13 19:42:24 +08:00
commit 9e9399a463
11 changed files with 837 additions and 387 deletions

View File

@ -1,3 +1,19 @@
<div align="center">
<img width="160" src="http://img.mamoe.net/2020/02/16/a759783b42f72.png" alt="logo"></br>
<img width="95" src="http://img.mamoe.net/2020/02/16/c4aece361224d.png" alt="title">
----
Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人库
这个项目的名字来源于
<p><a href = "http://www.kyotoanimation.co.jp/">京都动画</a>作品<a href = "https://zh.moegirl.org/zh-hans/%E5%A2%83%E7%95%8C%E7%9A%84%E5%BD%BC%E6%96%B9">《境界的彼方》</a><a href = "https://zh.moegirl.org/zh-hans/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5">栗山未来(Kuriyama <b>Mirai</b>)</a></p>
<p><a href = "https://www.crypton.co.jp/">CRYPTON</a><a href = "https://www.crypton.co.jp/miku_eng">初音未来</a>为代表的创作与活动<a href = "https://magicalmirai.com/2019/index_en.html">(Magical <b>Mirai</b>)</a></p>
图标以及形象由画师<a href = "">DazeCake</a>绘制
</div>
# mirai-console
高效率插件支持 QQ 机器人框架, 机器人核心来自 [mirai](https://github.com/mamoe/mirai)

View File

@ -21,6 +21,9 @@ kotlin {
languageSettings.useExperimentalAnnotation("kotlin.OptIn")
languageSettings.progressiveMode = true
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
}
}
}

View File

@ -1,100 +0,0 @@
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.utils
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.GroupContactCommandSender
import net.mamoe.mirai.contact.Group
/**
* this output type of that arg
* input is always String
*/
abstract class CommandArgParser<T : Any> {
abstract fun parse(s: String, sender: CommandSender): T
protected inline fun parseError(message: String, cause: Throwable? = null): Nothing {
throw ParserException(message, cause)
}
}
@Suppress("FunctionName")
inline fun <T : Any> CommandArgParser(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgParser<T> {
return object : CommandArgParser<T>() {
override fun parse(s: String, sender: CommandSender): T = parser(s, sender)
}
}
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
*/
class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
inline fun Int.Companion.parser(): CommandArgParser<Int> = IntArgParser
inline fun Long.Companion.parser(): CommandArgParser<Long> = LongArgParser
inline fun Byte.Companion.parser(): CommandArgParser<Byte> = ByteArgParser
inline fun Short.Companion.parser(): CommandArgParser<Short> = ShortArgParser
inline fun Float.Companion.parser(): CommandArgParser<Float> = FloatArgParser
inline fun Double.Companion.parser(): CommandArgParser<Double> = DoubleArgParser
object IntArgParser : CommandArgParser<Int>() {
override fun parse(s: String, sender: CommandSender): Int = s.toIntOrNull() ?: parseError("无法解析 $s 为整数")
}
object LongArgParser : CommandArgParser<Long>() {
override fun parse(s: String, sender: CommandSender): Long = s.toLongOrNull() ?: parseError("无法解析 $s 为长整数")
}
object ShortArgParser : CommandArgParser<Short>() {
override fun parse(s: String, sender: CommandSender): Short = s.toShortOrNull() ?: parseError("无法解析 $s 为短整数")
}
object ByteArgParser : CommandArgParser<Byte>() {
override fun parse(s: String, sender: CommandSender): Byte = s.toByteOrNull() ?: parseError("无法解析 $s 为字节")
}
object DoubleArgParser : CommandArgParser<Double>() {
override fun parse(s: String, sender: CommandSender): Double =
s.toDoubleOrNull() ?: parseError("无法解析 $s 为小数")
}
object FloatArgParser : CommandArgParser<Float>() {
override fun parse(s: String, sender: CommandSender): Float =
s.toFloatOrNull() ?: parseError("无法解析 $s 为小数")
}
object StringArgParser : CommandArgParser<String>() {
override fun parse(s: String, sender: CommandSender): String = s
}
/**
* require a bot that already login in console
* input: Bot UIN
* output: Bot
* errors: String->Int convert, Bot Not Exist
*/
object ExistBotArgParser : CommandArgParser<Bot>() {
override fun parse(s: String, sender: CommandSender): Bot {
val uin = s.toLongOrNull() ?: parseError("无法识别机器人账号 $s")
return try {
Bot.getInstance(uin)
} catch (e: NoSuchElementException) {
error("无法找到 Bot $uin")
}
}
}
object ExistGroupArgParser : CommandArgParser<Group>() {
override fun parse(s: String, sender: CommandSender): Group {
if ((s == "" || s == "~") && sender is GroupContactCommandSender) {
return sender.contact as Group
}
val code = s.toLongOrNull() ?: parseError("无法识别群号码 $s")
TODO()
}
}

View File

@ -1,272 +0,0 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.utils.fuzzySearchMember
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.content
import java.lang.NumberFormatException
/**
* this output type of that arg
* input is always String
*/
interface CommandArg<T:Any>{
operator fun invoke(s:String, commandSender: CommandSender):T = parse(s,commandSender)
operator fun invoke(s:SingleMessage, commandSender: CommandSender):T = parse(s,commandSender)
fun parse(s:String, commandSender: CommandSender):T
fun parse(s:SingleMessage, commandSender: CommandSender):T
}
abstract class CommandArgImpl<T:Any>(): CommandArg<T> {
override fun parse(s: SingleMessage, commandSender: CommandSender): T = parse(s.content,commandSender)
}
class IntArg: CommandArgImpl<Int>(){
override fun parse(s: String, commandSender: CommandSender): Int {
return try{
s.toInt()
}catch (e:Exception){
error("无法识别整数$s")
}
}
}
class LongArg: CommandArgImpl<Long>(){
override fun parse(s: String, commandSender: CommandSender): Long {
return try{
s.toLong()
}catch (e:Exception){
error("无法识别长整数$s")
}
}
}
class DoubleArg: CommandArgImpl<Double>(){
override fun parse(s: String, commandSender: CommandSender): Double {
return try{
s.toDouble()
}catch (e:Exception){
error("无法识别小数$s")
}
}
}
class FloatArg: CommandArgImpl<Float>(){
override fun parse(s: String, commandSender: CommandSender): Float{
return try{
s.toFloat()
}catch (e:Exception){
error("无法识别小数$s")
}
}
}
class BooleanArg: CommandArgImpl<Boolean>(){
override fun parse(s: String, commandSender: CommandSender): Boolean {
return s.equals("true",true) || s.equals("yes",true)
}
}
class StringArg: CommandArgImpl<String>(){
override fun parse(s: String, commandSender: CommandSender): String {
return s
}
}
/**
* require a bot that already login in console
* input: Bot UIN
* output: Bot
* errors: String->Int convert, Bot Not Exist
*/
class ExistBotArg : CommandArgImpl<Bot>() {
override fun parse(s: String, commandSender: CommandSender): Bot {
val uin = try {
s.toLong()
} catch (e: Exception) {
error("无法识别QQ UIN$s")
}
return try {
Bot.getInstance(uin)
} catch (e: NoSuchElementException) {
error("无法找到Bot $uin")
}
}
}
class ExistFriendArg: CommandArgImpl<Friend>(){
//Bot.friend
//friend
//~ = self
override fun parse(s: String, commandSender: CommandSender): Friend {
if(s == "~"){
if(commandSender !is BotAware){
error("无法解析~作为默认")
}
val targetID = when (commandSender) {
is GroupContactCommandSender -> commandSender.realSender.id
is ContactCommandSender -> commandSender.contact.id
else -> error("无法解析~作为默认")
}
return try{
commandSender.bot.friends[targetID]
}catch (e:NoSuchElementException){
error("无法解析~作为默认")
}
}
if(commandSender is BotAware){
return try{
commandSender.bot.friends[s.toLong()]
}catch (e:NoSuchElementException){
error("无法找到" + s + "这个好友")
}catch (e:NumberFormatException){
error("无法解析$s")
}
}else{
with(s.split(".")){
if(this.size != 2){
error("无法解析$s, 格式应为Bot.Friend")
}
return try{
Bot.getInstance(this[0].toLong()).friends[this[1].toLong()]
}catch (e:NoSuchElementException){
error("无法找到好友或Bot")
}catch (e:NumberFormatException){
error("无法解析$s")
}
}
}
}
override fun parse(s: SingleMessage, commandSender: CommandSender): Friend {
return if(s is At){
assert(commandSender is GroupContactCommandSender)
return try {
(commandSender as BotAware).bot.friends[s.target]
}catch (e:NoSuchElementException){
error("At的对象非Bot好友")
}
}else{
error("无法识别Member" + s.content)
}
}
}
class ExistGroupArg: CommandArgImpl<Group>(){
override fun parse(s: String, commandSender: CommandSender): Group {
//by default
if ((s == "" || s == "~") && commandSender is GroupContactCommandSender) {
return commandSender.contact as Group
}
//from bot to group
if (commandSender is BotAware) {
val code = try {
s.toLong()
} catch (e: NoSuchElementException) {
error("无法识别Group Code$s")
}
return try {
commandSender.bot.getGroup(code)
} catch (e: NoSuchElementException) {
error("无法找到Group " + code + " from Bot " + commandSender.bot.id)
}
}
//from console/other
return with(s.split(".")) {
if (this.size != 2) {
error("请使用BotQQ号.群号 来表示Bot的一个群")
}
try {
Bot.getInstance(this[0].toLong()).getGroup(this[1].toLong())
}catch (e:NoSuchElementException){
error("无法找到" + this[0] + "" + this[1] + "")
}catch (e:NumberFormatException){
error("无法识别群号或机器人UIN")
}
}
}
}
class ExistMemberArg: CommandArgImpl<Member>(){
//后台: Bot.Group.Member[QQ/名片]
//私聊: Group.Member[QQ/名片]
//群内: Q号
//群内: 名片
override fun parse(s: String, commandSender: CommandSender): Member {
if(commandSender !is BotAware){
with(s.split(".")){
if(this.size < 3){
error("无法识别Member, 请使用Bot.Group.Member[QQ/名片]的格式")
}
val bot = try {
Bot.getInstance(this[0].toLong())
}catch (e:NoSuchElementException){
error("无法找到Bot")
}catch (e:NumberFormatException){
error("无法识别Bot")
}
val group = try{
bot.getGroup(this[1].toLong())
}catch (e:NoSuchElementException){
error("无法找到Group")
}catch (e:NumberFormatException){
error("无法识别Group")
}
val memberIndex = this.subList(2,this.size).joinToString(".")
return try{
group.members[memberIndex.toLong()]
}catch (ignored:Exception){
group.fuzzySearchMember(memberIndex)?: error("无法找到成员$memberIndex")
}
}
}else {
val bot = commandSender.bot
if(commandSender is GroupContactCommandSender){
val group = commandSender.contact as Group
return try {
group.members[s.toLong()]
} catch (ignored: Exception) {
group.fuzzySearchMember(s) ?: error("无法找到成员$s")
}
}else {
with(s.split(".")) {
if (this.size < 2) {
error("无法识别Member, 请使用Group.Member[QQ/名片]的格式")
}
val group = try {
bot.getGroup(this[0].toLong())
} catch (e: NoSuchElementException) {
error("无法找到Group")
} catch (e: NumberFormatException) {
error("无法识别Group")
}
val memberIndex = this.subList(1, this.size).joinToString(".")
return try {
group.members[memberIndex.toLong()]
} catch (ignored: Exception) {
group.fuzzySearchMember(memberIndex) ?: error("无法找到成员$memberIndex")
}
}
}
}
}
override fun parse(s: SingleMessage, commandSender: CommandSender): Member {
return if(s is At){
assert(commandSender is GroupContactCommandSender)
((commandSender as GroupContactCommandSender).contact as Group).members[s.target]
}else{
error("无法识别Member" + s.content)
}
}
}

View File

@ -0,0 +1,286 @@
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.utils.fuzzySearchMember
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.content
import kotlin.contracts.contract
/**
* this output type of that arg
* input is always String
*/
abstract class CommandArgParser<out T : Any> {
abstract fun parse(s: String, sender: CommandSender): T
open fun parse(s: SingleMessage, sender: CommandSender): T = parse(s.content, sender)
}
@Suppress("unused")
@JvmSynthetic
inline fun CommandArgParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
throw ParserException(message, cause)
}
@JvmSynthetic
inline fun CommandArgParser<*>.checkArgument(
condition: Boolean,
crossinline message: () -> String = { "Check failed." }
) {
contract {
returns() implies condition
}
if (!condition) illegalArgument(message())
}
/**
* 创建匿名 [CommandArgParser]
*/
@Suppress("FunctionName")
@JvmSynthetic
inline fun <T : Any> CommandArgParser(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgParser<T> = object : CommandArgParser<T>() {
override fun parse(s: String, sender: CommandSender): T = parser(s, sender)
}
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
*/
class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
object IntArgParser : CommandArgParser<Int>() {
override fun parse(s: String, sender: CommandSender): Int =
s.toIntOrNull() ?: illegalArgument("无法解析 $s 为整数")
}
object LongArgParser : CommandArgParser<Long>() {
override fun parse(s: String, sender: CommandSender): Long =
s.toLongOrNull() ?: illegalArgument("无法解析 $s 为长整数")
}
object ShortArgParser : CommandArgParser<Short>() {
override fun parse(s: String, sender: CommandSender): Short =
s.toShortOrNull() ?: illegalArgument("无法解析 $s 为短整数")
}
object ByteArgParser : CommandArgParser<Byte>() {
override fun parse(s: String, sender: CommandSender): Byte =
s.toByteOrNull() ?: illegalArgument("无法解析 $s 为字节")
}
object DoubleArgParser : CommandArgParser<Double>() {
override fun parse(s: String, sender: CommandSender): Double =
s.toDoubleOrNull() ?: illegalArgument("无法解析 $s 为小数")
}
object FloatArgParser : CommandArgParser<Float>() {
override fun parse(s: String, sender: CommandSender): Float =
s.toFloatOrNull() ?: illegalArgument("无法解析 $s 为小数")
}
object StringArgParser : CommandArgParser<String>() {
override fun parse(s: String, sender: CommandSender): String = s
}
object BooleanArgParser : CommandArgParser<Boolean>() {
override fun parse(s: String, sender: CommandSender): Boolean = s.trim().let { str ->
str.equals("true", ignoreCase = true)
|| str.equals("yes", ignoreCase = true)
|| str.equals("enabled", ignoreCase = true)
}
}
/**
* require a bot that already login in console
* input: Bot UIN
* output: Bot
* errors: String->Int convert, Bot Not Exist
*/
object ExistBotArgParser : CommandArgParser<Bot>() {
override fun parse(s: String, sender: CommandSender): Bot {
val uin = try {
s.toLong()
} catch (e: Exception) {
error("无法识别QQ UIN$s")
}
return try {
Bot.getInstance(uin)
} catch (e: NoSuchElementException) {
error("无法找到Bot $uin")
}
}
}
object ExistFriendArgParser : CommandArgParser<Friend>() {
//Bot.friend
//friend
//~ = self
override fun parse(s: String, sender: CommandSender): Friend {
if (s == "~") {
if (sender !is BotAware) {
illegalArgument("无法解析~作为默认")
}
val targetID = when (sender) {
is GroupContactCommandSender -> sender.realSender.id
is ContactCommandSender -> sender.contact.id
else -> illegalArgument("无法解析~作为默认")
}
return try {
sender.bot.friends[targetID]
} catch (e: NoSuchElementException) {
illegalArgument("无法解析~作为默认")
}
}
if (sender is BotAware) {
return try {
sender.bot.friends[s.toLong()]
} catch (e: NoSuchElementException) {
error("无法找到" + s + "这个好友")
} catch (e: NumberFormatException) {
error("无法解析$s")
}
} else {
s.split(".").let { args ->
if (args.size != 2) {
illegalArgument("无法解析 $s, 格式应为 机器人账号.好友账号")
}
return try {
Bot.getInstance(args[0].toLong()).friends.getOrNull(
args[1].toLongOrNull() ?: illegalArgument("无法解析 $s 为好友")
) ?: illegalArgument("无法找到好友 ${args[1]}")
} catch (e: NoSuchElementException) {
illegalArgument("无法找到机器人账号 ${args[0]}")
}
}
}
}
override fun parse(s: SingleMessage, sender: CommandSender): Friend {
if (s is At) {
assert(sender is GroupContactCommandSender)
return (sender as BotAware).bot.friends.getOrNull(s.target) ?: illegalArgument("At的对象非Bot好友")
} else {
error("无法解析 $s 为好友")
}
}
}
object ExistGroupArgParser : CommandArgParser<Group>() {
override fun parse(s: String, sender: CommandSender): Group {
//by default
if ((s == "" || s == "~") && sender is GroupContactCommandSender) {
return sender.contact as Group
}
//from bot to group
if (sender is BotAware) {
val code = try {
s.toLong()
} catch (e: NoSuchElementException) {
error("无法识别Group Code$s")
}
return try {
sender.bot.getGroup(code)
} catch (e: NoSuchElementException) {
error("无法找到Group " + code + " from Bot " + sender.bot.id)
}
}
//from console/other
return with(s.split(".")) {
if (this.size != 2) {
error("请使用BotQQ号.群号 来表示Bot的一个群")
}
try {
Bot.getInstance(this[0].toLong()).getGroup(this[1].toLong())
} catch (e: NoSuchElementException) {
error("无法找到" + this[0] + "" + this[1] + "")
} catch (e: NumberFormatException) {
error("无法识别群号或机器人UIN")
}
}
}
}
object ExistMemberArgParser : CommandArgParser<Member>() {
//后台: Bot.Group.Member[QQ/名片]
//私聊: Group.Member[QQ/名片]
//群内: Q号
//群内: 名片
override fun parse(s: String, sender: CommandSender): Member {
if (sender !is BotAware) {
with(s.split(".")) {
checkArgument(this.size >= 3) {
"无法识别Member, 请使用Bot.Group.Member[QQ/名片]的格式"
}
val bot = try {
Bot.getInstance(this[0].toLong())
} catch (e: NoSuchElementException) {
illegalArgument("无法找到Bot")
} catch (e: NumberFormatException) {
illegalArgument("无法识别Bot")
}
val group = try {
bot.getGroup(this[1].toLong())
} catch (e: NoSuchElementException) {
illegalArgument("无法找到Group")
} catch (e: NumberFormatException) {
illegalArgument("无法识别Group")
}
val memberIndex = this.subList(2, this.size).joinToString(".")
return group.members.getOrNull(memberIndex.toLong())
?: group.fuzzySearchMember(memberIndex)
?: error("无法找到成员$memberIndex")
}
} else {
val bot = sender.bot
if (sender is GroupContactCommandSender) {
val group = sender.contact as Group
return try {
group.members[s.toLong()]
} catch (ignored: Exception) {
group.fuzzySearchMember(s) ?: illegalArgument("无法找到成员$s")
}
} else {
with(s.split(".")) {
if (this.size < 2) {
illegalArgument("无法识别Member, 请使用Group.Member[QQ/名片]的格式")
}
val group = try {
bot.getGroup(this[0].toLong())
} catch (e: NoSuchElementException) {
illegalArgument("无法找到Group")
} catch (e: NumberFormatException) {
illegalArgument("无法识别Group")
}
val memberIndex = this.subList(1, this.size).joinToString(".")
return try {
group.members[memberIndex.toLong()]
} catch (ignored: Exception) {
group.fuzzySearchMember(memberIndex) ?: illegalArgument("无法找到成员$memberIndex")
}
}
}
}
}
override fun parse(s: SingleMessage, sender: CommandSender): Member {
return if (s is At) {
checkArgument(sender is GroupContactCommandSender)
(sender.contact as Group).members[s.target]
} else {
illegalArgument("无法识别Member" + s.content)
}
}
}

View File

@ -0,0 +1,139 @@
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
import kotlin.reflect.KClass
/**
* 指令描述. 包含名称, 权限要求, 参数解析器环境, 参数列表.
*/
class CommandDescriptor(
/**
* 包含子命令的全名. "`group kick`", 其中 `kick` `group` 的子命令
*/
val fullName: String,
/**
* 指令参数解析器环境.
*/
val context: CommandParserContext,
/**
* 指令参数列表, 有顺序.
*/
val params: List<CommandParam<*>>,
/**
* 指令权限
*
* @see CommandPermission.or 要求其中一个权限
* @see CommandPermission.and 同时要求两个权限
*/
val permission: CommandPermission = CommandPermission.Default
)
/**
* 构建一个 [CommandDescriptor]
*/
@Suppress("FunctionName")
inline fun CommandDescriptor(
fullName: String,
block: CommandDescriptorBuilder.() -> Unit
): CommandDescriptor = CommandDescriptorBuilder(fullName).apply(block).build()
class CommandDescriptorBuilder(
val fullName: String
) {
@PublishedApi
internal var context: CommandParserContext = CommandParserContext.Builtins
@PublishedApi
internal var permission: CommandPermission = CommandPermission.Default
@PublishedApi
internal var params: MutableList<CommandParam<*>> = mutableListOf()
/** 增加指令参数解析器列表 */
@JvmSynthetic
inline fun context(block: CommandParserContextBuilder.() -> Unit) {
this.context += CommandParserContext(block)
}
/** 增加指令参数解析器列表 */
@JvmSynthetic
inline fun context(context: CommandParserContext): CommandDescriptorBuilder = apply {
this.context += context
}
/** 设置权限要求 */
fun permission(permission: CommandPermission): CommandDescriptorBuilder = apply {
this.permission = permission
}
/** 设置权限要求 */
@JvmSynthetic
inline fun permission(crossinline block: CommandSender.() -> Boolean) {
this.permission = AnonymousCommandPermission(block)
}
fun param(vararg params: CommandParam<*>): CommandDescriptorBuilder = apply {
this.params.addAll(params)
}
@JvmSynthetic
fun <T : Any> param(
name: String?,
type: KClass<T>,
overrideParser: CommandArgParser<T>? = null
): CommandDescriptorBuilder = apply {
this.params.add(CommandParam(name, type).apply { this.parser = overrideParser })
}
fun <T : Any> param(
name: String?,
type: Class<T>,
overrideParser: CommandArgParser<T>? = null
): CommandDescriptorBuilder =
param(name, type, overrideParser)
inline fun <reified T : Any> param(
name: String? = null,
overrideParser: CommandArgParser<T>? = null
): CommandDescriptorBuilder =
param(name, T::class, overrideParser)
@JvmSynthetic
fun param(vararg pairs: Pair<String?, KClass<*>>): CommandDescriptorBuilder = apply {
for (pair in pairs) {
this.params.add(CommandParam(pair.first, pair.second))
}
}
@JvmSynthetic
fun params(block: ParamBlock.() -> Unit): CommandDescriptorBuilder = apply {
ParamBlock(params).apply(block)
}
@JvmSynthetic
fun param(type: KClass<*>): CommandDescriptorBuilder = apply {
this.params.add(CommandParam(null, type))
}
fun param(type: Class<*>): CommandDescriptorBuilder = apply {
this.params.add(CommandParam(null, type.kotlin))
}
fun build(): CommandDescriptor = CommandDescriptor(fullName, context, params, permission)
}
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
inline class ParamBlock internal constructor(@PublishedApi internal val list: MutableList<CommandParam<*>>) {
/** 添加一个名称为 [this], 类型为 [klass] 的参数. 返回添加成功的对象 */
infix fun <T : Any> String.typed(klass: KClass<T>): CommandParam<T> =
CommandParam(this, klass).also { list.add(it) }
/** 指定 [CommandParam.overrideParser] */
infix fun <T : Any> CommandParam<T>.using(parser: CommandArgParser<T>): CommandParam<T> =
this.apply { this.parser = parser }
/** 覆盖 [CommandArgParser] */
inline infix fun <reified T : Any> String.using(parser: CommandArgParser<T>): CommandParam<T> =
this typed T::class using parser
}

View File

@ -0,0 +1,36 @@
@file:Suppress("unused")
package net.mamoe.mirai.console.command
import kotlin.reflect.KClass
/**
* 指令形式参数.
*/
data class CommandParam<T : Any>(
/**
* 参数名, `null` 时即为匿名参数.
* 参数名允许重复 (尽管并不建议这样做).
* 参数名仅提供给 [CommandArgParser] 以发送更好的错误信息.
*/
val name: String?,
/**
* 参数类型. 将从 [CommandDescriptor.context] 中寻找 [CommandArgParser] 解析.
*/
val type: KClass<T> // exact type
) {
constructor(name: String?, type: KClass<T>, parser: CommandArgParser<T>) : this(name, type) {
this.parser = parser
}
@JvmField
internal var parser: CommandArgParser<T>? = null
/**
* 覆盖的 [CommandArgParser].
*
* 如果非 `null`, 将不会从 [CommandParserContext] 寻找 [CommandArgParser]
*/
val overrideParser: CommandArgParser<T>? get() = parser
}

View File

@ -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
*/
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.AbstractCommandParserContext.Node
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
/**
* [KClass] [CommandArgParser] 的匹配
*/
interface CommandParserContext {
operator fun <T : Any> get(klass: KClass<T>): CommandArgParser<T>?
/**
* 内建的默认 [CommandArgParser]
*/
object Builtins : CommandParserContext by (CommandParserContext {
Int::class with IntArgParser
Byte::class with ByteArgParser
Short::class with ShortArgParser
Boolean::class with BooleanArgParser
String::class with StringArgParser
Long::class with LongArgParser
Double::class with DoubleArgParser
Float::class with FloatArgParser
Member::class with ExistMemberArgParser
Group::class with ExistGroupArgParser
Bot::class with ExistBotArgParser
})
}
fun <T : Any> CommandParserContext.parserFor(param: CommandParam<T>): CommandArgParser<T>? = this[param.type]
/**
* 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
*/
operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandParserContext {
return object : CommandParserContext {
override fun <T : Any> get(klass: KClass<T>): CommandArgParser<T>? = replacer[klass] ?: this@plus[klass]
}
}
@Suppress("UNCHECKED_CAST")
open class AbstractCommandParserContext(val list: List<Node<*>>) : CommandParserContext {
class Node<T : Any>(
val klass: KClass<T>,
val parser: CommandArgParser<T>
)
override fun <T : Any> get(klass: KClass<T>): CommandArgParser<T>? =
this.list.firstOrNull { it.klass == klass }?.parser as CommandArgParser<T>?
}
/**
* 构建一个 [CommandParserContext].
*
* ```
* CommandParserContext {
* Int::class with IntArgParser
* Member::class with ExistMemberArgParser
* Group::class with { s: String, sender: CommandSender ->
* Bot.getInstance(s.toLong()).getGroup(s.toLong())
* }
* Bot::class with { s: String ->
* Bot.getInstance(s.toLong())
* }
* }
* ```
*/
@Suppress("FunctionName")
@JvmSynthetic
inline fun CommandParserContext(block: CommandParserContextBuilder.() -> Unit): CommandParserContext {
return AbstractCommandParserContext(
CommandParserContextBuilder().apply(block).distinctByReversed { it.klass })
}
/**
* @see CommandParserContext
*/
class CommandParserContextBuilder : MutableList<Node<*>> by mutableListOf() {
@JvmName("add")
inline infix fun <T : Any> KClass<T>.with(parser: CommandArgParser<T>): Node<*> =
Node(this, parser)
/**
* 添加一个指令解析器
*/
@JvmSynthetic
@LowPriorityInOverloadResolution
inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): Node<*> = Node(this, CommandArgParser(parser))
/**
* 添加一个指令解析器
*/
@JvmSynthetic
inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String) -> T
): Node<*> = Node(this, CommandArgParser { s: String, _: CommandSender -> parser(s) })
}
@PublishedApi
internal inline fun <T, K> Iterable<T>.distinctByReversed(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (i in list.indices.reversed()) {
val element = list[i]
if (set.add(element.let(selector))) {
list.add(element)
}
}
return list
}

View File

@ -0,0 +1,204 @@
/*
* 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", "NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.utils.isManager
import net.mamoe.mirai.contact.isAdministrator
import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.isOwner
/**
* 指令权限
*
* @see AnonymousCommandPermission
*/
abstract class CommandPermission {
/**
* 判断 [this] 是否拥有这个指令的权限
*/
abstract fun CommandSender.hasPermission(): Boolean
/**
* 满足两个权限其中一个即可使用指令
*/ // no extension for Java
infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another)
/**
* 同时拥有两个权限才能使用指令
*/ // no extension for Java
infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)
/**
* 任何人都可以使用这个指令
*/
object Any : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean = true
}
/**
* 任何人都不能使用这个指令. 指令只能通过代码在 [CommandManager] 使用
*/
object None : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean = false
}
/**
* 管理员或群主可以使用这个指令
*/
class Operator(
/**
* 指定只有来自某个 [Bot] 的管理员或群主才可以使用这个指令
*/
vararg val fromBot: Long
) : CommandPermission() {
constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isOperator()
}
/**
* 来自任何 [Bot] 的任何一个管理员或群主都可以使用这个指令
*/
companion object Any : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.realSender.isOperator()
}
}
}
/**
* 群主可以使用这个指令
*/
class GroupOwner(
/**
* 指定只有来自某个 [Bot] 的群主才可以使用这个指令
*/
vararg val fromBot: Long
) : CommandPermission() {
constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isOwner()
}
/**
* 来自任何 [Bot] 的任何一个群主都可以使用这个指令
*/
companion object Any : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.realSender.isOwner()
}
}
}
/**
* 管理员 (不包含群主) 可以使用这个指令
*/
class Administrator(
/**
* 指定只有来自某个 [Bot] 的管理员 (不包含群主) 才可以使用这个指令
*/
vararg val fromBot: Long
) : CommandPermission() {
constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isAdministrator()
}
/**
* 来自任何 [Bot] 的任何一个管理员 (不包含群主) 都可以使用这个指令
*/
companion object Any : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.realSender.isAdministrator()
}
}
}
/**
* console 管理员可以使用这个指令
*/
class Manager(
/**
* 指定只有来自某个 [Bot] 的管理员或群主才可以使用这个指令
*/
vararg val fromBot: Long
) : CommandPermission() {
constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isManager
}
/**
* 任何 [Bot] manager 都可以使用这个指令
*/
companion object Any : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean {
return this is GroupContactCommandSender && this.realSender.isManager
}
}
}
/**
* 仅控制台能使用和这个指令
*/
object Console : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean = false
}
companion object {
@JvmStatic
val Default: CommandPermission = Manager or Console
}
}
/**
* 使用 [lambda][block] 快速构造 [CommandPermission]
*/
@JvmSynthetic
@Suppress("FunctionName")
inline fun AnonymousCommandPermission(crossinline block: CommandSender.() -> Boolean): CommandPermission {
return object : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean = block()
}
}
inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
permission.run { this@hasPermission.hasPermission() }
inline fun CommandPermission.hasPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
internal class OrCommandPermission(
private val first: CommandPermission,
private val second: CommandPermission
) : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean {
return this.hasPermission(first) || this.hasPermission(second)
}
}
internal class AndCommandPermission(
private val first: CommandPermission,
private val second: CommandPermission
) : CommandPermission() {
override fun CommandSender.hasPermission(): Boolean {
return this.hasPermission(first) || this.hasPermission(second)
}
}

View File

@ -13,8 +13,16 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.*
import net.mamoe.mirai.console.utils.BotManagers.BOT_MANAGERS
import net.mamoe.mirai.contact.User
import java.io.File
/**
* 判断此用户是否为 console 管理员
*/
val User.isManager: Boolean
get() = this.bot.managers.contains(this.id)
@OptIn(ToBeRemoved::class)
internal object BotManagers {
val config = File("${MiraiConsole.path}/bot.yml").loadAsConfig()

View File

@ -69,16 +69,16 @@ internal fun Throwable.addSuppressedMirai(e: Throwable) {
* XXXXXYYYYY.fuzzyCompare(XXXXXYYYZZ) = 0.8
*/
fun String.fuzzyCompare(target:String):Double{
internal fun String.fuzzyCompare(target: String): Double {
var step = 0
if(this == target){
if (this == target) {
return 1.0
}
if(target.length > this.length){
if (target.length > this.length) {
return 0.0
}
for(i in this.indices){
if(target.length == i){
for (i in this.indices) {
if (target.length == i) {
step--
}else {
if (this[i] != target[i]) {
@ -97,14 +97,14 @@ fun String.fuzzyCompare(target:String):Double{
/**
* 模糊搜索一个List中index最接近target的东西
*/
inline fun <T:Any> Collection<T>.fuzzySearch(
internal inline fun <T : Any> Collection<T>.fuzzySearch(
target: String,
index: (T) -> String
):T?{
if(this.isEmpty()){
): T? {
if (this.isEmpty()) {
return null
}
var potential:T? = null
var potential: T? = null
var rate = 0.0
this.forEach {
val thisIndex = index(it)
@ -126,14 +126,14 @@ inline fun <T:Any> Collection<T>.fuzzySearch(
* 并且确保target是唯一的
* 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null
*/
inline fun <T:Any> Collection<T>.fuzzySearchOnly(
internal inline fun <T : Any> Collection<T>.fuzzySearchOnly(
target: String,
index: (T) -> String
):T?{
if(this.isEmpty()){
): T? {
if (this.isEmpty()) {
return null
}
var potential:T? = null
var potential: T? = null
var rate = 0.0
var collide = 0
this.forEach {
@ -154,8 +154,8 @@ inline fun <T:Any> Collection<T>.fuzzySearchOnly(
}
fun Group.fuzzySearchMember(nameCardTarget:String):Member?{
return this.members.fuzzySearchOnly(nameCardTarget){
internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
return this.members.fuzzySearchOnly(nameCardTarget) {
it.nameCard
}
}