Commit 1b1be694 authored by Him188's avatar Him188

Merge remote-tracking branch 'origin/master'

parents dba3cb57 1b8a87b4
......@@ -2,6 +2,10 @@
开发版本. 频繁更新, 不保证高稳定性
## `0.26.0` 2020/3/7
- 使用 `kotlinx.io` 而不是 `ktor.io`
- 修复 #111, #108, #116, #112
## `0.25.0` 2020/3/6
- 适配 8.2.7 版本(2020 年 3 月)协议
- 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image
......
......@@ -4,12 +4,11 @@ import java.util.*
buildscript {
repositories {
mavenLocal()
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven") }
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter()
mavenCentral()
// mavenCentral()
google()
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
// maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
}
dependencies {
......@@ -41,11 +40,10 @@ allprojects {
repositories {
mavenLocal()
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven") }
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter()
mavenCentral()
// mavenCentral()
google()
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
// maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
}
}
\ No newline at end of file
# style guide
kotlin.code.style=official
# config
miraiVersion=0.25.0
miraiVersion=0.26.0
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin
......
......@@ -11,6 +11,7 @@ plugins {
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
......@@ -66,6 +67,7 @@ kotlin {
api(kotlin("stdlib", kotlinVersion))
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
api(kotlinx("io", kotlinXIoVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
}
......
......@@ -9,10 +9,20 @@
package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.consumeEachBufferRange
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.io.*
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import java.nio.ByteBuffer
@UseExperimental(MiraiInternalAPI::class)
internal actual class QQAndroidBot
......@@ -20,4 +30,167 @@ actual constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration
) : QQAndroidBotBase(context, account, configuration)
\ No newline at end of file
) : QQAndroidBotBase(context, account, configuration)
@UseExperimental(MiraiInternalAPI::class)
@Suppress("DEPRECATION")
internal actual fun ByteReadChannel.toKotlinByteReadChannel(): kotlinx.coroutines.io.ByteReadChannel {
return object : kotlinx.coroutines.io.ByteReadChannel {
override val availableForRead: Int
get() = this@toKotlinByteReadChannel.availableForRead
override val isClosedForRead: Boolean
get() = this@toKotlinByteReadChannel.isClosedForRead
override val isClosedForWrite: Boolean
get() = this@toKotlinByteReadChannel.isClosedForWrite
@Suppress("DEPRECATION_ERROR", "OverridingDeprecatedMember")
override var readByteOrder: ByteOrder
get() = when (this@toKotlinByteReadChannel.readByteOrder) {
io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN -> ByteOrder.BIG_ENDIAN
io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN -> ByteOrder.LITTLE_ENDIAN
}
set(value) {
this@toKotlinByteReadChannel.readByteOrder = when (value) {
ByteOrder.BIG_ENDIAN -> io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN
ByteOrder.LITTLE_ENDIAN -> io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN
}
}
@Suppress("DEPRECATION_ERROR", "DEPRECATION", "OverridingDeprecatedMember")
override val totalBytesRead: Long
get() = this@toKotlinByteReadChannel.totalBytesRead
override fun cancel(cause: Throwable?): Boolean = this@toKotlinByteReadChannel.cancel(cause)
override suspend fun consumeEachBufferRange(visitor: ConsumeEachBufferVisitor) =
this@toKotlinByteReadChannel.consumeEachBufferRange(visitor)
override suspend fun discard(max: Long): Long = this@toKotlinByteReadChannel.discard(max)
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override fun <R> lookAhead(visitor: LookAheadSession.() -> R): R {
return this@toKotlinByteReadChannel.lookAhead l@{
visitor(object : LookAheadSession {
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
}
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override suspend fun <R> lookAheadSuspend(visitor: suspend LookAheadSuspendSession.() -> R): R =
this@toKotlinByteReadChannel.lookAheadSuspend l@{
visitor(object : LookAheadSuspendSession {
override suspend fun awaitAtLeast(n: Int): Boolean {
return this@l.awaitAtLeast(n)
}
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
override suspend fun read(min: Int, consumer: (ByteBuffer) -> Unit) =
this@toKotlinByteReadChannel.read(min, consumer)
override suspend fun readAvailable(dst: ByteBuffer): Int = this@toKotlinByteReadChannel.readAvailable(dst)
override suspend fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int =
this@toKotlinByteReadChannel.readAvailable(dst, offset, length)
override suspend fun readAvailable(dst: IoBuffer): Int {
ByteArrayPool.useInstance {
val read = this@toKotlinByteReadChannel.readAvailable(it, 0, it.size)
dst.writeFully(it, 0, read)
return read
}
}
override suspend fun readBoolean(): Boolean = this@toKotlinByteReadChannel.readBoolean()
override suspend fun readByte(): Byte = this@toKotlinByteReadChannel.readByte()
override suspend fun readDouble(): Double = this@toKotlinByteReadChannel.readDouble()
override suspend fun readFloat(): Float = this@toKotlinByteReadChannel.readFloat()
override suspend fun readFully(dst: ByteBuffer): Int {
TODO("not implemented")
}
override suspend fun readFully(dst: ByteArray, offset: Int, length: Int) =
this@toKotlinByteReadChannel.readFully(dst, offset, length)
override suspend fun readFully(dst: IoBuffer, n: Int) {
ByteArrayPool.useInstance {
dst.writeFully(it, 0, this.readAvailable(it, 0, it.size))
}
}
override suspend fun readInt(): Int = this@toKotlinByteReadChannel.readInt()
override suspend fun readLong(): Long = this@toKotlinByteReadChannel.readLong()
override suspend fun readPacket(size: Int, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readPacket(size, headerSizeHint).readBytes().toReadPacket()
}
override suspend fun readRemaining(limit: Long, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readRemaining(limit, headerSizeHint).readBytes().toReadPacket()
}
@UseExperimental(ExperimentalIoApi::class)
@ExperimentalIoApi
override fun readSession(consumer: ReadSession.() -> Unit) {
@Suppress("DEPRECATION")
this@toKotlinByteReadChannel.readSession lambda@{
consumer(object : ReadSession {
override val availableForRead: Int
get() = this@lambda.availableForRead
override fun discard(n: Int): Int = this@lambda.discard(n)
override fun request(atLeast: Int): IoBuffer? {
val ioBuffer: io.ktor.utils.io.core.IoBuffer = this@lambda.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
}
override suspend fun readShort(): Short = this@toKotlinByteReadChannel.readShort()
@Suppress("EXPERIMENTAL_OVERRIDE", "EXPERIMENTAL_API_USAGE")
@ExperimentalIoApi
override suspend fun readSuspendableSession(consumer: suspend SuspendableReadSession.() -> Unit) =
this@toKotlinByteReadChannel.readSuspendableSession l@{
consumer(object : SuspendableReadSession {
override val availableForRead: Int
get() = this@l.availableForRead
override suspend fun await(atLeast: Int): Boolean = this@l.await(atLeast)
override fun discard(n: Int): Int = this@l.discard(n)
override fun request(atLeast: Int): IoBuffer? {
@Suppress("DuplicatedCode") val ioBuffer: io.ktor.utils.io.core.IoBuffer =
this@l.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
override suspend fun readUTF8Line(limit: Int): String? = this@toKotlinByteReadChannel.readUTF8Line(limit)
override suspend fun <A : Appendable> readUTF8LineTo(out: A, limit: Int): Boolean =
this@toKotlinByteReadChannel.readUTF8LineTo(out, limit)
}
}
\ No newline at end of file
......@@ -9,9 +9,9 @@
package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.core.Closeable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.io.core.Closeable
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
......
......@@ -11,8 +11,8 @@ package net.mamoe.mirai.qqandroid
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.LowLevelAPI
......@@ -222,6 +222,10 @@ internal abstract class QQAndroidBotBase constructor(
}
override suspend fun openChannel(image: Image): ByteReadChannel {
return Http.get<HttpResponse>(queryImageUrl(image)).content
return Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
}
}
\ No newline at end of file
}
@Suppress("DEPRECATION")
@UseExperimental(MiraiInternalAPI::class)
internal expect fun io.ktor.utils.io.ByteReadChannel.toKotlinByteReadChannel(): ByteReadChannel
\ No newline at end of file
......@@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.charsets.Charset
import io.ktor.utils.io.core.*
import kotlinx.io.charsets.Charset
import kotlinx.io.core.*
import kotlinx.serialization.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.EmptyModule
......
......@@ -12,10 +12,7 @@
package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.core.BytePacketBuilder
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.readBytes
import io.ktor.utils.io.core.writeFully
import kotlinx.io.core.*
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationStrategy
......
......@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.message
import io.ktor.utils.io.core.*
import kotlinx.io.core.buildPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.*
......
......@@ -9,12 +9,15 @@
package net.mamoe.mirai.qqandroid.network
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.use
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.*
......@@ -67,14 +70,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
_packetReceiverJob?.join()
return this.launch(CoroutineName("Incoming Packet Receiver")) {
while (channel.isOpen) {
while (channel.isOpen && isActive) {
val rawInput = try {
channel.read()
} catch (e: CancellationException) {
return@launch
} catch (e: Throwable) {
if (this@QQAndroidBotNetworkHandler.isActive) {
BotOfflineEvent.Dropped(bot, e).broadcast()
bot.launch { BotOfflineEvent.Dropped(bot, e).broadcast() }
}
return@launch
}
......@@ -141,10 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
continue@mainloop
}
is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> {
var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url)
if (ticket == null) {
ticket = ""
}
val ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url).orEmpty()
response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
continue@mainloop
}
......@@ -338,6 +338,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
suspend fun doHeartBeat(): Exception? {
val lastException: Exception?
try {
kotlin.runCatching {
Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 2
)
return null
}
Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
......
......@@ -11,9 +11,9 @@
package net.mamoe.mirai.qqandroid.network
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.*
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse
import net.mamoe.mirai.data.OnlineStatus
......@@ -159,8 +159,8 @@ internal open class QQAndroidClient(
val uin: Long get() = _uin
@UseExperimental(RawAccountIdUse::class)
@Suppress("PropertyName")
internal var _uin: Long = bot.uin
@Suppress("PropertyName", "DEPRECATION_ERROR")
internal var _uin: Long = bot.account.id
var t530: ByteArray? = null
var t528: ByteArray? = null
......@@ -315,7 +315,7 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
internal inline fun Input.readUShortLVString(): String = io.ktor.utils.io.core.String(this.readUShortLVByteArray())
internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray())
internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
......
......@@ -16,20 +16,21 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.content.OutgoingContent
import io.ktor.http.userAgent
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.copyAndClose
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readAvailable
import io.ktor.utils.io.core.readInt
import io.ktor.utils.io.core.use
import io.ktor.utils.io.pool.useInstance
import io.ktor.utils.io.ByteWriteChannel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.Input
import kotlinx.io.core.discardExact
import kotlinx.io.core.readAvailable
import kotlinx.io.core.use
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.copyAndClose
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.utils.io.withUse
......@@ -67,7 +68,7 @@ internal suspend fun HttpClient.postImage(
override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize
override suspend fun writeTo(channel: io.ktor.utils.io.ByteWriteChannel) {
override suspend fun writeTo(channel: ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray ->
when (imageInput) {
is Input -> {
......
......@@ -11,11 +11,14 @@
package net.mamoe.mirai.qqandroid.network.highway
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
......
......@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet
import io.ktor.utils.io.core.*
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.ECDHKeyPair
......
......@@ -10,7 +10,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet
import io.ktor.utils.io.core.*
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.encryptAndWrite
......@@ -43,7 +46,6 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
@Suppress("DuplicatedCode")
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
......@@ -52,7 +54,7 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
writeByte(0)
client.uin.toString().let {
writeInt(it.length + 4)
writeText(it)
writeStringUtf8(it)
}
encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) {
......@@ -75,7 +77,6 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
sequenceId: Int = client.nextSsoSequenceId(),
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
@Suppress("DuplicatedCode")
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
......@@ -84,7 +85,7 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
writeByte(0)
client.uin.toString().let {
writeInt(it.length + 4)
writeText(it)
writeStringUtf8(it)
}
encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) {
......@@ -105,7 +106,7 @@ internal inline fun BytePacketBuilder.writeUniPacket(
writeIntLVPacket(lengthOffset = { it + 4 }) {
commandName.let {
writeInt(it.length + 4)
writeText(it)
writeStringUtf8(it)
}
writeInt(4 + 4)
......@@ -152,7 +153,7 @@ internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
client.uin.toString().let {
writeInt(it.length + 4)
writeText(it)
writeStringUtf8(it)
}
if (key === NO_ENCRYPT) {
......@@ -207,7 +208,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
}
commandName.let {
writeInt(it.length + 4)
writeText(it)
writeStringUtf8(it)
}
writeInt(4 + 4)
......@@ -215,7 +216,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
client.device.imei.let {
writeInt(it.length + 4)
writeText(it)
writeStringUtf8(it)
}
writeInt(4)
......
......@@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet
import io.ktor.utils.io.core.*
import io.ktor.utils.io.pool.useInstance
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.qqandroid.QQAndroidBot
......
......@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet
import io.ktor.utils.io.core.*
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.protocol.LoginType
import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.currentTimeMillis
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
......@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
import io.ktor.utils.io.core.*
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.data.Packet
......@@ -375,7 +378,7 @@ internal class TroopManagement {
OutgoingPacketFactory<EditGroupNametag.Response>("friendlist.ModifyGroupCardReq") {
object Response : Packet
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): EditGroupNametag.Response {
return Response
}
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
......@@ -9,9 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
......
......@@ -11,7 +11,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import io.ktor.utils.io.core.*
import kotlinx.io.core.*
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MultiPacketBySequence
import net.mamoe.mirai.data.NoPacket
......@@ -341,7 +341,7 @@ internal class OnlinePush {
}
}
0x11 -> {
discard(1)
discardExact(1)
val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer())
proto.optMsgRecall?.let { recallReminder ->
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.list
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.*
......
......@@ -11,7 +11,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import io.ktor.util.InternalAPI
import io.ktor.utils.io.core.*
import kotlinx.io.core.*
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.*
......@@ -127,7 +127,7 @@ internal class WtLogin {
private const val appId = 16L
private const val subAppId = 537062845L
@UseExperimental(MiraiInternalAPI::class)
@UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
......
......@@ -11,11 +11,11 @@
package test
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readAvailable
import io.ktor.utils.io.core.use
import io.ktor.utils.io.pool.useInstance
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.readAvailable
import kotlinx.io.core.use
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
......
......@@ -9,19 +9,189 @@
package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.consumeEachBufferRange
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.io.*
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.ContextImpl
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import java.nio.ByteBuffer
@UseExperimental(MiraiInternalAPI::class)
@Suppress("FunctionName")
internal fun QQAndroidBot(account: BotAccount, configuration: BotConfiguration): QQAndroidBot = QQAndroidBot(ContextImpl(), account, configuration)
internal fun QQAndroidBot(account: BotAccount, configuration: BotConfiguration): QQAndroidBot =
QQAndroidBot(ContextImpl(), account, configuration)
@UseExperimental(MiraiInternalAPI::class)
internal actual class QQAndroidBot actual constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration
) : QQAndroidBotBase(context, account, configuration)
\ No newline at end of file
) : QQAndroidBotBase(context, account, configuration)
@UseExperimental(MiraiInternalAPI::class)
@Suppress("DEPRECATION")
internal actual fun ByteReadChannel.toKotlinByteReadChannel(): kotlinx.coroutines.io.ByteReadChannel {
return object : kotlinx.coroutines.io.ByteReadChannel {
override val availableForRead: Int
get() = this@toKotlinByteReadChannel.availableForRead
override val isClosedForRead: Boolean
get() = this@toKotlinByteReadChannel.isClosedForRead
override val isClosedForWrite: Boolean
get() = this@toKotlinByteReadChannel.isClosedForWrite
@Suppress("DEPRECATION_ERROR", "OverridingDeprecatedMember")
override var readByteOrder: ByteOrder
get() = when (this@toKotlinByteReadChannel.readByteOrder) {
io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN -> ByteOrder.BIG_ENDIAN
io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN -> ByteOrder.LITTLE_ENDIAN
}
set(value) {
this@toKotlinByteReadChannel.readByteOrder = when (value) {
ByteOrder.BIG_ENDIAN -> io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN
ByteOrder.LITTLE_ENDIAN -> io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN
}
}
@Suppress("DEPRECATION_ERROR", "DEPRECATION", "OverridingDeprecatedMember")
override val totalBytesRead: Long
get() = this@toKotlinByteReadChannel.totalBytesRead
override fun cancel(cause: Throwable?): Boolean = this@toKotlinByteReadChannel.cancel(cause)
override suspend fun consumeEachBufferRange(visitor: ConsumeEachBufferVisitor) = this@toKotlinByteReadChannel.consumeEachBufferRange(visitor)
override suspend fun discard(max: Long): Long = this@toKotlinByteReadChannel.discard(max)
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override fun <R> lookAhead(visitor: LookAheadSession.() -> R): R {
return this@toKotlinByteReadChannel.lookAhead l@{
visitor(object : LookAheadSession{
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
}
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override suspend fun <R> lookAheadSuspend(visitor: suspend LookAheadSuspendSession.() -> R): R =
this@toKotlinByteReadChannel.lookAheadSuspend l@{
visitor(object : LookAheadSuspendSession {
override suspend fun awaitAtLeast(n: Int): Boolean {
return this@l.awaitAtLeast(n)
}
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
override suspend fun read(min: Int, consumer: (ByteBuffer) -> Unit) =
this@toKotlinByteReadChannel.read(min, consumer)
override suspend fun readAvailable(dst: ByteBuffer): Int = this@toKotlinByteReadChannel.readAvailable(dst)
override suspend fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int =
this@toKotlinByteReadChannel.readAvailable(dst, offset, length)
override suspend fun readAvailable(dst: IoBuffer): Int {
ByteArrayPool.useInstance {
val read = this@toKotlinByteReadChannel.readAvailable(it, 0, it.size)
dst.writeFully(it, 0, read)
return read
}
}
override suspend fun readBoolean(): Boolean = this@toKotlinByteReadChannel.readBoolean()
override suspend fun readByte(): Byte = this@toKotlinByteReadChannel.readByte()
override suspend fun readDouble(): Double = this@toKotlinByteReadChannel.readDouble()
override suspend fun readFloat(): Float = this@toKotlinByteReadChannel.readFloat()
override suspend fun readFully(dst: ByteBuffer): Int {
TODO("not implemented")
}
override suspend fun readFully(dst: ByteArray, offset: Int, length: Int) =
this@toKotlinByteReadChannel.readFully(dst, offset, length)
override suspend fun readFully(dst: IoBuffer, n: Int) {
ByteArrayPool.useInstance {
dst.writeFully(it, 0, this.readAvailable(it, 0, it.size))
}
}
override suspend fun readInt(): Int = this@toKotlinByteReadChannel.readInt()
override suspend fun readLong(): Long = this@toKotlinByteReadChannel.readLong()
override suspend fun readPacket(size: Int, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readPacket(size, headerSizeHint).readBytes().toReadPacket()
}
override suspend fun readRemaining(limit: Long, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readRemaining(limit, headerSizeHint).readBytes().toReadPacket()
}
@UseExperimental(ExperimentalIoApi::class)
@ExperimentalIoApi
override fun readSession(consumer: ReadSession.() -> Unit) {
@Suppress("DEPRECATION")
this@toKotlinByteReadChannel.readSession lambda@{
consumer(object : ReadSession {
override val availableForRead: Int
get() = this@lambda.availableForRead
override fun discard(n: Int): Int = this@lambda.discard(n)
override fun request(atLeast: Int): IoBuffer? {
val ioBuffer: io.ktor.utils.io.core.IoBuffer = this@lambda.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
}
override suspend fun readShort(): Short = this@toKotlinByteReadChannel.readShort()
@Suppress("EXPERIMENTAL_OVERRIDE", "EXPERIMENTAL_API_USAGE")
@ExperimentalIoApi
override suspend fun readSuspendableSession(consumer: suspend SuspendableReadSession.() -> Unit) =
this@toKotlinByteReadChannel.readSuspendableSession l@{
consumer(object : SuspendableReadSession {
override val availableForRead: Int
get() = this@l.availableForRead
override suspend fun await(atLeast: Int): Boolean = this@l.await(atLeast)
override fun discard(n: Int): Int = this@l.discard(n)
override fun request(atLeast: Int): IoBuffer? {
@Suppress("DuplicatedCode") val ioBuffer: io.ktor.utils.io.core.IoBuffer =
this@l.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
override suspend fun readUTF8Line(limit: Int): String? = this@toKotlinByteReadChannel.readUTF8Line(limit)
override suspend fun <A : Appendable> readUTF8LineTo(out: A, limit: Int): Boolean =
this@toKotlinByteReadChannel.readUTF8LineTo(out, limit)
}
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ package net.mamoe.mirai.qqandroid.io.serialization
/*
import io.ktor.utils.io.core.readBytes
import kotlinx.io.core.readBytes
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceOutput
......
......@@ -28,7 +28,7 @@ fun main() {
println(
File(
"""
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\oidb\cmd0x857
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\msgrevoke
""".trimIndent()
)
.generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n")
......
......@@ -11,6 +11,7 @@ plugins {
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
......@@ -51,48 +52,37 @@ kotlin {
)
}
jvm("jvm") {
}
jvm()
sourceSets {
all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
}
commonMain {
dependencies {
api(kotlin("stdlib", kotlinVersion))
api(kotlin("serialization", kotlinVersion))
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
}
}
commonMain {
dependencies {
api(kotlin("reflect", kotlinVersion))
api(kotlin("serialization", kotlinVersion))
api(kotlinx("coroutines-core-common", coroutinesVersion))
api(kotlinx("serialization-runtime-common", serializationVersion))
api(kotlinx("io", kotlinXIoVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
api("org.jetbrains.kotlinx:atomicfu-common:$atomicFuVersion")
api(ktor("http-cio", ktorVersion))
api(ktor("http", ktorVersion))
api(ktor("client-core-jvm", ktorVersion))
api(ktor("client-cio", ktorVersion))
api(ktor("client-core", ktorVersion))
api(ktor("network", ktorVersion))
//implementation("io.ktor:ktor-io:1.3.0-beta-1")
//runtimeOnly(files("build/classes/kotlin/metadata/main")) // classpath is not properly set by IDE
}
}
commonTest {
dependencies {
implementation(kotlin("test-annotations-common"))
implementation(kotlin("test-common"))
//runtimeOnly(files("build/classes/kotlin/metadata/test")) // classpath is not properly set by IDE
}
}
......@@ -101,8 +91,10 @@ kotlin {
dependencies {
api(kotlin("reflect", kotlinVersion))
api(kotlinx("io-jvm", kotlinXIoVersion))
api(kotlinx("serialization-runtime", serializationVersion))
api(kotlinx("coroutines-android", coroutinesVersion))
api(kotlinx("coroutines-io-jvm", coroutinesIoVersion))
api(ktor("client-android", ktorVersion))
}
......@@ -125,10 +117,10 @@ kotlin {
api(kotlin("reflect", kotlinVersion))
api(ktor("client-core-jvm", ktorVersion))
api(kotlinx("io-jvm", kotlinXIoVersion))
api(kotlinx("serialization-runtime", serializationVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-io-jvm", coroutinesIoVersion))
api(kotlinx("io-jvm", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
api("org.bouncycastle:bcprov-jdk15on:1.64")
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
......
......@@ -2,8 +2,8 @@
package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.contact
import android.graphics.Bitmap
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers
import kotlinx.io.core.Input
import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaHappyAPI
import net.mamoe.mirai.event.events.*
......
......@@ -12,9 +12,9 @@
package net.mamoe.mirai.message
import android.graphics.Bitmap
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.OverFileSizeMaxException
......@@ -36,28 +36,31 @@ import java.net.URL
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun Bitmap.sendTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> Bitmap.sendTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun URL.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> URL.sendAsImageTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun Input.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> Input.sendAsImageTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun InputStream.sendAsImageTo(contact: Contact) =
suspend fun <C : Contact> InputStream.sendAsImageTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
......@@ -65,9 +68,9 @@ suspend fun InputStream.sendAsImageTo(contact: Contact) =
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun File.sendAsImageTo(contact: Contact) {
suspend fun <C : Contact> File.sendAsImageTo(contact: C): MessageReceipt<C> {
require(this.exists() && this.canRead())
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
}
// endregion
......@@ -124,35 +127,36 @@ suspend fun File.uploadAsImage(contact: Contact): Image {
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(bufferedImage: Bitmap) = bufferedImage.sendTo(this)
suspend inline fun <C : Contact> C.sendImage(bufferedImage: Bitmap): MessageReceipt<C> = bufferedImage.sendTo(this)
/**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(imageUrl: URL) = imageUrl.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(imageUrl: URL): MessageReceipt<C> = imageUrl.sendAsImageTo(this)
/**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(imageInput: Input) = imageInput.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(imageInput: Input): MessageReceipt<C> = imageInput.sendAsImageTo(this)
/**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(imageStream: InputStream) = imageStream.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(imageStream: InputStream): MessageReceipt<C> =
imageStream.sendAsImageTo(this)
/**
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(file: File): MessageReceipt<C> = file.sendAsImageTo(this)
// endregion
......
......@@ -13,13 +13,14 @@ package net.mamoe.mirai.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.errors.IOException
import io.ktor.utils.io.streams.asOutput
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.copyTo
import kotlinx.io.errors.IOException
import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput
import java.io.File
import java.io.InputStream
import java.net.URL
......@@ -30,7 +31,7 @@ import java.net.URL
/**
* 读取 [BufferedImage] 的属性, 然后构造 [ExternalImage]
* 读取 [Bitmap] 的属性, 然后构造 [ExternalImage]
*/
@Throws(IOException::class)
fun Bitmap.toExternalImage(formatName: String = "gif"): Nothing {
......@@ -52,7 +53,8 @@ fun File.toExternalImage(): ExternalImage {
height = input.height,
md5 = this.inputStream().use { it.md5() },
imageFormat = this.nameWithoutExtension,
input = this.inputStream(),
input = this.inputStream().asInput(IoBuffer.Pool),
inputSize = this.length(),
filename = this.name
)
}
......@@ -68,8 +70,8 @@ suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO
@Throws(IOException::class)
fun URL.toExternalImage(): ExternalImage {
val file = createTempFile().apply { deleteOnExit() }
file.outputStream().use { output ->
openStream().use { input ->
file.outputStream().asOutput().use { output ->
openStream().asInput().use { input ->
input.copyTo(output)
}
output.flush()
......@@ -121,6 +123,7 @@ fun Input.toExternalImage(): ExternalImage {
*/
suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/*
/**
* 保存为临时文件然后调用 [File.toExternalImage].
*/
......@@ -132,4 +135,4 @@ suspend fun ByteReadChannel.toExternalImage(): ExternalImage {
}
return file.suspendToExternalImage()
}
\ No newline at end of file
}*/
\ No newline at end of file
......@@ -13,7 +13,7 @@ import android.annotation.SuppressLint
import android.net.wifi.WifiManager
import android.os.Build
import android.telephony.TelephonyManager
import io.ktor.utils.io.core.toByteArray
import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
......
......@@ -6,7 +6,7 @@ private var isAddSuppressedSupported: Boolean = true
@PublishedApi
internal actual fun Throwable.addSuppressedMirai(e: Throwable) {
if (this === e) {
if (this == e) {
return
}
if (!isAddSuppressedSupported) {
......
......@@ -9,12 +9,12 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable
import io.ktor.utils.io.nio.readPacketAtMost
import io.ktor.utils.io.nio.writePacket
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.nio.readPacketAtMost
import kotlinx.io.nio.writePacket
import java.net.InetSocketAddress
import java.nio.channels.DatagramChannel
import java.nio.channels.ReadableByteChannel
......
......@@ -9,13 +9,13 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable
import io.ktor.utils.io.core.ExperimentalIoApi
import io.ktor.utils.io.streams.readPacketAtMost
import io.ktor.utils.io.streams.writePacket
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.ExperimentalIoApi
import kotlinx.io.streams.readPacketAtMost
import kotlinx.io.streams.writePacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
......
......@@ -12,7 +12,7 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI
import io.ktor.utils.io.pool.useInstance
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream
import java.io.DataInput
......
......@@ -11,10 +11,10 @@
package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
......@@ -89,7 +89,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
// region contacts
/**
* [_lowLevelNewQQ.id] 与 [Bot.uin] 相同的 [_lowLevelNewQQ] 实例
* [QQ.id] 与 [Bot.uin] 相同的 [_lowLevelNewQQ] 实例
*/
abstract val selfQQ: QQ
......
......@@ -11,7 +11,7 @@
package net.mamoe.mirai
import io.ktor.utils.io.core.toByteArray
import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.md5
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.data
import io.ktor.client.request.get
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.readBytes
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.Http
interface ImageLink {
......
......@@ -21,7 +21,6 @@ import net.mamoe.mirai.event.events.ImageUploadEvent.Failed
import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI
......
......@@ -11,7 +11,7 @@
package net.mamoe.mirai.message
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
......
......@@ -11,15 +11,16 @@
package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.OfflineImage
import net.mamoe.mirai.message.data.sendTo
import net.mamoe.mirai.utils.io.toUHexString
......@@ -159,7 +160,7 @@ suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> =
*
* @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人
*/
suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) {
suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) {
is Group -> contact.uploadImage(this)
is QQ -> contact.uploadImage(this)
else -> error("unreachable")
......
......@@ -14,12 +14,12 @@
package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.Output
import io.ktor.utils.io.core.writeFully
import io.ktor.utils.io.pool.useInstance
import io.ktor.utils.io.readAvailable
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.readAvailable
import kotlinx.io.OutputStream
import kotlinx.io.core.Output
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
......@@ -52,6 +52,19 @@ suspend fun ByteReadChannel.copyTo(dst: Output) {
}
}
/**
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/
suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { buffer ->
var size: Int
while (this.readAvailable(buffer).also { size = it } > 0) {
dst.writeFully(buffer, 0, size)
}
}
}
/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/**
......@@ -104,6 +117,44 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) {
}
}
/**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/
suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) {
@Suppress("DuplicatedCode")
try {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { buffer ->
var size: Int
while (this.readAvailable(buffer).also { size = it } > 0) {
dst.writeFully(buffer, 0, size)
}
}
} finally {
@Suppress("DuplicatedCode")
dst.close(null)
}
}
/**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/
suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) {
@Suppress("DuplicatedCode")
try {
@UseExperimental(MiraiInternalAPI::class)
ByteArrayPool.useInstance { buffer ->
var size: Int
while (this.readAvailable(buffer).also { size = it } > 0) {
dst.writeFully(buffer, 0, size)
}
}
} finally {
@Suppress("DuplicatedCode")
dst.close(null)
}
}
/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
......
......@@ -9,9 +9,8 @@
package net.mamoe.mirai.utils.cryptor
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.readFully
import io.ktor.utils.io.pool.useInstance
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toByteArray
......
......@@ -9,8 +9,8 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
......
......@@ -9,8 +9,8 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
......
......@@ -13,11 +13,11 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.charsets.Charset
import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.String
import io.ktor.utils.io.core.use
import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlinx.io.core.use
import net.mamoe.mirai.utils.checkOffsetAndLength
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
......
......@@ -9,17 +9,15 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.isNotEmpty
import io.ktor.utils.io.core.readAvailable
import io.ktor.utils.io.pool.useInstance
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI
......
......@@ -11,6 +11,8 @@
package net.mamoe.mirai.utils.io
import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.ObjectPool
import kotlin.random.Random
import kotlin.random.nextInt
......@@ -200,3 +202,13 @@ fun ByteArray.toInt(): Int =
(this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and(
255
) shl 0)
/**
* 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入.
* 注意回收 ([ObjectPool.recycle])
*/
fun ByteArray.toIoBuffer(
offset: Int = 0,
length: Int = this.size - offset,
pool: ObjectPool<IoBuffer> = IoBuffer.Pool
): IoBuffer = pool.borrow().let { it.writeFully(this, offset, length); it }
\ No newline at end of file
......@@ -13,7 +13,7 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.*
import kotlinx.io.core.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.coerceAtMostOrFail
import net.mamoe.mirai.utils.cryptor.TEA
......
......@@ -12,7 +12,7 @@
package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.utils.io.core.toByteArray
import kotlinx.io.core.toByteArray
/**
* 时间戳
......
......@@ -2,7 +2,7 @@
package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
......
......@@ -9,8 +9,8 @@
package net.mamoe.mirai.contact
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers
import kotlinx.io.core.Input
import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaHappyAPI
import net.mamoe.mirai.event.events.*
......
......@@ -11,8 +11,10 @@
package net.mamoe.mirai.message
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.use
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.core.use
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.Image
......@@ -41,17 +43,15 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
// endregion
// region 发送图片
suspend inline fun sendImage(image: BufferedImage) = subject.sendImage(image)
suspend inline fun sendImage(image: URL) = subject.sendImage(image)
suspend inline fun sendImage(image: Input) = subject.sendImage(image)
suspend inline fun sendImage(image: InputStream) = subject.sendImage(image)
suspend inline fun sendImage(image: File) = subject.sendImage(image)
suspend inline fun sendImage(image: BufferedImage): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: URL): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: Input): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image)
// endregion
// region 上传图片 (扩展)
suspend inline fun BufferedImage.upload(): Image = upload(subject)
suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject)
suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject)
suspend inline fun InputStream.uploadAsImage(): Image = uploadAsImage(subject)
......@@ -59,12 +59,11 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
// endregion 上传图片 (扩展)
// region 发送图片 (扩展)
suspend inline fun BufferedImage.send() = sendTo(subject)
suspend inline fun URL.sendAsImage() = sendAsImageTo(subject)
suspend inline fun Input.sendAsImage() = sendAsImageTo(subject)
suspend inline fun InputStream.sendAsImage() = sendAsImageTo(subject)
suspend inline fun File.sendAsImage() = sendAsImageTo(subject)
suspend inline fun BufferedImage.send(): MessageReceipt<TSubject> = sendTo(subject)
suspend inline fun URL.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun Input.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
// endregion 发送图片 (扩展)
// region 下载图片 (扩展)
......@@ -80,6 +79,26 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
*/
suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output)
/**
* 下载图片到 [output] 但不关闭这个 [output]
*/
suspend inline fun Image.downloadTo(output: Output) = channel().copyTo(output)
/**
* 下载图片到 [output] 并关闭这个 [output]
*/
suspend inline fun Image.downloadAndClose(output: Output) = channel().copyAndClose(output)
/**
* 下载图片到 [output] 但不关闭这个 [output]
*/
suspend inline fun Image.downloadTo(output: ByteWriteChannel) = channel().copyTo(output)
/**
* 下载图片到 [output] 并关闭这个 [output]
*/
suspend inline fun Image.downloadAndClose(output: ByteWriteChannel) = channel().copyAndClose(output)
/*
suspend inline fun Image.downloadAsStream(): InputStream = channel().asInputStream()
suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() }
......
......@@ -59,15 +59,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
actual suspend fun recall() {
@Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) {
when (val contact = target) {
is Group -> {
contact.bot.recall(source)
}
is QQ -> {
TODO()
}
else -> error("Unknown contact type")
}
target.bot.recall(source)
} else error("message is already or planned to be recalled")
}
......
......@@ -11,11 +11,12 @@
package net.mamoe.mirai.message
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.OfflineImage
import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.toExternalImage
......@@ -36,37 +37,41 @@ import java.net.URL
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun BufferedImage.sendTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> BufferedImage.sendTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun URL.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> URL.sendAsImageTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun Input.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> Input.sendAsImageTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun InputStream.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
suspend fun <C : Contact> InputStream.sendAsImageTo(contact: C): MessageReceipt<C> =
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/**
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun File.sendAsImageTo(contact: Contact) {
suspend fun <C : Contact> File.sendAsImageTo(contact: C): MessageReceipt<C> {
require(this.exists() && this.canRead())
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
}
// endregion
......@@ -78,35 +83,39 @@ suspend fun File.sendAsImageTo(contact: Contact) {
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun BufferedImage.upload(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
suspend fun BufferedImage.upload(contact: Contact): OfflineImage =
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
/**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun URL.uploadAsImage(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
suspend fun URL.uploadAsImage(contact: Contact): OfflineImage =
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
/**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun Input.uploadAsImage(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
suspend fun Input.uploadAsImage(contact: Contact): OfflineImage =
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
/**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun InputStream.uploadAsImage(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
suspend fun InputStream.uploadAsImage(contact: Contact): OfflineImage =
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
/**
* 在 [Dispatchers.IO] 中将文件作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend fun File.uploadAsImage(contact: Contact): Image {
suspend fun File.uploadAsImage(contact: Contact): OfflineImage {
require(this.exists() && this.canRead())
return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
}
......@@ -120,35 +129,37 @@ suspend fun File.uploadAsImage(contact: Contact): Image {
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(bufferedImage: BufferedImage) = bufferedImage.sendTo(this)
suspend inline fun <C : Contact> C.sendImage(bufferedImage: BufferedImage): MessageReceipt<C> =
bufferedImage.sendTo(this)
/**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(imageUrl: URL) = imageUrl.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(imageUrl: URL): MessageReceipt<C> = imageUrl.sendAsImageTo(this)
/**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(imageInput: Input) = imageInput.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(imageInput: Input): MessageReceipt<C> = imageInput.sendAsImageTo(this)
/**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(imageStream: InputStream) = imageStream.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(imageStream: InputStream): MessageReceipt<C> =
imageStream.sendAsImageTo(this)
/**
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
suspend inline fun <C : Contact> C.sendImage(file: File): MessageReceipt<C> = file.sendAsImageTo(this)
// endregion
......@@ -159,34 +170,34 @@ suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): Image = bufferedImage.upload(this)
suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): OfflineImage = bufferedImage.upload(this)
/**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this)
suspend inline fun Contact.uploadImage(imageUrl: URL): OfflineImage = imageUrl.uploadAsImage(this)
/**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this)
suspend inline fun Contact.uploadImage(imageInput: Input): OfflineImage = imageInput.uploadAsImage(this)
/**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this)
suspend inline fun Contact.uploadImage(imageStream: InputStream): OfflineImage = imageStream.uploadAsImage(this)
/**
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
* @throws OverFileSizeMaxException
*/
@Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(file: File): Image = file.uploadAsImage(this)
suspend inline fun Contact.uploadImage(file: File): OfflineImage = file.uploadAsImage(this)
// endregion
\ No newline at end of file
......@@ -9,7 +9,6 @@
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.use
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
......@@ -21,6 +20,7 @@ import kotlinx.coroutines.io.writeFully
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.io.core.use
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import java.awt.Image
......
......@@ -11,14 +11,14 @@
package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.buildPacket
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.errors.IOException
import io.ktor.utils.io.streams.asOutput
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.copyTo
import kotlinx.io.errors.IOException
import kotlinx.io.streams.asOutput
import net.mamoe.mirai.utils.io.getRandomString
import java.awt.image.BufferedImage
import java.io.File
......
......@@ -13,7 +13,7 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.utils.io.pool.useInstance
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.*
import java.net.InetAddress
......
......@@ -9,7 +9,7 @@
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.toByteArray
import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
......
......@@ -4,7 +4,7 @@ private var isAddSuppressedSupported: Boolean = true
@PublishedApi
internal actual fun Throwable.addSuppressedMirai(e: Throwable) {
if (this === e) {
if (this == e) {
return
}
if (!isAddSuppressedSupported) {
......
......@@ -9,13 +9,13 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable
import io.ktor.utils.io.core.ExperimentalIoApi
import io.ktor.utils.io.streams.readPacketAtMost
import io.ktor.utils.io.streams.writePacket
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.ExperimentalIoApi
import kotlinx.io.streams.readPacketAtMost
import kotlinx.io.streams.writePacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
......
......@@ -9,12 +9,12 @@
package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable
import io.ktor.utils.io.nio.readPacketAtMost
import io.ktor.utils.io.nio.writePacket
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.nio.readPacketAtMost
import kotlinx.io.nio.writePacket
import java.net.InetSocketAddress
import java.nio.channels.DatagramChannel
import java.nio.channels.ReadableByteChannel
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package mirai.test
import kotlin.reflect.KProperty
class A {
val valProp: Any = Any()
}
fun main() {
A::class.members.filterIsInstance<KProperty<*>>().forEach {
println(it.getter.call(A()))
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package mirai.test
fun main() {
repeat(100) {
println("\u001b[1;${it}m" + it)
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package mirai.test.testCaptchaPacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.TEA.decrypt
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toUHexString
@MiraiInternalAPI
fun main() {
val key = "65 F7 F3 14 E3 94 10 1F DD 95 84 A3 F5 9F AD 94".hexToBytes()
val data =
decrypt(
"8D 4F 6A 70 F8 4A DE 43 AF 75 D1 3F 3A 3F F2 E0 A8 16 1A 46 13 CD B0 51 45 00 29 52 57 75 6D 4A 4C D9 B7 98 8C B0 96 EC 57 4E 67 FB 8D C5 F1 BF 72 38 40 42 19 54 C2 28 F4 72 C8 AE 24 EB 66 B5 D0 45 0B 72 44 81 E2 F6 2B EE C3 85 93 BA CB B7 72 F4 1A 30 F9 5B 3D B0 79 3E F4 0B F2 1A A7 49 60 3B 37 02 60 0C 5D D5 76 76 47 4F B5 B3 F5 CA 58 6C FC D2 41 3E 24 D1 FB 0A 18 53 D8 E5 A5 85 A8 BC 51 54 3B 66 5B 21 C6 7B AF C9 62 F0 AA 9C CF 2E 84 0F CC 15 5B 35 93 49 5C E4 28 49 A7 8A D3 30 A9 6E 36 4E 7A 49 28 69 4D C3 25 39 6E 45 6E 40 F2 86 1E F4 4F 00 A6 9D E6 9B 84 19 69 C1 31 6A 17 BA F0 0D 8A 22 09 86 24 92 F7 22 C3 47 7F F2 BF 94 8A 8A B5 29".hexToBytes(),
key
)
println(data.toUHexString())
//00 02 00 00 08 04 01 E0 00 00 04 56 00 00 00 01 00 00 15 E3 01 00 38 58 CE A0 12 81 31 5C 5E 36 23 5B E4 0E 05 A6 47 BF 7C 1A 7A 35 37 59 90 17 50 66 0C 07 03 77 E4 48 DB 28 0A CF C3 A9 B7 C0 95 D3 9D 00 AA A5 EB FB D6 85 8D 10 61 5A D0 01 03 00 19 02 CA 53 7E F0 7B 32 82 EC 9F DE CF 51 8B A4 93 26 76 EC 42 1C 02 00 74 58 14 00 05 00 00 00 00 00 04 6C 73 64 61 00 40 CE 99 84 E8 F1 59 31 B0 3F 6C 4D 44 09 E4 82 77 96 67 03 A7 3A EA 8F 36 B9 20 79 7E C9 0F 75 3C 2A C3 E1 E5 C6 00 B3 5E 91 5B 47 63 EF AF 30 C0 48 2F 58 23 96 CF 65 2F 4C 75 95 A6 CA 5A 2C 5C 00 10 E1 50 C9 F4 F6 F4 2F D1 7F E9 8C AB B6 1C 38 7B
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment