From bfdcee1a1c37f15c65fcb37faf861c012cb27d3d Mon Sep 17 00:00:00 2001
From: "jiahua.liu" <n@mamoe.net>
Date: Mon, 3 Feb 2020 00:15:09 +0800
Subject: [PATCH] List complete

---
 .../net/mamoe/mirai/qqandroid/ContactImpl.kt  | 10 +--
 .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt |  8 +--
 .../network/QQAndroidBotNetworkHandler.kt     | 65 +++++++++++++++----
 .../network/protocol/packet/PacketFactory.kt  |  3 +-
 .../protocol/packet/list/FriendListPacket.kt  | 14 ++--
 .../kotlin/net.mamoe.mirai/contact/Group.kt   | 37 ++---------
 6 files changed, 77 insertions(+), 60 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 39433be13..d747fd454 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
@@ -10,6 +10,7 @@ import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
 import net.mamoe.mirai.utils.*
 import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
 
 internal abstract class ContactImpl : Contact
 
@@ -74,13 +75,12 @@ internal class MemberImpl(
 
 @UseExperimental(MiraiInternalAPI::class)
 internal class GroupImpl(
-    bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long
+    bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long,
+    override var name: String,
+    override var announcement: String,
+    override var members: ContactList<Member>
 ) : ContactImpl(), Group {
     override lateinit var owner: Member
-    override lateinit var name: String
-    override lateinit var announcement: String
-    override lateinit var members: ContactList<Member>
-
     override val internalId: GroupInternalId = GroupId(id).toInternalId()
 
     override fun getMember(id: Long): Member =
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
index 861b8d66d..f51768af4 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
@@ -43,17 +43,17 @@ internal abstract class QQAndroidBotBase constructor(
     override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
 
     override suspend fun getGroup(id: GroupId): Group {
-        return groups.delegate.filteringGetOrAdd({ it.id == id.value }, { GroupImpl(this as QQAndroidBot, coroutineContext, id.value) })
+        return groups.delegate.getOrNull(id.value) ?: throw NoSuchElementException("Can not found group ${id.value}")
     }
 
     override suspend fun getGroup(internalId: GroupInternalId): Group {
-        internalId.toId().value.let { id ->
-            return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this as QQAndroidBot, coroutineContext, id) })
+        with(internalId.toId().value) {
+            return groups.delegate.getOrNull(this) ?: throw NoSuchElementException("Can not found group $this")
         }
     }
 
     override suspend fun getGroup(id: Long): Group {
-        return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this as QQAndroidBot, coroutineContext, id) })
+        return groups.delegate.getOrNull(id) ?: throw NoSuchElementException("Can not found group $id")
     }
 
     override suspend fun Image.getLink(): ImageLink {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
index f50153147..c09abb61f 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
@@ -9,11 +9,15 @@ import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.Input
 import kotlinx.io.core.buildPacket
 import kotlinx.io.core.use
+import net.mamoe.mirai.contact.ContactList
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
 import net.mamoe.mirai.data.MultiPacket
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.event.*
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.qqandroid.GroupImpl
+import net.mamoe.mirai.qqandroid.MemberImpl
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.QQImpl
 import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
@@ -106,8 +110,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     }
 
     override suspend fun init() {
-        //  delay(5000)
-
         this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
             if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
                 close()
@@ -149,28 +151,65 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
         }
 
         try {
-            bot.logger.info("开始加载群组列表")
+            bot.logger.info("开始加载群组列表与群成员列表")
             val troopData = FriendList.GetTroopListSimplify(
                 bot.client
             ).sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 1000)
+            println("获取到群数量" + troopData.groups.size)
+            val toGet: MutableMap<GroupImpl, ContactList<Member>> = mutableMapOf()
             troopData.groups.forEach {
-                bot.groups.delegate.addLast(GroupImpl(bot, EmptyCoroutineContext, it.groupUin))
+                val contactList = ContactList(LockFreeLinkedList<Member>())
+                val group =
+                    GroupImpl(bot, EmptyCoroutineContext, it.groupUin, it.groupName!!, it.groupMemo!!, contactList)
+                group.owner =
+                    MemberImpl(QQImpl(bot, EmptyCoroutineContext, it.dwGroupOwnerUin!!), group, EmptyCoroutineContext)
+                toGet[group] = contactList
+                bot.groups.delegate.addLast(group)
+                println(it.groupUin.toString() + " - " + it.groupCode)
             }
-            bot.logger.info("群组列表加载完成, 共 ${troopData.groups.size}个")
+            toGet.forEach {
+                try {
+                    getTroopMemberList(it.key, it.value)
+                } catch (e: Exception) {
+                    bot.logger.info("群${it.key.id}的列表拉取失败, 将采用动态加入")
+                }
+                delay(200)
+            }
+            bot.logger.info("群组列表与群成员加载完成, 共 ${troopData.groups.size}个")
         } catch (e: Exception) {
             bot.logger.info("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
         }
 
     }
 
-    suspend fun getTroopMemberList(groupUni: Long) {
-        bot.logger.info("开始群[$groupUni]成员")
-        val data = FriendList.GetTroopMemberList(
-            bot.client,
-            groupUni,
-            0
-        ).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 1000)
-        println(data.contentToString())
+    suspend fun getTroopMemberList(group: GroupImpl, list: ContactList<Member>): ContactList<Member> {
+        bot.logger.info("开始获取群[${group.id}]成员列表")
+        var size = 0
+        var nextUin = 0L
+        while (true) {
+            val data = FriendList.GetTroopMemberList(
+                bot.client,
+                group.id,
+                nextUin
+            ).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
+            data.members.forEach {
+                list.delegate.addLast(
+                    MemberImpl(
+                        QQImpl(bot, EmptyCoroutineContext, it.memberUin),
+                        group,
+                        EmptyCoroutineContext
+                    )
+                )
+            }
+            size += data.members.size
+            nextUin = data.nextUin
+            if (nextUin == 0L) {
+                break
+            }
+            println("已获取群[${group.id}]成员列表前" + size + "个成员")
+        }
+        println("群[${group.id}]成员全部获取完成, 共${list.size}个成员")
+        return list
     }
 
     /**
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
index 22fa8b51a..4f80d1114 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
@@ -116,7 +116,8 @@ internal object KnownPacketFactories {
         MessageSvc.PushForceOffline,
         MessageSvc.PbSendMsg,
         FriendList.GetFriendGroupList,
-        FriendList.GetTroopListSimplify
+        FriendList.GetTroopListSimplify,
+        FriendList.GetTroopMemberList
     )
 
     object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
index 5b6893725..019f6374e 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
@@ -27,10 +27,11 @@ internal class FriendList {
 
     internal object GetTroopMemberList :
         OutgoingPacketFactory<GetTroopMemberList.Response>("friendlist.GetTroopMemberListReq") {
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GetTroopMemberList.Response {
+        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
             val res = this.debugIfFail { this.decodeUniPacket(GetTroopMemberListResp.serializer()) }
             return Response(
-                res.vecTroopMember
+                res.vecTroopMember,
+                res.nextUin
             )
         }
 
@@ -52,10 +53,11 @@ internal class FriendList {
                             GetTroopMemberListReq.serializer(),
                             GetTroopMemberListReq(
                                 uin = client.uin,
-                                groupCode = GroupId(targetGroupId).toInternalId().value,
+                                groupCode = targetGroupId,
                                 groupUin = targetGroupId,
                                 nextUin = nextUin,
-                                reqType = 0
+                                reqType = 0,
+                                version = 2
                             )
                         )
                     )
@@ -64,7 +66,8 @@ internal class FriendList {
         }
 
         class Response(
-            val members: List<stTroopMemberInfo>
+            val members: List<stTroopMemberInfo>,
+            val nextUin: Long
         ) : Packet {
             override fun toString(): String = "Friendlist.GetTroopMemberList.Response"
         }
@@ -73,7 +76,6 @@ internal class FriendList {
 
     internal object GetTroopListSimplify :
         OutgoingPacketFactory<GetTroopListSimplify.Response>("friendlist.GetTroopListReqV2") {
-
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
             val res = this.decodeUniPacket(GetTroopListRespV2.serializer())
             return Response(res.vecTroopList.orEmpty())
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 55775a094..693466638 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
@@ -10,10 +10,11 @@ import net.mamoe.mirai.utils.coerceAtLeastOrFail
 /**
  * 群. 在 QQ Android 中叫做 "Troop"
  *
- * Group ID 与 Group Number 并不是同一个值.
- * - Group Number([Group.id]) 是通常使用的群号码.(在 QQ 客户端中可见)
- * - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见)
- * @author Him188moe
+ * Group UIN 与 Group Code 并不是同一个值.
+ * Group Code是在客户端显示的code
+ * Group Uin是QQ内部的群ID
+ * 在网络调用层 Code与Uin会被混用
+ * 但在开发层 你应该只关注Group Code
  */
 interface Group : Contact, CoroutineScope {
     /**
@@ -75,30 +76,4 @@ interface Group : Contact, CoroutineScope {
  *
  * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
  * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
- */
-inline class GroupId(inline val value: Long)
-
-/**
- * 将 [this] 转为 [GroupInternalId].
- */
-fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
-
-/**
- * 将无符号整数格式的 [Long] 转为 [GroupId].
- *
- * 注: 在 Java 中常用 [Long] 来表示 [UInt].
- *
- * 注: 在 Kotlin/Java, 有符号的数据类型的二进制最高位为符号标志.
- * 如一个 byte, `1000 0000` 最高位为 1, 则为负数.
- */
-fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))
-
-/**
- * 一些群 API 使用的 ID. 在使用时会特别注明
- *
- * 注: 在引用群 ID 时, 应使用 [GroupId] 或 [GroupInternalId] 类型, 而不是 [UInt]
- *
- * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
- * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
- */
-inline class GroupInternalId(inline val value: Long)
\ No newline at end of file
+ */
\ No newline at end of file