diff --git a/mirai-core-qqandroid/README.md b/mirai-core-qqandroid/README.md index de5653bb7..f75364fbb 100644 --- a/mirai-core-qqandroid/README.md +++ b/mirai-core-qqandroid/README.md @@ -1,3 +1,5 @@ # mirai-core-qqandroid -Protocol support for QQ for Android for Mirai. + +QQ for Android 8.2.7 协议实现. +相较于 `mirai-core`, 此模块仅提供协议和功能的实现, 不提供额外的公开的 API. \ No newline at end of file diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 8f85846b3..df53ba41b 100644 --- a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -17,11 +17,11 @@ import kotlinx.coroutines.io.* import kotlinx.io.core.* import kotlinx.io.pool.useInstance import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.toReadPacket import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.toReadPacket import java.nio.ByteBuffer @OptIn(MiraiInternalAPI::class) diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.kt new file mode 100644 index 000000000..ff8dbc886 --- /dev/null +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.kt @@ -0,0 +1,102 @@ +@file:Suppress("unused", "unused") + +package net.mamoe.mirai.qqandroid.utils + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.util.KtorExperimentalAPI +import kotlinx.io.pool.useInstance +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.net.Inet4Address +import java.security.MessageDigest +import java.util.zip.Deflater +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream +import java.util.zip.Inflater + +internal actual object MiraiPlatformUtils { + actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val inflater = Inflater() + inflater.reset() + ByteArrayOutputStream().use { output -> + inflater.setInput(data, offset, length) + ByteArrayPool.useInstance { + while (!inflater.finished()) { + output.write(it, 0, inflater.inflate(it)) + } + } + + inflater.end() + return output.toByteArray() + } + } + + actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val deflater = Deflater() + deflater.setInput(data, offset, length) + deflater.finish() + + ByteArrayPool.useInstance { + return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } + } + } + + + actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() + } + + actual inline fun md5(str: String): ByteArray = md5(str.toByteArray()) + + /** + * Ktor HttpClient. 不同平台使用不同引擎. + */ + @OptIn(KtorExperimentalAPI::class) + actual val Http: HttpClient = HttpClient(CIO) + + /** + * Localhost 解析 + */ + actual fun localIpAddress(): String = runCatching { + Inet4Address.getLocalHost().hostAddress + }.getOrElse { "192.168.1.123" } + + fun md5(stream: InputStream): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + stream.readInSequence { + digest.update(it.toByte()) + } + return digest.digest() + } + + + private inline fun InputStream.readInSequence(block: (Int) -> Unit) { + var read: Int + while (this.read().also { read = it } != -1) { + block(read) + } + } + + actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray { + ByteArrayOutputStream().use { buf -> + GZIPOutputStream(buf).use { gzip -> + data.inputStream(offset, length).use { t -> t.copyTo(gzip) } + } + buf.flush() + return buf.toByteArray() + } + } + + actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray { + return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() } + } +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformDatagramChannel.android.kt similarity index 86% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt rename to mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformDatagramChannel.android.kt index ed02843f3..687fa9e9c 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformDatagramChannel.android.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -23,12 +23,13 @@ import java.nio.channels.WritableByteChannel /** * 多平台适配的 DatagramChannel. */ -actual class PlatformDatagramChannel actual constructor( +internal actual class PlatformDatagramChannel actual constructor( serverHost: String, serverPort: Short ) : Closeable { @PublishedApi - internal val channel: DatagramChannel = DatagramChannel.open().connect(InetSocketAddress(serverHost, serverPort.toInt())) + internal val channel: DatagramChannel = + DatagramChannel.open().connect(InetSocketAddress(serverHost, serverPort.toInt())) actual val isOpen: Boolean get() = channel.isOpen override fun close() = channel.close() diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt similarity index 94% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt rename to mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt index 90711ef01..4c1549858 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -16,7 +16,6 @@ import kotlinx.io.core.Closeable import kotlinx.io.core.ExperimentalIoApi import kotlinx.io.streams.readPacketAtMost import kotlinx.io.streams.writePacket -import net.mamoe.mirai.utils.MiraiInternalAPI import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.IOException @@ -25,8 +24,7 @@ import java.net.Socket /** * 多平台适配的 TCP Socket. */ -@MiraiInternalAPI -actual class PlatformSocket : Closeable { +internal actual class PlatformSocket : Closeable { private lateinit var socket: Socket actual val isOpen: Boolean diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/addSuppressedMirai.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/addSuppressedMirai.kt new file mode 100644 index 000000000..816beed09 --- /dev/null +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/addSuppressedMirai.kt @@ -0,0 +1,27 @@ +@file:Suppress("DuplicatedCode") + +package net.mamoe.mirai.qqandroid.utils + +import android.os.Build + + +private var isAddSuppressedSupported: Boolean = true + +@PublishedApi +internal actual fun Throwable.addSuppressedMirai(e: Throwable) { + if (this == e) { + return + } + if (!isAddSuppressedSupported) { + return + } + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + this.addSuppressed(e) + } else { + isAddSuppressedSupported = false + } + } catch (e: Exception) { + isAddSuppressedSupported = false + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt index 5924bef8e..9972c0045 100644 --- a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHAndroid.kt @@ -10,9 +10,9 @@ package net.mamoe.mirai.qqandroid.utils.cryptor import android.annotation.SuppressLint +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils.md5 import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.MiraiPlatformUtils.md5 import java.security.* import java.security.spec.ECGenParameterSpec import java.security.spec.X509EncodedKeySpec diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/setVisible.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/setVisible.kt similarity index 85% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/setVisible.kt rename to mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/setVisible.kt index ffecc5e9b..9952fb58a 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/setVisible.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/utils/setVisible.kt @@ -1,4 +1,4 @@ -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaField diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 85764aa94..7d5a11127 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -45,10 +45,11 @@ import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.* import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils +import net.mamoe.mirai.qqandroid.utils.encodeToString import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString +import net.mamoe.mirai.qqandroid.utils.toReadPacket import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.encodeToString -import net.mamoe.mirai.utils.io.toReadPacket import kotlin.collections.asSequence import kotlin.coroutines.CoroutineContext import kotlin.math.absoluteValue diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt index e142ced00..8fde06181 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt @@ -37,8 +37,9 @@ import net.mamoe.mirai.qqandroid.network.highway.postImage import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils +import net.mamoe.mirai.qqandroid.utils.toUHexString import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.toUHexString import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt index 070d578fb..69e05659a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialFormat import kotlinx.serialization.SerializationStrategy -interface IOFormat : SerialFormat { +internal interface IOFormat : SerialFormat { fun dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt index d3eb05a7b..47b46c4cf 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt @@ -38,7 +38,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.ZERO_TYPE import net.mamoe.mirai.qqandroid.io.serialization.jce.JceHead import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId import net.mamoe.mirai.qqandroid.utils.io.readString -import net.mamoe.mirai.utils.io.toReadPacket +import net.mamoe.mirai.qqandroid.utils.toReadPacket @PublishedApi internal val CharsetGBK = Charset.forName("GBK") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt index 8ec7e19c9..6f98801c1 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt @@ -19,7 +19,7 @@ import kotlinx.serialization.modules.SerialModule import net.mamoe.mirai.qqandroid.io.serialization.IOFormat import net.mamoe.mirai.qqandroid.io.serialization.JceCharset import net.mamoe.mirai.qqandroid.io.serialization.JceOld -import net.mamoe.mirai.utils.io.toReadPacket +import net.mamoe.mirai.qqandroid.utils.toReadPacket /** * Jce 数据结构序列化和反序列化器. diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt index 69ee59e10..59cc1ab9a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt @@ -23,11 +23,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.firstValue -import net.mamoe.mirai.utils.io.read +import net.mamoe.mirai.qqandroid.utils.read import net.mamoe.mirai.qqandroid.utils.io.readPacketExact -import net.mamoe.mirai.utils.io.toReadPacket -import net.mamoe.mirai.utils.io.toUHexString +import net.mamoe.mirai.qqandroid.utils.toReadPacket import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -76,6 +74,7 @@ internal fun ByteReadPacket.decodeUniPacket(deserializer: Deseria } } } +private fun Map.firstValue(): V = this.entries.first().value internal fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { val request = this.readJceStruct(RequestPacket.serializer()) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt index ed7edd4f0..95384ddec 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt @@ -22,10 +22,11 @@ import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils +import net.mamoe.mirai.qqandroid.utils.encodeToString +import net.mamoe.mirai.qqandroid.utils.hexToBytes import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.encodeToString -import net.mamoe.mirai.utils.io.hexToBytes -import net.mamoe.mirai.utils.io.read +import net.mamoe.mirai.qqandroid.utils.read private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt index 6cf4280b1..3fb9d20b5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt @@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.message import net.mamoe.mirai.message.data.Face import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody -import net.mamoe.mirai.utils.io.hexToBytes -import net.mamoe.mirai.utils.io.toByteArray +import net.mamoe.mirai.qqandroid.utils.hexToBytes +import net.mamoe.mirai.qqandroid.utils.toByteArray internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes() diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt index e2d2e4efe..e3ab8744c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt @@ -14,9 +14,8 @@ import net.mamoe.mirai.message.data.OfflineGroupImage import net.mamoe.mirai.message.data.OnlineFriendImage import net.mamoe.mirai.message.data.OnlineGroupImage import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.qqandroid.utils.hexToBytes import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.io.hexToBytes - internal class OnlineGroupImageImpl( internal val delegate: ImMsgBody.CustomFace diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index f58350f85..5e8ca7509 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -36,11 +36,12 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin +import net.mamoe.mirai.qqandroid.utils.PlatformSocket import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.useBytes +import net.mamoe.mirai.qqandroid.utils.tryNTimes +import net.mamoe.mirai.qqandroid.utils.tryNTimesOrException import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.PlatformSocket import kotlin.coroutines.CoroutineContext import kotlin.jvm.Volatile import kotlin.time.ExperimentalTime @@ -570,10 +571,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler /** * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms) - * - * @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry */ - suspend fun OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 0): E { + suspend fun OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 1): E { require(timeoutMillis > 100) { "timeoutMillis must > 100" } require(retry >= 0) { "retry must >= 0" } @@ -599,7 +598,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler return withTimeoutOrNull(timeoutMillis) { handler.await() // 不要 `withTimeout`. timeout 的报错会不正常. - } as E? ?: throw TimeoutException("timeout receiving response of $commandName") + } as E? ?: throw TimeoutException("timeout receiving response of $commandName") } if (retry == 0) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index fda220a5c..07507ca41 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -21,11 +21,30 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv +import net.mamoe.mirai.qqandroid.utils.* +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH -import net.mamoe.mirai.utils.* import net.mamoe.mirai.qqandroid.utils.cryptor.TEA -import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.qqandroid.utils.read +import net.mamoe.mirai.qqandroid.utils.toUHexString +import net.mamoe.mirai.utils.* +import kotlin.random.Random +import kotlin.random.nextInt + +internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress) + +/** + * Defaults "%4;7t>;28 internal typealias Pt4TokenMap = MutableMap @@ -323,7 +358,13 @@ internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String( internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) -internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) = +internal fun parsePSKeyMapAndPt4TokenMap( + data: ByteArray, + creationTime: Long, + expireTime: Long, + outPSKeyMap: PSKeyMap, + outPt4TokenMap: Pt4TokenMap +) = data.read { repeat(readShort().toInt()) { val domain = readUShortLVString() @@ -337,7 +378,8 @@ internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, ex } } -internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) +internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : + KeyWithExpiry(data, creationTime, expireTime) internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt index 03ab3af1a..b85a03415 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt @@ -31,10 +31,10 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.copyAndClose -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.PlatformSocket import net.mamoe.mirai.qqandroid.utils.io.withUse import kotlinx.serialization.InternalSerializationApi +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.PlatformSocket @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @Suppress("SpellCheckingInspection") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt index 9f38d8f7a..0f9a8b926 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt @@ -24,9 +24,10 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidClient 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.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.* import kotlinx.serialization.InternalSerializationApi -import net.mamoe.mirai.utils.MiraiPlatformUtils +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils +import net.mamoe.mirai.qqandroid.utils.io.chunkedFlow @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) internal fun createImageDataPacketSequence( // RequestDataTrans diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 6a0d35845..7ee8c19ac 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -27,20 +27,20 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray +import net.mamoe.mirai.qqandroid.utils.* +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.qqandroid.utils.io.useBytes import net.mamoe.mirai.qqandroid.utils.io.withUse +import net.mamoe.mirai.qqandroid.utils.toReadPacket +import net.mamoe.mirai.qqandroid.utils.toUHexString import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.toInt -import net.mamoe.mirai.utils.io.toReadPacket -import net.mamoe.mirai.utils.io.toUHexString import kotlin.jvm.JvmName - internal sealed class PacketFactory { /** * 筛选从服务器接收到的包时的 commandName @@ -127,9 +127,6 @@ internal typealias PacketConsumer = suspend (packetFactory: PacketFactory, @PublishedApi internal val PacketLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet").withSwitch(false) -/** - * 已知的数据包工厂列表. - */ @OptIn(ExperimentalUnsignedTypes::class) internal object KnownPacketFactories { object OutgoingFactories : List> by mutableListOf( @@ -292,9 +289,6 @@ internal object KnownPacketFactories { lateinit var consumer: PacketConsumer } - /** - * 解析 SSO 层包装 - */ @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket<*> { val commandName: String @@ -363,14 +357,14 @@ internal object KnownPacketFactories { ) { @Suppress("DuplicatedCode") check(readByte().toInt() == 2) - this.discardExact(2) // 27 + 2 + body.size - this.discardExact(2) // const, =8001 - this.readUShort() // commandId - this.readShort() // const, =0x0001 - this.readUInt().toLong() // qq + this.discardExact(2) + this.discardExact(2) + this.readUShort() + this.readShort() + this.readUInt().toLong() val encryptionMethod = this.readUShort().toInt() - this.discardExact(1) // const = 0 + this.discardExact(1) val packet = when (encryptionMethod) { 4 -> { var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt()) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index ce54b8901..38b0dc56c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -16,12 +16,12 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.toByteArray import kotlinx.io.core.writeFully import net.mamoe.mirai.qqandroid.network.protocol.LoginType +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.io.* +import net.mamoe.mirai.qqandroid.utils.toByteArray import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiPlatformUtils import net.mamoe.mirai.utils.currentTimeMillis -import net.mamoe.mirai.utils.io.* import kotlin.random.Random /** diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt index 4c19c70c8..6d132e34b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/MultiMsg.kt @@ -12,14 +12,11 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.contact.Group import net.mamoe.mirai.message.data.* import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf -import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend -import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.message.toRichTextElems import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.QQAndroidClient @@ -31,9 +28,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiPlatformUtils -import net.mamoe.mirai.utils._miraiContentToString +import net.mamoe.mirai.qqandroid.utils._miraiContentToString internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor( val data: ByteArray, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index b6de49a8e..6e37ad29f 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -27,8 +27,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.* import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.qqandroid.utils.encodeToString import net.mamoe.mirai.utils.daysToSeconds -import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo @OptIn(LowLevelAPI::class) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index cb803f549..6737b317c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -44,8 +44,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.debug -import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.toUHexString +import net.mamoe.mirai.qqandroid.utils.read +import net.mamoe.mirai.qqandroid.utils.toUHexString internal class OnlinePush { /** diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt index 5c1ac14e4..b3ac00e05 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt @@ -21,7 +21,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket -import net.mamoe.mirai.utils.io.toUHexString +import net.mamoe.mirai.qqandroid.utils.toUHexString import net.mamoe.mirai.qqandroid.network.protocol.data.jce.PushReq as PushReqJceStruct diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt index 5c2124c8b..72ebc7784 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt @@ -10,11 +10,12 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.* +import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.guid import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestMSFForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RspMSFForceOffline @@ -22,11 +23,11 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.SvcReqRegister import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x769 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.StatSvcGetOnline import net.mamoe.mirai.qqandroid.network.protocol.packet.* +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils.NetworkType +import net.mamoe.mirai.qqandroid.utils.encodeToString +import net.mamoe.mirai.qqandroid.utils.toReadPacket import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiPlatformUtils -import net.mamoe.mirai.utils.io.encodeToString -import net.mamoe.mirai.utils.io.toReadPacket @Suppress("EnumEntryName") internal enum class RegPushReason { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt index c8bc93354..7a2ee8eb0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt @@ -12,18 +12,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login import io.ktor.util.InternalAPI import kotlinx.io.core.* -import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.* import net.mamoe.mirai.qqandroid.network.protocol.LoginType import net.mamoe.mirai.qqandroid.network.protocol.packet.* -import net.mamoe.mirai.qqandroid.utils.GuidSource -import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag +import net.mamoe.mirai.qqandroid.utils.* +import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.qqandroid.utils.guidFlag import net.mamoe.mirai.qqandroid.utils.io.* -import net.mamoe.mirai.utils.* -import net.mamoe.mirai.qqandroid.utils.cryptor.TEA -import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.currentTimeSeconds +import net.mamoe.mirai.utils.error internal class WtLogin { /** @@ -310,7 +310,6 @@ internal class WtLogin { } @InternalAPI - @OptIn(MiraiDebugAPI::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse { discardExact(2) // subCommand @@ -369,7 +368,6 @@ internal class WtLogin { } @InternalAPI - @OptIn(MiraiDebugAPI::class) private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha { /* java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION: @@ -405,7 +403,6 @@ internal class WtLogin { error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString()) } - @OptIn(MiraiDebugAPI::class) private fun onLoginSuccess(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success { val client = bot.client //println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() }) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt new file mode 100644 index 000000000..b4b49f6b5 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt @@ -0,0 +1,25 @@ +package net.mamoe.mirai.qqandroid.utils + +import kotlinx.io.pool.DefaultPool + +/** + * 缓存 [ByteArray] 实例的 [ObjectPool] + */ +internal object ByteArrayPool : DefaultPool(256) { + /** + * 每一个 [ByteArray] 的大小 + */ + const val BUFFER_SIZE: Int = 81920 / 2 + + override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) + + override fun clearInstance(instance: ByteArray): ByteArray = instance + + fun checkBufferSize(size: Int) { + require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } + } + + fun checkBufferSize(size: Long) { + require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.common.kt similarity index 60% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.common.kt index 1d9c96cbe..cd8a221ac 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.common.kt @@ -1,31 +1,9 @@ -/* - * 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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") - -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils import io.ktor.client.HttpClient +import net.mamoe.mirai.utils.MiraiInternalAPI -/** - * 时间戳. - */ -expect val currentTimeMillis: Long - -inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000 - -/** - * 仅供内部使用的工具类. - * 不写为扩展是为了避免污染命名空间. - */ -@MiraiInternalAPI -expect object MiraiPlatformUtils { +internal expect object MiraiPlatformUtils { fun unzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray @@ -48,7 +26,6 @@ expect object MiraiPlatformUtils { val Http: HttpClient } - @Suppress("DuplicatedCode") // false positive. `this` is not the same for `List` and `ByteArray` internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) { require(offset >= 0) { "offset shouldn't be negative: $offset" } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformDatagramChannel.kt similarity index 74% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformDatagramChannel.kt index 9e4b98f1a..0792b9dd8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformDatagramChannel.kt @@ -7,17 +7,15 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable -import net.mamoe.mirai.utils.MiraiInternalAPI /** * 多平台适配的 DatagramChannel. */ -@MiraiInternalAPI -expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable { +internal expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable { /** * @throws SendPacketInternalException */ @@ -34,9 +32,9 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ -class SendPacketInternalException(cause: Throwable?) : Exception(cause) +internal class SendPacketInternalException(cause: Throwable?) : Exception(cause) /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ -class ReadPacketInternalException(cause: Throwable?) : Exception(cause) \ No newline at end of file +internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt similarity index 91% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt index 70b411fae..acd59c751 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable @@ -16,8 +16,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI /** * 多平台适配的 TCP Socket. */ -@MiraiInternalAPI -expect class PlatformSocket() : Closeable { +internal expect class PlatformSocket() : Closeable { suspend fun connect(serverHost: String, serverPort: Int) /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/byteArrays.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/byteArrays.kt similarity index 82% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/byteArrays.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/byteArrays.kt index 54cfe4507..d0c1df07a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/byteArrays.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/byteArrays.kt @@ -11,14 +11,13 @@ @file:JvmMultifileClass @file:JvmName("Utils") -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.io.charsets.Charset import kotlinx.io.charsets.Charsets import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.String import kotlinx.io.core.use -import net.mamoe.mirai.utils.checkOffsetAndLength import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -31,7 +30,7 @@ import kotlin.jvm.JvmSynthetic @JvmOverloads @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray @OptIn(ExperimentalUnsignedTypes::class) -fun List.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { +internal fun List.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { require(offset >= 0) { "offset shouldn't be negative: $offset" } require(length >= 0) { "length shouldn't be negative: $length" } require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" } @@ -55,7 +54,7 @@ fun List.toUHexString(separator: String = " ", offset: Int = 0, length: In @JvmOverloads @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray @OptIn(ExperimentalUnsignedTypes::class) -fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { +internal fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { this.checkOffsetAndLength(offset, length) if (length == 0) { return "" @@ -76,7 +75,7 @@ fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int @JvmSynthetic @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray @ExperimentalUnsignedTypes -fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { +internal fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { if (length == 0) { return "" } @@ -94,13 +93,14 @@ fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: In } @Suppress("NOTHING_TO_INLINE") -inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset) +internal inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset) -inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) = +@PublishedApi +internal inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) = ByteReadPacket(this, offset = offset, length = length) @OptIn(ExperimentalContracts::class) -inline fun ByteArray.read(t: ByteReadPacket.() -> R): R { +internal inline fun ByteArray.read(t: ByteReadPacket.() -> R): R { contract { callsInPlace(t, InvocationKind.EXACTLY_ONCE) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/contentToString.kt similarity index 95% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/contentToString.kt index 66627edb8..5dbe73edd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/contentToString.kt @@ -9,9 +9,8 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH") -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils -import net.mamoe.mirai.utils.io.toUHexString import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 @@ -22,8 +21,7 @@ private val indent: String = " ".repeat(4) /** * 将所有元素加入转换为多行的字符串表示. */ -@MiraiDebugAPI -internal fun Sequence.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { +private fun Sequence.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform) } @@ -38,9 +36,7 @@ internal fun Sequence.joinToStringPrefixed(prefix: String, transform: (T) * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 */ @Suppress("FunctionName") // 这样就不容易被 IDE 提示 -@MiraiDebugAPI("Extremely slow") -//@Suppress("Unsupported") // false positive -fun Any?._miraiContentToString(prefix: String = ""): String = when (this) { +internal fun Any?._miraiContentToString(prefix: String = ""): String = when (this) { is Unit -> "Unit" is UInt -> "0x" + this.toUHexString("") + "($this)" is UByte -> "0x" + this.toUHexString() + "($this)" @@ -135,7 +131,6 @@ private val KProperty1<*, *>.isConst: Boolean get() = false // on JVM, it will b @Suppress("EXTENSION_SHADOWED_BY_MEMBER") private val KClass<*>.isData: Boolean get() = false // on JVM, it will be resolved to member function -@MiraiDebugAPI private fun Any.contentToStringReflectively( prefix: String, filter: ((name: String, value: Any?) -> Boolean)? = null diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/conversion.kt similarity index 64% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/conversion.kt index 2b6206171..0a7fe289d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/conversion.kt @@ -9,7 +9,7 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused") -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlin.random.Random import kotlin.random.nextInt @@ -22,7 +22,7 @@ import kotlin.random.nextInt /** * 255 -> 00 FF */ -fun Short.toByteArray(): ByteArray = with(toInt()) { +internal fun Short.toByteArray(): ByteArray = with(toInt()) { byteArrayOf( (shr(8) and 0xFF).toByte(), (shr(0) and 0xFF).toByte() @@ -32,7 +32,7 @@ fun Short.toByteArray(): ByteArray = with(toInt()) { /** * 255 -> 00 00 00 FF */ -fun Int.toByteArray(): ByteArray = byteArrayOf( +internal fun Int.toByteArray(): ByteArray = byteArrayOf( ushr(24).toByte(), ushr(16).toByte(), ushr(8).toByte(), @@ -42,7 +42,7 @@ fun Int.toByteArray(): ByteArray = byteArrayOf( /** * 255 -> 00 00 00 FF */ -fun Long.toByteArray(): ByteArray = byteArrayOf( +internal fun Long.toByteArray(): ByteArray = byteArrayOf( (ushr(56) and 0xFF).toByte(), (ushr(48) and 0xFF).toByte(), (ushr(40) and 0xFF).toByte(), @@ -53,40 +53,40 @@ fun Long.toByteArray(): ByteArray = byteArrayOf( (ushr(0) and 0xFF).toByte() ) -fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) +internal fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) /** * 255 -> 00 FF */ -fun UShort.toByteArray(): ByteArray = with(toUInt()) { +internal fun UShort.toByteArray(): ByteArray = with(toUInt()) { byteArrayOf( (shr(8) and 255u).toByte(), (shr(0) and 255u).toByte() ) } -fun Short.toUHexString(separator: String = " "): String = this.toUShort().toUHexString(separator) +internal fun Short.toUHexString(separator: String = " "): String = this.toUShort().toUHexString(separator) -fun UShort.toUHexString(separator: String = " "): String = +internal fun UShort.toUHexString(separator: String = " "): String = this.toInt().shr(8).toUShort().toUByte().toUHexString() + separator + this.toUByte().toUHexString() -fun ULong.toUHexString(separator: String = " "): String = +internal fun ULong.toUHexString(separator: String = " "): String = this.toLong().toUHexString(separator) -fun Long.toUHexString(separator: String = " "): String = +internal fun Long.toUHexString(separator: String = " "): String = this.ushr(32).toUInt().toUHexString(separator) + separator + this.toUInt().toUHexString(separator) /** * 255 -> 00 FF */ -fun UByte.toByteArray(): ByteArray = byteArrayOf((this and 255u).toByte()) +internal fun UByte.toByteArray(): ByteArray = byteArrayOf((this and 255u).toByte()) -fun UByte.toUHexString(): String = this.toByte().toUHexString() +internal fun UByte.toUHexString(): String = this.toByte().toUHexString() /** * 255u -> 00 00 00 FF */ -fun UInt.toByteArray(): ByteArray = byteArrayOf( +internal fun UInt.toByteArray(): ByteArray = byteArrayOf( (shr(24) and 255u).toByte(), (shr(16) and 255u).toByte(), (shr(8) and 255u).toByte(), @@ -96,23 +96,23 @@ fun UInt.toByteArray(): ByteArray = byteArrayOf( /** * 转 [ByteArray] 后再转 hex */ -fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) +internal fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) /** * 转无符号十六进制表示, 并补充首位 `0`. * 转换结果示例: `FF`, `0E` */ -fun Byte.toUHexString(): String = this.toUByte().fixToUHex() +internal fun Byte.toUHexString(): String = this.toUByte().fixToUHex() /** * 转无符号十六进制表示, 并补充首位 `0`. */ -fun Byte.fixToUHex(): String = this.toUByte().fixToUHex() +internal fun Byte.fixToUHex(): String = this.toUByte().fixToUHex() /** * 转无符号十六进制表示, 并补充首位 `0`. */ -fun UByte.fixToUHex(): String = +internal fun UByte.fixToUHex(): String = if (this.toInt() in 0..15) "0${this.toString(16).toUpperCase()}" else this.toString(16).toUpperCase() /** @@ -120,7 +120,7 @@ fun UByte.fixToUHex(): String = * * 这个方法很累, 不建议经常使用. */ -fun String.hexToBytes(): ByteArray = +internal fun String.hexToBytes(): ByteArray = this.split(" ") .asSequence() .filterNot { it.isEmpty() } @@ -133,7 +133,7 @@ fun String.hexToBytes(): ByteArray = * * 这个方法很累, 不建议经常使用. */ -fun String.chunkedHexToBytes(): ByteArray = +internal fun String.chunkedHexToBytes(): ByteArray = this.asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray() /** @@ -141,7 +141,7 @@ fun String.chunkedHexToBytes(): ByteArray = * * 这个方法很累, 不建议经常使用. */ -fun String.autoHexToBytes(): ByteArray = +internal fun String.autoHexToBytes(): ByteArray = this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray() @@ -151,7 +151,7 @@ fun String.autoHexToBytes(): ByteArray = * * 这个方法很累, 不建议经常使用. */ -fun String.hexToUBytes(): UByteArray = +internal fun String.hexToUBytes(): UByteArray = this.split(" ") .asSequence() .filterNot { it.isEmpty() } @@ -159,27 +159,6 @@ fun String.hexToUBytes(): UByteArray = .toList() .toUByteArray() -/** - * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] - */ -fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } - -/** - * 随机生成长度为 [length] 的 [String]. - */ -fun getRandomString(length: Int): String = getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9') - -/** - * 根据所给 [charRange] 随机生成长度为 [length] 的 [String]. - */ -fun getRandomString(length: Int, charRange: CharRange): String = String(CharArray(length) { charRange.random() }) - -/** - * 根据所给 [charRanges] 随机生成长度为 [length] 的 [String]. - */ -fun getRandomString(length: Int, vararg charRanges: CharRange): String = - String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }) - /** * 将 [this] 前 4 个 [Byte] 的 bits 合并为一个 [Int] * @@ -188,15 +167,15 @@ fun getRandomString(length: Int, vararg charRanges: CharRange): String = * 一个 [Int] 有 32 bits * 本函数将 4 个 [Byte] 的 bits 连接得到 [Int] */ -fun ByteArray.toUInt(): UInt = +internal fun ByteArray.toUInt(): UInt = (this[0].toUInt().and(255u) shl 24) + (this[1].toUInt().and(255u) shl 16) + (this[2].toUInt().and(255u) shl 8) + (this[3].toUInt().and( 255u ) shl 0) -fun ByteArray.toUShort(): UShort = +internal fun ByteArray.toUShort(): UShort = ((this[0].toUInt().and(255u) shl 8) + (this[1].toUInt().and(255u) shl 0)).toUShort() -fun ByteArray.toInt(): Int = +internal fun ByteArray.toInt(): Int = (this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and( 255 ) shl 0) \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt index fac3d940f..7ebda47fb 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDH.kt @@ -9,7 +9,7 @@ package net.mamoe.mirai.qqandroid.utils.cryptor -import net.mamoe.mirai.utils.io.chunkedHexToBytes +import net.mamoe.mirai.qqandroid.utils.chunkedHexToBytes expect interface ECDHPrivateKey { fun getEncoded(): ByteArray diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt index bbd9fc83f..3feb46d1a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/TEA.kt @@ -11,10 +11,10 @@ package net.mamoe.mirai.qqandroid.utils.cryptor import kotlinx.io.core.ByteReadPacket import kotlinx.io.pool.useInstance +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.toByteArray +import net.mamoe.mirai.qqandroid.utils.toUHexString import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.toByteArray -import net.mamoe.mirai.utils.io.toUHexString import kotlin.experimental.and import kotlin.experimental.xor import kotlin.jvm.JvmStatic diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt deleted file mode 100644 index 3b41ac790..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 - */ - -@file:JvmName("Utils") -@file:JvmMultifileClass - -package net.mamoe.mirai.qqandroid.utils - -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - -/** - * Inline the block - */ -@OptIn(ExperimentalContracts::class) -@PublishedApi -internal inline fun inline(block: () -> R): R { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - return block() -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/chunked.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/chunked.kt index 278391688..27608dad4 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/chunked.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils.io import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -20,12 +20,13 @@ import kotlinx.io.core.Input import kotlinx.io.pool.useInstance import net.mamoe.mirai.utils.MiraiInternalAPI import kotlinx.serialization.InternalSerializationApi +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool /** * 由 [chunkedFlow] 分割得到的区块 */ -class ChunkedInput( +internal class ChunkedInput( /** * 区块的数据. * 由 [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问. @@ -51,11 +52,16 @@ class ChunkedInput( * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence] */ @OptIn(MiraiInternalAPI::class) -fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow { +internal fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) if (this.remaining <= sizePerPacket.toLong()) { ByteArrayPool.useInstance { buffer -> - return flowOf(ChunkedInput(buffer, this.readAvailable(buffer, 0, sizePerPacket))) + return flowOf( + ChunkedInput( + buffer, + this.readAvailable(buffer, 0, sizePerPacket) + ) + ) } } return flow { @@ -76,7 +82,7 @@ fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow { * 其长度分别为: 300, 300, 300, 100. */ @OptIn(MiraiInternalAPI::class) -fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow { +internal fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) if (this.isClosedForRead) { return flowOf() diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt index 4471a7be8..57f7265c6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt @@ -18,7 +18,6 @@ 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.MiraiInternalAPI import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind @@ -27,21 +26,12 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic import kotlinx.serialization.InternalSerializationApi -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.toReadPacket -import net.mamoe.mirai.utils.io.toUHexString - -@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) -fun ByteReadPacket.copyTo(outputStream: OutputStream) { - ByteArrayPool.useInstance { - while (this.isNotEmpty) { - outputStream.write(it, 0, this.readAvailable(it)) - } - } -} +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.toReadPacket +import net.mamoe.mirai.qqandroid.utils.toUHexString @MiraiInternalAPI -inline fun ByteReadPacket.useBytes( +internal inline fun ByteReadPacket.useBytes( n: Int = remaining.toInt(),//not that safe but adequate block: (data: ByteArray, length: Int) -> R ): R = ByteArrayPool.useInstance { @@ -50,12 +40,12 @@ inline fun ByteReadPacket.useBytes( } @MiraiInternalAPI -inline fun ByteReadPacket.readPacketExact( +internal inline fun ByteReadPacket.readPacketExact( n: Int = remaining.toInt()//not that safe but adequate ): ByteReadPacket = this.readBytes(n).toReadPacket() @OptIn(ExperimentalContracts::class) -inline fun C.withUse(block: C.() -> R): R { +internal inline fun C.withUse(block: C.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } @@ -64,24 +54,23 @@ inline fun C.withUse(block: C.() -> R): R { private inline fun inline(block: () -> R): R = block() -typealias TlvMap = MutableMap +internal typealias TlvMap = MutableMap -inline fun TlvMap.getOrFail(tag: Int): ByteArray { - return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)") +internal inline fun TlvMap.getOrFail(tag: Int): ByteArray { + return this[tag] ?: error("cannot find tlv 0x${tag. toUHexString("")}($tag)") } -inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray { +internal inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray { return this[tag] ?: error(lazyMessage(tag)) } @Suppress("FunctionName") @MiraiInternalAPI -inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = +internal inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = _readTLVMap(true, tagSize, suppressDuplication) -@MiraiDebugAPI @Suppress("DuplicatedCode", "FunctionName") -fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap { +internal fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap { val map = mutableMapOf() var key = 0 @@ -138,18 +127,18 @@ fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplic return map } -inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = +internal inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset) -inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = +internal inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) -inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = +internal inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) @JvmSynthetic -inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = +internal inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) -inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = +internal inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/tryNTimes.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt similarity index 71% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/tryNTimes.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt index b336967f7..5f890747c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/tryNTimes.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/tryNTimes.kt @@ -6,15 +6,23 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("DuplicatedCode") -package net.mamoe.mirai.utils +@file:JvmMultifileClass +@file:JvmName("Utils") + +package net.mamoe.mirai.qqandroid.utils + +import net.mamoe.mirai.utils.MiraiInternalAPI +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName @PublishedApi internal expect fun Throwable.addSuppressedMirai(e: Throwable) @MiraiInternalAPI @Suppress("DuplicatedCode") -inline fun tryNTimes(repeat: Int, block: (Int) -> R): R { +internal inline fun tryNTimes(repeat: Int, block: (Int) -> R): R { var lastException: Throwable? = null repeat(repeat) { @@ -30,24 +38,6 @@ inline fun tryNTimes(repeat: Int, block: (Int) -> R): R { throw lastException!! } -@MiraiInternalAPI -@Suppress("DuplicatedCode") -inline fun tryNTimesOrNull(repeat: Int, block: (Int) -> R): R? { - var lastException: Throwable? = null - - repeat(repeat) { - try { - return block(it) - } catch (e: Throwable) { - if (lastException == null) { - lastException = e - } else lastException!!.addSuppressedMirai(e) - } - } - - return null -} - @MiraiInternalAPI @Suppress("DuplicatedCode") inline fun tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? { diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/io/serialization/JceInputTest.kt similarity index 100% rename from mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt rename to mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/io/serialization/JceInputTest.kt diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/utils/PlatformUtilsTest.kt similarity index 90% rename from mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt rename to mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/utils/PlatformUtilsTest.kt index cead3f84e..ca1520cbf 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/PlatformUtilsTest.kt +++ b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/utils/PlatformUtilsTest.kt @@ -7,10 +7,10 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils import kotlinx.io.core.toByteArray -import net.mamoe.mirai.utils.io.encodeToString +import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.test.Test import kotlin.test.assertEquals diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/TypeConversionTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/utils/TypeConversionTest.kt similarity index 87% rename from mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/TypeConversionTest.kt rename to mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/utils/TypeConversionTest.kt index c0a9084f8..70f99dbca 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/TypeConversionTest.kt +++ b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/utils/TypeConversionTest.kt @@ -7,16 +7,12 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils -import net.mamoe.mirai.utils.io.hexToBytes -import net.mamoe.mirai.utils.io.toByteArray -import net.mamoe.mirai.utils.io.toUHexString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue - class TypeConversionTest { @ExperimentalUnsignedTypes diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt b/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt index 5290d5639..c30fc2271 100644 --- a/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt +++ b/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt @@ -16,12 +16,12 @@ import kotlinx.io.core.Input import kotlinx.io.core.readAvailable import kotlinx.io.core.use import kotlinx.io.pool.useInstance +import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import net.mamoe.mirai.qqandroid.utils.toReadPacket +import net.mamoe.mirai.qqandroid.utils.toUHexString import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLoggerWithSwitch -import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.toReadPacket -import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.withSwitch import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind @@ -30,13 +30,17 @@ import kotlin.contracts.contract val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true) -inline fun ByteArray.debugPrintThis(name: String): ByteArray { +internal inline fun ByteArray.debugPrintThis(name: String): ByteArray { DebugLogger.debug(name + "=" + this.toUHexString()) return this } @OptIn(ExperimentalContracts::class, MiraiInternalAPI::class) -inline fun Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R { +internal inline fun Input.debugIfFail( + name: String = "", + onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, + block: ByteReadPacket.() -> R +): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 041f10968..89c8b65d4 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -22,7 +22,7 @@ import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.ContextImpl import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.ByteArrayPool -import net.mamoe.mirai.utils.io.toReadPacket +import net.mamoe.mirai.qqandroid.utils.toReadPacket import java.nio.ByteBuffer @OptIn(MiraiInternalAPI::class) diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.kt new file mode 100644 index 000000000..f77836300 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/MiraiPlatformUtils.kt @@ -0,0 +1,101 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package net.mamoe.mirai.qqandroid.utils + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.util.KtorExperimentalAPI +import kotlinx.io.pool.useInstance +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.net.Inet4Address +import java.security.MessageDigest +import java.util.zip.Deflater +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream +import java.util.zip.Inflater + +internal actual object MiraiPlatformUtils { + actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val inflater = Inflater() + inflater.reset() + ByteArrayOutputStream().use { output -> + inflater.setInput(data, offset, length) + ByteArrayPool.useInstance { + while (!inflater.finished()) { + output.write(it, 0, inflater.inflate(it)) + } + } + + inflater.end() + return output.toByteArray() + } + } + + actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val deflater = Deflater() + deflater.setInput(data, offset, length) + deflater.finish() + + ByteArrayPool.useInstance { + return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } + } + } + + actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray { + ByteArrayOutputStream().use { buf -> + GZIPOutputStream(buf).use { gzip -> + data.inputStream(offset, length).use { t -> t.copyTo(gzip) } + } + buf.flush() + return buf.toByteArray() + } + } + + actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray { + return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() } + } + + actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() + } + + actual inline fun md5(str: String): ByteArray = md5(str.toByteArray()) + + /** + * Ktor HttpClient. 不同平台使用不同引擎. + */ + @OptIn(KtorExperimentalAPI::class) + actual val Http: HttpClient = HttpClient(CIO) + + /** + * Localhost 解析 + */ + actual fun localIpAddress(): String = runCatching { + Inet4Address.getLocalHost().hostAddress + }.getOrElse { "192.168.1.123" } + + fun md5(stream: InputStream): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + stream.use { input -> + object : OutputStream() { + override fun write(b: Int) { + digest.update(b.toByte()) + } + }.use { output -> + input.copyTo(output) + } + } + return digest.digest() + } + +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt similarity index 96% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt rename to mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt index 90711ef01..3c892b9af 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -25,8 +25,7 @@ import java.net.Socket /** * 多平台适配的 TCP Socket. */ -@MiraiInternalAPI -actual class PlatformSocket : Closeable { +internal actual class PlatformSocket : Closeable { private lateinit var socket: Socket actual val isOpen: Boolean diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocketJvm.kt similarity index 96% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt rename to mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocketJvm.kt index 1cf48c36f..8e78814e3 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocketJvm.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.utils.io +package net.mamoe.mirai.qqandroid.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -24,7 +24,7 @@ import java.nio.channels.WritableByteChannel /** * 多平台适配的 DatagramChannel. */ -actual class PlatformDatagramChannel actual constructor( +internal actual class PlatformDatagramChannel actual constructor( serverHost: String, serverPort: Short ) : Closeable { diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/addSuppressedMirai.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/addSuppressedMirai.kt new file mode 100644 index 000000000..0feb87627 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/addSuppressedMirai.kt @@ -0,0 +1,5 @@ +package net.mamoe.mirai.qqandroid.utils + +@PublishedApi +internal actual fun Throwable.addSuppressedMirai(e: Throwable) { +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt index fae5fb717..c194bce25 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/cryptor/ECDHJvm.kt @@ -9,8 +9,8 @@ package net.mamoe.mirai.qqandroid.utils.cryptor +import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiPlatformUtils import org.bouncycastle.jce.provider.BouncyCastleProvider import java.security.* import java.security.spec.ECGenParameterSpec diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setVisible.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/setVisible.kt similarity index 85% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setVisible.kt rename to mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/setVisible.kt index ffecc5e9b..9952fb58a 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setVisible.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/setVisible.kt @@ -1,4 +1,4 @@ -package net.mamoe.mirai.utils +package net.mamoe.mirai.qqandroid.utils import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaField diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt deleted file mode 100644 index 75e32ef35..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 net.mamoe.mirai.utils - -import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.BotNetworkHandler -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -@Suppress("ClassName", "PropertyName") -actual open class BotConfiguration actual constructor() { - /** - * 日志记录器 - */ - actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") } - /** - * 网络层日志构造器 - */ - actual var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") } - /** - * 设备信息覆盖. 默认使用随机的设备信息. - */ - actual var deviceInfo: ((Context) -> DeviceInfo)? = null - - /** - * 父 [CoroutineContext] - */ - actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext - - /** - * 心跳周期. 过长会导致被服务器断开连接. - */ - actual var heartbeatPeriodMillis: Long = 60.secondsToMillis - /** - * 每次心跳时等待结果的时间. - * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. - */ - actual var heartbeatTimeoutMillis: Long = 2.secondsToMillis - /** - * 心跳失败后的第一次重连前的等待时间. - */ - actual var firstReconnectDelayMillis: Long = 5.secondsToMillis - /** - * 重连失败后, 继续尝试的每次等待时间 - */ - actual var reconnectPeriodMillis: Long = 5.secondsToMillis - /** - * 最多尝试多少次重连 - */ - actual var reconnectionRetryTimes: Int = Int.MAX_VALUE - /** - * 验证码处理器 - */ - actual var loginSolver: LoginSolver = LoginSolver.Default - - actual companion object { - /** - * 默认的配置实例 - */ - @JvmStatic - actual val Default = BotConfiguration() - } - - actual operator fun _NoNetworkLog.unaryPlus() { - networkLoggerSupplier = supplier - } - - /** - * 不记录网络层的 log. - * 网络层 log 包含包接收, 包发送, 和一些调试用的记录. - */ - @BotConfigurationDsl - actual val NoNetworkLog: _NoNetworkLog - get() = _NoNetworkLog - - - @BotConfigurationDsl - actual object _NoNetworkLog { - internal val supplier = { _: BotNetworkHandler -> SilentLogger } - } -} - -/** - * 使用文件系统存储设备信息. - */ -@BotConfigurationDsl -inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: String) { - /** - * 使用 "device.json" 存储设备信息 - */ - @BotConfigurationDsl - companion object ByDeviceDotJson -} - -/** - * 验证码, 设备锁解决器 - */ -actual abstract class LoginSolver { - actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? - actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? - actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? - - actual companion object { - actual val Default: LoginSolver - get() = object : LoginSolver() { - override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { - error("should be implemented manually by you") - } - - override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { - error("should be implemented manually by you") - } - - override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { - error("should be implemented manually by you") - } - } - } - -} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/Context.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/Context.android.kt similarity index 100% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/Context.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/Context.android.kt diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImage.android.kt similarity index 97% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImage.android.kt index 2f433f224..9d6351218 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImage.android.kt @@ -10,7 +10,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.utils - import android.graphics.Bitmap import android.graphics.BitmapFactory import kotlinx.coroutines.Dispatchers.IO @@ -21,6 +20,7 @@ import kotlinx.io.core.copyTo import kotlinx.io.errors.IOException import kotlinx.io.streams.asInput import kotlinx.io.streams.asOutput +import net.mamoe.mirai.utils.internal.md5 import java.io.File import java.io.InputStream import java.net.URL @@ -29,7 +29,6 @@ import java.net.URL * 将各类型图片容器转为 [ExternalImage] */ - /** * 读取 [Bitmap] 的属性, 然后构造 [ExternalImage] */ @@ -53,7 +52,7 @@ fun File.toExternalImage(): ExternalImage { return ExternalImage( width = input.width, height = input.height, - md5 = this.inputStream().use { MiraiPlatformUtils.md5(it) }, + md5 = this.inputStream().use { it.md5() }, imageFormat = this.nameWithoutExtension, input = this.inputStream().asInput(IoBuffer.Pool), inputSize = this.length(), diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/LoginSolver.android.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/LoginSolver.android.kt new file mode 100644 index 000000000..586b94678 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/LoginSolver.android.kt @@ -0,0 +1,48 @@ +/* + * 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 net.mamoe.mirai.utils + +import net.mamoe.mirai.Bot +import java.io.File + +actual typealias Throws = kotlin.jvm.Throws + +/** + * 验证码, 设备锁解决器 + */ +actual abstract class LoginSolver { + actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? + actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? + actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? + + actual companion object { + actual val Default: LoginSolver + get() = object : LoginSolver() { + override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { + error("should be implemented manually by you") + } + + override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { + error("should be implemented manually by you") + } + + override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { + error("should be implemented manually by you") + } + } + } + +} + +internal actual fun getFileBasedDeviceInfoSupplier(filename: String): ((Context) -> DeviceInfo)? { + return { + File(filename).loadAsDeviceInfo(it) + } +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.android.kt similarity index 100% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.android.kt diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.android.kt similarity index 91% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.android.kt index f44ad9ae1..109103e48 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.android.kt @@ -20,9 +20,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonConfiguration -import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress -import net.mamoe.mirai.utils.MiraiPlatformUtils.md5 +import net.mamoe.mirai.utils.internal.md5 import java.io.File /** @@ -41,7 +39,10 @@ fun File.loadAsDeviceInfo(context: Context): DeviceInfo { } @OptIn(UnstableDefault::class) -private val JSON = Json(JsonConfiguration.Default) +private val JSON = Json { + isLenient = true + ignoreUnknownKeys = true +} /** * 部分引用指向 [Build]. @@ -109,9 +110,9 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { @OptIn(MiraiInternalAPI::class) override val imsiMd5: ByteArray @SuppressLint("HardwareIds") - get() = md5(kotlin.runCatching { + get() = kotlin.runCatching { (context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).subscriberId.toByteArray() - }.getOrEmpty()) + }.getOrEmpty().md5() override val imei: String @SuppressLint("HardwareIds") @@ -124,11 +125,6 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { } }.getOrElse { "" } - @OptIn(MiraiInternalAPI::class) - override val ipAddress: ByteArray - get() = kotlin.runCatching { - localIpAddress().split(".").map { it.toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: ByteArray(4) - }.getOrElse { ByteArray(4) } override val androidId: ByteArray get() = Build.ID.toByteArray() override val apn: ByteArray get() = "wifi".toByteArray() diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.android.kt similarity index 87% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.android.kt index 8bf594b5a..ab617e14d 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.android.kt @@ -1,4 +1,6 @@ -package net.mamoe.mirai.utils +@file:Suppress("DuplicatedCode") + +package net.mamoe.mirai.utils.internal import android.os.Build diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/internal/md5.android.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/internal/md5.android.kt new file mode 100644 index 000000000..13db2be72 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/internal/md5.android.kt @@ -0,0 +1,22 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") + +package net.mamoe.mirai.utils.internal + +import java.io.InputStream +import java.security.MessageDigest + +internal actual fun ByteArray.md5(offset: Int, length: Int): ByteArray { + this.checkOffsetAndLength(offset, length) + return MessageDigest.getInstance("MD5").apply { update(this@md5, offset, length) }.digest() +} + +internal actual fun InputStream.md5(): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + this.readInSequence { + digest.update(it.toByte()) + } + return digest.digest() +} + +internal actual typealias InputStream = InputStream \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt index ea2c41cd1..f08162e63 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt @@ -8,112 +8,12 @@ */ @file:Suppress("NOTHING_TO_INLINE") +@file:JvmMultifileClass +@file:JvmName("Utils") package net.mamoe.mirai.utils -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.util.KtorExperimentalAPI -import kotlinx.io.pool.useInstance -import net.mamoe.mirai.utils.io.ByteArrayPool -import java.io.ByteArrayOutputStream -import java.io.InputStream -import java.net.Inet4Address -import java.security.MessageDigest -import java.util.zip.Deflater -import java.util.zip.GZIPInputStream -import java.util.zip.GZIPOutputStream -import java.util.zip.Inflater - - /** * 时间戳 */ actual val currentTimeMillis: Long get() = System.currentTimeMillis() - -@MiraiInternalAPI -actual object MiraiPlatformUtils { - actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray { - data.checkOffsetAndLength(offset, length) - if (length == 0) return ByteArray(0) - - val inflater = Inflater() - inflater.reset() - ByteArrayOutputStream().use { output -> - inflater.setInput(data, offset, length) - ByteArrayPool.useInstance { - while (!inflater.finished()) { - output.write(it, 0, inflater.inflate(it)) - } - } - - inflater.end() - return output.toByteArray() - } - } - - actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray { - data.checkOffsetAndLength(offset, length) - if (length == 0) return ByteArray(0) - - val deflater = Deflater() - deflater.setInput(data, offset, length) - deflater.finish() - - ByteArrayPool.useInstance { - return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } - } - } - - - actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { - data.checkOffsetAndLength(offset, length) - return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() - } - - actual inline fun md5(str: String): ByteArray = md5(str.toByteArray()) - - /** - * Ktor HttpClient. 不同平台使用不同引擎. - */ - @OptIn(KtorExperimentalAPI::class) - actual val Http: HttpClient = HttpClient(CIO) - - /** - * Localhost 解析 - */ - actual fun localIpAddress(): String = runCatching { - Inet4Address.getLocalHost().hostAddress - }.getOrElse { "192.168.1.123" } - - fun md5(stream: InputStream): ByteArray { - val digest = MessageDigest.getInstance("md5") - digest.reset() - stream.readInSequence { - digest.update(it.toByte()) - } - return digest.digest() - } - - - private inline fun InputStream.readInSequence(block: (Int) -> Unit) { - var read: Int - while (this.read().also { read = it } != -1) { - block(read) - } - } - - actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray { - ByteArrayOutputStream().use { buf -> - GZIPOutputStream(buf).use { gzip -> - data.inputStream(offset, length).use { t -> t.copyTo(gzip) } - } - buf.flush() - return buf.toByteArray() - } - } - - actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray { - return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() } - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt index c8a87b302..a5b822b13 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt @@ -14,7 +14,7 @@ package net.mamoe.mirai import kotlinx.io.core.toByteArray import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiPlatformUtils +import net.mamoe.mirai.utils.internal.md5 import kotlin.annotation.AnnotationTarget.* @MiraiInternalAPI @@ -28,7 +28,7 @@ data class BotAccount( @MiraiInternalAPI val passwordMd5: ByteArray // md5 ) { - constructor(id: Long, passwordPlainText: String) : this(id, MiraiPlatformUtils.md5(passwordPlainText.toByteArray())) + constructor(id: Long, passwordPlainText: String) : this(id, passwordPlainText.toByteArray().md5()) override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 053b81be6..2e9be4a16 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -22,6 +22,7 @@ import net.mamoe.mirai.network.ForceOfflineException import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.closeAndJoin import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.internal.tryNTimesOrException import kotlin.coroutines.CoroutineContext /* diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/jvmHappy.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/javaFriendly.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/jvmHappy.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/javaFriendly.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt index 607ceb2bc..104cf0ade 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt @@ -18,8 +18,10 @@ import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.event.events.ImageUploadEvent.Failed import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed +import net.mamoe.mirai.message.data.ExperimentalMessageSource import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI @@ -96,6 +98,7 @@ sealed class MessageSendEvent : BotEvent, BotActiveEvent, AbstractCancellableEve /** * 消息撤回事件. 可是任意消息被任意人撤回. */ +@OptIn(ExperimentalMessageSource::class) sealed class MessageRecallEvent : BotEvent { /** * 消息原发送人 @@ -106,6 +109,7 @@ sealed class MessageRecallEvent : BotEvent { * 消息 id. * @see MessageSource.id */ + @ExperimentalMessageSource abstract val messageId: Long /** @@ -118,6 +122,7 @@ sealed class MessageRecallEvent : BotEvent { */ data class FriendRecall( override val bot: Bot, + @ExperimentalMessageSource override val messageId: Long, override val messageTime: Int, /** @@ -132,6 +137,7 @@ sealed class MessageRecallEvent : BotEvent { data class GroupRecall( override val bot: Bot, override val authorId: Long, + @ExperimentalMessageSource override val messageId: Long, override val messageTime: Int, /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index bd1369c22..3b1034336 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -16,14 +16,15 @@ import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.EventDisabled import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.ListeningStatus -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.LockFreeLinkedList +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.isRemoved import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.jvm.JvmField import kotlin.reflect.KClass -val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false) - @MiraiInternalAPI fun , E : Event> KClass.subscribeInternal(listener: L): L { with(this.listeners()) { @@ -67,7 +68,6 @@ internal class Handler } @Suppress("unused") - @OptIn(MiraiDebugAPI::class) override suspend fun onEvent(event: E): ListeningStatus { if (isCompleted || isCancelled) return ListeningStatus.STOPPED if (!isActive) return ListeningStatus.LISTENING @@ -144,8 +144,6 @@ internal object EventListenerManager { internal suspend inline fun Event.broadcastInternal() = coroutineScope { if (EventDisabled) return@coroutineScope - EventLogger.info { "Event broadcast: $this" } - val listeners = this@broadcastInternal::class.listeners() callAndRemoveIfRequired(this@broadcastInternal, listeners) listeners.supertypes.forEach { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt index 35971b65f..3e44b5036 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt @@ -84,6 +84,11 @@ inline fun CoroutineScope.subscribingGetAsync( } +////////////// +//// internal +////////////// + + @PublishedApi internal suspend inline fun subscribingGetOrNullImpl( coroutineScope: CoroutineScope, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt index eb14fc676..cfcbd57ee 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt @@ -18,6 +18,9 @@ import kotlin.jvm.JvmName /** * 链接的两个消息. * + * 不要直接构造 [CombinedMessage], 使用 [Message.plus] + * 要连接多个 [Message], 使用 [buildMessageChain] + * * @see Message.plus * * Left-biased list diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt index 01edbd102..b1687088f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.io.PlatformDatagramChannel /** * Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt index 8bfcafb8c..d20222867 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/ForceOfflineException.kt @@ -1,3 +1,10 @@ package net.mamoe.mirai.network -class ForceOfflineException(override val message: String?) : RuntimeException() \ No newline at end of file +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import net.mamoe.mirai.Bot + +/** + * 当 [Bot] 被迫下线时抛出, 作为 [Job.cancel] 的 `cause` + */ +class ForceOfflineException(override val message: String?) : CancellationException(message) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt index 74573fb1b..370b1ee88 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt @@ -7,10 +7,14 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.network +import net.mamoe.mirai.Bot + /** - * 正常登录失败时抛出 + * 在 [登录][Bot.login] 失败时抛出, 可正常地中断登录过程. */ sealed class LoginFailedException : RuntimeException { constructor() : super() @@ -19,4 +23,17 @@ sealed class LoginFailedException : RuntimeException { constructor(cause: Throwable?) : super(cause) } -class WrongPasswordException(message: String?) : LoginFailedException(message) \ No newline at end of file +/** + * 密码输入错误 + */ +class WrongPasswordException(message: String?) : LoginFailedException(message) + +/** + * 非 mirai 实现的异常 + */ +abstract class CustomLoginFailedException : LoginFailedException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 4f170b68d..dea6a4344 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -6,129 +6,122 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused", "DEPRECATION_ERROR") package net.mamoe.mirai.utils import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.LoginFailedException import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic -/** - * 验证码, 设备锁解决器 - */ -expect abstract class LoginSolver { - /** - * 处理图片验证码. - * 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录. - * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止 - * - * @throws LoginFailedException - */ - abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? - - /** - * 处理滑动验证码. - * 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录. - * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止 - * - * @throws LoginFailedException - * @return 验证码解决成功后获得的 ticket. - */ - abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? - - /** - * 处理不安全设备验证. - * 在处理完成后返回任意内容 (包含 `null`) 均视为处理成功. - * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止. - * - * @return 任意内容. 返回值保留以供未来更新. - * @throws LoginFailedException - */ - abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? - - companion object { - val Default: LoginSolver - } -} - /** * [Bot] 配置 */ @Suppress("PropertyName") -expect open class BotConfiguration() { +open class BotConfiguration { /** * 日志记录器 */ - var botLoggerSupplier: ((Bot) -> MiraiLogger) + var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") } /** * 网络层日志构造器 */ - var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) + var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") } /** * 设备信息覆盖. 默认使用随机的设备信息. */ - var deviceInfo: ((Context) -> DeviceInfo)? + var deviceInfo: ((Context) -> DeviceInfo)? = null /** * 父 [CoroutineContext] */ - var parentCoroutineContext: CoroutineContext + var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext /** * 心跳周期. 过长会导致被服务器断开连接. */ - var heartbeatPeriodMillis: Long + var heartbeatPeriodMillis: Long = 60.secondsToMillis /** * 每次心跳时等待结果的时间. * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. */ - var heartbeatTimeoutMillis: Long + var heartbeatTimeoutMillis: Long = 2.secondsToMillis /** * 心跳失败后的第一次重连前的等待时间. */ - var firstReconnectDelayMillis: Long + var firstReconnectDelayMillis: Long = 5.secondsToMillis /** * 重连失败后, 继续尝试的每次等待时间 */ - var reconnectPeriodMillis: Long + var reconnectPeriodMillis: Long = 5.secondsToMillis /** * 最多尝试多少次重连 */ - var reconnectionRetryTimes: Int + var reconnectionRetryTimes: Int = Int.MAX_VALUE /** * 验证码处理器 */ - var loginSolver: LoginSolver + var loginSolver: LoginSolver = LoginSolver.Default companion object { /** * 默认的配置实例 */ @JvmStatic - val Default: BotConfiguration + val Default = BotConfiguration() } - operator fun _NoNetworkLog.unaryPlus() + /** + * 不显示网络日志 + */ + fun noNetworkLog() { + networkLoggerSupplier = { _: BotNetworkHandler -> SilentLogger } + } /** - * 不记录网络层的 log. - * 网络层 log 包含包接收, 包发送, 和一些调试用的记录. + * 使用文件存储设备信息 + * + * 此函数只在 JVM 有效. 在其他平台将会导致一直使用默认的随机的设备信息. */ - @BotConfigurationDsl - val NoNetworkLog: _NoNetworkLog + @JvmOverloads + fun fileBasedDeviceInfo(filename: String = "device.json") { + deviceInfo = getFileBasedDeviceInfoSupplier(filename) + } - @Suppress("ClassName") - object _NoNetworkLog + + @PlannedRemoval("0.34.0") + @Deprecated( + "use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("fileBasedDeviceInfo") + ) + operator fun FileBasedDeviceInfo.unaryPlus() { + fileBasedDeviceInfo(this.filepath) + } } -@DslMarker -annotation class BotConfigurationDsl \ No newline at end of file +/** + * 使用文件系统存储设备信息. + */ +@PlannedRemoval("0.34.0") +@Deprecated( + "use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR +) +inline class FileBasedDeviceInfo(val filepath: String) { + /** + * 使用 "device.json" 存储设备信息 + */ + companion object ByDeviceDotJson +} + +@OptIn(ExperimentalMultiplatform::class) +internal expect fun getFileBasedDeviceInfoSupplier(filename: String): ((Context) -> DeviceInfo)? \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Context.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Context.common.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Context.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Context.common.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt index 2000b3889..4ace4af4a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt @@ -49,14 +49,12 @@ abstract class DeviceInfo { abstract val imsiMd5: ByteArray abstract val imei: String - abstract val ipAddress: ByteArray + val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123) abstract val androidId: ByteArray abstract val apn: ByteArray - val guid: ByteArray by lazy { generateGuid(androidId, macAddress) } - fun generateDeviceInfoData(): ByteArray { @Serializable class DevInfo( @@ -121,11 +119,6 @@ class DeviceInfoData( @Transient override lateinit var context: Context - @OptIn(ExperimentalUnsignedTypes::class) - override val ipAddress: ByteArray - get() = MiraiPlatformUtils.localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 } - ?.toByteArray() - ?: byteArrayOf() override val androidId: ByteArray get() = display @Serializable @@ -137,13 +130,6 @@ class DeviceInfoData( ) : Version } -/** - * Defaults "%4;7t>;28 ExternalImage.sendTo(contact: C): MessageReceipt = when (contact) { is Group -> contact.uploadImage(this).sendTo(contact) is QQ -> contact.uploadImage(this).sendTo(contact) @@ -162,6 +163,7 @@ suspend fun ExternalImage.sendTo(contact: C): MessageReceipt = * * @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人 */ +@JvmSynthetic suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) { is Group -> contact.uploadImage(this) is QQ -> contact.uploadImage(this) @@ -171,10 +173,18 @@ suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact /** * 将图片发送给 [this] */ +@JvmSynthetic suspend inline fun C.sendImage(image: ExternalImage): MessageReceipt = image.sendTo(this) -private operator fun ByteArray.get(range: IntRange): String = buildString { +internal operator fun ByteArray.get(range: IntRange): String = buildString { range.forEach { - append(this@get[it].toUHexString()) + append(this@get[it].fixToString()) + } +} + +private fun Byte.fixToString(): String { + return when (this.toInt()) { + in 0..15 -> "0${this.toString(16)}" + else -> this.toString(16) } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LoginSolver.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LoginSolver.kt new file mode 100644 index 000000000..09cd3a9ae --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LoginSolver.kt @@ -0,0 +1,81 @@ +/* + * 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 net.mamoe.mirai.utils + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.network.LoginFailedException +import kotlin.reflect.KClass + +/** + * This annotation indicates what exceptions should be declared by a function when compiled to a JVM method. + * + * Example: + * + * ``` + * @Throws(IOException::class) + * fun readFile(name: String): String {...} + * ``` + * + * will be translated to + * + * ``` + * String readFile(String name) throws IOException {...} + * ``` + * + * @property exceptionClasses the list of checked exception classes that may be thrown by the function. + */ +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.CONSTRUCTOR +) +@Retention(AnnotationRetention.SOURCE) +@OptIn(ExperimentalMultiplatform::class) +@OptionalExpectation +expect annotation class Throws(vararg val exceptionClasses: KClass) + +/** + * 验证码, 设备锁解决器 + */ +expect abstract class LoginSolver { + /** + * 处理图片验证码. + * 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录. + * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止 + * + * @throws LoginFailedException + */ + abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? + + /** + * 处理滑动验证码. + * 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录. + * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止 + * + * @throws LoginFailedException + * @return 验证码解决成功后获得的 ticket. + */ + abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? + + /** + * 处理不安全设备验证. + * 在处理完成后返回任意内容 (包含 `null`) 均视为处理成功. + * 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止. + * + * @return 任意内容. 返回值保留以供未来更新. + * @throws LoginFailedException + */ + abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? + + companion object { + val Default: LoginSolver + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt deleted file mode 100644 index 5a207f64d..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 - */ - -@file:Suppress("unused", "FunctionName", "NOTHING_TO_INLINE") - -package net.mamoe.mirai.utils - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async -import net.mamoe.mirai.contact.QQ -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -/** - * 创建一个在当前 [CoroutineScope] 下执行初始化的 [suspendLazy] - * - * ```kotlin - * val image: Deferred by suspendLazy{ /* intializer */ } - * ``` - */ -inline fun CoroutineScope.suspendLazy(context: CoroutineContext = EmptyCoroutineContext, noinline initializer: suspend () -> R): Lazy> = - SuspendLazy(this, context, initializer) - -/** - * 挂起初始化的 [lazy], 是属性不能 `suspend` 的替代品. - * - * 本对象代表值初始化时将会通过 [CoroutineScope.async] 运行 [valueUpdater] - * - * [SuspendLazy] 是: - * - 线程安全 - * - 只会被初始化一次. - * - `SuspendLazy.value` 返回 `Deferred` - * - 可以用作属性代表 (`val profile: by SuspendLazy(GlobalScope) { calculateValue() }`) - * - * @sample QQ.profile - */ -@PublishedApi -internal class SuspendLazy(scope: CoroutineScope, coroutineContext: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R) : - Lazy> { - private val valueUpdater: Deferred by lazy { scope.async(context = coroutineContext) { initializer() } } - - override val value: Deferred - get() = valueUpdater - - override fun isInitialized(): Boolean = valueUpdater.isCompleted -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt index dbc6b0a45..6f24d467c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt @@ -7,10 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("unused") +@file:Suppress("unused", "NOTHING_TO_INLINE") package net.mamoe.mirai.utils +import kotlin.jvm.JvmSynthetic import kotlin.reflect.KProperty // TODO: 2020/2/10 添加中文 doc @@ -18,7 +19,7 @@ import kotlin.reflect.KProperty /** * WeakRef that `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only */ -inline class UnsafeWeakRef(private val weakRef: WeakRef) { +class UnsafeWeakRef(private val weakRef: WeakRef) { fun get(): T = weakRef.get() ?: error("WeakRef is released") fun clear() = weakRef.clear() } @@ -30,7 +31,8 @@ inline class UnsafeWeakRef(private val weakRef: WeakRef) { * val bot: Bot by param.unsafeWeakRef() * ``` */ -operator fun UnsafeWeakRef.getValue(thisRef: Any?, property: KProperty<*>): T = get() +@JvmSynthetic +inline operator fun UnsafeWeakRef.getValue(thisRef: Any?, property: KProperty<*>): T = get() /** * Weak Reference. @@ -62,12 +64,14 @@ annotation class WeakRefProperty * Provides a weak reference to [this] * The `getValue` for delegation returns [this] when [this] is not released by GC */ -fun T.weakRef(): WeakRef = WeakRef(this) +@JvmSynthetic +inline fun T.weakRef(): WeakRef = WeakRef(this) /** * Constructs an unsafe inline delegate for [this] */ -fun WeakRef.unsafe(): UnsafeWeakRef = UnsafeWeakRef(this) +@JvmSynthetic +inline fun WeakRef.unsafe(): UnsafeWeakRef = UnsafeWeakRef(this) /** * Provides a weak reference to [this]. @@ -75,7 +79,8 @@ fun WeakRef.unsafe(): UnsafeWeakRef = UnsafeWeakRef(this) * * **UNSTABLE API**: It is strongly suggested not to use this api */ -fun T.unsafeWeakRef(): UnsafeWeakRef = UnsafeWeakRef(this.weakRef()) +@JvmSynthetic +inline fun T.unsafeWeakRef(): UnsafeWeakRef = UnsafeWeakRef(this.weakRef()) /** * Provides delegate value. @@ -84,9 +89,11 @@ fun T.unsafeWeakRef(): UnsafeWeakRef = UnsafeWeakRef(this.weakRef()) * val bot: Bot? by param.weakRef() * ``` */ -operator fun WeakRef.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get() +@JvmSynthetic +inline operator fun WeakRef.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get() /** * Call the block if the referent is absent */ +@JvmSynthetic inline fun WeakRef.ifAbsent(block: (T) -> R): R? = this.get()?.let(block) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotataions.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotations.kt similarity index 77% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotataions.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotations.kt index 9d359e3d8..a081c7ddf 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotataions.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotations.kt @@ -42,23 +42,18 @@ annotation class MiraiExperimentalAPI( val message: String = "" ) -/** - * 标记这个类, 类型, 函数, 属性, 字段, 或构造器为仅供调试阶段使用的. - * - * 这些 API 不具有稳定性, 可能会在任意时刻更改, 并且效率非常低下. - * 非常不建议在发行版本中使用这些 API. - */ -@Retention(AnnotationRetention.SOURCE) -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) -annotation class MiraiDebugAPI( - val message: String = "" -) - /** * 标记一个自 Mirai 某个版本起才支持或在这个版本修改过的 API. */ @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented -annotation class SinceMirai(val version: String) \ No newline at end of file +annotation class SinceMirai(val version: String) + +/** + * 标记一个正计划在 [version] 版本时删除的 API. + */ +@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +internal annotation class PlannedRemoval(val version: String) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt index f0495774a..ef2108821 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt @@ -18,10 +18,8 @@ import kotlinx.coroutines.io.ByteReadChannel import kotlinx.coroutines.io.ByteWriteChannel import kotlinx.coroutines.io.readAvailable import kotlinx.io.OutputStream -import kotlinx.serialization.InternalSerializationApi import kotlinx.io.core.Output -import kotlinx.io.pool.useInstance -import net.mamoe.mirai.utils.io.ByteArrayPool +import kotlinx.serialization.InternalSerializationApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -31,72 +29,53 @@ import kotlin.jvm.JvmName * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] */ @OptIn(InternalSerializationApi::class) +@MiraiExperimentalAPI suspend fun ByteReadChannel.copyTo(dst: OutputStream) { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.write(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.write(buffer, 0, size) } } /** * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] */ +@MiraiExperimentalAPI suspend fun ByteReadChannel.copyTo(dst: Output) { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.writeFully(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) } } /** * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] */ +@MiraiExperimentalAPI suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.writeFully(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) } } - -/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel -/** - * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] - */ -suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) { - ByteArrayPool.useInstance { - do { - val size = this.readAvailable(it) - dst.writeFully(it, 0, size) - } while (size != 0) - } -} -*/ - // copyAndClose /** * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] */ +@MiraiExperimentalAPI @OptIn(InternalSerializationApi::class) suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 API 不是 internal 的 try { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.write(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.write(buffer, 0, size) } } finally { dst.close() @@ -106,14 +85,13 @@ suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 /** * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] */ +@MiraiExperimentalAPI suspend fun ByteReadChannel.copyAndClose(dst: Output) { try { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.writeFully(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) } } finally { dst.close() @@ -123,15 +101,14 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) { /** * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] */ +@MiraiExperimentalAPI suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) { @Suppress("DuplicatedCode") try { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.writeFully(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) } } finally { @Suppress("DuplicatedCode") @@ -142,35 +119,17 @@ suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) { /** * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] */ +@MiraiExperimentalAPI suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) { @Suppress("DuplicatedCode") try { - @OptIn(MiraiInternalAPI::class) - ByteArrayPool.useInstance { buffer -> - var size: Int - while (this.readAvailable(buffer).also { size = it } > 0) { - dst.writeFully(buffer, 0, size) - } + val buffer = ByteArray(2048) + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) } } finally { @Suppress("DuplicatedCode") dst.close(null) } -} - -/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel -/** - * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] - */ -suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) { - dst.close(kotlin.runCatching { - ByteArrayPool.useInstance { - do { - val size = this.readAvailable(it) - dst.writeFully(it, 0, size) - } while (size != 0) - } - }.exceptionOrNull()) -} - - */ \ No newline at end of file +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt new file mode 100644 index 000000000..35c76ea19 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt @@ -0,0 +1,29 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") + +package net.mamoe.mirai.utils.internal + +expect abstract class InputStream { + open fun available(): Int + open fun close() + abstract fun read(): Int + open fun read(b: ByteArray): Int + open fun read(b: ByteArray, offset: Int, len: Int): Int + open fun skip(n: Long): Long +} + +internal expect fun InputStream.md5(): ByteArray +internal expect fun ByteArray.md5(offset: Int = 0, length: Int = this.size - offset): ByteArray + +@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List` and `ByteArray` +internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) { + require(offset >= 0) { "offset shouldn't be negative: $offset" } + require(length >= 0) { "length shouldn't be negative: $length" } + require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" } +} + +internal inline fun InputStream.readInSequence(block: (Int) -> Unit) { + var read: Int + while (this.read().also { read = it } != -1) { + block(read) + } +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/tryNTimes.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/tryNTimes.kt new file mode 100644 index 000000000..feeca5d43 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/tryNTimes.kt @@ -0,0 +1,34 @@ +/* + * 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 net.mamoe.mirai.utils.internal + +import net.mamoe.mirai.utils.MiraiInternalAPI + +@PublishedApi +internal expect fun Throwable.addSuppressedMirai(e: Throwable) + +@MiraiInternalAPI +@Suppress("DuplicatedCode") +internal inline fun tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? { + var lastException: Throwable? = null + + repeat(repeat) { + try { + block(it) + return null + } catch (e: Throwable) { + if (lastException == null) { + lastException = e + } else lastException!!.addSuppressedMirai(e) + } + } + + return lastException!! +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt deleted file mode 100644 index 12d327afa..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 - */ - -@file:Suppress("MemberVisibilityCanBePrivate") - -package net.mamoe.mirai.utils.io - -import kotlinx.io.pool.DefaultPool -import kotlinx.io.pool.ObjectPool -import net.mamoe.mirai.utils.MiraiInternalAPI - -/** - * 缓存 [ByteArray] 实例的 [ObjectPool] - * - * **注意**: 这是 Mirai 内部 API. 它可能在任何时刻被改动. 不要将它用于生产环境 - */ -@MiraiInternalAPI -object ByteArrayPool : DefaultPool(256) { - /** - * 每一个 [ByteArray] 的大小 - */ - const val BUFFER_SIZE: Int = 81920 / 2 - - override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) - - override fun clearInstance(instance: ByteArray): ByteArray = instance - - fun checkBufferSize(size: Int) { - require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } - } - - fun checkBufferSize(size: Long) { - require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } - } -} - diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/maps.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/maps.kt deleted file mode 100644 index 0c0dff47f..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/maps.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 - */ - -@file:Suppress("NOTHING_TO_INLINE") -@file:JvmMultifileClass -@file:JvmName("Utils") - - -package net.mamoe.mirai.utils - -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - -inline fun Map.firstValue(): V = this.entries.first().value - -inline fun Map.firstValueOrNull(): V? = this.entries.firstOrNull()?.value - -inline fun Map.firstKey(): K = this.entries.first().key - -inline fun Map.firstKeyOrNull(): K? = this.entries.firstOrNull()?.key \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/time.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/time.kt deleted file mode 100644 index 81eba7d3b..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/time.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 - */ - -@file:JvmMultifileClass -@file:JvmName("Utils") - -package net.mamoe.mirai.utils - -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - -// 临时使用, 待 Kotlin Duration 稳定后使用 Duration. -// 内联属性, 则将来删除这些 API 将不会导致二进制不兼容. - - -inline val Int.secondsToMillis: Long get() = this * 1000L - -inline val Int.minutesToMillis: Long get() = this * 60.secondsToMillis - -inline val Int.hoursToMillis: Long get() = this * 60.minutesToMillis - -inline val Int.daysToMillis: Long get() = this * 24.hoursToMillis - -inline val Int.weeksToMillis: Long get() = this * 7.daysToMillis - -inline val Int.monthsToMillis: Long get() = this * 30.daysToMillis - - - -inline val Int.millisToSeconds: Long get() = (this / 1000).toLong() - -inline val Int.minutesToSeconds: Long get() = (this * 60).toLong() - -inline val Int.hoursToSeconds: Long get() = this * 60.minutesToSeconds - -inline val Int.daysToSeconds: Long get() = this * 24.hoursToSeconds - -inline val Int.weeksToSeconds: Long get() = this * 7.daysToSeconds - -inline val Int.monthsToSeconds: Long get() = this * 30.daysToSeconds \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/utils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/utils.kt new file mode 100644 index 000000000..c31e03724 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/utils.kt @@ -0,0 +1,79 @@ +/* + * 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 + */ + +@file:JvmMultifileClass +@file:JvmName("Utils") + +package net.mamoe.mirai.utils + +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic + +/** + * 时间戳 + */ +expect val currentTimeMillis: Long + +@get:JvmSynthetic +inline val currentTimeSeconds: Long + get() = currentTimeMillis / 1000 + + +// 临时使用, 待 Kotlin Duration 稳定后使用 Duration. +// 内联属性, 则将来删除这些 API 将不会导致二进制不兼容. + +@get:JvmSynthetic +inline val Int.secondsToMillis: Long + get() = this * 1000L + +@get:JvmSynthetic +inline val Int.minutesToMillis: Long + get() = this * 60.secondsToMillis + +@get:JvmSynthetic +inline val Int.hoursToMillis: Long + get() = this * 60.minutesToMillis + +@get:JvmSynthetic +inline val Int.daysToMillis: Long + get() = this * 24.hoursToMillis + +@get:JvmSynthetic +inline val Int.weeksToMillis: Long + get() = this * 7.daysToMillis + +@get:JvmSynthetic +inline val Int.monthsToMillis: Long + get() = this * 30.daysToMillis + + +@get:JvmSynthetic +inline val Int.millisToSeconds: Long + get() = (this / 1000).toLong() + +@get:JvmSynthetic +inline val Int.minutesToSeconds: Long + get() = (this * 60).toLong() + +@get:JvmSynthetic +inline val Int.hoursToSeconds: Long + get() = this * 60.minutesToSeconds + +@get:JvmSynthetic +inline val Int.daysToSeconds: Long + get() = this * 24.hoursToSeconds + +@get:JvmSynthetic +inline val Int.weeksToSeconds: Long + get() = this * 7.daysToSeconds + +@get:JvmSynthetic +inline val Int.monthsToSeconds: Long + get() = this * 30.daysToSeconds \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt index 79ac19242..b57631c9b 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt @@ -7,13 +7,19 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.message.data -import net.mamoe.mirai.utils.io.autoHexToBytes import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +internal fun String.autoHexToBytes(): ByteArray = + this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { + (it[0].toString() + it[1]).toUByte(16).toByte() + }.toList().toByteArray() + internal class ImageTest { @Test diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt new file mode 100644 index 000000000..6cc178d5f --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt @@ -0,0 +1,14 @@ +package net.mamoe.mirai.utils + +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class ExternalImageTest { + + @Test + fun testByteArrayGet() { + assertEquals("0F", byteArrayOf(0x0f)[0..0]) + assertEquals("10", byteArrayOf(0x10)[0..0]) + assertEquals("0FFE", byteArrayOf(0x0F, 0xFE.toByte())[0..0]) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt index be5b4d1a1..3f25aee61 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt @@ -18,6 +18,7 @@ import kotlinx.io.core.use import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.copyAndClose import net.mamoe.mirai.utils.copyTo @@ -31,7 +32,7 @@ import java.net.URL * 一条从服务器接收到的消息事件. * JVM 平台相关扩展 */ -@OptIn(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) actual abstract class MessagePacket actual constructor() : MessagePacketBase() { // region 上传图片 suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/Context.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/Context.kt index a1300d47c..304c2af33 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/Context.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/Context.kt @@ -9,6 +9,8 @@ package net.mamoe.mirai.utils +import kotlinx.io.pool.DefaultPool + actual abstract class Context -open class ContextImpl : Context() \ No newline at end of file +open class ContextImpl : Context() diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index 47bfd47db..21b452814 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -19,7 +19,7 @@ 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.internal.md5 import java.awt.image.BufferedImage import java.io.File import java.io.InputStream @@ -72,7 +72,7 @@ fun File.toExternalImage(): ExternalImage { return ExternalImage( width = image.getWidth(0), height = image.getHeight(0), - md5 = MiraiPlatformUtils.md5(this.inputStream()), // dont change + md5 = this.inputStream().md5(), // dont change imageFormat = image.formatName, input = this.inputStream(), filename = this.name diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt similarity index 70% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt index 2e74f37fc..bcf31d57a 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt @@ -31,6 +31,9 @@ import javax.imageio.ImageIO import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +actual typealias Throws = kotlin.jvm.Throws + +@MiraiExperimentalAPI class DefaultLoginSolver( private val input: suspend () -> String, private val overrideLogger: MiraiLogger? = null @@ -92,6 +95,31 @@ class DefaultLoginSolver( } } +/** + * 验证码, 设备锁解决器 + */ +actual abstract class LoginSolver { + actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? + actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? + actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? + + actual companion object { + actual val Default: LoginSolver + @OptIn(MiraiExperimentalAPI::class) + get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") }) + } +} + +/////////////////////////////// +//////////////// internal +/////////////////////////////// + +internal actual fun getFileBasedDeviceInfoSupplier(filename: String): ((Context) -> DeviceInfo)? { + return { + File(filename).loadAsDeviceInfo(it) + } +} + // Copied from Ktor CIO private fun File.writeChannel( coroutineContext: CoroutineContext = Dispatchers.IO @@ -103,7 +131,6 @@ private fun File.writeChannel( } }.channel - private val loginSolverLock = Mutex() /** @@ -159,113 +186,3 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub } } } - -@Suppress("ClassName", "PropertyName") -actual open class BotConfiguration actual constructor() { - /** - * 日志记录器 - */ - actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") } - /** - * 网络层日志构造器 - */ - actual var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") } - /** - * 设备信息覆盖. 默认使用随机的设备信息. - */ - actual var deviceInfo: ((Context) -> DeviceInfo)? = null - - /** - * 父 [CoroutineContext] - */ - actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext - - /** - * 心跳周期. 过长会导致被服务器断开连接. - */ - actual var heartbeatPeriodMillis: Long = 60.secondsToMillis - /** - * 每次心跳时等待结果的时间. - * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. - */ - actual var heartbeatTimeoutMillis: Long = 2.secondsToMillis - /** - * 心跳失败后的第一次重连前的等待时间. - */ - actual var firstReconnectDelayMillis: Long = 5.secondsToMillis - /** - * 重连失败后, 继续尝试的每次等待时间 - */ - actual var reconnectPeriodMillis: Long = 5.secondsToMillis - /** - * 最多尝试多少次重连 - */ - actual var reconnectionRetryTimes: Int = Int.MAX_VALUE - /** - * 验证码处理器 - */ - actual var loginSolver: LoginSolver = LoginSolver.Default - - actual companion object { - /** - * 默认的配置实例 - */ - @JvmStatic - actual val Default = BotConfiguration() - } - - @Suppress("NOTHING_TO_INLINE") - @BotConfigurationDsl - inline operator fun FileBasedDeviceInfo.unaryPlus() { - deviceInfo = { File(filepath).loadAsDeviceInfo(it) } - } - - @Suppress("NOTHING_TO_INLINE") - @BotConfigurationDsl - inline operator fun FileBasedDeviceInfo.ByDeviceDotJson.unaryPlus() { - deviceInfo = { File("device.json").loadAsDeviceInfo(it) } - } - - actual operator fun _NoNetworkLog.unaryPlus() { - networkLoggerSupplier = supplier - } - - /** - * 不记录网络层的 log. - * 网络层 log 包含包接收, 包发送, 和一些调试用的记录. - */ - @BotConfigurationDsl - actual val NoNetworkLog: _NoNetworkLog - get() = _NoNetworkLog - - @BotConfigurationDsl - actual object _NoNetworkLog { - internal val supplier = { _: BotNetworkHandler -> SilentLogger } - } -} - -/** - * 使用文件系统存储设备信息. - */ -@BotConfigurationDsl -inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: String) { - /** - * 使用 "device.json" 存储设备信息 - */ - @BotConfigurationDsl - companion object ByDeviceDotJson -} - -/** - * 验证码, 设备锁解决器 - */ -actual abstract class LoginSolver { - actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? - actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? - actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? - - actual companion object { - actual val Default: LoginSolver - get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") }) - } -} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt similarity index 100% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt index d5af19847..aa0222856 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("Utils") @file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") package net.mamoe.mirai.utils @@ -15,7 +17,6 @@ import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.util.KtorExperimentalAPI import kotlinx.io.pool.useInstance -import net.mamoe.mirai.utils.io.ByteArrayPool import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream @@ -31,88 +32,3 @@ import java.util.zip.Inflater */ actual val currentTimeMillis: Long get() = System.currentTimeMillis() - -@MiraiInternalAPI -actual object MiraiPlatformUtils { - actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray { - data.checkOffsetAndLength(offset, length) - if (length == 0) return ByteArray(0) - - val inflater = Inflater() - inflater.reset() - ByteArrayOutputStream().use { output -> - inflater.setInput(data, offset, length) - ByteArrayPool.useInstance { - while (!inflater.finished()) { - output.write(it, 0, inflater.inflate(it)) - } - } - - inflater.end() - return output.toByteArray() - } - } - - actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray { - data.checkOffsetAndLength(offset, length) - if (length == 0) return ByteArray(0) - - val deflater = Deflater() - deflater.setInput(data, offset, length) - deflater.finish() - - ByteArrayPool.useInstance { - return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } - } - } - - actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray { - ByteArrayOutputStream().use { buf -> - GZIPOutputStream(buf).use { gzip -> - data.inputStream(offset, length).use { t -> t.copyTo(gzip) } - } - buf.flush() - return buf.toByteArray() - } - } - - actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray { - return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() } - } - - actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { - data.checkOffsetAndLength(offset, length) - return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() - } - - actual inline fun md5(str: String): ByteArray = md5(str.toByteArray()) - - /** - * Ktor HttpClient. 不同平台使用不同引擎. - */ - @OptIn(KtorExperimentalAPI::class) - actual val Http: HttpClient = HttpClient(CIO) - - /** - * Localhost 解析 - */ - actual fun localIpAddress(): String = runCatching { - Inet4Address.getLocalHost().hostAddress - }.getOrElse { "192.168.1.123" } - - fun md5(stream: InputStream): ByteArray { - val digest = MessageDigest.getInstance("md5") - digest.reset() - stream.use { input -> - object : OutputStream() { - override fun write(b: Int) { - digest.update(b.toByte()) - } - }.use { output -> - input.copyTo(output) - } - } - return digest.digest() - } - -} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt index c9c6729aa..a2bd20e8e 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt @@ -12,19 +12,16 @@ package net.mamoe.mirai.utils import kotlinx.io.core.toByteArray import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration -import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress -import net.mamoe.mirai.utils.MiraiPlatformUtils.md5 -import net.mamoe.mirai.utils.io.getRandomByteArray -import net.mamoe.mirai.utils.io.getRandomString +import net.mamoe.mirai.utils.internal.md5 import java.io.File +import kotlin.random.Random +import kotlin.random.nextInt /** * 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存. */ -@OptIn(UnstableDefault::class) fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo { if (!this.exists() || this.length() == 0L) { return SystemDeviceInfo(context).also { @@ -55,9 +52,11 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { override val brand: ByteArray = "mamoe".toByteArray() override val model: ByteArray = "mirai".toByteArray() override val bootloader: ByteArray = "unknown".toByteArray() - override val fingerprint: ByteArray = "mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray() - override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray() - override val procVersion: ByteArray = "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray() + override val fingerprint: ByteArray = + "mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray() + override val bootId: ByteArray = ExternalImage.generateUUID(getRandomByteArray(16).md5()).toByteArray() + override val procVersion: ByteArray = + "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray() override val baseBand: ByteArray = byteArrayOf() override val version: Version = Version override val simInfo: ByteArray = "T-Mobile".toByteArray() @@ -65,9 +64,8 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { override val macAddress: ByteArray = "02:00:00:00:00:00".toByteArray() override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray() override val wifiSSID: ByteArray? = "".toByteArray() - override val imsiMd5: ByteArray = md5(getRandomByteArray(16)) + override val imsiMd5: ByteArray = getRandomByteArray(16).md5() override val imei: String = getRandomString(15, '0'..'9') - override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf() override val androidId: ByteArray get() = display override val apn: ByteArray = "wifi".toByteArray() @@ -78,4 +76,27 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { override val codename: ByteArray = "REL".toByteArray() override val sdk: Int = 29 } -} \ No newline at end of file +} + +/** + * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] + */ +internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } + +/** + * 随机生成长度为 [length] 的 [String]. + */ +internal fun getRandomString(length: Int): String = + getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9') + +/** + * 根据所给 [charRange] 随机生成长度为 [length] 的 [String]. + */ +internal fun getRandomString(length: Int, charRange: CharRange): String = + String(CharArray(length) { charRange.random() }) + +/** + * 根据所给 [charRanges] 随机生成长度为 [length] 的 [String]. + */ +internal fun getRandomString(length: Int, vararg charRanges: CharRange): String = + String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRefJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRef.jvm.kt similarity index 100% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRefJvm.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRef.jvm.kt diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.jvm.kt similarity index 90% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.jvm.kt index ab0da8e54..1fd698979 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.jvm.kt @@ -1,4 +1,4 @@ -package net.mamoe.mirai.utils +package net.mamoe.mirai.utils.internal private var isAddSuppressedSupported: Boolean = true diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/md5.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/md5.jvm.kt new file mode 100644 index 000000000..f0e0cab9b --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/md5.jvm.kt @@ -0,0 +1,22 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") + +package net.mamoe.mirai.utils.internal + +import java.io.InputStream +import java.security.MessageDigest + +internal actual fun ByteArray.md5(offset: Int, length: Int): ByteArray { + this.checkOffsetAndLength(offset, length) + return MessageDigest.getInstance("MD5").apply { update(this@md5, offset, length) }.digest() +} + +internal actual fun InputStream.md5(): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + this.readInSequence { + digest.update(it.toByte()) + } + return digest.digest() +} + +internal actual typealias InputStream = java.io.InputStream \ No newline at end of file