Commit e39fe5a4 authored by Him188's avatar Him188

Merge branch 'master' into kotlin-1.3.70

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt
#	mirai-core/build.gradle.kts
#	mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt
#	mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt
#	mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt
#	mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt
#	mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt
#	mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt
parents 472da8d3 200b8b98
---
name: 特性申请
about: 申请 mirai 添加新的特性
title: ''
labels: feature
assignees: ''
---
以下相关功能将会被直接拒绝:
- 资金相关: 红包, 转账
- 主动加好友, 主动加群, 主动邀请加入群
可以提交的内容:
- 有较高使用频率的协议 (低频功能不接受提议)
- 架构 / 功能上的建议 (欢迎)
请删除以上内容后再描述问题
---
name: Bug 报告
about: 提交一个 bug
title: ''
labels: " bug "
assignees: ''
---
提交前请先确认:
- 已经在现有 issue 列表中检查并未发现重复问题
- 确认问题属于 `mirai-core`, 而不是 `mirai-console` 或其他
若有报错, 请尽可能附加完整控制台日志或截图
若无报错, 请尽可能描述如何复现这个问题
请删除以上内容后再描述问题
# This is a basic workflow to help you get started with Actions
name: Shadow
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
release:
types:
- created
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle and shadowJar
run: ./gradlew :mirai-core:shadowJar :mirai-core-qqandroid:shadowJar
- name: Upload artifact
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: mirai-core-all
# Directory containing files to upload
path: "mirai-core/build/libs/mirai-core-*-all.jar"
- name: Upload artifact
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: mirai-core-qqandroid-all
# Directory containing files to upload
path: "mirai-core-qqandroid/build/libs/mirai-core-qqandroid-*-all.jar"
...@@ -2,12 +2,15 @@ ...@@ -2,12 +2,15 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.25.0` 还未发布 ## `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 月)协议 - 适配 8.2.7 版本(2020 年 3 月)协议
- 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image - 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image
- 修复查询图片链接时好友图片链接错误的问题 - 修复查询图片链接时好友图片链接错误的问题
- Kotlin 1.3.70 - 修复 bugs: #105, #106, #107
- 修复事件处理错误 (#107)
## `0.24.1` 2020/3/3 ## `0.24.1` 2020/3/3
- 修复 `Member` 的委托 `QQ` 弱引用被释放的问题 - 修复 `Member` 的委托 `QQ` 弱引用被释放的问题
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
---- ----
[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Actions Status](https://github.com/mamoe/mirai/workflows/CI/badge.svg)](https://github.com/mamoe/mirai/actions) ![Gradle CI](https://github.com/mamoe/mirai/workflows/Gradle%20CI/badge.svg?branch=master)
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人框架 Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人框架
...@@ -22,6 +22,7 @@ Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持 ...@@ -22,6 +22,7 @@ Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持
**[English](README-eng.md)** **[English](README-eng.md)**
多平台 **QQ Android****TIM PC** 协议支持库与高效率的机器人框架. 多平台 **QQ Android****TIM PC** 协议支持库与高效率的机器人框架.
纯 Kotlin 实现协议和支持框架,模块<b>全部免费开源</b> 纯 Kotlin 实现协议和支持框架,模块<b>全部免费开源</b>
目前可运行在 JVM 或 Android 平台。 目前可运行在 JVM 或 Android 平台。
...@@ -60,7 +61,7 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos) ...@@ -60,7 +61,7 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
### 使用者 ### 使用者
- [mirai-console](https://github.com/mamoe/mirai/tree/master/mirai-console) 支持插件 **本模块正在完善** - [mirai-console](https://github.com/mamoe/mirai-console) 支持插件 **本模块正在完善**
### 我是其他平台的使用者 ### 我是其他平台的使用者
......
...@@ -4,12 +4,11 @@ import java.util.* ...@@ -4,12 +4,11 @@ import java.util.*
buildscript { buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven") } maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter() jcenter()
mavenCentral() // mavenCentral()
google() google()
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } // maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
} }
dependencies { dependencies {
...@@ -42,11 +41,10 @@ allprojects { ...@@ -42,11 +41,10 @@ allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven") } maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter() jcenter()
mavenCentral() // mavenCentral()
google() google()
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } // maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
} }
} }
\ No newline at end of file
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
miraiVersion=0.24.1 miraiVersion=0.26.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
...@@ -10,6 +10,7 @@ kotlinVersion=1.3.70 ...@@ -10,6 +10,7 @@ kotlinVersion=1.3.70
serializationVersion=0.20.0-1.3.70-eap-274-2 serializationVersion=0.20.0-1.3.70-eap-274-2
coroutinesVersion=1.3.3 coroutinesVersion=1.3.3
atomicFuVersion=0.14.1 atomicFuVersion=0.14.1
kotlinXIoVersion=0.1.16
coroutinesIoVersion=0.1.16 coroutinesIoVersion=0.1.16
# utility # utility
ktorVersion=1.3.1 ktorVersion=1.3.1
\ No newline at end of file
...@@ -11,6 +11,7 @@ plugins { ...@@ -11,6 +11,7 @@ plugins {
val kotlinVersion: String by rootProject.ext val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext val coroutinesIoVersion: String by rootProject.ext
...@@ -66,6 +67,7 @@ kotlin { ...@@ -66,6 +67,7 @@ kotlin {
api(kotlin("stdlib", kotlinVersion)) api(kotlin("stdlib", kotlinVersion))
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
api(kotlinx("io", kotlinXIoVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion)) api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion)) api(kotlinx("coroutines-core", coroutinesVersion))
} }
......
...@@ -9,10 +9,20 @@ ...@@ -9,10 +9,20 @@
package net.mamoe.mirai.qqandroid 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.BotAccount
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import java.nio.ByteBuffer
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal actual class QQAndroidBot internal actual class QQAndroidBot
...@@ -20,4 +30,167 @@ actual constructor( ...@@ -20,4 +30,167 @@ actual constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
) : QQAndroidBotBase(context, account, configuration) ) : QQAndroidBotBase(context, account, configuration)
\ No newline at end of file
@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 @@ ...@@ -9,9 +9,9 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.core.Closeable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.io.core.Closeable
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.* import net.mamoe.mirai.data.*
......
...@@ -11,8 +11,8 @@ package net.mamoe.mirai.qqandroid ...@@ -11,8 +11,8 @@ package net.mamoe.mirai.qqandroid
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.BotImpl import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
...@@ -222,6 +222,10 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -222,6 +222,10 @@ internal abstract class QQAndroidBotBase constructor(
} }
override suspend fun openChannel(image: Image): ByteReadChannel { 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 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.charsets.Charset import kotlinx.io.charsets.Charset
import io.ktor.utils.io.core.* import kotlinx.io.core.*
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.MapEntrySerializer import kotlinx.serialization.builtins.MapEntrySerializer
......
...@@ -12,10 +12,7 @@ ...@@ -12,10 +12,7 @@
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.core.BytePacketBuilder import kotlinx.io.core.*
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.readBytes
import io.ktor.utils.io.core.writeFully
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialDescriptor import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
......
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.message 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.LowLevelAPI
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
......
...@@ -9,12 +9,15 @@ ...@@ -9,12 +9,15 @@
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock 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.MultiPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
...@@ -67,14 +70,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -67,14 +70,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
_packetReceiverJob?.join() _packetReceiverJob?.join()
return this.launch(CoroutineName("Incoming Packet Receiver")) { return this.launch(CoroutineName("Incoming Packet Receiver")) {
while (channel.isOpen) { while (channel.isOpen && isActive) {
val rawInput = try { val rawInput = try {
channel.read() channel.read()
} catch (e: CancellationException) { } catch (e: CancellationException) {
return@launch return@launch
} catch (e: Throwable) { } catch (e: Throwable) {
if (this@QQAndroidBotNetworkHandler.isActive) { if (this@QQAndroidBotNetworkHandler.isActive) {
BotOfflineEvent.Dropped(bot, e).broadcast() bot.launch { BotOfflineEvent.Dropped(bot, e).broadcast() }
} }
return@launch return@launch
} }
...@@ -141,10 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -141,10 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
continue@mainloop continue@mainloop
} }
is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> { is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> {
var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url) val ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url).orEmpty()
if (ticket == null) {
ticket = ""
}
response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect() response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
continue@mainloop continue@mainloop
} }
...@@ -338,6 +338,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -338,6 +338,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
suspend fun doHeartBeat(): Exception? { suspend fun doHeartBeat(): Exception? {
val lastException: Exception? val lastException: Exception?
try { try {
kotlin.runCatching {
Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 2
)
return null
}
Heartbeat.Alive(bot.client) Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>( .sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis, timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
......
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.io.core.*
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse import net.mamoe.mirai.RawAccountIdUse
import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.data.OnlineStatus
...@@ -159,7 +159,7 @@ internal open class QQAndroidClient( ...@@ -159,7 +159,7 @@ internal open class QQAndroidClient(
val uin: Long get() = _uin val uin: Long get() = _uin
@OptIn(RawAccountIdUse::class) @OptIn(RawAccountIdUse::class)
@Suppress("PropertyName") @Suppress("PropertyName", "DEPRECATION_ERROR")
internal var _uin: Long = bot.account.id internal var _uin: Long = bot.account.id
var t530: ByteArray? = null var t530: ByteArray? = null
...@@ -315,7 +315,7 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : ...@@ -315,7 +315,7 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
internal typealias PSKeyMap = MutableMap<String, PSKey> internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token> 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()) internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
......
...@@ -16,24 +16,25 @@ import io.ktor.http.HttpStatusCode ...@@ -16,24 +16,25 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol import io.ktor.http.URLProtocol
import io.ktor.http.content.OutgoingContent import io.ktor.http.content.OutgoingContent
import io.ktor.http.userAgent import io.ktor.http.userAgent
import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteWriteChannel
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 kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream import kotlinx.io.InputStream
import kotlinx.serialization.InternalSerializationApi 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.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.utils.MiraiInternalAPI 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.ByteArrayPool
import net.mamoe.mirai.utils.io.PlatformSocket import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.utils.io.withUse import net.mamoe.mirai.utils.io.withUse
import kotlinx.serialization.InternalSerializationApi
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
...@@ -68,7 +69,7 @@ internal suspend fun HttpClient.postImage( ...@@ -68,7 +69,7 @@ internal suspend fun HttpClient.postImage(
override val contentType: ContentType = ContentType.Image.Any override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize 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 -> ByteArrayPool.useInstance { buffer: ByteArray ->
when (imageInput) { when (imageInput) {
is Input -> { is Input -> {
......
...@@ -11,18 +11,21 @@ ...@@ -11,18 +11,21 @@
package net.mamoe.mirai.qqandroid.network.highway 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.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream import kotlinx.io.InputStream
import kotlinx.serialization.InternalSerializationApi 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.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlinx.serialization.InternalSerializationApi
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence( // RequestDataTrans internal fun createImageDataPacketSequence( // RequestDataTrans
......
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet 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.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.ECDHKeyPair import net.mamoe.mirai.utils.cryptor.ECDHKeyPair
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet 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.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
...@@ -43,7 +46,6 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket( ...@@ -43,7 +46,6 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
body: BytePacketBuilder.(sequenceId: Int) -> Unit body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket { ): OutgoingPacket {
@Suppress("DuplicatedCode")
return OutgoingPacket(name, commandName, sequenceId, buildPacket { return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) { writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B) writeInt(0x0B)
...@@ -52,7 +54,7 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket( ...@@ -52,7 +54,7 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
writeByte(0) writeByte(0)
client.uin.toString().let { client.uin.toString().let {
writeInt(it.length + 4) writeInt(it.length + 4)
writeText(it) writeStringUtf8(it)
} }
encryptAndWrite(key) { encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) { writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) {
...@@ -75,7 +77,6 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket( ...@@ -75,7 +77,6 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
sequenceId: Int = client.nextSsoSequenceId(), sequenceId: Int = client.nextSsoSequenceId(),
body: BytePacketBuilder.(sequenceId: Int) -> Unit body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket { ): OutgoingPacket {
@Suppress("DuplicatedCode")
return OutgoingPacket(name, commandName, sequenceId, buildPacket { return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) { writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B) writeInt(0x0B)
...@@ -84,7 +85,7 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket( ...@@ -84,7 +85,7 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
writeByte(0) writeByte(0)
client.uin.toString().let { client.uin.toString().let {
writeInt(it.length + 4) writeInt(it.length + 4)
writeText(it) writeStringUtf8(it)
} }
encryptAndWrite(key) { encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) { writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) {
...@@ -105,7 +106,7 @@ internal inline fun BytePacketBuilder.writeUniPacket( ...@@ -105,7 +106,7 @@ internal inline fun BytePacketBuilder.writeUniPacket(
writeIntLVPacket(lengthOffset = { it + 4 }) { writeIntLVPacket(lengthOffset = { it + 4 }) {
commandName.let { commandName.let {
writeInt(it.length + 4) writeInt(it.length + 4)
writeText(it) writeStringUtf8(it)
} }
writeInt(4 + 4) writeInt(4 + 4)
...@@ -152,7 +153,7 @@ internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket( ...@@ -152,7 +153,7 @@ internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
client.uin.toString().let { client.uin.toString().let {
writeInt(it.length + 4) writeInt(it.length + 4)
writeText(it) writeStringUtf8(it)
} }
if (key === NO_ENCRYPT) { if (key === NO_ENCRYPT) {
...@@ -207,7 +208,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket( ...@@ -207,7 +208,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
} }
commandName.let { commandName.let {
writeInt(it.length + 4) writeInt(it.length + 4)
writeText(it) writeStringUtf8(it)
} }
writeInt(4 + 4) writeInt(4 + 4)
...@@ -215,7 +216,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket( ...@@ -215,7 +216,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
client.device.imei.let { client.device.imei.let {
writeInt(it.length + 4) writeInt(it.length + 4)
writeText(it) writeStringUtf8(it)
} }
writeInt(4) writeInt(4)
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet package net.mamoe.mirai.qqandroid.network.protocol.packet
import io.ktor.utils.io.core.* import kotlinx.io.core.*
import io.ktor.utils.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
......
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet 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.network.protocol.LoginType
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.currentTimeMillis
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
...@@ -9,7 +9,10 @@ ...@@ -9,7 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat 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.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
...@@ -375,7 +378,7 @@ internal class TroopManagement { ...@@ -375,7 +378,7 @@ internal class TroopManagement {
OutgoingPacketFactory<EditGroupNametag.Response>("friendlist.ModifyGroupCardReq") { OutgoingPacketFactory<EditGroupNametag.Response>("friendlist.ModifyGroupCardReq") {
object Response : Packet object Response : Packet
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): EditGroupNametag.Response {
return Response return Response
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
...@@ -9,9 +9,10 @@ ...@@ -9,9 +9,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.* 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.Group
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive 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.contact.MemberPermission
import net.mamoe.mirai.data.MultiPacketBySequence import net.mamoe.mirai.data.MultiPacketBySequence
import net.mamoe.mirai.data.NoPacket import net.mamoe.mirai.data.NoPacket
...@@ -341,7 +341,7 @@ internal class OnlinePush { ...@@ -341,7 +341,7 @@ internal class OnlinePush {
} }
} }
0x11 -> { 0x11 -> {
discard(1) discardExact(1)
val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()) val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer())
proto.optMsgRecall?.let { recallReminder -> proto.optMsgRecall?.let { recallReminder ->
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.list 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login 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.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.* import net.mamoe.mirai.qqandroid.io.serialization.*
......
...@@ -11,7 +11,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login ...@@ -11,7 +11,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import io.ktor.util.InternalAPI 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.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.* import net.mamoe.mirai.qqandroid.network.*
...@@ -127,7 +127,7 @@ internal class WtLogin { ...@@ -127,7 +127,7 @@ internal class WtLogin {
private const val appId = 16L private const val appId = 16L
private const val subAppId = 537062845L private const val subAppId = 537062845L
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
operator fun invoke( operator fun invoke(
client: QQAndroidClient client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
...@@ -355,7 +355,7 @@ internal class WtLogin { ...@@ -355,7 +355,7 @@ internal class WtLogin {
val otherInfo: String = readUShortLVString() val otherInfo: String = readUShortLVString()
LoginPacketResponse.Error(title, content, otherInfo) LoginPacketResponse.Error(title, content, otherInfo)
} ?: tlvMap[0x146]?.toReadPacket()?.run { } ?: tlvMap[0x146]?.read {
discardExact(2) // ver discardExact(2) // ver
discardExact(2) // code discardExact(2) // code
......
...@@ -11,11 +11,11 @@ ...@@ -11,11 +11,11 @@
package test package test
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input import kotlinx.io.core.Input
import io.ktor.utils.io.core.readAvailable import kotlinx.io.core.readAvailable
import io.ktor.utils.io.core.use import kotlinx.io.core.use
import io.ktor.utils.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
......
...@@ -9,19 +9,189 @@ ...@@ -9,19 +9,189 @@
package net.mamoe.mirai.qqandroid 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.BotAccount
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.ContextImpl import net.mamoe.mirai.utils.ContextImpl
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import java.nio.ByteBuffer
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@Suppress("FunctionName") @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)
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal actual class QQAndroidBot actual constructor( internal actual class QQAndroidBot actual constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
) : QQAndroidBotBase(context, account, configuration) ) : QQAndroidBotBase(context, account, configuration)
\ No newline at end of file
@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 ...@@ -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.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceOutput import net.mamoe.mirai.qqandroid.io.JceOutput
......
...@@ -28,7 +28,7 @@ fun main() { ...@@ -28,7 +28,7 @@ fun main() {
println( println(
File( File(
""" """
E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\oidb\cmd0x857 E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\msgrevoke
""".trimIndent() """.trimIndent()
) )
.generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n") .generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n")
......
...@@ -11,6 +11,7 @@ plugins { ...@@ -11,6 +11,7 @@ plugins {
val kotlinVersion: String by rootProject.ext val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext val coroutinesIoVersion: String by rootProject.ext
...@@ -51,49 +52,38 @@ kotlin { ...@@ -51,49 +52,38 @@ kotlin {
) )
} }
jvm("jvm") { jvm()
}
sourceSets { sourceSets {
all { all {
languageSettings.enableLanguageFeature("InlineClasses") languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.Experimental") languageSettings.useExperimentalAnnotation("kotlin.Experimental")
}
commonMain {
dependencies { dependencies {
api(kotlin("stdlib", kotlinVersion)) api(kotlin("stdlib", kotlinVersion))
api(kotlin("serialization", 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("reflect", kotlinVersion))
api(kotlin("serialization", kotlinVersion))
api(kotlinx("coroutines-core-common", coroutinesVersion)) api(kotlinx("coroutines-core-common", coroutinesVersion))
api(kotlinx("serialization-runtime-common", serializationVersion)) api(kotlinx("serialization-runtime-common", serializationVersion))
api(kotlinx("serialization-protobuf-common", serializationVersion)) api(kotlinx("serialization-protobuf-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-cio", ktorVersion))
api(ktor("client-core", ktorVersion)) api(ktor("client-core", ktorVersion))
api(ktor("network", 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 { commonTest {
dependencies { dependencies {
implementation(kotlin("test-annotations-common")) implementation(kotlin("test-annotations-common"))
implementation(kotlin("test-common")) implementation(kotlin("test-common"))
//runtimeOnly(files("build/classes/kotlin/metadata/test")) // classpath is not properly set by IDE
} }
} }
...@@ -102,9 +92,11 @@ kotlin { ...@@ -102,9 +92,11 @@ kotlin {
dependencies { dependencies {
api(kotlin("reflect", kotlinVersion)) api(kotlin("reflect", kotlinVersion))
api(kotlinx("io-jvm", kotlinXIoVersion))
api(kotlinx("serialization-runtime", serializationVersion)) api(kotlinx("serialization-runtime", serializationVersion))
api(kotlinx("serialization-protobuf", serializationVersion)) api(kotlinx("serialization-protobuf", serializationVersion))
api(kotlinx("coroutines-android", coroutinesVersion)) api(kotlinx("coroutines-android", coroutinesVersion))
api(kotlinx("coroutines-io-jvm", coroutinesIoVersion))
api(ktor("client-android", ktorVersion)) api(ktor("client-android", ktorVersion))
} }
...@@ -127,11 +119,11 @@ kotlin { ...@@ -127,11 +119,11 @@ kotlin {
api(kotlin("reflect", kotlinVersion)) api(kotlin("reflect", kotlinVersion))
api(ktor("client-core-jvm", ktorVersion)) api(ktor("client-core-jvm", ktorVersion))
api(kotlinx("io-jvm", kotlinXIoVersion))
api(kotlinx("serialization-runtime", serializationVersion)) api(kotlinx("serialization-runtime", serializationVersion))
api(kotlinx("serialization-protobuf", serializationVersion)) api(kotlinx("serialization-protobuf", serializationVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-io-jvm", coroutinesIoVersion)) api(kotlinx("coroutines-io-jvm", coroutinesIoVersion))
api(kotlinx("io-jvm", coroutinesIoVersion)) api(kotlinx("coroutines-core", coroutinesVersion))
api("org.bouncycastle:bcprov-jdk15on:1.64") api("org.bouncycastle:bcprov-jdk15on:1.64")
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
package net.mamoe.mirai package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
...@@ -56,12 +56,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA ...@@ -56,12 +56,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
*/ */
actual abstract val context: Context actual abstract val context: Context
/**
* 账号信息
*/
@MiraiInternalAPI
actual abstract val account: BotAccount
/** /**
* QQ 号码. 实际类型为 uint * QQ 号码. 实际类型为 uint
*/ */
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import android.graphics.Bitmap import android.graphics.Bitmap
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.io.core.Input
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaHappyAPI import net.mamoe.mirai.JavaHappyAPI
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
......
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import android.graphics.Bitmap import android.graphics.Bitmap
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.OverFileSizeMaxException import net.mamoe.mirai.utils.OverFileSizeMaxException
...@@ -36,28 +36,31 @@ import java.net.URL ...@@ -36,28 +36,31 @@ import java.net.URL
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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) withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
/** /**
...@@ -65,9 +68,9 @@ suspend fun InputStream.sendAsImageTo(contact: Contact) = ...@@ -65,9 +68,9 @@ suspend fun InputStream.sendAsImageTo(contact: Contact) =
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend fun File.sendAsImageTo(contact: Contact) { suspend fun <C : Contact> File.sendAsImageTo(contact: C): MessageReceipt<C> {
require(this.exists() && this.canRead()) require(this.exists() && this.canRead())
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
} }
// endregion // endregion
...@@ -124,35 +127,36 @@ suspend fun File.uploadAsImage(contact: Contact): Image { ...@@ -124,35 +127,36 @@ suspend fun File.uploadAsImage(contact: Contact): Image {
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 中将文件作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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 // endregion
......
...@@ -13,13 +13,14 @@ package net.mamoe.mirai.utils ...@@ -13,13 +13,14 @@ package net.mamoe.mirai.utils
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory 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.Dispatchers.IO
import kotlinx.coroutines.withContext 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.File
import java.io.InputStream import java.io.InputStream
import java.net.URL import java.net.URL
...@@ -53,7 +54,8 @@ fun File.toExternalImage(): ExternalImage { ...@@ -53,7 +54,8 @@ fun File.toExternalImage(): ExternalImage {
height = input.height, height = input.height,
md5 = this.inputStream().use { it.md5() }, md5 = this.inputStream().use { it.md5() },
imageFormat = this.nameWithoutExtension, imageFormat = this.nameWithoutExtension,
input = this.inputStream(), input = this.inputStream().asInput(IoBuffer.Pool),
inputSize = this.length(),
filename = this.name filename = this.name
) )
} }
...@@ -69,8 +71,8 @@ suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO ...@@ -69,8 +71,8 @@ suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO
@Throws(IOException::class) @Throws(IOException::class)
fun URL.toExternalImage(): ExternalImage { fun URL.toExternalImage(): ExternalImage {
val file = createTempFile().apply { deleteOnExit() } val file = createTempFile().apply { deleteOnExit() }
file.outputStream().use { output -> file.outputStream().asOutput().use { output ->
openStream().use { input -> openStream().asInput().use { input ->
input.copyTo(output) input.copyTo(output)
} }
output.flush() output.flush()
...@@ -122,6 +124,7 @@ fun Input.toExternalImage(): ExternalImage { ...@@ -122,6 +124,7 @@ fun Input.toExternalImage(): ExternalImage {
*/ */
suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/*
/** /**
* 保存为临时文件然后调用 [File.toExternalImage]. * 保存为临时文件然后调用 [File.toExternalImage].
*/ */
...@@ -133,4 +136,4 @@ suspend fun ByteReadChannel.toExternalImage(): ExternalImage { ...@@ -133,4 +136,4 @@ suspend fun ByteReadChannel.toExternalImage(): ExternalImage {
} }
return file.suspendToExternalImage() return file.suspendToExternalImage()
} }*/
\ No newline at end of file \ No newline at end of file
...@@ -15,7 +15,7 @@ import android.annotation.SuppressLint ...@@ -15,7 +15,7 @@ import android.annotation.SuppressLint
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import io.ktor.utils.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
......
...@@ -6,7 +6,7 @@ private var isAddSuppressedSupported: Boolean = true ...@@ -6,7 +6,7 @@ private var isAddSuppressedSupported: Boolean = true
@PublishedApi @PublishedApi
internal actual fun Throwable.addSuppressedMirai(e: Throwable) { internal actual fun Throwable.addSuppressedMirai(e: Throwable) {
if (this === e) { if (this == e) {
return return
} }
if (!isAddSuppressedSupported) { if (!isAddSuppressedSupported) {
......
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
package net.mamoe.mirai.utils.io 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.Dispatchers
import kotlinx.coroutines.withContext 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.net.InetSocketAddress
import java.nio.channels.DatagramChannel import java.nio.channels.DatagramChannel
import java.nio.channels.ReadableByteChannel import java.nio.channels.ReadableByteChannel
......
...@@ -9,13 +9,13 @@ ...@@ -9,13 +9,13 @@
package net.mamoe.mirai.utils.io 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.Dispatchers
import kotlinx.coroutines.withContext 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 net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
......
...@@ -12,7 +12,7 @@ package net.mamoe.mirai.utils ...@@ -12,7 +12,7 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI 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 net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataInput import java.io.DataInput
......
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
package net.mamoe.mirai package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
...@@ -76,12 +76,6 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { ...@@ -76,12 +76,6 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
*/ */
abstract val context: Context abstract val context: Context
/**
* 账号信息
*/
@MiraiInternalAPI
abstract val account: BotAccount
/** /**
* QQ 号码. 实际类型为 uint * QQ 号码. 实际类型为 uint
*/ */
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
package net.mamoe.mirai 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.MiraiInternalAPI
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
...@@ -23,6 +24,8 @@ data class BotAccount( ...@@ -23,6 +24,8 @@ data class BotAccount(
*/ */
@RawAccountIdUse @RawAccountIdUse
val id: Long, val id: Long,
@MiraiExperimentalAPI
@MiraiInternalAPI
val passwordMd5: ByteArray // md5 val passwordMd5: ByteArray // md5
) { ) {
constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray()))
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR")
package net.mamoe.mirai package net.mamoe.mirai
...@@ -40,12 +40,13 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -40,12 +40,13 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
?: CoroutineExceptionHandler { _, e -> logger.error("An exception was thrown under a coroutine of Bot", e) }) ?: CoroutineExceptionHandler { _, e -> logger.error("An exception was thrown under a coroutine of Bot", e) })
override val context: Context by context.unsafeWeakRef() override val context: Context by context.unsafeWeakRef()
@Suppress("CanBePrimaryConstructorProperty") // for logger @UseExperimental(LowLevelAPI::class)
@Suppress("CanBePrimaryConstructorProperty", "OverridingDeprecatedMember") // for logger
final override val account: BotAccount = account final override val account: BotAccount = account
@OptIn(RawAccountIdUse::class) @OptIn(RawAccountIdUse::class)
override val uin: Long override val uin: Long
get() = account.id get() = this.account.id
final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) } final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) }
init { init {
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
package net.mamoe.mirai.data package net.mamoe.mirai.data
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import io.ktor.utils.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.Http import net.mamoe.mirai.utils.Http
interface ImageLink { interface ImageLink {
......
...@@ -21,7 +21,6 @@ import net.mamoe.mirai.event.events.ImageUploadEvent.Failed ...@@ -21,7 +21,6 @@ import net.mamoe.mirai.event.events.ImageUploadEvent.Failed
import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain 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.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
......
...@@ -17,6 +17,7 @@ import net.mamoe.mirai.data.GroupInfo ...@@ -17,6 +17,7 @@ import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.WeakRef import net.mamoe.mirai.utils.WeakRef
/** /**
...@@ -37,6 +38,14 @@ annotation class LowLevelAPI ...@@ -37,6 +38,14 @@ annotation class LowLevelAPI
@Suppress("FunctionName", "unused") @Suppress("FunctionName", "unused")
@LowLevelAPI @LowLevelAPI
interface LowLevelBotAPIAccessor { interface LowLevelBotAPIAccessor {
/**
* 账号信息
*/
@Deprecated("将来会做修改", level = DeprecationLevel.ERROR)
@MiraiExperimentalAPI
@LowLevelAPI
@MiraiInternalAPI
abstract val account: BotAccount
/** /**
* 构造一个 [_lowLevelNewQQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]). * 构造一个 [_lowLevelNewQQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]).
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
package net.mamoe.mirai.message 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.Bot
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
......
...@@ -58,4 +58,8 @@ class CombinedMessage( ...@@ -58,4 +58,8 @@ class CombinedMessage(
override fun toString(): String { override fun toString(): String {
return element.toString() + left.toString() return element.toString() + left.toString()
} }
fun isFlat(): Boolean {
return element is SingleMessage && left is SingleMessage
}
} }
\ No newline at end of file
...@@ -61,7 +61,7 @@ interface MessageChain : Message, Iterable<SingleMessage> { ...@@ -61,7 +61,7 @@ interface MessageChain : Message, Iterable<SingleMessage> {
fun <M : Message> getOrNull(key: Message.Key<M>): M? = firstOrNull(key) fun <M : Message> getOrNull(key: Message.Key<M>): M? = firstOrNull(key)
/** /**
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage]. * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply].
* 仅供 `Java` 使用 * 仅供 `Java` 使用
*/ */
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
...@@ -71,13 +71,26 @@ interface MessageChain : Message, Iterable<SingleMessage> { ...@@ -71,13 +71,26 @@ interface MessageChain : Message, Iterable<SingleMessage> {
fun `__forEachContent for Java__`(block: (Message) -> Unit) { fun `__forEachContent for Java__`(block: (Message) -> Unit) {
this.foreachContent(block) this.foreachContent(block)
} }
/**
* 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply].
* 仅供 `Java` 使用
*/
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
@JsName("forEach")
@JvmName("forEach")
@MiraiInternalAPI
fun `__forEach for Java__`(block: (Message) -> Unit) {
this.forEach(block)
}
} }
// region accessors // region accessors
/** /**
* 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage] * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage], [QuoteReply]
*/ */
@JvmSynthetic
inline fun MessageChain.foreachContent(block: (Message) -> Unit) { inline fun MessageChain.foreachContent(block: (Message) -> Unit) {
this.forEach { this.forEach {
if (it !is MessageMetadata) block(it) if (it !is MessageMetadata) block(it)
...@@ -87,23 +100,27 @@ inline fun MessageChain.foreachContent(block: (Message) -> Unit) { ...@@ -87,23 +100,27 @@ inline fun MessageChain.foreachContent(block: (Message) -> Unit) {
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@JvmSynthetic
inline fun <reified M : Message?> MessageChain.firstOrNull(): M? = this.firstOrNull { it is M } as M? inline fun <reified M : Message?> MessageChain.firstOrNull(): M? = this.firstOrNull { it is M } as M?
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
* @throws [NoSuchElementException] 如果找不到该类型的实例 * @throws [NoSuchElementException] 如果找不到该类型的实例
*/ */
@JvmSynthetic
inline fun <reified M : Message> MessageChain.first(): M = this.first { it is M } as M inline fun <reified M : Message> MessageChain.first(): M = this.first { it is M } as M
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@JvmSynthetic
inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is M } inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is M }
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) { fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
At -> first<At>() At -> first<At>()
...@@ -120,6 +137,7 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) ...@@ -120,6 +137,7 @@ fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key)
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
* @throws [NoSuchElementException] 如果找不到该类型的实例 * @throws [NoSuchElementException] 如果找不到该类型的实例
*/ */
@JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.first(key: Message.Key<M>): M = fun <M : Message> MessageChain.first(key: Message.Key<M>): M =
firstOrNull(key) ?: throw NoSuchElementException("no such element: $key") firstOrNull(key) ?: throw NoSuchElementException("no such element: $key")
...@@ -127,6 +145,7 @@ fun <M : Message> MessageChain.first(key: Message.Key<M>): M = ...@@ -127,6 +145,7 @@ fun <M : Message> MessageChain.first(key: Message.Key<M>): M =
/** /**
* 获取第一个 [M] 类型的 [Message] 实例 * 获取第一个 [M] 类型的 [Message] 实例
*/ */
@JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstOrNull(key) != null fun <M : Message> MessageChain.any(key: Message.Key<M>): Boolean = firstOrNull(key) != null
...@@ -211,9 +230,6 @@ fun Message.asMessageChain(): MessageChain = when (this) { ...@@ -211,9 +230,6 @@ fun Message.asMessageChain(): MessageChain = when (this) {
else -> SingleMessageChainImpl(this as SingleMessage) else -> SingleMessageChainImpl(this as SingleMessage)
} }
@Deprecated("use asMessageChain instead", ReplaceWith("this.asMessageChain()"), DeprecationLevel.ERROR)
fun Message.toChain(): MessageChain = this.asMessageChain()
/** /**
* 直接将 [this] 委托为一个 [MessageChain] * 直接将 [this] 委托为一个 [MessageChain]
*/ */
...@@ -233,8 +249,18 @@ fun Collection<Message>.asMessageChain(): MessageChain = MessageChainImplBySeque ...@@ -233,8 +249,18 @@ fun Collection<Message>.asMessageChain(): MessageChain = MessageChainImplBySeque
@JvmSynthetic @JvmSynthetic
fun Iterable<SingleMessage>.asMessageChain(): MessageChain = MessageChainImplByIterable(this) fun Iterable<SingleMessage>.asMessageChain(): MessageChain = MessageChainImplByIterable(this)
@JvmSynthetic
inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃 inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃
@JvmSynthetic
fun CombinedMessage.asMessageChain(): MessageChain {
if (left is SingleMessage && this.element is SingleMessage) {
@Suppress("UNCHECKED_CAST")
return (this as Iterable<SingleMessage>).asMessageChain()
}
return (this as Iterable<Message>).asMessageChain()
} // 避免套娃
/** /**
* 将 [this] [扁平化后][flatten] 委托为一个 [MessageChain] * 将 [this] [扁平化后][flatten] 委托为一个 [MessageChain]
*/ */
...@@ -324,11 +350,20 @@ fun Sequence<SingleMessage>.flatten(): Sequence<SingleMessage> = this // fast pa ...@@ -324,11 +350,20 @@ fun Sequence<SingleMessage>.flatten(): Sequence<SingleMessage> = this // fast pa
fun Message.flatten(): Sequence<SingleMessage> { fun Message.flatten(): Sequence<SingleMessage> {
return when (this) { return when (this) {
is MessageChain -> this.asSequence() is MessageChain -> this.asSequence()
is CombinedMessage -> this.asSequence().flatten() is CombinedMessage -> this.flatten()
else -> sequenceOf(this as SingleMessage) else -> sequenceOf(this as SingleMessage)
} }
} }
fun CombinedMessage.flatten(): Sequence<SingleMessage> {
if (this.isFlat()){
@Suppress("UNCHECKED_CAST")
return (this as Iterable<SingleMessage>).asSequence()
} else return this.asSequence().flatten()
}
fun MessageChain.flatten(): Sequence<SingleMessage> = this.asSequence() // fast path
// endregion converters // endregion converters
// region implementations // region implementations
......
...@@ -11,18 +11,19 @@ ...@@ -11,18 +11,19 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.io.ByteReadChannel
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
import kotlinx.io.InputStream import kotlinx.io.InputStream
import kotlinx.serialization.InternalSerializationApi import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image 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.message.data.sendTo
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
import kotlinx.serialization.InternalSerializationApi
/** /**
* 外部图片. 图片数据还没有读取到内存. * 外部图片. 图片数据还没有读取到内存.
...@@ -161,7 +162,7 @@ suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> = ...@@ -161,7 +162,7 @@ suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> =
* *
* @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人 * @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 Group -> contact.uploadImage(this)
is QQ -> contact.uploadImage(this) is QQ -> contact.uploadImage(this)
else -> error("unreachable") else -> error("unreachable")
......
...@@ -14,13 +14,13 @@ ...@@ -14,13 +14,13 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.io.ByteReadChannel
import io.ktor.utils.io.core.Output import kotlinx.coroutines.io.ByteWriteChannel
import io.ktor.utils.io.core.writeFully import kotlinx.coroutines.io.readAvailable
import io.ktor.utils.io.pool.useInstance
import io.ktor.utils.io.readAvailable
import kotlinx.io.OutputStream import kotlinx.io.OutputStream
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import kotlinx.io.core.Output
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -54,6 +54,19 @@ suspend fun ByteReadChannel.copyTo(dst: Output) { ...@@ -54,6 +54,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 /* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/** /**
...@@ -107,6 +120,44 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) { ...@@ -107,6 +120,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 /*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/** /**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
......
...@@ -9,9 +9,8 @@ ...@@ -9,9 +9,8 @@
package net.mamoe.mirai.utils.cryptor package net.mamoe.mirai.utils.cryptor
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import io.ktor.utils.io.core.readFully import kotlinx.io.pool.useInstance
import io.ktor.utils.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toByteArray
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import io.ktor.utils.io.pool.DefaultPool import kotlinx.io.pool.DefaultPool
import kotlinx.io.pool.ObjectPool
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import io.ktor.utils.io.core.Closeable import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
......
...@@ -13,11 +13,11 @@ ...@@ -13,11 +13,11 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import io.ktor.utils.io.charsets.Charset import kotlinx.io.charsets.Charset
import io.ktor.utils.io.charsets.Charsets import kotlinx.io.charsets.Charsets
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import io.ktor.utils.io.core.String import kotlinx.io.core.String
import io.ktor.utils.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.utils.checkOffsetAndLength import net.mamoe.mirai.utils.checkOffsetAndLength
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
......
...@@ -9,19 +9,17 @@ ...@@ -9,19 +9,17 @@
package net.mamoe.mirai.utils.io 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream import kotlinx.io.InputStream
import kotlinx.serialization.InternalSerializationApi import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlinx.serialization.InternalSerializationApi
/** /**
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.ObjectPool
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
...@@ -200,3 +202,13 @@ fun ByteArray.toInt(): Int = ...@@ -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( (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 255
) shl 0) ) 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,12 +13,11 @@ ...@@ -13,12 +13,11 @@
package net.mamoe.mirai.utils.io 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.*
import io.ktor.utils.io.pool.useInstance
import kotlinx.io.OutputStream import kotlinx.io.OutputStream
import kotlinx.serialization.InternalSerializationApi import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
...@@ -27,6 +26,7 @@ import kotlin.contracts.contract ...@@ -27,6 +26,7 @@ import kotlin.contracts.contract
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlinx.serialization.InternalSerializationApi
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
fun ByteReadPacket.copyTo(outputStream: OutputStream) { fun ByteReadPacket.copyTo(outputStream: OutputStream) {
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
package net.mamoe.mirai.utils.io 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.MiraiInternalAPI
import net.mamoe.mirai.utils.coerceAtMostOrFail import net.mamoe.mirai.utils.coerceAtMostOrFail
import net.mamoe.mirai.utils.cryptor.TEA import net.mamoe.mirai.utils.cryptor.TEA
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.utils.io.core.toByteArray import kotlinx.io.core.toByteArray
/** /**
* 时间戳 * 时间戳
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
package net.mamoe.mirai package net.mamoe.mirai
import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
...@@ -66,12 +66,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA ...@@ -66,12 +66,6 @@ actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIA
*/ */
actual abstract val context: Context actual abstract val context: Context
/**
* 账号信息
*/
@MiraiInternalAPI
actual abstract val account: BotAccount
/** /**
* QQ 号码. 实际类型为 uint * QQ 号码. 实际类型为 uint
*/ */
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.io.core.Input
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaHappyAPI import net.mamoe.mirai.JavaHappyAPI
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
......
...@@ -11,8 +11,10 @@ ...@@ -11,8 +11,10 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import io.ktor.utils.io.core.Input import kotlinx.coroutines.io.ByteWriteChannel
import io.ktor.utils.io.core.use 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.Contact
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
...@@ -41,17 +43,15 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con ...@@ -41,17 +43,15 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
// endregion // endregion
// region 发送图片 // region 发送图片
suspend inline fun sendImage(image: BufferedImage) = 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: URL) = subject.sendImage(image) suspend inline fun sendImage(image: Input): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: Input) = subject.sendImage(image) suspend inline fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: InputStream) = subject.sendImage(image) suspend inline fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image)
suspend inline fun sendImage(image: File) = subject.sendImage(image)
// endregion // endregion
// region 上传图片 (扩展) // region 上传图片 (扩展)
suspend inline fun BufferedImage.upload(): Image = upload(subject) suspend inline fun BufferedImage.upload(): Image = upload(subject)
suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject) suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject)
suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject) suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject)
suspend inline fun InputStream.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 ...@@ -59,12 +59,11 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
// endregion 上传图片 (扩展) // endregion 上传图片 (扩展)
// region 发送图片 (扩展) // region 发送图片 (扩展)
suspend inline fun BufferedImage.send() = sendTo(subject) suspend inline fun BufferedImage.send(): MessageReceipt<TSubject> = sendTo(subject)
suspend inline fun URL.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun URL.sendAsImage() = sendAsImageTo(subject) suspend inline fun Input.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun Input.sendAsImage() = sendAsImageTo(subject) suspend inline fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun InputStream.sendAsImage() = sendAsImageTo(subject) suspend inline fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
suspend inline fun File.sendAsImage() = sendAsImageTo(subject)
// endregion 发送图片 (扩展) // endregion 发送图片 (扩展)
// region 下载图片 (扩展) // region 下载图片 (扩展)
...@@ -80,6 +79,26 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con ...@@ -80,6 +79,26 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
*/ */
suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output) 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.downloadAsStream(): InputStream = channel().asInputStream()
suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() } suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() }
......
...@@ -59,15 +59,7 @@ actual open class MessageReceipt<C : Contact> actual constructor( ...@@ -59,15 +59,7 @@ actual open class MessageReceipt<C : Contact> actual constructor(
actual suspend fun recall() { actual suspend fun recall() {
@Suppress("BooleanLiteralArgument") @Suppress("BooleanLiteralArgument")
if (_isRecalled.compareAndSet(false, true)) { if (_isRecalled.compareAndSet(false, true)) {
when (val contact = target) { target.bot.recall(source)
is Group -> {
contact.bot.recall(source)
}
is QQ -> {
TODO()
}
else -> error("Unknown contact type")
}
} else error("message is already or planned to be recalled") } else error("message is already or planned to be recalled")
} }
......
...@@ -11,11 +11,12 @@ ...@@ -11,11 +11,12 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
import io.ktor.utils.io.core.Input
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.Input
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.data.Image 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.OverFileSizeMaxException
import net.mamoe.mirai.utils.sendTo import net.mamoe.mirai.utils.sendTo
import net.mamoe.mirai.utils.toExternalImage import net.mamoe.mirai.utils.toExternalImage
...@@ -36,37 +37,41 @@ import java.net.URL ...@@ -36,37 +37,41 @@ import java.net.URL
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 中将文件作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend fun File.sendAsImageTo(contact: Contact) { suspend fun <C : Contact> File.sendAsImageTo(contact: C): MessageReceipt<C> {
require(this.exists() && this.canRead()) require(this.exists() && this.canRead())
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
} }
// endregion // endregion
...@@ -78,35 +83,39 @@ suspend fun File.sendAsImageTo(contact: Contact) { ...@@ -78,35 +83,39 @@ suspend fun File.sendAsImageTo(contact: Contact) {
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] * 在 [Dispatchers.IO] 中将文件作为图片上传后构造 [Image]
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend fun File.uploadAsImage(contact: Contact): Image { suspend fun File.uploadAsImage(contact: Contact): OfflineImage {
require(this.exists() && this.canRead()) require(this.exists() && this.canRead())
return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
} }
...@@ -120,35 +129,37 @@ suspend fun File.uploadAsImage(contact: Contact): Image { ...@@ -120,35 +129,37 @@ suspend fun File.uploadAsImage(contact: Contact): Image {
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 中将文件作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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 // endregion
...@@ -159,34 +170,34 @@ suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this) ...@@ -159,34 +170,34 @@ suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片上传, 但不发送 * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片上传, 但不发送 * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 到临时文件并将其作为图片上传, 但不发送 * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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] 中将文件作为图片上传, 但不发送 * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @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 // endregion
\ No newline at end of file
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.utils.io.core.use
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
...@@ -21,6 +20,7 @@ import kotlinx.coroutines.io.writeFully ...@@ -21,6 +20,7 @@ import kotlinx.coroutines.io.writeFully
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.use
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import java.awt.Image import java.awt.Image
......
...@@ -11,14 +11,14 @@ ...@@ -11,14 +11,14 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.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.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext 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 net.mamoe.mirai.utils.io.getRandomString
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
......
...@@ -13,7 +13,7 @@ package net.mamoe.mirai.utils ...@@ -13,7 +13,7 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO 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 net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.* import java.io.*
import java.net.InetAddress import java.net.InetAddress
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.utils.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
......
...@@ -4,7 +4,7 @@ private var isAddSuppressedSupported: Boolean = true ...@@ -4,7 +4,7 @@ private var isAddSuppressedSupported: Boolean = true
@PublishedApi @PublishedApi
internal actual fun Throwable.addSuppressedMirai(e: Throwable) { internal actual fun Throwable.addSuppressedMirai(e: Throwable) {
if (this === e) { if (this == e) {
return return
} }
if (!isAddSuppressedSupported) { if (!isAddSuppressedSupported) {
......
...@@ -9,13 +9,13 @@ ...@@ -9,13 +9,13 @@
package net.mamoe.mirai.utils.io 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.Dispatchers
import kotlinx.coroutines.withContext 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 net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
......
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
package net.mamoe.mirai.utils.io 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.Dispatchers
import kotlinx.coroutines.withContext 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.net.InetSocketAddress
import java.nio.channels.DatagramChannel import java.nio.channels.DatagramChannel
import java.nio.channels.ReadableByteChannel 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