Add bot.subscribe, close #61

This commit is contained in:
Him188 2020-02-19 19:10:14 +08:00
parent 385b36b09d
commit 2322387f73
2 changed files with 63 additions and 79 deletions

View File

@ -20,7 +20,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
* 若监听这个类, 监听器将会接收所有事件的广播.
*
* @see subscribeAlways
* @see subscribeWhile
* @see subscribeOnce
*
* @see subscribeMessages
*

View File

@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.subscribeInternal
import net.mamoe.mirai.utils.MiraiInternalAPI
@ -21,6 +22,7 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmName
/*
* 该文件为所有的订阅事件的方法.
@ -71,15 +73,16 @@ interface Listener<in E : Event> : CompletableJob {
* `runBlocking` 不会结束, 也就是下一行 `foo()` 不会被执行. 直到监听时创建的 `Listener` 被停止.
*
*
* 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数:
* 要创建一个仅在某个机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]).
* 这种方式创建的监听会自动筛选 [Bot].
* ```kotlin
* GlobalScope.subscribe<Event> { /* 一些处理 */ }
* bot1.subscribe<BotEvent> { /* 只会处理来自 bot1 的事件 */ }
* ```
*
*
* 要创建一个仅在某个机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]):
* 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数:
* ```kotlin
* bot.subscribe<Subscribe> { /* 一些处理 */ }
* GlobalScope.subscribe<Event> { /* 会收到来自全部 Bot 的事件和与 Bot 不相关的事件 */ }
* ```
*
*
@ -89,31 +92,34 @@ interface Listener<in E : Event> : CompletableJob {
* [this] 没有 [CoroutineExceptionHandler], 则在事件广播方的 [CoroutineExceptionHandler] 处理
* 若均找不到, 则会触发 logger warning.
* - 事件处理时抛出异常不会停止监听器.
* - 建议在事件处理中, [handler] 里处理异常, 或在 [this] 指定 [CoroutineExceptionHandler].
* - 建议在事件处理中 ( [handler] ) 处理异常,
* 或在 [this] [CoroutineScope.coroutineContext] 中添加 [CoroutineExceptionHandler].
*
*
* **注意:** 事件处理是 `suspend` , 严格控制 JVM 阻塞方法的使用. 若致事件处理阻塞, 则会导致一些逻辑无法进行.
* **注意:** 事件处理是 `suspend` , 规范处理 JVM 阻塞方法.
*
* // TODO: 2020/2/13 在 bot 下监听时同时筛选对应 bot 实例
* @see subscribeAlways 一直监听
* @see subscribeOnce 只监听一次
*
* @see subscribeMessages 监听消息 DSL
* @see subscribeGroupMessages 监听群消息 DSL
* @see subscribeMessages 监听消息 DSL
* @see subscribeGroupMessages 监听群消息 DSL
* @see subscribeFriendMessages 监听好友消息 DSL
*/
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
inline fun <reified E : Event> CoroutineScope.subscribe(noinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
E::class.subscribeInternal(Handler { it.handler(it); })
/**
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] , [listener] 都会被执行.
*
* 仅当 [Listener.complete] [Listener.cancel] 时结束.
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @see subscribe 获取更多说明
*/
@UseExperimental(MiraiInternalAPI::class, ExperimentalContracts::class)
inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener<E> {
inline fun <reified E : Event> CoroutineScope.subscribeAlways(noinline listener: suspend E.(E) -> Unit): Listener<E> {
contract {
callsInPlace(listener, InvocationKind.UNKNOWN)
}
@ -124,91 +130,69 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlways(crossinline listen
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 仅在第一次 [事件广播][Event.broadcast] , [listener] 会被执行.
*
* 在这之前, 可通过 [Listener.complete] 来停止监听.
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @see subscribe 获取更多说明
*/
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event> CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener<E> =
inline fun <reified E : Event> CoroutineScope.subscribeOnce(noinline listener: suspend E.(E) -> Unit): Listener<E> =
E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED })
//
// 以下为带筛选 Bot 的监听
//
/**
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] , [listener] 都会被执行, 直到 [listener] 的返回值 [equals] [valueIfStop]
* [Bot] [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] , [handler] 都会被执行,
* [handler] 返回 [ListeningStatus.STOPPED] 时停止监听
*
* 可在任意时刻通过 [Listener.complete] 来停止监听.
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @see subscribe 获取更多说明
*/
@JvmName("subscribeAlwaysForBot")
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event, T> CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
E::class.subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
inline fun <reified E : BotEvent> Bot.subscribe(noinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
E::class.subscribeInternal(Handler { if (it.bot === this) it.handler(it) else ListeningStatus.LISTENING })
/**
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] , [listener] 都会被执行,
* 如果 [listener] 的返回值 [equals] [valueIfContinue], 则继续监听, 否则停止
* [Bot] [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] , [listener] 都会被执行.
*
* 可在任意时刻通过 [Listener.complete] 来停止监听.
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @see subscribe 获取更多说明
*/
@JvmName("subscribeAlwaysForBot")
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : Event, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
// endregion
// region ListenerBuilder DSL
/*
/**
* 监听构建器. 可同时进行多种方式的监听
*
* ```kotlin
* FriendMessageEvent.subscribe {
* always{
* it.reply("永远发生")
* }
*
* untilFalse {
* it.reply("你发送了 ${it.event}")
* it.event eq "停止"
* }
* }
* ```
*/
@ListenersBuilderDsl
@Suppress("MemberVisibilityCanBePrivate", "unused")
inline class ListenerBuilder<out E : Event>(
@PublishedApi internal inline val handlerConsumer: CoroutineCoroutineScope.(Listener<E>) -> Unit
) {
fun CoroutineCoroutineScope.handler(listener: suspend E.(E) -> ListeningStatus) {
handlerConsumer(Handler { it.listener(it) })
}
fun CoroutineCoroutineScope.always(listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.LISTENING }
fun <T> CoroutineCoroutineScope.until(until: T, listener: suspend E.(E) -> T) =
handler { if (listener(it) == until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
fun CoroutineCoroutineScope.untilFalse(listener: suspend E.(E) -> Boolean) = until(false, listener)
fun CoroutineCoroutineScope.untilTrue(listener: suspend E.(E) -> Boolean) = until(true, listener)
fun CoroutineCoroutineScope.untilNull(listener: suspend E.(E) -> Any?) = until(null, listener)
fun <T> CoroutineCoroutineScope.`while`(until: T, listener: suspend E.(E) -> T) =
handler { if (listener(it) !== until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
fun CoroutineCoroutineScope.whileFalse(listener: suspend E.(E) -> Boolean) = `while`(false, listener)
fun CoroutineCoroutineScope.whileTrue(listener: suspend E.(E) -> Boolean) = `while`(true, listener)
fun CoroutineCoroutineScope.whileNull(listener: suspend E.(E) -> Any?) = `while`(null, listener)
fun CoroutineCoroutineScope.once(listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.STOPPED }
inline fun <reified E : BotEvent> Bot.subscribeAlways(noinline listener: suspend E.(E) -> Unit): Listener<E> {
return E::class.subscribeInternal(Handler { if (it.bot === this) it.listener(it); ListeningStatus.LISTENING })
}
@DslMarker
annotation class ListenersBuilderDsl
*/
/**
* [Bot] [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 仅在第一次 [事件广播][Event.broadcast] , [listener] 会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @see subscribe 获取更多说明
*/
@JvmName("subscribeOnceForBot")
@UseExperimental(MiraiInternalAPI::class)
inline fun <reified E : BotEvent> Bot.subscribeOnce(noinline listener: suspend E.(E) -> Unit): Listener<E> =
E::class.subscribeInternal(Handler {
if (it.bot === this) {
it.listener(it)
ListeningStatus.STOPPED
} else ListeningStatus.LISTENING
})
// endregion