mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-27 20:50:09 +08:00
Rearrange internal APIs
This commit is contained in:
parent
97b6627338
commit
97522bdf2a
@ -1,3 +1,5 @@
|
|||||||
# mirai-core-qqandroid
|
# mirai-core-qqandroid
|
||||||
|
|
||||||
Protocol support for QQ for Android for Mirai.
|
|
||||||
|
QQ for Android 8.2.7 协议实现.
|
||||||
|
相较于 `mirai-core`, 此模块仅提供协议和功能的实现, 不提供额外的公开的 API.
|
@ -17,11 +17,11 @@ import kotlinx.coroutines.io.*
|
|||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
import kotlinx.io.pool.useInstance
|
import kotlinx.io.pool.useInstance
|
||||||
import net.mamoe.mirai.BotAccount
|
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.BotConfiguration
|
||||||
import net.mamoe.mirai.utils.Context
|
import net.mamoe.mirai.utils.Context
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
@OptIn(MiraiInternalAPI::class)
|
@OptIn(MiraiInternalAPI::class)
|
||||||
|
@ -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() }
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -23,12 +23,13 @@ import java.nio.channels.WritableByteChannel
|
|||||||
/**
|
/**
|
||||||
* 多平台适配的 DatagramChannel.
|
* 多平台适配的 DatagramChannel.
|
||||||
*/
|
*/
|
||||||
actual class PlatformDatagramChannel actual constructor(
|
internal actual class PlatformDatagramChannel actual constructor(
|
||||||
serverHost: String,
|
serverHost: String,
|
||||||
serverPort: Short
|
serverPort: Short
|
||||||
) : Closeable {
|
) : Closeable {
|
||||||
@PublishedApi
|
@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
|
actual val isOpen: Boolean get() = channel.isOpen
|
||||||
override fun close() = channel.close()
|
override fun close() = channel.close()
|
||||||
|
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -16,7 +16,6 @@ import kotlinx.io.core.Closeable
|
|||||||
import kotlinx.io.core.ExperimentalIoApi
|
import kotlinx.io.core.ExperimentalIoApi
|
||||||
import kotlinx.io.streams.readPacketAtMost
|
import kotlinx.io.streams.readPacketAtMost
|
||||||
import kotlinx.io.streams.writePacket
|
import kotlinx.io.streams.writePacket
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -25,8 +24,7 @@ import java.net.Socket
|
|||||||
/**
|
/**
|
||||||
* 多平台适配的 TCP Socket.
|
* 多平台适配的 TCP Socket.
|
||||||
*/
|
*/
|
||||||
@MiraiInternalAPI
|
internal actual class PlatformSocket : Closeable {
|
||||||
actual class PlatformSocket : Closeable {
|
|
||||||
private lateinit var socket: Socket
|
private lateinit var socket: Socket
|
||||||
|
|
||||||
actual val isOpen: Boolean
|
actual val isOpen: Boolean
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -10,9 +10,9 @@
|
|||||||
package net.mamoe.mirai.qqandroid.utils.cryptor
|
package net.mamoe.mirai.qqandroid.utils.cryptor
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils.md5
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
|
|
||||||
import java.security.*
|
import java.security.*
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
import java.security.spec.X509EncodedKeySpec
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.qqandroid.utils
|
||||||
|
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
import kotlin.reflect.jvm.javaField
|
import kotlin.reflect.jvm.javaField
|
@ -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.data.proto.LongMsg
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
|
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.toIpV4AddressString
|
||||||
|
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||||
import net.mamoe.mirai.utils.*
|
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.collections.asSequence
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
@ -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.data.proto.Cmd0x352
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
|
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.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.*
|
||||||
import net.mamoe.mirai.utils.io.toUHexString
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
@ -6,7 +6,7 @@ import kotlinx.serialization.DeserializationStrategy
|
|||||||
import kotlinx.serialization.SerialFormat
|
import kotlinx.serialization.SerialFormat
|
||||||
import kotlinx.serialization.SerializationStrategy
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
|
||||||
interface IOFormat : SerialFormat {
|
internal interface IOFormat : SerialFormat {
|
||||||
|
|
||||||
fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)
|
fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)
|
||||||
|
|
||||||
|
@ -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.JceHead
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
|
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.readString
|
import net.mamoe.mirai.qqandroid.utils.io.readString
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal val CharsetGBK = Charset.forName("GBK")
|
internal val CharsetGBK = Charset.forName("GBK")
|
||||||
|
@ -19,7 +19,7 @@ import kotlinx.serialization.modules.SerialModule
|
|||||||
import net.mamoe.mirai.qqandroid.io.serialization.IOFormat
|
import net.mamoe.mirai.qqandroid.io.serialization.IOFormat
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
|
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.JceOld
|
import net.mamoe.mirai.qqandroid.io.serialization.JceOld
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jce 数据结构序列化和反序列化器.
|
* Jce 数据结构序列化和反序列化器.
|
||||||
|
@ -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.RequestDataVersion3
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.firstValue
|
import net.mamoe.mirai.qqandroid.utils.read
|
||||||
import net.mamoe.mirai.utils.io.read
|
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
|
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||||
import net.mamoe.mirai.utils.io.toUHexString
|
|
||||||
import kotlin.jvm.JvmMultifileClass
|
import kotlin.jvm.JvmMultifileClass
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
@ -76,6 +74,7 @@ internal fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: Deseria
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
|
||||||
|
|
||||||
internal fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R {
|
internal fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R {
|
||||||
val request = this.readJceStruct(RequestPacket.serializer())
|
val request = this.readJceStruct(RequestPacket.serializer())
|
||||||
|
@ -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.HummerCommelem
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
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.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.*
|
||||||
import net.mamoe.mirai.utils.io.encodeToString
|
import net.mamoe.mirai.qqandroid.utils.read
|
||||||
import net.mamoe.mirai.utils.io.hexToBytes
|
|
||||||
import net.mamoe.mirai.utils.io.read
|
|
||||||
|
|
||||||
|
|
||||||
private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
|
private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")
|
||||||
|
@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.message
|
|||||||
|
|
||||||
import net.mamoe.mirai.message.data.Face
|
import net.mamoe.mirai.message.data.Face
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
||||||
import net.mamoe.mirai.utils.io.hexToBytes
|
import net.mamoe.mirai.qqandroid.utils.hexToBytes
|
||||||
import net.mamoe.mirai.utils.io.toByteArray
|
import net.mamoe.mirai.qqandroid.utils.toByteArray
|
||||||
|
|
||||||
internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes()
|
internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes()
|
||||||
|
|
||||||
|
@ -14,9 +14,8 @@ import net.mamoe.mirai.message.data.OfflineGroupImage
|
|||||||
import net.mamoe.mirai.message.data.OnlineFriendImage
|
import net.mamoe.mirai.message.data.OnlineFriendImage
|
||||||
import net.mamoe.mirai.message.data.OnlineGroupImage
|
import net.mamoe.mirai.message.data.OnlineGroupImage
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
|
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.ExternalImage
|
||||||
import net.mamoe.mirai.utils.io.hexToBytes
|
|
||||||
|
|
||||||
|
|
||||||
internal class OnlineGroupImageImpl(
|
internal class OnlineGroupImageImpl(
|
||||||
internal val delegate: ImMsgBody.CustomFace
|
internal val delegate: ImMsgBody.CustomFace
|
||||||
|
@ -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.Heartbeat
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
|
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.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.readPacketExact
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.useBytes
|
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.*
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
|
||||||
import net.mamoe.mirai.utils.io.PlatformSocket
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.jvm.Volatile
|
import kotlin.jvm.Volatile
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
@ -570,10 +571,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
|
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
|
||||||
*
|
|
||||||
* @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry
|
|
||||||
*/
|
*/
|
||||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 0): E {
|
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 1): E {
|
||||||
require(timeoutMillis > 100) { "timeoutMillis must > 100" }
|
require(timeoutMillis > 100) { "timeoutMillis must > 100" }
|
||||||
require(retry >= 0) { "retry must >= 0" }
|
require(retry >= 0) { "retry must >= 0" }
|
||||||
|
|
||||||
|
@ -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.EMPTY_BYTE_ARRAY
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
|
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.NetworkType
|
||||||
import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH
|
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.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<fc.5*6".toByteArray()
|
||||||
|
*/
|
||||||
|
@OptIn(MiraiInternalAPI::class)
|
||||||
|
private fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
|
||||||
|
MiraiPlatformUtils.md5(androidId + macAddress)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray]
|
||||||
|
*/
|
||||||
|
internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
APP ID:
|
APP ID:
|
||||||
@ -150,10 +169,12 @@ internal open class QQAndroidClient(
|
|||||||
var t150: Tlv? = null
|
var t150: Tlv? = null
|
||||||
var rollbackSig: ByteArray? = null
|
var rollbackSig: ByteArray? = null
|
||||||
var ipFromT149: ByteArray? = null
|
var ipFromT149: ByteArray? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端与服务器时间差
|
* 客户端与服务器时间差
|
||||||
*/
|
*/
|
||||||
var timeDifference: Long = 0
|
var timeDifference: Long = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 真实 QQ 号. 使用邮箱等登录时则需获取这个 uin 进行后续一些操作.
|
* 真实 QQ 号. 使用邮箱等登录时则需获取这个 uin 进行后续一些操作.
|
||||||
*
|
*
|
||||||
@ -177,6 +198,7 @@ internal open class QQAndroidClient(
|
|||||||
* t186
|
* t186
|
||||||
*/
|
*/
|
||||||
var pwdFlag: Boolean = false
|
var pwdFlag: Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* t537
|
* t537
|
||||||
*/
|
*/
|
||||||
@ -301,20 +323,33 @@ internal class WLoginSigInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
|
||||||
|
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
|
|
||||||
|
internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
|
|
||||||
internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
|
|
||||||
internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
|
|
||||||
internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
||||||
internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
|
|
||||||
internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
|
|
||||||
internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
|
internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
|
||||||
|
KeyWithExpiry(data, creationTime, expireTime)
|
||||||
|
|
||||||
internal typealias PSKeyMap = MutableMap<String, PSKey>
|
internal typealias PSKeyMap = MutableMap<String, PSKey>
|
||||||
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
|
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
|
||||||
@ -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 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 {
|
data.read {
|
||||||
repeat(readShort().toInt()) {
|
repeat(readShort().toInt()) {
|
||||||
val domain = readUShortLVString()
|
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)
|
internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
|
||||||
|
|
||||||
|
@ -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.qqandroid.network.protocol.data.proto.CSDataHighwayHead
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.copyAndClose
|
import net.mamoe.mirai.utils.copyAndClose
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
|
||||||
import net.mamoe.mirai.utils.io.PlatformSocket
|
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.withUse
|
import net.mamoe.mirai.qqandroid.utils.io.withUse
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
|
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
|
||||||
|
import net.mamoe.mirai.qqandroid.utils.PlatformSocket
|
||||||
|
|
||||||
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
|
@ -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.data.proto.CSDataHighwayHead
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.io.*
|
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
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)
|
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
||||||
internal fun createImageDataPacketSequence( // RequestDataTrans
|
internal fun createImageDataPacketSequence( // RequestDataTrans
|
||||||
|
@ -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.StatSvc
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
|
||||||
import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray
|
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.TEA
|
||||||
import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey
|
import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
|
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.readString
|
import net.mamoe.mirai.qqandroid.utils.io.readString
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.useBytes
|
import net.mamoe.mirai.qqandroid.utils.io.useBytes
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.withUse
|
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.*
|
||||||
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
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
internal sealed class PacketFactory<TPacket : Packet?> {
|
internal sealed class PacketFactory<TPacket : Packet?> {
|
||||||
/**
|
/**
|
||||||
* 筛选从服务器接收到的包时的 commandName
|
* 筛选从服务器接收到的包时的 commandName
|
||||||
@ -127,9 +127,6 @@ internal typealias PacketConsumer<T> = suspend (packetFactory: PacketFactory<T>,
|
|||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal val PacketLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet").withSwitch(false)
|
internal val PacketLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet").withSwitch(false)
|
||||||
|
|
||||||
/**
|
|
||||||
* 已知的数据包工厂列表.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
internal object KnownPacketFactories {
|
internal object KnownPacketFactories {
|
||||||
object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf(
|
object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf(
|
||||||
@ -292,9 +289,6 @@ internal object KnownPacketFactories {
|
|||||||
lateinit var consumer: PacketConsumer<T>
|
lateinit var consumer: PacketConsumer<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析 SSO 层包装
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||||
private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket<*> {
|
private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket<*> {
|
||||||
val commandName: String
|
val commandName: String
|
||||||
@ -363,14 +357,14 @@ internal object KnownPacketFactories {
|
|||||||
) {
|
) {
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
check(readByte().toInt() == 2)
|
check(readByte().toInt() == 2)
|
||||||
this.discardExact(2) // 27 + 2 + body.size
|
this.discardExact(2)
|
||||||
this.discardExact(2) // const, =8001
|
this.discardExact(2)
|
||||||
this.readUShort() // commandId
|
this.readUShort()
|
||||||
this.readShort() // const, =0x0001
|
this.readShort()
|
||||||
this.readUInt().toLong() // qq
|
this.readUInt().toLong()
|
||||||
val encryptionMethod = this.readUShort().toInt()
|
val encryptionMethod = this.readUShort().toInt()
|
||||||
|
|
||||||
this.discardExact(1) // const = 0
|
this.discardExact(1)
|
||||||
val packet = when (encryptionMethod) {
|
val packet = when (encryptionMethod) {
|
||||||
4 -> {
|
4 -> {
|
||||||
var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())
|
var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())
|
||||||
|
@ -16,12 +16,12 @@ import kotlinx.io.core.ByteReadPacket
|
|||||||
import kotlinx.io.core.toByteArray
|
import kotlinx.io.core.toByteArray
|
||||||
import kotlinx.io.core.writeFully
|
import kotlinx.io.core.writeFully
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.LoginType
|
import net.mamoe.mirai.qqandroid.network.protocol.LoginType
|
||||||
|
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
|
||||||
import net.mamoe.mirai.qqandroid.utils.NetworkType
|
import net.mamoe.mirai.qqandroid.utils.NetworkType
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.*
|
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.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
|
||||||
import net.mamoe.mirai.utils.currentTimeMillis
|
import net.mamoe.mirai.utils.currentTimeMillis
|
||||||
import net.mamoe.mirai.utils.io.*
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,14 +12,11 @@
|
|||||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
|
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
|
||||||
|
|
||||||
import kotlinx.io.core.ByteReadPacket
|
import kotlinx.io.core.ByteReadPacket
|
||||||
import net.mamoe.mirai.contact.Group
|
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
|
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
|
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.message.toRichTextElems
|
||||||
import net.mamoe.mirai.qqandroid.network.Packet
|
import net.mamoe.mirai.qqandroid.network.Packet
|
||||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
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.OutgoingPacketFactory
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
|
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.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
import net.mamoe.mirai.qqandroid.utils._miraiContentToString
|
||||||
import net.mamoe.mirai.utils._miraiContentToString
|
|
||||||
|
|
||||||
internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor(
|
internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor(
|
||||||
val data: ByteArray,
|
val data: ByteArray,
|
||||||
|
@ -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.OutgoingPacket
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
|
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.daysToSeconds
|
||||||
import net.mamoe.mirai.utils.io.encodeToString
|
|
||||||
import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
|
import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
|
||||||
|
|
||||||
@OptIn(LowLevelAPI::class)
|
@OptIn(LowLevelAPI::class)
|
||||||
|
@ -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.qqandroid.utils.io.readString
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.debug
|
import net.mamoe.mirai.utils.debug
|
||||||
import net.mamoe.mirai.utils.io.read
|
import net.mamoe.mirai.qqandroid.utils.read
|
||||||
import net.mamoe.mirai.utils.io.toUHexString
|
import net.mamoe.mirai.qqandroid.utils.toUHexString
|
||||||
|
|
||||||
internal class OnlinePush {
|
internal class OnlinePush {
|
||||||
/**
|
/**
|
||||||
|
@ -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.IncomingPacketFactory
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
|
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
|
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.PushReq as PushReqJceStruct
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,11 +10,12 @@
|
|||||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
||||||
|
|
||||||
import kotlinx.io.core.ByteReadPacket
|
import kotlinx.io.core.ByteReadPacket
|
||||||
import net.mamoe.mirai.qqandroid.network.Packet
|
|
||||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||||
import net.mamoe.mirai.qqandroid.io.serialization.*
|
import net.mamoe.mirai.qqandroid.io.serialization.*
|
||||||
|
import net.mamoe.mirai.qqandroid.network.Packet
|
||||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
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.RequestMSFForceOffline
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RspMSFForceOffline
|
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.Oidb0x769
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.StatSvcGetOnline
|
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.StatSvcGetOnline
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
|
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.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.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
|
||||||
import net.mamoe.mirai.utils.io.encodeToString
|
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
|
||||||
|
|
||||||
@Suppress("EnumEntryName")
|
@Suppress("EnumEntryName")
|
||||||
internal enum class RegPushReason {
|
internal enum class RegPushReason {
|
||||||
|
@ -12,18 +12,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
|||||||
|
|
||||||
import io.ktor.util.InternalAPI
|
import io.ktor.util.InternalAPI
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
import net.mamoe.mirai.qqandroid.network.Packet
|
|
||||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||||
import net.mamoe.mirai.qqandroid.network.*
|
import net.mamoe.mirai.qqandroid.network.*
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.LoginType
|
import net.mamoe.mirai.qqandroid.network.protocol.LoginType
|
||||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
|
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
|
||||||
import net.mamoe.mirai.qqandroid.utils.GuidSource
|
import net.mamoe.mirai.qqandroid.utils.*
|
||||||
import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag
|
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA
|
||||||
import net.mamoe.mirai.qqandroid.utils.guidFlag
|
import net.mamoe.mirai.qqandroid.utils.guidFlag
|
||||||
import net.mamoe.mirai.qqandroid.utils.io.*
|
import net.mamoe.mirai.qqandroid.utils.io.*
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||||
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.io.*
|
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||||
|
import net.mamoe.mirai.utils.error
|
||||||
|
|
||||||
internal class WtLogin {
|
internal class WtLogin {
|
||||||
/**
|
/**
|
||||||
@ -310,7 +310,6 @@ internal class WtLogin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InternalAPI
|
@InternalAPI
|
||||||
@OptIn(MiraiDebugAPI::class)
|
|
||||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse {
|
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse {
|
||||||
|
|
||||||
discardExact(2) // subCommand
|
discardExact(2) // subCommand
|
||||||
@ -369,7 +368,6 @@ internal class WtLogin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@InternalAPI
|
@InternalAPI
|
||||||
@OptIn(MiraiDebugAPI::class)
|
|
||||||
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
|
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
|
||||||
/*
|
/*
|
||||||
java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION:
|
java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION:
|
||||||
@ -405,7 +403,6 @@ internal class WtLogin {
|
|||||||
error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString())
|
error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(MiraiDebugAPI::class)
|
|
||||||
private fun onLoginSuccess(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success {
|
private fun onLoginSuccess(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success {
|
||||||
val client = bot.client
|
val client = bot.client
|
||||||
//println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() })
|
//println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() })
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package net.mamoe.mirai.qqandroid.utils
|
||||||
|
|
||||||
|
import kotlinx.io.pool.DefaultPool
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存 [ByteArray] 实例的 [ObjectPool]
|
||||||
|
*/
|
||||||
|
internal object ByteArrayPool : DefaultPool<ByteArray>(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" }
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +1,9 @@
|
|||||||
/*
|
package net.mamoe.mirai.qqandroid.utils
|
||||||
* 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
|
|
||||||
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
|
|
||||||
/**
|
internal expect object MiraiPlatformUtils {
|
||||||
* 时间戳.
|
|
||||||
*/
|
|
||||||
expect val currentTimeMillis: Long
|
|
||||||
|
|
||||||
inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅供内部使用的工具类.
|
|
||||||
* 不写为扩展是为了避免污染命名空间.
|
|
||||||
*/
|
|
||||||
@MiraiInternalAPI
|
|
||||||
expect object MiraiPlatformUtils {
|
|
||||||
fun unzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
|
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
|
fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
|
||||||
@ -48,7 +26,6 @@ expect object MiraiPlatformUtils {
|
|||||||
val Http: HttpClient
|
val Http: HttpClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray`
|
@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray`
|
||||||
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
|
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
|
||||||
require(offset >= 0) { "offset shouldn't be negative: $offset" }
|
require(offset >= 0) { "offset shouldn't be negative: $offset" }
|
@ -7,17 +7,15 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.ByteReadPacket
|
||||||
import kotlinx.io.core.Closeable
|
import kotlinx.io.core.Closeable
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多平台适配的 DatagramChannel.
|
* 多平台适配的 DatagramChannel.
|
||||||
*/
|
*/
|
||||||
@MiraiInternalAPI
|
internal expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
|
||||||
expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
|
|
||||||
/**
|
/**
|
||||||
* @throws SendPacketInternalException
|
* @throws SendPacketInternalException
|
||||||
*/
|
*/
|
||||||
@ -34,9 +32,9 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl
|
|||||||
/**
|
/**
|
||||||
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
|
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
|
||||||
*/
|
*/
|
||||||
class SendPacketInternalException(cause: Throwable?) : Exception(cause)
|
internal class SendPacketInternalException(cause: Throwable?) : Exception(cause)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
|
* 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
|
||||||
*/
|
*/
|
||||||
class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
|
internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.ByteReadPacket
|
||||||
import kotlinx.io.core.Closeable
|
import kotlinx.io.core.Closeable
|
||||||
@ -16,8 +16,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
|||||||
/**
|
/**
|
||||||
* 多平台适配的 TCP Socket.
|
* 多平台适配的 TCP Socket.
|
||||||
*/
|
*/
|
||||||
@MiraiInternalAPI
|
internal expect class PlatformSocket() : Closeable {
|
||||||
expect class PlatformSocket() : Closeable {
|
|
||||||
suspend fun connect(serverHost: String, serverPort: Int)
|
suspend fun connect(serverHost: String, serverPort: Int)
|
||||||
|
|
||||||
/**
|
/**
|
@ -11,14 +11,13 @@
|
|||||||
@file:JvmMultifileClass
|
@file:JvmMultifileClass
|
||||||
@file:JvmName("Utils")
|
@file:JvmName("Utils")
|
||||||
|
|
||||||
package net.mamoe.mirai.utils.io
|
package net.mamoe.mirai.qqandroid.utils
|
||||||
|
|
||||||
import kotlinx.io.charsets.Charset
|
import kotlinx.io.charsets.Charset
|
||||||
import kotlinx.io.charsets.Charsets
|
import kotlinx.io.charsets.Charsets
|
||||||
import kotlinx.io.core.ByteReadPacket
|
import kotlinx.io.core.ByteReadPacket
|
||||||
import kotlinx.io.core.String
|
import kotlinx.io.core.String
|
||||||
import kotlinx.io.core.use
|
import kotlinx.io.core.use
|
||||||
import net.mamoe.mirai.utils.checkOffsetAndLength
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@ -31,7 +30,7 @@ import kotlin.jvm.JvmSynthetic
|
|||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
|
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
|
internal fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
|
||||||
require(offset >= 0) { "offset shouldn't be negative: $offset" }
|
require(offset >= 0) { "offset shouldn't be negative: $offset" }
|
||||||
require(length >= 0) { "length shouldn't be negative: $length" }
|
require(length >= 0) { "length shouldn't be negative: $length" }
|
||||||
require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }
|
require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }
|
||||||
@ -55,7 +54,7 @@ fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: In
|
|||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
|
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@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)
|
this.checkOffsetAndLength(offset, length)
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return ""
|
return ""
|
||||||
@ -76,7 +75,7 @@ fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int
|
|||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
|
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
|
||||||
@ExperimentalUnsignedTypes
|
@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) {
|
if (length == 0) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -94,13 +93,14 @@ fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: In
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@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)
|
ByteReadPacket(this, offset = offset, length = length)
|
||||||
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
@OptIn(ExperimentalContracts::class)
|
||||||
inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R {
|
internal inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R {
|
||||||
contract {
|
contract {
|
||||||
callsInPlace(t, InvocationKind.EXACTLY_ONCE)
|
callsInPlace(t, InvocationKind.EXACTLY_ONCE)
|
||||||
}
|
}
|
@ -9,9 +9,8 @@
|
|||||||
|
|
||||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH")
|
@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.KClass
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
@ -22,8 +21,7 @@ private val indent: String = " ".repeat(4)
|
|||||||
/**
|
/**
|
||||||
* 将所有元素加入转换为多行的字符串表示.
|
* 将所有元素加入转换为多行的字符串表示.
|
||||||
*/
|
*/
|
||||||
@MiraiDebugAPI
|
private fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
|
||||||
internal fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
|
|
||||||
return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform)
|
return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +36,7 @@ internal fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T)
|
|||||||
* 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
|
* 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
|
||||||
*/
|
*/
|
||||||
@Suppress("FunctionName") // 这样就不容易被 IDE 提示
|
@Suppress("FunctionName") // 这样就不容易被 IDE 提示
|
||||||
@MiraiDebugAPI("Extremely slow")
|
internal fun Any?._miraiContentToString(prefix: String = ""): String = when (this) {
|
||||||
//@Suppress("Unsupported") // false positive
|
|
||||||
fun Any?._miraiContentToString(prefix: String = ""): String = when (this) {
|
|
||||||
is Unit -> "Unit"
|
is Unit -> "Unit"
|
||||||
is UInt -> "0x" + this.toUHexString("") + "($this)"
|
is UInt -> "0x" + this.toUHexString("") + "($this)"
|
||||||
is UByte -> "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")
|
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||||
private val KClass<*>.isData: Boolean get() = false // on JVM, it will be resolved to member function
|
private val KClass<*>.isData: Boolean get() = false // on JVM, it will be resolved to member function
|
||||||
|
|
||||||
@MiraiDebugAPI
|
|
||||||
private fun Any.contentToStringReflectively(
|
private fun Any.contentToStringReflectively(
|
||||||
prefix: String,
|
prefix: String,
|
||||||
filter: ((name: String, value: Any?) -> Boolean)? = null
|
filter: ((name: String, value: Any?) -> Boolean)? = null
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
|
@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.Random
|
||||||
import kotlin.random.nextInt
|
import kotlin.random.nextInt
|
||||||
@ -22,7 +22,7 @@ import kotlin.random.nextInt
|
|||||||
/**
|
/**
|
||||||
* 255 -> 00 FF
|
* 255 -> 00 FF
|
||||||
*/
|
*/
|
||||||
fun Short.toByteArray(): ByteArray = with(toInt()) {
|
internal fun Short.toByteArray(): ByteArray = with(toInt()) {
|
||||||
byteArrayOf(
|
byteArrayOf(
|
||||||
(shr(8) and 0xFF).toByte(),
|
(shr(8) and 0xFF).toByte(),
|
||||||
(shr(0) and 0xFF).toByte()
|
(shr(0) and 0xFF).toByte()
|
||||||
@ -32,7 +32,7 @@ fun Short.toByteArray(): ByteArray = with(toInt()) {
|
|||||||
/**
|
/**
|
||||||
* 255 -> 00 00 00 FF
|
* 255 -> 00 00 00 FF
|
||||||
*/
|
*/
|
||||||
fun Int.toByteArray(): ByteArray = byteArrayOf(
|
internal fun Int.toByteArray(): ByteArray = byteArrayOf(
|
||||||
ushr(24).toByte(),
|
ushr(24).toByte(),
|
||||||
ushr(16).toByte(),
|
ushr(16).toByte(),
|
||||||
ushr(8).toByte(),
|
ushr(8).toByte(),
|
||||||
@ -42,7 +42,7 @@ fun Int.toByteArray(): ByteArray = byteArrayOf(
|
|||||||
/**
|
/**
|
||||||
* 255 -> 00 00 00 FF
|
* 255 -> 00 00 00 FF
|
||||||
*/
|
*/
|
||||||
fun Long.toByteArray(): ByteArray = byteArrayOf(
|
internal fun Long.toByteArray(): ByteArray = byteArrayOf(
|
||||||
(ushr(56) and 0xFF).toByte(),
|
(ushr(56) and 0xFF).toByte(),
|
||||||
(ushr(48) and 0xFF).toByte(),
|
(ushr(48) and 0xFF).toByte(),
|
||||||
(ushr(40) and 0xFF).toByte(),
|
(ushr(40) and 0xFF).toByte(),
|
||||||
@ -53,40 +53,40 @@ fun Long.toByteArray(): ByteArray = byteArrayOf(
|
|||||||
(ushr(0) and 0xFF).toByte()
|
(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
|
* 255 -> 00 FF
|
||||||
*/
|
*/
|
||||||
fun UShort.toByteArray(): ByteArray = with(toUInt()) {
|
internal fun UShort.toByteArray(): ByteArray = with(toUInt()) {
|
||||||
byteArrayOf(
|
byteArrayOf(
|
||||||
(shr(8) and 255u).toByte(),
|
(shr(8) and 255u).toByte(),
|
||||||
(shr(0) 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()
|
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)
|
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)
|
this.ushr(32).toUInt().toUHexString(separator) + separator + this.toUInt().toUHexString(separator)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 255 -> 00 FF
|
* 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
|
* 255u -> 00 00 00 FF
|
||||||
*/
|
*/
|
||||||
fun UInt.toByteArray(): ByteArray = byteArrayOf(
|
internal fun UInt.toByteArray(): ByteArray = byteArrayOf(
|
||||||
(shr(24) and 255u).toByte(),
|
(shr(24) and 255u).toByte(),
|
||||||
(shr(16) and 255u).toByte(),
|
(shr(16) and 255u).toByte(),
|
||||||
(shr(8) and 255u).toByte(),
|
(shr(8) and 255u).toByte(),
|
||||||
@ -96,23 +96,23 @@ fun UInt.toByteArray(): ByteArray = byteArrayOf(
|
|||||||
/**
|
/**
|
||||||
* 转 [ByteArray] 后再转 hex
|
* 转 [ByteArray] 后再转 hex
|
||||||
*/
|
*/
|
||||||
fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
|
internal fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转无符号十六进制表示, 并补充首位 `0`.
|
* 转无符号十六进制表示, 并补充首位 `0`.
|
||||||
* 转换结果示例: `FF`, `0E`
|
* 转换结果示例: `FF`, `0E`
|
||||||
*/
|
*/
|
||||||
fun Byte.toUHexString(): String = this.toUByte().fixToUHex()
|
internal fun Byte.toUHexString(): String = this.toUByte().fixToUHex()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转无符号十六进制表示, 并补充首位 `0`.
|
* 转无符号十六进制表示, 并补充首位 `0`.
|
||||||
*/
|
*/
|
||||||
fun Byte.fixToUHex(): String = this.toUByte().fixToUHex()
|
internal fun Byte.fixToUHex(): String = this.toUByte().fixToUHex()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转无符号十六进制表示, 并补充首位 `0`.
|
* 转无符号十六进制表示, 并补充首位 `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()
|
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(" ")
|
this.split(" ")
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filterNot { it.isEmpty() }
|
.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()
|
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 {
|
this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map {
|
||||||
(it[0].toString() + it[1]).toUByte(16).toByte()
|
(it[0].toString() + it[1]).toUByte(16).toByte()
|
||||||
}.toList().toByteArray()
|
}.toList().toByteArray()
|
||||||
@ -151,7 +151,7 @@ fun String.autoHexToBytes(): ByteArray =
|
|||||||
*
|
*
|
||||||
* 这个方法很累, 不建议经常使用.
|
* 这个方法很累, 不建议经常使用.
|
||||||
*/
|
*/
|
||||||
fun String.hexToUBytes(): UByteArray =
|
internal fun String.hexToUBytes(): UByteArray =
|
||||||
this.split(" ")
|
this.split(" ")
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filterNot { it.isEmpty() }
|
.filterNot { it.isEmpty() }
|
||||||
@ -159,27 +159,6 @@ fun String.hexToUBytes(): UByteArray =
|
|||||||
.toList()
|
.toList()
|
||||||
.toUByteArray()
|
.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]
|
* 将 [this] 前 4 个 [Byte] 的 bits 合并为一个 [Int]
|
||||||
*
|
*
|
||||||
@ -188,15 +167,15 @@ fun getRandomString(length: Int, vararg charRanges: CharRange): String =
|
|||||||
* 一个 [Int] 有 32 bits
|
* 一个 [Int] 有 32 bits
|
||||||
* 本函数将 4 个 [Byte] 的 bits 连接得到 [Int]
|
* 本函数将 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(
|
(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
|
255u
|
||||||
) shl 0)
|
) 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()
|
((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(
|
(this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and(
|
||||||
255
|
255
|
||||||
) shl 0)
|
) shl 0)
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.qqandroid.utils.cryptor
|
package net.mamoe.mirai.qqandroid.utils.cryptor
|
||||||
|
|
||||||
import net.mamoe.mirai.utils.io.chunkedHexToBytes
|
import net.mamoe.mirai.qqandroid.utils.chunkedHexToBytes
|
||||||
|
|
||||||
expect interface ECDHPrivateKey {
|
expect interface ECDHPrivateKey {
|
||||||
fun getEncoded(): ByteArray
|
fun getEncoded(): ByteArray
|
||||||
|
@ -11,10 +11,10 @@ package net.mamoe.mirai.qqandroid.utils.cryptor
|
|||||||
|
|
||||||
import kotlinx.io.core.ByteReadPacket
|
import kotlinx.io.core.ByteReadPacket
|
||||||
import kotlinx.io.pool.useInstance
|
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.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.and
|
||||||
import kotlin.experimental.xor
|
import kotlin.experimental.xor
|
||||||
import kotlin.jvm.JvmStatic
|
import kotlin.jvm.JvmStatic
|
||||||
|
@ -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 <R> inline(block: () -> R): R {
|
|
||||||
contract {
|
|
||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
|
||||||
}
|
|
||||||
return block()
|
|
||||||
}
|
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -20,12 +20,13 @@ import kotlinx.io.core.Input
|
|||||||
import kotlinx.io.pool.useInstance
|
import kotlinx.io.pool.useInstance
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
|
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 由 [chunkedFlow] 分割得到的区块
|
* 由 [chunkedFlow] 分割得到的区块
|
||||||
*/
|
*/
|
||||||
class ChunkedInput(
|
internal class ChunkedInput(
|
||||||
/**
|
/**
|
||||||
* 区块的数据.
|
* 区块的数据.
|
||||||
* 由 [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问.
|
* 由 [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问.
|
||||||
@ -51,11 +52,16 @@ class ChunkedInput(
|
|||||||
* 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence]
|
* 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence]
|
||||||
*/
|
*/
|
||||||
@OptIn(MiraiInternalAPI::class)
|
@OptIn(MiraiInternalAPI::class)
|
||||||
fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
|
internal fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
|
||||||
ByteArrayPool.checkBufferSize(sizePerPacket)
|
ByteArrayPool.checkBufferSize(sizePerPacket)
|
||||||
if (this.remaining <= sizePerPacket.toLong()) {
|
if (this.remaining <= sizePerPacket.toLong()) {
|
||||||
ByteArrayPool.useInstance { buffer ->
|
ByteArrayPool.useInstance { buffer ->
|
||||||
return flowOf(ChunkedInput(buffer, this.readAvailable(buffer, 0, sizePerPacket)))
|
return flowOf(
|
||||||
|
ChunkedInput(
|
||||||
|
buffer,
|
||||||
|
this.readAvailable(buffer, 0, sizePerPacket)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return flow {
|
return flow {
|
||||||
@ -76,7 +82,7 @@ fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
|
|||||||
* 其长度分别为: 300, 300, 300, 100.
|
* 其长度分别为: 300, 300, 300, 100.
|
||||||
*/
|
*/
|
||||||
@OptIn(MiraiInternalAPI::class)
|
@OptIn(MiraiInternalAPI::class)
|
||||||
fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
|
internal fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
|
||||||
ByteArrayPool.checkBufferSize(sizePerPacket)
|
ByteArrayPool.checkBufferSize(sizePerPacket)
|
||||||
if (this.isClosedForRead) {
|
if (this.isClosedForRead) {
|
||||||
return flowOf()
|
return flowOf()
|
@ -18,7 +18,6 @@ import kotlinx.io.charsets.Charset
|
|||||||
import kotlinx.io.charsets.Charsets
|
import kotlinx.io.charsets.Charsets
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
import kotlinx.io.pool.useInstance
|
import kotlinx.io.pool.useInstance
|
||||||
import net.mamoe.mirai.utils.MiraiDebugAPI
|
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
@ -27,21 +26,12 @@ import kotlin.jvm.JvmMultifileClass
|
|||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
import kotlin.jvm.JvmSynthetic
|
import kotlin.jvm.JvmSynthetic
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||||
import net.mamoe.mirai.utils.io.toUHexString
|
import net.mamoe.mirai.qqandroid.utils.toUHexString
|
||||||
|
|
||||||
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
|
||||||
fun ByteReadPacket.copyTo(outputStream: OutputStream) {
|
|
||||||
ByteArrayPool.useInstance {
|
|
||||||
while (this.isNotEmpty) {
|
|
||||||
outputStream.write(it, 0, this.readAvailable(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
inline fun <R> ByteReadPacket.useBytes(
|
internal inline fun <R> ByteReadPacket.useBytes(
|
||||||
n: Int = remaining.toInt(),//not that safe but adequate
|
n: Int = remaining.toInt(),//not that safe but adequate
|
||||||
block: (data: ByteArray, length: Int) -> R
|
block: (data: ByteArray, length: Int) -> R
|
||||||
): R = ByteArrayPool.useInstance {
|
): R = ByteArrayPool.useInstance {
|
||||||
@ -50,12 +40,12 @@ inline fun <R> ByteReadPacket.useBytes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
inline fun ByteReadPacket.readPacketExact(
|
internal inline fun ByteReadPacket.readPacketExact(
|
||||||
n: Int = remaining.toInt()//not that safe but adequate
|
n: Int = remaining.toInt()//not that safe but adequate
|
||||||
): ByteReadPacket = this.readBytes(n).toReadPacket()
|
): ByteReadPacket = this.readBytes(n).toReadPacket()
|
||||||
|
|
||||||
@OptIn(ExperimentalContracts::class)
|
@OptIn(ExperimentalContracts::class)
|
||||||
inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
|
internal inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
|
||||||
contract {
|
contract {
|
||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
}
|
}
|
||||||
@ -64,24 +54,23 @@ inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
|
|||||||
|
|
||||||
private inline fun <R> inline(block: () -> R): R = block()
|
private inline fun <R> inline(block: () -> R): R = block()
|
||||||
|
|
||||||
typealias TlvMap = MutableMap<Int, ByteArray>
|
internal typealias TlvMap = MutableMap<Int, ByteArray>
|
||||||
|
|
||||||
inline fun TlvMap.getOrFail(tag: Int): ByteArray {
|
internal inline fun TlvMap.getOrFail(tag: Int): ByteArray {
|
||||||
return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)")
|
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))
|
return this[tag] ?: error(lazyMessage(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
@MiraiInternalAPI
|
@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)
|
_readTLVMap(true, tagSize, suppressDuplication)
|
||||||
|
|
||||||
@MiraiDebugAPI
|
|
||||||
@Suppress("DuplicatedCode", "FunctionName")
|
@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<Int, ByteArray>()
|
val map = mutableMapOf<Int, ByteArray>()
|
||||||
var key = 0
|
var key = 0
|
||||||
|
|
||||||
@ -138,18 +127,18 @@ fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplic
|
|||||||
return map
|
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)
|
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)
|
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)
|
String(this.readBytes(length.toInt()), charset = charset)
|
||||||
|
|
||||||
@JvmSynthetic
|
@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)
|
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)
|
String(this.readBytes(length.toInt()), charset = charset)
|
@ -6,15 +6,23 @@
|
|||||||
*
|
*
|
||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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
|
@PublishedApi
|
||||||
internal expect fun Throwable.addSuppressedMirai(e: Throwable)
|
internal expect fun Throwable.addSuppressedMirai(e: Throwable)
|
||||||
|
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R {
|
internal inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R {
|
||||||
var lastException: Throwable? = null
|
var lastException: Throwable? = null
|
||||||
|
|
||||||
repeat(repeat) {
|
repeat(repeat) {
|
||||||
@ -30,24 +38,6 @@ inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R {
|
|||||||
throw lastException!!
|
throw lastException!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@MiraiInternalAPI
|
|
||||||
@Suppress("DuplicatedCode")
|
|
||||||
inline fun <R> 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
|
@MiraiInternalAPI
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
inline fun <R> tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? {
|
inline fun <R> tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? {
|
@ -7,10 +7,10 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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 kotlinx.io.core.toByteArray
|
||||||
import net.mamoe.mirai.utils.io.encodeToString
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
@ -7,16 +7,12 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
class TypeConversionTest {
|
class TypeConversionTest {
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
@ExperimentalUnsignedTypes
|
@ -16,12 +16,12 @@ import kotlinx.io.core.Input
|
|||||||
import kotlinx.io.core.readAvailable
|
import kotlinx.io.core.readAvailable
|
||||||
import kotlinx.io.core.use
|
import kotlinx.io.core.use
|
||||||
import kotlinx.io.pool.useInstance
|
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.DefaultLogger
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
|
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
|
||||||
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 net.mamoe.mirai.utils.withSwitch
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
@ -30,13 +30,17 @@ import kotlin.contracts.contract
|
|||||||
|
|
||||||
val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true)
|
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())
|
DebugLogger.debug(name + "=" + this.toUHexString())
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalContracts::class, MiraiInternalAPI::class)
|
@OptIn(ExperimentalContracts::class, MiraiInternalAPI::class)
|
||||||
inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R {
|
internal inline fun <R> Input.debugIfFail(
|
||||||
|
name: String = "",
|
||||||
|
onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() },
|
||||||
|
block: ByteReadPacket.() -> R
|
||||||
|
): R {
|
||||||
|
|
||||||
contract {
|
contract {
|
||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
|
@ -22,7 +22,7 @@ import net.mamoe.mirai.utils.Context
|
|||||||
import net.mamoe.mirai.utils.ContextImpl
|
import net.mamoe.mirai.utils.ContextImpl
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
import net.mamoe.mirai.utils.io.ByteArrayPool
|
||||||
import net.mamoe.mirai.utils.io.toReadPacket
|
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
@OptIn(MiraiInternalAPI::class)
|
@OptIn(MiraiInternalAPI::class)
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -25,8 +25,7 @@ import java.net.Socket
|
|||||||
/**
|
/**
|
||||||
* 多平台适配的 TCP Socket.
|
* 多平台适配的 TCP Socket.
|
||||||
*/
|
*/
|
||||||
@MiraiInternalAPI
|
internal actual class PlatformSocket : Closeable {
|
||||||
actual class PlatformSocket : Closeable {
|
|
||||||
private lateinit var socket: Socket
|
private lateinit var socket: Socket
|
||||||
|
|
||||||
actual val isOpen: Boolean
|
actual val isOpen: Boolean
|
@ -7,7 +7,7 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* 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.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -24,7 +24,7 @@ import java.nio.channels.WritableByteChannel
|
|||||||
/**
|
/**
|
||||||
* 多平台适配的 DatagramChannel.
|
* 多平台适配的 DatagramChannel.
|
||||||
*/
|
*/
|
||||||
actual class PlatformDatagramChannel actual constructor(
|
internal actual class PlatformDatagramChannel actual constructor(
|
||||||
serverHost: String,
|
serverHost: String,
|
||||||
serverPort: Short
|
serverPort: Short
|
||||||
) : Closeable {
|
) : Closeable {
|
@ -0,0 +1,5 @@
|
|||||||
|
package net.mamoe.mirai.qqandroid.utils
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal actual fun Throwable.addSuppressedMirai(e: Throwable) {
|
||||||
|
}
|
@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.qqandroid.utils.cryptor
|
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.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import java.security.*
|
import java.security.*
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.qqandroid.utils
|
||||||
|
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
import kotlin.reflect.jvm.javaField
|
import kotlin.reflect.jvm.javaField
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -10,7 +10,6 @@
|
|||||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
|
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
|
||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@ -21,6 +20,7 @@ import kotlinx.io.core.copyTo
|
|||||||
import kotlinx.io.errors.IOException
|
import kotlinx.io.errors.IOException
|
||||||
import kotlinx.io.streams.asInput
|
import kotlinx.io.streams.asInput
|
||||||
import kotlinx.io.streams.asOutput
|
import kotlinx.io.streams.asOutput
|
||||||
|
import net.mamoe.mirai.utils.internal.md5
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@ -29,7 +29,6 @@ import java.net.URL
|
|||||||
* 将各类型图片容器转为 [ExternalImage]
|
* 将各类型图片容器转为 [ExternalImage]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取 [Bitmap] 的属性, 然后构造 [ExternalImage]
|
* 读取 [Bitmap] 的属性, 然后构造 [ExternalImage]
|
||||||
*/
|
*/
|
||||||
@ -53,7 +52,7 @@ fun File.toExternalImage(): ExternalImage {
|
|||||||
return ExternalImage(
|
return ExternalImage(
|
||||||
width = input.width,
|
width = input.width,
|
||||||
height = input.height,
|
height = input.height,
|
||||||
md5 = this.inputStream().use { MiraiPlatformUtils.md5(it) },
|
md5 = this.inputStream().use { it.md5() },
|
||||||
imageFormat = this.nameWithoutExtension,
|
imageFormat = this.nameWithoutExtension,
|
||||||
input = this.inputStream().asInput(IoBuffer.Pool),
|
input = this.inputStream().asInput(IoBuffer.Pool),
|
||||||
inputSize = this.length(),
|
inputSize = this.length(),
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -20,9 +20,7 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlinx.serialization.UnstableDefault
|
import kotlinx.serialization.UnstableDefault
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import net.mamoe.mirai.utils.internal.md5
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress
|
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +39,10 @@ fun File.loadAsDeviceInfo(context: Context): DeviceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableDefault::class)
|
@OptIn(UnstableDefault::class)
|
||||||
private val JSON = Json(JsonConfiguration.Default)
|
private val JSON = Json {
|
||||||
|
isLenient = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部分引用指向 [Build].
|
* 部分引用指向 [Build].
|
||||||
@ -109,9 +110,9 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
|
|||||||
@OptIn(MiraiInternalAPI::class)
|
@OptIn(MiraiInternalAPI::class)
|
||||||
override val imsiMd5: ByteArray
|
override val imsiMd5: ByteArray
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint("HardwareIds")
|
||||||
get() = md5(kotlin.runCatching {
|
get() = kotlin.runCatching {
|
||||||
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).subscriberId.toByteArray()
|
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).subscriberId.toByteArray()
|
||||||
}.getOrEmpty())
|
}.getOrEmpty().md5()
|
||||||
|
|
||||||
override val imei: String
|
override val imei: String
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint("HardwareIds")
|
||||||
@ -124,11 +125,6 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
|
|||||||
}
|
}
|
||||||
}.getOrElse { "" }
|
}.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 androidId: ByteArray get() = Build.ID.toByteArray()
|
||||||
override val apn: ByteArray get() = "wifi".toByteArray()
|
override val apn: ByteArray get() = "wifi".toByteArray()
|
||||||
|
|
@ -1,4 +1,6 @@
|
|||||||
package net.mamoe.mirai.utils
|
@file:Suppress("DuplicatedCode")
|
||||||
|
|
||||||
|
package net.mamoe.mirai.utils.internal
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
|
@ -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
|
@ -8,112 +8,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("NOTHING_TO_INLINE")
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
|
@file:JvmMultifileClass
|
||||||
|
@file:JvmName("Utils")
|
||||||
|
|
||||||
package net.mamoe.mirai.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()
|
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() }
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,7 @@ package net.mamoe.mirai
|
|||||||
import kotlinx.io.core.toByteArray
|
import kotlinx.io.core.toByteArray
|
||||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils
|
import net.mamoe.mirai.utils.internal.md5
|
||||||
import kotlin.annotation.AnnotationTarget.*
|
import kotlin.annotation.AnnotationTarget.*
|
||||||
|
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
@ -28,7 +28,7 @@ data class BotAccount(
|
|||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
val passwordMd5: ByteArray // md5
|
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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
@ -22,6 +22,7 @@ import net.mamoe.mirai.network.ForceOfflineException
|
|||||||
import net.mamoe.mirai.network.LoginFailedException
|
import net.mamoe.mirai.network.LoginFailedException
|
||||||
import net.mamoe.mirai.network.closeAndJoin
|
import net.mamoe.mirai.network.closeAndJoin
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
|
import net.mamoe.mirai.utils.internal.tryNTimesOrException
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -18,8 +18,10 @@ import net.mamoe.mirai.event.BroadcastControllable
|
|||||||
import net.mamoe.mirai.event.CancellableEvent
|
import net.mamoe.mirai.event.CancellableEvent
|
||||||
import net.mamoe.mirai.event.events.ImageUploadEvent.Failed
|
import net.mamoe.mirai.event.events.ImageUploadEvent.Failed
|
||||||
import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed
|
import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed
|
||||||
|
import net.mamoe.mirai.message.data.ExperimentalMessageSource
|
||||||
import net.mamoe.mirai.message.data.Image
|
import net.mamoe.mirai.message.data.Image
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
|
import net.mamoe.mirai.message.data.MessageSource
|
||||||
import net.mamoe.mirai.qqandroid.network.Packet
|
import net.mamoe.mirai.qqandroid.network.Packet
|
||||||
import net.mamoe.mirai.utils.ExternalImage
|
import net.mamoe.mirai.utils.ExternalImage
|
||||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||||
@ -96,6 +98,7 @@ sealed class MessageSendEvent : BotEvent, BotActiveEvent, AbstractCancellableEve
|
|||||||
/**
|
/**
|
||||||
* 消息撤回事件. 可是任意消息被任意人撤回.
|
* 消息撤回事件. 可是任意消息被任意人撤回.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalMessageSource::class)
|
||||||
sealed class MessageRecallEvent : BotEvent {
|
sealed class MessageRecallEvent : BotEvent {
|
||||||
/**
|
/**
|
||||||
* 消息原发送人
|
* 消息原发送人
|
||||||
@ -106,6 +109,7 @@ sealed class MessageRecallEvent : BotEvent {
|
|||||||
* 消息 id.
|
* 消息 id.
|
||||||
* @see MessageSource.id
|
* @see MessageSource.id
|
||||||
*/
|
*/
|
||||||
|
@ExperimentalMessageSource
|
||||||
abstract val messageId: Long
|
abstract val messageId: Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,6 +122,7 @@ sealed class MessageRecallEvent : BotEvent {
|
|||||||
*/
|
*/
|
||||||
data class FriendRecall(
|
data class FriendRecall(
|
||||||
override val bot: Bot,
|
override val bot: Bot,
|
||||||
|
@ExperimentalMessageSource
|
||||||
override val messageId: Long,
|
override val messageId: Long,
|
||||||
override val messageTime: Int,
|
override val messageTime: Int,
|
||||||
/**
|
/**
|
||||||
@ -132,6 +137,7 @@ sealed class MessageRecallEvent : BotEvent {
|
|||||||
data class GroupRecall(
|
data class GroupRecall(
|
||||||
override val bot: Bot,
|
override val bot: Bot,
|
||||||
override val authorId: Long,
|
override val authorId: Long,
|
||||||
|
@ExperimentalMessageSource
|
||||||
override val messageId: Long,
|
override val messageId: Long,
|
||||||
override val messageTime: Int,
|
override val messageTime: Int,
|
||||||
/**
|
/**
|
||||||
|
@ -16,14 +16,15 @@ import net.mamoe.mirai.event.Event
|
|||||||
import net.mamoe.mirai.event.EventDisabled
|
import net.mamoe.mirai.event.EventDisabled
|
||||||
import net.mamoe.mirai.event.Listener
|
import net.mamoe.mirai.event.Listener
|
||||||
import net.mamoe.mirai.event.ListeningStatus
|
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.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
import kotlin.jvm.JvmField
|
import kotlin.jvm.JvmField
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false)
|
|
||||||
|
|
||||||
@MiraiInternalAPI
|
@MiraiInternalAPI
|
||||||
fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
|
fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
|
||||||
with(this.listeners()) {
|
with(this.listeners()) {
|
||||||
@ -67,7 +68,6 @@ internal class Handler<in E : Event>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@OptIn(MiraiDebugAPI::class)
|
|
||||||
override suspend fun onEvent(event: E): ListeningStatus {
|
override suspend fun onEvent(event: E): ListeningStatus {
|
||||||
if (isCompleted || isCancelled) return ListeningStatus.STOPPED
|
if (isCompleted || isCancelled) return ListeningStatus.STOPPED
|
||||||
if (!isActive) return ListeningStatus.LISTENING
|
if (!isActive) return ListeningStatus.LISTENING
|
||||||
@ -144,8 +144,6 @@ internal object EventListenerManager {
|
|||||||
internal suspend inline fun Event.broadcastInternal() = coroutineScope {
|
internal suspend inline fun Event.broadcastInternal() = coroutineScope {
|
||||||
if (EventDisabled) return@coroutineScope
|
if (EventDisabled) return@coroutineScope
|
||||||
|
|
||||||
EventLogger.info { "Event broadcast: $this" }
|
|
||||||
|
|
||||||
val listeners = this@broadcastInternal::class.listeners()
|
val listeners = this@broadcastInternal::class.listeners()
|
||||||
callAndRemoveIfRequired(this@broadcastInternal, listeners)
|
callAndRemoveIfRequired(this@broadcastInternal, listeners)
|
||||||
listeners.supertypes.forEach {
|
listeners.supertypes.forEach {
|
||||||
|
@ -84,6 +84,11 @@ inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
//// internal
|
||||||
|
//////////////
|
||||||
|
|
||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
|
internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
|
@ -18,6 +18,9 @@ import kotlin.jvm.JvmName
|
|||||||
/**
|
/**
|
||||||
* 链接的两个消息.
|
* 链接的两个消息.
|
||||||
*
|
*
|
||||||
|
* 不要直接构造 [CombinedMessage], 使用 [Message.plus]
|
||||||
|
* 要连接多个 [Message], 使用 [buildMessageChain]
|
||||||
|
*
|
||||||
* @see Message.plus
|
* @see Message.plus
|
||||||
*
|
*
|
||||||
* Left-biased list
|
* Left-biased list
|
||||||
|
@ -18,7 +18,6 @@ import kotlinx.coroutines.Job
|
|||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
|
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
package net.mamoe.mirai.network
|
package net.mamoe.mirai.network
|
||||||
|
|
||||||
class ForceOfflineException(override val message: String?) : RuntimeException()
|
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)
|
@ -7,10 +7,14 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.mamoe.mirai.network
|
package net.mamoe.mirai.network
|
||||||
|
|
||||||
|
import net.mamoe.mirai.Bot
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 正常登录失败时抛出
|
* 在 [登录][Bot.login] 失败时抛出, 可正常地中断登录过程.
|
||||||
*/
|
*/
|
||||||
sealed class LoginFailedException : RuntimeException {
|
sealed class LoginFailedException : RuntimeException {
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
@ -19,4 +23,17 @@ sealed class LoginFailedException : RuntimeException {
|
|||||||
constructor(cause: Throwable?) : super(cause)
|
constructor(cause: Throwable?) : super(cause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码输入错误
|
||||||
|
*/
|
||||||
class WrongPasswordException(message: String?) : LoginFailedException(message)
|
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)
|
||||||
|
}
|
@ -6,129 +6,122 @@
|
|||||||
*
|
*
|
||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
@file:Suppress("unused", "DEPRECATION_ERROR")
|
||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.network.BotNetworkHandler
|
import net.mamoe.mirai.network.BotNetworkHandler
|
||||||
import net.mamoe.mirai.network.LoginFailedException
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import kotlin.jvm.JvmOverloads
|
||||||
import kotlin.jvm.JvmStatic
|
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] 配置
|
* [Bot] 配置
|
||||||
*/
|
*/
|
||||||
@Suppress("PropertyName")
|
@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]
|
* 父 [CoroutineContext]
|
||||||
*/
|
*/
|
||||||
var parentCoroutineContext: CoroutineContext
|
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 心跳周期. 过长会导致被服务器断开连接.
|
* 心跳周期. 过长会导致被服务器断开连接.
|
||||||
*/
|
*/
|
||||||
var heartbeatPeriodMillis: Long
|
var heartbeatPeriodMillis: Long = 60.secondsToMillis
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每次心跳时等待结果的时间.
|
* 每次心跳时等待结果的时间.
|
||||||
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
|
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 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 {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* 默认的配置实例
|
* 默认的配置实例
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val Default: BotConfiguration
|
val Default = BotConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun _NoNetworkLog.unaryPlus()
|
/**
|
||||||
|
* 不显示网络日志
|
||||||
|
*/
|
||||||
|
fun noNetworkLog() {
|
||||||
|
networkLoggerSupplier = { _: BotNetworkHandler -> SilentLogger }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不记录网络层的 log.
|
* 使用文件存储设备信息
|
||||||
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
|
*
|
||||||
|
* 此函数只在 JVM 有效. 在其他平台将会导致一直使用默认的随机的设备信息.
|
||||||
*/
|
*/
|
||||||
@BotConfigurationDsl
|
@JvmOverloads
|
||||||
val NoNetworkLog: _NoNetworkLog
|
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
|
* 使用文件系统存储设备信息.
|
||||||
|
*/
|
||||||
|
@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)?
|
@ -49,14 +49,12 @@ abstract class DeviceInfo {
|
|||||||
abstract val imsiMd5: ByteArray
|
abstract val imsiMd5: ByteArray
|
||||||
abstract val imei: String
|
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 androidId: ByteArray
|
||||||
|
|
||||||
abstract val apn: ByteArray
|
abstract val apn: ByteArray
|
||||||
|
|
||||||
val guid: ByteArray by lazy { generateGuid(androidId, macAddress) }
|
|
||||||
|
|
||||||
fun generateDeviceInfoData(): ByteArray {
|
fun generateDeviceInfoData(): ByteArray {
|
||||||
@Serializable
|
@Serializable
|
||||||
class DevInfo(
|
class DevInfo(
|
||||||
@ -121,11 +119,6 @@ class DeviceInfoData(
|
|||||||
@Transient
|
@Transient
|
||||||
override lateinit var context: Context
|
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
|
override val androidId: ByteArray get() = display
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -137,13 +130,6 @@ class DeviceInfoData(
|
|||||||
) : Version
|
) : Version
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
|
|
||||||
*/
|
|
||||||
@OptIn(MiraiInternalAPI::class)
|
|
||||||
fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
|
|
||||||
MiraiPlatformUtils.md5(androidId + macAddress)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo(
|
fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo(
|
||||||
brand = brand.encodeToString(),
|
brand = brand.encodeToString(),
|
||||||
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.io.ByteReadChannel
|
|||||||
import kotlinx.io.InputStream
|
import kotlinx.io.InputStream
|
||||||
import kotlinx.io.core.ByteReadPacket
|
import kotlinx.io.core.ByteReadPacket
|
||||||
import kotlinx.io.core.Input
|
import kotlinx.io.core.Input
|
||||||
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
import net.mamoe.mirai.contact.Contact
|
import net.mamoe.mirai.contact.Contact
|
||||||
import net.mamoe.mirai.contact.Group
|
import net.mamoe.mirai.contact.Group
|
||||||
import net.mamoe.mirai.contact.QQ
|
import net.mamoe.mirai.contact.QQ
|
||||||
@ -22,8 +23,7 @@ import net.mamoe.mirai.message.MessageReceipt
|
|||||||
import net.mamoe.mirai.message.data.Image
|
import net.mamoe.mirai.message.data.Image
|
||||||
import net.mamoe.mirai.message.data.OfflineImage
|
import net.mamoe.mirai.message.data.OfflineImage
|
||||||
import net.mamoe.mirai.message.data.sendTo
|
import net.mamoe.mirai.message.data.sendTo
|
||||||
import net.mamoe.mirai.utils.io.toUHexString
|
import kotlin.jvm.JvmSynthetic
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外部图片. 图片数据还没有读取到内存.
|
* 外部图片. 图片数据还没有读取到内存.
|
||||||
@ -150,6 +150,7 @@ class ExternalImage private constructor(
|
|||||||
/**
|
/**
|
||||||
* 将图片发送给指定联系人
|
* 将图片发送给指定联系人
|
||||||
*/
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> = when (contact) {
|
suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> = when (contact) {
|
||||||
is Group -> contact.uploadImage(this).sendTo(contact)
|
is Group -> contact.uploadImage(this).sendTo(contact)
|
||||||
is QQ -> contact.uploadImage(this).sendTo(contact)
|
is QQ -> contact.uploadImage(this).sendTo(contact)
|
||||||
@ -162,6 +163,7 @@ suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> =
|
|||||||
*
|
*
|
||||||
* @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人
|
* @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人
|
||||||
*/
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) {
|
suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) {
|
||||||
is Group -> contact.uploadImage(this)
|
is Group -> contact.uploadImage(this)
|
||||||
is QQ -> contact.uploadImage(this)
|
is QQ -> contact.uploadImage(this)
|
||||||
@ -171,10 +173,18 @@ suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact
|
|||||||
/**
|
/**
|
||||||
* 将图片发送给 [this]
|
* 将图片发送给 [this]
|
||||||
*/
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
suspend inline fun <C : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = image.sendTo(this)
|
suspend inline fun <C : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = image.sendTo(this)
|
||||||
|
|
||||||
private operator fun ByteArray.get(range: IntRange): String = buildString {
|
internal operator fun ByteArray.get(range: IntRange): String = buildString {
|
||||||
range.forEach {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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<out Throwable>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码, 设备锁解决器
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -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<Image> by suspendLazy{ /* intializer */ }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
inline fun <R> CoroutineScope.suspendLazy(context: CoroutineContext = EmptyCoroutineContext, noinline initializer: suspend () -> R): Lazy<Deferred<R>> =
|
|
||||||
SuspendLazy(this, context, initializer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 挂起初始化的 [lazy], 是属性不能 `suspend` 的替代品.
|
|
||||||
*
|
|
||||||
* 本对象代表值初始化时将会通过 [CoroutineScope.async] 运行 [valueUpdater]
|
|
||||||
*
|
|
||||||
* [SuspendLazy] 是:
|
|
||||||
* - 线程安全
|
|
||||||
* - 只会被初始化一次.
|
|
||||||
* - `SuspendLazy<R>.value` 返回 `Deferred<R>`
|
|
||||||
* - 可以用作属性代表 (`val profile: by SuspendLazy(GlobalScope) { calculateValue() }`)
|
|
||||||
*
|
|
||||||
* @sample QQ.profile
|
|
||||||
*/
|
|
||||||
@PublishedApi
|
|
||||||
internal class SuspendLazy<R>(scope: CoroutineScope, coroutineContext: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R) :
|
|
||||||
Lazy<Deferred<R>> {
|
|
||||||
private val valueUpdater: Deferred<R> by lazy { scope.async(context = coroutineContext) { initializer() } }
|
|
||||||
|
|
||||||
override val value: Deferred<R>
|
|
||||||
get() = valueUpdater
|
|
||||||
|
|
||||||
override fun isInitialized(): Boolean = valueUpdater.isCompleted
|
|
||||||
}
|
|
@ -7,10 +7,11 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused", "NOTHING_TO_INLINE")
|
||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmSynthetic
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
// TODO: 2020/2/10 添加中文 doc
|
// 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
|
* WeakRef that `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only
|
||||||
*/
|
*/
|
||||||
inline class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) {
|
class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) {
|
||||||
fun get(): T = weakRef.get() ?: error("WeakRef is released")
|
fun get(): T = weakRef.get() ?: error("WeakRef is released")
|
||||||
fun clear() = weakRef.clear()
|
fun clear() = weakRef.clear()
|
||||||
}
|
}
|
||||||
@ -30,7 +31,8 @@ inline class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) {
|
|||||||
* val bot: Bot by param.unsafeWeakRef()
|
* val bot: Bot by param.unsafeWeakRef()
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
operator fun <T> UnsafeWeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
|
@JvmSynthetic
|
||||||
|
inline operator fun <T> UnsafeWeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Weak Reference.
|
* Weak Reference.
|
||||||
@ -62,12 +64,14 @@ annotation class WeakRefProperty
|
|||||||
* Provides a weak reference to [this]
|
* Provides a weak reference to [this]
|
||||||
* The `getValue` for delegation returns [this] when [this] is not released by GC
|
* The `getValue` for delegation returns [this] when [this] is not released by GC
|
||||||
*/
|
*/
|
||||||
fun <T> T.weakRef(): WeakRef<T> = WeakRef(this)
|
@JvmSynthetic
|
||||||
|
inline fun <T> T.weakRef(): WeakRef<T> = WeakRef(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an unsafe inline delegate for [this]
|
* Constructs an unsafe inline delegate for [this]
|
||||||
*/
|
*/
|
||||||
fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this)
|
@JvmSynthetic
|
||||||
|
inline fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a weak reference to [this].
|
* Provides a weak reference to [this].
|
||||||
@ -75,7 +79,8 @@ fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this)
|
|||||||
*
|
*
|
||||||
* **UNSTABLE API**: It is strongly suggested not to use this api
|
* **UNSTABLE API**: It is strongly suggested not to use this api
|
||||||
*/
|
*/
|
||||||
fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef())
|
@JvmSynthetic
|
||||||
|
inline fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides delegate value.
|
* Provides delegate value.
|
||||||
@ -84,9 +89,11 @@ fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef())
|
|||||||
* val bot: Bot? by param.weakRef()
|
* val bot: Bot? by param.weakRef()
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
operator fun <T> WeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get()
|
@JvmSynthetic
|
||||||
|
inline operator fun <T> WeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call the block if the referent is absent
|
* Call the block if the referent is absent
|
||||||
*/
|
*/
|
||||||
|
@JvmSynthetic
|
||||||
inline fun <T, R> WeakRef<T>.ifAbsent(block: (T) -> R): R? = this.get()?.let(block)
|
inline fun <T, R> WeakRef<T>.ifAbsent(block: (T) -> R): R? = this.get()?.let(block)
|
@ -42,19 +42,6 @@ annotation class MiraiExperimentalAPI(
|
|||||||
val message: String = ""
|
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.
|
* 标记一个自 Mirai 某个版本起才支持或在这个版本修改过的 API.
|
||||||
*/
|
*/
|
||||||
@ -62,3 +49,11 @@ annotation class MiraiDebugAPI(
|
|||||||
@Retention(AnnotationRetention.SOURCE)
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
@MustBeDocumented
|
@MustBeDocumented
|
||||||
annotation class SinceMirai(val version: String)
|
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)
|
@ -18,10 +18,8 @@ import kotlinx.coroutines.io.ByteReadChannel
|
|||||||
import kotlinx.coroutines.io.ByteWriteChannel
|
import kotlinx.coroutines.io.ByteWriteChannel
|
||||||
import kotlinx.coroutines.io.readAvailable
|
import kotlinx.coroutines.io.readAvailable
|
||||||
import kotlinx.io.OutputStream
|
import kotlinx.io.OutputStream
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
|
||||||
import kotlinx.io.core.Output
|
import kotlinx.io.core.Output
|
||||||
import kotlinx.io.pool.useInstance
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
|
||||||
import kotlin.jvm.JvmMultifileClass
|
import kotlin.jvm.JvmMultifileClass
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
@ -31,73 +29,54 @@ import kotlin.jvm.JvmName
|
|||||||
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
|
||||||
*/
|
*/
|
||||||
@OptIn(InternalSerializationApi::class)
|
@OptIn(InternalSerializationApi::class)
|
||||||
|
@MiraiExperimentalAPI
|
||||||
suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
|
suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.write(buffer, 0, size)
|
dst.write(buffer, 0, size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
|
||||||
*/
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
suspend fun ByteReadChannel.copyTo(dst: Output) {
|
suspend fun ByteReadChannel.copyTo(dst: Output) {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.writeFully(buffer, 0, size)
|
dst.writeFully(buffer, 0, size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
|
||||||
*/
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) {
|
suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.writeFully(buffer, 0, size)
|
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
|
// copyAndClose
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
||||||
*/
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
@OptIn(InternalSerializationApi::class)
|
@OptIn(InternalSerializationApi::class)
|
||||||
suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 API 不是 internal 的
|
suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 API 不是 internal 的
|
||||||
try {
|
try {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.write(buffer, 0, size)
|
dst.write(buffer, 0, size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
dst.close()
|
dst.close()
|
||||||
}
|
}
|
||||||
@ -106,15 +85,14 @@ suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个
|
|||||||
/**
|
/**
|
||||||
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
||||||
*/
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
suspend fun ByteReadChannel.copyAndClose(dst: Output) {
|
suspend fun ByteReadChannel.copyAndClose(dst: Output) {
|
||||||
try {
|
try {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.writeFully(buffer, 0, size)
|
dst.writeFully(buffer, 0, size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
dst.close()
|
dst.close()
|
||||||
}
|
}
|
||||||
@ -123,16 +101,15 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) {
|
|||||||
/**
|
/**
|
||||||
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
||||||
*/
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) {
|
suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) {
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
try {
|
try {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.writeFully(buffer, 0, size)
|
dst.writeFully(buffer, 0, size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
dst.close(null)
|
dst.close(null)
|
||||||
@ -142,35 +119,17 @@ suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) {
|
|||||||
/**
|
/**
|
||||||
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
|
||||||
*/
|
*/
|
||||||
|
@MiraiExperimentalAPI
|
||||||
suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) {
|
suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) {
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
try {
|
try {
|
||||||
@OptIn(MiraiInternalAPI::class)
|
val buffer = ByteArray(2048)
|
||||||
ByteArrayPool.useInstance { buffer ->
|
|
||||||
var size: Int
|
var size: Int
|
||||||
while (this.readAvailable(buffer).also { size = it } > 0) {
|
while (this.readAvailable(buffer).also { size = it } > 0) {
|
||||||
dst.writeFully(buffer, 0, size)
|
dst.writeFully(buffer, 0, size)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
dst.close(null)
|
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
@ -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<Byte>` 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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 <R> 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!!
|
||||||
|
}
|
@ -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<ByteArray>(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" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
|
|
||||||
|
|
||||||
inline fun <K, V> Map<K, V>.firstValueOrNull(): V? = this.entries.firstOrNull()?.value
|
|
||||||
|
|
||||||
inline fun <K, V> Map<K, V>.firstKey(): K = this.entries.first().key
|
|
||||||
|
|
||||||
inline fun <K, V> Map<K, V>.firstKeyOrNull(): K? = this.entries.firstOrNull()?.key
|
|
@ -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
|
|
@ -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
|
@ -7,13 +7,19 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
|
|
||||||
package net.mamoe.mirai.message.data
|
package net.mamoe.mirai.message.data
|
||||||
|
|
||||||
import net.mamoe.mirai.utils.io.autoHexToBytes
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
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 {
|
internal class ImageTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -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])
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import kotlinx.io.core.use
|
|||||||
import net.mamoe.mirai.contact.Contact
|
import net.mamoe.mirai.contact.Contact
|
||||||
import net.mamoe.mirai.contact.QQ
|
import net.mamoe.mirai.contact.QQ
|
||||||
import net.mamoe.mirai.message.data.Image
|
import net.mamoe.mirai.message.data.Image
|
||||||
|
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||||
import net.mamoe.mirai.utils.copyAndClose
|
import net.mamoe.mirai.utils.copyAndClose
|
||||||
import net.mamoe.mirai.utils.copyTo
|
import net.mamoe.mirai.utils.copyTo
|
||||||
@ -31,7 +32,7 @@ import java.net.URL
|
|||||||
* 一条从服务器接收到的消息事件.
|
* 一条从服务器接收到的消息事件.
|
||||||
* JVM 平台相关扩展
|
* JVM 平台相关扩展
|
||||||
*/
|
*/
|
||||||
@OptIn(MiraiInternalAPI::class)
|
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
|
||||||
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
|
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
|
||||||
// region 上传图片
|
// region 上传图片
|
||||||
suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
|
suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
|
import kotlinx.io.pool.DefaultPool
|
||||||
|
|
||||||
actual abstract class Context
|
actual abstract class Context
|
||||||
|
|
||||||
open class ContextImpl : Context()
|
open class ContextImpl : Context()
|
@ -19,7 +19,7 @@ import kotlinx.io.core.buildPacket
|
|||||||
import kotlinx.io.core.copyTo
|
import kotlinx.io.core.copyTo
|
||||||
import kotlinx.io.errors.IOException
|
import kotlinx.io.errors.IOException
|
||||||
import kotlinx.io.streams.asOutput
|
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.awt.image.BufferedImage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -72,7 +72,7 @@ fun File.toExternalImage(): ExternalImage {
|
|||||||
return ExternalImage(
|
return ExternalImage(
|
||||||
width = image.getWidth(0),
|
width = image.getWidth(0),
|
||||||
height = image.getHeight(0),
|
height = image.getHeight(0),
|
||||||
md5 = MiraiPlatformUtils.md5(this.inputStream()), // dont change
|
md5 = this.inputStream().md5(), // dont change
|
||||||
imageFormat = image.formatName,
|
imageFormat = image.formatName,
|
||||||
input = this.inputStream(),
|
input = this.inputStream(),
|
||||||
filename = this.name
|
filename = this.name
|
||||||
|
@ -31,6 +31,9 @@ import javax.imageio.ImageIO
|
|||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
|
actual typealias Throws = kotlin.jvm.Throws
|
||||||
|
|
||||||
|
@MiraiExperimentalAPI
|
||||||
class DefaultLoginSolver(
|
class DefaultLoginSolver(
|
||||||
private val input: suspend () -> String,
|
private val input: suspend () -> String,
|
||||||
private val overrideLogger: MiraiLogger? = null
|
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
|
// Copied from Ktor CIO
|
||||||
private fun File.writeChannel(
|
private fun File.writeChannel(
|
||||||
coroutineContext: CoroutineContext = Dispatchers.IO
|
coroutineContext: CoroutineContext = Dispatchers.IO
|
||||||
@ -103,7 +131,6 @@ private fun File.writeChannel(
|
|||||||
}
|
}
|
||||||
}.channel
|
}.channel
|
||||||
|
|
||||||
|
|
||||||
private val loginSolverLock = Mutex()
|
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") })
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,8 @@
|
|||||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:JvmMultifileClass
|
||||||
|
@file:JvmName("Utils")
|
||||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
|
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
|
||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
@ -15,7 +17,6 @@ import io.ktor.client.HttpClient
|
|||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.ktor.util.KtorExperimentalAPI
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
import kotlinx.io.pool.useInstance
|
import kotlinx.io.pool.useInstance
|
||||||
import net.mamoe.mirai.utils.io.ByteArrayPool
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -31,88 +32,3 @@ import java.util.zip.Inflater
|
|||||||
*/
|
*/
|
||||||
actual val currentTimeMillis: Long
|
actual val currentTimeMillis: Long
|
||||||
get() = System.currentTimeMillis()
|
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -12,19 +12,16 @@ package net.mamoe.mirai.utils
|
|||||||
import kotlinx.io.core.toByteArray
|
import kotlinx.io.core.toByteArray
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlinx.serialization.UnstableDefault
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress
|
import net.mamoe.mirai.utils.internal.md5
|
||||||
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
|
|
||||||
import net.mamoe.mirai.utils.io.getRandomByteArray
|
|
||||||
import net.mamoe.mirai.utils.io.getRandomString
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
|
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
|
||||||
*/
|
*/
|
||||||
@OptIn(UnstableDefault::class)
|
|
||||||
fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
|
fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
|
||||||
if (!this.exists() || this.length() == 0L) {
|
if (!this.exists() || this.length() == 0L) {
|
||||||
return SystemDeviceInfo(context).also {
|
return SystemDeviceInfo(context).also {
|
||||||
@ -55,9 +52,11 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
|
|||||||
override val brand: ByteArray = "mamoe".toByteArray()
|
override val brand: ByteArray = "mamoe".toByteArray()
|
||||||
override val model: ByteArray = "mirai".toByteArray()
|
override val model: ByteArray = "mirai".toByteArray()
|
||||||
override val bootloader: ByteArray = "unknown".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 fingerprint: ByteArray =
|
||||||
override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray()
|
"mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray()
|
||||||
override val procVersion: ByteArray = "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".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 baseBand: ByteArray = byteArrayOf()
|
||||||
override val version: Version = Version
|
override val version: Version = Version
|
||||||
override val simInfo: ByteArray = "T-Mobile".toByteArray()
|
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 macAddress: ByteArray = "02:00:00:00:00:00".toByteArray()
|
||||||
override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray()
|
override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray()
|
||||||
override val wifiSSID: ByteArray? = "<unknown ssid>".toByteArray()
|
override val wifiSSID: ByteArray? = "<unknown ssid>".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 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 androidId: ByteArray get() = display
|
||||||
override val apn: ByteArray = "wifi".toByteArray()
|
override val apn: ByteArray = "wifi".toByteArray()
|
||||||
|
|
||||||
@ -79,3 +77,26 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
|
|||||||
override val sdk: Int = 29
|
override val sdk: Int = 29
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成长度为 [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() })
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils.internal
|
||||||
|
|
||||||
private var isAddSuppressedSupported: Boolean = true
|
private var isAddSuppressedSupported: Boolean = true
|
||||||
|
|
@ -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
|
Loading…
Reference in New Issue
Block a user