diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 0b8048f00..ce67facff 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -31,7 +31,7 @@ object Versions { const val coroutines = "1.6.2" const val atomicFU = "0.17.2" const val serialization = "1.3.2" - const val ktor = "1.6.8" + const val ktor = "2.0.2" const val binaryValidator = "0.4.0" @@ -110,7 +110,7 @@ val `ktor-client-core` = ktor("client-core", Versions.ktor) val `ktor-client-cio` = ktor("client-cio", Versions.ktor) val `ktor-client-mock` = ktor("client-mock", Versions.ktor) val `ktor-client-curl` = ktor("client-curl", Versions.ktor) -val `ktor-client-ios` = ktor("client-ios", Versions.ktor) +val `ktor-client-darwin` = ktor("client-darwin", Versions.ktor) val `ktor-client-okhttp` = ktor("client-okhttp", Versions.ktor) val `ktor-client-android` = ktor("client-android", Versions.ktor) val `ktor-client-logging` = ktor("client-logging", Versions.ktor) diff --git a/logging/mirai-logging-log4j2/build.gradle.kts b/logging/mirai-logging-log4j2/build.gradle.kts index 6fa225ab4..2f71c7b35 100644 --- a/logging/mirai-logging-log4j2/build.gradle.kts +++ b/logging/mirai-logging-log4j2/build.gradle.kts @@ -1,10 +1,10 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:Suppress("UnusedImport") @@ -32,6 +32,7 @@ dependencies { testImplementation(`slf4j-api`) testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) + testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-log4j2") \ No newline at end of file diff --git a/logging/mirai-logging-slf4j-logback/build.gradle.kts b/logging/mirai-logging-slf4j-logback/build.gradle.kts index 8b3023311..78f520fab 100644 --- a/logging/mirai-logging-slf4j-logback/build.gradle.kts +++ b/logging/mirai-logging-slf4j-logback/build.gradle.kts @@ -1,10 +1,10 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:Suppress("UnusedImport") @@ -33,6 +33,7 @@ dependencies { testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) + testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-slf4j-logback") \ No newline at end of file diff --git a/logging/mirai-logging-slf4j-simple/build.gradle.kts b/logging/mirai-logging-slf4j-simple/build.gradle.kts index 8015c1070..7eabbc7ee 100644 --- a/logging/mirai-logging-slf4j-simple/build.gradle.kts +++ b/logging/mirai-logging-slf4j-simple/build.gradle.kts @@ -1,10 +1,10 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:Suppress("UnusedImport") @@ -33,6 +33,7 @@ dependencies { testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) + testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-slf4j-simple") \ No newline at end of file diff --git a/logging/mirai-logging-slf4j/build.gradle.kts b/logging/mirai-logging-slf4j/build.gradle.kts index 51bc654ba..a06e9681f 100644 --- a/logging/mirai-logging-slf4j/build.gradle.kts +++ b/logging/mirai-logging-slf4j/build.gradle.kts @@ -1,10 +1,10 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:Suppress("UnusedImport") @@ -32,6 +32,7 @@ dependencies { testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) + testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-slf4j") \ No newline at end of file diff --git a/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/build.gradle.kts b/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/build.gradle.kts new file mode 100644 index 000000000..861dbe064 --- /dev/null +++ b/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2022 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/dev/LICENSE + */ + +@file:Suppress("UnusedImport") + +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("java") +} + +version = "0.0.0" + +kotlin { + explicitApiWarning() +} + +dependencies { + api(project(":mirai-console.integration-test")) + compileOnly(`ktor-client-okhttp`) +} diff --git a/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/src/PluginUseConsoleDepsFallback.kt b/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/src/PluginUseConsoleDepsFallback.kt index 394db3041..d65d31c12 100644 --- a/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/src/PluginUseConsoleDepsFallback.kt +++ b/mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/src/PluginUseConsoleDepsFallback.kt @@ -22,7 +22,7 @@ public class PluginUseConsoleDepsFallback : override fun onEnable() { logger.info { "Plugin loaded" } logger.info { - HttpClient(OkHttp).toString() + HttpClient(OkHttp).toString() // dependency is compileOnly } } } \ No newline at end of file diff --git a/mirai-core-api/build.gradle.kts b/mirai-core-api/build.gradle.kts index 6c7aa3964..70e1ebf2e 100644 --- a/mirai-core-api/build.gradle.kts +++ b/mirai-core-api/build.gradle.kts @@ -56,7 +56,6 @@ kotlin { val jvmBaseMain by getting { dependencies { - api(`ktor-client-okhttp`) api(`kotlinx-coroutines-jdk8`) implementation(`jetbrains-annotations`) implementation(`log4j-api`) diff --git a/mirai-core-api/compatibility-validation/android/api/android.api b/mirai-core-api/compatibility-validation/android/api/android.api index 03653bd1b..e402a68a7 100644 --- a/mirai-core-api/compatibility-validation/android/api/android.api +++ b/mirai-core-api/compatibility-validation/android/api/android.api @@ -96,7 +96,6 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve public abstract fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getBotFactory ()Lnet/mamoe/mirai/BotFactory; public abstract fun getFileCacheStrategy ()Lnet/mamoe/mirai/utils/FileCacheStrategy; - public abstract fun getHttp ()Lio/ktor/client/HttpClient; public fun getOnlineOtherClientsList (Lnet/mamoe/mirai/Bot;Z)Ljava/util/List; public abstract fun getOnlineOtherClientsList (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getOnlineOtherClientsList$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/Bot;ZILjava/lang/Object;)Ljava/util/List; @@ -125,7 +124,6 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve public fun sendNudge (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/action/Nudge;Lnet/mamoe/mirai/contact/Contact;)Z public abstract fun sendNudge (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/action/Nudge;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun setFileCacheStrategy (Lnet/mamoe/mirai/utils/FileCacheStrategy;)V - public abstract fun setHttp (Lio/ktor/client/HttpClient;)V } public abstract interface class net/mamoe/mirai/LowLevelApiAccessor { diff --git a/mirai-core-api/compatibility-validation/jvm/api/jvm.api b/mirai-core-api/compatibility-validation/jvm/api/jvm.api index 28ca081ee..ee7ca4faa 100644 --- a/mirai-core-api/compatibility-validation/jvm/api/jvm.api +++ b/mirai-core-api/compatibility-validation/jvm/api/jvm.api @@ -96,7 +96,6 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve public abstract fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getBotFactory ()Lnet/mamoe/mirai/BotFactory; public abstract fun getFileCacheStrategy ()Lnet/mamoe/mirai/utils/FileCacheStrategy; - public abstract fun getHttp ()Lio/ktor/client/HttpClient; public fun getOnlineOtherClientsList (Lnet/mamoe/mirai/Bot;Z)Ljava/util/List; public abstract fun getOnlineOtherClientsList (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getOnlineOtherClientsList$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/Bot;ZILjava/lang/Object;)Ljava/util/List; @@ -125,7 +124,6 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve public fun sendNudge (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/action/Nudge;Lnet/mamoe/mirai/contact/Contact;)Z public abstract fun sendNudge (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/action/Nudge;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun setFileCacheStrategy (Lnet/mamoe/mirai/utils/FileCacheStrategy;)V - public abstract fun setHttp (Lio/ktor/client/HttpClient;)V } public abstract interface class net/mamoe/mirai/LowLevelApiAccessor { diff --git a/mirai-core-api/src/commonMain/kotlin/IMirai.kt b/mirai-core-api/src/commonMain/kotlin/IMirai.kt index 908cd03e2..2d2b6bce3 100644 --- a/mirai-core-api/src/commonMain/kotlin/IMirai.kt +++ b/mirai-core-api/src/commonMain/kotlin/IMirai.kt @@ -92,15 +92,15 @@ public interface IMirai : LowLevelApiAccessor { */ public var FileCacheStrategy: FileCacheStrategy - /** - * Mirai 上传好友图片等使用的 Ktor [HttpClient]. - * 默认使用 [OkHttp] 引擎, 连接超时为 30s. - * - * 覆盖后将会立即应用到全局. - */ - @Deprecated("Mirai is not going to use ktor. This is deprecated for removal.", level = DeprecationLevel.WARNING) - @DeprecatedSinceMirai(warningSince = "2.11.0") - public var Http: HttpClient +// /** +// * Mirai 上传好友图片等使用的 Ktor [HttpClient]. +// * 默认使用 [OkHttp] 引擎, 连接超时为 30s. +// * +// * 覆盖后将会立即应用到全局. +// */ +// @Deprecated("Mirai is not going to use ktor. This is deprecated for removal.", level = DeprecationLevel.WARNING) +// @DeprecatedSinceMirai(warningSince = "2.11.0") +// public var Http: HttpClient /** * 获取 uin. diff --git a/mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.TxCaptchaHelper.kt b/mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.TxCaptchaHelper.kt index c30d80971..5ceaacd3a 100644 --- a/mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.TxCaptchaHelper.kt +++ b/mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.TxCaptchaHelper.kt @@ -11,27 +11,15 @@ package net.mamoe.mirai.utils import io.ktor.client.* import io.ktor.client.request.* +import io.ktor.client.statement.* import kotlinx.coroutines.* -import net.mamoe.mirai.Mirai internal abstract class TxCaptchaHelper { - private val newClient: Boolean - val client: HttpClient + private val newClient: Boolean = true + val client: HttpClient = HttpClient() private lateinit var queue: Job - init { - var newClient = false - client = try { - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - Mirai.Http - } catch (ignore: Throwable) { - newClient = true - HttpClient() - } - this.newClient = newClient - } - - internal var latestDisplay = "Sending request..." + private var latestDisplay = "Sending request..." abstract fun onComplete(ticket: String) abstract fun updateDisplay(msg: String) @@ -42,7 +30,7 @@ internal abstract class TxCaptchaHelper { updateDisplay(latestDisplay) while (isActive) { try { - val response: String = client.get(url0) + val response: String = client.get(url0).bodyAsText() if (response.startsWith("请在")) { if (response != latestDisplay) { latestDisplay = response diff --git a/mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/CommonByteArrayOpTest.kt b/mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/CommonByteArrayOpTest.kt index 8f2c564ab..920788768 100644 --- a/mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/CommonByteArrayOpTest.kt +++ b/mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/CommonByteArrayOpTest.kt @@ -220,7 +220,7 @@ internal open class CommonByteArrayOpTest { val result = "1F 8B 08 00 00 00 00 00 00 FF 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 A8 35 6D D9 0A 00 00 00".hexToBytes() .toReadPacket(release = { released = true }).let { input -> - GzipDecompressionInput(input).readText().also { + GzipDecompressionInput(input).readAllText().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } @@ -278,7 +278,7 @@ internal open class CommonByteArrayOpTest { val result = "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes() .toReadPacket(release = { released = true }).let { input -> - InflateInput(input).readText().also { + InflateInput(input).readAllText().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } diff --git a/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt b/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt index 8064065d7..170284331 100644 --- a/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt +++ b/mirai-core-utils/src/nativeMain/kotlin/ByteArrayOp.kt @@ -153,7 +153,7 @@ internal class ZlibInput( private val zlibHasPending: ((z_streamp) -> Boolean)?, // null lambda means operation not defined private val zlibFlushMode: (shouldFlushAll: Boolean) -> Int, private val zlibEnd: (z_streamp) -> Int, -) : Input { +) : Input() { private val z: z_stream = nativeHeap.alloc() // Zlib manual: https://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/zlib-inflate-1.html @@ -165,123 +165,73 @@ internal class ZlibInput( } } - @Deprecated( - "Not supported anymore. All operations are big endian by default. Use readXXXLittleEndian or readXXX then X.reverseByteOrder() instead.", - level = DeprecationLevel.ERROR - ) - override var byteOrder: ByteOrder - get() = throw UnsupportedOperationException() - set(_) { - throw UnsupportedOperationException() - } - - private var bufferReadableSize = 0 + private var bufferReadableSize = 0L private val inputBuffer = nativeHeap.allocArray<ByteVar>(ZLIB_BUFFER_SIZE) - private val buffer = nativeHeap.allocArray<ByteVar>(ZLIB_BUFFER_SIZE) - private var bufferIndex = 0L - private var closed: Boolean = false - - override val endOfInput: Boolean - get() = closed || !prepare() + private var closed = false override fun close() { - debug { "closing" } if (closed) return - this.closed = true - - source.close() - zlibEnd(z.ptr) - nativeHeap.free(z) - nativeHeap.free(buffer) + closed = true + debug { "close" } + super.close() + debug { "freeing inputBuffer" } nativeHeap.free(inputBuffer) + debug { "freed" } } - override fun discard(n: Long): Long { - if (closed) { - return 0 - } - val old = bufferIndex - if (old >= bufferReadableSize) { - if (prepare()) return discard(n) - return 0 - } - bufferIndex = (bufferIndex + n).coerceAtMost(ZLIB_BUFFER_SIZE) - return bufferIndex - old + override fun closeSource() { + debug { "closeSource" } + source.close() + debug { "zlibEnd" } + zlibEnd(z.ptr) + debug { "zlibEnd done" } } - override fun peekTo(destination: Memory, destinationOffset: Long, offset: Long, min: Long, max: Long): Long { - debug() - debug { "peekTo" } - require(min <= max) { "min > max" } - if (!prepare()) return 0 - val readableLength = (bufferReadableSize - bufferIndex - offset).coerceAtLeast(0) - if (offset > readableLength) { - if (min == 0L) { - throw EOFException("offset($offset) > readableLength($readableLength)") - } - } - if (min > readableLength) return 0 - val len = readableLength.coerceAtMost(max).coerceAtMost(destination.size) - debug { "peekTo: read $len" } - buffer.copyTo(destination, bufferIndex + offset, len, destinationOffset) + override fun fill(destination: Memory, offset: Int, length: Int): Int { + require(offset in 0..destination.size32) { "invalid offset: $offset" } + require(length in 0..destination.size32) { "invalid length: $length" } + require(offset + length in 0..destination.size32) { "invalid offset and length: $offset, $length" } - return len - } - - override fun readByte(): Byte { - if (!prepare()) { - throw EOFException("One more byte required") - } - return buffer[bufferIndex++] - } - - override fun tryPeek(): Int { - if (!prepare()) { - return -1 - } - return buffer[bufferIndex].toIntUnsigned() - } - - private fun prepare(): Boolean { - if (closed) { - return false - } - debug { "prepare: bufferIndex = $bufferIndex, bufferReadableSize = $bufferReadableSize" } - if (bufferIndex < bufferReadableSize) { - debug { "prepare returned, because " } - return true // has buf unused - } - - bufferIndex = 0 + debug { "prepare: bufferReadableSize = $bufferReadableSize" } debug { "prepare: previous value: z.avail_in=${z.avail_in}, z.avail_out=${z.avail_out}" } - if (z.avail_in == 0u) { + val filled = try { + if (z.avail_in == 0u) { - // These two cases are similar. + // These two cases are similar. // if (z.avail_out == 0u) { // // Last time we used all the output, there is either something cached in Zlib, or no further source. // } else { // // We did not use all the inputs, meaning least time we used all avail_in. // } - // bot input and output are used - val flush = updateAvailIn() ?: return false - copyOutputsFromZlib(flush) - } else { - // Inputs not used up. - copyOutputsFromZlib(Z_NO_FLUSH) - } + // bot input and output are used + val flush = updateAvailIn() ?: return 0 + copyOutputsFromZlib(destination, offset, length, flush) + } else { + // Inputs not used up. + copyOutputsFromZlib(destination, offset, length, Z_NO_FLUSH) + } - return true + } catch (e: Throwable) { + // If you throw this error up, ktor will somehow kill the process. (Ktor 2.0.2) + debug { e.printStackTrace(); "" } + return 0 + } + check(filled in 0..length) { "Filled more than $length bytes: $filled" } + check(filled in 0..destination.size) { "Filled more than ${destination.size} bytes: $filled" } + return filled } - private fun copyOutputsFromZlib(flush: Int): Boolean { - z.avail_out = ZLIB_BUFFER_SIZE.toUInt() - z.next_out = buffer.reinterpret() + private fun copyOutputsFromZlib(memory: Memory, offset: Int, length: Int, flush: Int): Int { + debug { "copyOutputsFromZlib, memory.offset = $offset, memory.length=$length, memory.size=${memory.size}" } + + z.avail_out = length.convert() + z.next_out = (memory.pointer + offset)!!.reinterpret() // We still have input, no need to update. - debug { "Set z.avail_out=${z.avail_out}, z.next_out=buffer.reinterpret()" } + debug { "Set z.avail_out=${z.avail_out}, z.next_out=(memory.pointer + offset)!!.reinterpret()" } debug { "Calling zlib, flush = $flush" } val p = zlibProcess(z.ptr, flush) @@ -293,23 +243,23 @@ internal class ZlibInput( Z_NEED_DICT -> error("Zlib failed to process data. (Z_NEED_DICT)") else -> debug { "zlib: $p" } } - bufferReadableSize = (ZLIB_BUFFER_SIZE.toUInt() - z.avail_out).toInt() + val readSize = (length.toUInt() - z.avail_out).toInt() - debug { "Zlib produced bufferReadableSize=$bufferReadableSize bytes" } - debug { "Partial output: ${buffer.readBytes(bufferReadableSize).toUHexString()}" } + debug { "Zlib produced readSize=$readSize bytes" } +// debug { "Partial output: ${memory.readBytes(bufferReadableSize).toUHexString()}" } debug { "Now z.avail_in=${z.avail_in}, z.avail_out=${z.avail_out}" } if (p == Z_FINISH) { debug { "Zlib returned Z_FINISH. Ignoring result check." } - return true + return readSize } if (p == Z_STREAM_END) { debug { "Zlib returned Z_STREAM_END. Ignoring result check." } - return true + return readSize } - if (bufferReadableSize == 0 && (z.avail_in == 0u && source.endOfInput)) { + if (bufferReadableSize == 0L && (z.avail_in == 0u && source.endOfInput)) { if (zlibHasPending?.invoke(z.ptr) == true) { // has pending. So the data must be incomplete. error("Failed to process data, possibly bad data inputted.") @@ -324,7 +274,7 @@ internal class ZlibInput( } // can't read } - return true + return readSize } private fun updateAvailIn(): Int? { @@ -334,6 +284,7 @@ internal class ZlibInput( close() // automatically close return null // no more source available } + bufferReadableSize = read z.avail_in = read.toUInt() val flush = zlibFlushMode(read < ZLIB_BUFFER_SIZE || source.endOfInput) debug { "inputBuffer content: " + inputBuffer.readBytes(read.toInt()).toUHexString() } diff --git a/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt b/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt index 308bd3b86..798037742 100644 --- a/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt +++ b/mirai-core-utils/src/nativeMain/kotlin/MiraiFile.kt @@ -14,7 +14,6 @@ package net.mamoe.mirai.utils import io.ktor.utils.io.bits.* import io.ktor.utils.io.core.* import io.ktor.utils.io.errors.* -import io.ktor.utils.io.streams.* import kotlinx.cinterop.* import platform.posix.* @@ -162,8 +161,7 @@ internal class FileNotFoundException(message: String, cause: Throwable? = null) @Suppress("DEPRECATION") -@OptIn(ExperimentalIoApi::class) -internal class PosixFileInstanceOutput(val file: CPointer<FILE>) : AbstractOutput() { +internal class PosixFileInstanceOutput(val file: CPointer<FILE>) : Output() { private var closed = false override fun flush(source: Memory, offset: Int, length: Int) { @@ -171,7 +169,12 @@ internal class PosixFileInstanceOutput(val file: CPointer<FILE>) : AbstractOutpu var currentOffset = offset while (currentOffset < end) { - val result = fwrite(source, currentOffset, end - currentOffset, file.cast()) + val result = fwrite( + source.pointer + currentOffset.convert(), + sizeOf<ByteVar>().convert(), + (end - currentOffset).convert(), + file.cast() + ).convert<Int>() if (result == 0) { throw PosixException.forErrno(posixFunctionName = "fwrite()").wrapIO() } @@ -190,12 +193,16 @@ internal class PosixFileInstanceOutput(val file: CPointer<FILE>) : AbstractOutpu } @Suppress("DEPRECATION") -@OptIn(ExperimentalIoApi::class) -internal class PosixInputForFile(val file: CPointer<FILE>) : AbstractInput() { +internal class PosixInputForFile(val file: CPointer<FILE>) : Input() { private var closed = false override fun fill(destination: Memory, offset: Int, length: Int): Int { - val size = fread(destination, offset, length, file.cast()) + val size = fread( + destination.pointer + offset.convert(), + sizeOf<ByteVar>().convert(), + length.convert(), + file.cast() + ).toInt() if (size == 0) { if (feof(file) != 0) return 0 throw PosixException.forErrno(posixFunctionName = "read()").wrapIO() @@ -214,6 +221,5 @@ internal class PosixInputForFile(val file: CPointer<FILE>) : AbstractInput() { } } -@OptIn(ExperimentalIoApi::class) public fun PosixException.wrapIO(): IOException = IOException("I/O operation failed due to posix error code $errno", this) diff --git a/mirai-core-utils/src/unixMain/kotlin/MiraiFileImpl.kt b/mirai-core-utils/src/unixMain/kotlin/MiraiFileImpl.kt index 9cfe54a8c..8fdbebe64 100644 --- a/mirai-core-utils/src/unixMain/kotlin/MiraiFileImpl.kt +++ b/mirai-core-utils/src/unixMain/kotlin/MiraiFileImpl.kt @@ -14,7 +14,6 @@ import io.ktor.utils.io.errors.* import kotlinx.cinterop.* import platform.posix.* -@OptIn(ExperimentalIoApi::class) private fun readlink(path: String): String = memScoped { val len = realpath(path, null) if (len != null) { @@ -114,7 +113,6 @@ internal actual class MiraiFileImpl actual constructor( return resolve(parent).resolve(file.name) } - @OptIn(UnsafeNumber::class) override fun createNewFile(): Boolean { memScoped { val fp = fopen(absolutePath, "w") @@ -155,7 +153,6 @@ internal actual class MiraiFileImpl actual constructor( } } - @OptIn(ExperimentalIoApi::class) override fun input(): Input { val handle = fopen(absolutePath, "rb") ?: throw IOException( @@ -165,7 +162,6 @@ internal actual class MiraiFileImpl actual constructor( return PosixInputForFile(handle) } - @OptIn(ExperimentalIoApi::class) override fun output(): Output { val handle = fopen(absolutePath, "wb") ?: throw IOException( diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts index 5fd0f1d5d..794d5069e 100644 --- a/mirai-core/build.gradle.kts +++ b/mirai-core/build.gradle.kts @@ -153,13 +153,13 @@ kotlin { configure(LINUX_TARGETS.map { getByName(it + "Main") }) { dependencies { - implementation(`ktor-client-curl`) + implementation(`ktor-client-cio`) } } val darwinMain by getting { dependencies { - implementation(`ktor-client-ios`) + implementation(`ktor-client-darwin`) } } diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index aed9103cd..264470a8d 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.internal import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.request.forms.* +import io.ktor.client.statement.* import io.ktor.utils.io.core.* import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject @@ -93,8 +94,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { override var FileCacheStrategy: FileCacheStrategy = net.mamoe.mirai.utils.FileCacheStrategy.PlatformDefault - @Deprecated("Mirai is not going to use ktor. This is deprecated for removal.", level = DeprecationLevel.WARNING) - override var Http: HttpClient = createDefaultHttpClient() + @Suppress("PrivatePropertyName") + private val httpClient: HttpClient = createDefaultHttpClient() override suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) { @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @@ -530,8 +531,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { override suspend fun getRawGroupActiveData(bot: Bot, groupId: Long, page: Int): GroupActiveData = bot.asQQAndroidBot().run { val rep = network.run { - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - Mirai.Http.get<String> { + httpClient.get() { url("https://qqweb.qq.com/c/activedata/get_mygroup_data") parameter("bkn", client.wLoginSigInfo.bkn) parameter("gc", groupId) @@ -547,7 +547,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { } } } - return json.decodeFromString(GroupActiveData.serializer(), rep) + return json.decodeFromString(GroupActiveData.serializer(), rep.bodyAsText()) } @LowLevelApi @@ -558,8 +558,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { type: GroupHonorType ): GroupHonorListData? = bot.asQQAndroidBot().run { val rep = network.run { - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - Mirai.Http.get<String> { + httpClient.get { url("https://qun.qq.com/interactive/honorlist") parameter("gc", groupId) parameter("type", type.value) @@ -575,7 +574,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { } } } - val jsonText = Regex("""window.__INITIAL_STATE__=(.+?)</script>""").find(rep)?.groupValues?.get(1) + val jsonText = Regex("""window.__INITIAL_STATE__=(.+?)</script>""").find(rep.bodyAsText())?.groupValues?.get(1) return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) } } @@ -670,16 +669,17 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { seconds: Int ) { bot as QQAndroidBot - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - val response = Mirai.Http.post<String> { + val response = httpClient.post { url("https://qqweb.qq.com/c/anonymoustalk/blacklist") - body = MultiPartFormDataContent(formData { - append("anony_id", anonymousId) - append("group_code", groupId) - append("seconds", seconds) - append("anony_nick", anonymousNick) - append("bkn", bot.client.wLoginSigInfo.bkn) - }) + setBody( + MultiPartFormDataContent(formData { + append("anony_id", anonymousId) + append("group_code", groupId) + append("seconds", seconds) + append("anony_nick", anonymousNick) + append("bkn", bot.client.wLoginSigInfo.bkn) + }) + ) headers { // ktor bug append( @@ -687,7 +687,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { "uin=o${bot.id}; skey=${bot.sKey};" ) } - } + }.bodyAsText() val jsonObj = Json.decodeFromString(JsonObject.serializer(), response) if ((jsonObj["retcode"] ?: jsonObj["cgicode"] ?: error("missing response code")).jsonPrimitive.long != 0L) { throw IllegalStateException(response) @@ -828,8 +828,6 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { bot.asQQAndroidBot() when (val resp = bot.network.sendAndExpect(MultiMsg.ApplyDown(bot.client, 2, resourceId, 1))) { is MultiMsg.ApplyDown.Response.RequireDownload -> { - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - val http = Mirai.Http val origin = resp.origin val data: ByteArray = if (origin.msgExternInfo?.channelType == 2) { @@ -841,16 +839,16 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { resourceKind = resourceKind, channelKind = ChannelKind.HTTP ) { host, _ -> - http.get("$host${origin.thumbDownPara}") - } + httpClient.get("$host${origin.thumbDownPara}") + }.readBytes() } else tryServersDownload( bot = bot, servers = origin.uint32DownIp.zip(origin.uint32DownPort), resourceKind = resourceKind, channelKind = ChannelKind.HTTP ) { ip, port -> - http.get("http://$ip:$port${origin.thumbDownPara}") - } + httpClient.get("http://$ip:$port${origin.thumbDownPara}") + }.readBytes() val body = data.read { check(readByte() == 40.toByte()) { diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index f8615b865..b921edfe3 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -203,6 +203,7 @@ internal open class QQAndroidBot constructor( set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext))) set(HeartbeatProcessor, HeartbeatProcessorImpl()) set(HeartbeatScheduler, TimeBasedHeartbeatSchedulerImpl(networkLogger.subLogger("HeartbeatScheduler"))) + set(HttpClientProvider, HttpClientProviderImpl()) set(KeyRefreshProcessor, KeyRefreshProcessorImpl(networkLogger.subLogger("KeyRefreshProcessor"))) set(ConfigPushProcessor, ConfigPushProcessorImpl(networkLogger.subLogger("ConfigPushProcessor"))) set(BotOfflineEventMonitor, BotOfflineEventMonitorImpl()) diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index 1b35fcc14..37e35bb2c 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.internal.contact -import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Stranger @@ -27,6 +26,7 @@ import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrateg import net.mamoe.mirai.internal.network.component.buildComponentStorage import net.mamoe.mirai.internal.network.components.BdhSession import net.mamoe.mirai.internal.network.components.ClockHolder +import net.mamoe.mirai.internal.network.components.HttpClientProvider 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 @@ -202,7 +202,7 @@ internal sealed class AbstractUser( resourceKind = PRIVATE_IMAGE, channelKind = ChannelKind.HTTP ) { ip, port -> - @Suppress("DEPRECATION", "DEPRECATION_ERROR") Mirai.Http.postImage( + bot.components[HttpClientProvider].getHttpClient().postImage( serverIp = ip, serverPort = port, htcmd = "0x6ff0070", @@ -214,7 +214,7 @@ internal sealed class AbstractUser( } }.recoverCatchingSuppressed { // try upload by http on fallback server - @Suppress("DEPRECATION", "DEPRECATION_ERROR") Mirai.Http.postImage( + bot.components[HttpClientProvider].getHttpClient().postImage( serverIp = "htdata2.qq.com", htcmd = "0x6ff0070", uin = bot.id, diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index 4f8ab87d7..1ea206b66 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -16,7 +16,6 @@ package net.mamoe.mirai.internal.contact import io.ktor.utils.io.core.* import net.mamoe.mirai.LowLevelApi -import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.events.FriendMessagePostSendEvent @@ -27,6 +26,7 @@ import net.mamoe.mirai.internal.contact.roaming.RoamingMessagesImplFriend import net.mamoe.mirai.internal.message.data.OfflineAudioImpl import net.mamoe.mirai.internal.message.protocol.outgoing.FriendMessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy +import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.highway.* import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody @@ -93,6 +93,7 @@ internal class FriendImpl( override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = AudioToSilkService.convert( resource ).useAutoClose { res -> + var audio: OfflineAudioImpl? = null kotlin.runCatching { val resp = Highway.uploadResourceBdh( @@ -133,8 +134,8 @@ internal class FriendImpl( ResourceKind.GROUP_AUDIO, ChannelKind.HTTP ) { ip, port -> - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - Mirai.Http.postPtt(ip, port, res, resp.uKey, resp.fileKey) + bot.components[HttpClientProvider].getHttpClient() + .postPtt(ip, port, res, resp.uKey, resp.fileKey) } audio = OfflineAudioImpl( filename = "${res.md5.toUHexString("")}.amr", diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 3d63afd12..2b7de1258 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -15,7 +15,6 @@ package net.mamoe.mirai.internal.contact import kotlinx.atomicfu.atomic import net.mamoe.mirai.Bot import net.mamoe.mirai.LowLevelApi -import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.contact.file.RemoteFiles @@ -36,6 +35,7 @@ import net.mamoe.mirai.internal.message.image.getImageTypeById import net.mamoe.mirai.internal.message.protocol.outgoing.GroupMessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.components.BdhSession +import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.highway.ChannelKind import net.mamoe.mirai.internal.network.highway.Highway @@ -322,8 +322,8 @@ internal abstract class CommonGroupImpl constructor( GROUP_AUDIO, ChannelKind.HTTP ) { ip, port -> - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey) + bot.components[HttpClientProvider].getHttpClient() + .postPtt(ip, port, resource, resp.uKey, resp.fileKey) } } } diff --git a/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt b/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt index f9d9221c0..50977a8d7 100644 --- a/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt @@ -13,19 +13,17 @@ package net.mamoe.mirai.internal.contact.announcement import io.ktor.client.request.* import io.ktor.client.request.forms.* +import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.util.* import kotlinx.coroutines.flow.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import net.mamoe.mirai.Bot -import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.announcement.* import net.mamoe.mirai.contact.checkBotPermission +import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.QQAndroidBot -import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.OnlineAnnouncementImpl import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.deleteGroupAnnouncement @@ -34,6 +32,8 @@ import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.getRaw import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.sendGroupAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toGroupAnnouncement +import net.mamoe.mirai.internal.network.client +import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.highway.ChannelKind import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.highway.tryServersUpload @@ -154,31 +154,32 @@ internal object AnnouncementProtocol { ) : CheckableResponseA(), JsonStruct suspend fun uploadGroupAnnouncementImage( - bot: Bot, + bot: AbstractBot, resource: ExternalResource - ): AnnouncementImage = bot.asQQAndroidBot().run { - @OptIn(InternalAPI::class) // ktor bug - val resp = Mirai.Http.post<String> { + ): AnnouncementImage = bot.run { + val resp = bot.components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/upload_img") - body = MultiPartFormDataContent(formData { - append("\"bkn\"", client.wLoginSigInfo.bkn) - append("\"source\"", "troopNotice") - append("m", "0") - append( - "\"pic_up\"", - headers = Headers.build { - append(HttpHeaders.ContentType, ContentType.Image.PNG) - append(HttpHeaders.ContentDisposition, "filename=\"temp_uploadFile.png\"") + setBody( + MultiPartFormDataContent(formData { + append("\"bkn\"", client.wLoginSigInfo.bkn) + append("\"source\"", "troopNotice") + append("m", "0") + append( + "\"pic_up\"", + headers = Headers.build { + append(HttpHeaders.ContentType, ContentType.Image.PNG) + append(HttpHeaders.ContentDisposition, "filename=\"temp_uploadFile.png\"") + } + ) { + writeResource(resource) } - ) { - writeResource(resource) - } - }) + }) + ) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) - }.loadSafelyAs(UploadImageResp.serializer()).check() + }.bodyAsText().loadSafelyAs(UploadImageResp.serializer()).check() return resp.id.replace(""", "\"").loadSafelyAs(GroupAnnouncementImage.serializer()).check().toPublic() } @@ -194,7 +195,7 @@ internal object AnnouncementProtocol { announcement: GroupAnnouncement, image: AnnouncementImage?, ): String { - return Mirai.Http.post<String> { + return bot.components[HttpClientProvider].getHttpClient().post { url( "https://web.qun.qq.com/cgi-bin/announce/add_qun_" + if (announcement.type == 20) { "instruction" @@ -202,28 +203,30 @@ internal object AnnouncementProtocol { "notice" } ) - body = MultiPartFormDataContent(formData { - append("qid", groupId) - append("bkn", client.wLoginSigInfo.bkn) - append("text", announcement.msg.text) - append("pinned", announcement.pinned) - image?.let { - append("pic", image.id) - append("imgWidth", image.width) - append("imgHeight", image.height) - } - append( - "settings", - announcement.settings.toJsonString(GroupAnnouncementSettings.serializer()), - ) - append("format", "json") - // append("type", announcement.type.toString()) - }) + setBody( + MultiPartFormDataContent(formData { + append("qid", groupId) + append("bkn", client.wLoginSigInfo.bkn) + append("text", announcement.msg.text) + append("pinned", announcement.pinned) + image?.let { + append("pic", image.id) + append("imgWidth", image.width) + append("imgHeight", image.height) + } + append( + "settings", + announcement.settings.toJsonString(GroupAnnouncementSettings.serializer()), + ) + append("format", "json") + // append("type", announcement.type.toString()) + }) + ) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) - }.loadSafelyAs(SendGroupAnnouncementResp.serializer()).check().fid + }.bodyAsText().loadSafelyAs(SendGroupAnnouncementResp.serializer()).check().fid } suspend fun QQAndroidBot.getRawGroupAnnouncements( @@ -231,20 +234,22 @@ internal object AnnouncementProtocol { page: Int, amount: Int = 10 ): Either<DeserializationFailure, GroupAnnouncementList> { - return Mirai.Http.post<String> { + return bot.components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/list_announce") - body = MultiPartFormDataContent(formData { - append("qid", groupId) - append("bkn", client.wLoginSigInfo.bkn) - append("ft", 23) //好像是一个用来识别应用的参数 - append("s", if (page == 1) 0 else -(page * amount + 1)) // 第一页这里的参数应该是-1 - append("n", amount) - append("ni", if (page == 1) 1 else 0) - append("format", "json") - }) + setBody( + MultiPartFormDataContent(formData { + append("qid", groupId) + append("bkn", client.wLoginSigInfo.bkn) + append("ft", 23) //好像是一个用来识别应用的参数 + append("s", if (page == 1) 0 else -(page * amount + 1)) // 第一页这里的参数应该是-1 + append("n", amount) + append("ni", if (page == 1) 1 else 0) + append("format", "json") + }) + ) cookie("uin", "o$id") cookie("skey", sKey) - }.loadSafelyAs(GroupAnnouncementList.serializer()) + }.bodyAsText().loadSafelyAs(GroupAnnouncementList.serializer()) } @Serializable @@ -254,25 +259,25 @@ internal object AnnouncementProtocol { ) : CheckableResponseA(), JsonStruct suspend fun QQAndroidBot.deleteGroupAnnouncement(groupId: Long, fid: String): Boolean { - Mirai.Http.post<String> { + components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/del_feed") - body = feedBody(groupId, fid) + setBody(feedBody(groupId, fid)) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) - }.loadSafelyAs(DeleteResp.serializer()).check() + }.bodyAsText().loadSafelyAs(DeleteResp.serializer()).check() return true } suspend fun QQAndroidBot.getGroupAnnouncement(groupId: Long, fid: String): GroupAnnouncement { - return Mirai.Http.post<String> { + return bot.components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/get_feed") - body = feedBody(groupId, fid) + setBody(feedBody(groupId, fid)) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) - }.loadAs(GroupAnnouncement.serializer()) + }.bodyAsText().loadAs(GroupAnnouncement.serializer()) } private fun QQAndroidBot.feedBody( diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt index c24450715..7ac4ea657 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt @@ -140,7 +140,7 @@ internal class RichMessageProtocol : MessageProtocol() { { "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) { when (lightApp.data[0].toInt()) { 0 -> lightApp.data.decodeToString(startIndex = 1) - 1 -> lightApp.data.toReadPacket(offset = 1).inflateInput().readText() + 1 -> lightApp.data.toReadPacket(offset = 1).inflateInput().readAllText() else -> error("unknown compression flag=${lightApp.data[0]}") } } @@ -159,7 +159,7 @@ internal class RichMessageProtocol : MessageProtocol() { val content = runWithBugReport("解析 richMsg", { richMsg.template1.toUHexString() }) { when (richMsg.template1[0].toInt()) { 0 -> richMsg.template1.decodeToString(startIndex = 1) - 1 -> richMsg.template1.toReadPacket(offset = 1).inflateInput().readText() + 1 -> richMsg.template1.toReadPacket(offset = 1).inflateInput().readAllText() else -> error("unknown compression flag=${richMsg.template1[0]}") } } diff --git a/mirai-core/src/commonMain/kotlin/network/components/EcdhInitialPublicKeyUpdater.kt b/mirai-core/src/commonMain/kotlin/network/components/EcdhInitialPublicKeyUpdater.kt index 3a19ddd66..cea70d255 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/EcdhInitialPublicKeyUpdater.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/EcdhInitialPublicKeyUpdater.kt @@ -10,11 +10,11 @@ package net.mamoe.mirai.internal.network.components import io.ktor.client.request.* +import io.ktor.client.statement.* import kotlinx.coroutines.withTimeout import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import net.mamoe.mirai.Mirai import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.utils.crypto.ECDH @@ -89,8 +89,11 @@ internal class EcdhInitialPublicKeyUpdaterImpl( } else { logger.info("ECDH key is invalid, start to fetch ecdh public key from server.") val respStr = - @Suppress("DEPRECATION", "DEPRECATION_ERROR") - withTimeout(10.seconds) { Mirai.Http.get<String>("https://keyrotate.qq.com/rotate_key?cipher_suite_ver=305&uin=${bot.client.uin}") } + withTimeout(10.seconds) { + bot.components[HttpClientProvider].getHttpClient() + .get("https://keyrotate.qq.com/rotate_key?cipher_suite_ver=305&uin=${bot.client.uin}") + .bodyAsText() + } val resp = json.decodeFromString(ServerRespPOJO.serializer(), respStr) resp.pubKeyMeta.let { meta -> val isValid = ECDH.verifyPublicKey( diff --git a/mirai-core/src/commonMain/kotlin/network/components/HttpClientProvider.kt b/mirai-core/src/commonMain/kotlin/network/components/HttpClientProvider.kt new file mode 100644 index 000000000..33119f2a9 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/components/HttpClientProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2022 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/dev/LICENSE + */ + +package net.mamoe.mirai.internal.network.components + +import io.ktor.client.* +import net.mamoe.mirai.internal.createDefaultHttpClient +import net.mamoe.mirai.internal.network.component.ComponentKey + +internal interface HttpClientProvider { + fun getHttpClient(): HttpClient + + companion object : ComponentKey<HttpClientProvider> +} + +internal class HttpClientProviderImpl : HttpClientProvider { + private val instance by lazy { createDefaultHttpClient() } + override fun getHttpClient(): HttpClient = instance +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/highway/Http.kt b/mirai-core/src/commonMain/kotlin/network/highway/Http.kt index 3e9f99bf1..3f46f562a 100644 --- a/mirai-core/src/commonMain/kotlin/network/highway/Http.kt +++ b/mirai-core/src/commonMain/kotlin/network/highway/Http.kt @@ -49,7 +49,7 @@ internal suspend fun HttpClient.postImage( groupcode: Long?, imageInput: ExternalResource, uKeyHex: String, -): Boolean = post<HttpStatusCode> { +): Boolean = post { url { protocol = URLProtocol.HTTP host = serverIp // "htdata2.qq.com" @@ -71,7 +71,7 @@ internal suspend fun HttpClient.postImage( } body = imageInput.consumeAsWriteChannelContent(ContentType.Image.Any) -} == HttpStatusCode.OK +}.status == HttpStatusCode.OK internal suspend fun HttpClient.postPtt( serverIp: String, @@ -80,7 +80,7 @@ internal suspend fun HttpClient.postPtt( uKey: ByteArray, fileKey: ByteArray, ) { - post<String> { + post { url("http://$serverIp:$serverPort") parameter("ver", 4679) parameter("ukey", uKey.toUHexString("")) @@ -89,6 +89,6 @@ internal suspend fun HttpClient.postPtt( parameter("bmd5", resource.md5.toUHexString("")) parameter("mType", "pttDu") parameter("voice_encodec", resource.voiceCodec) - body = resource.consumeAsWriteChannelContent(null) + setBody(resource.consumeAsWriteChannelContent(null)) } } diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsOld.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsOld.kt index b0cfd8595..ed14bb84a 100644 --- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsOld.kt +++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsOld.kt @@ -97,7 +97,6 @@ internal class TarsOld internal constructor( * From: com.qq.taf.Tars.TarsOutputStream */ @Suppress("unused", "MemberVisibilityCanBePrivate") - @OptIn(ExperimentalIoApi::class) private open inner class TarsEncoder( val output: BytePacketBuilder, ) : TaggedEncoder<Int>() { diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt index 5cbca9643..2004dac3a 100644 --- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt +++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt @@ -95,7 +95,7 @@ private fun <T : JceStruct> ByteArray.doLoadAs( } catch (secondFailure: Exception) { throw contextualBugReportException( "解析 " + deserializer.descriptor.serialName, - build.readText(), + build.readAllText(), ExceptionCollector.compressExceptions(originalException, secondFailure) ) } diff --git a/mirai-core/src/commonTest/kotlin/message/ImageReadingTest.kt b/mirai-core/src/commonTest/kotlin/message/ImageReadingTest.kt index 99ac63c0f..a6459e100 100644 --- a/mirai-core/src/commonTest/kotlin/message/ImageReadingTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/ImageReadingTest.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.internal.message +import io.ktor.utils.io.core.EOFException import io.ktor.utils.io.errors.* import net.mamoe.mirai.internal.message.image.calculateImageInfo import net.mamoe.mirai.internal.test.AbstractTest @@ -78,7 +79,7 @@ internal class ImageReadingTest : AbstractTest() { ImageType.JPG ) } - assertFailsWith(IllegalStateException::class) { + assertFailsWith(EOFException::class) { "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF E1 00 5A".testMatch( ImageType.JPG ) diff --git a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt index 3bccfaa34..e1122caf7 100644 --- a/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt +++ b/mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt @@ -76,6 +76,7 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT set(ImagePatcher, TestImagePatcher()) set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot)) set(AccountSecretsManager, MemoryAccountSecretsManager()) + set(HttpClientProvider, HttpClientProviderImpl()) } fun NetworkHandler.assertState(state: NetworkHandler.State) { diff --git a/mirai-core/src/darwinMain/kotlin/MiraiImpl.kt b/mirai-core/src/darwinMain/kotlin/MiraiImpl.kt index e55c4c34d..07f8cd43a 100644 --- a/mirai-core/src/darwinMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/darwinMain/kotlin/MiraiImpl.kt @@ -10,11 +10,11 @@ package net.mamoe.mirai.internal import io.ktor.client.* -import io.ktor.client.engine.ios.* -import io.ktor.client.features.* +import io.ktor.client.engine.darwin.* +import io.ktor.client.plugins.* internal actual fun createDefaultHttpClient(): HttpClient { - return HttpClient(Ios) { + return HttpClient(Darwin) { install(HttpTimeout) { this.requestTimeoutMillis = 30_0000 this.connectTimeoutMillis = 30_0000 diff --git a/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt b/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt index f46c0686b..2679e62d6 100644 --- a/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt @@ -13,7 +13,7 @@ package net.mamoe.mirai.internal import io.ktor.client.* import io.ktor.client.engine.okhttp.* -import io.ktor.client.features.* +import io.ktor.client.plugins.* import kotlinx.atomicfu.atomic import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade diff --git a/mirai-core/src/linuxX64Main/kotlin/MiraiImpl.kt b/mirai-core/src/linuxX64Main/kotlin/MiraiImpl.kt index 16390e369..841be8763 100644 --- a/mirai-core/src/linuxX64Main/kotlin/MiraiImpl.kt +++ b/mirai-core/src/linuxX64Main/kotlin/MiraiImpl.kt @@ -10,11 +10,11 @@ package net.mamoe.mirai.internal import io.ktor.client.* -import io.ktor.client.engine.curl.* -import io.ktor.client.features.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* internal actual fun createDefaultHttpClient(): HttpClient { - return HttpClient(Curl) { + return HttpClient(CIO) { install(HttpTimeout) { this.requestTimeoutMillis = 30_0000 this.connectTimeoutMillis = 30_0000 diff --git a/mirai-core/src/mingwX64Main/kotlin/MiraiImpl.kt b/mirai-core/src/mingwX64Main/kotlin/MiraiImpl.kt index 16390e369..62a9aa7db 100644 --- a/mirai-core/src/mingwX64Main/kotlin/MiraiImpl.kt +++ b/mirai-core/src/mingwX64Main/kotlin/MiraiImpl.kt @@ -11,7 +11,7 @@ package net.mamoe.mirai.internal import io.ktor.client.* import io.ktor.client.engine.curl.* -import io.ktor.client.features.* +import io.ktor.client.plugins.* internal actual fun createDefaultHttpClient(): HttpClient { return HttpClient(Curl) { diff --git a/mirai-core/src/mingwX64Main/kotlin/utils/PlatformSocket.kt b/mirai-core/src/mingwX64Main/kotlin/utils/PlatformSocket.kt index 58542abdb..34873f035 100644 --- a/mirai-core/src/mingwX64Main/kotlin/utils/PlatformSocket.kt +++ b/mirai-core/src/mingwX64Main/kotlin/utils/PlatformSocket.kt @@ -45,14 +45,12 @@ internal actual class PlatformSocket( actual val isOpen: Boolean get() = write(socket, null, 0) != 0 - @OptIn(ExperimentalIoApi::class) actual override fun close() { if (close(socket) != 0) { throw PosixException.forErrno(posixFunctionName = "close()").wrapIO() } } - @OptIn(ExperimentalIoApi::class) actual suspend fun send(packet: ByteArray, offset: Int, length: Int): Unit = readLock.withLock { withContext(dispatcher) { require(offset >= 0) { "offset must >= 0" } @@ -66,10 +64,7 @@ internal actual class PlatformSocket( } } - /** - * @throws SendPacketInternalException - */ - @OptIn(ExperimentalIoApi::class) + actual override suspend fun send(packet: ByteReadPacket): Unit = readLock.withLock { withContext(dispatcher) { val writeBuffer = writeBuffer @@ -93,7 +88,6 @@ internal actual class PlatformSocket( actual companion object { - @OptIn(UnsafeNumber::class, ExperimentalIoApi::class) actual suspend fun connect( serverIp: String, serverPort: Int diff --git a/mirai-core/src/nativeMain/kotlin/network/handler/LengthDelimitedPacketReader.kt b/mirai-core/src/nativeMain/kotlin/network/handler/LengthDelimitedPacketReader.kt index ca1fa3ba3..73b35c5ea 100644 --- a/mirai-core/src/nativeMain/kotlin/network/handler/LengthDelimitedPacketReader.kt +++ b/mirai-core/src/nativeMain/kotlin/network/handler/LengthDelimitedPacketReader.kt @@ -79,7 +79,7 @@ internal class LengthDelimitedPacketReader( else -> { if (missingLength == 0L) { debugLogger.info { "Multiple packets length perfectly matched." } - sendDecode(buildPacket(bufferedParts.sumOf { it.remaining }.toInt()) { + sendDecode(buildPacket { bufferedParts.forEach { writePacket(it) } }) @@ -95,7 +95,7 @@ internal class LengthDelimitedPacketReader( if (combinedLength < 0) return // not enough, still more parts missing. - sendDecode(buildPacket(combinedLength) { + sendDecode(buildPacket { repeat(bufferedParts.size - 1) { i -> writePacket(bufferedParts[i]) } diff --git a/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt b/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt index 8cd91578c..aaa6bfba1 100644 --- a/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt +++ b/mirai-core/src/unixMain/kotlin/utils/PlatformSocket.kt @@ -55,7 +55,6 @@ internal actual class PlatformSocket( writeBuffer.unpin() } - @OptIn(ExperimentalIoApi::class) actual suspend fun send(packet: ByteArray, offset: Int, length: Int): Unit = writeLock.withLock { withContext(sendDispatcher) { require(offset >= 0) { "offset must >= 0" } @@ -72,7 +71,6 @@ internal actual class PlatformSocket( /** * @throws SendPacketInternalException */ - @OptIn(ExperimentalIoApi::class) actual override suspend fun send(packet: ByteReadPacket): Unit = writeLock.withLock { withContext(sendDispatcher) { logger.info { "Native socket sending: len=${packet.remaining}" }