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 b136af038..259fe6591 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
@@ -325,6 +325,15 @@ internal class GroupImpl(
     @UseExperimental(MiraiExperimentalAPI::class)
     override lateinit var botPermission: MemberPermission
 
+    var _botMuteRemaining: Int = groupInfo.botMuteRemaining
+
+    override val botMuteRemaining: Int =
+        if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) {
+            0
+        } else {
+            _botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
+        }
+
     override val members: ContactList<Member> = ContactList(members.mapNotNull {
         if (it.uin == bot.uin) {
             botPermission = it.permission
@@ -487,6 +496,7 @@ internal class GroupImpl(
     }
 
     override suspend fun sendMessage(message: MessageChain) {
+        check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" }
         val event = GroupMessageSendEvent(this, message).broadcast()
         if (event.isCancelled) {
             throw EventCancelledException("cancelled by FriendMessageSendEvent")
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt
index e073d068d..3d12d9022 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt
@@ -44,6 +44,7 @@ internal inline class GroupInfoImpl(
     override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0
     override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0
     override val muteAll: Boolean get() = delegate.shutupTimestamp != 0
+    override val botMuteRemaining: Int get() = delegate.shutupTimestampMe ?: 0
 }
 
 internal class TroopManagement {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
index 90bc21f74..7aad3fefa 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
@@ -11,12 +11,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
+import net.mamoe.mirai.contact.Group
+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.BroadcastControllable
+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.message.FriendMessage
 import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.qqandroid.GroupImpl
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
 import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
@@ -32,6 +38,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
 import net.mamoe.mirai.qqandroid.network.protocol.packet.*
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
+import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
 import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.currentTimeSeconds
 import kotlin.math.absoluteValue
@@ -94,13 +103,13 @@ internal class MessageSvc {
         }
 
         @UseExperimental(MiraiInternalAPI::class)
-        internal class GetMsgSuccess(delegate: List<FriendMessage>) : Response(MsgSvc.SyncFlag.STOP, delegate)
+        internal class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate)
 
         /**
          * 不要直接 expect 这个 class. 它可能
          */
         @MiraiInternalAPI
-        open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<FriendMessage>) : MultiPacket<FriendMessage>(delegate),
+        open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<Packet>) : MultiPacket<Packet>(delegate),
             BroadcastControllable {
             override val shouldBroadcast: Boolean
                 get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP
@@ -112,7 +121,7 @@ internal class MessageSvc {
 
         object EmptyResponse : Response(MsgSvc.SyncFlag.STOP, emptyList())
 
-        @UseExperimental(MiraiInternalAPI::class)
+        @UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
             // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
             val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
@@ -130,40 +139,86 @@ internal class MessageSvc {
                 return EmptyResponse
             }
 
-            val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull {
-                when (it.msgHead.msgType) {
-                    33 -> {
-                        if (it.msgHead.authUin == bot.uin) {
-                            val group = bot.getGroupByUinOrNull(it.msgHead.fromUin)
-                            if (group == null) {
-                                TODO("查询群信息, 添加群")
+            val messages = resp.uinPairMsgs.asSequence()
+                .filterNot { it.msg == null }
+                .flatMap { it.msg!!.asSequence() }
+                .toList() // so as to inline
+                .mapNotNull<MsgComm.Msg, Packet> { msg ->
+                    when (msg.msgHead.msgType) {
+                        33 -> {
+                            val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
+                            if (msg.msgHead.authUin == bot.uin) {
+                                if (group != null) {
+                                    error("group is not null while bot is invited to the group")
+                                }
+                                // 新群
+
+                                val troopNum = bot.network.run {
+                                    FriendList.GetTroopListSimplify(bot.client)
+                                        .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
+                                }.groups.first { it.groupUin == msg.msgHead.fromUin }
+
+
+                                val newGroup = GroupImpl(
+                                    bot = bot,
+                                    coroutineContext = bot.coroutineContext,
+                                    id = Group.calculateGroupCodeByGroupUin(msg.msgHead.fromUin),
+                                    groupInfo = bot.queryGroupInfo(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.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin)
+                                )
+                                bot.groups.delegate.addLast(newGroup)
+                                return@mapNotNull BotJoinGroupEvent(newGroup)
+                            } else {
+                                checkNotNull(group) { "group is null while a member is joining to" }
+                                if (group.members.contains(msg.msgHead.authUin)) {
+                                    return@mapNotNull null
+                                } else {
+                                    return@mapNotNull MemberJoinEvent(group.Member(object : MemberInfo {
+                                        override val nameCard: String get() = ""
+                                        override val permission: MemberPermission get() = MemberPermission.MEMBER
+                                        override val specialTitle: String get() = ""
+                                        override val uin: Long get() = msg.msgHead.authUin
+                                        override val nick: String get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromNick
+                                    }).also { group.members.delegate.addLast(it) })
+                                }
                             }
                         }
-
-                        TODO("为 group 添加一个 fun Member() 来构造 member")
-                        // bot.getGroupByUin(it.msgHead.fromUin).members.delegate.addLast()
-                        println("GroupUin" + it.msgHead.fromUin + "新群员" + it.msgHead.authUin + " 出现了[" + it.msgHead.authNick + "] 添加刷新")
-                        null
-                    }
-                    166 -> {
-                        when {
-                            it.msgHead.fromUin == bot.uin -> null
-                            !bot.firstLoginSucceed -> null
-                            else -> FriendMessage(
-                                bot,
-                                bot.getFriend(it.msgHead.fromUin),
-                                it.toMessageChain()
-                            )
+                        166 -> {
+                            return@mapNotNull when {
+                                msg.msgHead.fromUin == bot.uin -> null
+                                !bot.firstLoginSucceed -> null
+                                else -> FriendMessage(
+                                    bot,
+                                    bot.getFriend(msg.msgHead.fromUin),
+                                    msg.toMessageChain()
+                                )
+                            }
                         }
+                        else -> return@mapNotNull null
                     }
-                    else -> null
                 }
-            }.toMutableList()
             if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
                 messages.ifEmpty {
                     return EmptyResponse
                 }
-                return GetMsgSuccess(mutableListOf(messages.last()))
+                return GetMsgSuccess(listOf(messages.last()))
             }
             return Response(resp.syncFlag, messages)
         }
@@ -201,7 +256,8 @@ internal class MessageSvc {
             }
 
             data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
-                override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
+                override fun toString(): String =
+                    "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
             }
         }
 
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 9ab84545c..3754f973e 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
@@ -187,6 +187,9 @@ internal class OnlinePush {
                                             )
                                         }
                                     } else {
+                                        if (target == bot.uin) {
+
+                                        }
                                         val member = group[target]
                                         if (time == 0) {
                                             MemberUnmuteEvent(operator = operator, member = member)
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
index 96ba7609c..42a96bec4 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
@@ -14,6 +14,7 @@ package net.mamoe.mirai.contact
 import kotlinx.coroutines.CoroutineScope
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.event.events.BeforeImageUploadEvent
+import net.mamoe.mirai.event.events.EventCancelledException
 import net.mamoe.mirai.event.events.ImageUploadEvent
 import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
 import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
@@ -47,16 +48,20 @@ interface Contact : CoroutineScope {
      *
      * @see FriendMessageSendEvent 发送好友信息事件, cancellable
      * @see GroupMessageSendEvent  发送群消息事件. cancellable
+     *
+     * @throws EventCancelledException 当发送消息事件被取消
+     * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
      */
     suspend fun sendMessage(message: MessageChain)
 
     /**
      * 上传一个图片以备发送.
-     * TODO: 群图片与好友图片之间是否通用还不确定.
-     * TODO: 好友之间图片是否通用还不确定.
+     * TODO 群图片与好友图片在服务器上是通用的, 在 mirai 目前不通用.
      *
      * @see BeforeImageUploadEvent 图片发送前事件, cancellable
      * @see ImageUploadEvent 图片发送完成事件
+     *
+     * @throws EventCancelledException 当发送消息事件被取消
      */
     suspend fun uploadImage(image: ExternalImage): Image
 
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 ff0a60346..7414d14c3 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
@@ -24,18 +24,20 @@ interface Group : Contact, CoroutineScope {
     /**
      * 群名称.
      *
-     * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
+     * 在修改时将会异步上传至服务器.
      * 频繁修改可能会被服务器拒绝.
      *
      * @see MemberPermissionChangeEvent
+     * @throws PermissionDeniedException 无权限修改时将会抛出异常
      */
     var name: String
     /**
      * 入群公告, 没有时为空字符串.
      *
-     * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
+     * 在修改时将会异步上传至服务器.
      *
      * @see GroupEntranceAnnouncementChangeEvent
+     * @throws PermissionDeniedException 无权限修改时将会抛出异常
      */
     var entranceAnnouncement: String
     /**
@@ -49,17 +51,19 @@ interface Group : Contact, CoroutineScope {
     /**
      * 坦白说状态. `true` 为允许.
      *
-     * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
-
+     * 在修改时将会异步上传至服务器.
+     *
      * @see GroupAllowConfessTalkEvent
+     * @throws PermissionDeniedException 无权限修改时将会抛出异常
      */
     var confessTalk: Boolean
     /**
      * 允许群员邀请好友入群的状态. `true` 为允许
      *
-     * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException]
+     * 在修改时将会异步上传至服务器.
      *
      * @see GroupAllowMemberInviteEvent
+     * @throws PermissionDeniedException 无权限修改时将会抛出异常
      */
     var allowMemberInvite: Boolean
     /**
@@ -77,10 +81,17 @@ interface Group : Contact, CoroutineScope {
     override val id: Long
 
     /**
-     * 群主 (同步事件更新)
+     * 群主
      */
     val owner: Member
 
+    /**
+     * 机器人被禁言还剩余多少秒
+     *
+     * @see BotMuteEvent
+     * @see isBotMuted
+     */
+    val botMuteRemaining: Int
 
     /**
      * 机器人在这个群里的权限
@@ -124,7 +135,7 @@ interface Group : Contact, CoroutineScope {
      * 非特殊情况请不要使用这个函数. 优先使用 [get].
      */
     @MiraiExperimentalAPI("dangerous")
-    @Suppress("INAPPLICABLE_JVM_NAME")
+    @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName")
     @JvmName("newMember")
     fun Member(memberInfo: MemberInfo): Member
 
@@ -168,4 +179,9 @@ interface Group : Contact, CoroutineScope {
 
     @MiraiExperimentalAPI
     fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
-}
\ No newline at end of file
+}
+
+/**
+ * 返回机器人是否正在被禁言
+ */
+val Group.isBotMuted: Boolean get() = this.botMuteRemaining == 0
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
index 98b722d6d..0a594a4b0 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt
@@ -57,4 +57,9 @@ interface GroupInfo {
      * 全员禁言
      */
     val muteAll: Boolean
+
+    /**
+     * 机器人被禁言还剩时间, 秒.
+     */
+    val botMuteRemaining: 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 79f888a41..b33e6c205 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
@@ -18,6 +18,7 @@ import net.mamoe.mirai.event.CancellableEvent
 import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.utils.ExternalImage
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
 
 
 @Suppress("unused")
@@ -123,6 +124,26 @@ data class BotGroupPermissionChangeEvent(
     val new: MemberPermission
 ) : BotPassiveEvent, GroupEvent, Packet
 
+/**
+ * Bot 被禁言
+ */
+data class BotMuteEvent(
+    val durationSeconds: Int,
+    override val group: Group,
+    /**
+     * 操作人. 为 null 则为机器人操作
+     */
+    val operator: Member?
+) : GroupEvent, Packet, BotPassiveEvent
+
+/**
+ * Bot 加入了一个新群
+ */
+@MiraiExperimentalAPI
+data class BotJoinGroupEvent(
+    override val group: Group
+) : BotPassiveEvent, GroupEvent, Packet
+
 // region 群设置
 
 /**
@@ -219,7 +240,7 @@ data class GroupAllowMemberInviteEvent(
 /**
  * 成员加入群的事件
  */
-data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent
+data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet
 
 /**
  * 成员离开群的事件