diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt index 07a6b7297..5d63183b4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.async import kotlinx.coroutines.io.ByteReadChannel import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration @@ -417,11 +418,12 @@ internal abstract class QQAndroidBotBase constructor( val group = getGroup(groupCode) val time = currentTimeSeconds + val sequenceId = client.atomicNextMessageSequenceId() message.firstIsInstanceOrNull()?.source?.ensureSequenceIdAvailable() network.run { val data = message.calculateValidationDataForGroup( - sequenceId = client.atomicNextMessageSequenceId(), + sequenceId = sequenceId, time = time.toInt(), random = Random.nextInt().absoluteValue.toUInt(), groupCode = groupCode, @@ -464,16 +466,28 @@ internal abstract class QQAndroidBotBase constructor( ) ).toByteArray(LongMsg.ReqBody.serializer()) - HighwayHelper.uploadImage( - client, - serverIp = response.proto.uint32UpIp!!.first().toIpV4AddressString(), - serverPort = response.proto.uint32UpPort!!.first(), - ticket = response.proto.msgSig, // 104 - imageInput = body.toReadPacket(), - inputSize = body.size, - fileMd5 = MiraiPlatformUtils.md5(body), - commandId = 27 // long msg - ) + val success = response.proto.uint32UpIp.zip(response.proto.uint32UpPort).any { (ip, port) -> + withTimeoutOrNull((body.size * 1000L / 1024 / 10).coerceAtLeast(5000L)) { + network.logger.verbose { "[Highway] Uploading group long message#$sequenceId to ${ip.toIpV4AddressString()}:$port: size=${body.size}" } + HighwayHelper.uploadImage( + client, + serverIp = ip.toIpV4AddressString(), + serverPort = port, + ticket = response.proto.msgSig, // 104 + imageInput = body.toReadPacket(), + inputSize = body.size, + fileMd5 = MiraiPlatformUtils.md5(body), + commandId = 27 // long msg + ) + network.logger.verbose { "[Highway] Uploading group long message#$sequenceId: succeed" } + true + } ?: kotlin.run { + network.logger.verbose { "[Highway] Uploading group long message: timeout, retrying next server" } + false + } + } + + check(success) { "cannot upload group image, failed on all servers." } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt index 6552df793..31bedfc44 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt @@ -389,19 +389,30 @@ internal class GroupImpl( ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } } is ImgStore.GroupPicUp.Response.RequireUpload -> { - // 每 10KB 等 1 秒 - withTimeoutOrNull(image.inputSize * 1000 / 1024 / 10) { - HighwayHelper.uploadImage( - client = bot.client, - serverIp = response.uploadIpList.first().toIpV4AddressString(), - serverPort = response.uploadPortList.first(), - imageInput = image.input, - inputSize = image.inputSize.toInt(), - fileMd5 = image.md5, - ticket = response.uKey, - commandId = 2 - ) - } ?: error("timeout uploading image: ${image.filename}") + // 每 10KB 等 1 秒, 最少等待 5 秒 + val success = response.uploadIpList.zip(response.uploadPortList).any { (ip, port) -> + withTimeoutOrNull((image.inputSize * 1000 / 1024 / 10).coerceAtLeast(5000)) { + bot.network.logger.verbose { "[Highway] Uploading group image to ${ip.toIpV4AddressString()}:$port: size=${image.inputSize / 1024} KiB" } + HighwayHelper.uploadImage( + client = bot.client, + serverIp = ip.toIpV4AddressString(), + serverPort = port, + imageInput = image.input, + inputSize = image.inputSize.toInt(), + fileMd5 = image.md5, + ticket = response.uKey, + commandId = 2 + ) + bot.network.logger.verbose { "[Highway] Uploading group image: succeed" } + true + } ?: kotlin.run { + bot.network.logger.verbose { "[Highway] Uploading group image: timeout, retrying next server" } + false + } + } + + check(success) { "cannot upload group image, failed on all servers." } + val resourceId = image.calculateImageResourceId() // return NotOnlineImageFromFile( // resourceId = resourceId, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt index 4f2f56fe3..3fd2fee80 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt @@ -206,7 +206,8 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation */ override val id: Int - get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.toInt() + get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids?.toInt() + ?: error("在读取 OfflineMessageSourceImplBySourceMsg.id 时找不到 origUids, delegate=${delegate._miraiContentToString()}") // override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val fromId: Long get() = delegate.senderUin diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt index 917242652..563bc2d64 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt @@ -42,7 +42,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans data: Any, dataSize: Int, fileMd5: ByteArray, - sizePerPacket: Int = 8192 + sizePerPacket: Int = 8192.coerceAtMost(ByteArrayPool.BUFFER_SIZE) ): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) require(data is Input || data is InputStream || data is ByteReadChannel) { "unsupported data: ${data::class.simpleName}" } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt index 4dded44fc..7f6f392f9 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MultiMsg.kt @@ -46,8 +46,8 @@ internal class MultiMsg : ProtoBuf { @ProtoId(1) val result: Int = 0, @ProtoId(2) val msgResid: String = "", @ProtoId(3) val msgUkey: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoId(4) val uint32UpIp: List? = null, - @ProtoId(5) val uint32UpPort: List? = null, + @ProtoId(4) val uint32UpIp: List, + @ProtoId(5) val uint32UpPort: List, @ProtoId(6) val blockSize: Long = 0L, @ProtoId(7) val upOffset: Long = 0L, @ProtoId(8) val applyId: Int = 0,