mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-03 23:22:29 +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 getFirstReconnectDelayMillis ()J
|
||||||
public final fun getHeartbeatPeriodMillis ()J
|
public final fun getHeartbeatPeriodMillis ()J
|
||||||
public final fun getHeartbeatTimeoutMillis ()J
|
public final fun getHeartbeatTimeoutMillis ()J
|
||||||
|
public final fun getHighwayUploadCoroutineCount ()I
|
||||||
public final fun getJson ()Lkotlinx/serialization/json/Json;
|
public final fun getJson ()Lkotlinx/serialization/json/Json;
|
||||||
public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver;
|
public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver;
|
||||||
public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1;
|
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 setFirstReconnectDelayMillis (J)V
|
||||||
public final fun setHeartbeatPeriodMillis (J)V
|
public final fun setHeartbeatPeriodMillis (J)V
|
||||||
public final fun setHeartbeatTimeoutMillis (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 setJson (Lkotlinx/serialization/json/Json;)V
|
||||||
public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V
|
public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V
|
||||||
public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
|
public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
import org.gradle.api.attributes.Attribute
|
import org.gradle.api.attributes.Attribute
|
||||||
|
|
||||||
object Versions {
|
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 kotlinCompiler = "1.4.21"
|
||||||
const val kotlinStdlib = "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
|
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 使用指定文件存储设备信息
|
* @see fileBasedDeviceInfo 使用指定文件存储设备信息
|
||||||
|
@ -12,10 +12,11 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import kotlinx.coroutines.withContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
@Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DeprecatedCallableAddReplaceWith")
|
@Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DeprecatedCallableAddReplaceWith")
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@ -31,3 +32,13 @@ public suspend inline fun <R> runBIO(
|
|||||||
public suspend inline fun <R> runBIO(
|
public suspend inline fun <R> runBIO(
|
||||||
noinline block: () -> R
|
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
|
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")
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
|
||||||
@kotlin.internal.InlineOnly
|
@kotlin.internal.InlineOnly
|
||||||
@kotlin.internal.LowPriorityInOverloadResolution
|
@kotlin.internal.LowPriorityInOverloadResolution
|
||||||
|
@ -68,6 +68,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// region network
|
// region network
|
||||||
|
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
|
||||||
|
|
||||||
val network: N get() = _network
|
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.contact.*
|
||||||
import net.mamoe.mirai.internal.message.*
|
import net.mamoe.mirai.internal.message.*
|
||||||
import net.mamoe.mirai.internal.network.highway.Highway
|
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.jce.SvcDevLoginInfo
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
|
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
|
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
|
||||||
@ -745,19 +746,20 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
|||||||
)
|
)
|
||||||
).toByteArray(LongMsg.ReqBody.serializer())
|
).toByteArray(LongMsg.ReqBody.serializer())
|
||||||
|
|
||||||
Highway.uploadResource(
|
body.toExternalResource().use { resource ->
|
||||||
bot,
|
Highway.uploadResourceBdh(
|
||||||
response.proto.uint32UpIp.zip(response.proto.uint32UpPort),
|
bot = bot,
|
||||||
response.proto.msgSig,
|
resource = resource,
|
||||||
body.toExternalResource(null),
|
kind = when (isLong) {
|
||||||
when (isLong) {
|
true -> ResourceKind.GROUP_LONG_MESSAGE
|
||||||
true -> "group long message"
|
false -> ResourceKind.GROUP_FORWARD_MESSAGE
|
||||||
false -> "group forward message"
|
|
||||||
},
|
},
|
||||||
27
|
commandId = 27,
|
||||||
|
initialTicket = response.proto.msgSig
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return resId
|
return resId
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.message.*
|
|||||||
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
|
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
|
||||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
|
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.message.data.*
|
||||||
import net.mamoe.mirai.network.LoginFailedException
|
import net.mamoe.mirai.network.LoginFailedException
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
@ -63,6 +64,8 @@ internal class QQAndroidBot constructor(
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val bot: QQAndroidBot get() = this
|
||||||
|
|
||||||
internal var firstLoginSucceed: Boolean = false
|
internal var firstLoginSucceed: Boolean = false
|
||||||
|
|
||||||
inline val json get() = configuration.json
|
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.event.events.ImageUploadEvent
|
||||||
import net.mamoe.mirai.internal.message.OfflineFriendImage
|
import net.mamoe.mirai.internal.message.OfflineFriendImage
|
||||||
import net.mamoe.mirai.internal.message.getImageType
|
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.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.data.proto.Cmd0x352
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
|
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
|
||||||
import net.mamoe.mirai.message.data.Image
|
import net.mamoe.mirai.message.data.Image
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.time.measureTime
|
|
||||||
|
|
||||||
internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info
|
internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ internal abstract class AbstractUser(
|
|||||||
if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) {
|
if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) {
|
||||||
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
||||||
}
|
}
|
||||||
val response = bot.network.run {
|
val resp = bot.network.run {
|
||||||
LongConn.OffPicUp(
|
LongConn.OffPicUp(
|
||||||
bot.client, Cmd0x352.TryUpImgReq(
|
bot.client, Cmd0x352.TryUpImgReq(
|
||||||
srcUin = bot.id.toInt(),
|
srcUin = bot.id.toInt(),
|
||||||
@ -73,56 +74,64 @@ internal abstract class AbstractUser(
|
|||||||
is Member -> "temp"
|
is Member -> "temp"
|
||||||
else -> "unknown"
|
else -> "unknown"
|
||||||
}
|
}
|
||||||
return when (response) {
|
return when (resp) {
|
||||||
is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(
|
is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(
|
||||||
imageId = generateImageIdFromResourceId(
|
imageId = generateImageIdFromResourceId(
|
||||||
resourceId = response.resourceId,
|
resourceId = resp.resourceId,
|
||||||
format = getImageType(response.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME }
|
format = getImageType(resp.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME }
|
||||||
?: resource.formatName
|
?: resource.formatName
|
||||||
) ?: response.resourceId
|
) ?: resp.resourceId
|
||||||
).also {
|
).also {
|
||||||
ImageUploadEvent.Succeed(this, resource, it).broadcast()
|
ImageUploadEvent.Succeed(this, resource, it).broadcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
is LongConn.OffPicUp.Response.RequireUpload -> {
|
is LongConn.OffPicUp.Response.RequireUpload -> {
|
||||||
bot.network.logger.verbose {
|
|
||||||
"[Http] Uploading $kind image, size=${resource.size.sizeToString()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
val time = measureTime {
|
kotlin.runCatching {
|
||||||
|
Highway.uploadResourceBdh(
|
||||||
|
bot = bot,
|
||||||
|
resource = resource,
|
||||||
|
kind = PRIVATE_IMAGE,
|
||||||
|
commandId = 1,
|
||||||
|
initialTicket = resp.uKey
|
||||||
|
)
|
||||||
|
}.recoverCatchingSuppressed {
|
||||||
|
tryServers(
|
||||||
|
bot = bot,
|
||||||
|
servers = resp.serverIp.zip(resp.serverPort),
|
||||||
|
resourceSize = resource.size,
|
||||||
|
resourceKind = PRIVATE_IMAGE,
|
||||||
|
channelKind = ChannelKind.HTTP
|
||||||
|
) { ip, port ->
|
||||||
Mirai.Http.postImage(
|
Mirai.Http.postImage(
|
||||||
"0x6ff0070",
|
serverIp = ip, serverPort = port,
|
||||||
bot.id,
|
htcmd = "0x6ff0070",
|
||||||
null,
|
uin = bot.id,
|
||||||
|
groupcode = null,
|
||||||
imageInput = resource,
|
imageInput = resource,
|
||||||
uKeyHex = response.uKey.toUHexString("")
|
uKeyHex = resp.uKey.toUHexString("")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}.recoverCatchingSuppressed {
|
||||||
bot.network.logger.verbose {
|
Mirai.Http.postImage(
|
||||||
"[Http] Uploading $kind image: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
serverIp = "htdata2.qq.com",
|
||||||
}
|
htcmd = "0x6ff0070",
|
||||||
|
uin = bot.id,
|
||||||
/*
|
groupcode = null,
|
||||||
HighwayHelper.uploadImageToServers(
|
imageInput = resource,
|
||||||
bot,
|
uKeyHex = resp.uKey.toUHexString("")
|
||||||
response.serverIp.zip(response.serverPort),
|
)
|
||||||
response.uKey,
|
}.getOrThrow()
|
||||||
image,
|
|
||||||
kind = "friend",
|
|
||||||
commandId = 1
|
|
||||||
)*/
|
|
||||||
// 为什么不能 ??
|
|
||||||
|
|
||||||
OfflineFriendImage(
|
OfflineFriendImage(
|
||||||
generateImageIdFromResourceId(response.resourceId, resource.formatName) ?: response.resourceId
|
generateImageIdFromResourceId(resp.resourceId, resource.formatName) ?: resp.resourceId
|
||||||
).also {
|
).also {
|
||||||
ImageUploadEvent.Succeed(this, resource, it).broadcast()
|
ImageUploadEvent.Succeed(this, resource, it).broadcast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LongConn.OffPicUp.Response.Failed -> {
|
is LongConn.OffPicUp.Response.Failed -> {
|
||||||
ImageUploadEvent.Failed(this, resource, -1, response.message).broadcast()
|
ImageUploadEvent.Failed(this, resource, -1, resp.message).broadcast()
|
||||||
error(response.message)
|
error(resp.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package net.mamoe.mirai.internal.contact
|
package net.mamoe.mirai.internal.contact
|
||||||
|
|
||||||
import net.mamoe.mirai.LowLevelApi
|
import net.mamoe.mirai.LowLevelApi
|
||||||
|
import net.mamoe.mirai.Mirai
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.data.GroupInfo
|
import net.mamoe.mirai.data.GroupInfo
|
||||||
import net.mamoe.mirai.data.MemberInfo
|
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.QQAndroidBot
|
||||||
import net.mamoe.mirai.internal.message.*
|
import net.mamoe.mirai.internal.message.*
|
||||||
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
|
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.image.ImgStore
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
|
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.chat.voice.voiceCodec
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
|
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
|
||||||
import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
|
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.MessageReceipt
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
@ -152,16 +157,16 @@ internal class GroupImpl(
|
|||||||
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
|
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
|
||||||
}
|
}
|
||||||
is ImgStore.GroupPicUp.Response.RequireUpload -> {
|
is ImgStore.GroupPicUp.Response.RequireUpload -> {
|
||||||
Highway.uploadResource(
|
// val servers = response.uploadIpList.zip(response.uploadPortList)
|
||||||
bot,
|
Highway.uploadResourceBdh(
|
||||||
response.uploadIpList.zip(response.uploadPortList),
|
bot = bot,
|
||||||
response.uKey,
|
resource = resource,
|
||||||
resource,
|
kind = GROUP_IMAGE,
|
||||||
kind = "group image",
|
commandId = 2,
|
||||||
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() }
|
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,20 +174,36 @@ internal class GroupImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun uploadVoice(resource: ExternalResource): Voice {
|
override suspend fun uploadVoice(resource: ExternalResource): Voice {
|
||||||
if (resource.size > 1048576) {
|
|
||||||
throw OverFileSizeMaxException()
|
|
||||||
}
|
|
||||||
return bot.network.run {
|
return bot.network.run {
|
||||||
val response: PttStore.GroupPttUp.Response.RequireUpload =
|
kotlin.runCatching {
|
||||||
PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()
|
val (_) = Highway.uploadResourceBdh(
|
||||||
|
bot = bot,
|
||||||
Highway.uploadPttToServers(
|
resource = resource,
|
||||||
bot,
|
kind = GROUP_VOICE,
|
||||||
response.uploadIpList.zip(response.uploadPortList),
|
commandId = 29,
|
||||||
resource,
|
extendInfo = PttStore.GroupPttUp.createTryUpPttPack(bot.id, id, resource)
|
||||||
response.uKey,
|
.toByteArray(Cmd0x388.ReqBody.serializer()),
|
||||||
response.fileKey,
|
|
||||||
)
|
)
|
||||||
|
}.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")
|
||||||
|
|
||||||
Voice(
|
Voice(
|
||||||
"${resource.md5.toUHexString("")}.amr",
|
"${resource.md5.toUHexString("")}.amr",
|
||||||
resource.md5,
|
resource.md5,
|
||||||
|
@ -213,21 +213,27 @@ private suspend fun MessageChain.convertToLongMessageIfNeeded(
|
|||||||
step: GroupMessageSendingStep,
|
step: GroupMessageSendingStep,
|
||||||
groupImpl: GroupImpl,
|
groupImpl: GroupImpl,
|
||||||
): MessageChain {
|
): MessageChain {
|
||||||
return when (step) {
|
suspend fun sendLongImpl(): MessageChain {
|
||||||
GroupMessageSendingStep.FIRST -> {
|
|
||||||
// 只需要在第一次发送的时候验证长度
|
|
||||||
// 后续重试直接跳过
|
|
||||||
verityLength(this, groupImpl)
|
|
||||||
this
|
|
||||||
}
|
|
||||||
GroupMessageSendingStep.LONG_MESSAGE -> {
|
|
||||||
val resId = groupImpl.uploadGroupLongMessageHighway(this)
|
val resId = groupImpl.uploadGroupLongMessageHighway(this)
|
||||||
this + RichMessage.longMessage(
|
return this + RichMessage.longMessage(
|
||||||
brief = takeContent(27),
|
brief = takeContent(27),
|
||||||
resId = resId,
|
resId = resId,
|
||||||
timeSeconds = currentTimeSeconds()
|
timeSeconds = currentTimeSeconds()
|
||||||
) // LongMessageInternal replaces all contents and preserves metadata
|
) // LongMessageInternal replaces all contents and preserves metadata
|
||||||
}
|
}
|
||||||
|
return when (step) {
|
||||||
|
GroupMessageSendingStep.FIRST -> {
|
||||||
|
// 只需要在第一次发送的时候验证长度
|
||||||
|
// 后续重试直接跳过
|
||||||
|
if (contains(ForceAsLongMessage)) {
|
||||||
|
sendLongImpl()
|
||||||
|
}
|
||||||
|
verityLength(this, groupImpl)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
GroupMessageSendingStep.LONG_MESSAGE -> {
|
||||||
|
sendLongImpl()
|
||||||
|
}
|
||||||
GroupMessageSendingStep.FRAGMENTED -> this
|
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 RichMessage // already transformed above
|
||||||
-> {
|
-> {
|
||||||
|
|
||||||
|
}
|
||||||
|
is InternalFlagOnlyMessage -> {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
else -> error("unsupported message type: ${currentMessage::class.simpleName}")
|
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")) {
|
this@QQAndroidBotNetworkHandler.launch(CoroutineName("Awaiting ConfigPushSvc.PushReq")) {
|
||||||
logger.info { "Awaiting ConfigPushSvc.PushReq." }
|
logger.info { "Awaiting ConfigPushSvc.PushReq." }
|
||||||
when (val resp: ConfigPushSvc.PushReq.PushReqResponse? = nextEventOrNull(10_000)) {
|
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 -> {
|
is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
|
||||||
logger.info { "ConfigPushSvc.PushReq: Success." }
|
logger.info { "ConfigPushSvc.PushReq: Success." }
|
||||||
}
|
}
|
||||||
is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
|
is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
|
||||||
bot.logger.info { "Server requires reconnect." }
|
bot.logger.info { "Server requires reconnect." }
|
||||||
logger.debug { "ChangeServer.unknown = ${resp.unknown}." }
|
|
||||||
bot.logger.info { "Server list: ${resp.serverList.joinToString()}." }
|
bot.logger.info { "Server list: ${resp.serverList.joinToString()}." }
|
||||||
|
|
||||||
|
if (resp.serverList.isNotEmpty()) {
|
||||||
|
bot.serverList.clear()
|
||||||
resp.serverList.forEach {
|
resp.serverList.forEach {
|
||||||
bot.client.serverList.add(it.host to it.port)
|
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.AtomicBoolean
|
||||||
import kotlinx.atomicfu.AtomicInt
|
import kotlinx.atomicfu.AtomicInt
|
||||||
import kotlinx.atomicfu.atomic
|
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.data.OnlineStatus
|
||||||
import net.mamoe.mirai.internal.BotAccount
|
import net.mamoe.mirai.internal.BotAccount
|
||||||
import net.mamoe.mirai.internal.QQAndroidBot
|
import net.mamoe.mirai.internal.QQAndroidBot
|
||||||
import net.mamoe.mirai.internal.network.protocol.SyncingCacheList
|
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.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.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.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 net.mamoe.mirai.utils.*
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress)
|
internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress)
|
||||||
@ -82,36 +82,8 @@ internal open class QQAndroidClient(
|
|||||||
get() = protocol.id
|
get() = protocol.id
|
||||||
|
|
||||||
internal var strangerSeq: Int = 0
|
internal var strangerSeq: Int = 0
|
||||||
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
|
|
||||||
|
|
||||||
val keys: Map<String, ByteArray> by lazy {
|
val keys: Map<String, ByteArray> by lazy { 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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')"
|
|
||||||
}
|
|
||||||
|
|
||||||
var onlineStatus: OnlineStatus = OnlineStatus.ONLINE
|
var onlineStatus: OnlineStatus = OnlineStatus.ONLINE
|
||||||
|
|
||||||
@ -126,33 +98,7 @@ internal open class QQAndroidClient(
|
|||||||
|
|
||||||
private val _ssoSequenceId: AtomicInt = atomic(85600)
|
private val _ssoSequenceId: AtomicInt = atomic(85600)
|
||||||
|
|
||||||
lateinit var fileStoragePushFSSvcList: FileStoragePushFSSvcListFuckKotlin
|
var fileStoragePushFSSvcList: FileStoragePushFSSvcList? = null
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket")
|
@MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket")
|
||||||
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
|
internal fun nextSsoSequenceId() = _ssoSequenceId.addAndGet(2)
|
||||||
@ -318,7 +264,6 @@ internal open class QQAndroidClient(
|
|||||||
lateinit var wFastLoginInfo: WFastLoginInfo
|
lateinit var wFastLoginInfo: WFastLoginInfo
|
||||||
var reserveUinInfo: ReserveUinInfo? = null
|
var reserveUinInfo: ReserveUinInfo? = null
|
||||||
lateinit var wLoginSigInfo: WLoginSigInfo
|
lateinit var wLoginSigInfo: WLoginSigInfo
|
||||||
var tlv113: ByteArray? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* from tlvMap119
|
* from tlvMap119
|
||||||
@ -331,193 +276,17 @@ internal open class QQAndroidClient(
|
|||||||
var transportSequenceId = 1
|
var transportSequenceId = 1
|
||||||
|
|
||||||
lateinit var t104: ByteArray
|
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]
|
@JvmField
|
||||||
val G: ByteArray, // sigInfo[2]
|
val bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
|
||||||
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 BdhSession(
|
||||||
internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
|
val sigSession: ByteArray,
|
||||||
KeyWithExpiry(data, creationTime, expireTime)
|
val sessionKey: ByteArray,
|
||||||
|
var ssoAddresses: MutableSet<Pair<Int, Int>> = CopyOnWriteArraySet(),
|
||||||
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) :
|
var otherAddresses: MutableSet<Pair<Int, Int>> = CopyOnWriteArraySet(),
|
||||||
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)"
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,10 +9,16 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal.network.highway
|
package net.mamoe.mirai.internal.network.highway
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.io.core.Closeable
|
import kotlinx.io.core.Closeable
|
||||||
import net.mamoe.mirai.utils.runBIO
|
import net.mamoe.mirai.utils.runBIO
|
||||||
|
import net.mamoe.mirai.utils.toLongUnsigned
|
||||||
import net.mamoe.mirai.utils.withUse
|
import net.mamoe.mirai.utils.withUse
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
internal class ChunkedFlowSession<T>(
|
internal class ChunkedFlowSession<T>(
|
||||||
private val input: InputStream,
|
private val input: InputStream,
|
||||||
@ -23,14 +29,18 @@ internal class ChunkedFlowSession<T>(
|
|||||||
input.close()
|
input.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var offset = 0L
|
private var offset = AtomicLong(0L)
|
||||||
|
|
||||||
internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) = withUse {
|
internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) {
|
||||||
|
contract { callsInPlace(block, InvocationKind.UNKNOWN) }
|
||||||
|
withUse {
|
||||||
while (true) {
|
while (true) {
|
||||||
val size = runBIO { input.read(buffer) }
|
val size = runBIO { input.read(buffer) }
|
||||||
if (size == -1) return
|
if (size == -1) return
|
||||||
block(mapper(buffer, size, offset))
|
block(mapper(buffer, size, offset.getAndAdd(size.toLongUnsigned())))
|
||||||
offset += size
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal suspend fun asFlow(): Flow<T> = flow { useAll { emit(it) } } // 'single thread' producer
|
||||||
|
}
|
@ -9,146 +9,143 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal.network.highway
|
package net.mamoe.mirai.internal.network.highway
|
||||||
|
|
||||||
import io.ktor.client.*
|
import kotlinx.coroutines.*
|
||||||
import io.ktor.client.request.*
|
import kotlinx.coroutines.channels.Channel
|
||||||
import io.ktor.http.*
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import io.ktor.http.content.*
|
import kotlinx.coroutines.channels.receiveOrNull
|
||||||
import io.ktor.utils.io.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
import io.ktor.utils.io.jvm.javaio.*
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.flow.produceIn
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.io.core.ByteReadPacket
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.io.core.buildPacket
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.discardExact
|
||||||
import net.mamoe.mirai.Mirai
|
import kotlinx.io.core.writeFully
|
||||||
import net.mamoe.mirai.internal.QQAndroidBot
|
import net.mamoe.mirai.internal.QQAndroidBot
|
||||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.CSDataHighwayHead
|
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.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.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.readProtoBuf
|
||||||
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
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 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.math.roundToInt
|
||||||
import kotlin.time.measureTime
|
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 {
|
internal object Highway {
|
||||||
|
@Suppress("ArrayInDataClass")
|
||||||
suspend fun uploadResource(
|
data class BdhUploadResponse(
|
||||||
bot: QQAndroidBot,
|
var extendInfo: ByteArray? = null,
|
||||||
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()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
val time = measureTime {
|
|
||||||
uploadResourceImpl(
|
|
||||||
client = bot.client,
|
|
||||||
serverIp = ip,
|
|
||||||
serverPort = port,
|
|
||||||
resource = resource,
|
|
||||||
fileMd5 = resource.md5,
|
|
||||||
ticket = uKey,
|
|
||||||
commandId = commandId
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
bot.network.logger.verbose {
|
suspend fun uploadResourceBdh(
|
||||||
"[Highway] Uploading $kind: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
bot: QQAndroidBot,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun uploadResourceImpl(
|
|
||||||
client: QQAndroidClient,
|
|
||||||
serverIp: String,
|
|
||||||
serverPort: Int,
|
|
||||||
ticket: ByteArray,
|
|
||||||
resource: ExternalResource,
|
resource: ExternalResource,
|
||||||
fileMd5: ByteArray,
|
kind: ResourceKind,
|
||||||
commandId: Int // group=2, friend=1
|
commandId: Int, // group image=2, friend image=1, groupPtt=29
|
||||||
) {
|
extendInfo: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
|
encrypt: Boolean = false,
|
||||||
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
|
initialTicket: ByteArray? = null,
|
||||||
// require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
|
): BdhUploadResponse {
|
||||||
|
val bdhSession = bot.client.bdhSession.await() // no need to care about timeout. proceed by bot init
|
||||||
|
|
||||||
val socket = PlatformSocket()
|
return tryServers(bot, bdhSession.ssoAddresses, resource.size, kind, ChannelKind.HIGHWAY) { ip, port ->
|
||||||
while (client.bot.network.areYouOk() && client.bot.isActive) {
|
val md5 = resource.md5
|
||||||
try {
|
require(md5.size == 16) { "bad md5. Required size=16, got ${md5.size}" }
|
||||||
socket.connect(serverIp, serverPort)
|
|
||||||
break
|
val resp = BdhUploadResponse()
|
||||||
} catch (e: SocketException) {
|
highwayPacketSession(
|
||||||
delay(3000)
|
client = bot.client,
|
||||||
}
|
appId = bot.client.subAppId.toInt(),
|
||||||
}
|
|
||||||
socket.use {
|
|
||||||
createImageDataPacketSequence(
|
|
||||||
client = client,
|
|
||||||
appId = client.subAppId.toInt(),
|
|
||||||
command = "PicUp.DataUp",
|
command = "PicUp.DataUp",
|
||||||
commandId = commandId,
|
commandId = commandId,
|
||||||
ticket = ticket,
|
initialTicket = initialTicket ?: bdhSession.sigSession,
|
||||||
data = resource,
|
data = resource,
|
||||||
fileMd5 = fileMd5
|
fileMd5 = md5,
|
||||||
).useAll {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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"),
|
||||||
|
|
||||||
|
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)
|
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
|
//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
|
||||||
|
|
||||||
@ -158,58 +155,78 @@ internal object Highway {
|
|||||||
discardExact(4)
|
discardExact(4)
|
||||||
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
|
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
|
||||||
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadPttToServers(
|
private suspend fun <E : Any> ReceiveChannel<E>.tryReceive(): E? {
|
||||||
bot: QQAndroidBot,
|
return kotlin.runCatching {
|
||||||
servers: List<Pair<Int, Int>>,
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
resource: ExternalResource,
|
receiveOrNull() // this is experimental api
|
||||||
uKey: ByteArray,
|
}.recoverCatching {
|
||||||
fileKey: ByteArray,
|
// in case binary changes
|
||||||
) {
|
receive()
|
||||||
servers.retryWithServers(10 * 1000, {
|
}.getOrNull()
|
||||||
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 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)
|
||||||
private suspend fun uploadPttToServer(
|
val headLength = readInt()
|
||||||
serverIp: String,
|
discardExact(4)
|
||||||
serverPort: Int,
|
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
|
||||||
resource: ExternalResource,
|
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
|
||||||
uKey: ByteArray,
|
return proto
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun highwayPacketSession(
|
||||||
internal fun createImageDataPacketSequence(
|
|
||||||
// RequestDataTrans
|
// RequestDataTrans
|
||||||
client: QQAndroidClient,
|
client: QQAndroidClient,
|
||||||
command: String,
|
command: String,
|
||||||
@ -217,14 +234,17 @@ internal fun createImageDataPacketSequence(
|
|||||||
dataFlag: Int = 4096,
|
dataFlag: Int = 4096,
|
||||||
commandId: Int,
|
commandId: Int,
|
||||||
localId: Int = 2052,
|
localId: Int = 2052,
|
||||||
ticket: ByteArray,
|
initialTicket: ByteArray,
|
||||||
data: ExternalResource,
|
data: ExternalResource,
|
||||||
fileMd5: ByteArray,
|
fileMd5: ByteArray,
|
||||||
sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE
|
sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE,
|
||||||
|
extendInfo: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
): ChunkedFlowSession<ByteReadPacket> {
|
): ChunkedFlowSession<ByteReadPacket> {
|
||||||
ByteArrayPool.checkBufferSize(sizePerPacket)
|
ByteArrayPool.checkBufferSize(sizePerPacket)
|
||||||
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
|
// 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 ->
|
return ChunkedFlowSession(data.inputStream(), ByteArray(sizePerPacket)) { buffer, size, offset ->
|
||||||
val head = CSDataHighwayHead.ReqDataHighwayHead(
|
val head = CSDataHighwayHead.ReqDataHighwayHead(
|
||||||
msgBasehead = CSDataHighwayHead.DataHighwayHead(
|
msgBasehead = CSDataHighwayHead.DataHighwayHead(
|
||||||
@ -235,6 +255,7 @@ internal fun createImageDataPacketSequence(
|
|||||||
2 -> client.nextHighwayDataTransSequenceIdForGroup()
|
2 -> client.nextHighwayDataTransSequenceIdForGroup()
|
||||||
1 -> client.nextHighwayDataTransSequenceIdForFriend()
|
1 -> client.nextHighwayDataTransSequenceIdForFriend()
|
||||||
27 -> client.nextHighwayDataTransSequenceIdForApplyUp()
|
27 -> client.nextHighwayDataTransSequenceIdForApplyUp()
|
||||||
|
29 -> client.nextHighwayDataTransSequenceIdForGroup()
|
||||||
else -> error("illegal commandId: $commandId")
|
else -> error("illegal commandId: $commandId")
|
||||||
},
|
},
|
||||||
retryTimes = 0,
|
retryTimes = 0,
|
||||||
@ -248,13 +269,13 @@ internal fun createImageDataPacketSequence(
|
|||||||
datalength = size,
|
datalength = size,
|
||||||
dataoffset = offset,
|
dataoffset = offset,
|
||||||
filesize = data.size,
|
filesize = data.size,
|
||||||
serviceticket = ticket,
|
serviceticket = ticket.get(),
|
||||||
md5 = buffer.md5(0, size),
|
md5 = buffer.md5(0, size),
|
||||||
fileMd5 = fileMd5,
|
fileMd5 = fileMd5,
|
||||||
flag = 0,
|
flag = 0,
|
||||||
rtcode = 0
|
rtcode = 0
|
||||||
),
|
),
|
||||||
reqExtendinfo = EMPTY_BYTE_ARRAY,
|
reqExtendinfo = extendInfo,
|
||||||
msgLoginSigHead = null
|
msgLoginSigHead = null
|
||||||
).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer())
|
).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 许可证的约束, 可以在以下链接找到该许可证.
|
* 此源代码的使用受 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.
|
* 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 = ""
|
@TarsId(9) @JvmField val field1306: String = ""
|
||||||
) : JceStruct
|
) : JceStruct
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v8.5.5
|
||||||
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class FileStoragePushFSSvcListFuckKotlin(
|
internal class FileStoragePushFSSvcList(
|
||||||
@TarsId(0) @JvmField val vUpLoadList: List<FileStorageServerListInfo>? = listOf(),
|
@TarsId(0) @JvmField val vUpLoadList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo>? = listOf(),
|
@TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
|
@TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
|
@TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
|
@TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null,
|
@TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null,
|
||||||
@TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo>? = null,
|
@TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo>? = null,
|
@TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo> = emptyList(),
|
||||||
@TarsId(8) @JvmField val fmtIPInfo: FmtIPInfo? = null,
|
@TarsId(8) @JvmField val fmtIPInfo: FmtIPInfo? = null,
|
||||||
@TarsId(9) @JvmField val domainIpChannel: DomainIpChannel? = null,
|
@TarsId(9) @JvmField val domainIpChannel: DomainIpChannel? = null,
|
||||||
@TarsId(10) @JvmField val pttlist: ByteArray? = null
|
@TarsId(10) @JvmField val pttlist: ByteArray? = null,
|
||||||
) : JceStruct
|
) : JceStruct
|
||||||
|
|
||||||
@Serializable
|
@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 许可证的约束, 可以在以下链接找到该许可证.
|
* 此源代码的使用受 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.
|
* 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
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused", "SpellCheckingInspection")
|
||||||
|
|
||||||
package net.mamoe.mirai.internal.network.protocol.data.proto
|
package net.mamoe.mirai.internal.network.protocol.data.proto
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
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.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||||
import net.mamoe.mirai.internal.utils.io.ProtoBuf
|
import net.mamoe.mirai.internal.utils.io.ProtoBuf
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v8.5.5
|
||||||
|
*/
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class BdhExtinfo : ProtoBuf {
|
internal class BdhExtinfo : ProtoBuf {
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class CommFileExtReq(
|
internal class CommFileExtReq(
|
||||||
@ProtoNumber(1) @JvmField val actionType: Int = 0,
|
@JvmField @ProtoNumber(1) val actionType: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val uuid: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class CommFileExtRsp(
|
internal class CommFileExtRsp(
|
||||||
@ProtoNumber(1) @JvmField val int32Retcode: Int = 0,
|
@JvmField @ProtoNumber(1) val int32Retcode: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class PicInfo(
|
internal class PicInfo(
|
||||||
@ProtoNumber(1) @JvmField val idx: Int = 0,
|
@JvmField @ProtoNumber(1) val idx: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val size: Int = 0,
|
@JvmField @ProtoNumber(2) val size: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(4) @JvmField val type: Int = 0
|
@JvmField @ProtoNumber(4) val type: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class QQVoiceExtReq(
|
internal class QQVoiceExtReq(
|
||||||
@ProtoNumber(1) @JvmField val qid: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val fmt: Int = 0,
|
@JvmField @ProtoNumber(2) val fmt: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val rate: Int = 0,
|
@JvmField @ProtoNumber(3) val rate: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val bits: Int = 0,
|
@JvmField @ProtoNumber(4) val bits: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val channel: Int = 0,
|
@JvmField @ProtoNumber(5) val channel: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val pinyin: Int = 0
|
@JvmField @ProtoNumber(6) val pinyin: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class QQVoiceExtRsp(
|
internal class QQVoiceExtRsp(
|
||||||
@ProtoNumber(1) @JvmField val qid: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val int32Retcode: Int = 0,
|
@JvmField @ProtoNumber(2) val int32Retcode: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val msgResult: List<QQVoiceResult> = emptyList()
|
@JvmField @ProtoNumber(3) val msgResult: List<QQVoiceResult> = emptyList()
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class QQVoiceResult(
|
internal class QQVoiceResult(
|
||||||
@ProtoNumber(1) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val text: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val pinyin: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val source: Int = 0
|
@JvmField @ProtoNumber(3) val source: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ShortVideoReqExtInfo(
|
internal class ShortVideoReqExtInfo(
|
||||||
@ProtoNumber(1) @JvmField val cmd: Int = 0,
|
@JvmField @ProtoNumber(1) val cmd: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val sessionId: Long = 0L,
|
@JvmField @ProtoNumber(2) val sessionId: Long = 0L,
|
||||||
@ProtoNumber(3) @JvmField val msgThumbinfo: PicInfo? = null,
|
@JvmField @ProtoNumber(3) val msgThumbinfo: PicInfo? = null,
|
||||||
@ProtoNumber(4) @JvmField val msgVideoinfo: VideoInfo? = null,
|
@JvmField @ProtoNumber(4) val msgVideoinfo: VideoInfo? = null,
|
||||||
@ProtoNumber(5) @JvmField val msgShortvideoSureReq: ShortVideoSureReqInfo? = null,
|
@JvmField @ProtoNumber(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null,
|
||||||
@ProtoNumber(6) @JvmField val boolIsMergeCmdBeforeData: Boolean = false
|
@JvmField @ProtoNumber(6) val boolIsMergeCmdBeforeData: Boolean = false
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ShortVideoRspExtInfo(
|
internal class ShortVideoRspExtInfo(
|
||||||
@ProtoNumber(1) @JvmField val cmd: Int = 0,
|
@JvmField @ProtoNumber(1) val cmd: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val sessionId: Long = 0L,
|
@JvmField @ProtoNumber(2) val sessionId: Long = 0L,
|
||||||
@ProtoNumber(3) @JvmField val int32Retcode: Int = 0,
|
@JvmField @ProtoNumber(3) val int32Retcode: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val errinfo: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(5) @JvmField val msgThumbinfo: PicInfo? = null,
|
@JvmField @ProtoNumber(5) val msgThumbinfo: PicInfo? = null,
|
||||||
@ProtoNumber(6) @JvmField val msgVideoinfo: VideoInfo? = null,
|
@JvmField @ProtoNumber(6) val msgVideoinfo: VideoInfo? = null,
|
||||||
@ProtoNumber(7) @JvmField val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null,
|
@JvmField @ProtoNumber(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null,
|
||||||
@ProtoNumber(8) @JvmField val retryFlag: Int = 0
|
@JvmField @ProtoNumber(8) val retryFlag: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ShortVideoSureReqInfo(
|
internal class ShortVideoSureReqInfo(
|
||||||
@ProtoNumber(1) @JvmField val fromuin: Long = 0L,
|
@JvmField @ProtoNumber(1) val fromuin: Long = 0L,
|
||||||
@ProtoNumber(2) @JvmField val chatType: Int = 0,
|
@JvmField @ProtoNumber(2) val chatType: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val touin: Long = 0L,
|
@JvmField @ProtoNumber(3) val touin: Long = 0L,
|
||||||
@ProtoNumber(4) @JvmField val groupCode: Long = 0L,
|
@JvmField @ProtoNumber(4) val groupCode: Long = 0L,
|
||||||
@ProtoNumber(5) @JvmField val clientType: Int = 0,
|
@JvmField @ProtoNumber(5) val clientType: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val msgThumbinfo: PicInfo? = null,
|
@JvmField @ProtoNumber(6) val msgThumbinfo: PicInfo? = null,
|
||||||
@ProtoNumber(7) @JvmField val msgMergeVideoinfo: List<VideoInfo> = emptyList(),
|
@JvmField @ProtoNumber(7) val msgMergeVideoinfo: List<VideoInfo> = emptyList(),
|
||||||
@ProtoNumber(8) @JvmField val msgDropVideoinfo: List<VideoInfo> = emptyList(),
|
@JvmField @ProtoNumber(8) val msgDropVideoinfo: List<VideoInfo> = emptyList(),
|
||||||
@ProtoNumber(9) @JvmField val businessType: Int = 0,
|
@JvmField @ProtoNumber(9) val businessType: Int = 0,
|
||||||
@ProtoNumber(10) @JvmField val subBusinessType: Int = 0
|
@JvmField @ProtoNumber(10) val subBusinessType: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ShortVideoSureRspInfo(
|
internal class ShortVideoSureRspInfo(
|
||||||
@ProtoNumber(1) @JvmField val fileid: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val ukey: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val msgVideoinfo: VideoInfo? = null,
|
@JvmField @ProtoNumber(3) val msgVideoinfo: VideoInfo? = null,
|
||||||
@ProtoNumber(4) @JvmField val mergeCost: Int = 0
|
@JvmField @ProtoNumber(4) val mergeCost: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -111,31 +118,31 @@ internal class BdhExtinfo : ProtoBuf {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class StoryVideoExtRsp(
|
internal class StoryVideoExtRsp(
|
||||||
@ProtoNumber(1) @JvmField val int32Retcode: Int = 0,
|
@JvmField @ProtoNumber(1) val int32Retcode: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val msg: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val msg: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(4) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(5) @JvmField val fileId: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class UploadPicExtInfo(
|
internal class UploadPicExtInfo(
|
||||||
@ProtoNumber(1) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class VideoInfo(
|
internal class VideoInfo(
|
||||||
@ProtoNumber(1) @JvmField val idx: Int = 0,
|
@JvmField @ProtoNumber(1) val idx: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val size: Int = 0,
|
@JvmField @ProtoNumber(2) val size: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(4) @JvmField val format: Int = 0,
|
@JvmField @ProtoNumber(4) val format: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val resLen: Int = 0,
|
@JvmField @ProtoNumber(5) val resLen: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val resWidth: Int = 0,
|
@JvmField @ProtoNumber(6) val resWidth: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val time: Int = 0,
|
@JvmField @ProtoNumber(7) val time: Int = 0,
|
||||||
@ProtoNumber(8) @JvmField val starttime: Long = 0L,
|
@JvmField @ProtoNumber(8) val starttime: Long = 0L,
|
||||||
@ProtoNumber(9) @JvmField val isAudio: Int = 0
|
@JvmField @ProtoNumber(9) val isAudio: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,142 +150,143 @@ internal class BdhExtinfo : ProtoBuf {
|
|||||||
internal class CSDataHighwayHead : ProtoBuf {
|
internal class CSDataHighwayHead : ProtoBuf {
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class C2CCommonExtendinfo(
|
internal class C2CCommonExtendinfo(
|
||||||
@ProtoNumber(1) @JvmField val infoId: Int = 0,
|
@JvmField @ProtoNumber(1) val infoId: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val msgFilterExtendinfo: FilterExtendinfo? = null
|
@JvmField @ProtoNumber(2) val msgFilterExtendinfo: FilterExtendinfo? = null
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class DataHighwayHead(
|
internal class DataHighwayHead(
|
||||||
@ProtoNumber(1) @JvmField val version: Int = 0,
|
@JvmField @ProtoNumber(1) val version: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val uin: String = "", // yes
|
@JvmField @ProtoNumber(2) val uin: String = "",
|
||||||
@ProtoNumber(3) @JvmField val command: String = "",
|
@JvmField @ProtoNumber(3) val command: String = "",
|
||||||
@ProtoNumber(4) @JvmField val seq: Int = 0,
|
@JvmField @ProtoNumber(4) val seq: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val retryTimes: Int = 0,
|
@JvmField @ProtoNumber(5) val retryTimes: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val appid: Int = 0,
|
@JvmField @ProtoNumber(6) val appid: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val dataflag: Int = 0,
|
@JvmField @ProtoNumber(7) val dataflag: Int = 0,
|
||||||
@ProtoNumber(8) @JvmField val commandId: Int = 0,
|
@JvmField @ProtoNumber(8) val commandId: Int = 0,
|
||||||
@ProtoNumber(9) @JvmField val buildVer: String = "",
|
@JvmField @ProtoNumber(9) val buildVer: String = "",
|
||||||
@ProtoNumber(10) @JvmField val localeId: Int = 0
|
@JvmField @ProtoNumber(10) val localeId: Int = 0,
|
||||||
|
@JvmField @ProtoNumber(11) val envId: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class DataHole(
|
internal class DataHole(
|
||||||
@ProtoNumber(1) @JvmField val begin: Long = 0L,
|
@JvmField @ProtoNumber(1) val begin: Long = 0L,
|
||||||
@ProtoNumber(2) @JvmField val end: Long = 0L
|
@JvmField @ProtoNumber(2) val end: Long = 0L
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class FilterExtendinfo(
|
internal class FilterExtendinfo(
|
||||||
@ProtoNumber(1) @JvmField val filterFlag: Int = 0,
|
@JvmField @ProtoNumber(1) val filterFlag: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val msgImageFilterRequest: ImageFilterRequest? = null
|
@JvmField @ProtoNumber(2) val msgImageFilterRequest: ImageFilterRequest? = null
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class FilterStyle(
|
internal class FilterStyle(
|
||||||
@ProtoNumber(1) @JvmField val styleId: Int = 0,
|
@JvmField @ProtoNumber(1) val styleId: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val styleName: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ImageFilterRequest(
|
internal class ImageFilterRequest(
|
||||||
@ProtoNumber(1) @JvmField val sessionId: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val clientIp: Int = 0,
|
@JvmField @ProtoNumber(2) val clientIp: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val uin: Long = 0L,
|
@JvmField @ProtoNumber(3) val uin: Long = 0L,
|
||||||
@ProtoNumber(4) @JvmField val style: FilterStyle? = null,
|
@JvmField @ProtoNumber(4) val style: FilterStyle? = null,
|
||||||
@ProtoNumber(5) @JvmField val width: Int = 0,
|
@JvmField @ProtoNumber(5) val width: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val height: Int = 0,
|
@JvmField @ProtoNumber(6) val height: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val imageData: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ImageFilterResponse(
|
internal class ImageFilterResponse(
|
||||||
@ProtoNumber(1) @JvmField val retCode: Int = 0,
|
@JvmField @ProtoNumber(1) val retCode: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val imageData: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val costTime: Int = 0
|
@JvmField @ProtoNumber(3) val costTime: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class LoginSigHead(
|
internal class LoginSigHead(
|
||||||
@ProtoNumber(1) @JvmField val loginsigType: Int = 0,
|
@JvmField @ProtoNumber(1) val loginsigType: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val loginsig: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class NewServiceTicket(
|
internal class NewServiceTicket(
|
||||||
@ProtoNumber(1) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val signature: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val ukey: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class PicInfoExt(
|
internal class PicInfoExt(
|
||||||
@ProtoNumber(1) @JvmField val picWidth: Int = 0,
|
@JvmField @ProtoNumber(1) val picWidth: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val picHeight: Int = 0,
|
@JvmField @ProtoNumber(2) val picHeight: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val picFlag: Int = 0,
|
@JvmField @ProtoNumber(3) val picFlag: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val busiType: Int = 0,
|
@JvmField @ProtoNumber(4) val busiType: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val srcTerm: Int = 0,
|
@JvmField @ProtoNumber(5) val srcTerm: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val platType: Int = 0,
|
@JvmField @ProtoNumber(6) val platType: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val netType: Int = 0,
|
@JvmField @ProtoNumber(7) val netType: Int = 0,
|
||||||
@ProtoNumber(8) @JvmField val imgType: Int = 0,
|
@JvmField @ProtoNumber(8) val imgType: Int = 0,
|
||||||
@ProtoNumber(9) @JvmField val appPicType: Int = 0
|
@JvmField @ProtoNumber(9) val appPicType: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class PicRspExtInfo(
|
internal class PicRspExtInfo(
|
||||||
@ProtoNumber(1) @JvmField val skey: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val skey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val clientIp: Int = 0,
|
@JvmField @ProtoNumber(2) val clientIp: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val upOffset: Long = 0L,
|
@JvmField @ProtoNumber(3) val upOffset: Long = 0L,
|
||||||
@ProtoNumber(4) @JvmField val blockSize: Long = 0L
|
@JvmField @ProtoNumber(4) val blockSize: Long = 0L
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class QueryHoleRsp(
|
internal class QueryHoleRsp(
|
||||||
@ProtoNumber(1) @JvmField val result: Int = 0,
|
@JvmField @ProtoNumber(1) val result: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val dataHole: List<DataHole> = emptyList(),
|
@JvmField @ProtoNumber(2) val dataHole: List<DataHole> = emptyList(),
|
||||||
@ProtoNumber(3) @JvmField val boolCompFlag: Boolean = false
|
@JvmField @ProtoNumber(3) val boolCompFlag: Boolean = false
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ReqDataHighwayHead(
|
internal class ReqDataHighwayHead(
|
||||||
@ProtoNumber(1) @JvmField val msgBasehead: DataHighwayHead? = null,
|
@JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null,
|
||||||
@ProtoNumber(2) @JvmField val msgSeghead: SegHead? = null,
|
@JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null,
|
||||||
@ProtoNumber(3) @JvmField val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(4) @JvmField val timestamp: Long = 0L,
|
@JvmField @ProtoNumber(4) val timestamp: Long = 0L,
|
||||||
@ProtoNumber(5) @JvmField val msgLoginSigHead: LoginSigHead? = null
|
@JvmField @ProtoNumber(5) val msgLoginSigHead: LoginSigHead? = null
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class RspBody(
|
internal class RspBody(
|
||||||
@ProtoNumber(1) @JvmField val msgQueryHoleRsp: QueryHoleRsp? = null
|
@JvmField @ProtoNumber(1) val msgQueryHoleRsp: QueryHoleRsp? = null
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class RspDataHighwayHead(
|
internal class RspDataHighwayHead(
|
||||||
@ProtoNumber(1) @JvmField val msgBasehead: DataHighwayHead? = null,
|
@JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null,
|
||||||
@ProtoNumber(2) @JvmField val msgSeghead: SegHead? = null,
|
@JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null,
|
||||||
@ProtoNumber(3) @JvmField val errorCode: Int = 0,
|
@JvmField @ProtoNumber(3) val errorCode: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val allowRetry: Int = 0,
|
@JvmField @ProtoNumber(4) val allowRetry: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val cachecost: Int = 0,
|
@JvmField @ProtoNumber(5) val cachecost: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val htcost: Int = 0,
|
@JvmField @ProtoNumber(6) val htcost: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(8) @JvmField val timestamp: Long = 0L,
|
@JvmField @ProtoNumber(8) val timestamp: Long = 0L,
|
||||||
@ProtoNumber(9) @JvmField val range: Long = 0L,
|
@JvmField @ProtoNumber(9) val range: Long = 0L,
|
||||||
@ProtoNumber(10) @JvmField val isReset: Int = 0
|
@JvmField @ProtoNumber(10) val isReset: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class SegHead(
|
internal class SegHead(
|
||||||
@ProtoNumber(1) @JvmField val serviceid: Int = 0,
|
@JvmField @ProtoNumber(1) val serviceid: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val filesize: Long = 0L,
|
@JvmField @ProtoNumber(2) val filesize: Long = 0L,
|
||||||
@ProtoNumber(3) @JvmField val dataoffset: Long = 0L,
|
@JvmField @ProtoNumber(3) val dataoffset: Long = 0L,
|
||||||
@ProtoNumber(4) @JvmField val datalength: Int = 0,
|
@JvmField @ProtoNumber(4) val datalength: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val rtcode: Int = 0,
|
@JvmField @ProtoNumber(5) val rtcode: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val serviceticket: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(7) @JvmField val flag: Int = 0,
|
@JvmField @ProtoNumber(7) val flag: Int = 0,
|
||||||
@ProtoNumber(8) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(8) val md5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(9) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(10) @JvmField val cacheAddr: Int = 0,
|
@JvmField @ProtoNumber(10) val cacheAddr: Int = 0,
|
||||||
@ProtoNumber(11) @JvmField val queryTimes: Int = 0,
|
@JvmField @ProtoNumber(11) val queryTimes: Int = 0,
|
||||||
@ProtoNumber(12) @JvmField val updateCacheip: Int = 0
|
@JvmField @ProtoNumber(12) val updateCacheip: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,31 +294,31 @@ internal class CSDataHighwayHead : ProtoBuf {
|
|||||||
internal class HwConfigPersistentPB : ProtoBuf {
|
internal class HwConfigPersistentPB : ProtoBuf {
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class HwConfigItemPB(
|
internal class HwConfigItemPB(
|
||||||
@ProtoNumber(1) @JvmField val ingKey: String = "",
|
@JvmField @ProtoNumber(1) val key: String = "",
|
||||||
@ProtoNumber(2) @JvmField val endPointList: List<HwEndPointPB> = emptyList()
|
@JvmField @ProtoNumber(2) val endPointList: List<HwEndPointPB> = emptyList()
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class HwConfigPB(
|
internal class HwConfigPB(
|
||||||
@ProtoNumber(1) @JvmField val configItemList: List<HwConfigItemPB> = emptyList(),
|
@JvmField @ProtoNumber(1) val configItemList: List<HwConfigItemPB> = emptyList(),
|
||||||
@ProtoNumber(2) @JvmField val netSegConfList: List<HwNetSegConfPB> = emptyList(),
|
@JvmField @ProtoNumber(2) val netSegConfList: List<HwNetSegConfPB> = emptyList(),
|
||||||
@ProtoNumber(3) @JvmField val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(),
|
@JvmField @ProtoNumber(3) val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(),
|
||||||
@ProtoNumber(4) @JvmField val configItemListIp6: List<HwConfigItemPB> = emptyList()
|
@JvmField @ProtoNumber(4) val configItemListIp6: List<HwConfigItemPB> = emptyList()
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class HwEndPointPB(
|
internal class HwEndPointPB(
|
||||||
@ProtoNumber(1) @JvmField val ingHost: String = "",
|
@JvmField @ProtoNumber(1) val host: String = "",
|
||||||
@ProtoNumber(2) @JvmField val int32Port: Int = 0,
|
@JvmField @ProtoNumber(2) val int32Port: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val int64Timestampe: Long = 0L
|
@JvmField @ProtoNumber(3) val int64Timestampe: Long = 0L
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class HwNetSegConfPB(
|
internal class HwNetSegConfPB(
|
||||||
@ProtoNumber(1) @JvmField val int64NetType: Long = 0L,
|
@JvmField @ProtoNumber(1) val int64NetType: Long = 0L,
|
||||||
@ProtoNumber(2) @JvmField val int64SegSize: Long = 0L,
|
@JvmField @ProtoNumber(2) val int64SegSize: Long = 0L,
|
||||||
@ProtoNumber(3) @JvmField val int64SegNum: Long = 0L,
|
@JvmField @ProtoNumber(3) val int64SegNum: Long = 0L,
|
||||||
@ProtoNumber(4) @JvmField val int64CurConnNum: Long = 0L
|
@JvmField @ProtoNumber(4) val int64CurConnNum: Long = 0L
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,8 +326,8 @@ internal class HwConfigPersistentPB : ProtoBuf {
|
|||||||
internal class HwSessionInfoPersistentPB : ProtoBuf {
|
internal class HwSessionInfoPersistentPB : ProtoBuf {
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class HwSessionInfoPB(
|
internal class HwSessionInfoPB(
|
||||||
@ProtoNumber(1) @JvmField val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY
|
@JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,137 +335,145 @@ internal class HwSessionInfoPersistentPB : ProtoBuf {
|
|||||||
internal class Subcmd0x501 : ProtoBuf {
|
internal class Subcmd0x501 : ProtoBuf {
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ReqBody(
|
internal class ReqBody(
|
||||||
@ProtoNumber(1281) @JvmField val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null
|
@JvmField @ProtoNumber(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class RspBody(
|
internal class RspBody(
|
||||||
@ProtoNumber(1281) @JvmField val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null
|
@JvmField @ProtoNumber(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class SubCmd0x501ReqBody(
|
internal class SubCmd0x501ReqBody(
|
||||||
@ProtoNumber(1) @JvmField val uin: Long = 0L,
|
@JvmField @ProtoNumber(1) val uin: Long = 0L,
|
||||||
@ProtoNumber(2) @JvmField val idcId: Int = 0,
|
@JvmField @ProtoNumber(2) val idcId: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val appid: Int = 0,
|
@JvmField @ProtoNumber(3) val appid: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val loginSigType: Int = 0,
|
@JvmField @ProtoNumber(4) val loginSigType: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(6) @JvmField val requestFlag: Int = 0,
|
@JvmField @ProtoNumber(6) val requestFlag: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val uint32ServiceTypes: List<Int> = emptyList(),
|
@JvmField @ProtoNumber(7) val uint32ServiceTypes: List<Int> = emptyList(),
|
||||||
@ProtoNumber(8) @JvmField val bid: Int = 0,
|
@JvmField @ProtoNumber(8) val bid: Int = 0,
|
||||||
@ProtoNumber(9) @JvmField val term: Int = 0,
|
@JvmField @ProtoNumber(9) val term: Int = 0,
|
||||||
@ProtoNumber(10) @JvmField val plat: Int = 0,
|
@JvmField @ProtoNumber(10) val plat: Int = 0,
|
||||||
@ProtoNumber(11) @JvmField val net: Int = 0,
|
@JvmField @ProtoNumber(11) val net: Int = 0,
|
||||||
@ProtoNumber(12) @JvmField val caller: Int = 0
|
@JvmField @ProtoNumber(12) val caller: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class SubCmd0x501Rspbody(
|
internal class SubCmd0x501Rspbody(
|
||||||
@ProtoNumber(1) @JvmField val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(2) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val msgHttpconnAddrs: List<SrvAddrs> = emptyList(),
|
@JvmField @ProtoNumber(3) val msgHttpconnAddrs: List<SrvAddrs> = emptyList(),
|
||||||
@ProtoNumber(4) @JvmField val preConnection: Int = 0,
|
@JvmField @ProtoNumber(4) val preConnection: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val csConn: Int = 0,
|
@JvmField @ProtoNumber(5) val csConn: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val msgIpLearnConf: IpLearnConf? = null,
|
@JvmField @ProtoNumber(6) val msgIpLearnConf: IpLearnConf? = null,
|
||||||
@ProtoNumber(7) @JvmField val msgDynTimeoutConf: DynTimeOutConf? = null,
|
@JvmField @ProtoNumber(7) val msgDynTimeoutConf: DynTimeOutConf? = null,
|
||||||
@ProtoNumber(8) @JvmField val msgOpenUpConf: OpenUpConf? = null,
|
@JvmField @ProtoNumber(8) val msgOpenUpConf: OpenUpConf? = null,
|
||||||
@ProtoNumber(9) @JvmField val msgDownloadEncryptConf: DownloadEncryptConf? = null,
|
@JvmField @ProtoNumber(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null,
|
||||||
@ProtoNumber(10) @JvmField val msgShortVideoConf: ShortVideoConf? = null,
|
@JvmField @ProtoNumber(10) val msgShortVideoConf: ShortVideoConf? = null,
|
||||||
@ProtoNumber(11) @JvmField val msgPtvConf: PTVConf? = 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 {
|
) : ProtoBuf {
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class DownloadEncryptConf(
|
internal class DownloadEncryptConf(
|
||||||
@ProtoNumber(1) @JvmField val boolEnableEncryptRequest: Boolean = false,
|
@JvmField @ProtoNumber(1) val boolEnableEncryptRequest: Boolean = false,
|
||||||
@ProtoNumber(2) @JvmField val boolEnableEncryptedPic: Boolean = false,
|
@JvmField @ProtoNumber(2) val boolEnableEncryptedPic: Boolean = false,
|
||||||
@ProtoNumber(3) @JvmField val ctrlFlag: Int = 0
|
@JvmField @ProtoNumber(3) val ctrlFlag: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class DynTimeOutConf(
|
internal class DynTimeOutConf(
|
||||||
@ProtoNumber(1) @JvmField val tbase2g: Int = 0,
|
@JvmField @ProtoNumber(1) val tbase2g: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val tbase3g: Int = 0,
|
@JvmField @ProtoNumber(2) val tbase3g: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val tbase4g: Int = 0,
|
@JvmField @ProtoNumber(3) val tbase4g: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val tbaseWifi: Int = 0,
|
@JvmField @ProtoNumber(4) val tbaseWifi: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val torg2g: Int = 0,
|
@JvmField @ProtoNumber(5) val torg2g: Int = 0,
|
||||||
@ProtoNumber(6) @JvmField val torg3g: Int = 0,
|
@JvmField @ProtoNumber(6) val torg3g: Int = 0,
|
||||||
@ProtoNumber(7) @JvmField val torg4g: Int = 0,
|
@JvmField @ProtoNumber(7) val torg4g: Int = 0,
|
||||||
@ProtoNumber(8) @JvmField val torgWifi: Int = 0,
|
@JvmField @ProtoNumber(8) val torgWifi: Int = 0,
|
||||||
@ProtoNumber(9) @JvmField val maxTimeout: Int = 0,
|
@JvmField @ProtoNumber(9) val maxTimeout: Int = 0,
|
||||||
@ProtoNumber(10) @JvmField val enableDynTimeout: Int = 0,
|
@JvmField @ProtoNumber(10) val enableDynTimeout: Int = 0,
|
||||||
@ProtoNumber(11) @JvmField val maxTimeout2g: Int = 0,
|
@JvmField @ProtoNumber(11) val maxTimeout2g: Int = 0,
|
||||||
@ProtoNumber(12) @JvmField val maxTimeout3g: Int = 0,
|
@JvmField @ProtoNumber(12) val maxTimeout3g: Int = 0,
|
||||||
@ProtoNumber(13) @JvmField val maxTimeout4g: Int = 0,
|
@JvmField @ProtoNumber(13) val maxTimeout4g: Int = 0,
|
||||||
@ProtoNumber(14) @JvmField val maxTimeoutWifi: Int = 0,
|
@JvmField @ProtoNumber(14) val maxTimeoutWifi: Int = 0,
|
||||||
@ProtoNumber(15) @JvmField val hbTimeout2g: Int = 0,
|
@JvmField @ProtoNumber(15) val hbTimeout2g: Int = 0,
|
||||||
@ProtoNumber(16) @JvmField val hbTimeout3g: Int = 0,
|
@JvmField @ProtoNumber(16) val hbTimeout3g: Int = 0,
|
||||||
@ProtoNumber(17) @JvmField val hbTimeout4g: Int = 0,
|
@JvmField @ProtoNumber(17) val hbTimeout4g: Int = 0,
|
||||||
@ProtoNumber(18) @JvmField val hbTimeoutWifi: Int = 0,
|
@JvmField @ProtoNumber(18) val hbTimeoutWifi: Int = 0,
|
||||||
@ProtoNumber(19) @JvmField val hbTimeoutDefault: Int = 0
|
@JvmField @ProtoNumber(19) val hbTimeoutDefault: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class Ip6Addr(
|
internal class Ip6Addr(
|
||||||
@ProtoNumber(1) @JvmField val type: Int = 0,
|
@JvmField @ProtoNumber(1) val type: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val ip6: ByteArray = EMPTY_BYTE_ARRAY,
|
@JvmField @ProtoNumber(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
@ProtoNumber(3) @JvmField val port: Int = 0,
|
@JvmField @ProtoNumber(3) val port: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val area: Int = 0,
|
@JvmField @ProtoNumber(4) val area: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val sameIsp: Int = 0
|
@JvmField @ProtoNumber(5) val sameIsp: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class IpAddr(
|
internal class IpAddr(
|
||||||
@ProtoNumber(1) @JvmField val type: Int = 0,
|
@JvmField @ProtoNumber(1) val type: Int = 0,
|
||||||
@ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(2) @JvmField val ip: Int = 0,
|
@ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(2) val ip: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val port: Int = 0,
|
@JvmField @ProtoNumber(3) val port: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val area: Int = 0,
|
@JvmField @ProtoNumber(4) val area: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val sameIsp: Int = 0
|
@JvmField @ProtoNumber(5) val sameIsp: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf {
|
||||||
|
fun decode(): Pair<Int, Int> = ip to port
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class IpLearnConf(
|
internal class IpLearnConf(
|
||||||
@ProtoNumber(1) @JvmField val refreshCachedIp: Int = 0,
|
@JvmField @ProtoNumber(1) val refreshCachedIp: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val enableIpLearn: Int = 0
|
@JvmField @ProtoNumber(2) val enableIpLearn: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class NetSegConf(
|
internal class NetSegConf(
|
||||||
@ProtoNumber(1) @JvmField val netType: Int = 0,
|
@JvmField @ProtoNumber(1) val netType: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val segsize: Int = 0,
|
@JvmField @ProtoNumber(2) val segsize: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val segnum: Int = 0,
|
@JvmField @ProtoNumber(3) val segnum: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val curconnnum: Int = 0
|
@JvmField @ProtoNumber(4) val curconnnum: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class OpenUpConf(
|
internal class OpenUpConf(
|
||||||
@ProtoNumber(1) @JvmField val boolEnableOpenup: Boolean = false,
|
@JvmField @ProtoNumber(1) val boolEnableOpenup: Boolean = false,
|
||||||
@ProtoNumber(2) @JvmField val preSendSegnum: Int = 0,
|
@JvmField @ProtoNumber(2) val preSendSegnum: Int = 0,
|
||||||
@ProtoNumber(3) @JvmField val preSendSegnum3g: Int = 0,
|
@JvmField @ProtoNumber(3) val preSendSegnum3g: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val preSendSegnum4g: Int = 0,
|
@JvmField @ProtoNumber(4) val preSendSegnum4g: Int = 0,
|
||||||
@ProtoNumber(5) @JvmField val preSendSegnumWifi: Int = 0
|
@JvmField @ProtoNumber(5) val preSendSegnumWifi: Int = 0
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class PTVConf(
|
internal class PTVConf(
|
||||||
@ProtoNumber(1) @JvmField val channelType: Int = 0,
|
@JvmField @ProtoNumber(1) val channelType: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(),
|
@JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = emptyList(),
|
||||||
@ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false
|
@JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class ShortVideoConf(
|
internal class ShortVideoConf(
|
||||||
@ProtoNumber(1) @JvmField val channelType: Int = 0,
|
@JvmField @ProtoNumber(1) val channelType: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(),
|
@JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = emptyList(),
|
||||||
@ProtoNumber(3) @JvmField val boolOpenHardwareCodec: Boolean = false,
|
@JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false,
|
||||||
@ProtoNumber(4) @JvmField val boolSendAheadSignal: Boolean = false
|
@JvmField @ProtoNumber(4) val boolSendAheadSignal: Boolean = false
|
||||||
) : ProtoBuf
|
) : ProtoBuf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal class SrvAddrs(
|
internal data class SrvAddrs(
|
||||||
@ProtoNumber(1) @JvmField val serviceType: Int = 0,
|
@JvmField @ProtoNumber(1) val serviceType: Int = 0,
|
||||||
@ProtoNumber(2) @JvmField val msgAddrs: List<IpAddr> = emptyList(),
|
@JvmField @ProtoNumber(2) val msgAddrs: List<IpAddr> = emptyList(),
|
||||||
@ProtoNumber(3) @JvmField val fragmentSize: Int = 0,
|
@JvmField @ProtoNumber(3) val fragmentSize: Int = 0,
|
||||||
@ProtoNumber(4) @JvmField val msgNetsegconf: List<NetSegConf> = emptyList(),
|
@JvmField @ProtoNumber(4) val msgNetsegconf: List<NetSegConf> = emptyList(),
|
||||||
@ProtoNumber(5) @JvmField val msgAddrsV6: List<Ip6Addr> = emptyList()
|
@JvmField @ProtoNumber(5) val msgAddrsV6: List<Ip6Addr> = emptyList()
|
||||||
) : ProtoBuf
|
) : 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.login.WtLogin
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
|
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
|
||||||
import net.mamoe.mirai.internal.network.readUShortLVByteArray
|
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.TEA
|
||||||
import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
|
import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
|
||||||
import net.mamoe.mirai.utils.*
|
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 许可证的约束, 可以在以下链接找到该许可证.
|
* 此源代码的使用受 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.
|
* 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.data.proto.Cmd0x388
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
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.OutgoingPacketFactory
|
||||||
|
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
|
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.readProtoBuf
|
||||||
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
|
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
|
||||||
@ -63,14 +64,12 @@ internal class PttStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
fun createTryUpPttPack(
|
||||||
operator fun invoke(
|
|
||||||
client: QQAndroidClient,
|
|
||||||
uin: Long,
|
uin: Long,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
resource: ExternalResource
|
resource: ExternalResource
|
||||||
): OutgoingPacket {
|
): Cmd0x388.ReqBody {
|
||||||
val pack = Cmd0x388.ReqBody(
|
return Cmd0x388.ReqBody(
|
||||||
netType = 3, // wifi
|
netType = 3, // wifi
|
||||||
subcmd = 3,
|
subcmd = 3,
|
||||||
msgTryupPttReq = listOf(
|
msgTryupPttReq = listOf(
|
||||||
@ -87,12 +86,22 @@ internal class PttStore {
|
|||||||
innerIp = 0,
|
innerIp = 0,
|
||||||
buildVer = "6.5.5.663".encodeToByteArray(),
|
buildVer = "6.5.5.663".encodeToByteArray(),
|
||||||
voiceLength = 1,
|
voiceLength = 1,
|
||||||
codec = 0, // don't use resource.codec
|
codec = resource.voiceCodec, // HTTP 时只支持 0
|
||||||
|
// 2021/1/26 因为 #577 修改为 resource.voiceCodec
|
||||||
voiceType = 1,
|
voiceType = 1,
|
||||||
boolNewUpChan = true
|
boolNewUpChan = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(
|
||||||
|
client: QQAndroidClient,
|
||||||
|
uin: Long,
|
||||||
|
groupCode: Long,
|
||||||
|
resource: ExternalResource
|
||||||
|
): OutgoingPacketWithRespType<Response> {
|
||||||
|
val pack = createTryUpPttPack(uin, groupCode, resource)
|
||||||
return buildOutgoingUniPacket(client) {
|
return buildOutgoingUniPacket(client) {
|
||||||
writeProtoBuf(Cmd0x388.ReqBody.serializer(), pack)
|
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.core.Closeable
|
||||||
import kotlinx.io.streams.readPacketAtMost
|
import kotlinx.io.streams.readPacketAtMost
|
||||||
import kotlinx.io.streams.writePacket
|
import kotlinx.io.streams.writePacket
|
||||||
|
import net.mamoe.mirai.utils.withUse
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -23,6 +24,8 @@ import java.net.NoRouteToHostException
|
|||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TCP Socket.
|
* TCP Socket.
|
||||||
@ -97,6 +100,26 @@ internal class PlatformSocket : Closeable {
|
|||||||
writeChannel = socket.getOutputStream().buffered()
|
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