|
|
@@ -20,22 +20,24 @@ import io.ktor.utils.io.ByteWriteChannel
|
|
|
import kotlinx.coroutines.InternalCoroutinesApi
|
|
|
import kotlinx.coroutines.delay
|
|
|
import kotlinx.coroutines.flow.collect
|
|
|
-import kotlinx.coroutines.io.ByteReadChannel
|
|
|
import kotlinx.coroutines.isActive
|
|
|
import kotlinx.coroutines.withTimeoutOrNull
|
|
|
-import kotlinx.io.InputStream
|
|
|
-import kotlinx.io.core.Input
|
|
|
import kotlinx.io.core.discardExact
|
|
|
-import kotlinx.io.core.readAvailable
|
|
|
import kotlinx.io.core.use
|
|
|
import kotlinx.serialization.InternalSerializationApi
|
|
|
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
|
|
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
|
|
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
|
|
|
-import net.mamoe.mirai.qqandroid.utils.*
|
|
|
+import net.mamoe.mirai.qqandroid.utils.PlatformSocket
|
|
|
+import net.mamoe.mirai.qqandroid.utils.SocketException
|
|
|
+import net.mamoe.mirai.qqandroid.utils.addSuppressedMirai
|
|
|
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
|
|
|
import net.mamoe.mirai.qqandroid.utils.io.withUse
|
|
|
-import net.mamoe.mirai.utils.*
|
|
|
+import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
|
|
|
+import net.mamoe.mirai.utils.ExternalImage
|
|
|
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
|
|
+import net.mamoe.mirai.utils.MiraiInternalAPI
|
|
|
+import net.mamoe.mirai.utils.verbose
|
|
|
import kotlin.coroutines.EmptyCoroutineContext
|
|
|
import kotlin.math.roundToInt
|
|
|
import kotlin.time.ExperimentalTime
|
|
|
@@ -75,26 +77,8 @@ internal suspend fun HttpClient.postImage(
|
|
|
|
|
|
@OptIn(MiraiExperimentalAPI::class)
|
|
|
override suspend fun writeTo(channel: ByteWriteChannel) {
|
|
|
- ByteArrayPool.useInstance { buffer: ByteArray ->
|
|
|
- when (imageInput) {
|
|
|
- is Input -> {
|
|
|
- var size: Int
|
|
|
- while (imageInput.readAvailable(buffer).also { size = it } > 0) {
|
|
|
- channel.writeFully(buffer, 0, size)
|
|
|
- channel.flush()
|
|
|
- }
|
|
|
- }
|
|
|
- is ByteReadChannel -> imageInput.copyAndClose(channel)
|
|
|
- is InputStream -> {
|
|
|
- var size: Int
|
|
|
- while (imageInput.read(buffer).also { size = it } > 0) {
|
|
|
- channel.writeFully(buffer, 0, size)
|
|
|
- channel.flush()
|
|
|
- }
|
|
|
- }
|
|
|
- else -> error("unsupported imageInput: ${imageInput::class.simpleName}")
|
|
|
- }
|
|
|
- }
|
|
|
+ imageInput.writeTo(channel)
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
} == HttpStatusCode.OK
|
|
|
@@ -109,26 +93,26 @@ internal object HighwayHelper {
|
|
|
image: ExternalImage.ReusableInput,
|
|
|
kind: String,
|
|
|
commandId: Int
|
|
|
- ) = uploadImageToServers(bot, servers, uKey, image.md5, image.input(), image.size, kind, commandId)
|
|
|
+ ) = uploadImageToServers(bot, servers, uKey, image.md5, image, kind, commandId)
|
|
|
|
|
|
+ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
|
|
@OptIn(ExperimentalTime::class)
|
|
|
suspend fun uploadImageToServers(
|
|
|
bot: QQAndroidBot,
|
|
|
servers: List<Pair<Int, Int>>,
|
|
|
uKey: ByteArray,
|
|
|
md5: ByteArray,
|
|
|
- input: Any,
|
|
|
- inputSize: Long,
|
|
|
+ input: ExternalImage.ReusableInput,
|
|
|
kind: String,
|
|
|
commandId: Int
|
|
|
) = servers.retryWithServers(
|
|
|
- (inputSize * 1000 / 1024 / 10).coerceAtLeast(5000),
|
|
|
+ (input.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=${inputSize.sizeToString()}"
|
|
|
+ "[Highway] Uploading $kind to ${ip}:$port, size=${input.size.sizeToString()}"
|
|
|
}
|
|
|
|
|
|
val time = measureTime {
|
|
|
@@ -137,7 +121,6 @@ internal object HighwayHelper {
|
|
|
serverIp = ip,
|
|
|
serverPort = port,
|
|
|
imageInput = input,
|
|
|
- inputSize = inputSize,
|
|
|
fileMd5 = md5,
|
|
|
ticket = uKey,
|
|
|
commandId = commandId
|
|
|
@@ -145,22 +128,21 @@ internal object HighwayHelper {
|
|
|
}
|
|
|
|
|
|
bot.network.logger.verbose {
|
|
|
- "[Highway] Uploading $kind: succeed at ${(inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
|
|
+ "[Highway] Uploading $kind: succeed at ${(input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
|
|
@OptIn(InternalCoroutinesApi::class)
|
|
|
internal suspend fun uploadImage(
|
|
|
client: QQAndroidClient,
|
|
|
serverIp: String,
|
|
|
serverPort: Int,
|
|
|
ticket: ByteArray,
|
|
|
- imageInput: Any,
|
|
|
- inputSize: Long,
|
|
|
+ imageInput: ExternalImage.ReusableInput,
|
|
|
fileMd5: ByteArray,
|
|
|
commandId: Int // group=2, friend=1
|
|
|
) {
|
|
|
- require(imageInput is Input || imageInput is InputStream || imageInput is ByteReadChannel) { "unsupported imageInput: ${imageInput::class.simpleName}" }
|
|
|
require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
|
|
|
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
|
|
|
// require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
|
|
|
@@ -181,18 +163,19 @@ internal object HighwayHelper {
|
|
|
commandId = commandId,
|
|
|
ticket = ticket,
|
|
|
data = imageInput,
|
|
|
- dataSize = inputSize,
|
|
|
fileMd5 = fileMd5
|
|
|
- ).collect {
|
|
|
- socket.send(it)
|
|
|
- //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
|
|
|
-
|
|
|
- socket.read().withUse {
|
|
|
- discardExact(1)
|
|
|
- val headLength = readInt()
|
|
|
- discardExact(4)
|
|
|
- val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
|
|
|
- check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
|
|
|
+ ).withUse {
|
|
|
+ flow.collect {
|
|
|
+ socket.send(it)
|
|
|
+ //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
|
|
|
+
|
|
|
+ socket.read().withUse {
|
|
|
+ discardExact(1)
|
|
|
+ val headLength = readInt()
|
|
|
+ discardExact(4)
|
|
|
+ val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
|
|
|
+ check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|