From 4ac7d3fa9a51dcbde3ed5567b52b796265219d9f Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Sat, 13 Feb 2021 11:34:23 +0800
Subject: [PATCH 1/2] Support Dice (#1018)

* Add Dice public API #1012, close #1017

* Extract MarketFaceImpl to separate file

* Dice protocol impl #1012

* Dice refinement

* Add serialization support for Dice

* Add mirai code support for Dice

* Update docs/Messages.md

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Update mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt

Co-authored-by: Karlatemp <karlatemp@vip.qq.com>

* Add dice mirai code test

Co-authored-by: sandtechnology <a1294790523@hotmail.com>
Co-authored-by: lc6a <1952511149@qq.com>
Co-authored-by: Karlatemp <karlatemp@vip.qq.com>
---
 .../api/binary-compatibility-validator.api    | 35 +++++++
 docs/Messages.md                              |  5 +-
 .../message/MessageSerializersImpl.kt         |  2 +
 .../kotlin/message/code/internal/impl.kt      |  5 +-
 .../commonMain/kotlin/message/data/Dice.kt    | 67 +++++++++++++
 .../kotlin/message/data/MarketFace.kt         |  4 +-
 .../kotlin/message/code/TestMiraiCode.kt      |  3 +
 .../kotlin/contact/SendMessageHandler.kt      | 16 +++-
 .../kotlin/message/MarketFaceImpl.kt          | 93 +++++++++++++++++++
 .../kotlin/message/ReceiveMessageHandler.kt   |  2 +-
 .../src/commonMain/kotlin/message/faceImpl.kt | 18 ----
 .../message/data/MessageSerializationTest.kt  |  3 +-
 12 files changed, 227 insertions(+), 26 deletions(-)
 create mode 100644 mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
 create mode 100644 mirai-core/src/commonMain/kotlin/message/MarketFaceImpl.kt

diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api
index 91edf6844..aa4beb76c 100644
--- a/binary-compatibility-validator/api/binary-compatibility-validator.api
+++ b/binary-compatibility-validator/api/binary-compatibility-validator.api
@@ -3475,6 +3475,41 @@ public final class net/mamoe/mirai/message/data/CustomMessageMetadata$Companion
 	public final fun serializer ()Lkotlinx/serialization/KSerializer;
 }
 
+public final class net/mamoe/mirai/message/data/Dice : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MarketFace {
+	public static final field Key Lnet/mamoe/mirai/message/data/Dice$Key;
+	public static final field SERIAL_NAME Ljava/lang/String;
+	public fun <init> (I)V
+	public synthetic fun <init> (IILkotlinx/serialization/internal/SerializationConstructorMarker;)V
+	public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V
+	public final fun component1 ()I
+	public final fun copy (I)Lnet/mamoe/mirai/message/data/Dice;
+	public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/Dice;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Dice;
+	public fun equals (Ljava/lang/Object;)Z
+	public fun getId ()I
+	public fun getName ()Ljava/lang/String;
+	public final fun getValue ()I
+	public fun hashCode ()I
+	public static final fun random ()Lnet/mamoe/mirai/message/data/Dice;
+	public fun toString ()Ljava/lang/String;
+	public static final fun write$Self (Lnet/mamoe/mirai/message/data/Dice;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
+}
+
+public final class net/mamoe/mirai/message/data/Dice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+	public static final field INSTANCE Lnet/mamoe/mirai/message/data/Dice$$serializer;
+	public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+	public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+	public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Dice;
+	public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+	public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+	public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Dice;)V
+	public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class net/mamoe/mirai/message/data/Dice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
+	public final fun random ()Lnet/mamoe/mirai/message/data/Dice;
+	public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
 public final class net/mamoe/mirai/message/data/EmptyMessageChain : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/data/MessageChain {
 	public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain;
 	public synthetic fun add (ILjava/lang/Object;)V
diff --git a/docs/Messages.md b/docs/Messages.md
index f31caf988..0ec299070 100644
--- a/docs/Messages.md
+++ b/docs/Messages.md
@@ -76,6 +76,7 @@ Mirai 支持富文本消息。
 [`FlashImage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt
 [`MarketFace`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt
 [`MusicShare`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt
+[`Dice`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
 
 [`MessageSource`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt
 [`QuoteReply`]: ../mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt
@@ -102,7 +103,8 @@ Mirai 支持富文本消息。
 |      [`MarketFace`]      | 商城表情              | `[表情对应的中文名]`       |          2.0          |
 |    [`ForwardMessage`]    | 合并转发              | `[转发消息]`             | 2.0  *<sup>(1)</sup>* |
 | [`SimpleServiceMessage`] | (不稳定)服务消息      | `$content`              |          2.0          |
-|      [`MusicShare`]      | 音乐分享              | `[分享]曲名`              |          2.1          |
+|      [`MusicShare`]      | 音乐分享              | `[分享]曲名`             |          2.1          |
+|         [`Dice`]         | 骰子                 | `[骰子:$value]`          |          2.5          |
 
 
 
@@ -357,6 +359,7 @@ at.serializeToMiraiCode() // 结果为 `[mirai:at:123]`
 |       [`VipFace`]        | `[mirai:vipface:${kind.id},${kind.name},$count]` |
 |       [`LightApp`]       | `[mirai:app:$content]`                           |
 | [`SimpleServiceMessage`] | `[mirai:service:$serviceId,$content]`            |
+|         [`Dice`]         | `[mirai:dice:$value]`                            |
 
 ### 由 mirai 码字符串取得 `MessageChain` 实例
 
diff --git a/mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt b/mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt
index 67c15a509..feed5c709 100644
--- a/mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt
+++ b/mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt
@@ -141,6 +141,8 @@ private val builtInSerializersModule by lazy {
             subclass(FlashImage::class, FlashImage.serializer())
 
             subclass(MusicShare::class, MusicShare.serializer())
+
+            subclass(Dice::class, Dice.serializer())
         }
 
 
diff --git a/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt b/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt
index 6d87eb554..39f226255 100644
--- a/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt
+++ b/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt
@@ -124,7 +124,10 @@ private object MiraiCodeParsers : Map<String, MiraiCodeParser> by mapOf(
     },
     "app" to MiraiCodeParser(Regex("""(.*)""")) { (content) ->
         LightApp(content.decodeMiraiCode())
-    }
+    },
+    "dice" to MiraiCodeParser(Regex("""([1-6])""")) { (value) ->
+        Dice(value.toInt())
+    },
 )
 
 private class MiraiCodeParser(
diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
new file mode 100644
index 000000000..8265fa2eb
--- /dev/null
+++ b/mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019-2021 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
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+@file:JvmMultifileClass
+@file:JvmName("MessageUtils")
+
+package net.mamoe.mirai.message.data
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.mamoe.mirai.message.code.CodableMessage
+import net.mamoe.mirai.utils.MiraiExperimentalApi
+import net.mamoe.mirai.utils.safeCast
+import org.jetbrains.annotations.Range
+import kotlin.random.Random
+import kotlin.random.nextInt
+
+/**
+ * 骰子.
+ *
+ * @since 2.5
+ */
+@Serializable
+@SerialName(Dice.SERIAL_NAME)
+public data class Dice(
+    /**
+     * 骰子的点数. 范围为 1..6
+     */
+    public val value: @Range(from = 1, to = 6) Int
+) : MarketFace, CodableMessage {
+    init {
+        require(value in 1..6) { "Dice.value must be in 1 and 6 inclusive." }
+    }
+
+    @MiraiExperimentalApi
+    override val name: String
+        get() = "[骰子:$value]"
+
+    @MiraiExperimentalApi
+    override val id: Int
+        get() = 11464
+
+    @MiraiExperimentalApi
+    override fun appendMiraiCodeTo(builder: StringBuilder) {
+        builder.append("[mirai:dice:").append(value).append(']')
+    }
+
+    override fun toString(): String = "[mirai:dice:$value]"
+
+    public companion object Key :
+        AbstractPolymorphicMessageKey<MarketFace, Dice>(MarketFace, { it.safeCast() }) {
+        public const val SERIAL_NAME: String = "Dice"
+
+        /**
+         * 创建随机点数的 [骰子][Dice]
+         */
+        @JvmStatic
+        public fun random(): Dice = Dice(Random.nextInt(1..6))
+    }
+}
diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt
index 4a4151743..74d59ee34 100644
--- a/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt
+++ b/mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt
@@ -15,7 +15,9 @@ import net.mamoe.mirai.utils.safeCast
 /**
  * 商城表情
  *
- * 目前不支持直接发送,可保存接收到的来自官方客户端的商城表情然后转发.
+ * 除 [Dice] 可以发送外, 目前不支持直接发送,可保存接收到的来自官方客户端的商城表情然后转发.
+ *
+ * @see Dice
  */
 public interface MarketFace : HummerMessage {
     /**
diff --git a/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt b/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt
index 4a332ce9d..6b0dcc4b3 100644
--- a/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt
+++ b/mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt
@@ -43,5 +43,8 @@ class TestMiraiCode {
             +SimpleServiceMessage(1, "[HiHi!!!\\]")
             +PlainText(" XE")
         }, "[mirai:service:1,\\[HiHi!!!\\\\\\]] XE".deserializeMiraiCode())
+        assertEquals(buildMessageChain {
+            +Dice(1)
+        }, "[mirai:dice:1]".deserializeMiraiCode())
     }
 }
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
index 9fb5a732f..bd682a32f 100644
--- a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
@@ -240,7 +240,9 @@ internal abstract class SendMessageHandler<C : Contact> {
  * - ... any others for future
  */
 internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessages(message: Message): MessageChain {
-    return message.takeSingleContent<ForwardMessage>()?.let { forward ->
+    suspend fun processForwardMessage(
+        forward: ForwardMessage
+    ): ForwardMessageInternal {
         if (!(message is MessageChain && message.contains(IgnoreLengthCheck))) {
             check(forward.nodeList.size <= 200) {
                 throw MessageTooLargeException(
@@ -256,12 +258,20 @@ internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessage
             message = forward.nodeList,
             isLong = false,
         )
-        RichMessage.forwardMessage(
+        return RichMessage.forwardMessage(
             resId = resId,
             timeSeconds = currentTimeSeconds(),
             forwardMessage = forward,
         )
-    }?.toMessageChain() ?: message.toMessageChain()
+    }
+
+    fun processDice(dice: Dice): MarketFaceImpl {
+        return MarketFaceImpl(dice.toJceStruct())
+    }
+
+    return message.takeSingleContent<ForwardMessage>()?.let { processForwardMessage(it) }?.toMessageChain()
+        ?: message.takeSingleContent<Dice>()?.let { processDice(it) }?.toMessageChain()
+        ?: message.toMessageChain()
 }
 
 /**
diff --git a/mirai-core/src/commonMain/kotlin/message/MarketFaceImpl.kt b/mirai-core/src/commonMain/kotlin/message/MarketFaceImpl.kt
new file mode 100644
index 000000000..0f08e39e4
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/message/MarketFaceImpl.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019-2021 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.internal.message
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import net.mamoe.mirai.contact.Contact
+import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
+import net.mamoe.mirai.message.data.Dice
+import net.mamoe.mirai.message.data.MarketFace
+import net.mamoe.mirai.message.data.Message
+import net.mamoe.mirai.message.data.MessageChain
+
+@SerialName(MarketFace.SERIAL_NAME)
+@Serializable
+internal data class MarketFaceImpl internal constructor(
+    internal val delegate: ImMsgBody.MarketFace,
+) : MarketFace {
+
+    override val name: String get() = delegate.faceName.decodeToString()
+
+    @Transient
+    override val id: Int = delegate.tabId
+
+    override fun toString() = "[mirai:marketface:$id,$name]"
+}
+
+/**
+ * For refinement
+ */
+internal class MarketFaceInternal(
+    @JvmField private val delegate: ImMsgBody.MarketFace,
+) : MarketFace, RefinableMessage {
+    override val name: String get() = delegate.faceName.decodeToString()
+    override val id: Int get() = delegate.tabId
+
+    override suspend fun refine(contact: Contact, context: MessageChain): Message {
+        delegate.toDiceOrNull()?.let { return it } // TODO: 2021/2/12 add dice origin, maybe rename RichMessageOrigin
+        return MarketFaceImpl(delegate)
+    }
+
+    override fun toString(): String = "[mirai:marketface:$id,$name]"
+}
+
+// From https://github.com/mamoe/mirai/issues/1012
+internal fun Dice.toJceStruct(): ImMsgBody.MarketFace {
+    return ImMsgBody.MarketFace(
+        faceName = byteArrayOf(91, -23, -86, -80, -27, -83, -112, 93),
+        itemType = 6,
+        faceInfo = 1,
+        faceId = byteArrayOf(
+            72, 35, -45, -83, -79, 93,
+            -16, -128, 20, -50, 93, 103,
+            -106, -73, 110, -31
+        ),
+        tabId = 11464,
+        subType = 3,
+        key = byteArrayOf(52, 48, 57, 101, 50, 97, 54, 57, 98, 49, 54, 57, 49, 56, 102, 57),
+        mediaType = 0,
+        imageWidth = 200,
+        imageHeight = 200,
+        mobileParam = byteArrayOf(
+            114, 115, 99, 84, 121, 112, 101,
+            63, 49, 59, 118, 97, 108, 117,
+            101, 61,
+            (47 + value).toByte()
+        ),
+        pbReserve = byteArrayOf(
+            10, 6, 8, -56, 1, 16, -56, 1, 64,
+            1, 88, 0, 98, 9, 35, 48, 48, 48,
+            48, 48, 48, 48, 48, 106, 9, 35,
+            48, 48, 48, 48, 48, 48, 48, 48
+        )
+    )
+}
+
+private fun ImMsgBody.MarketFace.toDiceOrNull(): Dice? {
+    if (this.tabId != 11464) return null
+    val value = mobileParam.lastOrNull()?.toInt()?.and(0xff)?.minus(47) ?: 0
+    if (value in 1..6) {
+        return Dice(value)
+    }
+    return null
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt
index 2a22cb2a2..5b60f2157 100644
--- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt
@@ -140,7 +140,7 @@ private object ReceiveMessageTransformer {
             element.customFace != null -> decodeCustomFace(element.customFace, builder)
             element.face != null -> builder.add(Face(element.face.index))
             element.text != null -> decodeText(element.text, builder)
-            element.marketFace != null -> builder.add(MarketFaceImpl(element.marketFace))
+            element.marketFace != null -> builder.add(MarketFaceInternal(element.marketFace))
             element.lightApp != null -> decodeLightApp(element.lightApp, builder)
             element.customElem != null -> decodeCustomElem(element.customElem, builder)
             element.commonElem != null -> decodeCommonElem(element.commonElem, builder)
diff --git a/mirai-core/src/commonMain/kotlin/message/faceImpl.kt b/mirai-core/src/commonMain/kotlin/message/faceImpl.kt
index 50386fdf3..c29bf5507 100644
--- a/mirai-core/src/commonMain/kotlin/message/faceImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/message/faceImpl.kt
@@ -10,14 +10,10 @@
 package net.mamoe.mirai.internal.message
 
 import kotlinx.io.core.toByteArray
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.Transient
 import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
 import net.mamoe.mirai.message.data.Face
-import net.mamoe.mirai.message.data.MarketFace
 import net.mamoe.mirai.utils.hexToBytes
 import net.mamoe.mirai.utils.toByteArray
 
@@ -42,18 +38,4 @@ internal fun Face.toCommData(): ImMsgBody.CommonElem {
         businessType = 1
     )
 
-}
-
-@SerialName(MarketFace.SERIAL_NAME)
-@Serializable
-internal data class MarketFaceImpl internal constructor(
-    internal val delegate: ImMsgBody.MarketFace,
-) : MarketFace {
-
-    override val name: String get() = delegate.faceName.decodeToString()
-
-    @Transient
-    override val id: Int = delegate.tabId
-
-    override fun toString() = "[mirai:marketface:$id,$name]"
 }
\ No newline at end of file
diff --git a/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt
index 7e36e82c0..a044de77b 100644
--- a/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt
+++ b/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt
@@ -109,7 +109,8 @@ internal class MessageSerializationTest {
         image.toForwardMessage(1L, "test"),
         MusicShare(MusicKind.NeteaseCloudMusic, "123", "123", "123", "123", "123", "123"),
         RichMessageOrigin(SimpleServiceMessage(1, "content"), "resource id", RichMessageKind.LONG),
-        ShowImageFlag
+        ShowImageFlag,
+        Dice(1),
     )
 
     companion object {

From 969921860163800d083201e7d8c11eb65b0dff94 Mon Sep 17 00:00:00 2001
From: sandtechnology <20417547+sandtechnology@users.noreply.github.com>
Date: Sat, 13 Feb 2021 11:34:48 +0800
Subject: [PATCH 2/2] Fix wrong decoding on ConfigPushSvc and update default
 server list (#1015)

* Fix wrong decoding on ConfigPushSvc and update default server list

* Fix a bug which won't update server list

* Improve wording

* Fix an encoding error

* Fix wording

* Naming consistently

* Improve ServerListPush message

Co-authored-by: Him188 <Him188@mamoe.net>

Co-authored-by: Him188 <Him188@mamoe.net>
---
 .../kotlin/network/QQAndroidClient.kt         |   3 +-
 .../handler/QQAndroidBotNetworkHandler.kt     |  10 +-
 .../network/protocol/data/jce/ConfigPush.kt   |  49 +++++
 .../protocol/packet/login/ConfigPushSvc.kt    | 178 +++++++++---------
 4 files changed, 141 insertions(+), 99 deletions(-)

diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt
index b7473f040..ac5db3141 100644
--- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt
+++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt
@@ -14,7 +14,6 @@ package net.mamoe.mirai.internal.network
 import kotlinx.atomicfu.AtomicBoolean
 import kotlinx.atomicfu.AtomicInt
 import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.CompletableDeferred
 import kotlinx.io.core.BytePacketBuilder
 import kotlinx.io.core.String
 import kotlinx.io.core.toByteArray
@@ -46,7 +45,7 @@ internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Ra
 // [114.221.148.179:14000, 113.96.13.125:8080, 14.22.3.51:8080, 42.81.172.207:443, 114.221.144.89:80, 125.94.60.148:14000, 42.81.192.226:443, 114.221.148.233:8080, msfwifi.3g.qq.com:8080, 42.81.172.22:80]
 
 internal val DefaultServerList: MutableSet<Pair<String, Int>> =
-    "114.221.148.179:14000, 113.96.13.125:8080, 14.22.3.51:8080, 42.81.172.207:443, 114.221.144.89:80, 125.94.60.148:14000, 42.81.192.226:443, 114.221.148.233:8080, msfwifi.3g.qq.com:8080, 42.81.172.22:80"
+    "msfwifi.3g.qq.com:8080, 14.215.138.110:8080, 113.96.12.224:8080, 157.255.13.77:14000, 120.232.18.27:443, 183.3.235.162:14000, 163.177.89.195:443, 183.232.94.44:80, 203.205.255.224:8080, 203.205.255.221:8080"
         .split(", ")
         .map {
             val host = it.substringBefore(':')
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt
index f3ed11327..bc4fb5a2c 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt
@@ -368,14 +368,14 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
                     logger.warning { "Missing ConfigPushSvc.PushReq. Switching server..." }
                     bot.launch { BotOfflineEvent.RequireReconnect(bot).broadcast() }
                 } else {
-                    logger.warning { "Missing ConfigPushSvc.PushReq. Using latest response. File uploading may be affected." }
+                    logger.warning { "Missing ConfigPushSvc.PushReq. Using the latest response. File uploading may be affected." }
                 }
             }
-            is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
-                logger.info { "ConfigPushSvc.PushReq: Success." }
+            is ConfigPushSvc.PushReq.PushReqResponse.ConfigPush -> {
+                logger.info { "ConfigPushSvc.PushReq: Config updated." }
             }
-            is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
-                logger.info { "ConfigPushSvc.PushReq: Require reconnect" }
+            is ConfigPushSvc.PushReq.PushReqResponse.ServerListPush -> {
+                logger.info { "ConfigPushSvc.PushReq: Server updated." }
                 // handled in ConfigPushSvc
                 return@launch
             }
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ConfigPush.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ConfigPush.kt
index 3eddddcf2..45e7d4293 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ConfigPush.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ConfigPush.kt
@@ -147,6 +147,55 @@ internal class PushReq(
     @TarsId(3) @JvmField val seq: Long
 ) : JceStruct, Packet
 
+@Serializable
+internal data class ServerListPush(
+    @TarsId(1) val mobileSSOServerList: List<ServerInfo>,
+    @TarsId(3) val wifiSSOServerList: List<ServerInfo>,
+    @TarsId(4) val reconnectNeeded: Int = 0,
+    //@JvmField @TarsId(5)  val skipped:Byte? = 0,
+    //@JvmField @TarsId(6)  val skipped:Byte? = 0,
+    //@JvmField @TarsId(7)  val skipped:Int? = 1,
+    @TarsId(8) val mobileHttpServerList: List<ServerInfo>,
+    @TarsId(9) val wifiHttpServerList: List<ServerInfo>,
+    @TarsId(10) val quicServerList: List<ServerInfo>,
+    @TarsId(11) val ssoServerListIpv6: List<ServerInfo>,
+    @TarsId(12) val httpServerListIpv6: List<ServerInfo>,
+    @TarsId(13) val quicServerListIpv6: List<ServerInfo>,
+    /**
+     * wifi下&1==1则启用
+     * 移动数据(mobile)下&2==2则启用
+     */
+    @TarsId(14) val ipv6ConfigVal: Byte? = 0,
+    //@JvmField @TarsId(15) val netTestDelay:Int? = 0,
+    @TarsId(16) val configDesc: String? = ""
+) : JceStruct {
+
+    @Serializable
+    data class ServerInfo(
+        @TarsId(1) val host: String,
+        @TarsId(2) val port: Int,
+        //@JvmField @TarsId(3) val skipped: Byte = 0,
+        //@JvmField @TarsId(4) val skipped: Byte = 0,
+        /**
+         * 2,3->http
+         * 0,1->socket
+         */
+        //@JvmField @TarsId(5) val protocolType: Byte? = 0,
+        //@JvmField @TarsId(6) val skipped: Int? = 8,
+        //@JvmField @TarsId(7) val skipped: Byte? = 0,
+        @TarsId(8) val location: String = "",
+        /**
+         * cm->China mobile 中国移动
+         * uni->China unicom 中国联通
+         * others->其他
+         */
+        @TarsId(9) val ispName: String = ""
+    ) : JceStruct {
+        override fun toString(): String {
+            return "$host:$port"
+        }
+    }
+}
 @Serializable
 internal class PushResp(
     @TarsId(1) @JvmField val type: Int,
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt
index 070803cb9..52cdd4c80 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt
@@ -12,7 +12,6 @@ package net.mamoe.mirai.internal.network.protocol.packet.login
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.io.core.ByteReadPacket
-import kotlinx.serialization.Serializable
 import net.mamoe.mirai.event.AbstractEvent
 import net.mamoe.mirai.event.Event
 import net.mamoe.mirai.event.broadcast
@@ -24,15 +23,15 @@ import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList
 import net.mamoe.mirai.internal.network.protocol.data.jce.PushResp
 import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket
+import net.mamoe.mirai.internal.network.protocol.data.jce.ServerListPush
 import net.mamoe.mirai.internal.network.protocol.data.proto.Subcmd0x501
 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket
-import net.mamoe.mirai.internal.utils.io.JceStruct
+import net.mamoe.mirai.internal.utils.NetworkType
 import net.mamoe.mirai.internal.utils.io.serialization.jceRequestSBuffer
 import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket
-import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId
 import net.mamoe.mirai.internal.utils.io.serialization.writeJceStruct
 import net.mamoe.mirai.utils.info
 import net.mamoe.mirai.utils.toUHexString
@@ -46,66 +45,46 @@ internal class ConfigPushSvc {
     ) {
         override val canBeCached: Boolean get() = false
 
-        sealed class PushReqResponse : Packet, Event, AbstractEvent(), Packet.NoEventLog {
-            class Success(
-                val struct: PushReqJceStruct
-            ) : PushReqResponse() {
+        sealed class PushReqResponse(val struct: PushReqJceStruct) : Packet, Event, AbstractEvent(), Packet.NoEventLog {
+            class Unknown(struct: PushReqJceStruct) : PushReqResponse(struct) {
                 override fun toString(): String {
-                    return "ConfigPushSvc.PushReq.PushReqResponse.Success"
+                    return "ConfigPushSvc.PushReq.PushReqResponse.Unknown"
                 }
             }
 
-            @Serializable
-            data class ChangeServer(
-                @TarsId(1) val serverList: List<ServerInfo>,
-                // @TarsId(3) val serverList2: List<ServerInfo>,
-                // @TarsId(8) val serverList3: List<ServerInfo>,
-            ) : JceStruct, PushReqResponse() {
+            class LogAction(struct: PushReqJceStruct) : PushReqResponse(struct) {
                 override fun toString(): String {
-                    return "ConfigPushSvc.PushReq.PushReqResponse.ChangeServer"
-                }
-
-                @Serializable
-                data class ServerInfo(
-                    /*
-                    skipping String1
-                    skipping Short
-                    skipping Byte
-                    skipping Zero
-                    skipping Zero
-                    skipping Byte
-                    skipping Byte
-                    skipping String1
-                    skipping String1
-                     */
-                    @TarsId(1) val host: String,
-                    @TarsId(2) val port: Int,
-                    @TarsId(8) val location: String
-                ) : JceStruct {
-                    override fun toString(): String {
-                        return "$host:$port"
-                    }
+                    return "ConfigPushSvc.PushReq.PushReqResponse.LogAction"
                 }
             }
+
+            class ServerListPush(struct: PushReqJceStruct) : PushReqResponse(struct) {
+                override fun toString(): String {
+                    return "ConfigPushSvc.PushReq.PushReqResponse.ServerListPush"
+                }
+            }
+
+            class ConfigPush(struct: PushReqJceStruct) : PushReqResponse(struct) {
+                override fun toString(): String {
+                    return "ConfigPushSvc.PushReq.PushReqResponse.ConfigPush"
+                }
+            }
+
+
         }
 
         override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse {
             val pushReq = readUniPacket(PushReqJceStruct.serializer(), "PushReq")
             return when (pushReq.type) {
-                1 -> kotlin.runCatching {
-                    pushReq.jcebuf.loadAs(PushReqResponse.ChangeServer.serializer())
-                }.getOrElse {
-                    throw contextualBugReportException(
-                        "ConfigPush.ReqPush type=1",
-                        forDebug = pushReq.jcebuf.toUHexString(),
-                    )
-                }
-                else -> PushReqResponse.Success(pushReq)
+                1 -> PushReqResponse.ServerListPush(pushReq)
+                2 -> PushReqResponse.ConfigPush(pushReq)
+                3 -> PushReqResponse.LogAction(pushReq)
+                else -> PushReqResponse.Unknown(pushReq)
             }
         }
 
         override suspend fun QQAndroidBot.handle(packet: PushReqResponse, sequenceId: Int): OutgoingPacket? {
-            fun handleSuccess(packet: PushReqResponse.Success) {
+            fun handleConfigPush(packet: PushReqResponse.ConfigPush) {
                 val pushReq = packet.struct
 
                 // FS server
@@ -148,64 +127,79 @@ internal class ConfigPushSvc {
                 )
             }
 
-            fun handleRequireReconnect(resp: PushReqResponse.ChangeServer) {
-                bot.logger.info { "Server requires reconnect." }
-                bot.logger.info { "Server list: ${resp.serverList.joinToString()}." }
+            fun handleServerListPush(resp: PushReqResponse.ServerListPush) {
+                bot.network.logger.info { "Server list updated." }
+                val serverListPush = kotlin.runCatching {
+                    resp.struct.jcebuf.loadAs(ServerListPush.serializer())
+                }.getOrElse {
+                    throw contextualBugReportException(
+                        "ConfigPush.ReqPush type=1",
+                        forDebug = resp.struct.jcebuf.toUHexString(),
+                    )
+                }
+                val pushServerList = if (client.networkType == NetworkType.WIFI) {
+                    serverListPush.wifiSSOServerList
+                } else {
+                    serverListPush.mobileSSOServerList
+                }
 
-                if (resp.serverList.isNotEmpty()) {
+                bot.logger.info { "Server list: ${pushServerList.joinToString()}." }
+
+                if (pushServerList.isNotEmpty()) {
                     bot.serverList.clear()
-                    resp.serverList.shuffled().forEach {
+                    pushServerList.shuffled().forEach {
                         bot.serverList.add(it.host to it.port)
                     }
                 }
                 bot.bdhSyncer.saveToCache()
                 bot.bdhSyncer.saveServerListToCache()
-
-                bot.launch {
-                    delay(1000)
-                    BotOfflineEvent.RequireReconnect(bot).broadcast()
+                if (serverListPush.reconnectNeeded == 1) {
+                    bot.logger.info { "Server request to change server." }
+                    bot.launch {
+                        delay(1000)
+                        BotOfflineEvent.RequireReconnect(bot).broadcast()
+                    }
                 }
             }
 
             when (packet) {
-                is PushReqResponse.Success -> {
-                    handleSuccess(packet)
-                    if (!client.wLoginSigInfoInitialized) return null // concurrently doing reconnection
-                    return buildResponseUniPacket(
-                        client,
-                        sequenceId = sequenceId,
-                        key = client.wLoginSigInfo.d2Key
-                    ) {
-                        writeJceStruct(
-                            RequestPacket.serializer(),
-                            RequestPacket(
-                                requestId = 0,
-                                version = 3,
-                                servantName = "QQService.ConfigPushSvc.MainServant",
-                                funcName = "PushResp",
-                                sBuffer = jceRequestSBuffer(
-                                    "PushResp",
-                                    PushResp.serializer(),
-                                    PushResp(
-                                        type = packet.struct.type,
-                                        seq = packet.struct.seq,
-                                        jcebuf = if (packet.struct.type == 3) packet.struct.jcebuf else null
-                                    )
-                                )
+                is PushReqResponse.ConfigPush -> {
+                    handleConfigPush(packet)
+                }
+                is PushReqResponse.ServerListPush -> {
+                    handleServerListPush(packet)
+                }
+                is PushReqResponse.LogAction, is PushReqResponse.Unknown -> {
+                    //ignore
+                }
+            }
+            //Always send resp
+            if (!client.wLoginSigInfoInitialized) return null // concurrently doing reconnection
+            return buildResponseUniPacket(
+                client,
+                sequenceId = sequenceId,
+                key = client.wLoginSigInfo.d2Key
+            ) {
+                writeJceStruct(
+                    RequestPacket.serializer(),
+                    RequestPacket(
+                        requestId = client.nextRequestPacketRequestId(),
+                        version = 3,
+                        servantName = "QQService.ConfigPushSvc.MainServant",
+                        funcName = "PushResp",
+                        sBuffer = jceRequestSBuffer(
+                            "PushResp",
+                            PushResp.serializer(),
+                            PushResp(
+                                type = packet.struct.type,
+                                seq = packet.struct.seq,
+                                jcebuf = if (packet.struct.type == 3) packet.struct.jcebuf else null
                             )
                         )
-                        // writePacket(this.build().debugPrintThis())
-                    }
-                }
-                is PushReqResponse.ChangeServer -> {
-                    handleRequireReconnect(packet)
-                    return null
-                }
-                else -> {
-                    // handled in QQABot
-                    return null
-                }
+                    )
+                )
+                // writePacket(this.build().debugPrintThis())
             }
         }
     }
-}
\ No newline at end of file
+}