From ab464388c1a64fa55f3fedde4f0a55d6c5625009 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Sun, 24 May 2020 13:50:36 +0800
Subject: [PATCH] Support BotJoinGroupEvent.Invite, close #344

---
 .../mirai/qqandroid/message/convension.kt     |  5 +-
 .../protocol/packet/chat/NewContact.kt        | 20 +++--
 .../chat/receive/MessageSvc.PbGetMsg.kt       | 82 +++++++++----------
 .../kotlin/net.mamoe.mirai/contact/Group.kt   |  2 +
 .../net.mamoe.mirai/event/events/group.kt     | 49 +++++++++--
 .../net.mamoe.mirai/event/events/types.kt     |  4 +-
 6 files changed, 106 insertions(+), 56 deletions(-)

diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt
index a16b11763..b93466cd7 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt
@@ -450,9 +450,10 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
 internal fun contextualBugReportException(
     context: String,
     forDebug: String,
-    e: Throwable? = null
+    e: Throwable? = null,
+    additional: String = ""
 ): IllegalStateException {
-    return IllegalStateException("在 $context 时遇到了意料之中的问题. 请完整复制此日志提交给 mirai. 调试信息: $forDebug", e)
+    return IllegalStateException("在 $context 时遇到了意料之中的问题. 请完整复制此日志提交给 mirai. $additional 调试信息: $forDebug", e)
 }
 
 @OptIn(ExperimentalContracts::class)
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt
index 62e3c4478..ea756175c 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt
@@ -14,10 +14,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.readBytes
 import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
-import net.mamoe.mirai.event.events.BotLeaveEvent
-import net.mamoe.mirai.event.events.MemberJoinRequestEvent
-import net.mamoe.mirai.event.events.NewFriendRequestEvent
+import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.message.contextualBugReportException
 import net.mamoe.mirai.qqandroid.network.Packet
@@ -25,6 +22,7 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidClient
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Structmsg
 import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
 import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.getNewGroup
 import net.mamoe.mirai.qqandroid.utils._miraiContentToString
 import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs
 import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
@@ -169,10 +167,19 @@ internal class NewContact {
                                 }
                                 else -> throw contextualBugReportException(
                                     "parse SystemMsgNewGroup, subType=1",
-                                    forDebug = this._miraiContentToString()
+                                    this._miraiContentToString(),
+                                    additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群"
                                 )
                             }
                         }
+                        2 -> {
+                            // 被邀请入群, 自动同意
+
+                            val group = bot.getNewGroup(groupCode) ?: return null
+                            val invitor = group[actionUin]
+
+                            BotJoinGroupEvent.Invite(reqUinNick, invitor)
+                        }
                         5 -> {
                             val group = bot.getGroup(groupCode)
                             val operator = group[actionUin]
@@ -180,7 +187,8 @@ internal class NewContact {
                         }
                         else -> throw contextualBugReportException(
                             "parse SystemMsgNewGroup",
-                            forDebug = this._miraiContentToString()
+                            forDebug = this._miraiContentToString(),
+                            additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群"
                         )
                     }
                 } as Packet // 没有 as Packet 垃圾 kotlin 会把类型推断为Any
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
index 94a14e88e..33baf8fdd 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt
@@ -7,6 +7,8 @@
  * https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+
 package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
 
 import kotlinx.atomicfu.loop
@@ -105,42 +107,6 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
 
     object EmptyResponse : GetMsgSuccess(emptyList())
 
-    private suspend fun MsgComm.Msg.getNewGroup(bot: QQAndroidBot): Group? {
-        val troopNum = bot.network.run {
-            FriendList.GetTroopListSimplify(bot.client)
-                .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
-        }.groups.firstOrNull { it.groupUin == msgHead.fromUin } ?: return null
-
-        @Suppress("DuplicatedCode")
-        return GroupImpl(
-            bot = bot,
-            coroutineContext = bot.coroutineContext,
-            id = Group.calculateGroupCodeByGroupUin(msgHead.fromUin),
-            groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
-                this as GroupInfoImpl
-
-                if (this.delegate.groupName == null) {
-                    this.delegate.groupName = troopNum.groupName
-                }
-
-                if (this.delegate.groupMemo == null) {
-                    this.delegate.groupMemo = troopNum.groupMemo
-                }
-
-                if (this.delegate.groupUin == null) {
-                    this.delegate.groupUin = troopNum.groupUin
-                }
-
-                this.delegate.groupCode = troopNum.groupCode
-            },
-            members = bot._lowLevelQueryGroupMemberList(
-                troopNum.groupUin,
-                troopNum.groupCode,
-                troopNum.dwGroupOwnerUin
-            )
-        )
-    }
-
     private fun MsgComm.Msg.getNewMemberInfo(): MemberInfo {
         return object : MemberInfo {
             override val nameCard: String get() = ""
@@ -189,10 +155,10 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
                             }
                             // 新群
 
-                            val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
-                            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+                            val newGroup = bot.getNewGroup(Group.calculateGroupCodeByGroupUin(msg.msgHead.fromUin))
+                                ?: return@mapNotNull null
                             bot.groups.delegate.addLast(newGroup)
-                            return@mapNotNull BotJoinGroupEvent(newGroup)
+                            return@mapNotNull BotJoinGroupEvent.Active(newGroup)
                         } else {
                             group ?: return@mapNotNull null
 
@@ -212,12 +178,10 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
                                     discardExact(9)
                                     readByte().toInt().and(0xff)
                                 } == 0x83) {
-                                @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
                                 return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
                                     .also { group.members.delegate.addLast(it) })
                             }
 
-                            @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
                             return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
                                 .also { group.members.delegate.addLast(it) })
                         }
@@ -394,3 +358,39 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
     }
 }
 
+
+internal suspend fun QQAndroidBot.getNewGroup(groupCode: Long): Group? {
+    val troopNum = network.run {
+        FriendList.GetTroopListSimplify(client)
+            .sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 10_000, retry = 5)
+    }.groups.firstOrNull { it.groupCode == groupCode } ?: return null
+
+    @Suppress("DuplicatedCode")
+    return GroupImpl(
+        bot = this,
+        coroutineContext = coroutineContext,
+        id = groupCode,
+        groupInfo = _lowLevelQueryGroupInfo(troopNum.groupCode).apply {
+            this as GroupInfoImpl
+
+            if (this.delegate.groupName == null) {
+                this.delegate.groupName = troopNum.groupName
+            }
+
+            if (this.delegate.groupMemo == null) {
+                this.delegate.groupMemo = troopNum.groupMemo
+            }
+
+            if (this.delegate.groupUin == null) {
+                this.delegate.groupUin = troopNum.groupUin
+            }
+
+            this.delegate.groupCode = troopNum.groupCode
+        },
+        members = _lowLevelQueryGroupMemberList(
+            troopNum.groupUin,
+            troopNum.groupCode,
+            troopNum.dwGroupOwnerUin
+        )
+    )
+}
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 8833892a0..a79d731f7 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
@@ -174,6 +174,7 @@ abstract class Group : Contact(), CoroutineScope {
 
     companion object {
         /**
+         * 使用 groupCode 计算 groupUin. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意.
          * @suppress internal api
          */
         @JvmStatic
@@ -181,6 +182,7 @@ abstract class Group : Contact(), CoroutineScope {
             CommonGroupCalculations.calculateGroupUinByGroupCode(groupCode)
 
         /**
+         * 使用 groupUin 计算 groupCode. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意.
          * @suppress internal api
          */
         @JvmStatic
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt
index 4f8e3b74f..9426c83d2 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt
@@ -9,7 +9,7 @@
 
 @file:JvmMultifileClass
 @file:JvmName("BotEventsKt")
-@file:Suppress("unused", "FunctionName")
+@file:Suppress("unused", "FunctionName", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
 
 package net.mamoe.mirai.event.events
 
@@ -25,6 +25,7 @@ import net.mamoe.mirai.event.internal.MiraiAtomicBoolean
 import net.mamoe.mirai.qqandroid.network.Packet
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
 import net.mamoe.mirai.utils.internal.runBlocking
+import kotlin.internal.LowPriorityInOverloadResolution
 import kotlin.jvm.*
 
 /**
@@ -91,10 +92,44 @@ data class BotUnmuteEvent internal constructor(
 /**
  * Bot 成功加入了一个新群
  */
-@MiraiExperimentalAPI
-data class BotJoinGroupEvent internal constructor(
-    override val group: Group
-) : BotPassiveEvent, GroupEvent, Packet, AbstractEvent()
+sealed class BotJoinGroupEvent : GroupEvent, Packet, AbstractEvent() {
+    abstract override val group: Group
+
+    /**
+     * 不确定. 可能是主动加入
+     */
+    @MiraiExperimentalAPI
+    data class Active internal constructor(
+        override val group: Group
+    ) : BotPassiveEvent, GroupEvent, Packet, AbstractEvent() {
+        override fun toString(): String {
+            return "BotJoinGroupEvent.Active(group=$group)"
+        }
+    }
+
+    /**
+     * Bot 被一个群内的成员直接邀请加入了群.
+     *
+     * 此时服务器基于 Bot 的 QQ 设置自动同意了请求.
+     */
+    @MiraiExperimentalAPI
+    data class Invite internal constructor(
+        /**
+         * 邀请人昵称 (可能为备注或群名片)
+         */
+        val invitorName: String,
+        /**
+         * 邀请人
+         */
+        val invitor: Member
+    ) : BotPassiveEvent, GroupEvent, Packet, AbstractEvent() {
+        override val group: Group get() = invitor.group
+
+        override fun toString(): String {
+            return "BotJoinGroupEvent.Invite(invitorName='$invitorName', invitor=$invitor)"
+        }
+    }
+}
 
 // region 群设置
 
@@ -121,6 +156,7 @@ data class GroupNameChangeEvent internal constructor(
      */
     override val operator: Member?
 ) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent() {
+    @LowPriorityInOverloadResolution
     @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
     val isByBot: Boolean
         get() = operator == null
@@ -202,7 +238,8 @@ data class GroupAllowMemberInviteEvent internal constructor(
 /**
  * 成员已经加入群的事件
  */
-sealed class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent() {
+sealed class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet,
+    AbstractEvent() {
     /**
      * 被邀请加入群
      */
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt
index 51ddf519d..552a12252 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt
@@ -7,7 +7,7 @@
  * https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
-@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION")
+@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
 
 package net.mamoe.mirai.event.events
 
@@ -16,6 +16,7 @@ import net.mamoe.mirai.contact.Friend
 import net.mamoe.mirai.contact.Group
 import net.mamoe.mirai.contact.Member
 import net.mamoe.mirai.event.Event
+import kotlin.internal.HidesMembers
 import kotlin.jvm.JvmSynthetic
 
 /**
@@ -69,6 +70,7 @@ interface GroupOperableEvent : GroupEvent {
 /**
  * 是否由 [Bot] 操作
  */
+@HidesMembers
 @get:JvmSynthetic // inline: planning to change to another file (1.2.0)
 inline val GroupOperableEvent.isByBot: Boolean
     get() = operator == null