mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-03 07:00:49 +08:00
Highway big data channel (#917)
- Fix retryWithServers exception suppress - ConfigPush BDH session - ConfigPush SSO change server - Upload group PTT silk, #577 - Support upload private image through highway, close #194; - Add fallback strategies for uploading group voice #577 and private image #916 - Upload all resources through highway BDH - Support concurrent uploading - Add BotConfiguration.highwayUploadCoroutineCount close #916, close #577, close #194
This commit is contained in:
parent
8ad0b91974
commit
d8b1505181
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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 使用指定文件存储设备信息
|
||||
|
@ -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 <R> runBIO(
|
||||
|
||||
public suspend inline fun <R> runBIO(
|
||||
noinline block: () -> R
|
||||
): R = runInterruptible(context = Dispatchers.IO, block = block)
|
||||
): 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() }
|
||||
}
|
||||
}
|
@ -20,6 +20,24 @@ public inline fun <reified T> Any?.safeCast(): T? = this as? T
|
||||
|
||||
public inline fun <reified T> 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 <R, T : R> Result<T>.recoverCatchingSuppressed(transform: (exception: Throwable) -> R): Result<R> {
|
||||
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
|
||||
|
@ -68,6 +68,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
|
||||
}
|
||||
|
||||
// region network
|
||||
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
|
||||
|
||||
val network: N get() = _network
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<AbstractUser>()?.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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<ForceAsLongMessage>({ it.safeCast() }) {
|
||||
override val key: MessageKey<ForceAsLongMessage> get() = this
|
||||
|
||||
override fun toString(): String = "ForceLongMessage"
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore on transformation
|
||||
*/
|
||||
internal interface InternalFlagOnlyMessage : SingleMessage
|
@ -243,6 +243,9 @@ internal fun MessageChain.toRichTextElems(
|
||||
is RichMessage // already transformed above
|
||||
-> {
|
||||
|
||||
}
|
||||
is InternalFlagOnlyMessage -> {
|
||||
// ignore
|
||||
}
|
||||
else -> error("unsupported message type: ${currentMessage::class.simpleName}")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Pair<String, Int>> = DefaultServerList.toMutableList()
|
||||
|
||||
val keys: Map<String, ByteArray> 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 <R> 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<String, ByteArray> 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<BdhSession> = 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<String, PSKey>
|
||||
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
|
||||
|
||||
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)"
|
||||
}
|
||||
}
|
||||
internal class BdhSession(
|
||||
val sigSession: ByteArray,
|
||||
val sessionKey: ByteArray,
|
||||
var ssoAddresses: MutableSet<Pair<Int, Int>> = CopyOnWriteArraySet(),
|
||||
var otherAddresses: MutableSet<Pair<Int, Int>> = CopyOnWriteArraySet(),
|
||||
)
|
@ -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<T>(
|
||||
private val input: InputStream,
|
||||
@ -23,14 +29,18 @@ internal class ChunkedFlowSession<T>(
|
||||
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<T> = flow { useAll { emit(it) } } // 'single thread' producer
|
||||
}
|
@ -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<HttpStatusCode> {
|
||||
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<Pair<Int, Int>>,
|
||||
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<Pair<Int, Int>>,
|
||||
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<String> {
|
||||
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 <reified R> tryServers(
|
||||
bot: QQAndroidBot,
|
||||
servers: Collection<Pair<Int, Int>>,
|
||||
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<ByteReadPacket>.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 <T> Flow<T>.produceIn0(coroutineScope: CoroutineScope): ReceiveChannel<T> {
|
||||
return kotlin.runCatching {
|
||||
@OptIn(FlowPreview::class)
|
||||
produceIn(coroutineScope) // this is experimental api
|
||||
}.getOrElse {
|
||||
// fallback strategy in case binary changes.
|
||||
|
||||
val channel = Channel<T>()
|
||||
coroutineScope.launch(CoroutineName("Flow collector")) {
|
||||
collect {
|
||||
channel.send(it)
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
channel
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun ChunkedFlowSession<ByteReadPacket>.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 <E : Any> ReceiveChannel<E>.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<ByteReadPacket> {
|
||||
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<Pair<Int, Int>>.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"
|
||||
}
|
92
mirai-core/src/commonMain/kotlin/network/highway/Http.kt
Normal file
92
mirai-core/src/commonMain/kotlin/network/highway/Http.kt
Normal file
@ -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<HttpStatusCode> {
|
||||
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<String> {
|
||||
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)
|
||||
}
|
||||
}
|
264
mirai-core/src/commonMain/kotlin/network/keys.kt
Normal file
264
mirai-core/src/commonMain/kotlin/network/keys.kt
Normal file
@ -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<String, PSKey>
|
||||
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
|
||||
|
||||
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 <R> 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
|
||||
)
|
@ -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<FileStorageServerListInfo>? = listOf(),
|
||||
@TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo>? = listOf(),
|
||||
@TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
|
||||
@TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
|
||||
@TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
|
||||
internal class FileStoragePushFSSvcList(
|
||||
@TarsId(0) @JvmField val vUpLoadList: List<FileStorageServerListInfo> = emptyList(),
|
||||
@TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo> = emptyList(),
|
||||
@TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo> = emptyList(),
|
||||
@TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo> = emptyList(),
|
||||
@TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo> = emptyList(),
|
||||
@TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null,
|
||||
@TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo>? = null,
|
||||
@TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo>? = null,
|
||||
@TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo> = emptyList(),
|
||||
@TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo> = 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
|
||||
|
@ -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<QQVoiceResult> = emptyList()
|
||||
@JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(2) val int32Retcode: Int = 0,
|
||||
@JvmField @ProtoNumber(3) val msgResult: List<QQVoiceResult> = 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<VideoInfo> = emptyList(),
|
||||
@ProtoNumber(8) @JvmField val msgDropVideoinfo: List<VideoInfo> = 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<VideoInfo> = emptyList(),
|
||||
@JvmField @ProtoNumber(8) val msgDropVideoinfo: List<VideoInfo> = 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<DataHole> = emptyList(),
|
||||
@ProtoNumber(3) @JvmField val boolCompFlag: Boolean = false
|
||||
@JvmField @ProtoNumber(1) val result: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val dataHole: List<DataHole> = 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<HwEndPointPB> = emptyList()
|
||||
@JvmField @ProtoNumber(1) val key: String = "",
|
||||
@JvmField @ProtoNumber(2) val endPointList: List<HwEndPointPB> = emptyList()
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class HwConfigPB(
|
||||
@ProtoNumber(1) @JvmField val configItemList: List<HwConfigItemPB> = emptyList(),
|
||||
@ProtoNumber(2) @JvmField val netSegConfList: List<HwNetSegConfPB> = emptyList(),
|
||||
@ProtoNumber(3) @JvmField val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(),
|
||||
@ProtoNumber(4) @JvmField val configItemListIp6: List<HwConfigItemPB> = emptyList()
|
||||
@JvmField @ProtoNumber(1) val configItemList: List<HwConfigItemPB> = emptyList(),
|
||||
@JvmField @ProtoNumber(2) val netSegConfList: List<HwNetSegConfPB> = emptyList(),
|
||||
@JvmField @ProtoNumber(3) val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(),
|
||||
@JvmField @ProtoNumber(4) val configItemListIp6: List<HwConfigItemPB> = 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<Int> = 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<Int> = 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<SrvAddrs> = 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<SrvAddrs> = 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<Int, Int> = 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<NetSegConf> = emptyList(),
|
||||
@ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false
|
||||
@JvmField @ProtoNumber(1) val channelType: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = 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<NetSegConf> = 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<NetSegConf> = 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<IpAddr> = emptyList(),
|
||||
@ProtoNumber(3) @JvmField val fragmentSize: Int = 0,
|
||||
@ProtoNumber(4) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(),
|
||||
@ProtoNumber(5) @JvmField val msgAddrsV6: List<Ip6Addr> = emptyList()
|
||||
internal data class SrvAddrs(
|
||||
@JvmField @ProtoNumber(1) val serviceType: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val msgAddrs: List<IpAddr> = emptyList(),
|
||||
@JvmField @ProtoNumber(3) val fragmentSize: Int = 0,
|
||||
@JvmField @ProtoNumber(4) val msgNetsegconf: List<NetSegConf> = emptyList(),
|
||||
@JvmField @ProtoNumber(5) val msgAddrsV6: List<Ip6Addr> = emptyList()
|
||||
) : ProtoBuf
|
||||
}
|
||||
}
|
||||
|
@ -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.*
|
||||
|
@ -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<Response> {
|
||||
val pack = createTryUpPttPack(uin, groupCode, resource)
|
||||
return buildOutgoingUniPacket(client) {
|
||||
writeProtoBuf(Cmd0x388.ReqBody.serializer(), pack)
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -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 <R> withConnection(
|
||||
serverIp: String,
|
||||
serverPort: Int,
|
||||
block: PlatformSocket.() -> R
|
||||
): R {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
return connect(serverIp, serverPort).withUse(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
46
mirai-core/src/commonMain/kotlin/utils/retryWithServers.kt
Normal file
46
mirai-core/src/commonMain/kotlin/utils/retryWithServers.kt
Normal file
@ -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 <R> Collection<Pair<Int, Int>>.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"
|
||||
}
|
Loading…
Reference in New Issue
Block a user