diff --git a/UpdateLog.md b/UpdateLog.md index 4f4a7cd9c..30405f3d9 100644 --- a/UpdateLog.md +++ b/UpdateLog.md @@ -2,6 +2,31 @@ 开发版本. 频繁更新, 不保证高稳定性 +## `0.12.0` *2020/1/19* +### mirai-core +1. 监听消息时允许使用条件式的表达式, 如: +```kotlin +(contains("1") and has()){ + reply("Your message has a string '1' and an image contained") +} + +(contains("1") or endsWith("2")){ + +} +``` +原有单一条件语法不变: +```kotlin +contains("1"){ + +} + +"Hello" reply "World" +``` + +2. Message: 修复 `eq` 无法正确判断的问题; 性能优化. +3. 简化 logger 结构(API 不变). +4. 事件 `cancelled` 属性修改为 `val` (以前是 `var` with `private set`) + ## `0.11.0` *2020/1/12* ### mirai-core - 弃用 `BotAccount.id`. 将来它可能会被改名成为邮箱等账号. QQ 号码需通过 `bot.uin` 获取. diff --git a/gradle.properties b/gradle.properties index 0ef7411ee..7b2a54474 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # style guide kotlin.code.style=official # config -mirai_version=0.11.0 +mirai_version=0.12.0 kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true # kotlin diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index 51a77ec48..43d9ab6a3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -111,6 +111,9 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribers }.apply { listeners() } } + +typealias MessageListener = @MessageDsl suspend T.(String) -> Unit + /** * 消息订阅构造器 * @@ -121,130 +124,312 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribers @Suppress("unused") @MessageDsl class MessageSubscribersBuilder>( - val subscriber: (@MessageDsl suspend T.(String) -> Unit) -> Listener + val subscriber: (MessageListener) -> Listener ) { + /** + * 监听的条件 + */ + inner class ListeningFilter( + val filter: T.(String) -> Boolean + ) { + /** + * 进行逻辑 `or`. + */ + infix fun or(another: ListeningFilter): ListeningFilter = + ListeningFilter { filter.invoke(this, it) || another.filter.invoke(this, it) } + + /** + * 进行逻辑 `and`. + */ + infix fun and(another: ListeningFilter): ListeningFilter = + ListeningFilter { filter.invoke(this, it) && another.filter.invoke(this, it) } + + /** + * 进行逻辑 `xor`. + */ + infix fun xor(another: ListeningFilter): ListeningFilter = + ListeningFilter { filter.invoke(this, it) xor another.filter.invoke(this, it) } + + /** + * 进行逻辑 `nand`, 即 `not and`. + */ + infix fun nand(another: ListeningFilter): ListeningFilter = + ListeningFilter { !filter.invoke(this, it) || !another.filter.invoke(this, it) } + + /** + * 启动时间监听. + */ + // do not inline due to kotlin (1.3.61) bug: java.lang.IllegalAccessError + operator fun invoke(onEvent: MessageListener): Listener { + return content(filter, onEvent) + } + } + /** * 无任何触发条件. */ @MessageDsl - fun always(onEvent: @MessageDsl suspend T.(String) -> Unit): Listener { - return subscriber(onEvent) + fun always(onEvent: MessageListener): Listener = subscriber(onEvent) + + /** + * 如果消息内容 `==` [equals] + */ + @MessageDsl + fun case( + equals: String, + ignoreCase: Boolean = false, + trim: Boolean = true + ): ListeningFilter { + return if (trim) { + val toCheck = equals.trim() + content { it.trim().equals(toCheck, ignoreCase = ignoreCase) } + } else { + content { it.equals(equals, ignoreCase = ignoreCase) } + } } /** - * 如果消息内容 `==` [equals], 就执行 [onEvent] + * 如果消息内容 `==` [equals] * @param trim `true` 则删除首尾空格后比较 * @param ignoreCase `true` 则不区分大小写 */ @MessageDsl inline fun case( equals: String, - trim: Boolean = true, ignoreCase: Boolean = false, + trim: Boolean = true, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit ): Listener { val toCheck = if (trim) equals.trim() else equals - return content({ toCheck.equals(if (trim) it.trim() else it, ignoreCase = ignoreCase) }, onEvent) + return content({ (if (trim) it.trim() else it).equals(toCheck, ignoreCase = ignoreCase) }, { + onEvent(this, this.message.toString()) + }) } /** - * 如果消息内容包含 [sub], 就执行 [onEvent] + * 如果消息内容包含 [sub] */ @MessageDsl - inline fun contains(sub: String, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ sub in it }, onEvent) + fun contains(sub: String): ListeningFilter = + content { sub in it } /** - * 如果消息的前缀是 [prefix], 就执行 [onEvent] + * 如果消息内容包含 [sub] + */ + @MessageDsl + inline fun contains( + sub: String, + ignoreCase: Boolean = false, + trim: Boolean = true, + crossinline onEvent: MessageListener + ): Listener { + return if (trim) { + val toCheck = sub.trim() + content({ it.trimStart().contains(toCheck, ignoreCase = ignoreCase) }, { + onEvent(this, this.message.toString().trim()) + }) + } else { + content({ it.contains(sub, ignoreCase = ignoreCase) }, { + onEvent(this, this.message.toString()) + }) + } + } + + /** + * 如果消息的前缀是 [prefix] + */ + @MessageDsl + fun startsWith( + prefix: String, + trim: Boolean = true + ): ListeningFilter { + val toCheck = if (trim) prefix.trim() else prefix + return content { (if (trim) it.trim() else it).startsWith(toCheck) } + } + + /** + * 如果消息的前缀是 [prefix] */ @MessageDsl inline fun startsWith( prefix: String, removePrefix: Boolean = true, + trim: Boolean = true, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit - ): Listener = - content({ it.startsWith(prefix) }) { - if (removePrefix) this.onEvent(this.message.toString().substringAfter(prefix)) - else onEvent(this, this.message.toString()) + ): Listener { + return if (trim) { + val toCheck = prefix.trim() + content({ it.trimStart().startsWith(toCheck) }, { + if (removePrefix) this.onEvent(this.message.toString().substringAfter(toCheck).trim()) + else onEvent(this, this.message.toString().trim()) + }) + } else { + content({ it.startsWith(prefix) }, { + if (removePrefix) this.onEvent(this.message.toString().removePrefix(prefix)) + else onEvent(this, this.message.toString()) + }) } + } /** - * 如果消息的结尾是 [suffix], 就执行 [onEvent] + * 如果消息的结尾是 [suffix] */ @MessageDsl - inline fun endsWith(suffix: String, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - content({ it.endsWith(suffix) }, onEvent) + fun endsWith(suffix: String): ListeningFilter = + content { it.endsWith(suffix) } /** - * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息 + * 如果消息的结尾是 [suffix] */ @MessageDsl - inline fun sentBy(qqId: Long, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - content({ sender.id == qqId }, onEvent) + inline fun endsWith( + suffix: String, + removeSuffix: Boolean = true, + trim: Boolean = true, + crossinline onEvent: @MessageDsl suspend T.(String) -> Unit + ): Listener { + return if (trim) { + val toCheck = suffix.trim() + content({ it.trimStart().startsWith(toCheck) }, { + if (removeSuffix) this.onEvent(this.message.toString().substringBeforeLast(toCheck).trim()) + else onEvent(this, this.message.toString().trim()) + }) + } else { + content({ it.startsWith(suffix) }, { + if (removeSuffix) this.onEvent(this.message.toString().removeSuffix(suffix)) + else onEvent(this, this.message.toString()) + }) + } + } /** - * 如果是管理员或群主发的消息, 就执行 [onEvent] + * 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl - inline fun sentByOperator(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - content({ this is GroupMessage && sender.permission.isOperator() }, onEvent) + fun sentBy(qqId: Long): ListeningFilter = + content { sender.id == qqId } /** - * 如果是管理员发的消息, 就执行 [onEvent] + * 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl - inline fun sentByAdministrator(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - content({ this is GroupMessage && sender.permission.isAdministrator() }, onEvent) + inline fun sentBy(qqId: Long, crossinline onEvent: MessageListener): Listener = + content({ this.sender.id == qqId }, onEvent) /** - * 如果是群主发的消息, 就执行 [onEvent] + * 如果是管理员或群主发的消息 */ @MessageDsl - inline fun sentByOwner(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - content({ this is GroupMessage && sender.permission.isOwner() }, onEvent) + fun sentByOperator(): ListeningFilter = + content { this is GroupMessage && sender.permission.isOperator() } + + /** + * 如果是管理员或群主发的消息 + */ + @MessageDsl + inline fun sentByOperator(crossinline onEvent: MessageListener): Listener = + content({ this is GroupMessage && this.sender.isOperator() }, onEvent) + + /** + * 如果是管理员发的消息 + */ + @MessageDsl + fun sentByAdministrator(): ListeningFilter = + content { this is GroupMessage && sender.permission.isAdministrator() } + + /** + * 如果是管理员发的消息 + */ + @MessageDsl + inline fun sentByAdministrator(crossinline onEvent: MessageListener): Listener = + content({ this is GroupMessage && this.sender.isAdministrator() }, onEvent) + + /** + * 如果是群主发的消息 + */ + @MessageDsl + fun sentByOwner(): ListeningFilter = + content { this is GroupMessage && sender.isOwner() } + + /** + * 如果是群主发的消息 + */ + @MessageDsl + inline fun sentByOwner(crossinline onEvent: MessageListener): Listener = + content({ this is GroupMessage && this.sender.isOwner() }, onEvent) + + /** + * 如果是来自这个群的消息 + */ + @MessageDsl + fun sentFrom(groupId: Long): ListeningFilter = + content { this is GroupMessage && group.id == groupId } /** * 如果是来自这个群的消息, 就执行 [onEvent] */ @MessageDsl - inline fun sentFrom(id: Long, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - content({ if (this is GroupMessage) group.id == id else false }, onEvent) + inline fun sentFrom(groupId: Long, crossinline onEvent: MessageListener): Listener = + content({ this is GroupMessage && this.group.id == groupId }, onEvent) + + /** + * 如果消息内容包含 [M] 类型的 [Message] + */ + @MessageDsl + inline fun has(): ListeningFilter = + content { message.any { it::class == M::class } } /** * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent] */ @MessageDsl - inline fun has(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - subscriber { if (message.any { it::class == M::class }) onEvent(this, this.message.toString()) } + inline fun has(crossinline onEvent: MessageListener): Listener = + content({ message.any { it::class == M::class } }, onEvent) + + /** + * 如果 [filter] 返回 `true` + */ + @MessageDsl + fun content(filter: T.(String) -> Boolean): ListeningFilter = + ListeningFilter(filter) /** * 如果 [filter] 返回 `true` 就执行 `onEvent` */ @MessageDsl - inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = - subscriber { if (this.filter(message.toString())) onEvent(this, this.message.toString()) } + inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: MessageListener): Listener = + subscriber { + if (filter(this, it)) onEvent(this, it) + } /** * 如果消息内容可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent` */ @MessageDsl - inline fun matching(regex: Regex, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit) { + fun matching(regex: Regex): ListeningFilter = + content { regex.matchEntire(it) != null } + + /** + * 如果 [filter] 返回 `true` 就执行 `onEvent` + */ + @MessageDsl + inline fun matching(regex: Regex, crossinline onEvent: MessageListener): Listener = content({ regex.matchEntire(it) != null }, onEvent) - } /** * 如果消息内容可由正则表达式查找([Regex.find]), 就执行 `onEvent` */ @MessageDsl - inline fun finding(regex: Regex, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit) { - content({ regex.find(it) != null }, onEvent) - } + fun finding(regex: Regex): ListeningFilter = + content { regex.find(it) != null } + /** * 若消息内容包含 [this] 则回复 [reply] */ @MessageDsl - infix fun String.containsReply(reply: String) = - content({ this@containsReply in it }) { this@content.reply(reply) } + infix fun String.containsReply(reply: String): Listener = + content({ this@containsReply in it }, { reply(reply) }) /** * 若消息内容包含 [this] 则执行 [replier] 并将其返回值回复给发信对象. @@ -254,11 +439,11 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun String.containsReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) = - content({ this@containsReply in it }) { - @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning - executeAndReply(replier) - } + inline infix fun String.containsReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener = + content({ this@containsReply in it }, { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply(replier) + }) /** * 若消息内容可由正则表达式匹配([Regex.matchEntire]), 则执行 [replier] 并将其返回值回复给发信对象. @@ -268,12 +453,11 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun Regex.matchingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { - content({ this@matchingReply.matchEntire(it) != null }) { - @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning - executeAndReply(replier) - } - } + inline infix fun Regex.matchingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener = + content({ this@matchingReply.matchEntire(it) != null }, { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply(replier) + }) /** * 若消息内容可由正则表达式查找([Regex.find]), 则执行 [replier] 并将其返回值回复给发信对象. @@ -283,12 +467,11 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun Regex.findingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { - content({ this@findingReply.find(it) != null }) { - @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning - executeAndReply(replier) - } - } + inline infix fun Regex.findingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener = + content({ this@findingReply.find(it) != null }, { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply(replier) + }) /** * 不考虑空格, 若消息内容以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象. @@ -304,14 +487,14 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun String.startsWithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { + inline infix fun String.startsWithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener { val toCheck = this.trimStart() - content({ it.trimStart().startsWith(toCheck) }) { - @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning - executeAndReply { - replier(it.removePrefix(toCheck).trim()) + return content({ it.trim().startsWith(toCheck) }, { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply { + replier(this, it.trim().removePrefix(toCheck)) } - } + }) } /** @@ -328,30 +511,36 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun String.endswithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { + inline infix fun String.endswithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener { val toCheck = this.trimEnd() - content({ it.endsWith(this@endswithReply) }) { - @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning - executeAndReply { - replier(it.removeSuffix(toCheck).trim()) + return content({ it.trim().endsWith(toCheck) }, { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply { + replier(this, it.trim().removeSuffix(toCheck)) } - } + }) } @MessageDsl - infix fun String.reply(reply: String) = case(this) { - this@case.reply(reply) + infix fun String.reply(reply: String): Listener { + val toCheck = this.trim() + return content({ it.trim() == toCheck }, { reply(reply) }) } @MessageDsl - inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) = case(this) { - @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning - executeAndReply(replier) + inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener { + val toCheck = this.trim() + return content({ it.trim() == toCheck }, { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply { + replier(this, it.trim()) + } + }) } @PublishedApi @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") // false positive - internal suspend inline fun T.executeAndReply(replier: @MessageDsl suspend T.(String) -> Any?) { + internal suspend inline fun T.executeAndReply(replier: suspend T.(String) -> Any?) { when (val message = replier(this, this.message.toString())) { is Message -> this.reply(message) is Unit -> { diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt index e6a3800da..98cf4a85a 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt @@ -138,6 +138,10 @@ suspend fun main() { }.reply() } + (contains("1") and has()){ + reply("Your message has a string \"1\" and an image contained") + } + has { if (this is FriendMessage || (this is GroupMessage && this.permission == MemberPermission.ADMINISTRATOR)) withContext(IO) { val image: Image by message