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