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* ## `0.11.0` *2020/1/12*
### mirai-core ### mirai-core
- 弃用 `BotAccount.id`. 将来它可能会被改名成为邮箱等账号. QQ 号码需通过 `bot.uin` 获取. - 弃用 `BotAccount.id`. 将来它可能会被改名成为邮箱等账号. QQ 号码需通过 `bot.uin` 获取.

View File

@ -1,7 +1,7 @@
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.11.0 mirai_version=0.12.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin

View File

@ -111,6 +111,9 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribers
}.apply { listeners() } }.apply { listeners() }
} }
typealias MessageListener<T> = @MessageDsl suspend T.(String) -> Unit
/** /**
* 消息订阅构造器 * 消息订阅构造器
* *
@ -121,130 +124,312 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribers
@Suppress("unused") @Suppress("unused")
@MessageDsl @MessageDsl
class MessageSubscribersBuilder<T : MessagePacket<*, *>>( 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 @MessageDsl
fun always(onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> { fun always(onEvent: MessageListener<T>): Listener<T> = subscriber(onEvent)
return 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 trim `true` 则删除首尾空格后比较
* @param ignoreCase `true` 则不区分大小写 * @param ignoreCase `true` 则不区分大小写
*/ */
@MessageDsl @MessageDsl
inline fun case( inline fun case(
equals: String, equals: String,
trim: Boolean = true,
ignoreCase: Boolean = false, ignoreCase: Boolean = false,
trim: Boolean = true,
crossinline onEvent: @MessageDsl suspend T.(String) -> Unit crossinline onEvent: @MessageDsl suspend T.(String) -> Unit
): Listener<T> { ): Listener<T> {
val toCheck = if (trim) equals.trim() else equals 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 @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 @MessageDsl
inline fun startsWith( inline fun startsWith(
prefix: String, prefix: String,
removePrefix: Boolean = true, removePrefix: Boolean = true,
trim: Boolean = true,
crossinline onEvent: @MessageDsl suspend T.(String) -> Unit crossinline onEvent: @MessageDsl suspend T.(String) -> Unit
): Listener<T> = ): Listener<T> {
content({ it.startsWith(prefix) }) { return if (trim) {
if (removePrefix) this.onEvent(this.message.toString().substringAfter(prefix)) val toCheck = prefix.trim()
else onEvent(this, this.message.toString()) 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 @MessageDsl
inline fun endsWith(suffix: String, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = fun endsWith(suffix: String): ListeningFilter =
content({ it.endsWith(suffix) }, onEvent) content { it.endsWith(suffix) }
/** /**
* 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息 * 如果消息的结尾是 [suffix]
*/ */
@MessageDsl @MessageDsl
inline fun sentBy(qqId: Long, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = inline fun endsWith(
content({ sender.id == qqId }, onEvent) 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 @MessageDsl
inline fun sentByOperator(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = fun sentBy(qqId: Long): ListeningFilter =
content({ this is GroupMessage && sender.permission.isOperator() }, onEvent) content { sender.id == qqId }
/** /**
* 如果是管理员发的消息, 就执行 [onEvent] * 如果是这个人发的消息. 消息可以是好友消息也可以是群消息
*/ */
@MessageDsl @MessageDsl
inline fun sentByAdministrator(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = inline fun sentBy(qqId: Long, crossinline onEvent: MessageListener<T>): Listener<T> =
content({ this is GroupMessage && sender.permission.isAdministrator() }, onEvent) content({ this.sender.id == qqId }, onEvent)
/** /**
* 如果是群主发的消息, 就执行 [onEvent] * 如果是管理员或群主发的消息
*/ */
@MessageDsl @MessageDsl
inline fun sentByOwner(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = fun sentByOperator(): ListeningFilter =
content({ this is GroupMessage && sender.permission.isOwner() }, onEvent) 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] * 如果是来自这个群的消息, 就执行 [onEvent]
*/ */
@MessageDsl @MessageDsl
inline fun sentFrom(id: Long, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = inline fun sentFrom(groupId: Long, crossinline onEvent: MessageListener<T>): Listener<T> =
content({ if (this is GroupMessage) group.id == id else false }, onEvent) 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] * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent]
*/ */
@MessageDsl @MessageDsl
inline fun <reified M : Message> has(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = inline fun <reified M : Message> has(crossinline onEvent: MessageListener<T>): Listener<T> =
subscriber { if (message.any { it::class == M::class }) onEvent(this, this.message.toString()) } content({ message.any { it::class == M::class } }, onEvent)
/**
* 如果 [filter] 返回 `true`
*/
@MessageDsl
fun content(filter: T.(String) -> Boolean): ListeningFilter =
ListeningFilter(filter)
/** /**
* 如果 [filter] 返回 `true` 就执行 `onEvent` * 如果 [filter] 返回 `true` 就执行 `onEvent`
*/ */
@MessageDsl @MessageDsl
inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener<T> = inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: MessageListener<T>): Listener<T> =
subscriber { if (this.filter(message.toString())) onEvent(this, this.message.toString()) } subscriber {
if (filter(this, it)) onEvent(this, it)
}
/** /**
* 如果消息内容可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent` * 如果消息内容可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent`
*/ */
@MessageDsl @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) content({ regex.matchEntire(it) != null }, onEvent)
}
/** /**
* 如果消息内容可由正则表达式查找([Regex.find]), 就执行 `onEvent` * 如果消息内容可由正则表达式查找([Regex.find]), 就执行 `onEvent`
*/ */
@MessageDsl @MessageDsl
inline fun finding(regex: Regex, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit) { fun finding(regex: Regex): ListeningFilter =
content({ regex.find(it) != null }, onEvent) content { regex.find(it) != null }
}
/** /**
* 若消息内容包含 [this] 则回复 [reply] * 若消息内容包含 [this] 则回复 [reply]
*/ */
@MessageDsl @MessageDsl
infix fun String.containsReply(reply: String) = infix fun String.containsReply(reply: String): Listener<T> =
content({ this@containsReply in it }) { this@content.reply(reply) } content({ this@containsReply in it }, { reply(reply) })
/** /**
* 若消息内容包含 [this] 则执行 [replier] 并将其返回值回复给发信对象. * 若消息内容包含 [this] 则执行 [replier] 并将其返回值回复给发信对象.
@ -254,11 +439,11 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复
*/ */
@MessageDsl @MessageDsl
inline infix fun String.containsReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) = inline infix fun String.containsReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener<T> =
content({ this@containsReply in it }) { content({ this@containsReply in it }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @Suppress("DSL_SCOPE_VIOLATION_WARNING")
executeAndReply(replier) this.executeAndReply(replier)
} })
/** /**
* 若消息内容可由正则表达式匹配([Regex.matchEntire]), 则执行 [replier] 并将其返回值回复给发信对象. * 若消息内容可由正则表达式匹配([Regex.matchEntire]), 则执行 [replier] 并将其返回值回复给发信对象.
@ -268,12 +453,11 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复
*/ */
@MessageDsl @MessageDsl
inline infix fun Regex.matchingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { inline infix fun Regex.matchingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener<T> =
content({ this@matchingReply.matchEntire(it) != null }) { content({ this@matchingReply.matchEntire(it) != null }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @Suppress("DSL_SCOPE_VIOLATION_WARNING")
executeAndReply(replier) this.executeAndReply(replier)
} })
}
/** /**
* 若消息内容可由正则表达式查找([Regex.find]), 则执行 [replier] 并将其返回值回复给发信对象. * 若消息内容可由正则表达式查找([Regex.find]), 则执行 [replier] 并将其返回值回复给发信对象.
@ -283,12 +467,11 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复
*/ */
@MessageDsl @MessageDsl
inline infix fun Regex.findingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { inline infix fun Regex.findingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener<T> =
content({ this@findingReply.find(it) != null }) { content({ this@findingReply.find(it) != null }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @Suppress("DSL_SCOPE_VIOLATION_WARNING")
executeAndReply(replier) this.executeAndReply(replier)
} })
}
/** /**
* 不考虑空格, 若消息内容以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象. * 不考虑空格, 若消息内容以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象.
@ -304,14 +487,14 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复 * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复
*/ */
@MessageDsl @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() val toCheck = this.trimStart()
content({ it.trimStart().startsWith(toCheck) }) { return content({ it.trim().startsWith(toCheck) }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @Suppress("DSL_SCOPE_VIOLATION_WARNING")
executeAndReply { this.executeAndReply {
replier(it.removePrefix(toCheck).trim()) replier(this, it.trim().removePrefix(toCheck))
} }
} })
} }
/** /**
@ -328,30 +511,36 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复
*/ */
@MessageDsl @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() val toCheck = this.trimEnd()
content({ it.endsWith(this@endswithReply) }) { return content({ it.trim().endsWith(toCheck) }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @Suppress("DSL_SCOPE_VIOLATION_WARNING")
executeAndReply { this.executeAndReply {
replier(it.removeSuffix(toCheck).trim()) replier(this, it.trim().removeSuffix(toCheck))
} }
} })
} }
@MessageDsl @MessageDsl
infix fun String.reply(reply: String) = case(this) { infix fun String.reply(reply: String): Listener<T> {
this@case.reply(reply) val toCheck = this.trim()
return content({ it.trim() == toCheck }, { reply(reply) })
} }
@MessageDsl @MessageDsl
inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) = case(this) { inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener<T> {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning val toCheck = this.trim()
executeAndReply(replier) return content({ it.trim() == toCheck }, {
@Suppress("DSL_SCOPE_VIOLATION_WARNING")
this.executeAndReply {
replier(this, it.trim())
}
})
} }
@PublishedApi @PublishedApi
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") // false positive @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())) { when (val message = replier(this, this.message.toString())) {
is Message -> this.reply(message) is Message -> this.reply(message)
is Unit -> { is Unit -> {

View File

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