mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-13 20:10:09 +08:00
Upgrade to Ktor 2.0.2, use CIO engine for linux targets; Remove ktor-client-okhttp from mirai-core-api jvmBaseMain
Use Input.readAllText instead of Input.readText, because readText only reads one buffer. #2084
This commit is contained in:
parent
6293208c26
commit
4c6b879873
@ -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)
|
||||
|
@ -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")
|
@ -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")
|
@ -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")
|
@ -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")
|
@ -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`)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -56,7 +56,6 @@ kotlin {
|
||||
|
||||
val jvmBaseMain by getting {
|
||||
dependencies {
|
||||
api(`ktor-client-okhttp`)
|
||||
api(`kotlinx-coroutines-jdk8`)
|
||||
implementation(`jetbrains-annotations`)
|
||||
implementation(`log4j-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 {
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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() }
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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]}")
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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>() {
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
}
|
||||
|
@ -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}" }
|
||||
|
Loading…
Reference in New Issue
Block a user