diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api index 34fa7ef41..8d7117256 100644 --- a/binary-compatibility-validator/api/binary-compatibility-validator.api +++ b/binary-compatibility-validator/api/binary-compatibility-validator.api @@ -5329,6 +5329,7 @@ public class net/mamoe/mirai/utils/BotConfiguration { public final fun getFirstReconnectDelayMillis ()J public final fun getHeartbeatPeriodMillis ()J public final fun getHeartbeatTimeoutMillis ()J + public final fun getHighwayUploadCoroutineCount ()I public final fun getJson ()Lkotlinx/serialization/json/Json; public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver; public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1; @@ -5366,6 +5367,7 @@ public class net/mamoe/mirai/utils/BotConfiguration { public final fun setFirstReconnectDelayMillis (J)V public final fun setHeartbeatPeriodMillis (J)V public final fun setHeartbeatTimeoutMillis (J)V + public final fun setHighwayUploadCoroutineCount (I)V public final fun setJson (Lkotlinx/serialization/json/Json;)V public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 741d410d1..a94fb0392 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,7 +12,7 @@ import org.gradle.api.attributes.Attribute object Versions { - const val project = "2.2.0-dev-1" + const val project = "2.2.0-dev-bdh-2" const val kotlinCompiler = "1.4.21" const val kotlinStdlib = "1.4.21" diff --git a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt index 5bde42b26..87866bf46 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt @@ -120,6 +120,16 @@ public open class BotConfiguration { // open for Java /** 使用协议类型 */ public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE + /** + * Highway 通道上传图片, 语音, 文件等资源时的协程数量. + * + * 每个协程的速度约为 200KB/s. 协程数量越多越快, 同时也更要求性能. + * 默认 [CPU 核心数][Runtime.availableProcessors]. + * + * @since 2.2 + */ + public var highwayUploadCoroutineCount: Int = Runtime.getRuntime().availableProcessors() + /** * 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息. * @see fileBasedDeviceInfo 使用指定文件存储设备信息 diff --git a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt index 6c5e5446b..2bffdcf5c 100644 --- a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt +++ b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt @@ -12,10 +12,11 @@ package net.mamoe.mirai.utils -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runInterruptible -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext @Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DeprecatedCallableAddReplaceWith") @Deprecated( @@ -30,4 +31,14 @@ public suspend inline fun runBIO( public suspend inline fun runBIO( noinline block: () -> R -): R = runInterruptible(context = Dispatchers.IO, block = block) \ No newline at end of file +): R = runInterruptible(context = Dispatchers.IO, block = block) + +public inline fun CoroutineScope.launchWithPermit( + semaphore: Semaphore, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + crossinline block: suspend () -> Unit +): Job { + return launch(coroutineContext) { + semaphore.withPermit { block() } + } +} \ No newline at end of file diff --git a/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt b/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt index 2ace34a81..be1a5379a 100644 --- a/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt +++ b/mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt @@ -20,6 +20,24 @@ public inline fun Any?.safeCast(): T? = this as? T public inline fun Any?.castOrNull(): T? = this as? T + +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") +@kotlin.internal.InlineOnly +@kotlin.internal.LowPriorityInOverloadResolution +public inline fun Result.recoverCatchingSuppressed(transform: (exception: Throwable) -> R): Result { + return when (val exception = exceptionOrNull()) { + null -> this + else -> { + try { + Result.success(transform(exception)) + } catch (e: Throwable) { + e.addSuppressed(exception) + Result.failure(e) + } + } + } +} + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly @kotlin.internal.LowPriorityInOverloadResolution diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt index bf5508538..7ab673cac 100644 --- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt +++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt @@ -68,6 +68,7 @@ internal abstract class AbstractBot constructor( } // region network + internal val serverList: MutableList> = DefaultServerList.toMutableList() val network: N get() = _network diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index eaf28729e..eee652af5 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -25,6 +25,7 @@ import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.network.highway.Highway +import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.* @@ -745,17 +746,18 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { ) ).toByteArray(LongMsg.ReqBody.serializer()) - Highway.uploadResource( - bot, - response.proto.uint32UpIp.zip(response.proto.uint32UpPort), - response.proto.msgSig, - body.toExternalResource(null), - when (isLong) { - true -> "group long message" - false -> "group forward message" - }, - 27 - ) + body.toExternalResource().use { resource -> + Highway.uploadResourceBdh( + bot = bot, + resource = resource, + kind = when (isLong) { + true -> ResourceKind.GROUP_LONG_MESSAGE + false -> ResourceKind.GROUP_FORWARD_MESSAGE + }, + commandId = 27, + initialTicket = response.proto.msgSig + ) + } } } diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index 03eca6012..384a62808 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.packet.chat.* +import net.mamoe.mirai.internal.network.useNextServers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* @@ -63,6 +64,8 @@ internal class QQAndroidBot constructor( return client } + override val bot: QQAndroidBot get() = this + internal var firstLoginSucceed: Boolean = false inline val json get() = configuration.json diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index c3a8024dc..10bbdeb78 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -22,15 +22,16 @@ import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.internal.message.OfflineFriendImage import net.mamoe.mirai.internal.message.getImageType +import net.mamoe.mirai.internal.network.highway.ChannelKind +import net.mamoe.mirai.internal.network.highway.Highway +import net.mamoe.mirai.internal.network.highway.ResourceKind.PRIVATE_IMAGE import net.mamoe.mirai.internal.network.highway.postImage -import net.mamoe.mirai.internal.network.highway.sizeToString +import net.mamoe.mirai.internal.network.highway.tryServers import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext -import kotlin.math.roundToInt -import kotlin.time.measureTime internal val User.info: UserInfo? get() = this.castOrNull()?.info @@ -53,7 +54,7 @@ internal abstract class AbstractUser( if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } - val response = bot.network.run { + val resp = bot.network.run { LongConn.OffPicUp( bot.client, Cmd0x352.TryUpImgReq( srcUin = bot.id.toInt(), @@ -73,56 +74,64 @@ internal abstract class AbstractUser( is Member -> "temp" else -> "unknown" } - return when (response) { + return when (resp) { is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage( imageId = generateImageIdFromResourceId( - resourceId = response.resourceId, - format = getImageType(response.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME } + resourceId = resp.resourceId, + format = getImageType(resp.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME } ?: resource.formatName - ) ?: response.resourceId + ) ?: resp.resourceId ).also { ImageUploadEvent.Succeed(this, resource, it).broadcast() } is LongConn.OffPicUp.Response.RequireUpload -> { - bot.network.logger.verbose { - "[Http] Uploading $kind image, size=${resource.size.sizeToString()}" - } - val time = measureTime { - Mirai.Http.postImage( - "0x6ff0070", - bot.id, - null, - imageInput = resource, - uKeyHex = response.uKey.toUHexString("") + kotlin.runCatching { + Highway.uploadResourceBdh( + bot = bot, + resource = resource, + kind = PRIVATE_IMAGE, + commandId = 1, + initialTicket = resp.uKey ) - } - - bot.network.logger.verbose { - "[Http] Uploading $kind image: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" - } - - /* - HighwayHelper.uploadImageToServers( - bot, - response.serverIp.zip(response.serverPort), - response.uKey, - image, - kind = "friend", - commandId = 1 - )*/ - // 为什么不能 ?? + }.recoverCatchingSuppressed { + tryServers( + bot = bot, + servers = resp.serverIp.zip(resp.serverPort), + resourceSize = resource.size, + resourceKind = PRIVATE_IMAGE, + channelKind = ChannelKind.HTTP + ) { ip, port -> + Mirai.Http.postImage( + serverIp = ip, serverPort = port, + htcmd = "0x6ff0070", + uin = bot.id, + groupcode = null, + imageInput = resource, + uKeyHex = resp.uKey.toUHexString("") + ) + } + }.recoverCatchingSuppressed { + Mirai.Http.postImage( + serverIp = "htdata2.qq.com", + htcmd = "0x6ff0070", + uin = bot.id, + groupcode = null, + imageInput = resource, + uKeyHex = resp.uKey.toUHexString("") + ) + }.getOrThrow() OfflineFriendImage( - generateImageIdFromResourceId(response.resourceId, resource.formatName) ?: response.resourceId + generateImageIdFromResourceId(resp.resourceId, resource.formatName) ?: resp.resourceId ).also { ImageUploadEvent.Succeed(this, resource, it).broadcast() } } is LongConn.OffPicUp.Response.Failed -> { - ImageUploadEvent.Failed(this, resource, -1, response.message).broadcast() - error(response.message) + ImageUploadEvent.Failed(this, resource, -1, resp.message).broadcast() + error(resp.message) } } } diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 534a3f840..d1d6abd3b 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.LowLevelApi +import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.MemberInfo @@ -21,12 +22,16 @@ import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler -import net.mamoe.mirai.internal.network.highway.Highway +import net.mamoe.mirai.internal.network.highway.* +import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_IMAGE +import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_VOICE +import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388 import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache +import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* @@ -152,16 +157,16 @@ internal class GroupImpl( .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() } } is ImgStore.GroupPicUp.Response.RequireUpload -> { - Highway.uploadResource( - bot, - response.uploadIpList.zip(response.uploadPortList), - response.uKey, - resource, - kind = "group image", - commandId = 2 + // val servers = response.uploadIpList.zip(response.uploadPortList) + Highway.uploadResourceBdh( + bot = bot, + resource = resource, + kind = GROUP_IMAGE, + commandId = 2, + initialTicket = response.uKey ) - val resourceId = resource.calculateResourceId() - return OfflineGroupImage(imageId = resourceId) + + return OfflineGroupImage(imageId = resource.calculateResourceId()) .also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() } } } @@ -169,20 +174,36 @@ internal class GroupImpl( } override suspend fun uploadVoice(resource: ExternalResource): Voice { - if (resource.size > 1048576) { - throw OverFileSizeMaxException() - } return bot.network.run { - val response: PttStore.GroupPttUp.Response.RequireUpload = - PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect() + kotlin.runCatching { + val (_) = Highway.uploadResourceBdh( + bot = bot, + resource = resource, + kind = GROUP_VOICE, + commandId = 29, + extendInfo = PttStore.GroupPttUp.createTryUpPttPack(bot.id, id, resource) + .toByteArray(Cmd0x388.ReqBody.serializer()), + ) + }.recoverCatchingSuppressed { + when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()) { + is PttStore.GroupPttUp.Response.RequireUpload -> { + tryServers( + bot, + resp.uploadIpList.zip(resp.uploadPortList), + resource.size, + GROUP_VOICE, + ChannelKind.HTTP + ) { ip, port -> + Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey) + } + } + } + }.getOrThrow() + + // val body = resp?.loadAs(Cmd0x388.RspBody.serializer()) + // ?.msgTryupPttRsp + // ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey") - Highway.uploadPttToServers( - bot, - response.uploadIpList.zip(response.uploadPortList), - resource, - response.uKey, - response.fileKey, - ) Voice( "${resource.md5.toUHexString("")}.amr", resource.md5, diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt index e4a98682f..f9fd6fa74 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt @@ -213,20 +213,26 @@ private suspend fun MessageChain.convertToLongMessageIfNeeded( step: GroupMessageSendingStep, groupImpl: GroupImpl, ): MessageChain { + suspend fun sendLongImpl(): MessageChain { + val resId = groupImpl.uploadGroupLongMessageHighway(this) + return this + RichMessage.longMessage( + brief = takeContent(27), + resId = resId, + timeSeconds = currentTimeSeconds() + ) // LongMessageInternal replaces all contents and preserves metadata + } return when (step) { GroupMessageSendingStep.FIRST -> { // 只需要在第一次发送的时候验证长度 // 后续重试直接跳过 + if (contains(ForceAsLongMessage)) { + sendLongImpl() + } verityLength(this, groupImpl) this } GroupMessageSendingStep.LONG_MESSAGE -> { - val resId = groupImpl.uploadGroupLongMessageHighway(this) - this + RichMessage.longMessage( - brief = takeContent(27), - resId = resId, - timeSeconds = currentTimeSeconds() - ) // LongMessageInternal replaces all contents and preserves metadata + sendLongImpl() } GroupMessageSendingStep.FRAGMENTED -> this } diff --git a/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt b/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt new file mode 100644 index 000000000..cf415ee33 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/message/ForceAsLongMessage.kt @@ -0,0 +1,28 @@ +/* + * 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 net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.safeCast + +/** + * 内部 flag, 放入 chain 强制作为 long 发送 + */ +internal object ForceAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, + AbstractMessageKey({ it.safeCast() }) { + override val key: MessageKey get() = this + + override fun toString(): String = "ForceLongMessage" +} + +/** + * Ignore on transformation + */ +internal interface InternalFlagOnlyMessage : SingleMessage \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/conversions.kt b/mirai-core/src/commonMain/kotlin/message/conversions.kt index c55e1e0ab..6c774bb8a 100644 --- a/mirai-core/src/commonMain/kotlin/message/conversions.kt +++ b/mirai-core/src/commonMain/kotlin/message/conversions.kt @@ -243,6 +243,9 @@ internal fun MessageChain.toRichTextElems( is RichMessage // already transformed above -> { + } + is InternalFlagOnlyMessage -> { + // ignore } else -> error("unsupported message type: ${currentMessage::class.simpleName}") } diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index 1276edd93..f1bb0f75f 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -447,19 +447,26 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo this@QQAndroidBotNetworkHandler.launch(CoroutineName("Awaiting ConfigPushSvc.PushReq")) { logger.info { "Awaiting ConfigPushSvc.PushReq." } when (val resp: ConfigPushSvc.PushReq.PushReqResponse? = nextEventOrNull(10_000)) { - null -> logger.info { "Missing ConfigPushSvc.PushReq." } + null -> { + kotlin.runCatching { bot.client.bdhSession.completeExceptionally(TimeoutCancellationException("Timeout waiting for ConfigPushSvc.PushReq")) } + logger.warning { "Missing ConfigPushSvc.PushReq. File uploading may be affected." } + } is ConfigPushSvc.PushReq.PushReqResponse.Success -> { logger.info { "ConfigPushSvc.PushReq: Success." } } is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> { bot.logger.info { "Server requires reconnect." } - logger.debug { "ChangeServer.unknown = ${resp.unknown}." } bot.logger.info { "Server list: ${resp.serverList.joinToString()}." } - resp.serverList.forEach { - bot.client.serverList.add(it.host to it.port) + if (resp.serverList.isNotEmpty()) { + bot.serverList.clear() + resp.serverList.forEach { + bot.serverList.add(it.host to it.port) + } } - BotOfflineEvent.RequireReconnect(bot).broadcast() + + bot.launch { BotOfflineEvent.RequireReconnect(bot).broadcast() } + return@launch } } } diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt index 2ee248e50..37d7f30f1 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt @@ -14,21 +14,21 @@ package net.mamoe.mirai.internal.network import kotlinx.atomicfu.AtomicBoolean import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.atomic -import kotlinx.io.core.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.io.core.String +import kotlinx.io.core.toByteArray import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.protocol.SyncingCacheList -import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin +import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger import net.mamoe.mirai.internal.network.protocol.packet.Tlv -import net.mamoe.mirai.internal.utils.* +import net.mamoe.mirai.internal.utils.MiraiProtocolInternal +import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.internal.utils.crypto.ECDH -import net.mamoe.mirai.internal.utils.crypto.TEA -import net.mamoe.mirai.network.LoginFailedException -import net.mamoe.mirai.network.NoServerAvailableException import net.mamoe.mirai.utils.* +import java.util.concurrent.CopyOnWriteArraySet import kotlin.random.Random internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress) @@ -82,36 +82,8 @@ internal open class QQAndroidClient( get() = protocol.id internal var strangerSeq: Int = 0 - internal val serverList: MutableList> = DefaultServerList.toMutableList() - val keys: Map by lazy { - mapOf( - "16 zero" to ByteArray(16), - "D2 key" to wLoginSigInfo.d2Key, - "wtSessionTicketKey" to wLoginSigInfo.wtSessionTicketKey, - "userStKey" to wLoginSigInfo.userStKey, - "tgtgtKey" to tgtgtKey, - "tgtKey" to wLoginSigInfo.tgtKey, - "deviceToken" to wLoginSigInfo.deviceToken, - "shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey - //"t108" to wLoginSigInfo.t1, - //"t10c" to t10c, - //"t163" to t163 - ) - } - - internal inline fun tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? { - keys.forEach { (key, value) -> - kotlin.runCatching { - return mapper(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } }) - } - } - return null - } - - override fun toString(): String { // extremely slow - return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.toUHexString()}, randomKey=${randomKey.toUHexString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.toUHexString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.toUHexString()}, protocolVersion=$protocolVersion, apkId=${apkId.toUHexString()}, t150=${t150?.value?.toUHexString()}, rollbackSig=${rollbackSig?.toUHexString()}, ipFromT149=${ipFromT149?.toUHexString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.toUHexString()}, t528=${t528?.toUHexString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.toUHexString()}, qrPushSig=${qrPushSig.toUHexString()}, mainDisplayName='$mainDisplayName')" - } + val keys: Map by lazy { allKeys() } var onlineStatus: OnlineStatus = OnlineStatus.ONLINE @@ -126,33 +98,7 @@ internal open class QQAndroidClient( private val _ssoSequenceId: AtomicInt = atomic(85600) - lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin - - internal suspend inline fun useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) { - if (bot.client.serverList.isEmpty()) { - bot.client.serverList.addAll(DefaultServerList) - } - retryCatchingExceptions(bot.client.serverList.size, except = LoginFailedException::class) l@{ - val pair = bot.client.serverList[0] - runCatchingExceptions { - block(pair.first, pair.second) - return@l - }.getOrElse { - bot.client.serverList.remove(pair) - if (it !is LoginFailedException) { - // 不要重复打印. - bot.logger.warning(it) - } - throw it - } - }.getOrElse { - if (it is LoginFailedException) { - throw it - } - bot.client.serverList.addAll(DefaultServerList) - throw NoServerAvailableException(it) - } - } + var fileStoragePushFSSvcList: FileStoragePushFSSvcList? = null @MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket") internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2) @@ -318,7 +264,6 @@ internal open class QQAndroidClient( lateinit var wFastLoginInfo: WFastLoginInfo var reserveUinInfo: ReserveUinInfo? = null lateinit var wLoginSigInfo: WLoginSigInfo - var tlv113: ByteArray? = null /** * from tlvMap119 @@ -331,193 +276,17 @@ internal open class QQAndroidClient( var transportSequenceId = 1 lateinit var t104: ByteArray -} -@Suppress("RemoveRedundantQualifierName") // bug -internal fun generateTgtgtKey(guid: ByteArray): ByteArray = - (getRandomByteArray(16) + guid).md5() - - -internal class ReserveUinInfo( - val imgType: ByteArray, - val imgFormat: ByteArray, - val imgUrl: ByteArray -) { - override fun toString(): String { - return "ReserveUinInfo(imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()})" - } -} - -internal class WFastLoginInfo( - val outA1: ByteReadPacket, - var adUrl: String = "", - var iconUrl: String = "", - var profileUrl: String = "", - var userJson: String = "" -) { - override fun toString(): String { - return "WFastLoginInfo(outA1=$outA1, adUrl='$adUrl', iconUrl='$iconUrl', profileUrl='$profileUrl', userJson='$userJson')" - } -} - -internal class WLoginSimpleInfo( - val uin: Long, // uin - val face: Int, // ubyte actually - val age: Int, // ubyte - val gender: Int, // ubyte - val nick: String, // ubyte lv string - val imgType: ByteArray, - val imgFormat: ByteArray, - val imgUrl: ByteArray, - val mainDisplayName: ByteArray -) { - override fun toString(): String { - return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()}, mainDisplayName=${mainDisplayName.toUHexString()})" - } -} - -internal class LoginExtraData( - val uin: Long, - val ip: ByteArray, - val time: Int, - val version: Int -) { - override fun toString(): String { - return "LoginExtraData(uin=$uin, ip=${ip.toUHexString()}, time=$time, version=$version)" - } -} - -internal class WLoginSigInfo( - val uin: Long, - val encryptA1: ByteArray?, // sigInfo[0] /** - * WARNING, please check [QQAndroidClient.tlv16a] + * from ConfigPush.PushReq */ - val noPicSig: ByteArray?, // sigInfo[1] - val G: ByteArray, // sigInfo[2] - val dpwd: ByteArray, - val randSeed: ByteArray, - - val simpleInfo: WLoginSimpleInfo, - - val appPri: Long, - val a2ExpiryTime: Long, - val loginBitmap: Long, - val tgt: ByteArray, - val a2CreationTime: Long, - val tgtKey: ByteArray, - val userStSig: UserStSig, - /** - * TransEmpPacket 加密使用 - */ - val userStKey: ByteArray, - val userStWebSig: UserStWebSig, - val userA5: UserA5, - val userA8: UserA8, - val lsKey: LSKey, - val sKey: SKey, - val userSig64: UserSig64, - val openId: ByteArray, - val openKey: OpenKey, - val vKey: VKey, - val accessToken: AccessToken, - val d2: D2, - val d2Key: ByteArray, - val sid: Sid, - val aqSig: AqSig, - val psKeyMap: PSKeyMap, - val pt4TokenMap: Pt4TokenMap, - val superKey: ByteArray, - val payToken: ByteArray, - val pf: ByteArray, - val pfKey: ByteArray, - val da2: ByteArray, - // val pt4Token: ByteArray, - val wtSessionTicket: WtSessionTicket, - val wtSessionTicketKey: ByteArray, - val deviceToken: ByteArray -) { - override fun toString(): String { - return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})" - } + @JvmField + val bdhSession: CompletableDeferred = CompletableDeferred() } -internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) -internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) -internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) - -internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal typealias PSKeyMap = MutableMap -internal typealias Pt4TokenMap = MutableMap - -internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray()) - -internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) - -internal fun parsePSKeyMapAndPt4TokenMap( - data: ByteArray, - creationTime: Long, - expireTime: Long, - outPSKeyMap: PSKeyMap, - outPt4TokenMap: Pt4TokenMap -) = - data.read { - repeat(readShort().toInt()) { - val domain = readUShortLVString() - val psKey = readUShortLVByteArray() - val pt4token = readUShortLVByteArray() - - when { - psKey.isNotEmpty() -> outPSKeyMap[domain] = PSKey(psKey, creationTime, expireTime) - pt4token.isNotEmpty() -> outPt4TokenMap[domain] = Pt4Token(pt4token, creationTime, expireTime) - } - } - } - -internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : - KeyWithExpiry(data, creationTime, expireTime) - -internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) - -internal open class KeyWithExpiry( - data: ByteArray, - creationTime: Long, - val expireTime: Long -) : KeyWithCreationTime(data, creationTime) { - override fun toString(): String { - return "KeyWithExpiry(data=${data.toUHexString()}, creationTime=$creationTime)" - } -} - -internal open class KeyWithCreationTime( - val data: ByteArray, - val creationTime: Long -) { - override fun toString(): String { - return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)" - } -} \ No newline at end of file +internal class BdhSession( + val sigSession: ByteArray, + val sessionKey: ByteArray, + var ssoAddresses: MutableSet> = CopyOnWriteArraySet(), + var otherAddresses: MutableSet> = CopyOnWriteArraySet(), +) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/highway/ChunkedFlowSession.kt b/mirai-core/src/commonMain/kotlin/network/highway/ChunkedFlowSession.kt index 068e94b7a..e8508fc8c 100644 --- a/mirai-core/src/commonMain/kotlin/network/highway/ChunkedFlowSession.kt +++ b/mirai-core/src/commonMain/kotlin/network/highway/ChunkedFlowSession.kt @@ -9,10 +9,16 @@ package net.mamoe.mirai.internal.network.highway +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import kotlinx.io.core.Closeable import net.mamoe.mirai.utils.runBIO +import net.mamoe.mirai.utils.toLongUnsigned import net.mamoe.mirai.utils.withUse import java.io.InputStream +import java.util.concurrent.atomic.AtomicLong +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract internal class ChunkedFlowSession( private val input: InputStream, @@ -23,14 +29,18 @@ internal class ChunkedFlowSession( input.close() } - private var offset = 0L + private var offset = AtomicLong(0L) - internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) = withUse { - while (true) { - val size = runBIO { input.read(buffer) } - if (size == -1) return - block(mapper(buffer, size, offset)) - offset += size + internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) { + contract { callsInPlace(block, InvocationKind.UNKNOWN) } + withUse { + while (true) { + val size = runBIO { input.read(buffer) } + if (size == -1) return + block(mapper(buffer, size, offset.getAndAdd(size.toLongUnsigned()))) + } } } + + internal suspend fun asFlow(): Flow = flow { useAll { emit(it) } } // 'single thread' producer } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt index a86be3589..ee6115d87 100644 --- a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt +++ b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt @@ -9,207 +9,224 @@ package net.mamoe.mirai.internal.network.highway -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.utils.io.* -import io.ktor.utils.io.jvm.javaio.* -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.io.core.* -import net.mamoe.mirai.Mirai +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.receiveOrNull +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.produceIn +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.buildPacket +import kotlinx.io.core.discardExact +import kotlinx.io.core.writeFully import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec import net.mamoe.mirai.internal.utils.PlatformSocket -import net.mamoe.mirai.internal.utils.SocketException +import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray -import net.mamoe.mirai.internal.utils.toIpV4AddressString +import net.mamoe.mirai.internal.utils.retryWithServers +import net.mamoe.mirai.internal.utils.sizeToString import net.mamoe.mirai.utils.* -import java.io.InputStream +import java.util.concurrent.atomic.AtomicReference +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.math.roundToInt import kotlin.time.measureTime - -/** - * 在发送完成后将会 [InputStream.close] - */ -internal fun ExternalResource.consumeAsWriteChannelContent(contentType: ContentType?): OutgoingContent.WriteChannelContent { - return object : OutgoingContent.WriteChannelContent() { - override val contentType: ContentType? = contentType - override val contentLength: Long = size - - override suspend fun writeTo(channel: ByteWriteChannel) { - inputStream().withUse { copyTo(channel) } - } - } -} - -@Suppress("SpellCheckingInspection") -internal suspend fun HttpClient.postImage( - htcmd: String, - uin: Long, - groupcode: Long?, - imageInput: ExternalResource, - uKeyHex: String -): Boolean = post { - url { - protocol = URLProtocol.HTTP - host = "htdata2.qq.com" - path("cgi-bin/httpconn") - - parameters["htcmd"] = htcmd - parameters["uin"] = uin.toString() - - if (groupcode != null) parameters["groupcode"] = groupcode.toString() - - parameters["term"] = "pc" - parameters["ver"] = "5603" - parameters["filesize"] = imageInput.size.toString() - parameters["range"] = 0.toString() - parameters["ukey"] = uKeyHex - - userAgent("QQClient") - } - - body = imageInput.consumeAsWriteChannelContent(ContentType.Image.Any) -} == HttpStatusCode.OK - - internal object Highway { + @Suppress("ArrayInDataClass") + data class BdhUploadResponse( + var extendInfo: ByteArray? = null, + ) - suspend fun uploadResource( + suspend fun uploadResourceBdh( bot: QQAndroidBot, - servers: List>, - uKey: ByteArray, resource: ExternalResource, - kind: String, - commandId: Int - ) = servers.retryWithServers( - (resource.size * 1000 / 1024 / 10).coerceAtLeast(5000), - onFail = { - throw IllegalStateException("cannot upload $kind, failed on all servers.", it) - } - ) { ip, port -> - bot.network.logger.verbose { - "[Highway] Uploading $kind to ${ip}:$port, size=${resource.size.sizeToString()}" - } + kind: ResourceKind, + commandId: Int, // group image=2, friend image=1, groupPtt=29 + extendInfo: ByteArray = EMPTY_BYTE_ARRAY, + encrypt: Boolean = false, + initialTicket: ByteArray? = null, + ): BdhUploadResponse { + val bdhSession = bot.client.bdhSession.await() // no need to care about timeout. proceed by bot init - val time = measureTime { - uploadResourceImpl( + return tryServers(bot, bdhSession.ssoAddresses, resource.size, kind, ChannelKind.HIGHWAY) { ip, port -> + val md5 = resource.md5 + require(md5.size == 16) { "bad md5. Required size=16, got ${md5.size}" } + + val resp = BdhUploadResponse() + highwayPacketSession( client = bot.client, - serverIp = ip, - serverPort = port, - resource = resource, - fileMd5 = resource.md5, - ticket = uKey, - commandId = commandId - ) - } - - bot.network.logger.verbose { - "[Highway] Uploading $kind: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" - } - } - - private suspend fun uploadResourceImpl( - client: QQAndroidClient, - serverIp: String, - serverPort: Int, - ticket: ByteArray, - resource: ExternalResource, - fileMd5: ByteArray, - commandId: Int // group=2, friend=1 - ) { - require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" } - // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } - // require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" } - - val socket = PlatformSocket() - while (client.bot.network.areYouOk() && client.bot.isActive) { - try { - socket.connect(serverIp, serverPort) - break - } catch (e: SocketException) { - delay(3000) - } - } - socket.use { - createImageDataPacketSequence( - client = client, - appId = client.subAppId.toInt(), + appId = bot.client.subAppId.toInt(), command = "PicUp.DataUp", commandId = commandId, - ticket = ticket, + initialTicket = initialTicket ?: bdhSession.sigSession, data = resource, - fileMd5 = fileMd5 - ).useAll { - socket.send(it) - //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 - - socket.read().withUse { - discardExact(1) - val headLength = readInt() - discardExact(4) - val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) - check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" } + fileMd5 = md5, + extendInfo = if (encrypt) TEA.encrypt(extendInfo, bdhSession.sessionKey) else extendInfo + ).sendConcurrently( + createConnection = { PlatformSocket.connect(ip, port) }, + coroutines = bot.configuration.highwayUploadCoroutineCount + ) { head -> + if (head.rspExtendinfo.isNotEmpty()) { + resp.extendInfo = head.rspExtendinfo } } - } - } - - suspend fun uploadPttToServers( - bot: QQAndroidBot, - servers: List>, - resource: ExternalResource, - uKey: ByteArray, - fileKey: ByteArray, - ) { - servers.retryWithServers(10 * 1000, { - throw IllegalStateException("cannot upload ptt, failed on all servers.", it) - }, { s: String, i: Int -> - bot.network.logger.verbose { - "[Highway] Uploading ptt to ${s}:$i, size=${resource.size.sizeToString()}" - } - val time = measureTime { - uploadPttToServer(s, i, resource, uKey, fileKey) - } - bot.network.logger.verbose { - "[Highway] Uploading ptt: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" - } - - }) - - } - - private suspend fun uploadPttToServer( - serverIp: String, - serverPort: Int, - resource: ExternalResource, - uKey: ByteArray, - fileKey: ByteArray, - ) { - Mirai.Http.post { - url("http://$serverIp:$serverPort") - parameter("ver", 4679) - parameter("ukey", uKey.toUHexString("")) - parameter("filekey", fileKey.toUHexString("")) - parameter("filesize", resource.size) - parameter("bmd5", resource.md5.toUHexString("")) - parameter("mType", "pttDu") - parameter("voice_encodec", resource.voiceCodec) - body = resource.consumeAsWriteChannelContent(null) + resp } } } +internal enum class ResourceKind( + private val display: String +) { + PRIVATE_IMAGE("private image"), + GROUP_IMAGE("group image"), + PRIVATE_VOICE("private voice"), + GROUP_VOICE("group voice"), -internal fun createImageDataPacketSequence( + GROUP_LONG_MESSAGE("group long message"), + GROUP_FORWARD_MESSAGE("group forward message"), + ; + + override fun toString(): String = display +} + +internal enum class ChannelKind( + private val display: String +) { + HIGHWAY("Highway"), + HTTP("Http") + ; + + override fun toString(): String = display +} + +internal suspend inline fun tryServers( + bot: QQAndroidBot, + servers: Collection>, + resourceSize: Long, + resourceKind: ResourceKind, + channelKind: ChannelKind, + crossinline implOnEachServer: suspend (ip: String, port: Int) -> R +) = servers.retryWithServers( + (resourceSize * 1000 / 1024 / 10).coerceAtLeast(5000), + onFail = { throw IllegalStateException("cannot upload $resourceKind, failed on all servers.", it) } +) { ip, port -> + bot.network.logger.verbose { + "[${channelKind}] Uploading $resourceKind to ${ip}:$port, size=${resourceSize.sizeToString()}" + } + + var resp: R? = null + val time = measureTime { + runCatching { + resp = implOnEachServer(ip, port) + }.onFailure { + bot.network.logger.verbose { + "[${channelKind}] Uploading $resourceKind to ${ip}:$port, size=${resourceSize.sizeToString()} failed: $it" + } + throw it + } + } + + bot.network.logger.verbose { + "[${channelKind}] Uploading $resourceKind: succeed at ${(resourceSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" + } + + resp as R +} + +internal suspend fun ChunkedFlowSession.sendSequentially( + socket: PlatformSocket, + respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {} +) { + contract { callsInPlace(respCallback, InvocationKind.UNKNOWN) } + useAll { + socket.send(it) + //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 + + socket.read().withUse { + discardExact(1) + val headLength = readInt() + discardExact(4) + val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) + check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" } + respCallback(proto) + } + } +} + +private fun Flow.produceIn0(coroutineScope: CoroutineScope): ReceiveChannel { + return kotlin.runCatching { + @OptIn(FlowPreview::class) + produceIn(coroutineScope) // this is experimental api + }.getOrElse { + // fallback strategy in case binary changes. + + val channel = Channel() + coroutineScope.launch(CoroutineName("Flow collector")) { + collect { + channel.send(it) + } + channel.close() + } + channel + } +} + +internal suspend fun ChunkedFlowSession.sendConcurrently( + createConnection: suspend () -> PlatformSocket, + coroutines: Int = 5, + respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {} +) = coroutineScope { + val channel = asFlow().produceIn0(this) + // 'single thread' producer emits chunks to channel + + repeat(coroutines) { + launch(CoroutineName("Worker $it")) { + val socket = createConnection() + while (isActive) { + val next = channel.tryReceive() ?: break // concurrent-safe receive + val result = next.withUse { + socket.sendReceiveHighway(next) + } + respCallback(result) + } + } + } +} + +private suspend fun ReceiveChannel.tryReceive(): E? { + return kotlin.runCatching { + @OptIn(ExperimentalCoroutinesApi::class) + receiveOrNull() // this is experimental api + }.recoverCatching { + // in case binary changes + receive() + }.getOrNull() +} + +private suspend fun PlatformSocket.sendReceiveHighway( + it: ByteReadPacket, +): CSDataHighwayHead.RspDataHighwayHead { + send(it) + //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 + + read().withUse { + discardExact(1) + val headLength = readInt() + discardExact(4) + val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) + check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" } + return proto + } +} + +internal fun highwayPacketSession( // RequestDataTrans client: QQAndroidClient, command: String, @@ -217,14 +234,17 @@ internal fun createImageDataPacketSequence( dataFlag: Int = 4096, commandId: Int, localId: Int = 2052, - ticket: ByteArray, + initialTicket: ByteArray, data: ExternalResource, fileMd5: ByteArray, - sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE + sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE, + extendInfo: ByteArray = EMPTY_BYTE_ARRAY, ): ChunkedFlowSession { ByteArrayPool.checkBufferSize(sizePerPacket) // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } + val ticket = AtomicReference(initialTicket) + return ChunkedFlowSession(data.inputStream(), ByteArray(sizePerPacket)) { buffer, size, offset -> val head = CSDataHighwayHead.ReqDataHighwayHead( msgBasehead = CSDataHighwayHead.DataHighwayHead( @@ -235,6 +255,7 @@ internal fun createImageDataPacketSequence( 2 -> client.nextHighwayDataTransSequenceIdForGroup() 1 -> client.nextHighwayDataTransSequenceIdForFriend() 27 -> client.nextHighwayDataTransSequenceIdForApplyUp() + 29 -> client.nextHighwayDataTransSequenceIdForGroup() else -> error("illegal commandId: $commandId") }, retryTimes = 0, @@ -248,13 +269,13 @@ internal fun createImageDataPacketSequence( datalength = size, dataoffset = offset, filesize = data.size, - serviceticket = ticket, + serviceticket = ticket.get(), md5 = buffer.md5(0, size), fileMd5 = fileMd5, flag = 0, rtcode = 0 ), - reqExtendinfo = EMPTY_BYTE_ARRAY, + reqExtendinfo = extendInfo, msgLoginSigHead = null ).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer()) @@ -268,35 +289,3 @@ internal fun createImageDataPacketSequence( } } } - -internal suspend inline fun List>.retryWithServers( - timeoutMillis: Long, - onFail: (exception: Throwable?) -> Unit, - crossinline block: suspend (ip: String, port: Int) -> Unit -) { - require(this.isNotEmpty()) { "receiver of retryWithServers must not be empty" } - - var exception: Throwable? = null - for (pair in this) { - return kotlin.runCatching { - withTimeoutOrNull(timeoutMillis) { - block(pair.first.toIpV4AddressString(), pair.second) - } - }.recover { - if (exception != null) { - exception!!.addSuppressed(it) - } - exception = it - null - }.getOrNull() ?: continue - } - - onFail(exception) -} - -internal fun Int.sizeToString() = this.toLong().sizeToString() -internal fun Long.sizeToString(): String { - return if (this < 1024) { - "$this B" - } else ((this * 100.0 / 1024).roundToInt() / 100.0).toString() + " KiB" -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/highway/Http.kt b/mirai-core/src/commonMain/kotlin/network/highway/Http.kt new file mode 100644 index 000000000..a732a0081 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/highway/Http.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2020 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.network.highway + +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.http.content.* +import io.ktor.utils.io.* +import io.ktor.utils.io.jvm.javaio.* +import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec +import net.mamoe.mirai.utils.ExternalResource +import net.mamoe.mirai.utils.toUHexString +import net.mamoe.mirai.utils.withUse +import java.io.InputStream + + +/** + * 在发送完成后将会 [InputStream.close] + */ +internal fun ExternalResource.consumeAsWriteChannelContent(contentType: ContentType?): OutgoingContent.WriteChannelContent { + return object : OutgoingContent.WriteChannelContent() { + override val contentType: ContentType? = contentType + override val contentLength: Long = size + + override suspend fun writeTo(channel: ByteWriteChannel) { + inputStream().withUse { copyTo(channel) } + } + } +} + +internal val FALLBACK_HTTP_SERVER = "htdata2.qq.com" to 0 + +@Suppress("SpellCheckingInspection") +internal suspend fun HttpClient.postImage( + serverIp: String, + serverPort: Int = DEFAULT_PORT, + htcmd: String, + uin: Long, + groupcode: Long?, + imageInput: ExternalResource, + uKeyHex: String +): Boolean = post { + url { + protocol = URLProtocol.HTTP + host = serverIp // "htdata2.qq.com" + port = serverPort + path("cgi-bin/httpconn") + + parameters["htcmd"] = htcmd + parameters["uin"] = uin.toString() + + if (groupcode != null) parameters["groupcode"] = groupcode.toString() + + parameters["term"] = "pc" + parameters["ver"] = "5603" + parameters["filesize"] = imageInput.size.toString() + parameters["range"] = 0.toString() + parameters["ukey"] = uKeyHex + + userAgent("QQClient") + } + + body = imageInput.consumeAsWriteChannelContent(ContentType.Image.Any) +} == HttpStatusCode.OK + +internal suspend fun HttpClient.postPtt( + serverIp: String, + serverPort: Int, + resource: ExternalResource, + uKey: ByteArray, + fileKey: ByteArray, +) { + post { + url("http://$serverIp:$serverPort") + parameter("ver", 4679) + parameter("ukey", uKey.toUHexString("")) + parameter("filekey", fileKey.toUHexString("")) + parameter("filesize", resource.size) + parameter("bmd5", resource.md5.toUHexString("")) + parameter("mType", "pttDu") + parameter("voice_encodec", resource.voiceCodec) + body = resource.consumeAsWriteChannelContent(null) + } +} diff --git a/mirai-core/src/commonMain/kotlin/network/keys.kt b/mirai-core/src/commonMain/kotlin/network/keys.kt new file mode 100644 index 000000000..d892f63fa --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/keys.kt @@ -0,0 +1,264 @@ +/* + * Copyright 2020 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.network + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.Input +import kotlinx.io.core.readBytes +import kotlinx.io.core.readUShort +import net.mamoe.mirai.internal.network.getRandomByteArray +import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger +import net.mamoe.mirai.internal.utils.crypto.TEA +import net.mamoe.mirai.network.LoginFailedException +import net.mamoe.mirai.network.NoServerAvailableException +import net.mamoe.mirai.utils.* + + +internal class ReserveUinInfo( + val imgType: ByteArray, + val imgFormat: ByteArray, + val imgUrl: ByteArray +) { + override fun toString(): String { + return "ReserveUinInfo(imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()})" + } +} + +internal class WFastLoginInfo( + val outA1: ByteReadPacket, + var adUrl: String = "", + var iconUrl: String = "", + var profileUrl: String = "", + var userJson: String = "" +) { + override fun toString(): String { + return "WFastLoginInfo(outA1=$outA1, adUrl='$adUrl', iconUrl='$iconUrl', profileUrl='$profileUrl', userJson='$userJson')" + } +} + +internal class WLoginSimpleInfo( + val uin: Long, // uin + val face: Int, // ubyte actually + val age: Int, // ubyte + val gender: Int, // ubyte + val nick: String, // ubyte lv string + val imgType: ByteArray, + val imgFormat: ByteArray, + val imgUrl: ByteArray, + val mainDisplayName: ByteArray +) { + override fun toString(): String { + return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()}, mainDisplayName=${mainDisplayName.toUHexString()})" + } +} + +internal class LoginExtraData( + val uin: Long, + val ip: ByteArray, + val time: Int, + val version: Int +) { + override fun toString(): String { + return "LoginExtraData(uin=$uin, ip=${ip.toUHexString()}, time=$time, version=$version)" + } +} + +internal class WLoginSigInfo( + val uin: Long, + val encryptA1: ByteArray?, // sigInfo[0] + /** + * WARNING, please check [QQAndroidClient.tlv16a] + */ + val noPicSig: ByteArray?, // sigInfo[1] + val G: ByteArray, // sigInfo[2] + val dpwd: ByteArray, + val randSeed: ByteArray, + + val simpleInfo: WLoginSimpleInfo, + + val appPri: Long, + val a2ExpiryTime: Long, + val loginBitmap: Long, + val tgt: ByteArray, + val a2CreationTime: Long, + val tgtKey: ByteArray, + val userStSig: UserStSig, + /** + * TransEmpPacket 加密使用 + */ + val userStKey: ByteArray, + val userStWebSig: UserStWebSig, + val userA5: UserA5, + val userA8: UserA8, + val lsKey: LSKey, + val sKey: SKey, + val userSig64: UserSig64, + val openId: ByteArray, + val openKey: OpenKey, + val vKey: VKey, + val accessToken: AccessToken, + val d2: D2, + val d2Key: ByteArray, + val sid: Sid, + val aqSig: AqSig, + val psKeyMap: PSKeyMap, + val pt4TokenMap: Pt4TokenMap, + val superKey: ByteArray, + val payToken: ByteArray, + val pf: ByteArray, + val pfKey: ByteArray, + val da2: ByteArray, + // val pt4Token: ByteArray, + val wtSessionTicket: WtSessionTicket, + val wtSessionTicketKey: ByteArray, + val deviceToken: ByteArray +) { + override fun toString(): String { + return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})" + } +} + +internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) +internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) + +internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal typealias PSKeyMap = MutableMap +internal typealias Pt4TokenMap = MutableMap + +internal fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray()) + +internal fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) + +internal fun parsePSKeyMapAndPt4TokenMap( + data: ByteArray, + creationTime: Long, + expireTime: Long, + outPSKeyMap: PSKeyMap, + outPt4TokenMap: Pt4TokenMap +) = + data.read { + repeat(readShort().toInt()) { + val domain = readUShortLVString() + val psKey = readUShortLVByteArray() + val pt4token = readUShortLVByteArray() + + when { + psKey.isNotEmpty() -> outPSKeyMap[domain] = PSKey(psKey, creationTime, expireTime) + pt4token.isNotEmpty() -> outPt4TokenMap[domain] = Pt4Token(pt4token, creationTime, expireTime) + } + } + } + +internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) + +internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) + +internal open class KeyWithExpiry( + data: ByteArray, + creationTime: Long, + val expireTime: Long +) : KeyWithCreationTime(data, creationTime) { + override fun toString(): String { + return "KeyWithExpiry(data=${data.toUHexString()}, creationTime=$creationTime)" + } +} + +internal open class KeyWithCreationTime( + val data: ByteArray, + val creationTime: Long +) { + override fun toString(): String { + return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)" + } +} + +internal suspend inline fun QQAndroidClient.useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) { + if (bot.serverList.isEmpty()) { + bot.serverList.addAll(DefaultServerList) + } + retryCatchingExceptions(bot.serverList.size, except = LoginFailedException::class) l@{ + val pair = bot.serverList[0] + runCatchingExceptions { + block(pair.first, pair.second) + return@l + }.getOrElse { + bot.serverList.remove(pair) + if (it !is LoginFailedException) { + // 不要重复打印. + bot.logger.warning(it) + } + throw it + } + }.getOrElse { + if (it is LoginFailedException) { + throw it + } + bot.serverList.addAll(DefaultServerList) + throw NoServerAvailableException(it) + } +} + + +@Suppress("RemoveRedundantQualifierName") // bug +internal fun generateTgtgtKey(guid: ByteArray): ByteArray = + (getRandomByteArray(16) + guid).md5() + +internal inline fun QQAndroidClient.tryDecryptOrNull( + data: ByteArray, + size: Int = data.size, + mapper: (ByteArray) -> R +): R? { + keys.forEach { (key, value) -> + kotlin.runCatching { + return mapper(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } }) + } + } + return null +} + +internal fun QQAndroidClient.allKeys() = mapOf( + "16 zero" to ByteArray(16), + "D2 key" to wLoginSigInfo.d2Key, + "wtSessionTicketKey" to wLoginSigInfo.wtSessionTicketKey, + "userStKey" to wLoginSigInfo.userStKey, + "tgtgtKey" to tgtgtKey, + "tgtKey" to wLoginSigInfo.tgtKey, + "deviceToken" to wLoginSigInfo.deviceToken, + "shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey + //"t108" to wLoginSigInfo.t1, + //"t10c" to t10c, + //"t163" to t163 +) 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 4dc0a7b44..3eddddcf2 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Mamoe Technologies and contributors. + * 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. @@ -100,19 +100,23 @@ internal class _339( @TarsId(9) @JvmField val field1306: String = "" ) : JceStruct + +/** + * v8.5.5 + */ @Serializable -internal class FileStoragePushFSSvcListFuckKotlin( - @TarsId(0) @JvmField val vUpLoadList: List? = listOf(), - @TarsId(1) @JvmField val vPicDownLoadList: List? = listOf(), - @TarsId(2) @JvmField val vGPicDownLoadList: List? = null, - @TarsId(3) @JvmField val vQzoneProxyServiceList: List? = null, - @TarsId(4) @JvmField val vUrlEncodeServiceList: List? = null, +internal class FileStoragePushFSSvcList( + @TarsId(0) @JvmField val vUpLoadList: List = emptyList(), + @TarsId(1) @JvmField val vPicDownLoadList: List = emptyList(), + @TarsId(2) @JvmField val vGPicDownLoadList: List = emptyList(), + @TarsId(3) @JvmField val vQzoneProxyServiceList: List = emptyList(), + @TarsId(4) @JvmField val vUrlEncodeServiceList: List = emptyList(), @TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null, - @TarsId(6) @JvmField val vVipEmotionList: List? = null, - @TarsId(7) @JvmField val vC2CPicDownList: List? = null, + @TarsId(6) @JvmField val vVipEmotionList: List = emptyList(), + @TarsId(7) @JvmField val vC2CPicDownList: List = emptyList(), @TarsId(8) @JvmField val fmtIPInfo: FmtIPInfo? = null, @TarsId(9) @JvmField val domainIpChannel: DomainIpChannel? = null, - @TarsId(10) @JvmField val pttlist: ByteArray? = null + @TarsId(10) @JvmField val pttlist: ByteArray? = null, ) : JceStruct @Serializable diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Highway.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Highway.kt index 9055c6619..54c06f2b6 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Highway.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Highway.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Mamoe Technologies and contributors. + * 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. @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused", "SpellCheckingInspection") + package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable @@ -16,94 +18,99 @@ import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.utils.io.ProtoBuf + +/** + * v8.5.5 + */ + @Serializable internal class BdhExtinfo : ProtoBuf { @Serializable internal class CommFileExtReq( - @ProtoNumber(1) @JvmField val actionType: Int = 0, - @ProtoNumber(2) @JvmField val uuid: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val actionType: Int = 0, + @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class CommFileExtRsp( - @ProtoNumber(1) @JvmField val int32Retcode: Int = 0, - @ProtoNumber(2) @JvmField val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val int32Retcode: Int = 0, + @JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PicInfo( - @ProtoNumber(1) @JvmField val idx: Int = 0, - @ProtoNumber(2) @JvmField val size: Int = 0, - @ProtoNumber(3) @JvmField val binMd5: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(4) @JvmField val type: Int = 0 + @JvmField @ProtoNumber(1) val idx: Int = 0, + @JvmField @ProtoNumber(2) val size: Int = 0, + @JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(4) val type: Int = 0 ) : ProtoBuf @Serializable internal class QQVoiceExtReq( - @ProtoNumber(1) @JvmField val qid: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val fmt: Int = 0, - @ProtoNumber(3) @JvmField val rate: Int = 0, - @ProtoNumber(4) @JvmField val bits: Int = 0, - @ProtoNumber(5) @JvmField val channel: Int = 0, - @ProtoNumber(6) @JvmField val pinyin: Int = 0 + @JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val fmt: Int = 0, + @JvmField @ProtoNumber(3) val rate: Int = 0, + @JvmField @ProtoNumber(4) val bits: Int = 0, + @JvmField @ProtoNumber(5) val channel: Int = 0, + @JvmField @ProtoNumber(6) val pinyin: Int = 0 ) : ProtoBuf @Serializable internal class QQVoiceExtRsp( - @ProtoNumber(1) @JvmField val qid: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val int32Retcode: Int = 0, - @ProtoNumber(3) @JvmField val msgResult: List = emptyList() + @JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val int32Retcode: Int = 0, + @JvmField @ProtoNumber(3) val msgResult: List = emptyList() ) : ProtoBuf @Serializable internal class QQVoiceResult( - @ProtoNumber(1) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val pinyin: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val source: Int = 0 + @JvmField @ProtoNumber(1) val text: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val source: Int = 0 ) : ProtoBuf @Serializable internal class ShortVideoReqExtInfo( - @ProtoNumber(1) @JvmField val cmd: Int = 0, - @ProtoNumber(2) @JvmField val sessionId: Long = 0L, - @ProtoNumber(3) @JvmField val msgThumbinfo: PicInfo? = null, - @ProtoNumber(4) @JvmField val msgVideoinfo: VideoInfo? = null, - @ProtoNumber(5) @JvmField val msgShortvideoSureReq: ShortVideoSureReqInfo? = null, - @ProtoNumber(6) @JvmField val boolIsMergeCmdBeforeData: Boolean = false + @JvmField @ProtoNumber(1) val cmd: Int = 0, + @JvmField @ProtoNumber(2) val sessionId: Long = 0L, + @JvmField @ProtoNumber(3) val msgThumbinfo: PicInfo? = null, + @JvmField @ProtoNumber(4) val msgVideoinfo: VideoInfo? = null, + @JvmField @ProtoNumber(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null, + @JvmField @ProtoNumber(6) val boolIsMergeCmdBeforeData: Boolean = false ) : ProtoBuf @Serializable internal class ShortVideoRspExtInfo( - @ProtoNumber(1) @JvmField val cmd: Int = 0, - @ProtoNumber(2) @JvmField val sessionId: Long = 0L, - @ProtoNumber(3) @JvmField val int32Retcode: Int = 0, - @ProtoNumber(4) @JvmField val errinfo: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(5) @JvmField val msgThumbinfo: PicInfo? = null, - @ProtoNumber(6) @JvmField val msgVideoinfo: VideoInfo? = null, - @ProtoNumber(7) @JvmField val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null, - @ProtoNumber(8) @JvmField val retryFlag: Int = 0 + @JvmField @ProtoNumber(1) val cmd: Int = 0, + @JvmField @ProtoNumber(2) val sessionId: Long = 0L, + @JvmField @ProtoNumber(3) val int32Retcode: Int = 0, + @JvmField @ProtoNumber(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(5) val msgThumbinfo: PicInfo? = null, + @JvmField @ProtoNumber(6) val msgVideoinfo: VideoInfo? = null, + @JvmField @ProtoNumber(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null, + @JvmField @ProtoNumber(8) val retryFlag: Int = 0 ) : ProtoBuf @Serializable internal class ShortVideoSureReqInfo( - @ProtoNumber(1) @JvmField val fromuin: Long = 0L, - @ProtoNumber(2) @JvmField val chatType: Int = 0, - @ProtoNumber(3) @JvmField val touin: Long = 0L, - @ProtoNumber(4) @JvmField val groupCode: Long = 0L, - @ProtoNumber(5) @JvmField val clientType: Int = 0, - @ProtoNumber(6) @JvmField val msgThumbinfo: PicInfo? = null, - @ProtoNumber(7) @JvmField val msgMergeVideoinfo: List = emptyList(), - @ProtoNumber(8) @JvmField val msgDropVideoinfo: List = emptyList(), - @ProtoNumber(9) @JvmField val businessType: Int = 0, - @ProtoNumber(10) @JvmField val subBusinessType: Int = 0 + @JvmField @ProtoNumber(1) val fromuin: Long = 0L, + @JvmField @ProtoNumber(2) val chatType: Int = 0, + @JvmField @ProtoNumber(3) val touin: Long = 0L, + @JvmField @ProtoNumber(4) val groupCode: Long = 0L, + @JvmField @ProtoNumber(5) val clientType: Int = 0, + @JvmField @ProtoNumber(6) val msgThumbinfo: PicInfo? = null, + @JvmField @ProtoNumber(7) val msgMergeVideoinfo: List = emptyList(), + @JvmField @ProtoNumber(8) val msgDropVideoinfo: List = emptyList(), + @JvmField @ProtoNumber(9) val businessType: Int = 0, + @JvmField @ProtoNumber(10) val subBusinessType: Int = 0 ) : ProtoBuf @Serializable internal class ShortVideoSureRspInfo( - @ProtoNumber(1) @JvmField val fileid: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val ukey: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val msgVideoinfo: VideoInfo? = null, - @ProtoNumber(4) @JvmField val mergeCost: Int = 0 + @JvmField @ProtoNumber(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val msgVideoinfo: VideoInfo? = null, + @JvmField @ProtoNumber(4) val mergeCost: Int = 0 ) : ProtoBuf @Serializable @@ -111,31 +118,31 @@ internal class BdhExtinfo : ProtoBuf { @Serializable internal class StoryVideoExtRsp( - @ProtoNumber(1) @JvmField val int32Retcode: Int = 0, - @ProtoNumber(2) @JvmField val msg: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(4) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(5) @JvmField val fileId: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val int32Retcode: Int = 0, + @JvmField @ProtoNumber(2) val msg: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class UploadPicExtInfo( - @ProtoNumber(1) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class VideoInfo( - @ProtoNumber(1) @JvmField val idx: Int = 0, - @ProtoNumber(2) @JvmField val size: Int = 0, - @ProtoNumber(3) @JvmField val binMd5: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(4) @JvmField val format: Int = 0, - @ProtoNumber(5) @JvmField val resLen: Int = 0, - @ProtoNumber(6) @JvmField val resWidth: Int = 0, - @ProtoNumber(7) @JvmField val time: Int = 0, - @ProtoNumber(8) @JvmField val starttime: Long = 0L, - @ProtoNumber(9) @JvmField val isAudio: Int = 0 + @JvmField @ProtoNumber(1) val idx: Int = 0, + @JvmField @ProtoNumber(2) val size: Int = 0, + @JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(4) val format: Int = 0, + @JvmField @ProtoNumber(5) val resLen: Int = 0, + @JvmField @ProtoNumber(6) val resWidth: Int = 0, + @JvmField @ProtoNumber(7) val time: Int = 0, + @JvmField @ProtoNumber(8) val starttime: Long = 0L, + @JvmField @ProtoNumber(9) val isAudio: Int = 0 ) : ProtoBuf } @@ -143,142 +150,143 @@ internal class BdhExtinfo : ProtoBuf { internal class CSDataHighwayHead : ProtoBuf { @Serializable internal class C2CCommonExtendinfo( - @ProtoNumber(1) @JvmField val infoId: Int = 0, - @ProtoNumber(2) @JvmField val msgFilterExtendinfo: FilterExtendinfo? = null + @JvmField @ProtoNumber(1) val infoId: Int = 0, + @JvmField @ProtoNumber(2) val msgFilterExtendinfo: FilterExtendinfo? = null ) : ProtoBuf @Serializable internal class DataHighwayHead( - @ProtoNumber(1) @JvmField val version: Int = 0, - @ProtoNumber(2) @JvmField val uin: String = "", // yes - @ProtoNumber(3) @JvmField val command: String = "", - @ProtoNumber(4) @JvmField val seq: Int = 0, - @ProtoNumber(5) @JvmField val retryTimes: Int = 0, - @ProtoNumber(6) @JvmField val appid: Int = 0, - @ProtoNumber(7) @JvmField val dataflag: Int = 0, - @ProtoNumber(8) @JvmField val commandId: Int = 0, - @ProtoNumber(9) @JvmField val buildVer: String = "", - @ProtoNumber(10) @JvmField val localeId: Int = 0 + @JvmField @ProtoNumber(1) val version: Int = 0, + @JvmField @ProtoNumber(2) val uin: String = "", + @JvmField @ProtoNumber(3) val command: String = "", + @JvmField @ProtoNumber(4) val seq: Int = 0, + @JvmField @ProtoNumber(5) val retryTimes: Int = 0, + @JvmField @ProtoNumber(6) val appid: Int = 0, + @JvmField @ProtoNumber(7) val dataflag: Int = 0, + @JvmField @ProtoNumber(8) val commandId: Int = 0, + @JvmField @ProtoNumber(9) val buildVer: String = "", + @JvmField @ProtoNumber(10) val localeId: Int = 0, + @JvmField @ProtoNumber(11) val envId: Int = 0 ) : ProtoBuf @Serializable internal class DataHole( - @ProtoNumber(1) @JvmField val begin: Long = 0L, - @ProtoNumber(2) @JvmField val end: Long = 0L + @JvmField @ProtoNumber(1) val begin: Long = 0L, + @JvmField @ProtoNumber(2) val end: Long = 0L ) : ProtoBuf @Serializable internal class FilterExtendinfo( - @ProtoNumber(1) @JvmField val filterFlag: Int = 0, - @ProtoNumber(2) @JvmField val msgImageFilterRequest: ImageFilterRequest? = null + @JvmField @ProtoNumber(1) val filterFlag: Int = 0, + @JvmField @ProtoNumber(2) val msgImageFilterRequest: ImageFilterRequest? = null ) : ProtoBuf @Serializable internal class FilterStyle( - @ProtoNumber(1) @JvmField val styleId: Int = 0, - @ProtoNumber(2) @JvmField val styleName: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val styleId: Int = 0, + @JvmField @ProtoNumber(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ImageFilterRequest( - @ProtoNumber(1) @JvmField val sessionId: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val clientIp: Int = 0, - @ProtoNumber(3) @JvmField val uin: Long = 0L, - @ProtoNumber(4) @JvmField val style: FilterStyle? = null, - @ProtoNumber(5) @JvmField val width: Int = 0, - @ProtoNumber(6) @JvmField val height: Int = 0, - @ProtoNumber(7) @JvmField val imageData: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val clientIp: Int = 0, + @JvmField @ProtoNumber(3) val uin: Long = 0L, + @JvmField @ProtoNumber(4) val style: FilterStyle? = null, + @JvmField @ProtoNumber(5) val width: Int = 0, + @JvmField @ProtoNumber(6) val height: Int = 0, + @JvmField @ProtoNumber(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ImageFilterResponse( - @ProtoNumber(1) @JvmField val retCode: Int = 0, - @ProtoNumber(2) @JvmField val imageData: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val costTime: Int = 0 + @JvmField @ProtoNumber(1) val retCode: Int = 0, + @JvmField @ProtoNumber(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val costTime: Int = 0 ) : ProtoBuf @Serializable internal class LoginSigHead( - @ProtoNumber(1) @JvmField val loginsigType: Int = 0, - @ProtoNumber(2) @JvmField val loginsig: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val loginsigType: Int = 0, + @JvmField @ProtoNumber(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class NewServiceTicket( - @ProtoNumber(1) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val ukey: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val signature: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PicInfoExt( - @ProtoNumber(1) @JvmField val picWidth: Int = 0, - @ProtoNumber(2) @JvmField val picHeight: Int = 0, - @ProtoNumber(3) @JvmField val picFlag: Int = 0, - @ProtoNumber(4) @JvmField val busiType: Int = 0, - @ProtoNumber(5) @JvmField val srcTerm: Int = 0, - @ProtoNumber(6) @JvmField val platType: Int = 0, - @ProtoNumber(7) @JvmField val netType: Int = 0, - @ProtoNumber(8) @JvmField val imgType: Int = 0, - @ProtoNumber(9) @JvmField val appPicType: Int = 0 + @JvmField @ProtoNumber(1) val picWidth: Int = 0, + @JvmField @ProtoNumber(2) val picHeight: Int = 0, + @JvmField @ProtoNumber(3) val picFlag: Int = 0, + @JvmField @ProtoNumber(4) val busiType: Int = 0, + @JvmField @ProtoNumber(5) val srcTerm: Int = 0, + @JvmField @ProtoNumber(6) val platType: Int = 0, + @JvmField @ProtoNumber(7) val netType: Int = 0, + @JvmField @ProtoNumber(8) val imgType: Int = 0, + @JvmField @ProtoNumber(9) val appPicType: Int = 0 ) : ProtoBuf @Serializable internal class PicRspExtInfo( - @ProtoNumber(1) @JvmField val skey: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val clientIp: Int = 0, - @ProtoNumber(3) @JvmField val upOffset: Long = 0L, - @ProtoNumber(4) @JvmField val blockSize: Long = 0L + @JvmField @ProtoNumber(1) val skey: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val clientIp: Int = 0, + @JvmField @ProtoNumber(3) val upOffset: Long = 0L, + @JvmField @ProtoNumber(4) val blockSize: Long = 0L ) : ProtoBuf @Serializable internal class QueryHoleRsp( - @ProtoNumber(1) @JvmField val result: Int = 0, - @ProtoNumber(2) @JvmField val dataHole: List = emptyList(), - @ProtoNumber(3) @JvmField val boolCompFlag: Boolean = false + @JvmField @ProtoNumber(1) val result: Int = 0, + @JvmField @ProtoNumber(2) val dataHole: List = emptyList(), + @JvmField @ProtoNumber(3) val boolCompFlag: Boolean = false ) : ProtoBuf @Serializable internal class ReqDataHighwayHead( - @ProtoNumber(1) @JvmField val msgBasehead: DataHighwayHead? = null, - @ProtoNumber(2) @JvmField val msgSeghead: SegHead? = null, - @ProtoNumber(3) @JvmField val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(4) @JvmField val timestamp: Long = 0L, - @ProtoNumber(5) @JvmField val msgLoginSigHead: LoginSigHead? = null + @JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null, + @JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null, + @JvmField @ProtoNumber(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(4) val timestamp: Long = 0L, + @JvmField @ProtoNumber(5) val msgLoginSigHead: LoginSigHead? = null ) : ProtoBuf @Serializable internal class RspBody( - @ProtoNumber(1) @JvmField val msgQueryHoleRsp: QueryHoleRsp? = null + @JvmField @ProtoNumber(1) val msgQueryHoleRsp: QueryHoleRsp? = null ) : ProtoBuf @Serializable internal class RspDataHighwayHead( - @ProtoNumber(1) @JvmField val msgBasehead: DataHighwayHead? = null, - @ProtoNumber(2) @JvmField val msgSeghead: SegHead? = null, - @ProtoNumber(3) @JvmField val errorCode: Int = 0, - @ProtoNumber(4) @JvmField val allowRetry: Int = 0, - @ProtoNumber(5) @JvmField val cachecost: Int = 0, - @ProtoNumber(6) @JvmField val htcost: Int = 0, - @ProtoNumber(7) @JvmField val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(8) @JvmField val timestamp: Long = 0L, - @ProtoNumber(9) @JvmField val range: Long = 0L, - @ProtoNumber(10) @JvmField val isReset: Int = 0 + @JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null, + @JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null, + @JvmField @ProtoNumber(3) val errorCode: Int = 0, + @JvmField @ProtoNumber(4) val allowRetry: Int = 0, + @JvmField @ProtoNumber(5) val cachecost: Int = 0, + @JvmField @ProtoNumber(6) val htcost: Int = 0, + @JvmField @ProtoNumber(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(8) val timestamp: Long = 0L, + @JvmField @ProtoNumber(9) val range: Long = 0L, + @JvmField @ProtoNumber(10) val isReset: Int = 0 ) : ProtoBuf @Serializable internal class SegHead( - @ProtoNumber(1) @JvmField val serviceid: Int = 0, - @ProtoNumber(2) @JvmField val filesize: Long = 0L, - @ProtoNumber(3) @JvmField val dataoffset: Long = 0L, - @ProtoNumber(4) @JvmField val datalength: Int = 0, - @ProtoNumber(5) @JvmField val rtcode: Int = 0, - @ProtoNumber(6) @JvmField val serviceticket: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(7) @JvmField val flag: Int = 0, - @ProtoNumber(8) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(9) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(10) @JvmField val cacheAddr: Int = 0, - @ProtoNumber(11) @JvmField val queryTimes: Int = 0, - @ProtoNumber(12) @JvmField val updateCacheip: Int = 0 + @JvmField @ProtoNumber(1) val serviceid: Int = 0, + @JvmField @ProtoNumber(2) val filesize: Long = 0L, + @JvmField @ProtoNumber(3) val dataoffset: Long = 0L, + @JvmField @ProtoNumber(4) val datalength: Int = 0, + @JvmField @ProtoNumber(5) val rtcode: Int = 0, + @JvmField @ProtoNumber(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(7) val flag: Int = 0, + @JvmField @ProtoNumber(8) val md5: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(10) val cacheAddr: Int = 0, + @JvmField @ProtoNumber(11) val queryTimes: Int = 0, + @JvmField @ProtoNumber(12) val updateCacheip: Int = 0 ) : ProtoBuf } @@ -286,31 +294,31 @@ internal class CSDataHighwayHead : ProtoBuf { internal class HwConfigPersistentPB : ProtoBuf { @Serializable internal class HwConfigItemPB( - @ProtoNumber(1) @JvmField val ingKey: String = "", - @ProtoNumber(2) @JvmField val endPointList: List = emptyList() + @JvmField @ProtoNumber(1) val key: String = "", + @JvmField @ProtoNumber(2) val endPointList: List = emptyList() ) : ProtoBuf @Serializable internal class HwConfigPB( - @ProtoNumber(1) @JvmField val configItemList: List = emptyList(), - @ProtoNumber(2) @JvmField val netSegConfList: List = emptyList(), - @ProtoNumber(3) @JvmField val shortVideoNetConf: List = emptyList(), - @ProtoNumber(4) @JvmField val configItemListIp6: List = emptyList() + @JvmField @ProtoNumber(1) val configItemList: List = emptyList(), + @JvmField @ProtoNumber(2) val netSegConfList: List = emptyList(), + @JvmField @ProtoNumber(3) val shortVideoNetConf: List = emptyList(), + @JvmField @ProtoNumber(4) val configItemListIp6: List = emptyList() ) : ProtoBuf @Serializable internal class HwEndPointPB( - @ProtoNumber(1) @JvmField val ingHost: String = "", - @ProtoNumber(2) @JvmField val int32Port: Int = 0, - @ProtoNumber(3) @JvmField val int64Timestampe: Long = 0L + @JvmField @ProtoNumber(1) val host: String = "", + @JvmField @ProtoNumber(2) val int32Port: Int = 0, + @JvmField @ProtoNumber(3) val int64Timestampe: Long = 0L ) : ProtoBuf @Serializable internal class HwNetSegConfPB( - @ProtoNumber(1) @JvmField val int64NetType: Long = 0L, - @ProtoNumber(2) @JvmField val int64SegSize: Long = 0L, - @ProtoNumber(3) @JvmField val int64SegNum: Long = 0L, - @ProtoNumber(4) @JvmField val int64CurConnNum: Long = 0L + @JvmField @ProtoNumber(1) val int64NetType: Long = 0L, + @JvmField @ProtoNumber(2) val int64SegSize: Long = 0L, + @JvmField @ProtoNumber(3) val int64SegNum: Long = 0L, + @JvmField @ProtoNumber(4) val int64CurConnNum: Long = 0L ) : ProtoBuf } @@ -318,8 +326,8 @@ internal class HwConfigPersistentPB : ProtoBuf { internal class HwSessionInfoPersistentPB : ProtoBuf { @Serializable internal class HwSessionInfoPB( - @ProtoNumber(1) @JvmField val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY + @JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -327,137 +335,145 @@ internal class HwSessionInfoPersistentPB : ProtoBuf { internal class Subcmd0x501 : ProtoBuf { @Serializable internal class ReqBody( - @ProtoNumber(1281) @JvmField val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null + @JvmField @ProtoNumber(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null ) : ProtoBuf @Serializable internal class RspBody( - @ProtoNumber(1281) @JvmField val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null + @JvmField @ProtoNumber(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null ) : ProtoBuf @Serializable internal class SubCmd0x501ReqBody( - @ProtoNumber(1) @JvmField val uin: Long = 0L, - @ProtoNumber(2) @JvmField val idcId: Int = 0, - @ProtoNumber(3) @JvmField val appid: Int = 0, - @ProtoNumber(4) @JvmField val loginSigType: Int = 0, - @ProtoNumber(5) @JvmField val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(6) @JvmField val requestFlag: Int = 0, - @ProtoNumber(7) @JvmField val uint32ServiceTypes: List = emptyList(), - @ProtoNumber(8) @JvmField val bid: Int = 0, - @ProtoNumber(9) @JvmField val term: Int = 0, - @ProtoNumber(10) @JvmField val plat: Int = 0, - @ProtoNumber(11) @JvmField val net: Int = 0, - @ProtoNumber(12) @JvmField val caller: Int = 0 + @JvmField @ProtoNumber(1) val uin: Long = 0L, + @JvmField @ProtoNumber(2) val idcId: Int = 0, + @JvmField @ProtoNumber(3) val appid: Int = 0, + @JvmField @ProtoNumber(4) val loginSigType: Int = 0, + @JvmField @ProtoNumber(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(6) val requestFlag: Int = 0, + @JvmField @ProtoNumber(7) val uint32ServiceTypes: List = emptyList(), + @JvmField @ProtoNumber(8) val bid: Int = 0, + @JvmField @ProtoNumber(9) val term: Int = 0, + @JvmField @ProtoNumber(10) val plat: Int = 0, + @JvmField @ProtoNumber(11) val net: Int = 0, + @JvmField @ProtoNumber(12) val caller: Int = 0 ) : ProtoBuf @Serializable internal class SubCmd0x501Rspbody( - @ProtoNumber(1) @JvmField val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(2) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val msgHttpconnAddrs: List = emptyList(), - @ProtoNumber(4) @JvmField val preConnection: Int = 0, - @ProtoNumber(5) @JvmField val csConn: Int = 0, - @ProtoNumber(6) @JvmField val msgIpLearnConf: IpLearnConf? = null, - @ProtoNumber(7) @JvmField val msgDynTimeoutConf: DynTimeOutConf? = null, - @ProtoNumber(8) @JvmField val msgOpenUpConf: OpenUpConf? = null, - @ProtoNumber(9) @JvmField val msgDownloadEncryptConf: DownloadEncryptConf? = null, - @ProtoNumber(10) @JvmField val msgShortVideoConf: ShortVideoConf? = null, - @ProtoNumber(11) @JvmField val msgPtvConf: PTVConf? = null + @JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val msgHttpconnAddrs: List = emptyList(), + @JvmField @ProtoNumber(4) val preConnection: Int = 0, + @JvmField @ProtoNumber(5) val csConn: Int = 0, + @JvmField @ProtoNumber(6) val msgIpLearnConf: IpLearnConf? = null, + @JvmField @ProtoNumber(7) val msgDynTimeoutConf: DynTimeOutConf? = null, + @JvmField @ProtoNumber(8) val msgOpenUpConf: OpenUpConf? = null, + @JvmField @ProtoNumber(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null, + @JvmField @ProtoNumber(10) val msgShortVideoConf: ShortVideoConf? = null, + @JvmField @ProtoNumber(11) val msgPtvConf: PTVConf? = null, + @JvmField @ProtoNumber(12) val shareType: Int = 0, + @JvmField @ProtoNumber(13) val shareChannel: Int = 0, + @JvmField @ProtoNumber(14) val fmtPolicy: Int = 0, + @JvmField @ProtoNumber(15) val bigdataPolicy: Int = 0, + @JvmField @ProtoNumber(16) val connAttemptDelay: Int = 0 ) : ProtoBuf { @Serializable internal class DownloadEncryptConf( - @ProtoNumber(1) @JvmField val boolEnableEncryptRequest: Boolean = false, - @ProtoNumber(2) @JvmField val boolEnableEncryptedPic: Boolean = false, - @ProtoNumber(3) @JvmField val ctrlFlag: Int = 0 + @JvmField @ProtoNumber(1) val boolEnableEncryptRequest: Boolean = false, + @JvmField @ProtoNumber(2) val boolEnableEncryptedPic: Boolean = false, + @JvmField @ProtoNumber(3) val ctrlFlag: Int = 0 ) : ProtoBuf @Serializable internal class DynTimeOutConf( - @ProtoNumber(1) @JvmField val tbase2g: Int = 0, - @ProtoNumber(2) @JvmField val tbase3g: Int = 0, - @ProtoNumber(3) @JvmField val tbase4g: Int = 0, - @ProtoNumber(4) @JvmField val tbaseWifi: Int = 0, - @ProtoNumber(5) @JvmField val torg2g: Int = 0, - @ProtoNumber(6) @JvmField val torg3g: Int = 0, - @ProtoNumber(7) @JvmField val torg4g: Int = 0, - @ProtoNumber(8) @JvmField val torgWifi: Int = 0, - @ProtoNumber(9) @JvmField val maxTimeout: Int = 0, - @ProtoNumber(10) @JvmField val enableDynTimeout: Int = 0, - @ProtoNumber(11) @JvmField val maxTimeout2g: Int = 0, - @ProtoNumber(12) @JvmField val maxTimeout3g: Int = 0, - @ProtoNumber(13) @JvmField val maxTimeout4g: Int = 0, - @ProtoNumber(14) @JvmField val maxTimeoutWifi: Int = 0, - @ProtoNumber(15) @JvmField val hbTimeout2g: Int = 0, - @ProtoNumber(16) @JvmField val hbTimeout3g: Int = 0, - @ProtoNumber(17) @JvmField val hbTimeout4g: Int = 0, - @ProtoNumber(18) @JvmField val hbTimeoutWifi: Int = 0, - @ProtoNumber(19) @JvmField val hbTimeoutDefault: Int = 0 + @JvmField @ProtoNumber(1) val tbase2g: Int = 0, + @JvmField @ProtoNumber(2) val tbase3g: Int = 0, + @JvmField @ProtoNumber(3) val tbase4g: Int = 0, + @JvmField @ProtoNumber(4) val tbaseWifi: Int = 0, + @JvmField @ProtoNumber(5) val torg2g: Int = 0, + @JvmField @ProtoNumber(6) val torg3g: Int = 0, + @JvmField @ProtoNumber(7) val torg4g: Int = 0, + @JvmField @ProtoNumber(8) val torgWifi: Int = 0, + @JvmField @ProtoNumber(9) val maxTimeout: Int = 0, + @JvmField @ProtoNumber(10) val enableDynTimeout: Int = 0, + @JvmField @ProtoNumber(11) val maxTimeout2g: Int = 0, + @JvmField @ProtoNumber(12) val maxTimeout3g: Int = 0, + @JvmField @ProtoNumber(13) val maxTimeout4g: Int = 0, + @JvmField @ProtoNumber(14) val maxTimeoutWifi: Int = 0, + @JvmField @ProtoNumber(15) val hbTimeout2g: Int = 0, + @JvmField @ProtoNumber(16) val hbTimeout3g: Int = 0, + @JvmField @ProtoNumber(17) val hbTimeout4g: Int = 0, + @JvmField @ProtoNumber(18) val hbTimeoutWifi: Int = 0, + @JvmField @ProtoNumber(19) val hbTimeoutDefault: Int = 0 ) : ProtoBuf @Serializable internal class Ip6Addr( - @ProtoNumber(1) @JvmField val type: Int = 0, - @ProtoNumber(2) @JvmField val ip6: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoNumber(3) @JvmField val port: Int = 0, - @ProtoNumber(4) @JvmField val area: Int = 0, - @ProtoNumber(5) @JvmField val sameIsp: Int = 0 + @JvmField @ProtoNumber(1) val type: Int = 0, + @JvmField @ProtoNumber(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val port: Int = 0, + @JvmField @ProtoNumber(4) val area: Int = 0, + @JvmField @ProtoNumber(5) val sameIsp: Int = 0 ) : ProtoBuf @Serializable internal class IpAddr( - @ProtoNumber(1) @JvmField val type: Int = 0, - @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(2) @JvmField val ip: Int = 0, - @ProtoNumber(3) @JvmField val port: Int = 0, - @ProtoNumber(4) @JvmField val area: Int = 0, - @ProtoNumber(5) @JvmField val sameIsp: Int = 0 - ) : ProtoBuf + @JvmField @ProtoNumber(1) val type: Int = 0, + @ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(2) val ip: Int = 0, + @JvmField @ProtoNumber(3) val port: Int = 0, + @JvmField @ProtoNumber(4) val area: Int = 0, + @JvmField @ProtoNumber(5) val sameIsp: Int = 0 + ) : ProtoBuf { + fun decode(): Pair = ip to port + } @Serializable internal class IpLearnConf( - @ProtoNumber(1) @JvmField val refreshCachedIp: Int = 0, - @ProtoNumber(2) @JvmField val enableIpLearn: Int = 0 + @JvmField @ProtoNumber(1) val refreshCachedIp: Int = 0, + @JvmField @ProtoNumber(2) val enableIpLearn: Int = 0 ) : ProtoBuf @Serializable internal class NetSegConf( - @ProtoNumber(1) @JvmField val netType: Int = 0, - @ProtoNumber(2) @JvmField val segsize: Int = 0, - @ProtoNumber(3) @JvmField val segnum: Int = 0, - @ProtoNumber(4) @JvmField val curconnnum: Int = 0 + @JvmField @ProtoNumber(1) val netType: Int = 0, + @JvmField @ProtoNumber(2) val segsize: Int = 0, + @JvmField @ProtoNumber(3) val segnum: Int = 0, + @JvmField @ProtoNumber(4) val curconnnum: Int = 0 ) : ProtoBuf @Serializable internal class OpenUpConf( - @ProtoNumber(1) @JvmField val boolEnableOpenup: Boolean = false, - @ProtoNumber(2) @JvmField val preSendSegnum: Int = 0, - @ProtoNumber(3) @JvmField val preSendSegnum3g: Int = 0, - @ProtoNumber(4) @JvmField val preSendSegnum4g: Int = 0, - @ProtoNumber(5) @JvmField val preSendSegnumWifi: Int = 0 + @JvmField @ProtoNumber(1) val boolEnableOpenup: Boolean = false, + @JvmField @ProtoNumber(2) val preSendSegnum: Int = 0, + @JvmField @ProtoNumber(3) val preSendSegnum3g: Int = 0, + @JvmField @ProtoNumber(4) val preSendSegnum4g: Int = 0, + @JvmField @ProtoNumber(5) val preSendSegnumWifi: Int = 0 ) : ProtoBuf @Serializable internal class PTVConf( - @ProtoNumber(1) @JvmField val channelType: Int = 0, - @ProtoNumber(2) @JvmField val msgNetsegconf: List = emptyList(), - @ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false + @JvmField @ProtoNumber(1) val channelType: Int = 0, + @JvmField @ProtoNumber(2) val msgNetsegconf: List = emptyList(), + @JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false ) : ProtoBuf @Serializable internal class ShortVideoConf( - @ProtoNumber(1) @JvmField val channelType: Int = 0, - @ProtoNumber(2) @JvmField val msgNetsegconf: List = emptyList(), - @ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false, - @ProtoNumber(4) @JvmField val boolSendAheadSignal: Boolean = false + @JvmField @ProtoNumber(1) val channelType: Int = 0, + @JvmField @ProtoNumber(2) val msgNetsegconf: List = emptyList(), + @JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false, + @JvmField @ProtoNumber(4) val boolSendAheadSignal: Boolean = false ) : ProtoBuf @Serializable - internal class SrvAddrs( - @ProtoNumber(1) @JvmField val serviceType: Int = 0, - @ProtoNumber(2) @JvmField val msgAddrs: List = emptyList(), - @ProtoNumber(3) @JvmField val fragmentSize: Int = 0, - @ProtoNumber(4) @JvmField val msgNetsegconf: List = emptyList(), - @ProtoNumber(5) @JvmField val msgAddrsV6: List = emptyList() + internal data class SrvAddrs( + @JvmField @ProtoNumber(1) val serviceType: Int = 0, + @JvmField @ProtoNumber(2) val msgAddrs: List = emptyList(), + @JvmField @ProtoNumber(3) val fragmentSize: Int = 0, + @JvmField @ProtoNumber(4) val msgNetsegconf: List = emptyList(), + @JvmField @ProtoNumber(5) val msgAddrsV6: List = emptyList() ) : ProtoBuf } } + \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index 7196f09ff..814d45eee 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard import net.mamoe.mirai.internal.network.readUShortLVByteArray +import net.mamoe.mirai.internal.network.tryDecryptOrNull import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey import net.mamoe.mirai.utils.* diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/voice/PttStore.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/voice/PttStore.kt index 14e2f2104..cf8f1f93b 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/voice/PttStore.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/voice/PttStore.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Mamoe Technologies and contributors. + * 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. @@ -17,6 +17,7 @@ import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf @@ -63,14 +64,12 @@ internal class PttStore { } } - @OptIn(ExperimentalStdlibApi::class) - operator fun invoke( - client: QQAndroidClient, + fun createTryUpPttPack( uin: Long, groupCode: Long, resource: ExternalResource - ): OutgoingPacket { - val pack = Cmd0x388.ReqBody( + ): Cmd0x388.ReqBody { + return Cmd0x388.ReqBody( netType = 3, // wifi subcmd = 3, msgTryupPttReq = listOf( @@ -87,12 +86,22 @@ internal class PttStore { innerIp = 0, buildVer = "6.5.5.663".encodeToByteArray(), voiceLength = 1, - codec = 0, // don't use resource.codec + codec = resource.voiceCodec, // HTTP 时只支持 0 + // 2021/1/26 因为 #577 修改为 resource.voiceCodec voiceType = 1, boolNewUpChan = true ) ) ) + } + + operator fun invoke( + client: QQAndroidClient, + uin: Long, + groupCode: Long, + resource: ExternalResource + ): OutgoingPacketWithRespType { + val pack = createTryUpPttPack(uin, groupCode, resource) return buildOutgoingUniPacket(client) { writeProtoBuf(Cmd0x388.ReqBody.serializer(), pack) } 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 26976da73..cf2814733 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Mamoe Technologies and contributors. + * 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. @@ -11,29 +11,32 @@ package net.mamoe.mirai.internal.network.protocol.packet.login import kotlinx.io.core.ByteReadPacket import kotlinx.serialization.Serializable -import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.message.contextualBugReportException +import net.mamoe.mirai.internal.network.BdhSession import net.mamoe.mirai.internal.network.Packet -import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin +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.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.ProtoBuf +import net.mamoe.mirai.internal.utils.io.JceStruct 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.readProtoBuf 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.* +import net.mamoe.mirai.utils.toUHexString +import java.lang.IllegalStateException import net.mamoe.mirai.internal.network.protocol.data.jce.PushReq as PushReqJceStruct internal class ConfigPushSvc { - object PushReq : IncomingPacketFactory( + object PushReq : IncomingPacketFactory( receivingCommandName = "ConfigPushSvc.PushReq", responseCommandName = "ConfigPushSvc.PushResp" ) { @@ -50,16 +53,28 @@ internal class ConfigPushSvc { @Serializable data class ChangeServer( - @ProtoNumber(1) val unknown: Int, // =08 - @ProtoNumber(2) val serverList: List - ) : ProtoBuf, PushReqResponse() { + @TarsId(1) val serverList: List, + // @TarsId(3) val serverList2: List, + // @TarsId(8) val serverList3: List, + ) : JceStruct, PushReqResponse() { @Serializable data class ServerInfo( - @ProtoNumber(1) val host: String, - @ProtoNumber(2) val port: Int, - @ProtoNumber(3) val unknown: Int - ) : ProtoBuf { + /* + 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" } @@ -67,176 +82,68 @@ internal class ConfigPushSvc { } } - private val default by lazy { - val default = """ - 09 00 01 0A 16 0C 31 38 33 2E 35 37 2E 35 33 2E 31 36 21 1F 90 0B 19 00 01 0A 16 0B 31 38 30 2E 39 36 2E 31 2E 33 30 20 50 0B 29 00 02 0A 16 0D 36 31 2E 31 38 33 2E 31 36 34 2E 34 34 20 50 0B 0A 16 0D 31 31 33 2E 39 36 2E 32 33 32 2E 39 32 20 50 0B 39 00 06 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 31 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 35 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 33 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 2D 00 00 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 32 76 E4 B8 DD 40 01 5D 00 01 02 54 8A 50 D0 04 0A 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 12 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 1A 40 08 01 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 40 08 05 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 78 08 0A 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 70 02 78 02 80 01 FA 01 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0E 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 20 50 0B 0A 16 0D 36 31 2E 31 35 31 2E 31 38 33 2E 32 31 20 50 0B 8A 06 0F 31 37 31 2E 31 31 32 2E 32 32 36 2E 32 33 37 10 03 0B 9A 09 00 0B 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - """.trimIndent().hexToBytes() - - default.loadAs(FileStoragePushFSSvcListFuckKotlin.serializer()) + 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) + } } - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse? { - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - ByteArrayPool.useInstance(this.remaining.toInt()) { buffer -> - val length = this.readAvailable(buffer) + override suspend fun QQAndroidBot.handle(packet: PushReqResponse, sequenceId: Int): OutgoingPacket? { + fun handleSuccess(packet: PushReqResponse.Success) { + val pushReq = packet.struct + + // FS server + val fileStoragePushFSSvcList = pushReq.jcebuf.loadAs(FileStoragePushFSSvcList.serializer()) + bot.client.fileStoragePushFSSvcList = fileStoragePushFSSvcList + + val bigDataChannel = fileStoragePushFSSvcList.bigDataChannel + if (bigDataChannel?.vBigdataPbBuf == null) { + client.bdhSession.completeExceptionally(IllegalStateException("BdhSession not received.")) + return + } kotlin.runCatching { - val configPush = - buffer.toReadPacket(length = length) - .use { it.readUniPacket(PushReqJceStruct.serializer(), "PushReq") } + val resp = + bigDataChannel.vBigdataPbBuf.loadAs(Subcmd0x501.RspBody.serializer()).msgSubcmd0x501RspBody + ?: error("msgSubcmd0x501RspBody not found") - // 09 00 01 0A 16 0C 31 38 33 2E 35 37 2E 35 33 2E 31 36 21 1F 90 0B 19 00 01 0A 16 0B 31 38 30 2E 39 36 2E 31 2E 33 30 20 50 0B 29 00 02 0A 16 0D 36 31 2E 31 38 33 2E 31 36 34 2E 34 34 20 50 0B 0A 16 0D 31 31 33 2E 39 36 2E 32 33 32 2E 39 32 20 50 0B 39 00 06 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 31 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 35 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 33 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 2D 00 00 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 32 76 E4 B8 DD 40 01 5D 00 01 02 54 8A 50 D0 04 0A 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 12 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 1A 40 08 01 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 40 08 05 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 78 08 0A 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 70 02 78 02 80 01 FA 01 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0E 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 20 50 0B 0A 16 0D 36 31 2E 31 35 31 2E 31 38 33 2E 32 31 20 50 0B 8A 06 0F 31 37 31 2E 31 31 32 2E 32 32 36 2E 32 33 37 10 03 0B 9A 09 00 0B 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 + val session = BdhSession( + sigSession = resp.httpconnSigSession, + sessionKey = resp.sessionKey + ) - // ?? - // 19 00 0A 0A 16 0C 31 31 33 2E 39 36 2E 31 32 2E 38 35 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 33 33 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0D 31 31 33 2E 39 36 2E 31 32 2E 32 32 34 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0C 34 32 2E 38 31 2E 31 37 32 2E 36 33 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 37 36 20 50 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0D 31 31 33 2E 39 36 2E 31 32 2E 32 31 37 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 30 2E 31 32 32 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 38 2E 31 34 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 32 2E 32 30 37 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 39 00 0A 0A 16 0C 31 31 33 2E 39 36 2E 31 32 2E 38 35 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 33 33 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0D 31 31 33 2E 39 36 2E 31 32 2E 32 32 34 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0C 34 32 2E 38 31 2E 31 37 32 2E 36 33 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 37 36 20 50 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0D 31 31 33 2E 39 36 2E 31 32 2E 32 31 37 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 30 2E 31 32 32 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 38 2E 31 34 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 32 2E 32 30 37 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 4C 5C 6C 70 01 89 00 04 0A 16 0C 31 31 33 2E 39 36 2E 31 33 2E 34 34 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 68 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B 99 00 04 0A 16 0C 31 31 33 2E 39 36 2E 31 33 2E 34 34 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 68 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B A9 0C B9 0C C9 0C D9 0C E0 01 FC 0F F6 10 28 32 30 32 30 2D 30 34 2D 31 32 20 31 39 3A 31 30 3A 30 38 20 64 65 6C 69 76 65 72 79 69 6E 67 20 61 20 70 6F 6C 69 63 79 - - runCatching { - configPush.jcebuf.loadAs(FileStoragePushFSSvcListFuckKotlin.serializer()).also { - bot.client.fileStoragePushFSSvcList = it + for ((type, addresses) in resp.msgHttpconnAddrs) { + when (type) { + 10 -> session.ssoAddresses.addAll(addresses.map { it.decode() }) + 21 -> session.otherAddresses.addAll(addresses.map { it.decode() }) } - }.getOrElse { - bot.client.fileStoragePushFSSvcList = default - - // 19 00 0A 0A 16 0F 31 31 34 2E 32 32 31 2E 31 34 34 2E 31 36 30 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0C 31 31 33 2E 39 36 2E 31 33 2E 39 35 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 31 31 33 2E 39 36 2E 31 32 2E 32 31 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 30 2E 31 32 32 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0F 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 31 32 20 50 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0A 31 34 2E 32 32 2E 33 2E 33 35 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 32 2E 31 35 36 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 31 39 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 30 2E 31 32 32 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 39 00 0A 0A 16 0F 31 31 34 2E 32 32 31 2E 31 34 34 2E 31 36 30 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0C 31 31 33 2E 39 36 2E 31 33 2E 39 35 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 31 31 33 2E 39 36 2E 31 32 2E 32 31 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 30 2E 31 32 32 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0F 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 31 32 20 50 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 0A 31 34 2E 32 32 2E 33 2E 33 35 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 32 2E 31 35 36 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 31 39 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 73 68 96 03 74 65 6C 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 30 2E 31 32 32 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C 0B 4C 5C 6C 70 01 89 00 04 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 68 96 03 74 65 6C 0B 0A 16 0C 31 31 33 2E 39 36 2E 31 33 2E 34 34 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B 99 00 04 0A 16 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 68 96 03 74 65 6C 0B 0A 16 0C 31 31 33 2E 39 36 2E 31 33 2E 34 34 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B 0A 16 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C 0B A9 0C B9 0C C9 0C D9 0C E0 01 FC 0F F6 10 28 32 30 32 30 2D 30 34 2D 31 34 20 32 31 3A 33 31 3A 31 36 20 64 65 6C 69 76 65 72 79 69 6E 67 20 61 20 70 6F 6C 69 63 79 - // failed } - return PushReqResponse.Success(configPush) - }.recoverCatching { - // 02 00 00 01 2B 00 01 00 02 CB 1E 16 27 08 08 12 13 0A 0C 34 32 2E 38 31 2E 31 36 39 2E 34 36 10 90 3F 18 00 12 12 0A 0C 34 32 2E 38 31 2E 31 37 32 2E 38 31 10 50 18 00 12 15 0A 0E 31 31 34 2E 32 32 31 2E 31 34 38 2E 35 39 10 B0 6D 18 00 12 14 0A 0D 34 32 2E 38 31 2E 31 37 32 2E 31 34 37 10 BB 03 18 00 12 13 0A 0D 31 32 35 2E 39 34 2E 36 30 2E 31 34 36 10 50 18 00 12 15 0A 0F 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 31 35 10 50 18 00 12 18 0A 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 10 90 3F 18 00 12 12 0A 0C 34 32 2E 38 31 2E 31 37 32 2E 32 32 10 50 18 00 1A 13 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 10 50 18 00 1A 14 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 10 50 18 00 1A 14 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 10 50 18 00 1A 13 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 10 50 18 00 1A 13 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 10 50 18 00 03 - - // tail = 0x03 - buffer.toReadPacket(offset = 13, length = length - 1).withUse { - return readProtoBuf(PushReqResponse.ChangeServer.serializer()) + session + }.fold( + onSuccess = { + client.bdhSession.complete(it) + }, + onFailure = { cause -> + val e = IllegalStateException("Failed to decode BdhSession", cause) + client.bdhSession.completeExceptionally(e) + logger.error(e) } - }.getOrElse { - bot.network.logger.verbose { "Cannot decode ConfigPushSvc.PushReq, this can often be ignored." } - // bot.network.logger.debug(it) - return null - } + ) } - // 114.221.144.89 8080 - // 113.96.13.125 80 - // 42.81.172.63 14000 - - // println(bytes.toUHexString()) - // 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 08 7E 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 08 56 0A 10 02 2D 00 01 08 44 09 00 01 0A 16 0C 31 38 33 2E 35 37 2E 35 33 2E 31 36 21 1F 90 0B 19 00 01 0A 16 0B 31 38 30 2E 39 36 2E 31 2E 33 30 20 50 0B 29 00 02 0A 16 0D 36 31 2E 31 38 33 2E 31 36 34 2E 32 37 20 50 0B 0A 16 0B 31 34 2E 31 37 2E 34 33 2E 34 38 20 50 0B 39 00 06 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 33 35 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 31 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 35 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 20 50 0B 0A 16 0C 31 30 31 2E 39 31 2E 35 2E 31 38 37 21 01 BB 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 00 01 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 20 50 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 38 37 21 01 BB 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 00 01 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 20 50 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 38 37 21 01 BB 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 00 01 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 20 50 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 38 37 21 01 BB 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 67 84 36 BC AB 01 05 B2 79 8B F8 C3 51 00 DA AF 1E EF D8 E6 01 AF 08 05 B5 8B A6 61 9B 1B 1C 5A 0B AC FB D8 4C FB 2D 47 3F D0 8D 56 2D 6C FF 9B 48 B0 1D BC 14 34 F8 64 36 F2 0D EA 8B 63 C5 CC 37 54 0A A0 81 27 7D B8 91 EB 88 DC 69 2B 5C 88 BD 7B D3 B6 31 33 46 E1 BA BE A3 88 52 17 8B E6 11 5F DA C0 D2 DA 31 BB 2D 00 00 10 78 56 76 4A 44 49 62 33 6B 76 53 52 61 62 74 52 32 76 E4 B8 DD 40 01 5D 00 01 02 4F 8A 50 CB 04 0A 68 67 84 36 BC AB 01 05 B2 79 8B F8 C3 51 00 DA AF 1E EF D8 E6 01 AF 08 05 B5 8B A6 61 9B 1B 1C 5A 0B AC FB D8 4C FB 2D 47 3F D0 8D 56 2D 6C FF 9B 48 B0 1D BC 14 34 F8 64 36 F2 0D EA 8B 63 C5 CC 37 54 0A A0 81 27 7D B8 91 EB 88 DC 69 2B 5C 88 BD 7B D3 B6 31 33 46 E1 BA BE A3 88 52 17 8B E6 11 5F DA C0 D2 DA 31 BB 12 10 78 56 76 4A 44 49 62 33 6B 76 53 52 61 62 74 52 1A 41 08 01 12 0E 08 01 15 7B 96 4C AA 18 BB 03 20 02 28 01 12 0E 08 01 15 3B 24 59 FC 18 90 3F 20 01 28 01 12 0D 08 01 15 7B 96 4C A8 18 50 20 02 28 00 12 0E 08 01 15 65 5B 05 BB 18 BB 03 20 04 28 00 1A 41 08 05 12 0E 08 01 15 7B 96 4C AA 18 BB 03 20 02 28 01 12 0E 08 01 15 3B 24 59 FC 18 90 3F 20 01 28 01 12 0D 08 01 15 7B 96 4C A8 18 50 20 02 28 00 12 0E 08 01 15 65 5B 05 BB 18 BB 03 20 04 28 00 1A 79 08 0A 12 0E 08 01 15 7B 96 4C AA 18 BB 03 20 02 28 01 12 0E 08 01 15 3B 24 59 FC 18 90 3F 20 01 28 01 12 0D 08 01 15 7B 96 4C A8 18 50 20 02 28 00 12 0E 08 01 15 65 5B 05 BB 18 BB 03 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0E 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 20 50 0B 0A 16 0C 31 30 31 2E 38 39 2E 33 39 2E 32 31 20 50 0B 8A 06 0E 31 37 31 2E 31 31 32 2E 32 32 34 2E 31 30 10 03 0B 9A 09 00 0C 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 5B A0 6A 72 20 50 0B 0A 12 71 EB 3F 3B 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 5B A0 6A 72 20 50 0B 0A 12 71 EB 3F 3B 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 15 8C D7 0E 20 50 0B 0A 12 18 8C D7 0E 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 15 8C D7 0E 20 50 0B 0A 12 18 8C D7 0E 20 50 0B 29 0C 0B 0A 00 0B 19 00 02 0A 12 6D 01 B1 6F 20 50 0B 0A 12 4D 01 B1 6F 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DF 3F 5B 65 20 50 0B 0A 12 DE 3F 5B 65 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 2C B7 97 3D 20 50 0B 0A 12 1B 45 5B 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 01 0A 12 76 01 B1 6F 20 50 0B 29 0C 0B 0B AD 00 01 01 5A 08 01 10 80 EE D3 1D 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 81 57 32 12 08 8E A4 D8 9D 0A 10 50 18 89 D8 AC A0 0E 20 50 28 64 32 12 08 8E A4 D8 B5 0A 10 50 18 89 D8 AC B0 09 20 50 28 64 32 13 08 B4 C7 DA F0 01 10 50 18 8A EE D4 92 08 20 50 28 C8 01 32 13 08 E5 B6 F9 A1 09 10 50 18 89 88 E0 B4 08 20 50 28 C8 01 32 13 08 DF CF DE A2 07 10 50 18 8A EE D4 82 0E 20 50 28 AC 02 32 13 08 F8 98 CB C0 0A 10 50 18 E4 E0 8D A5 0E 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 10 00 00 00 00 00 00 00 1F 10 50 18 89 88 E0 CC 04 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 10 00 00 00 00 00 00 00 42 10 50 18 89 88 E0 F4 0C 20 50 28 64 3A 1F 0A 10 24 02 4E 00 80 20 00 02 00 00 00 00 00 00 00 A9 10 50 18 89 E6 80 B8 02 20 50 28 C8 01 3A 1F 0A 10 24 02 4E 00 80 20 00 02 00 00 00 00 00 00 00 A7 10 50 18 89 E6 80 A0 06 20 50 28 C8 01 3A 1F 0A 10 24 08 80 F1 00 31 00 10 00 00 00 00 00 00 00 40 10 50 18 89 88 8C DC 06 20 50 28 AC 02 3A 1F 0A 10 24 02 4E 00 80 10 00 00 00 00 00 00 00 00 01 58 10 50 18 89 DC C4 DC 03 20 50 28 AC 02 33 00 00 00 02 6C 97 2A BD 0B 8C 98 0C A8 0C - // 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 08 82 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 08 5A 0A 10 02 2D 00 01 08 4C 09 00 01 0A 16 0C 31 38 33 2E 35 37 2E 35 33 2E 31 36 21 1F 90 0B 19 00 01 0A 16 0B 31 38 30 2E 39 36 2E 31 2E 33 30 20 50 0B 29 00 02 0A 16 0D 36 31 2E 31 38 33 2E 31 36 34 2E 33 35 20 50 0B 0A 16 0D 31 38 30 2E 39 37 2E 31 30 2E 31 31 31 20 50 0B 39 00 06 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 33 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 33 33 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 33 34 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0C 31 38 33 2E 33 2E 32 32 35 2E 35 38 20 50 0B 0A 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 16 0F 31 32 33 2E 31 35 31 2E 31 39 30 2E 31 36 32 21 01 BB 0B 0A 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0C 31 38 33 2E 33 2E 32 32 35 2E 35 38 20 50 0B 0A 00 01 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 00 01 16 0F 31 32 33 2E 31 35 31 2E 31 39 30 2E 31 36 32 21 01 BB 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0C 31 38 33 2E 33 2E 32 32 35 2E 35 38 20 50 0B 0A 00 01 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 00 01 16 0F 31 32 33 2E 31 35 31 2E 31 39 30 2E 31 36 32 21 01 BB 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0C 31 38 33 2E 33 2E 32 32 35 2E 35 38 20 50 0B 0A 00 01 16 0C 35 39 2E 33 36 2E 38 39 2E 32 35 32 21 1F 90 0B 0A 00 01 16 0F 31 32 33 2E 31 35 31 2E 31 39 30 2E 31 36 32 21 01 BB 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 2E 0A 92 69 8B 45 38 A1 78 17 9F B5 DB FF 79 C1 EE A3 D7 68 B5 FC C1 3D 20 DD 4B 3B 3E D3 58 65 7E 0B D2 B8 A1 7B 0D 46 52 7E 10 53 18 0E 83 5D C3 3C 5F 1D DB 74 AE 63 3D 51 A1 2C 53 2D 2F CA DD 1C 37 A8 D6 C5 EA FE C3 2D 7B 16 CC 12 13 D3 27 19 57 31 8D 89 8C 44 71 21 BE 87 30 F2 A3 3D 2D 93 E1 0F F1 D1 22 45 2D 00 00 10 47 75 6D 44 64 44 51 6B 76 58 6E 61 66 4A 52 35 32 76 E4 B8 DD 40 01 5D 00 01 02 54 8A 50 D0 04 0A 68 2E 0A 92 69 8B 45 38 A1 78 17 9F B5 DB FF 79 C1 EE A3 D7 68 B5 FC C1 3D 20 DD 4B 3B 3E D3 58 65 7E 0B D2 B8 A1 7B 0D 46 52 7E 10 53 18 0E 83 5D C3 3C 5F 1D DB 74 AE 63 3D 51 A1 2C 53 2D 2F CA DD 1C 37 A8 D6 C5 EA FE C3 2D 7B 16 CC 12 13 D3 27 19 57 31 8D 89 8C 44 71 21 BE 87 30 F2 A3 3D 2D 93 E1 0F F1 D1 22 45 12 10 47 75 6D 44 64 44 51 6B 76 58 6E 61 66 4A 52 35 1A 40 08 01 12 0D 08 01 15 B7 03 E1 3A 18 50 20 01 28 01 12 0E 08 01 15 3B 24 59 FC 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 97 BE A2 18 BB 03 20 02 28 00 12 0D 08 01 15 65 5B 05 C2 18 50 20 04 28 00 1A 40 08 05 12 0D 08 01 15 B7 03 E1 3A 18 50 20 01 28 01 12 0E 08 01 15 3B 24 59 FC 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 97 BE A2 18 BB 03 20 02 28 00 12 0D 08 01 15 65 5B 05 C2 18 50 20 04 28 00 1A 78 08 0A 12 0D 08 01 15 B7 03 E1 3A 18 50 20 01 28 01 12 0E 08 01 15 3B 24 59 FC 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 97 BE A2 18 BB 03 20 02 28 00 12 0D 08 01 15 65 5B 05 C2 18 50 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 70 02 78 02 80 01 FA 01 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0C 31 30 31 2E 39 31 2E 35 2E 32 31 30 20 50 0B 0A 16 0C 31 30 31 2E 38 39 2E 33 39 2E 32 31 20 50 0B 8A 06 0F 31 37 31 2E 31 31 32 2E 32 32 36 2E 32 31 34 10 03 0B 9A 09 00 0C 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 0E EB 3F 3B 20 50 0B 0A 12 8D 9D 31 3A 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 0E 01 B1 6F 20 50 0B 0A 12 91 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 35 8C D7 0E 20 50 0B 0A 12 19 8C D7 0E 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 8C B4 12 0E 20 50 0B 0A 12 7B B4 12 0E 20 50 0B 29 0C 0B 0A 00 0B 19 00 02 0A 12 6D 01 B1 6F 20 50 0B 0A 12 4D 01 B1 6F 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 78 09 61 B4 20 50 0B 0A 12 78 08 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 64 09 61 B4 20 50 0B 0A 12 6B D4 E2 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 6B 89 31 3A 20 50 0B 0A 12 76 01 B1 6F 20 50 0B 29 0C 0B 0B AD 00 01 01 5B 08 01 10 96 FA C9 03 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 B7 0D 32 12 08 B7 87 F4 97 05 10 50 18 8A F0 D8 F6 08 20 50 28 64 32 12 08 B7 87 F4 DF 03 10 50 18 8A F0 D4 BA 01 20 50 28 64 32 13 08 B4 C7 DA A0 01 10 50 18 8A EC DC D6 01 20 50 28 C8 01 32 13 08 E5 B6 BD D0 03 10 50 18 89 D6 AD CC 02 20 50 28 C8 01 32 13 08 DF CF DE AA 07 10 50 18 8A EE D4 8A 0E 20 50 28 AC 02 32 13 08 B7 81 97 86 07 10 50 18 8A EC D4 86 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 10 00 00 00 00 00 00 00 4A 10 50 18 89 88 E0 DC 04 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 10 00 00 00 00 00 00 00 1F 10 50 18 89 88 E0 CC 04 20 50 28 64 3A 1F 0A 10 24 02 4E 00 80 20 00 02 00 00 00 00 00 00 00 A9 10 50 18 89 E6 80 B8 02 20 50 28 C8 01 3A 1F 0A 10 24 02 4E 00 80 20 00 02 00 00 00 00 00 00 00 72 10 50 18 89 E6 80 D8 02 20 50 28 C8 01 3A 1F 0A 10 24 08 80 F1 00 31 00 50 00 00 00 00 00 00 00 5A 10 50 18 89 88 8C A4 06 20 50 28 AC 02 3A 1F 0A 10 24 02 4E 00 80 10 00 00 00 00 00 00 00 00 01 51 10 50 18 89 DC C0 E4 0F 20 50 28 AC 02 32 4A D6 38 1D 0B 8C 98 0C A8 0C - // 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 08 70 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 08 48 0A 10 02 2D 00 01 08 36 09 00 01 0A 16 0C 31 38 33 2E 35 37 2E 35 33 2E 31 36 21 1F 90 0B 19 00 01 0A 16 0B 31 38 30 2E 39 36 2E 31 2E 33 30 20 50 0B 29 00 02 0A 16 0D 36 31 2E 31 38 33 2E 31 36 34 2E 33 36 20 50 0B 0A 16 0C 35 39 2E 33 36 2E 31 31 33 2E 36 32 20 50 0B 39 00 06 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 33 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 33 33 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 33 34 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0D 31 34 2E 31 38 2E 31 38 30 2E 31 38 34 20 50 0B 0A 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 37 21 1F 90 0B 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0D 31 34 2E 31 38 2E 31 38 30 2E 31 38 34 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 37 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0D 31 34 2E 31 38 2E 31 38 30 2E 31 38 34 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 37 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0D 31 34 2E 31 38 2E 31 38 30 2E 31 38 34 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 37 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 37 30 21 01 BB 0B 0A 00 01 16 0C 31 30 31 2E 39 31 2E 35 2E 31 39 34 20 50 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 24 A8 EE 05 B6 9E 51 AB 2C 10 75 F5 32 F5 CB F1 35 79 02 F4 B2 B9 BB FB B9 8E 20 16 39 61 79 24 47 59 C2 82 EB 13 9B 62 D5 66 66 CB EC F3 54 4B 3D E5 14 37 F2 0B C5 01 7D 21 E7 BC EF 30 8E 5C D6 C0 36 5B E6 3F 68 A9 DE 22 09 39 F5 A8 D6 9A 9B 58 09 37 80 EF B7 D4 C5 84 97 30 F4 69 A2 31 BC 2F 76 1E 06 F0 C5 7E 2D 00 00 10 36 39 42 41 71 51 46 65 43 4E 64 6A 62 65 59 39 32 76 E4 B8 DD 40 01 5D 00 01 02 54 8A 50 D0 04 0A 68 24 A8 EE 05 B6 9E 51 AB 2C 10 75 F5 32 F5 CB F1 35 79 02 F4 B2 B9 BB FB B9 8E 20 16 39 61 79 24 47 59 C2 82 EB 13 9B 62 D5 66 66 CB EC F3 54 4B 3D E5 14 37 F2 0B C5 01 7D 21 E7 BC EF 30 8E 5C D6 C0 36 5B E6 3F 68 A9 DE 22 09 39 F5 A8 D6 9A 9B 58 09 37 80 EF B7 D4 C5 84 97 30 F4 69 A2 31 BC 2F 76 1E 06 F0 C5 7E 12 10 36 39 42 41 71 51 46 65 43 4E 64 6A 62 65 59 39 1A 40 08 01 12 0D 08 01 15 0E 12 B4 B8 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E3 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C AA 18 BB 03 20 02 28 00 12 0D 08 01 15 65 5B 05 C2 18 50 20 04 28 00 1A 40 08 05 12 0D 08 01 15 0E 12 B4 B8 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E3 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C AA 18 BB 03 20 02 28 00 12 0D 08 01 15 65 5B 05 C2 18 50 20 04 28 00 1A 78 08 0A 12 0D 08 01 15 0E 12 B4 B8 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E3 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C AA 18 BB 03 20 02 28 00 12 0D 08 01 15 65 5B 05 C2 18 50 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 70 02 78 02 80 01 FA 01 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0C 31 30 31 2E 39 31 2E 35 2E 32 31 30 20 50 0B 0A 16 0E 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 20 50 0B 8A 06 0F 31 37 31 2E 31 31 32 2E 32 32 36 2E 32 33 37 10 03 0B 9A 09 00 0B 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 0D 31 BA DE 20 50 0B 0A 12 C3 B9 D3 74 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 17 D0 60 71 20 50 0B 0A 12 30 E8 60 71 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 E8 E9 03 B7 20 50 0B 0A 12 2E AF 12 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 13 75 61 B4 20 50 0B 0A 12 15 75 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 55 10 59 65 20 50 0B 0A 12 47 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 6B 89 31 3A 20 50 0B 0A 12 76 01 B1 6F 20 50 0B 29 0C 0B 0B AD 00 01 01 5B 08 01 10 BD 8F A6 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 B7 87 FC D7 05 10 50 18 8A F0 C8 98 06 20 50 28 64 32 12 08 B7 87 F4 87 02 10 50 18 8A F0 CC E8 02 20 50 28 64 32 13 08 E5 B6 BD E0 05 10 50 18 89 D6 AD BC 06 20 50 28 C8 01 32 13 08 E5 B2 F1 E8 0E 10 50 18 89 B0 C4 D1 08 20 50 28 C8 01 32 13 08 DF CD DE BC 0A 10 50 18 E4 E0 8D ED 0E 20 50 28 AC 02 32 13 08 B7 81 CF F5 0E 10 50 18 89 EC F4 F0 0C 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 AA 00 00 13 00 00 00 00 00 00 00 18 10 50 18 89 EC A0 88 0D 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 53 10 50 18 89 EC 8C B9 03 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 00 80 19 00 00 00 00 00 00 00 11 10 50 18 8A F0 D0 A2 0C 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 8F D0 00 50 00 00 00 00 00 00 00 5A 10 50 18 89 EC A0 C8 0A 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 10 19 00 00 00 00 00 00 00 13 10 50 18 E4 E6 A9 B4 07 20 50 28 AC 02 33 00 00 00 01 D3 68 60 BF 0B 8C 98 0C A8 0C - - // 02 00 00 01 2C 00 01 00 02 40 63 83 C1 - // - // 08 08 - // - // 12 - // 15 - // 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 38 39 - // 10 90 3F - // 18 00 - // - // 12 - // 13 - // 0A 0D 31 31 33 2E 39 36 2E 31 33 2E 31 32 35 - // 10 50 - // 18 00 - // - // 12 - // 13 - // 0A 0C 34 32 2E 38 31 2E 31 37 32 2E 36 33 - // 10 B0 6D - // 18 00 - // - // 12 - // 15 - // 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 33 34 - // 10 BB 03 - // 18 00 - // - // 12 - // 13 - // 0A 0D 31 32 35 2E 39 34 2E 36 30 2E 31 34 36 - // 10 50 - // 18 00 - // - // 12 - // 13 - // 0A 0D 34 32 2E 38 31 2E 31 37 32 2E 31 34 37 - // 10 50 - // 18 00 - // - // 12 - // 18 - // 0A 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D - // 10 90 3F - // 18 00 - // - // 12 - // 13 - // 0A 0D 34 32 2E 38 31 2E 31 37 32 2E 31 34 37 - // 10 50 - // 18 00 - // - // 1A - // 14 - // 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 - // 10 50 - // 18 00 - // - // 1A - // 13 - // 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 - // 10 50 - // 18 00 - // - // 1A - // 13 - // 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 - // 10 50 - // 18 00 - // - // 1A - // 14 - // 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 - // 10 50 - // 18 00 - // - // 1A - // 13 - // 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 - // 10 50 - // 18 00 - // - // 03 - - - // 02 00 00 01 2B 00 01 00 02 CB 1E 16 27 08 08 12 13 0A 0C 34 32 2E 38 31 2E 31 36 39 2E 34 36 10 90 3F 18 00 12 12 0A 0C 34 32 2E 38 31 2E 31 37 32 2E 38 31 10 50 18 00 12 15 0A 0E 31 31 34 2E 32 32 31 2E 31 34 38 2E 35 39 10 B0 6D 18 00 12 14 0A 0D 34 32 2E 38 31 2E 31 37 32 2E 31 34 37 10 BB 03 18 00 12 13 0A 0D 31 32 35 2E 39 34 2E 36 30 2E 31 34 36 10 50 18 00 12 15 0A 0F 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 31 35 10 50 18 00 12 18 0A 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 10 90 3F 18 00 12 12 0A 0C 34 32 2E 38 31 2E 31 37 32 2E 32 32 10 50 18 00 1A 13 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 10 50 18 00 1A 14 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 10 50 18 00 1A 14 0A 0E 31 31 34 2E 32 32 31 2E 31 34 34 2E 32 32 10 50 18 00 1A 13 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 10 50 18 00 1A 13 0A 0D 34 32 2E 38 31 2E 31 36 39 2E 31 30 35 10 50 18 00 03 - - - /* - + ' -42.81.169.46 ? -42.81.172.81P -114.221.148.59 m -42.81.172.147 -125.94.60.146P -114.221.144.215P -msfwifi.3g.qq.com ? -42.81.172.22P -42.81.169.105P -114.221.144.22P -114.221.144.22P -42.81.169.105P -42.81.169.105P - */ - } - - override suspend fun QQAndroidBot.handle(packet: PushReqResponse?, sequenceId: Int): OutgoingPacket? { - if (packet == null) { - return null - } when (packet) { is PushReqResponse.Success -> { + handleSuccess(packet) return buildResponseUniPacket( client, sequenceId = sequenceId diff --git a/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt index 4ea9bf723..0a85038ea 100644 --- a/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt +++ b/mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt @@ -16,6 +16,7 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable import kotlinx.io.streams.readPacketAtMost import kotlinx.io.streams.writePacket +import net.mamoe.mirai.utils.withUse import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.IOException @@ -23,6 +24,8 @@ import java.net.NoRouteToHostException import java.net.Socket import java.net.UnknownHostException import java.util.concurrent.Executors +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * TCP Socket. @@ -97,6 +100,26 @@ internal class PlatformSocket : Closeable { writeChannel = socket.getOutputStream().buffered() } } + + companion object { + suspend fun connect( + serverIp: String, + serverPort: Int, + ): PlatformSocket { + val socket = PlatformSocket() + socket.connect(serverIp, serverPort) + return socket + } + + suspend inline fun withConnection( + serverIp: String, + serverPort: Int, + block: PlatformSocket.() -> R + ): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return connect(serverIp, serverPort).withUse(block) + } + } } diff --git a/mirai-core/src/commonMain/kotlin/utils/retryWithServers.kt b/mirai-core/src/commonMain/kotlin/utils/retryWithServers.kt new file mode 100644 index 000000000..0e0be66e4 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/utils/retryWithServers.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 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.utils + +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.math.roundToInt + + +internal suspend inline fun Collection>.retryWithServers( + timeoutMillis: Long, + onFail: (exception: Throwable?) -> Nothing, + crossinline block: suspend (ip: String, port: Int) -> R +): R { + require(this.isNotEmpty()) { "receiver of retryWithServers must not be empty" } + + var exception: Throwable? = null + for (pair in this) { + return kotlin.runCatching { + withTimeoutOrNull(timeoutMillis) { + block(pair.first.toIpV4AddressString(), pair.second) + } + }.recover { + if (exception != null) { + it.addSuppressed(exception!!) + } + exception = it // so as to show last exception followed by suppressed others + null + }.getOrNull() ?: continue + } + + onFail(exception) +} + +internal fun Int.sizeToString() = this.toLong().sizeToString() +internal fun Long.sizeToString(): String { + return if (this < 1024) { + "$this B" + } else ((this * 100.0 / 1024).roundToInt() / 100.0).toString() + " KiB" +} \ No newline at end of file