mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-15 04:50:11 +08:00
Support server changing, close #52
This commit is contained in:
parent
e6ef03f60d
commit
da801d7b6d
@ -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.data.proto.LongMsg
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
|
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.MiraiPlatformUtils
|
||||||
import net.mamoe.mirai.qqandroid.utils.encodeToString
|
import net.mamoe.mirai.qqandroid.utils.encodeToString
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
|
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
|
@LowLevelAPI
|
||||||
override fun _lowLevelNewQQ(friendInfo: FriendInfo): QQ {
|
override fun _lowLevelNewQQ(friendInfo: FriendInfo): QQ {
|
||||||
return QQImpl(
|
return QQImpl(
|
||||||
|
@ -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.GroupInfoImpl
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
|
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.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.Heartbeat
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
|
||||||
@ -107,7 +108,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(MiraiExperimentalAPI::class)
|
@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?.cancel(CancellationException("relogin", cause))
|
||||||
heartbeatJob?.join()
|
heartbeatJob?.join()
|
||||||
if (::channel.isInitialized) {
|
if (::channel.isInitialized) {
|
||||||
@ -121,10 +122,11 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
}
|
}
|
||||||
channel = PlatformSocket()
|
channel = PlatformSocket()
|
||||||
// TODO: 2020/2/14 连接多个服务器, #52
|
// TODO: 2020/2/14 连接多个服务器, #52
|
||||||
|
|
||||||
withTimeoutOrNull(3000) {
|
withTimeoutOrNull(3000) {
|
||||||
channel.connect("114.221.144.22", 8080)
|
channel.connect(host, port)
|
||||||
} ?: error("timeout connecting server")
|
} ?: 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))
|
startPacketReceiverJobOrKill(CancellationException("relogin", cause))
|
||||||
|
|
||||||
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
|
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
|
||||||
@ -301,6 +303,30 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
launch { reloadGroupList() }
|
launch { reloadGroupList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this@QQAndroidBotNetworkHandler.launch {
|
||||||
|
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) {
|
withTimeoutOrNull(30000) {
|
||||||
launch { subscribingGet<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
|
launch { subscribingGet<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
|
||||||
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
|
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
|
||||||
|
@ -15,8 +15,10 @@ import kotlinx.atomicfu.AtomicInt
|
|||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
import net.mamoe.mirai.data.OnlineStatus
|
import net.mamoe.mirai.data.OnlineStatus
|
||||||
|
import net.mamoe.mirai.network.NoServerAvailableException
|
||||||
import net.mamoe.mirai.qqandroid.BotAccount
|
import net.mamoe.mirai.qqandroid.BotAccount
|
||||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
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.EMPTY_BYTE_ARRAY
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
|
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 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:
|
APP ID:
|
||||||
GetStViaSMSVerifyLogin = 16
|
GetStViaSMSVerifyLogin = 16
|
||||||
@ -64,6 +77,8 @@ internal open class QQAndroidClient(
|
|||||||
val device: DeviceInfo = SystemDeviceInfo(context),
|
val device: DeviceInfo = SystemDeviceInfo(context),
|
||||||
bot: QQAndroidBot
|
bot: QQAndroidBot
|
||||||
) {
|
) {
|
||||||
|
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
|
||||||
|
|
||||||
val keys: Map<String, ByteArray> by lazy {
|
val keys: Map<String, ByteArray> by lazy {
|
||||||
mapOf(
|
mapOf(
|
||||||
"16 zero" to ByteArray(16),
|
"16 zero" to ByteArray(16),
|
||||||
@ -102,11 +117,30 @@ internal open class QQAndroidClient(
|
|||||||
val randomKey: ByteArray = getRandomByteArray(16)
|
val randomKey: ByteArray = getRandomByteArray(16)
|
||||||
|
|
||||||
var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
|
var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
|
||||||
var mainSigMap: Int = 16724722
|
private var mainSigMap: Int = 16724722
|
||||||
var subSigMap: Int = 0x10400 //=66,560
|
var subSigMap: Int = 0x10400 //=66,560
|
||||||
|
|
||||||
private val _ssoSequenceId: AtomicInt = atomic(85600)
|
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")
|
@MiraiInternalAPI("Do not use directly. Get from the lambda param of buildSsoPacket")
|
||||||
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
|
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.mamoe.mirai.qqandroid.network.Packet
|
import net.mamoe.mirai.qqandroid.network.Packet
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
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
|
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -85,7 +84,7 @@ internal class _340(
|
|||||||
@JceId(14) val netType: Byte? = 0,
|
@JceId(14) val netType: Byte? = 0,
|
||||||
@JceId(15) val heThreshold: Int? = 0,
|
@JceId(15) val heThreshold: Int? = 0,
|
||||||
@JceId(16) val policyId: String? = ""
|
@JceId(16) val policyId: String? = ""
|
||||||
) : ProtoBuf
|
) : JceStruct
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class _339(
|
internal class _339(
|
||||||
@ -102,8 +101,8 @@ internal class _339(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class FileStoragePushFSSvcList(
|
internal class FileStoragePushFSSvcList(
|
||||||
@JceId(0) val vUpLoadList: List<FileStorageServerListInfo> = listOf(),
|
@JceId(0) val vUpLoadList: List<FileStorageServerListInfo>? = listOf(),
|
||||||
@JceId(1) val vPicDownLoadList: List<FileStorageServerListInfo> = listOf(),
|
@JceId(1) val vPicDownLoadList: List<FileStorageServerListInfo>? = listOf(),
|
||||||
@JceId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
|
@JceId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
|
||||||
@JceId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
|
@JceId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
|
||||||
@JceId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
|
@JceId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
|
||||||
|
@ -43,6 +43,8 @@ internal sealed class PacketFactory<TPacket : Packet?> {
|
|||||||
* 筛选从服务器接收到的包时的 commandName
|
* 筛选从服务器接收到的包时的 commandName
|
||||||
*/
|
*/
|
||||||
abstract val receivingCommandName: String
|
abstract val receivingCommandName: String
|
||||||
|
|
||||||
|
open val canBeCached: Boolean get() = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,7 +218,7 @@ internal object KnownPacketFactories {
|
|||||||
}?.let {
|
}?.let {
|
||||||
it as IncomingPacket<T>
|
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 {
|
bot.network.pendingIncomingPackets?.addLast(it.also {
|
||||||
it.consumer = consumer
|
it.consumer = consumer
|
||||||
it.flag2 = flag2
|
it.flag2 = flag2
|
||||||
|
File diff suppressed because one or more lines are too long
@ -83,10 +83,14 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
|||||||
@Suppress("PropertyName")
|
@Suppress("PropertyName")
|
||||||
internal lateinit var _network: N
|
internal lateinit var _network: N
|
||||||
|
|
||||||
|
protected abstract suspend fun relogin(cause: Throwable?)
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private val offlineListener: Listener<BotOfflineEvent> = this.subscribeAlways { event ->
|
private val offlineListener: Listener<BotOfflineEvent> = this.subscribeAlways { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is BotOfflineEvent.Dropped -> {
|
is BotOfflineEvent.Dropped,
|
||||||
|
is BotOfflineEvent.RequireReconnect
|
||||||
|
-> {
|
||||||
if (!_network.isActive) {
|
if (!_network.isActive) {
|
||||||
return@subscribeAlways
|
return@subscribeAlways
|
||||||
}
|
}
|
||||||
@ -96,9 +100,9 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
|||||||
if (tryCount != 0) {
|
if (tryCount != 0) {
|
||||||
delay(configuration.reconnectPeriodMillis)
|
delay(configuration.reconnectPeriodMillis)
|
||||||
}
|
}
|
||||||
network.relogin(event.cause)
|
relogin((event as? BotOfflineEvent.Dropped)?.cause)
|
||||||
logger.info("Reconnected successfully")
|
logger.info("Reconnected successfully")
|
||||||
BotReloginEvent(bot, event.cause).broadcast()
|
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
|
||||||
return@subscribeAlways
|
return@subscribeAlways
|
||||||
}?.let {
|
}?.let {
|
||||||
logger.info("Cannot reconnect")
|
logger.info("Cannot reconnect")
|
||||||
@ -134,7 +138,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
|||||||
while (true) {
|
while (true) {
|
||||||
_network = createNetworkHandler(this.coroutineContext)
|
_network = createNetworkHandler(this.coroutineContext)
|
||||||
try {
|
try {
|
||||||
_network.relogin()
|
relogin(null)
|
||||||
return
|
return
|
||||||
} catch (e: LoginFailedException) {
|
} catch (e: LoginFailedException) {
|
||||||
throw e
|
throw e
|
||||||
|
@ -71,6 +71,12 @@ sealed class BotOfflineEvent : BotEvent {
|
|||||||
* 被服务器断开或因网络问题而掉线
|
* 被服务器断开或因网络问题而掉线
|
||||||
*/
|
*/
|
||||||
data class Dropped(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet, BotPassiveEvent
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,7 +93,7 @@ inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
|
|||||||
internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
|
internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
noinline mapper: suspend E.(E) -> R?
|
noinline mapper: suspend E.(E) -> R?
|
||||||
): R {
|
): R? {
|
||||||
var result: Result<R?> = Result.success(null) // stub
|
var result: Result<R?> = Result.success(null) // stub
|
||||||
var listener: Listener<E>? = null
|
var listener: Listener<E>? = null
|
||||||
@Suppress("DuplicatedCode") // for better performance
|
@Suppress("DuplicatedCode") // for better performance
|
||||||
@ -114,5 +114,5 @@ internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
|
|||||||
}
|
}
|
||||||
listener.join()
|
listener.join()
|
||||||
|
|
||||||
return result.getOrThrow()!!
|
return result.getOrThrow()
|
||||||
}
|
}
|
@ -67,7 +67,7 @@ abstract class BotNetworkHandler : CoroutineScope {
|
|||||||
*/
|
*/
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
abstract suspend fun relogin(cause: Throwable? = null)
|
abstract suspend fun relogin(host: String, port: Int, cause: Throwable? = null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化获取好友列表等值.
|
* 初始化获取好友列表等值.
|
||||||
|
@ -29,6 +29,11 @@ sealed class LoginFailedException : RuntimeException {
|
|||||||
*/
|
*/
|
||||||
class WrongPasswordException(message: String?) : LoginFailedException(message)
|
class WrongPasswordException(message: String?) : LoginFailedException(message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无可用服务器
|
||||||
|
*/
|
||||||
|
class NoServerAvailableException(override val cause: Throwable?) : LoginFailedException("no server available")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 需要短信验证时抛出. mirai 目前还不支持短信验证.
|
* 需要短信验证时抛出. mirai 目前还不支持短信验证.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user