2
0
Эх сурвалжийг харах

Merge remote-tracking branch 'origin'

ryoii 6 жил өмнө
parent
commit
5c287faf4e

+ 15 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt

@@ -166,7 +166,7 @@ internal class QQImpl(
 }
 
 
-@Suppress("MemberVisibilityCanBePrivate", "DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
+@Suppress("MemberVisibilityCanBePrivate")
 internal class MemberImpl(
     qq: QQImpl,
     group: GroupImpl,
@@ -286,6 +286,20 @@ internal class MemberImpl(
         }
     }
 
+    override fun hashCode(): Int {
+        var result = bot.hashCode()
+        result = 31 * result + id.hashCode()
+        return result
+    }
+
+    @Suppress("DuplicatedCode")
+    override fun equals(other: Any?): Boolean { // 不要删除. trust me
+        if (this === other) return true
+        if (other !is Contact) return false
+        if (this::class != other::class) return false
+        return this.id == other.id && this.bot == other.bot
+    }
+
     override fun toString(): String {
         return "Member($id)"
     }

+ 8 - 12
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt

@@ -9,7 +9,7 @@
 
 package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
 
-import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
@@ -19,11 +19,10 @@ import net.mamoe.mirai.contact.MemberPermission
 import net.mamoe.mirai.data.MemberInfo
 import net.mamoe.mirai.data.MultiPacket
 import net.mamoe.mirai.data.Packet
-import net.mamoe.mirai.event.ListeningStatus
 import net.mamoe.mirai.event.events.BotJoinGroupEvent
 import net.mamoe.mirai.event.events.BotOfflineEvent
 import net.mamoe.mirai.event.events.MemberJoinEvent
-import net.mamoe.mirai.event.subscribe
+import net.mamoe.mirai.event.subscribingGetAsync
 import net.mamoe.mirai.message.FriendMessage
 import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.message.data.MessageSource
@@ -279,17 +278,14 @@ internal class MessageSvc {
             override val groupId: Long,
             override val sourceMessage: MessageChain
         ) : MessageSource {
-            lateinit var sequenceIdDeferred: CompletableDeferred<Int>
+            lateinit var sequenceIdDeferred: Deferred<Int>
 
+            @UseExperimental(MiraiExperimentalAPI::class)
             fun startWaitingSequenceId(contact: Contact) {
-                sequenceIdDeferred = CompletableDeferred()
-                contact.subscribe<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt> { event ->
-                    if (event.messageRandom == messageUid.toInt()) {
-                        sequenceIdDeferred.complete(event.sequenceId)
-                        return@subscribe ListeningStatus.STOPPED
-                    }
-
-                    return@subscribe ListeningStatus.LISTENING
+                sequenceIdDeferred = contact.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int> {
+                    if (it.messageRandom == messageUid.toInt()) {
+                        it.sequenceId
+                    } else null
                 }
             }
 

+ 3 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt

@@ -72,6 +72,9 @@ interface Member : QQ, Contact {
     /**
      * 禁言.
      *
+     * QQ 中最小操作和显示的时间都是一分钟.
+     * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
+     *
      * 管理员可禁言成员, 群主可禁言管理员和群员.
      *
      * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.

+ 101 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt

@@ -9,3 +9,104 @@
 
 package net.mamoe.mirai.event
 
+import kotlinx.coroutines.*
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值.
+ *
+ * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
+ *
+ * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
+ * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
+ *
+ * @see subscribingGetAsync 本函数的异步版本
+ */
+@MiraiExperimentalAPI
+suspend inline fun <reified E : Event, R : Any> subscribingGet(
+    timeoutMillis: Long = -1,
+    noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
+): R {
+    require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
+    return subscribingGetOrNull(timeoutMillis, filter) ?: error("timeout subscribingGet")
+}
+
+/**
+ * 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值.
+ *
+ * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
+ *
+ * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
+ * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
+ *
+ * @see subscribingGetAsync 本函数的异步版本
+ */
+@MiraiExperimentalAPI
+suspend inline fun <reified E : Event, R : Any> subscribingGetOrNull(
+    timeoutMillis: Long = -1,
+    noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
+): R? {
+    require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
+    var result: R? = null
+    var resultThrowable: Throwable? = null
+
+    if (timeoutMillis == -1L) {
+        @Suppress("DuplicatedCode") // for better performance
+        coroutineScope {
+            var listener: Listener<E>? = null
+            listener = this.subscribe {
+                val value = try {
+                    filter.invoke(this, it)
+                } catch (e: Exception) {
+                    resultThrowable = e
+                    return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
+                }
+
+                if (value != null) {
+                    result = value
+                    return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
+                } else return@subscribe ListeningStatus.LISTENING
+            }
+        }
+    } else {
+        withTimeoutOrNull(timeoutMillis) {
+            var listener: Listener<E>? = null
+            @Suppress("DuplicatedCode") // for better performance
+            listener = this.subscribe {
+                val value = try {
+                    filter.invoke(this, it)
+                } catch (e: Exception) {
+                    resultThrowable = e
+                    return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
+                }
+
+                if (value != null) {
+                    result = value
+                    return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() }
+                } else return@subscribe ListeningStatus.LISTENING
+            }
+        }
+    }
+    resultThrowable?.let { throw it }
+    return result
+}
+
+/**
+ * 异步监听这个事件, 并尝试从这个事件中获取一个值.
+ *
+ * 若 [filter] 抛出了一个异常, [Deferred.await] 会抛出这个异常或.
+ *
+ * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
+ * @param coroutineContext 额外的 [CoroutineContext]
+ * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
+ */
+@MiraiExperimentalAPI
+inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
+    coroutineContext: CoroutineContext = EmptyCoroutineContext,
+    timeoutMillis: Long = -1,
+    noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常
+): Deferred<R> = this.async(coroutineContext) {
+    subscribingGet(timeoutMillis, filter)
+}

+ 3 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt

@@ -101,6 +101,9 @@ interface Listener<in E : Event> : CompletableJob {
  *
  * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
  *
+ * @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值.
+ * @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值.
+ *
  * @see subscribeAlways 一直监听
  * @see subscribeOnce   只监听一次
  *

+ 9 - 8
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt

@@ -9,6 +9,7 @@
 
 package net.mamoe.mirai.message
 
+import kotlinx.coroutines.Job
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.event.Event
@@ -44,25 +45,25 @@ class GroupMessage(
      * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
      * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
      */
-    suspend inline fun quoteReply(message: MessageChain) = reply(this.message.quote() + message)
+    suspend inline fun quoteReply(message: MessageChain): MessageReceipt<Group> = reply(this.message.quote() + message)
 
-    suspend inline fun quoteReply(message: Message) = reply(this.message.quote() + message)
-    suspend inline fun quoteReply(plain: String) = reply(this.message.quote() + plain)
+    suspend inline fun quoteReply(message: Message): MessageReceipt<Group> = reply(this.message.quote() + message)
+    suspend inline fun quoteReply(plain: String): MessageReceipt<Group> = reply(this.message.quote() + plain)
 
 
     @JvmName("reply2")
-    suspend inline fun String.quoteReply() = quoteReply(this)
+    suspend inline fun String.quoteReply(): MessageReceipt<Group> = quoteReply(this)
 
     @JvmName("reply2")
-    suspend inline fun Message.quoteReply() = quoteReply(this)
+    suspend inline fun Message.quoteReply(): MessageReceipt<Group> = quoteReply(this)
 
     @JvmName("reply2")
-    suspend inline fun MessageChain.quoteReply() = quoteReply(this)
+    suspend inline fun MessageChain.quoteReply(): MessageReceipt<Group> = quoteReply(this)
 
     suspend inline fun MessageChain.recall() = group.recall(this)
     suspend inline fun MessageSource.recall() = group.recall(this)
-    inline fun MessageSource.recallIn(delay: Long) = group.recallIn(this, delay)
-    inline fun MessageChain.recallIn(delay: Long) = group.recallIn(this, delay)
+    inline fun MessageSource.recallIn(delay: Long): Job = group.recallIn(this, delay)
+    inline fun MessageChain.recallIn(delay: Long): Job = group.recallIn(this, delay)
 
     override fun toString(): String =
         "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"

+ 49 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt

@@ -21,6 +21,8 @@ import net.mamoe.mirai.contact.Member
 import net.mamoe.mirai.contact.QQ
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.event.events.BotEvent
+import net.mamoe.mirai.event.subscribingGet
+import net.mamoe.mirai.event.subscribingGetAsync
 import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.utils.*
 import kotlin.jvm.JvmName
@@ -109,6 +111,10 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
     suspend inline fun String.send() = this.toMessage().sendTo(subject)
     // endregion
 
+    operator fun <M : Message> get(at: Message.Key<M>): M {
+        return this.message[at]
+    }
+
     /**
      * 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException]
      */
@@ -134,4 +140,47 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) :
      */
     suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
     // endregion
+}
+
+/**
+ * 判断两个 [MessagePacket] 的 [MessagePacket.sender] 和 [MessagePacket.subject] 是否相同
+ */
+fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean {
+    return this.sender == another.sender && this.subject == another.subject
+}
+
+/**
+ * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [P] 相同且通过 [筛选][filter] 的 [MessagePacket]
+ *
+ * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
+ *
+ * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
+ * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值
+ *
+ * @see subscribingGetAsync 本函数的异步版本
+ */
+suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
+    timeoutMillis: Long = -1,
+    crossinline filter: P.(P) -> Boolean
+): P {
+    return subscribingGet<P, P>(timeoutMillis) {
+        takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) }
+    }
+}
+
+/**
+ * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [P] 相同的 [MessagePacket]
+ *
+ * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
+ *
+ * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
+ *
+ * @see subscribingGetAsync 本函数的异步版本
+ */
+suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
+    timeoutMillis: Long = -1
+): P {
+    return subscribingGet<P, P>(timeoutMillis) {
+        takeIf { this.isContextIdenticalWith(this@nextMessage) }
+    }
 }

+ 23 - 7
mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt

@@ -16,17 +16,14 @@ import net.mamoe.mirai.Bot
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.alsoLogin
 import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.contact.isOperator
 import net.mamoe.mirai.contact.sendMessage
 import net.mamoe.mirai.event.*
 import net.mamoe.mirai.message.FriendMessage
 import net.mamoe.mirai.message.GroupMessage
-import net.mamoe.mirai.message.data.AtAll
-import net.mamoe.mirai.message.data.Image
-import net.mamoe.mirai.message.data.PlainText
-import net.mamoe.mirai.message.data.firstOrNull
+import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.message.nextMessage
 import net.mamoe.mirai.message.sendAsImageTo
-import net.mamoe.mirai.qqandroid.Bot
-import net.mamoe.mirai.qqandroid.QQAndroid
 import net.mamoe.mirai.utils.FileBasedDeviceInfo
 import net.mamoe.mirai.utils.MiraiInternalAPI
 import java.io.File
@@ -49,7 +46,7 @@ private fun readTestAccount(): BotAccount? {
 
 @Suppress("UNUSED_VARIABLE")
 suspend fun main() {
-    val bot = QQAndroid.Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
+    val bot = Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
         123456789,
         "123456"
     ) {
@@ -207,6 +204,25 @@ fun Bot.messageDSL() {
         // sender: QQ
         // it: String (来自 MessageChain.toString)
         // group: Group
+
+        case("recall") {
+            reply("😎").recallIn(3000) // 3 秒后自动撤回这条消息
+        }
+
+        case("禁言") {
+            // 挂起当前协程, 等待下一条满足条件的消息.
+            // 发送 "禁言" 后需要再发送一条消息 at 一个人.
+            val value: At = nextMessage { message.any(At) }[At]
+            value.member().mute(10)
+        }
+
+        startsWith("群名=") {
+            if (!sender.isOperator()) {
+                sender.mute(5)
+                return@startsWith
+            }
+            group.name = it
+        }
     }
 }