Browse Source

Support server changing, close #52

Him188 5 years ago
parent
commit
da801d7b6d

+ 6 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt

@@ -46,7 +46,6 @@ import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
-import net.mamoe.mirai.qqandroid.utils.*
 import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
 import net.mamoe.mirai.qqandroid.utils.encodeToString
 import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
@@ -204,6 +203,12 @@ internal abstract class QQAndroidBotBase constructor(
         })
     }
 
+    override suspend fun relogin(cause: Throwable?) {
+        client.useNextServers { host, port ->
+            network.relogin(host, port, cause)
+        }
+    }
+
     @LowLevelAPI
     override fun _lowLevelNewQQ(friendInfo: FriendInfo): QQ {
         return QQImpl(

+ 29 - 3
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt

@@ -37,6 +37,7 @@ 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.chat.receive.MessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
+import net.mamoe.mirai.qqandroid.network.protocol.packet.login.ConfigPushSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
@@ -107,7 +108,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     }
 
     @OptIn(MiraiExperimentalAPI::class)
-    override suspend fun relogin(cause: Throwable?) {
+    override suspend fun relogin(host: String, port: Int, cause: Throwable?) {
         heartbeatJob?.cancel(CancellationException("relogin", cause))
         heartbeatJob?.join()
         if (::channel.isInitialized) {
@@ -121,10 +122,11 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
         }
         channel = PlatformSocket()
         // TODO: 2020/2/14 连接多个服务器, #52
+
         withTimeoutOrNull(3000) {
-            channel.connect("114.221.144.22", 8080)
+            channel.connect(host, port)
         } ?: error("timeout connecting server")
-        logger.info("Connected to server 114.221.144.22:8080")
+        logger.info("Connected to server $host:$port")
         startPacketReceiverJobOrKill(CancellationException("relogin", cause))
 
         var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
@@ -301,6 +303,30 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
             launch { reloadGroupList() }
         }
 
+        [email protected] {
+            logger.info { "Awaiting ConfigPushSvc.PushReq" }
+            val resp =
+                subscribingGetOrNull<ConfigPushSvc.PushReq.PushReqResponse, ConfigPushSvc.PushReq.PushReqResponse>(
+                    10_000) { it }
+
+            when (resp) {
+                null -> logger.info { "Missing ConfigPushSvc.PushReq." }
+                is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
+                    logger.info { "ConfigPushSvc.PushReq: success" }
+                }
+                is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
+                    logger.info { "Server requires reconnect." }
+                    logger.info { "ChangeServer.unknown = ${resp.unknown}" }
+                    logger.info { "Server list: ${resp.serverList.joinToString()}" }
+
+                    resp.serverList.forEach {
+                        bot.client.serverList.add(it.host to it.port)
+                    }
+                    BotOfflineEvent.RequireReconnect(bot).broadcast()
+                }
+            }
+        }
+
         withTimeoutOrNull(30000) {
             launch { subscribingGet<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
             MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()

+ 35 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt

@@ -15,8 +15,10 @@ import kotlinx.atomicfu.AtomicInt
 import kotlinx.atomicfu.atomic
 import kotlinx.io.core.*
 import net.mamoe.mirai.data.OnlineStatus
+import net.mamoe.mirai.network.NoServerAvailableException
 import net.mamoe.mirai.qqandroid.BotAccount
 import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FileStoragePushFSSvcList
 import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
 import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
 import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
@@ -42,6 +44,17 @@ private fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray
  */
 internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
 
+internal object DefaultServerList : Set<Pair<String, Int>> by setOf(
+    "42.81.169.46" to 8080,
+    "42.81.172.81" to 80,
+    "114.221.148.59" to 14000,
+    "42.81.172.147" to 443,
+    "125.94.60.146" to 80,
+    "114.221.144.215" to 80,
+    "msfwifi.3g.qq.com" to 8080,
+    "42.81.172.22" to 80
+)
+
 /*
  APP ID:
  GetStViaSMSVerifyLogin = 16
@@ -64,6 +77,8 @@ internal open class QQAndroidClient(
     val device: DeviceInfo = SystemDeviceInfo(context),
     bot: QQAndroidBot
 ) {
+    internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
+
     val keys: Map<String, ByteArray> by lazy {
         mapOf(
             "16 zero" to ByteArray(16),
@@ -102,11 +117,30 @@ internal open class QQAndroidClient(
     val randomKey: ByteArray = getRandomByteArray(16)
 
     var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
-    var mainSigMap: Int = 16724722
+    private var mainSigMap: Int = 16724722
     var subSigMap: Int = 0x10400 //=66,560
 
     private val _ssoSequenceId: AtomicInt = atomic(85600)
 
+    lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcList
+    internal suspend inline fun useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
+        @Suppress("UNREACHABLE_CODE", "ThrowableNotThrown") // false positive
+        retryCatching(bot.client.serverList.size) {
+            val pair = bot.client.serverList.random()
+            kotlin.runCatching {
+                block(pair.first, pair.second)
+                return
+            }.getOrElse {
+                bot.client.serverList.remove(pair)
+                bot.logger.warning(it)
+                throw it
+            }
+        }.getOrElse {
+            bot.client.serverList.addAll(DefaultServerList)
+            throw NoServerAvailableException(it)
+        }
+    }
+
     @MiraiInternalAPI("Do not use directly. Get from the lambda param of buildSsoPacket")
     internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
 

+ 3 - 4
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt

@@ -12,7 +12,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.qqandroid.network.Packet
 import net.mamoe.mirai.qqandroid.utils.io.JceStruct
-import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
 import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
 
 @Serializable
@@ -85,7 +84,7 @@ internal class _340(
     @JceId(14) val netType: Byte? = 0,
     @JceId(15) val heThreshold: Int? = 0,
     @JceId(16) val policyId: String? = ""
-) : ProtoBuf
+) : JceStruct
 
 @Serializable
 internal class _339(
@@ -102,8 +101,8 @@ internal class _339(
 
 @Serializable
 internal class FileStoragePushFSSvcList(
-    @JceId(0) val vUpLoadList: List<FileStorageServerListInfo> = listOf(),
-    @JceId(1) val vPicDownLoadList: List<FileStorageServerListInfo> = listOf(),
+    @JceId(0) val vUpLoadList: List<FileStorageServerListInfo>? = listOf(),
+    @JceId(1) val vPicDownLoadList: List<FileStorageServerListInfo>? = listOf(),
     @JceId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
     @JceId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
     @JceId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,

+ 3 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt

@@ -43,6 +43,8 @@ internal sealed class PacketFactory<TPacket : Packet?> {
      * 筛选从服务器接收到的包时的 commandName
      */
     abstract val receivingCommandName: String
+
+    open val canBeCached: Boolean get() = true
 }
 
 /**
@@ -216,7 +218,7 @@ internal object KnownPacketFactories {
                 }?.let {
                     it as IncomingPacket<T>
 
-                    if (it.packetFactory is IncomingPacketFactory<T> && bot.network.pendingEnabled) {
+                    if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) {
                         bot.network.pendingIncomingPackets?.addLast(it.also {
                             it.consumer = consumer
                             it.flag2 = flag2

File diff suppressed because it is too large
+ 10 - 15
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt


+ 8 - 4
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt

@@ -83,10 +83,14 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
     @Suppress("PropertyName")
     internal lateinit var _network: N
 
+    protected abstract suspend fun relogin(cause: Throwable?)
+
     @Suppress("unused")
     private val offlineListener: Listener<BotOfflineEvent> = this.subscribeAlways { event ->
         when (event) {
-            is BotOfflineEvent.Dropped -> {
+            is BotOfflineEvent.Dropped,
+            is BotOfflineEvent.RequireReconnect
+            -> {
                 if (!_network.isActive) {
                     return@subscribeAlways
                 }
@@ -96,9 +100,9 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
                     if (tryCount != 0) {
                         delay(configuration.reconnectPeriodMillis)
                     }
-                    network.relogin(event.cause)
+                    relogin((event as? BotOfflineEvent.Dropped)?.cause)
                     logger.info("Reconnected successfully")
-                    BotReloginEvent(bot, event.cause).broadcast()
+                    BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
                     return@subscribeAlways
                 }?.let {
                     logger.info("Cannot reconnect")
@@ -134,7 +138,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
             while (true) {
                 _network = createNetworkHandler(this.coroutineContext)
                 try {
-                    _network.relogin()
+                    relogin(null)
                     return
                 } catch (e: LoginFailedException) {
                     throw e

+ 6 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt

@@ -71,6 +71,12 @@ sealed class BotOfflineEvent : BotEvent {
      * 被服务器断开或因网络问题而掉线
      */
     data class Dropped(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet, BotPassiveEvent
+
+    /**
+     * 服务器主动要求更换另一个服务器
+     */
+    @SinceMirai("0.38.0")
+    data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
 }
 
 /**

+ 2 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt

@@ -93,7 +93,7 @@ inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
 internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
     coroutineScope: CoroutineScope,
     noinline mapper: suspend E.(E) -> R?
-): R {
+): R? {
     var result: Result<R?> = Result.success(null) // stub
     var listener: Listener<E>? = null
     @Suppress("DuplicatedCode") // for better performance
@@ -114,5 +114,5 @@ internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
     }
     listener.join()
 
-    return result.getOrThrow()!!
+    return result.getOrThrow()
 }

+ 1 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt

@@ -67,7 +67,7 @@ abstract class BotNetworkHandler : CoroutineScope {
      */
     @Suppress("SpellCheckingInspection")
     @MiraiInternalAPI
-    abstract suspend fun relogin(cause: Throwable? = null)
+    abstract suspend fun relogin(host: String, port: Int, cause: Throwable? = null)
 
     /**
      * 初始化获取好友列表等值.

+ 5 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt

@@ -29,6 +29,11 @@ sealed class LoginFailedException : RuntimeException {
  */
 class WrongPasswordException(message: String?) : LoginFailedException(message)
 
+/**
+ * 无可用服务器
+ */
+class NoServerAvailableException(override val cause: Throwable?) : LoginFailedException("no server available")
+
 /**
  * 需要短信验证时抛出. mirai 目前还不支持短信验证.
  */

Some files were not shown because too many files changed in this diff