From 086f04e1f508b5364f44d39f76bf9b729924e73f Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Thu, 20 Feb 2020 22:36:19 +0800
Subject: [PATCH] Add member.muteTimeRemaining

---
 .../net/mamoe/mirai/qqandroid/ContactImpl.kt  | 18 +++++++--
 .../packet/chat/receive/OnlinePush.kt         | 22 +++++-----
 .../kotlin/net.mamoe.mirai/contact/Member.kt  | 40 ++++++++++++++++---
 .../kotlin/net.mamoe.mirai/data/MemberInfo.kt |  2 +
 .../net.mamoe.mirai/event/events/BotEvents.kt |  9 ++++-
 5 files changed, 71 insertions(+), 20 deletions(-)

diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt
index af1977697..708a07064 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt
@@ -180,6 +180,15 @@ internal class MemberImpl(
     internal var _nameCard: String = memberInfo.nameCard
     internal var _specialTitle: String = memberInfo.specialTitle
 
+    var _muteTimestamp: Int = memberInfo.muteTimestamp
+
+    override val muteTimeRemaining: Int =
+        if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) {
+            0
+        } else {
+            _muteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
+        }
+
     override var nameCard: String
         get() = _nameCard
         set(newValue) {
@@ -215,7 +224,7 @@ internal class MemberImpl(
                             newValue
                         ).sendWithoutExpect()
                     }
-                    MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl).broadcast()
+                    MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast()
                 }
             }
         }
@@ -293,6 +302,7 @@ internal class MemberInfoImpl(
             else -> MemberPermission.MEMBER
         }
     override val specialTitle: String get() = jceInfo.sSpecialTitle ?: ""
+    override val muteTimestamp: Int get() = jceInfo.dwShutupTimestap?.toInt() ?: 0
 }
 
 /**
@@ -315,13 +325,13 @@ internal class GroupImpl(
     @UseExperimental(MiraiExperimentalAPI::class)
     override lateinit var botPermission: MemberPermission
 
-    var _botMuteRemaining: Int = groupInfo.botMuteRemaining
+    var _botMuteTimestamp: Int = groupInfo.botMuteRemaining
 
     override val botMuteRemaining: Int =
-        if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) {
+        if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) {
             0
         } else {
-            _botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
+            _botMuteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
         }
 
     override val members: ContactList<Member> = ContactList(members.mapNotNull {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
index 4aa8a4439..117b1601e 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
@@ -181,12 +181,12 @@ internal class OnlinePush {
                                         }
                                     } else {
                                         if (target == bot.uin) {
-                                            if (group._botMuteRemaining != time) {
+                                            if (group._botMuteTimestamp != time) {
                                                 if (time == 0) {
-                                                    group._botMuteRemaining = 0
+                                                    group._botMuteTimestamp = 0
                                                     return@mapNotNull BotUnmuteEvent(operator) as Packet
                                                 } else {
-                                                    group._botMuteRemaining = time
+                                                    group._botMuteTimestamp = time
                                                     return@mapNotNull BotMuteEvent(durationSeconds = time, operator = operator) as Packet
                                                 }
                                             } else {
@@ -194,11 +194,17 @@ internal class OnlinePush {
                                             }
                                         } else {
                                             val member = group[target]
-                                            // TODO: 2020/2/20 检查是否重复
-                                            return@mapNotNull if (time == 0) {
-                                                MemberUnmuteEvent(operator = operator, member = member)
+                                            member as MemberImpl
+                                            if (member._muteTimestamp != time) {
+                                                if (time == 0) {
+                                                    member._muteTimestamp = 0
+                                                    return@mapNotNull MemberUnmuteEvent(member, operator) as Packet
+                                                } else {
+                                                    member._muteTimestamp = time
+                                                    return@mapNotNull MemberMuteEvent(member, time, operator) as Packet
+                                                }
                                             } else {
-                                                MemberMuteEvent(operator = operator, member = member, durationSeconds = time) as Packet
+                                                return@mapNotNull null
                                             }
                                         }
                                     }
@@ -261,7 +267,6 @@ internal class OnlinePush {
                                     return@mapNotNull null
                                 }
                             }
-                            return@mapNotNull null
                         }
                         msgInfo.shMsgType.toInt() == 528 -> {
                             bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
@@ -274,7 +279,6 @@ internal class OnlinePush {
                             return@mapNotNull null
                         }
                     }
-                    return@mapNotNull null
                 }
             }
             return MultiPacket(packets)
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt
index d21bf9b1d..74f0a1431 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt
@@ -1,7 +1,7 @@
 /*
  * Copyright 2020 Mamoe Technologies and contributors.
  *
- * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可在以下链接找到该许可证.
  * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  *
  * https://github.com/mamoe/mirai/blob/master/LICENSE
@@ -29,33 +29,50 @@ interface Member : QQ, Contact {
 
     /**
      * 成员的权限, 动态更新.
+     *
+     * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发.
      */
     val permission: MemberPermission
 
     /**
-     * 群名片. 可能为空. 修改时将会触发事件
+     * 群名片. 可能为空.
+     *
+     * 管理员和群主都可修改任何人(包括群主)的群名片.
      *
      * 在修改时将会异步上传至服务器.
      *
      * @see [nameCardOrNick] 获取非空群名片或昵称
      *
-     * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件
+     * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
      * @throws PermissionDeniedException 无权限修改时
      */
     var nameCard: String
 
     /**
-     * 群头衔
+     * 群头衔.
+     *
+     * 仅群主可以修改群头衔.
      *
      * 在修改时将会异步上传至服务器.
      *
-     * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件
+     * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
      * @throws PermissionDeniedException 无权限修改时
      */
     var specialTitle: String
 
     /**
-     * 禁言
+     * 被禁言剩余时长. 单位为秒.
+     *
+     * @see isMuted 判断改成员是否处于禁言状态
+     * @see mute 设置禁言
+     * @see unmute 取消禁言
+     */
+    val muteTimeRemaining: Int
+
+    /**
+     * 禁言.
+     *
+     * 管理员可禁言成员, 群主可禁言管理员和群员.
      *
      * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
      * @return 机器人无权限时返回 `false`
@@ -72,6 +89,8 @@ interface Member : QQ, Contact {
     /**
      * 解除禁言.
      *
+     * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
+     *
      * @see MemberUnmuteEvent 成员被取消禁言事件.
      * @throws PermissionDeniedException 无权限修改时
      */
@@ -80,6 +99,8 @@ interface Member : QQ, Contact {
     /**
      * 踢出该成员.
      *
+     * 管理员可踢出成员, 群主可踢出管理员和群员.
+     *
      * @see MemberLeaveEvent.Kick 成员被踢出事件.
      * @throws PermissionDeniedException 无权限修改时
      */
@@ -98,6 +119,13 @@ interface Member : QQ, Contact {
  */
 val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick
 
+/**
+ * 判断改成员是否处于禁言状态.
+ */
+fun Member.isMuted(): Boolean {
+    return muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt()
+}
+
 @ExperimentalTime
 suspend inline fun Member.mute(duration: Duration) {
     require(duration.inDays <= 30) { "duration must be at most 1 month" }
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt
index 3ed39ef10..37e6ba4b0 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt
@@ -17,4 +17,6 @@ interface MemberInfo : FriendInfo {
     val permission: MemberPermission
 
     val specialTitle: String
+
+    val muteTimestamp: Int
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
index 0deab64d0..c9ad80052 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
@@ -333,7 +333,14 @@ data class MemberSpecialTitleChangeEvent(
      */
     val new: String,
 
-    override val member: Member
+    override val member: Member,
+
+    /**
+     * 操作人.
+     * 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改.
+     * 为 null 时则是机器人操作.
+     */
+    val operator: Member?
 ) : GroupMemberEvent
 
 // endregion