Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-01-19 19:31:00 +08:00
commit c0a9db0e07
4 changed files with 295 additions and 77 deletions

View File

@ -2,6 +2,31 @@
开发版本. 频繁更新, 不保证高稳定性
## `0.12.0` *2020/1/19*
### mirai-core
1. 监听消息时允许使用条件式的表达式, 如:
```kotlin
(contains("1") and has<Image>()){
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` 获取.

View File

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

View File

@ -111,6 +111,9 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribers
}.apply { listeners() }
}
typealias MessageListener<T> = @MessageDsl suspend T.(String) -> Unit
/**
* 消息订阅构造器
*
@ -121,130 +124,312 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribers
@Suppress("unused")
@MessageDsl
class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
val subscriber: (@MessageDsl suspend T.(String) -> Unit) -> Listener<T>
val subscriber: (MessageListener<T>) -> Listener<T>
) {
/**
* 监听的条件
*/
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<T>): Listener<T> {
return content(filter, onEvent)
}
}
/**
* 无任何触发条件.
*/
@MessageDsl
fun always(onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> {
return subscriber(onEvent)
fun always(onEvent: MessageListener<T>): Listener<T> = 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<T> {
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<T> = 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<T>
): Listener<T> {
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<T> =
content({ it.startsWith(prefix) }) {
if (removePrefix) this.onEvent(this.message.toString().substringAfter(prefix))
else onEvent(this, this.message.toString())
): Listener<T> {
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<T> =
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<T> =
content({ sender.id == qqId }, onEvent)
inline fun endsWith(
suffix: String,
removeSuffix: Boolean = true,
trim: Boolean = true,
crossinline onEvent: @MessageDsl suspend T.(String) -> Unit
): Listener<T> {
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<T> =
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<T> =
content({ this is GroupMessage && sender.permission.isAdministrator() }, onEvent)
inline fun sentBy(qqId: Long, crossinline onEvent: MessageListener<T>): Listener<T> =
content({ this.sender.id == qqId }, onEvent)
/**
* 如果是群主发的消息, 就执行 [onEvent]
* 如果是管理员或群主发的消息
*/
@MessageDsl
inline fun sentByOwner(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> =
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<T>): Listener<T> =
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<T>): Listener<T> =
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<T>): Listener<T> =
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<T> =
content({ if (this is GroupMessage) group.id == id else false }, onEvent)
inline fun sentFrom(groupId: Long, crossinline onEvent: MessageListener<T>): Listener<T> =
content({ this is GroupMessage && this.group.id == groupId }, onEvent)
/**
* 如果消息内容包含 [M] 类型的 [Message]
*/
@MessageDsl
inline fun <reified M : Message> has(): ListeningFilter =
content { message.any { it::class == M::class } }
/**
* 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent]
*/
@MessageDsl
inline fun <reified M : Message> has(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> =
subscriber { if (message.any { it::class == M::class }) onEvent(this, this.message.toString()) }
inline fun <reified M : Message> has(crossinline onEvent: MessageListener<T>): Listener<T> =
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<T> =
subscriber { if (this.filter(message.toString())) onEvent(this, this.message.toString()) }
inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: MessageListener<T>): Listener<T> =
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<T>): Listener<T> =
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<T> =
content({ this@containsReply in it }, { reply(reply) })
/**
* 若消息内容包含 [this] 则执行 [replier] 并将其返回值回复给发信对象.
@ -254,11 +439,11 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @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<T> =
content({ this@containsReply in it }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING")
this.executeAndReply(replier)
})
/**
* 若消息内容可由正则表达式匹配([Regex.matchEntire]), 则执行 [replier] 并将其返回值回复给发信对象.
@ -268,12 +453,11 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @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<T> =
content({ this@matchingReply.matchEntire(it) != null }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING")
this.executeAndReply(replier)
})
/**
* 若消息内容可由正则表达式查找([Regex.find]), 则执行 [replier] 并将其返回值回复给发信对象.
@ -283,12 +467,11 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @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<T> =
content({ this@findingReply.find(it) != null }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING")
this.executeAndReply(replier)
})
/**
* 不考虑空格, 若消息内容以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象.
@ -304,14 +487,14 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @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<T> {
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<T : MessagePacket<*, *>>(
* @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<T> {
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<T> {
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<T> {
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 -> {

View File

@ -138,6 +138,10 @@ suspend fun main() {
}.reply()
}
(contains("1") and has<Image>()){
reply("Your message has a string \"1\" and an image contained")
}
has<Image> {
if (this is FriendMessage || (this is GroupMessage && this.permission == MemberPermission.ADMINISTRATOR)) withContext(IO) {
val image: Image by message