From 51ee123a1edd10dc42073e7ae2707df66de1f3bd Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Wed, 12 Feb 2020 20:03:19 +0800
Subject: [PATCH] Introduce information descriptors for contacts

---
 .../net/mamoe/mirai/qqandroid/ContactImpl.kt  | 124 +++++++++++++-----
 .../commonMain/kotlin/net.mamoe.mirai/Bot.kt  |  36 ++++-
 .../kotlin/net.mamoe.mirai/contact/Group.kt   |  10 ++
 .../kotlin/net.mamoe.mirai/data/FriendInfo.kt |  16 +++
 .../kotlin/net.mamoe.mirai/data/GroupInfo.kt  |  60 +++++++++
 .../kotlin/net.mamoe.mirai/data/MemberInfo.kt |  20 +++
 6 files changed, 228 insertions(+), 38 deletions(-)
 create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt
 create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt
 create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt

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 08eefee5c..e986e74a1 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
@@ -11,9 +11,7 @@ package net.mamoe.mirai.qqandroid
 
 import kotlinx.coroutines.launch
 import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.data.FriendNameRemark
-import net.mamoe.mirai.data.PreviousNameList
-import net.mamoe.mirai.data.Profile
+import net.mamoe.mirai.data.*
 import net.mamoe.mirai.event.broadcast
 import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
@@ -24,6 +22,7 @@ import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.message.data.NotOnlineImageFromFile
 import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
 import net.mamoe.mirai.qqandroid.network.highway.postImage
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore
@@ -33,6 +32,7 @@ import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.io.toUHexString
 import kotlin.coroutines.CoroutineContext
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
 
 internal abstract class ContactImpl : Contact {
     override fun hashCode(): Int {
@@ -49,10 +49,22 @@ internal abstract class ContactImpl : Contact {
     }
 }
 
-internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
-    override val bot: QQAndroidBot by bot.unsafeWeakRef()
+internal inline class FriendInfoImpl(
+    private val jceFriendInfo: JceFriendInfo
+) : FriendInfo {
+    override val nick: String get() = jceFriendInfo.nick ?: ""
+    override val uin: Long get() = jceFriendInfo.friendUin
+}
 
-    override lateinit var nick: String
+internal class QQImpl(
+    bot: QQAndroidBot,
+    override val coroutineContext: CoroutineContext,
+    override val id: Long,
+    private val friendInfo: FriendInfo
+) : ContactImpl(), QQ {
+    override val bot: QQAndroidBot by bot.unsafeWeakRef()
+    override val nick: String
+        get() = friendInfo.nick
 
     override suspend fun sendMessage(message: MessageChain) {
         val event = FriendMessageSendEvent(this, message).broadcast()
@@ -135,14 +147,17 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
         image.input.close()
     }
 
+    @MiraiExperimentalAPI
     override suspend fun queryProfile(): Profile {
         TODO("not implemented")
     }
 
+    @MiraiExperimentalAPI
     override suspend fun queryPreviousNameList(): PreviousNameList {
         TODO("not implemented")
     }
 
+    @MiraiExperimentalAPI
     override suspend fun queryRemark(): FriendNameRemark {
         TODO("not implemented")
     }
@@ -156,24 +171,27 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin
 }
 
 
+@Suppress("MemberVisibilityCanBePrivate")
 internal class MemberImpl(
     qq: QQImpl,
-    var _groupCard: String,
-    var _specialTitle: String,
     group: GroupImpl,
     override val coroutineContext: CoroutineContext,
-    override var permission: MemberPermission
+    memberInfo: MemberInfo
 ) : ContactImpl(), Member, QQ by qq {
     override val group: GroupImpl by group.unsafeWeakRef()
     val qq: QQImpl by qq.unsafeWeakRef()
 
+    override var permission: MemberPermission = memberInfo.permission
+    internal var _nameCard: String = memberInfo.nameCard
+    internal var _specialTitle: String = memberInfo.specialTitle
+
     override var nameCard: String
-        get() = _groupCard
+        get() = _nameCard
         set(newValue) {
             group.checkBotPermissionOperator()
-            if (_groupCard != newValue) {
-                val oldValue = _groupCard
-                _groupCard = newValue
+            if (_nameCard != newValue) {
+                val oldValue = _nameCard
+                _nameCard = newValue
                 launch {
                     bot.network.run {
                         TroopManagement.EditGroupNametag(
@@ -223,7 +241,8 @@ internal class MemberImpl(
             ).sendAndExpect<TroopManagement.Mute.Response>()
         }
 
-        MemberMuteEvent(this@MemberImpl, durationSeconds, null).broadcast()
+        @Suppress("RemoveRedundantQualifierName") // or unresolved reference
+        net.mamoe.mirai.event.events.MemberMuteEvent(this@MemberImpl, durationSeconds, null).broadcast()
         return true
     }
 
@@ -241,7 +260,8 @@ internal class MemberImpl(
             ).sendAndExpect<TroopManagement.Mute.Response>()
         }
 
-        MemberUnmuteEvent(this@MemberImpl, null).broadcast()
+        @Suppress("RemoveRedundantQualifierName") // or unresolved reference
+        net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast()
         return true
     }
 
@@ -269,25 +289,60 @@ internal class MemberImpl(
     override fun hashCode(): Int = super.hashCode()
 }
 
+internal class MemberInfoImpl(
+    private val jceInfo: StTroopMemberInfo,
+    private val groupOwnerId: Long
+) : MemberInfo {
+    override val uin: Long get() = jceInfo.memberUin
+    override val nameCard: String get() = jceInfo.sName ?: ""
+    override val nick: String get() = jceInfo.nick
+    override val permission: MemberPermission
+        get() = when {
+            jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER
+            jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
+            else -> MemberPermission.MEMBER
+        }
+    override val specialTitle: String get() = jceInfo.sSpecialTitle ?: ""
+}
 
 /**
  * 对GroupImpl
  * 中name/announcement的更改会直接向服务器异步汇报
  */
+@Suppress("PropertyName")
 @UseExperimental(MiraiInternalAPI::class)
 internal class GroupImpl(
     bot: QQAndroidBot, override val coroutineContext: CoroutineContext,
     override val id: Long,
-    val uin: Long,
-    var _name: String,
-    var _announcement: String,
-    var _allowMemberInvite: Boolean,
-    var _confessTalk: Boolean,
-    var _muteAll: Boolean,
-    var _autoApprove: Boolean,
-    var _anonymousChat: Boolean,
-    override val members: ContactList<Member>
+    groupInfo: GroupInfo,
+    members: Sequence<MemberInfo>
 ) : ContactImpl(), Group {
+    override val bot: QQAndroidBot by bot.unsafeWeakRef()
+    val uin: Long = groupInfo.uin
+
+    override lateinit var owner: Member
+
+    @UseExperimental(MiraiExperimentalAPI::class)
+    override lateinit var botPermission: MemberPermission
+
+    override val members: ContactList<Member> = ContactList(members.asSequence().mapNotNull {
+        if (it.uin == bot.uin) {
+            botPermission = it.permission
+            null
+        } else Member(it).also { member ->
+            if (member.permission == MemberPermission.OWNER) {
+                owner = member
+            }
+        }
+    }.toLockFreeLinkedList())
+
+    internal var _name: String = groupInfo.name
+    internal var _announcement: String = groupInfo.memo
+    internal var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
+    internal var _confessTalk: Boolean = groupInfo.confessTalk
+    internal var _muteAll: Boolean = groupInfo.muteAll
+    internal var _autoApprove: Boolean = groupInfo.autoApprove
+    internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat
 
     override var name: String
         get() = _name
@@ -304,7 +359,7 @@ internal class GroupImpl(
                             newName = newValue
                         ).sendWithoutExpect()
                     }
-                    GroupNameChangeEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
+                    GroupNameChangeEvent(oldValue, newValue, this@GroupImpl, true).broadcast()
                 }
             }
         }
@@ -377,7 +432,7 @@ internal class GroupImpl(
                             switch = newValue
                         ).sendWithoutExpect()
                     }
-                    GroupAllowConfessTalkEvent(oldValue, newValue, this@GroupImpl, null).broadcast()
+                    GroupAllowConfessTalkEvent(oldValue, newValue, this@GroupImpl, true).broadcast()
                 }
             }
         }
@@ -403,16 +458,21 @@ internal class GroupImpl(
             }
         }
 
-
-    override lateinit var owner: Member
-    @UseExperimental(MiraiExperimentalAPI::class)
-    override var botPermission: MemberPermission = MemberPermission.MEMBER
-
     override suspend fun quit(): Boolean {
         check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" }
         TODO("not implemented")
     }
 
+    @UseExperimental(MiraiExperimentalAPI::class)
+    override fun Member(memberInfo: MemberInfo): Member {
+        return MemberImpl(
+            bot.QQ(memberInfo) as QQImpl,
+            this,
+            this.coroutineContext,
+            memberInfo
+        )
+    }
+
 
     override operator fun get(id: Long): Member {
         return members.delegate.filteringGetOrNull { it.id == id } ?: throw NoSuchElementException("member $id not found in group $uin")
@@ -426,8 +486,6 @@ internal class GroupImpl(
         return members.delegate.filteringGetOrNull { it.id == id }
     }
 
-    override val bot: QQAndroidBot by bot.unsafeWeakRef()
-
     override suspend fun sendMessage(message: MessageChain) {
         val event = GroupMessageSendEvent(this, message).broadcast()
         if (event.isCancelled) {
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
index c87295985..c8fac4b3f 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -19,13 +19,13 @@ import kotlinx.io.core.IoBuffer
 import kotlinx.io.core.use
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.AddFriendResult
+import net.mamoe.mirai.data.FriendInfo
+import net.mamoe.mirai.data.GroupInfo
+import net.mamoe.mirai.data.MemberInfo
 import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.MiraiLogger
-import net.mamoe.mirai.utils.WeakRef
+import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.io.transferTo
-import net.mamoe.mirai.utils.toList
 
 /**
  * 机器人对象. 一个机器人实例登录一个 QQ 账号.
@@ -65,6 +65,13 @@ abstract class Bot : CoroutineScope {
      */
     abstract val uin: Long
 
+    /**
+     * 昵称
+     */
+    @MiraiExperimentalAPI("还未支持")
+    val nick: String
+        get() = TODO("bot 昵称获取")
+
     /**
      * 日志记录器
      */
@@ -116,7 +123,7 @@ abstract class Bot : CoroutineScope {
      * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job.
      * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭.
      */
-    abstract fun QQ(id: Long): QQ
+    abstract fun QQ(friendInfo: FriendInfo): QQ
 
     /**
      * 机器人加入的群列表.
@@ -131,6 +138,25 @@ abstract class Bot : CoroutineScope {
             ?: throw NoSuchElementException("No such group $id for bot ${this.uin}")
     }
 
+    /**
+     * 获取群列表. 返回值前 32 bits 为 uin, 后 32 bits 为 groupCode
+     */
+    abstract suspend fun queryGroupList(): Sequence<Long>
+
+    /**
+     * 查询群资料. 获得的仅为当前时刻的资料.
+     * 请优先使用 [getGroup] 然后查看群资料.
+     */
+    abstract suspend fun queryGroupInfo(id: Long): GroupInfo
+
+    /**
+     * 查询群成员列表.
+     * 请优先使用 [getGroup], [Group.members] 查看群成员.
+     *
+     * 这个函数很慢. 请不要频繁使用.
+     */
+    abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
+
     // TODO 目前还不能构造群对象. 这将在以后支持
 
     // endregion
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
index d4448a6ea..ff0a60346 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
@@ -12,8 +12,10 @@
 package net.mamoe.mirai.contact
 
 import kotlinx.coroutines.CoroutineScope
+import net.mamoe.mirai.data.MemberInfo
 import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import kotlin.jvm.JvmName
 
 /**
  * 群. 在 QQ Android 中叫做 "Troop"
@@ -117,6 +119,14 @@ interface Group : Contact, CoroutineScope {
      */
     suspend fun quit(): Boolean
 
+    /**
+     * 构造一个 [Member].
+     * 非特殊情况请不要使用这个函数. 优先使用 [get].
+     */
+    @MiraiExperimentalAPI("dangerous")
+    @Suppress("INAPPLICABLE_JVM_NAME")
+    @JvmName("newMember")
+    fun Member(memberInfo: MemberInfo): Member
 
     companion object {
 
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt
new file mode 100644
index 000000000..4eeb0cfff
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 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
+ */
+
+package net.mamoe.mirai.data
+
+interface FriendInfo {
+    val uin: Long
+
+    val nick: String
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt
new file mode 100644
index 000000000..20a1e3db5
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt
@@ -0,0 +1,60 @@
+package net.mamoe.mirai.data
+
+import net.mamoe.mirai.Bot
+
+/**
+ * 群资料.
+ *
+ * 通过 [Bot.queryGroupInfo] 得到
+ */
+interface GroupInfo {
+    /**
+     * Uin
+     */
+    val uin: Long
+
+    /**
+     * 群号码
+     */ // 由 uin 计算得到
+    val groupCode: Long
+
+    /**
+     * 名称
+     */
+    val name: String
+
+    /**
+     * 群主
+     */
+    val owner: Long
+
+    /**
+     * 入群公告
+     */
+    val memo: String
+
+    /**
+     * 允许群员邀请其他人加入群
+     */
+    val allowMemberInvite: Boolean
+
+    /**
+     * 允许匿名聊天
+     */
+    val allowAnonymousChat: Boolean
+
+    /**
+     * 自动审批加群请求
+     */
+    val autoApprove: Boolean
+
+    /**
+     * 坦白说开启状态
+     */
+    val confessTalk: Boolean
+
+    /**
+     * 全员禁言
+     */
+    val muteAll: Boolean
+}
\ No newline at end of file
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
new file mode 100644
index 000000000..3ed39ef10
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 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
+ */
+
+package net.mamoe.mirai.data
+
+import net.mamoe.mirai.contact.MemberPermission
+
+interface MemberInfo : FriendInfo {
+    val nameCard: String
+
+    val permission: MemberPermission
+
+    val specialTitle: String
+}
\ No newline at end of file