Commit eda578ec authored by Him188's avatar Him188

Add docs

parent aac7dbf6
...@@ -11,13 +11,42 @@ package net.mamoe.mirai.event ...@@ -11,13 +11,42 @@ package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.message.MessagePacket import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.isContextIdenticalWith
import net.mamoe.mirai.message.nextMessage
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import kotlin.experimental.ExperimentalTypeInference import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 挂起当前协程, 等待任意一个事件监听器返回 `false` 后返回.
*
* 创建的所有事件监听器都会判断发送人信息 ([isContextIdenticalWith]), 监听之后的所有消息.
*
* ```kotlin
* reply("开启复读模式")
*
* whileSelectMessages {
* "stop" `->` {
* reply("已关闭复读")
* false // 停止循环
* }
* always {
* reply(message)
* true // 继续循环
* }
* } // 等待直到 `false`
*
* reply("复读模式结束")
* ```
* *
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
*
* @see subscribe
* @see subscribeMessages
* @see nextMessage 挂起协程并等待下一条消息
*/ */
@SinceMirai("0.29.0") @SinceMirai("0.29.0")
@Suppress("unused") @Suppress("unused")
...@@ -25,34 +54,33 @@ import kotlin.jvm.JvmName ...@@ -25,34 +54,33 @@ import kotlin.jvm.JvmName
suspend inline fun <reified T : MessagePacket<*, *>> T.whileSelectMessages( suspend inline fun <reified T : MessagePacket<*, *>> T.whileSelectMessages(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
) { ) = withTimeoutOrCoroutineScope(timeoutMillis) {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } var deferred: CompletableDeferred<Boolean>? = CompletableDeferred()
return coroutineScope { MessageSelectBuilder<T, Boolean>(SELECT_MESSAGE_STUB) { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> ->
var deferred: CompletableDeferred<Boolean> = CompletableDeferred() subscribeAlways<T> {
if (deferred?.isCompleted == false && isActive) {
MessageSelectBuilder<T, Boolean>(SELECT_MESSAGE_STUB) { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> ->
subscribeAlways<T> {
val toString = it.message.toString() val toString = it.message.toString()
if (filter.invoke(it, toString)) { if (filter.invoke(it, toString)) {
val value = listener.invoke(it, toString) val value = listener.invoke(it, toString)
if (value !== SELECT_MESSAGE_STUB) { if (value !== SELECT_MESSAGE_STUB) {
while (deferred.isCompleted) deferred?.complete(value as Boolean)
delay(1)
deferred.complete(value as Boolean)
} }
} }
} }
}.apply(selectBuilder)
while (deferred.await()) {
deferred = CompletableDeferred()
} }
coroutineContext[Job]!!.cancelChildren() }.apply(selectBuilder)
while (deferred?.await() == true) {
deferred = CompletableDeferred()
} }
deferred = null
coroutineContext[Job]!!.cancelChildren()
} }
/**
* [selectMessages] 的 [Unit] 返回值捷径 (由于 Kotlin 无法推断 [Unit] 类型)
*/
@OptIn(ExperimentalTypeInference::class) @OptIn(ExperimentalTypeInference::class)
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.29.0") @SinceMirai("0.29.0")
...@@ -64,7 +92,21 @@ suspend inline fun <reified T : MessagePacket<*, *>> T.selectMessagesUnit( ...@@ -64,7 +92,21 @@ suspend inline fun <reified T : MessagePacket<*, *>> T.selectMessagesUnit(
/** /**
* 挂起当前协程, 等待任意一个事件监听器触发后返回其返回值.
*
* 创建的所有事件监听器都会判断发送人信息 ([isContextIdenticalWith]), 监听之后的所有消息.
*
* ```kotlin
* val value: String = selectMessages {
* "hello" `->` { "111" }
* "hi" `->` { "222" }
* startsWith("/") { it }
* }
* ```
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
*
* @see nextMessage 挂起协程并等待下一条消息
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.29.0") @SinceMirai("0.29.0")
...@@ -75,34 +117,49 @@ suspend inline fun <reified T : MessagePacket<*, *>, R> T.selectMessages( ...@@ -75,34 +117,49 @@ suspend inline fun <reified T : MessagePacket<*, *>, R> T.selectMessages(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
@BuilderInference @BuilderInference
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, R>.() -> Unit crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, R>.() -> Unit
): R { ): R = withTimeoutOrCoroutineScope(timeoutMillis) {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } val deferred = CompletableDeferred<R>()
return coroutineScope { MessageSelectBuilder<T, R>(SELECT_MESSAGE_STUB) { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> ->
val deferred = CompletableDeferred<R>() subscribeAlways<T> {
if (!this.isContextIdenticalWith(this@selectMessages))
return@subscribeAlways
MessageSelectBuilder<T, R>(SELECT_MESSAGE_STUB) { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> -> val toString = it.message.toString()
subscribeAlways<T> { if (!filter.invoke(it, toString))
val toString = it.message.toString() return@subscribeAlways
if (filter.invoke(it, toString)) {
val value = listener.invoke(it, toString) val value = listener.invoke(it, toString)
if (value !== SELECT_MESSAGE_STUB) { if (value !== SELECT_MESSAGE_STUB) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
deferred.complete(value as R) deferred.complete(value as R)
}
}
} }
}.apply(selectBuilder) }
}.apply(selectBuilder)
deferred.await().also { coroutineContext[Job]!!.cancelChildren() } deferred.await().also { coroutineContext[Job]!!.cancelChildren() }
}
} }
@PublishedApi
internal val SELECT_MESSAGE_STUB = Any()
@SinceMirai("0.29.0") @SinceMirai("0.29.0")
class MessageSelectBuilder<M : MessagePacket<*, *>, R> internal constructor( class MessageSelectBuilder<M : MessagePacket<*, *>, R> @PublishedApi internal constructor(
stub: Any?, stub: Any?,
subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
) : MessageSubscribersBuilder<M, Unit, R, Any?>(stub, subscriber) ) : MessageSubscribersBuilder<M, Unit, R, Any?>(stub, subscriber)
\ No newline at end of file
@JvmSynthetic
@PublishedApi
internal suspend inline fun <R> withTimeoutOrCoroutineScope(
timeoutMillis: Long,
noinline block: suspend CoroutineScope.() -> R
): R {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " }
return if (timeoutMillis == -1L) {
coroutineScope(block)
} else {
withTimeout(timeoutMillis, block)
}
}
@PublishedApi
internal val SELECT_MESSAGE_STUB = Any()
\ No newline at end of file
...@@ -244,6 +244,11 @@ inline fun <reified E : BotEvent> Bot.incoming( ...@@ -244,6 +244,11 @@ inline fun <reified E : BotEvent> Bot.incoming(
*/ */
typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
// TODO: 2020/3/21 抽象消息构造器, 以在 select 时不可访问 `reply` 等函数
// TODO: 2020/3/21 引入 TypeAlias 或新的类型以缩短类型参数
/** /**
* 消息订阅构造器 * 消息订阅构造器
* *
......
...@@ -100,7 +100,7 @@ interface Listener<in E : Event> : CompletableJob { ...@@ -100,7 +100,7 @@ interface Listener<in E : Event> : CompletableJob {
* ``` * ```
* *
* *
* 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数: * 要创建一个全局都存在的监听(不推荐), 即守护协程, 请在 [GlobalScope] 下调用本函数:
* ```kotlin * ```kotlin
* GlobalScope.subscribe<Event> { /* 会收到来自全部 Bot 的事件和与 Bot 不相关的事件 */ } * GlobalScope.subscribe<Event> { /* 会收到来自全部 Bot 的事件和与 Bot 不相关的事件 */ }
* ``` * ```
...@@ -123,6 +123,8 @@ interface Listener<in E : Event> : CompletableJob { ...@@ -123,6 +123,8 @@ interface Listener<in E : Event> : CompletableJob {
* @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值. * @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值.
* @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值. * @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值.
* *
* @see whileSelectMessages 挂起当前协程, 等待任意一个事件监听器返回 `false` 后返回.
*
* @see subscribeAlways 一直监听 * @see subscribeAlways 一直监听
* @see subscribeOnce 只监听一次 * @see subscribeOnce 只监听一次
* *
......
...@@ -21,8 +21,10 @@ import net.mamoe.mirai.contact.Member ...@@ -21,8 +21,10 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.selectMessages
import net.mamoe.mirai.event.subscribingGet import net.mamoe.mirai.event.subscribingGet
import net.mamoe.mirai.event.subscribingGetOrNull import net.mamoe.mirai.event.subscribingGetOrNull
import net.mamoe.mirai.event.whileSelectMessages
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recall import net.mamoe.mirai.recall
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
...@@ -212,7 +214,7 @@ fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Bo ...@@ -212,7 +214,7 @@ fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Bo
*/ */
suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage( suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
crossinline filter: P.(P) -> Boolean crossinline filter: suspend P.(P) -> Boolean
): MessageChain { ): MessageChain {
return subscribingGet<P, P>(timeoutMillis) { return subscribingGet<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) } takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) }
...@@ -232,7 +234,7 @@ suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage( ...@@ -232,7 +234,7 @@ suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
*/ */
suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNull( suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNull(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
crossinline filter: P.(P) -> Boolean crossinline filter: suspend P.(P) -> Boolean
): MessageChain? { ): MessageChain? {
return subscribingGetOrNull<P, P>(timeoutMillis) { return subscribingGetOrNull<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) }
...@@ -270,6 +272,22 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync( ...@@ -270,6 +272,22 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync(
} }
} }
/**
* @see nextMessage
*/
inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline filter: suspend P.(P) -> Boolean
): Deferred<MessageChain> {
return this.bot.async(coroutineContext) {
subscribingGet<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageAsync) }
.takeIf { filter(this, this) }
}.message
}
}
/** /**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket]
* *
...@@ -310,6 +328,8 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNullAsync( ...@@ -310,6 +328,8 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNullAsync(
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* *
* @see subscribingGet * @see subscribingGet
* @see whileSelectMessages
* @see selectMessages
*/ */
suspend inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContaining( suspend inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContaining(
timeoutMillis: Long = -1 timeoutMillis: Long = -1
...@@ -359,4 +379,3 @@ inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContainingOrNull ...@@ -359,4 +379,3 @@ inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContainingOrNull
}?.message?.first<M>() }?.message?.first<M>()
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment