Rearrange internal APIs

This commit is contained in:
Him188 2020-04-02 09:43:50 +08:00
parent 97b6627338
commit 97522bdf2a
99 changed files with 1086 additions and 1111 deletions

View File

@ -1,3 +1,5 @@
# mirai-core-qqandroid # mirai-core-qqandroid
Protocol support for QQ for Android for Mirai.
QQ for Android 8.2.7 协议实现.
相较于 `mirai-core`, 此模块仅提供协议和功能的实现, 不提供额外的公开的 API.

View File

@ -17,11 +17,11 @@ import kotlinx.coroutines.io.*
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import java.nio.ByteBuffer import java.nio.ByteBuffer
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)

View File

@ -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() }
}
}

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -23,12 +23,13 @@ import java.nio.channels.WritableByteChannel
/** /**
* 多平台适配的 DatagramChannel. * 多平台适配的 DatagramChannel.
*/ */
actual class PlatformDatagramChannel actual constructor( internal actual class PlatformDatagramChannel actual constructor(
serverHost: String, serverHost: String,
serverPort: Short serverPort: Short
) : Closeable { ) : Closeable {
@PublishedApi @PublishedApi
internal val channel: DatagramChannel = DatagramChannel.open().connect(InetSocketAddress(serverHost, serverPort.toInt())) internal val channel: DatagramChannel =
DatagramChannel.open().connect(InetSocketAddress(serverHost, serverPort.toInt()))
actual val isOpen: Boolean get() = channel.isOpen actual val isOpen: Boolean get() = channel.isOpen
override fun close() = channel.close() override fun close() = channel.close()

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -16,7 +16,6 @@ import kotlinx.io.core.Closeable
import kotlinx.io.core.ExperimentalIoApi import kotlinx.io.core.ExperimentalIoApi
import kotlinx.io.streams.readPacketAtMost import kotlinx.io.streams.readPacketAtMost
import kotlinx.io.streams.writePacket import kotlinx.io.streams.writePacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.IOException import java.io.IOException
@ -25,8 +24,7 @@ import java.net.Socket
/** /**
* 多平台适配的 TCP Socket. * 多平台适配的 TCP Socket.
*/ */
@MiraiInternalAPI internal actual class PlatformSocket : Closeable {
actual class PlatformSocket : Closeable {
private lateinit var socket: Socket private lateinit var socket: Socket
actual val isOpen: Boolean actual val isOpen: Boolean

View File

@ -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
}
}

View File

@ -10,9 +10,9 @@
package net.mamoe.mirai.qqandroid.utils.cryptor package net.mamoe.mirai.qqandroid.utils.cryptor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils.md5
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
import java.security.* import java.security.*
import java.security.spec.ECGenParameterSpec import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.qqandroid.utils
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaField

View File

@ -45,10 +45,11 @@ import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.* import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.collections.asSequence import kotlin.collections.asSequence
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.math.absoluteValue import kotlin.math.absoluteValue

View File

@ -37,8 +37,9 @@ import net.mamoe.mirai.qqandroid.network.highway.postImage
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext

View File

@ -6,7 +6,7 @@ import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialFormat import kotlinx.serialization.SerialFormat
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
interface IOFormat : SerialFormat { internal interface IOFormat : SerialFormat {
fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output) fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)

View File

@ -38,7 +38,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.ZERO_TYPE
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceHead import net.mamoe.mirai.qqandroid.io.serialization.jce.JceHead
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.qqandroid.utils.toReadPacket
@PublishedApi @PublishedApi
internal val CharsetGBK = Charset.forName("GBK") internal val CharsetGBK = Charset.forName("GBK")

View File

@ -19,7 +19,7 @@ import kotlinx.serialization.modules.SerialModule
import net.mamoe.mirai.qqandroid.io.serialization.IOFormat import net.mamoe.mirai.qqandroid.io.serialization.IOFormat
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.qqandroid.io.serialization.JceOld import net.mamoe.mirai.qqandroid.io.serialization.JceOld
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.qqandroid.utils.toReadPacket
/** /**
* Jce 数据结构序列化和反序列化器. * Jce 数据结构序列化和反序列化器.

View File

@ -23,11 +23,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -76,6 +74,7 @@ internal fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: Deseria
} }
} }
} }
private fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
internal fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { internal fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R {
val request = this.readJceStruct(RequestPacket.serializer()) val request = this.readJceStruct(RequestPacket.serializer())

View File

@ -22,10 +22,11 @@ import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.hexToBytes
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。")

View File

@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.message
import net.mamoe.mirai.message.data.Face import net.mamoe.mirai.message.data.Face
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.qqandroid.utils.hexToBytes
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.qqandroid.utils.toByteArray
internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes() internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes()

View File

@ -14,9 +14,8 @@ import net.mamoe.mirai.message.data.OfflineGroupImage
import net.mamoe.mirai.message.data.OnlineFriendImage import net.mamoe.mirai.message.data.OnlineFriendImage
import net.mamoe.mirai.message.data.OnlineGroupImage import net.mamoe.mirai.message.data.OnlineGroupImage
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.utils.hexToBytes
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.io.hexToBytes
internal class OnlineGroupImageImpl( internal class OnlineGroupImageImpl(
internal val delegate: ImMsgBody.CustomFace internal val delegate: ImMsgBody.CustomFace

View File

@ -36,11 +36,12 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.qqandroid.utils.PlatformSocket
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
import net.mamoe.mirai.qqandroid.utils.io.useBytes import net.mamoe.mirai.qqandroid.utils.io.useBytes
import net.mamoe.mirai.qqandroid.utils.tryNTimes
import net.mamoe.mirai.qqandroid.utils.tryNTimesOrException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.PlatformSocket
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.Volatile import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ -570,10 +571,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
/** /**
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms) * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
*
* @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry
*/ */
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 0): E { suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 1): E {
require(timeoutMillis > 100) { "timeoutMillis must > 100" } require(timeoutMillis > 100) { "timeoutMillis must > 100" }
require(retry >= 0) { "retry must >= 0" } require(retry >= 0) { "retry must >= 0" }
@ -599,7 +598,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
return withTimeoutOrNull(timeoutMillis) { return withTimeoutOrNull(timeoutMillis) {
handler.await() handler.await()
// 不要 `withTimeout`. timeout 的报错会不正常. // 不要 `withTimeout`. timeout 的报错会不正常.
} as E? ?: throw TimeoutException("timeout receiving response of $commandName") } as E? ?: throw TimeoutException("timeout receiving response of $commandName")
} }
if (retry == 0) { if (retry == 0) {

View File

@ -21,11 +21,30 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH import net.mamoe.mirai.qqandroid.utils.cryptor.ECDH
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.qqandroid.utils.cryptor.TEA
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.*
import kotlin.random.Random
import kotlin.random.nextInt
internal val DeviceInfo.guid: ByteArray get() = generateGuid(androidId, macAddress)
/**
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/
@OptIn(MiraiInternalAPI::class)
private fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
MiraiPlatformUtils.md5(androidId + macAddress)
/**
* 生成长度为 [length], 元素为随机 `0..255` [ByteArray]
*/
internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/* /*
APP ID: APP ID:
@ -150,10 +169,12 @@ internal open class QQAndroidClient(
var t150: Tlv? = null var t150: Tlv? = null
var rollbackSig: ByteArray? = null var rollbackSig: ByteArray? = null
var ipFromT149: ByteArray? = null var ipFromT149: ByteArray? = null
/** /**
* 客户端与服务器时间差 * 客户端与服务器时间差
*/ */
var timeDifference: Long = 0 var timeDifference: Long = 0
/** /**
* 真实 QQ . 使用邮箱等登录时则需获取这个 uin 进行后续一些操作. * 真实 QQ . 使用邮箱等登录时则需获取这个 uin 进行后续一些操作.
* *
@ -177,6 +198,7 @@ internal open class QQAndroidClient(
* t186 * t186
*/ */
var pwdFlag: Boolean = false var pwdFlag: Boolean = false
/** /**
* t537 * t537
*/ */
@ -301,20 +323,33 @@ internal class WLoginSigInfo(
} }
internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) KeyWithExpiry(data, creationTime, expireTime)
internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal typealias PSKeyMap = MutableMap<String, PSKey> internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token> internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
@ -323,7 +358,13 @@ internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String(
internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) = internal fun parsePSKeyMapAndPt4TokenMap(
data: ByteArray,
creationTime: Long,
expireTime: Long,
outPSKeyMap: PSKeyMap,
outPt4TokenMap: Pt4TokenMap
) =
data.read { data.read {
repeat(readShort().toInt()) { repeat(readShort().toInt()) {
val domain = readUShortLVString() val domain = readUShortLVString()
@ -337,7 +378,8 @@ internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, ex
} }
} }
internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) :
KeyWithExpiry(data, creationTime, expireTime)
internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)

View File

@ -31,10 +31,10 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.copyAndClose import net.mamoe.mirai.utils.copyAndClose
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.qqandroid.utils.io.withUse import net.mamoe.mirai.qqandroid.utils.io.withUse
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.PlatformSocket
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")

View File

@ -24,9 +24,10 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.*
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.io.chunkedFlow
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence( // RequestDataTrans internal fun createImageDataPacketSequence( // RequestDataTrans

View File

@ -27,20 +27,20 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray import net.mamoe.mirai.qqandroid.network.readUShortLVByteArray
import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.qqandroid.utils.cryptor.TEA
import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey import net.mamoe.mirai.qqandroid.utils.cryptor.adjustToPublicKey
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.qqandroid.utils.io.useBytes import net.mamoe.mirai.qqandroid.utils.io.useBytes
import net.mamoe.mirai.qqandroid.utils.io.withUse import net.mamoe.mirai.qqandroid.utils.io.withUse
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toInt
import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
internal sealed class PacketFactory<TPacket : Packet?> { internal sealed class PacketFactory<TPacket : Packet?> {
/** /**
* 筛选从服务器接收到的包时的 commandName * 筛选从服务器接收到的包时的 commandName
@ -127,9 +127,6 @@ internal typealias PacketConsumer<T> = suspend (packetFactory: PacketFactory<T>,
@PublishedApi @PublishedApi
internal val PacketLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet").withSwitch(false) internal val PacketLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet").withSwitch(false)
/**
* 已知的数据包工厂列表.
*/
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
internal object KnownPacketFactories { internal object KnownPacketFactories {
object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf( object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf(
@ -292,9 +289,6 @@ internal object KnownPacketFactories {
lateinit var consumer: PacketConsumer<T> lateinit var consumer: PacketConsumer<T>
} }
/**
* 解析 SSO 层包装
*/
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket<*> { private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket<*> {
val commandName: String val commandName: String
@ -363,14 +357,14 @@ internal object KnownPacketFactories {
) { ) {
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
check(readByte().toInt() == 2) check(readByte().toInt() == 2)
this.discardExact(2) // 27 + 2 + body.size this.discardExact(2)
this.discardExact(2) // const, =8001 this.discardExact(2)
this.readUShort() // commandId this.readUShort()
this.readShort() // const, =0x0001 this.readShort()
this.readUInt().toLong() // qq this.readUInt().toLong()
val encryptionMethod = this.readUShort().toInt() val encryptionMethod = this.readUShort().toInt()
this.discardExact(1) // const = 0 this.discardExact(1)
val packet = when (encryptionMethod) { val packet = when (encryptionMethod) {
4 -> { 4 -> {
var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt()) var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())

View File

@ -16,12 +16,12 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.protocol.LoginType import net.mamoe.mirai.qqandroid.network.protocol.LoginType
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.io.* import net.mamoe.mirai.qqandroid.utils.io.*
import net.mamoe.mirai.qqandroid.utils.toByteArray
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.mirai.utils.io.*
import kotlin.random.Random import kotlin.random.Random
/** /**

View File

@ -12,14 +12,11 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup
import net.mamoe.mirai.qqandroid.message.toRichTextElems import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
@ -31,9 +28,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils._miraiContentToString
import net.mamoe.mirai.utils._miraiContentToString
internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor( internal class MessageValidationData @OptIn(MiraiInternalAPI::class) constructor(
val data: ByteArray, val data: ByteArray,

View File

@ -27,8 +27,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.utils.daysToSeconds import net.mamoe.mirai.utils.daysToSeconds
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)

View File

@ -44,8 +44,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.qqandroid.utils.io.readString import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.qqandroid.utils.read
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.qqandroid.utils.toUHexString
internal class OnlinePush { internal class OnlinePush {
/** /**

View File

@ -21,7 +21,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.PushReq as PushReqJceStruct import net.mamoe.mirai.qqandroid.network.protocol.data.jce.PushReq as PushReqJceStruct

View File

@ -10,11 +10,12 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.* import net.mamoe.mirai.qqandroid.io.serialization.*
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.guid
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestMSFForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestMSFForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RspMSFForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RspMSFForceOffline
@ -22,11 +23,11 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x769 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x769
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.StatSvcGetOnline import net.mamoe.mirai.qqandroid.network.protocol.data.proto.StatSvcGetOnline
import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.toReadPacket
@Suppress("EnumEntryName") @Suppress("EnumEntryName")
internal enum class RegPushReason { internal enum class RegPushReason {

View File

@ -12,18 +12,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import io.ktor.util.InternalAPI import io.ktor.util.InternalAPI
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.* import net.mamoe.mirai.qqandroid.network.*
import net.mamoe.mirai.qqandroid.network.protocol.LoginType import net.mamoe.mirai.qqandroid.network.protocol.LoginType
import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.utils.GuidSource import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag import net.mamoe.mirai.qqandroid.utils.cryptor.TEA
import net.mamoe.mirai.qqandroid.utils.guidFlag import net.mamoe.mirai.qqandroid.utils.guidFlag
import net.mamoe.mirai.qqandroid.utils.io.* import net.mamoe.mirai.qqandroid.utils.io.*
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.qqandroid.utils.cryptor.TEA import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.error
internal class WtLogin { internal class WtLogin {
/** /**
@ -310,7 +310,6 @@ internal class WtLogin {
} }
@InternalAPI @InternalAPI
@OptIn(MiraiDebugAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse {
discardExact(2) // subCommand discardExact(2) // subCommand
@ -369,7 +368,6 @@ internal class WtLogin {
} }
@InternalAPI @InternalAPI
@OptIn(MiraiDebugAPI::class)
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha { private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
/* /*
java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION: java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION:
@ -405,7 +403,6 @@ internal class WtLogin {
error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString()) error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString())
} }
@OptIn(MiraiDebugAPI::class)
private fun onLoginSuccess(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success { private fun onLoginSuccess(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success {
val client = bot.client val client = bot.client
//println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() }) //println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() })

View File

@ -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" }
}
}

View File

@ -1,31 +1,9 @@
/* package net.mamoe.mirai.qqandroid.utils
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import net.mamoe.mirai.utils.MiraiInternalAPI
/** internal expect object MiraiPlatformUtils {
* 时间戳.
*/
expect val currentTimeMillis: Long
inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000
/**
* 仅供内部使用的工具类.
* 不写为扩展是为了避免污染命名空间.
*/
@MiraiInternalAPI
expect object MiraiPlatformUtils {
fun unzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray fun unzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray
@ -48,7 +26,6 @@ expect object MiraiPlatformUtils {
val Http: HttpClient val Http: HttpClient
} }
@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray` @Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray`
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) { internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
require(offset >= 0) { "offset shouldn't be negative: $offset" } require(offset >= 0) { "offset shouldn't be negative: $offset" }

View File

@ -7,17 +7,15 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
* 多平台适配的 DatagramChannel. * 多平台适配的 DatagramChannel.
*/ */
@MiraiInternalAPI internal expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
/** /**
* @throws SendPacketInternalException * @throws SendPacketInternalException
*/ */
@ -34,9 +32,9 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl
/** /**
* [PlatformDatagramChannel.send] [PlatformDatagramChannel.read] 时出现的错误. * [PlatformDatagramChannel.send] [PlatformDatagramChannel.read] 时出现的错误.
*/ */
class SendPacketInternalException(cause: Throwable?) : Exception(cause) internal class SendPacketInternalException(cause: Throwable?) : Exception(cause)
/** /**
* [PlatformDatagramChannel.send] [PlatformDatagramChannel.read] 时出现的错误. * [PlatformDatagramChannel.send] [PlatformDatagramChannel.read] 时出现的错误.
*/ */
class ReadPacketInternalException(cause: Throwable?) : Exception(cause) internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause)

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
@ -16,8 +16,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
* 多平台适配的 TCP Socket. * 多平台适配的 TCP Socket.
*/ */
@MiraiInternalAPI internal expect class PlatformSocket() : Closeable {
expect class PlatformSocket() : Closeable {
suspend fun connect(serverHost: String, serverPort: Int) suspend fun connect(serverHost: String, serverPort: Int)
/** /**

View File

@ -11,14 +11,13 @@
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("Utils") @file:JvmName("Utils")
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.io.charsets.Charset import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String import kotlinx.io.core.String
import kotlinx.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.utils.checkOffsetAndLength
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
@ -31,7 +30,7 @@ import kotlin.jvm.JvmSynthetic
@JvmOverloads @JvmOverloads
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { internal fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
require(offset >= 0) { "offset shouldn't be negative: $offset" } require(offset >= 0) { "offset shouldn't be negative: $offset" }
require(length >= 0) { "length shouldn't be negative: $length" } require(length >= 0) { "length shouldn't be negative: $length" }
require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" } require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }
@ -55,7 +54,7 @@ fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: In
@JvmOverloads @JvmOverloads
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { internal fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
this.checkOffsetAndLength(offset, length) this.checkOffsetAndLength(offset, length)
if (length == 0) { if (length == 0) {
return "" return ""
@ -76,7 +75,7 @@ fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int
@JvmSynthetic @JvmSynthetic
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { internal fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String {
if (length == 0) { if (length == 0) {
return "" return ""
} }
@ -94,13 +93,14 @@ fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: In
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset) internal inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset)
inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) = @PublishedApi
internal inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) =
ByteReadPacket(this, offset = offset, length = length) ByteReadPacket(this, offset = offset, length = length)
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R { internal inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R {
contract { contract {
callsInPlace(t, InvocationKind.EXACTLY_ONCE) callsInPlace(t, InvocationKind.EXACTLY_ONCE)
} }

View File

@ -9,9 +9,8 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH")
package net.mamoe.mirai.utils package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
@ -22,8 +21,7 @@ private val indent: String = " ".repeat(4)
/** /**
* 将所有元素加入转换为多行的字符串表示. * 将所有元素加入转换为多行的字符串表示.
*/ */
@MiraiDebugAPI private fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
internal fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform) return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform)
} }
@ -38,9 +36,7 @@ internal fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T)
* 其他类型: 反射获取它和它的所有来自 Mirai super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 * 其他类型: 反射获取它和它的所有来自 Mirai super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
*/ */
@Suppress("FunctionName") // 这样就不容易被 IDE 提示 @Suppress("FunctionName") // 这样就不容易被 IDE 提示
@MiraiDebugAPI("Extremely slow") internal fun Any?._miraiContentToString(prefix: String = ""): String = when (this) {
//@Suppress("Unsupported") // false positive
fun Any?._miraiContentToString(prefix: String = ""): String = when (this) {
is Unit -> "Unit" is Unit -> "Unit"
is UInt -> "0x" + this.toUHexString("") + "($this)" is UInt -> "0x" + this.toUHexString("") + "($this)"
is UByte -> "0x" + this.toUHexString() + "($this)" is UByte -> "0x" + this.toUHexString() + "($this)"
@ -135,7 +131,6 @@ private val KProperty1<*, *>.isConst: Boolean get() = false // on JVM, it will b
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
private val KClass<*>.isData: Boolean get() = false // on JVM, it will be resolved to member function private val KClass<*>.isData: Boolean get() = false // on JVM, it will be resolved to member function
@MiraiDebugAPI
private fun Any.contentToStringReflectively( private fun Any.contentToStringReflectively(
prefix: String, prefix: String,
filter: ((name: String, value: Any?) -> Boolean)? = null filter: ((name: String, value: Any?) -> Boolean)? = null

View File

@ -9,7 +9,7 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
@ -22,7 +22,7 @@ import kotlin.random.nextInt
/** /**
* 255 -> 00 FF * 255 -> 00 FF
*/ */
fun Short.toByteArray(): ByteArray = with(toInt()) { internal fun Short.toByteArray(): ByteArray = with(toInt()) {
byteArrayOf( byteArrayOf(
(shr(8) and 0xFF).toByte(), (shr(8) and 0xFF).toByte(),
(shr(0) and 0xFF).toByte() (shr(0) and 0xFF).toByte()
@ -32,7 +32,7 @@ fun Short.toByteArray(): ByteArray = with(toInt()) {
/** /**
* 255 -> 00 00 00 FF * 255 -> 00 00 00 FF
*/ */
fun Int.toByteArray(): ByteArray = byteArrayOf( internal fun Int.toByteArray(): ByteArray = byteArrayOf(
ushr(24).toByte(), ushr(24).toByte(),
ushr(16).toByte(), ushr(16).toByte(),
ushr(8).toByte(), ushr(8).toByte(),
@ -42,7 +42,7 @@ fun Int.toByteArray(): ByteArray = byteArrayOf(
/** /**
* 255 -> 00 00 00 FF * 255 -> 00 00 00 FF
*/ */
fun Long.toByteArray(): ByteArray = byteArrayOf( internal fun Long.toByteArray(): ByteArray = byteArrayOf(
(ushr(56) and 0xFF).toByte(), (ushr(56) and 0xFF).toByte(),
(ushr(48) and 0xFF).toByte(), (ushr(48) and 0xFF).toByte(),
(ushr(40) and 0xFF).toByte(), (ushr(40) and 0xFF).toByte(),
@ -53,40 +53,40 @@ fun Long.toByteArray(): ByteArray = byteArrayOf(
(ushr(0) and 0xFF).toByte() (ushr(0) and 0xFF).toByte()
) )
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) internal fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
/** /**
* 255 -> 00 FF * 255 -> 00 FF
*/ */
fun UShort.toByteArray(): ByteArray = with(toUInt()) { internal fun UShort.toByteArray(): ByteArray = with(toUInt()) {
byteArrayOf( byteArrayOf(
(shr(8) and 255u).toByte(), (shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte() (shr(0) and 255u).toByte()
) )
} }
fun Short.toUHexString(separator: String = " "): String = this.toUShort().toUHexString(separator) internal fun Short.toUHexString(separator: String = " "): String = this.toUShort().toUHexString(separator)
fun UShort.toUHexString(separator: String = " "): String = internal fun UShort.toUHexString(separator: String = " "): String =
this.toInt().shr(8).toUShort().toUByte().toUHexString() + separator + this.toUByte().toUHexString() this.toInt().shr(8).toUShort().toUByte().toUHexString() + separator + this.toUByte().toUHexString()
fun ULong.toUHexString(separator: String = " "): String = internal fun ULong.toUHexString(separator: String = " "): String =
this.toLong().toUHexString(separator) this.toLong().toUHexString(separator)
fun Long.toUHexString(separator: String = " "): String = internal fun Long.toUHexString(separator: String = " "): String =
this.ushr(32).toUInt().toUHexString(separator) + separator + this.toUInt().toUHexString(separator) this.ushr(32).toUInt().toUHexString(separator) + separator + this.toUInt().toUHexString(separator)
/** /**
* 255 -> 00 FF * 255 -> 00 FF
*/ */
fun UByte.toByteArray(): ByteArray = byteArrayOf((this and 255u).toByte()) internal fun UByte.toByteArray(): ByteArray = byteArrayOf((this and 255u).toByte())
fun UByte.toUHexString(): String = this.toByte().toUHexString() internal fun UByte.toUHexString(): String = this.toByte().toUHexString()
/** /**
* 255u -> 00 00 00 FF * 255u -> 00 00 00 FF
*/ */
fun UInt.toByteArray(): ByteArray = byteArrayOf( internal fun UInt.toByteArray(): ByteArray = byteArrayOf(
(shr(24) and 255u).toByte(), (shr(24) and 255u).toByte(),
(shr(16) and 255u).toByte(), (shr(16) and 255u).toByte(),
(shr(8) and 255u).toByte(), (shr(8) and 255u).toByte(),
@ -96,23 +96,23 @@ fun UInt.toByteArray(): ByteArray = byteArrayOf(
/** /**
* [ByteArray] 后再转 hex * [ByteArray] 后再转 hex
*/ */
fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) internal fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
/** /**
* 转无符号十六进制表示, 并补充首位 `0`. * 转无符号十六进制表示, 并补充首位 `0`.
* 转换结果示例: `FF`, `0E` * 转换结果示例: `FF`, `0E`
*/ */
fun Byte.toUHexString(): String = this.toUByte().fixToUHex() internal fun Byte.toUHexString(): String = this.toUByte().fixToUHex()
/** /**
* 转无符号十六进制表示, 并补充首位 `0`. * 转无符号十六进制表示, 并补充首位 `0`.
*/ */
fun Byte.fixToUHex(): String = this.toUByte().fixToUHex() internal fun Byte.fixToUHex(): String = this.toUByte().fixToUHex()
/** /**
* 转无符号十六进制表示, 并补充首位 `0`. * 转无符号十六进制表示, 并补充首位 `0`.
*/ */
fun UByte.fixToUHex(): String = internal fun UByte.fixToUHex(): String =
if (this.toInt() in 0..15) "0${this.toString(16).toUpperCase()}" else this.toString(16).toUpperCase() if (this.toInt() in 0..15) "0${this.toString(16).toUpperCase()}" else this.toString(16).toUpperCase()
/** /**
@ -120,7 +120,7 @@ fun UByte.fixToUHex(): String =
* *
* 这个方法很累, 不建议经常使用. * 这个方法很累, 不建议经常使用.
*/ */
fun String.hexToBytes(): ByteArray = internal fun String.hexToBytes(): ByteArray =
this.split(" ") this.split(" ")
.asSequence() .asSequence()
.filterNot { it.isEmpty() } .filterNot { it.isEmpty() }
@ -133,7 +133,7 @@ fun String.hexToBytes(): ByteArray =
* *
* 这个方法很累, 不建议经常使用. * 这个方法很累, 不建议经常使用.
*/ */
fun String.chunkedHexToBytes(): ByteArray = internal fun String.chunkedHexToBytes(): ByteArray =
this.asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray() this.asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray()
/** /**
@ -141,7 +141,7 @@ fun String.chunkedHexToBytes(): ByteArray =
* *
* 这个方法很累, 不建议经常使用. * 这个方法很累, 不建议经常使用.
*/ */
fun String.autoHexToBytes(): ByteArray = internal fun String.autoHexToBytes(): ByteArray =
this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map {
(it[0].toString() + it[1]).toUByte(16).toByte() (it[0].toString() + it[1]).toUByte(16).toByte()
}.toList().toByteArray() }.toList().toByteArray()
@ -151,7 +151,7 @@ fun String.autoHexToBytes(): ByteArray =
* *
* 这个方法很累, 不建议经常使用. * 这个方法很累, 不建议经常使用.
*/ */
fun String.hexToUBytes(): UByteArray = internal fun String.hexToUBytes(): UByteArray =
this.split(" ") this.split(" ")
.asSequence() .asSequence()
.filterNot { it.isEmpty() } .filterNot { it.isEmpty() }
@ -159,27 +159,6 @@ fun String.hexToUBytes(): UByteArray =
.toList() .toList()
.toUByteArray() .toUByteArray()
/**
* 生成长度为 [length], 元素为随机 `0..255` [ByteArray]
*/
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/**
* 随机生成长度为 [length] [String].
*/
fun getRandomString(length: Int): String = getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9')
/**
* 根据所给 [charRange] 随机生成长度为 [length] [String].
*/
fun getRandomString(length: Int, charRange: CharRange): String = String(CharArray(length) { charRange.random() })
/**
* 根据所给 [charRanges] 随机生成长度为 [length] [String].
*/
fun getRandomString(length: Int, vararg charRanges: CharRange): String =
String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() })
/** /**
* [this] 4 [Byte] bits 合并为一个 [Int] * [this] 4 [Byte] bits 合并为一个 [Int]
* *
@ -188,15 +167,15 @@ fun getRandomString(length: Int, vararg charRanges: CharRange): String =
* 一个 [Int] 32 bits * 一个 [Int] 32 bits
* 本函数将 4 [Byte] bits 连接得到 [Int] * 本函数将 4 [Byte] bits 连接得到 [Int]
*/ */
fun ByteArray.toUInt(): UInt = internal fun ByteArray.toUInt(): UInt =
(this[0].toUInt().and(255u) shl 24) + (this[1].toUInt().and(255u) shl 16) + (this[2].toUInt().and(255u) shl 8) + (this[3].toUInt().and( (this[0].toUInt().and(255u) shl 24) + (this[1].toUInt().and(255u) shl 16) + (this[2].toUInt().and(255u) shl 8) + (this[3].toUInt().and(
255u 255u
) shl 0) ) shl 0)
fun ByteArray.toUShort(): UShort = internal fun ByteArray.toUShort(): UShort =
((this[0].toUInt().and(255u) shl 8) + (this[1].toUInt().and(255u) shl 0)).toUShort() ((this[0].toUInt().and(255u) shl 8) + (this[1].toUInt().and(255u) shl 0)).toUShort()
fun ByteArray.toInt(): Int = internal fun ByteArray.toInt(): Int =
(this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and( (this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and(
255 255
) shl 0) ) shl 0)

View File

@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.utils.cryptor package net.mamoe.mirai.qqandroid.utils.cryptor
import net.mamoe.mirai.utils.io.chunkedHexToBytes import net.mamoe.mirai.qqandroid.utils.chunkedHexToBytes
expect interface ECDHPrivateKey { expect interface ECDHPrivateKey {
fun getEncoded(): ByteArray fun getEncoded(): ByteArray

View File

@ -11,10 +11,10 @@ package net.mamoe.mirai.qqandroid.utils.cryptor
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.toByteArray
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.experimental.xor import kotlin.experimental.xor
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic

View File

@ -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()
}

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils.io
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -20,12 +20,13 @@ import kotlinx.io.core.Input
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
/** /**
* [chunkedFlow] 分割得到的区块 * [chunkedFlow] 分割得到的区块
*/ */
class ChunkedInput( internal class ChunkedInput(
/** /**
* 区块的数据. * 区块的数据.
* [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问. * [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问.
@ -51,11 +52,16 @@ class ChunkedInput(
* [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] [Sequence] * [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] [Sequence]
*/ */
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> { internal fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
ByteArrayPool.checkBufferSize(sizePerPacket) ByteArrayPool.checkBufferSize(sizePerPacket)
if (this.remaining <= sizePerPacket.toLong()) { if (this.remaining <= sizePerPacket.toLong()) {
ByteArrayPool.useInstance { buffer -> ByteArrayPool.useInstance { buffer ->
return flowOf(ChunkedInput(buffer, this.readAvailable(buffer, 0, sizePerPacket))) return flowOf(
ChunkedInput(
buffer,
this.readAvailable(buffer, 0, sizePerPacket)
)
)
} }
} }
return flow { return flow {
@ -76,7 +82,7 @@ fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
* 其长度分别为: 300, 300, 300, 100. * 其长度分别为: 300, 300, 300, 100.
*/ */
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> { internal fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> {
ByteArrayPool.checkBufferSize(sizePerPacket) ByteArrayPool.checkBufferSize(sizePerPacket)
if (this.isClosedForRead) { if (this.isClosedForRead) {
return flowOf() return flowOf()

View File

@ -18,7 +18,6 @@ import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets import kotlinx.io.charsets.Charsets
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
@ -27,21 +26,12 @@ import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.qqandroid.utils.toUHexString
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
fun ByteReadPacket.copyTo(outputStream: OutputStream) {
ByteArrayPool.useInstance {
while (this.isNotEmpty) {
outputStream.write(it, 0, this.readAvailable(it))
}
}
}
@MiraiInternalAPI @MiraiInternalAPI
inline fun <R> ByteReadPacket.useBytes( internal inline fun <R> ByteReadPacket.useBytes(
n: Int = remaining.toInt(),//not that safe but adequate n: Int = remaining.toInt(),//not that safe but adequate
block: (data: ByteArray, length: Int) -> R block: (data: ByteArray, length: Int) -> R
): R = ByteArrayPool.useInstance { ): R = ByteArrayPool.useInstance {
@ -50,12 +40,12 @@ inline fun <R> ByteReadPacket.useBytes(
} }
@MiraiInternalAPI @MiraiInternalAPI
inline fun ByteReadPacket.readPacketExact( internal inline fun ByteReadPacket.readPacketExact(
n: Int = remaining.toInt()//not that safe but adequate n: Int = remaining.toInt()//not that safe but adequate
): ByteReadPacket = this.readBytes(n).toReadPacket() ): ByteReadPacket = this.readBytes(n).toReadPacket()
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R { internal inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
@ -64,24 +54,23 @@ inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
private inline fun <R> inline(block: () -> R): R = block() private inline fun <R> inline(block: () -> R): R = block()
typealias TlvMap = MutableMap<Int, ByteArray> internal typealias TlvMap = MutableMap<Int, ByteArray>
inline fun TlvMap.getOrFail(tag: Int): ByteArray { internal inline fun TlvMap.getOrFail(tag: Int): ByteArray {
return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)") return this[tag] ?: error("cannot find tlv 0x${tag. toUHexString("")}($tag)")
} }
inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray { internal inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray {
return this[tag] ?: error(lazyMessage(tag)) return this[tag] ?: error(lazyMessage(tag))
} }
@Suppress("FunctionName") @Suppress("FunctionName")
@MiraiInternalAPI @MiraiInternalAPI
inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = internal inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap =
_readTLVMap(true, tagSize, suppressDuplication) _readTLVMap(true, tagSize, suppressDuplication)
@MiraiDebugAPI
@Suppress("DuplicatedCode", "FunctionName") @Suppress("DuplicatedCode", "FunctionName")
fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap { internal fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap {
val map = mutableMapOf<Int, ByteArray>() val map = mutableMapOf<Int, ByteArray>()
var key = 0 var key = 0
@ -138,18 +127,18 @@ fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplic
return map return map
} }
inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = internal inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length), charset = charset) String(this.readBytes(length), charset = charset)
inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = internal inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length.toInt()), charset = charset) String(this.readBytes(length.toInt()), charset = charset)
inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = internal inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length.toInt()), charset = charset) String(this.readBytes(length.toInt()), charset = charset)
@JvmSynthetic @JvmSynthetic
inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = internal inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length.toInt()), charset = charset) String(this.readBytes(length.toInt()), charset = charset)
inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = internal inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String =
String(this.readBytes(length.toInt()), charset = charset) String(this.readBytes(length.toInt()), charset = charset)

View File

@ -6,15 +6,23 @@
* *
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("DuplicatedCode")
package net.mamoe.mirai.utils @file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@PublishedApi @PublishedApi
internal expect fun Throwable.addSuppressedMirai(e: Throwable) internal expect fun Throwable.addSuppressedMirai(e: Throwable)
@MiraiInternalAPI @MiraiInternalAPI
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R { internal inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R {
var lastException: Throwable? = null var lastException: Throwable? = null
repeat(repeat) { repeat(repeat) {
@ -30,24 +38,6 @@ inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R {
throw lastException!! throw lastException!!
} }
@MiraiInternalAPI
@Suppress("DuplicatedCode")
inline fun <R> tryNTimesOrNull(repeat: Int, block: (Int) -> R): R? {
var lastException: Throwable? = null
repeat(repeat) {
try {
return block(it)
} catch (e: Throwable) {
if (lastException == null) {
lastException = e
} else lastException!!.addSuppressedMirai(e)
}
}
return null
}
@MiraiInternalAPI @MiraiInternalAPI
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
inline fun <R> tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? { inline fun <R> tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? {

View File

@ -7,10 +7,10 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils package net.mamoe.mirai.qqandroid.utils
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -7,16 +7,12 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toByteArray
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
class TypeConversionTest { class TypeConversionTest {
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes

View File

@ -16,12 +16,12 @@ import kotlinx.io.core.Input
import kotlinx.io.core.readAvailable import kotlinx.io.core.readAvailable
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.withSwitch import net.mamoe.mirai.utils.withSwitch
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
@ -30,13 +30,17 @@ import kotlin.contracts.contract
val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true) val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true)
inline fun ByteArray.debugPrintThis(name: String): ByteArray { internal inline fun ByteArray.debugPrintThis(name: String): ByteArray {
DebugLogger.debug(name + "=" + this.toUHexString()) DebugLogger.debug(name + "=" + this.toUHexString())
return this return this
} }
@OptIn(ExperimentalContracts::class, MiraiInternalAPI::class) @OptIn(ExperimentalContracts::class, MiraiInternalAPI::class)
inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R { internal inline fun <R> Input.debugIfFail(
name: String = "",
onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() },
block: ByteReadPacket.() -> R
): R {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)

View File

@ -22,7 +22,7 @@ import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.ContextImpl import net.mamoe.mirai.utils.ContextImpl
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.qqandroid.utils.toReadPacket
import java.nio.ByteBuffer import java.nio.ByteBuffer
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)

View File

@ -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()
}
}

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -25,8 +25,7 @@ import java.net.Socket
/** /**
* 多平台适配的 TCP Socket. * 多平台适配的 TCP Socket.
*/ */
@MiraiInternalAPI internal actual class PlatformSocket : Closeable {
actual class PlatformSocket : Closeable {
private lateinit var socket: Socket private lateinit var socket: Socket
actual val isOpen: Boolean actual val isOpen: Boolean

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.utils.io package net.mamoe.mirai.qqandroid.utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -24,7 +24,7 @@ import java.nio.channels.WritableByteChannel
/** /**
* 多平台适配的 DatagramChannel. * 多平台适配的 DatagramChannel.
*/ */
actual class PlatformDatagramChannel actual constructor( internal actual class PlatformDatagramChannel actual constructor(
serverHost: String, serverHost: String,
serverPort: Short serverPort: Short
) : Closeable { ) : Closeable {

View File

@ -0,0 +1,5 @@
package net.mamoe.mirai.qqandroid.utils
@PublishedApi
internal actual fun Throwable.addSuppressedMirai(e: Throwable) {
}

View File

@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.utils.cryptor package net.mamoe.mirai.qqandroid.utils.cryptor
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.* import java.security.*
import java.security.spec.ECGenParameterSpec import java.security.spec.ECGenParameterSpec

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.qqandroid.utils
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaField

View File

@ -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")
}
}
}
}

View File

@ -10,7 +10,6 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -21,6 +20,7 @@ import kotlinx.io.core.copyTo
import kotlinx.io.errors.IOException import kotlinx.io.errors.IOException
import kotlinx.io.streams.asInput import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput import kotlinx.io.streams.asOutput
import net.mamoe.mirai.utils.internal.md5
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.net.URL import java.net.URL
@ -29,7 +29,6 @@ import java.net.URL
* 将各类型图片容器转为 [ExternalImage] * 将各类型图片容器转为 [ExternalImage]
*/ */
/** /**
* 读取 [Bitmap] 的属性, 然后构造 [ExternalImage] * 读取 [Bitmap] 的属性, 然后构造 [ExternalImage]
*/ */
@ -53,7 +52,7 @@ fun File.toExternalImage(): ExternalImage {
return ExternalImage( return ExternalImage(
width = input.width, width = input.width,
height = input.height, height = input.height,
md5 = this.inputStream().use { MiraiPlatformUtils.md5(it) }, md5 = this.inputStream().use { it.md5() },
imageFormat = this.nameWithoutExtension, imageFormat = this.nameWithoutExtension,
input = this.inputStream().asInput(IoBuffer.Pool), input = this.inputStream().asInput(IoBuffer.Pool),
inputSize = this.length(), inputSize = this.length(),

View File

@ -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)
}
}

View File

@ -20,9 +20,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import net.mamoe.mirai.utils.internal.md5
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
import java.io.File import java.io.File
/** /**
@ -41,7 +39,10 @@ fun File.loadAsDeviceInfo(context: Context): DeviceInfo {
} }
@OptIn(UnstableDefault::class) @OptIn(UnstableDefault::class)
private val JSON = Json(JsonConfiguration.Default) private val JSON = Json {
isLenient = true
ignoreUnknownKeys = true
}
/** /**
* 部分引用指向 [Build]. * 部分引用指向 [Build].
@ -109,9 +110,9 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
override val imsiMd5: ByteArray override val imsiMd5: ByteArray
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
get() = md5(kotlin.runCatching { get() = kotlin.runCatching {
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).subscriberId.toByteArray() (context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).subscriberId.toByteArray()
}.getOrEmpty()) }.getOrEmpty().md5()
override val imei: String override val imei: String
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
@ -124,11 +125,6 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
} }
}.getOrElse { "" } }.getOrElse { "" }
@OptIn(MiraiInternalAPI::class)
override val ipAddress: ByteArray
get() = kotlin.runCatching {
localIpAddress().split(".").map { it.toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: ByteArray(4)
}.getOrElse { ByteArray(4) }
override val androidId: ByteArray get() = Build.ID.toByteArray() override val androidId: ByteArray get() = Build.ID.toByteArray()
override val apn: ByteArray get() = "wifi".toByteArray() override val apn: ByteArray get() = "wifi".toByteArray()

View File

@ -1,4 +1,6 @@
package net.mamoe.mirai.utils @file:Suppress("DuplicatedCode")
package net.mamoe.mirai.utils.internal
import android.os.Build import android.os.Build

View File

@ -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

View File

@ -8,112 +8,12 @@
*/ */
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.Inet4Address
import java.security.MessageDigest
import java.util.zip.Deflater
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.zip.Inflater
/** /**
* 时间戳 * 时间戳
*/ */
actual val currentTimeMillis: Long get() = System.currentTimeMillis() actual val currentTimeMillis: Long get() = System.currentTimeMillis()
@MiraiInternalAPI
actual object MiraiPlatformUtils {
actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(data, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val deflater = Deflater()
deflater.setInput(data, offset, length)
deflater.finish()
ByteArrayPool.useInstance {
return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() }
}
}
actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest()
}
actual inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@OptIn(KtorExperimentalAPI::class)
actual val Http: HttpClient = HttpClient(CIO)
/**
* Localhost 解析
*/
actual fun localIpAddress(): String = runCatching {
Inet4Address.getLocalHost().hostAddress
}.getOrElse { "192.168.1.123" }
fun md5(stream: InputStream): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
stream.readInSequence {
digest.update(it.toByte())
}
return digest.digest()
}
private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
var read: Int
while (this.read().also { read = it } != -1) {
block(read)
}
}
actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray {
ByteArrayOutputStream().use { buf ->
GZIPOutputStream(buf).use { gzip ->
data.inputStream(offset, length).use { t -> t.copyTo(gzip) }
}
buf.flush()
return buf.toByteArray()
}
}
actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray {
return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() }
}
}

View File

@ -14,7 +14,7 @@ package net.mamoe.mirai
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils import net.mamoe.mirai.utils.internal.md5
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
@MiraiInternalAPI @MiraiInternalAPI
@ -28,7 +28,7 @@ data class BotAccount(
@MiraiInternalAPI @MiraiInternalAPI
val passwordMd5: ByteArray // md5 val passwordMd5: ByteArray // md5
) { ) {
constructor(id: Long, passwordPlainText: String) : this(id, MiraiPlatformUtils.md5(passwordPlainText.toByteArray())) constructor(id: Long, passwordPlainText: String) : this(id, passwordPlainText.toByteArray().md5())
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -22,6 +22,7 @@ import net.mamoe.mirai.network.ForceOfflineException
import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.closeAndJoin import net.mamoe.mirai.network.closeAndJoin
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.tryNTimesOrException
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/* /*

View File

@ -18,8 +18,10 @@ import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.event.CancellableEvent
import net.mamoe.mirai.event.events.ImageUploadEvent.Failed import net.mamoe.mirai.event.events.ImageUploadEvent.Failed
import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed
import net.mamoe.mirai.message.data.ExperimentalMessageSource
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
@ -96,6 +98,7 @@ sealed class MessageSendEvent : BotEvent, BotActiveEvent, AbstractCancellableEve
/** /**
* 消息撤回事件. 可是任意消息被任意人撤回. * 消息撤回事件. 可是任意消息被任意人撤回.
*/ */
@OptIn(ExperimentalMessageSource::class)
sealed class MessageRecallEvent : BotEvent { sealed class MessageRecallEvent : BotEvent {
/** /**
* 消息原发送人 * 消息原发送人
@ -106,6 +109,7 @@ sealed class MessageRecallEvent : BotEvent {
* 消息 id. * 消息 id.
* @see MessageSource.id * @see MessageSource.id
*/ */
@ExperimentalMessageSource
abstract val messageId: Long abstract val messageId: Long
/** /**
@ -118,6 +122,7 @@ sealed class MessageRecallEvent : BotEvent {
*/ */
data class FriendRecall( data class FriendRecall(
override val bot: Bot, override val bot: Bot,
@ExperimentalMessageSource
override val messageId: Long, override val messageId: Long,
override val messageTime: Int, override val messageTime: Int,
/** /**
@ -132,6 +137,7 @@ sealed class MessageRecallEvent : BotEvent {
data class GroupRecall( data class GroupRecall(
override val bot: Bot, override val bot: Bot,
override val authorId: Long, override val authorId: Long,
@ExperimentalMessageSource
override val messageId: Long, override val messageId: Long,
override val messageTime: Int, override val messageTime: Int,
/** /**

View File

@ -16,14 +16,15 @@ import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.EventDisabled import net.mamoe.mirai.event.EventDisabled
import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.isRemoved
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
import kotlin.reflect.KClass import kotlin.reflect.KClass
val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false)
@MiraiInternalAPI @MiraiInternalAPI
fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L { fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
with(this.listeners()) { with(this.listeners()) {
@ -67,7 +68,6 @@ internal class Handler<in E : Event>
} }
@Suppress("unused") @Suppress("unused")
@OptIn(MiraiDebugAPI::class)
override suspend fun onEvent(event: E): ListeningStatus { override suspend fun onEvent(event: E): ListeningStatus {
if (isCompleted || isCancelled) return ListeningStatus.STOPPED if (isCompleted || isCancelled) return ListeningStatus.STOPPED
if (!isActive) return ListeningStatus.LISTENING if (!isActive) return ListeningStatus.LISTENING
@ -144,8 +144,6 @@ internal object EventListenerManager {
internal suspend inline fun Event.broadcastInternal() = coroutineScope { internal suspend inline fun Event.broadcastInternal() = coroutineScope {
if (EventDisabled) return@coroutineScope if (EventDisabled) return@coroutineScope
EventLogger.info { "Event broadcast: $this" }
val listeners = this@broadcastInternal::class.listeners() val listeners = this@broadcastInternal::class.listeners()
callAndRemoveIfRequired(this@broadcastInternal, listeners) callAndRemoveIfRequired(this@broadcastInternal, listeners)
listeners.supertypes.forEach { listeners.supertypes.forEach {

View File

@ -84,6 +84,11 @@ inline fun <reified E : Event, R : Any> CoroutineScope.subscribingGetAsync(
} }
//////////////
//// internal
//////////////
@PublishedApi @PublishedApi
internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl( internal suspend inline fun <reified E : Event, R> subscribingGetOrNullImpl(
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,

View File

@ -18,6 +18,9 @@ import kotlin.jvm.JvmName
/** /**
* 链接的两个消息. * 链接的两个消息.
* *
* 不要直接构造 [CombinedMessage], 使用 [Message.plus]
* 要连接多个 [Message], 使用 [buildMessageChain]
*
* @see Message.plus * @see Message.plus
* *
* Left-biased list * Left-biased list

View File

@ -18,7 +18,6 @@ import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/** /**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务. * Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.

View File

@ -1,3 +1,10 @@
package net.mamoe.mirai.network package net.mamoe.mirai.network
class ForceOfflineException(override val message: String?) : RuntimeException() import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
/**
* [Bot] 被迫下线时抛出, 作为 [Job.cancel] `cause`
*/
class ForceOfflineException(override val message: String?) : CancellationException(message)

View File

@ -7,10 +7,14 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused")
package net.mamoe.mirai.network package net.mamoe.mirai.network
import net.mamoe.mirai.Bot
/** /**
* 正常登录失败时抛出 * [登录][Bot.login] 失败时抛出, 可正常地中断登录过程.
*/ */
sealed class LoginFailedException : RuntimeException { sealed class LoginFailedException : RuntimeException {
constructor() : super() constructor() : super()
@ -19,4 +23,17 @@ sealed class LoginFailedException : RuntimeException {
constructor(cause: Throwable?) : super(cause) constructor(cause: Throwable?) : super(cause)
} }
/**
* 密码输入错误
*/
class WrongPasswordException(message: String?) : LoginFailedException(message) class WrongPasswordException(message: String?) : LoginFailedException(message)
/**
* mirai 实现的异常
*/
abstract class CustomLoginFailedException : LoginFailedException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -6,129 +6,122 @@
* *
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused", "DEPRECATION_ERROR")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.LoginFailedException
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
/**
* 验证码, 设备锁解决器
*/
expect abstract class LoginSolver {
/**
* 处理图片验证码.
* 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录.
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
*
* @throws LoginFailedException
*/
abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
/**
* 处理滑动验证码.
* 返回 null 以表示无法处理验证码, 将会刷新验证码或重试登录.
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止
*
* @throws LoginFailedException
* @return 验证码解决成功后获得的 ticket.
*/
abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
/**
* 处理不安全设备验证.
* 在处理完成后返回任意内容 (包含 `null`) 均视为处理成功.
* 抛出一个 [LoginFailedException] 以正常地终止登录, 抛出任意其他 [Exception] 将视为异常终止.
*
* @return 任意内容. 返回值保留以供未来更新.
* @throws LoginFailedException
*/
abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
companion object {
val Default: LoginSolver
}
}
/** /**
* [Bot] 配置 * [Bot] 配置
*/ */
@Suppress("PropertyName") @Suppress("PropertyName")
expect open class BotConfiguration() { open class BotConfiguration {
/** /**
* 日志记录器 * 日志记录器
*/ */
var botLoggerSupplier: ((Bot) -> MiraiLogger) var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") }
/** /**
* 网络层日志构造器 * 网络层日志构造器
*/ */
var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") }
/** /**
* 设备信息覆盖. 默认使用随机的设备信息. * 设备信息覆盖. 默认使用随机的设备信息.
*/ */
var deviceInfo: ((Context) -> DeviceInfo)? var deviceInfo: ((Context) -> DeviceInfo)? = null
/** /**
* [CoroutineContext] * [CoroutineContext]
*/ */
var parentCoroutineContext: CoroutineContext var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
/** /**
* 心跳周期. 过长会导致被服务器断开连接. * 心跳周期. 过长会导致被服务器断开连接.
*/ */
var heartbeatPeriodMillis: Long var heartbeatPeriodMillis: Long = 60.secondsToMillis
/** /**
* 每次心跳时等待结果的时间. * 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/ */
var heartbeatTimeoutMillis: Long var heartbeatTimeoutMillis: Long = 2.secondsToMillis
/** /**
* 心跳失败后的第一次重连前的等待时间. * 心跳失败后的第一次重连前的等待时间.
*/ */
var firstReconnectDelayMillis: Long var firstReconnectDelayMillis: Long = 5.secondsToMillis
/** /**
* 重连失败后, 继续尝试的每次等待时间 * 重连失败后, 继续尝试的每次等待时间
*/ */
var reconnectPeriodMillis: Long var reconnectPeriodMillis: Long = 5.secondsToMillis
/** /**
* 最多尝试多少次重连 * 最多尝试多少次重连
*/ */
var reconnectionRetryTimes: Int var reconnectionRetryTimes: Int = Int.MAX_VALUE
/** /**
* 验证码处理器 * 验证码处理器
*/ */
var loginSolver: LoginSolver var loginSolver: LoginSolver = LoginSolver.Default
companion object { companion object {
/** /**
* 默认的配置实例 * 默认的配置实例
*/ */
@JvmStatic @JvmStatic
val Default: BotConfiguration val Default = BotConfiguration()
} }
operator fun _NoNetworkLog.unaryPlus() /**
* 不显示网络日志
*/
fun noNetworkLog() {
networkLoggerSupplier = { _: BotNetworkHandler -> SilentLogger }
}
/** /**
* 不记录网络层的 log. * 使用文件存储设备信息
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录. *
* 此函数只在 JVM 有效. 在其他平台将会导致一直使用默认的随机的设备信息.
*/ */
@BotConfigurationDsl @JvmOverloads
val NoNetworkLog: _NoNetworkLog fun fileBasedDeviceInfo(filename: String = "device.json") {
deviceInfo = getFileBasedDeviceInfoSupplier(filename)
}
@Suppress("ClassName")
object _NoNetworkLog @PlannedRemoval("0.34.0")
@Deprecated(
"use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("fileBasedDeviceInfo")
)
operator fun FileBasedDeviceInfo.unaryPlus() {
fileBasedDeviceInfo(this.filepath)
}
} }
@DslMarker /**
annotation class BotConfigurationDsl * 使用文件系统存储设备信息.
*/
@PlannedRemoval("0.34.0")
@Deprecated(
"use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR
)
inline class FileBasedDeviceInfo(val filepath: String) {
/**
* 使用 "device.json" 存储设备信息
*/
companion object ByDeviceDotJson
}
@OptIn(ExperimentalMultiplatform::class)
internal expect fun getFileBasedDeviceInfoSupplier(filename: String): ((Context) -> DeviceInfo)?

View File

@ -49,14 +49,12 @@ abstract class DeviceInfo {
abstract val imsiMd5: ByteArray abstract val imsiMd5: ByteArray
abstract val imei: String abstract val imei: String
abstract val ipAddress: ByteArray val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
abstract val androidId: ByteArray abstract val androidId: ByteArray
abstract val apn: ByteArray abstract val apn: ByteArray
val guid: ByteArray by lazy { generateGuid(androidId, macAddress) }
fun generateDeviceInfoData(): ByteArray { fun generateDeviceInfoData(): ByteArray {
@Serializable @Serializable
class DevInfo( class DevInfo(
@ -121,11 +119,6 @@ class DeviceInfoData(
@Transient @Transient
override lateinit var context: Context override lateinit var context: Context
@OptIn(ExperimentalUnsignedTypes::class)
override val ipAddress: ByteArray
get() = MiraiPlatformUtils.localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }
?.toByteArray()
?: byteArrayOf()
override val androidId: ByteArray get() = display override val androidId: ByteArray get() = display
@Serializable @Serializable
@ -137,13 +130,6 @@ class DeviceInfoData(
) : Version ) : Version
} }
/**
* Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/
@OptIn(MiraiInternalAPI::class)
fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray =
MiraiPlatformUtils.md5(androidId + macAddress)
/* /*
fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo( fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo(
brand = brand.encodeToString(), brand = brand.encodeToString(),

View File

@ -15,6 +15,7 @@ import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream import kotlinx.io.InputStream
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
@ -22,8 +23,7 @@ import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.OfflineImage import net.mamoe.mirai.message.data.OfflineImage
import net.mamoe.mirai.message.data.sendTo import net.mamoe.mirai.message.data.sendTo
import net.mamoe.mirai.utils.io.toUHexString import kotlin.jvm.JvmSynthetic
import kotlinx.serialization.InternalSerializationApi
/** /**
* 外部图片. 图片数据还没有读取到内存. * 外部图片. 图片数据还没有读取到内存.
@ -150,6 +150,7 @@ class ExternalImage private constructor(
/** /**
* 将图片发送给指定联系人 * 将图片发送给指定联系人
*/ */
@JvmSynthetic
suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> = when (contact) { suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> = when (contact) {
is Group -> contact.uploadImage(this).sendTo(contact) is Group -> contact.uploadImage(this).sendTo(contact)
is QQ -> contact.uploadImage(this).sendTo(contact) is QQ -> contact.uploadImage(this).sendTo(contact)
@ -162,6 +163,7 @@ suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> =
* *
* @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人 * @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人
*/ */
@JvmSynthetic
suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) { suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) {
is Group -> contact.uploadImage(this) is Group -> contact.uploadImage(this)
is QQ -> contact.uploadImage(this) is QQ -> contact.uploadImage(this)
@ -171,10 +173,18 @@ suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact
/** /**
* 将图片发送给 [this] * 将图片发送给 [this]
*/ */
@JvmSynthetic
suspend inline fun <C : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = image.sendTo(this) suspend inline fun <C : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = image.sendTo(this)
private operator fun ByteArray.get(range: IntRange): String = buildString { internal operator fun ByteArray.get(range: IntRange): String = buildString {
range.forEach { range.forEach {
append(this@get[it].toUHexString()) append(this@get[it].fixToString())
}
}
private fun Byte.fixToString(): String {
return when (this.toInt()) {
in 0..15 -> "0${this.toString(16)}"
else -> this.toString(16)
} }
} }

View File

@ -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
}
}

View File

@ -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
}

View File

@ -7,10 +7,11 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused") @file:Suppress("unused", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlin.jvm.JvmSynthetic
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
// TODO: 2020/2/10 添加中文 doc // TODO: 2020/2/10 添加中文 doc
@ -18,7 +19,7 @@ import kotlin.reflect.KProperty
/** /**
* WeakRef that `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only * WeakRef that `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only
*/ */
inline class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) { class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) {
fun get(): T = weakRef.get() ?: error("WeakRef is released") fun get(): T = weakRef.get() ?: error("WeakRef is released")
fun clear() = weakRef.clear() fun clear() = weakRef.clear()
} }
@ -30,7 +31,8 @@ inline class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) {
* val bot: Bot by param.unsafeWeakRef() * val bot: Bot by param.unsafeWeakRef()
* ``` * ```
*/ */
operator fun <T> UnsafeWeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get() @JvmSynthetic
inline operator fun <T> UnsafeWeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
/** /**
* Weak Reference. * Weak Reference.
@ -62,12 +64,14 @@ annotation class WeakRefProperty
* Provides a weak reference to [this] * Provides a weak reference to [this]
* The `getValue` for delegation returns [this] when [this] is not released by GC * The `getValue` for delegation returns [this] when [this] is not released by GC
*/ */
fun <T> T.weakRef(): WeakRef<T> = WeakRef(this) @JvmSynthetic
inline fun <T> T.weakRef(): WeakRef<T> = WeakRef(this)
/** /**
* Constructs an unsafe inline delegate for [this] * Constructs an unsafe inline delegate for [this]
*/ */
fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this) @JvmSynthetic
inline fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this)
/** /**
* Provides a weak reference to [this]. * Provides a weak reference to [this].
@ -75,7 +79,8 @@ fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this)
* *
* **UNSTABLE API**: It is strongly suggested not to use this api * **UNSTABLE API**: It is strongly suggested not to use this api
*/ */
fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef()) @JvmSynthetic
inline fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef())
/** /**
* Provides delegate value. * Provides delegate value.
@ -84,9 +89,11 @@ fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef())
* val bot: Bot? by param.weakRef() * val bot: Bot? by param.weakRef()
* ``` * ```
*/ */
operator fun <T> WeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get() @JvmSynthetic
inline operator fun <T> WeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get()
/** /**
* Call the block if the referent is absent * Call the block if the referent is absent
*/ */
@JvmSynthetic
inline fun <T, R> WeakRef<T>.ifAbsent(block: (T) -> R): R? = this.get()?.let(block) inline fun <T, R> WeakRef<T>.ifAbsent(block: (T) -> R): R? = this.get()?.let(block)

View File

@ -42,19 +42,6 @@ annotation class MiraiExperimentalAPI(
val message: String = "" val message: String = ""
) )
/**
* 标记这个类, 类型, 函数, 属性, 字段, 或构造器为仅供调试阶段使用的.
*
* 这些 API 不具有稳定性, 可能会在任意时刻更改, 并且效率非常低下.
* 非常不建议在发行版本中使用这些 API.
*/
@Retention(AnnotationRetention.SOURCE)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
annotation class MiraiDebugAPI(
val message: String = ""
)
/** /**
* 标记一个自 Mirai 某个版本起才支持或在这个版本修改过的 API. * 标记一个自 Mirai 某个版本起才支持或在这个版本修改过的 API.
*/ */
@ -62,3 +49,11 @@ annotation class MiraiDebugAPI(
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@MustBeDocumented @MustBeDocumented
annotation class SinceMirai(val version: String) annotation class SinceMirai(val version: String)
/**
* 标记一个正计划在 [version] 版本时删除的 API.
*/
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
internal annotation class PlannedRemoval(val version: String)

View File

@ -18,10 +18,8 @@ import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.io.ByteWriteChannel import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.readAvailable import kotlinx.coroutines.io.readAvailable
import kotlinx.io.OutputStream import kotlinx.io.OutputStream
import kotlinx.serialization.InternalSerializationApi
import kotlinx.io.core.Output import kotlinx.io.core.Output
import kotlinx.io.pool.useInstance import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.utils.io.ByteArrayPool
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -31,72 +29,53 @@ import kotlin.jvm.JvmName
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/ */
@OptIn(InternalSerializationApi::class) @OptIn(InternalSerializationApi::class)
@MiraiExperimentalAPI
suspend fun ByteReadChannel.copyTo(dst: OutputStream) { suspend fun ByteReadChannel.copyTo(dst: OutputStream) {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.write(buffer, 0, size)
dst.write(buffer, 0, size)
}
} }
} }
/** /**
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/ */
@MiraiExperimentalAPI
suspend fun ByteReadChannel.copyTo(dst: Output) { suspend fun ByteReadChannel.copyTo(dst: Output) {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.writeFully(buffer, 0, size)
dst.writeFully(buffer, 0, size)
}
} }
} }
/** /**
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/ */
@MiraiExperimentalAPI
suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) { suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.writeFully(buffer, 0, size)
dst.writeFully(buffer, 0, size)
}
} }
} }
/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/**
* 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
*/
suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) {
ByteArrayPool.useInstance {
do {
val size = this.readAvailable(it)
dst.writeFully(it, 0, size)
} while (size != 0)
}
}
*/
// copyAndClose // copyAndClose
/** /**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/ */
@MiraiExperimentalAPI
@OptIn(InternalSerializationApi::class) @OptIn(InternalSerializationApi::class)
suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 API 不是 internal 的 suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 API 不是 internal 的
try { try {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.write(buffer, 0, size)
dst.write(buffer, 0, size)
}
} }
} finally { } finally {
dst.close() dst.close()
@ -106,14 +85,13 @@ suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个
/** /**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/ */
@MiraiExperimentalAPI
suspend fun ByteReadChannel.copyAndClose(dst: Output) { suspend fun ByteReadChannel.copyAndClose(dst: Output) {
try { try {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.writeFully(buffer, 0, size)
dst.writeFully(buffer, 0, size)
}
} }
} finally { } finally {
dst.close() dst.close()
@ -123,15 +101,14 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) {
/** /**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/ */
@MiraiExperimentalAPI
suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) { suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) {
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
try { try {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.writeFully(buffer, 0, size)
dst.writeFully(buffer, 0, size)
}
} }
} finally { } finally {
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
@ -142,35 +119,17 @@ suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) {
/** /**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/ */
@MiraiExperimentalAPI
suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) { suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) {
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
try { try {
@OptIn(MiraiInternalAPI::class) val buffer = ByteArray(2048)
ByteArrayPool.useInstance { buffer -> var size: Int
var size: Int while (this.readAvailable(buffer).also { size = it } > 0) {
while (this.readAvailable(buffer).also { size = it } > 0) { dst.writeFully(buffer, 0, size)
dst.writeFully(buffer, 0, size)
}
} }
} finally { } finally {
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
dst.close(null) dst.close(null)
} }
} }
/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel
/**
* 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
*/
suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) {
dst.close(kotlin.runCatching {
ByteArrayPool.useInstance {
do {
val size = this.readAvailable(it)
dst.writeFully(it, 0, size)
} while (size != 0)
}
}.exceptionOrNull())
}
*/

View File

@ -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)
}
}

View File

@ -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!!
}

View File

@ -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" }
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -7,13 +7,19 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.io.autoHexToBytes
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
internal fun String.autoHexToBytes(): ByteArray =
this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map {
(it[0].toString() + it[1]).toUByte(16).toByte()
}.toList().toByteArray()
internal class ImageTest { internal class ImageTest {
@Test @Test

View File

@ -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])
}
}

View File

@ -18,6 +18,7 @@ import kotlinx.io.core.use
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.copyAndClose import net.mamoe.mirai.utils.copyAndClose
import net.mamoe.mirai.utils.copyTo import net.mamoe.mirai.utils.copyTo
@ -31,7 +32,7 @@ import java.net.URL
* 一条从服务器接收到的消息事件. * 一条从服务器接收到的消息事件.
* JVM 平台相关扩展 * JVM 平台相关扩展
*/ */
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() { actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
// region 上传图片 // region 上传图片
suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)

View File

@ -9,6 +9,8 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.pool.DefaultPool
actual abstract class Context actual abstract class Context
open class ContextImpl : Context() open class ContextImpl : Context()

View File

@ -19,7 +19,7 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.copyTo import kotlinx.io.core.copyTo
import kotlinx.io.errors.IOException import kotlinx.io.errors.IOException
import kotlinx.io.streams.asOutput import kotlinx.io.streams.asOutput
import net.mamoe.mirai.utils.io.getRandomString import net.mamoe.mirai.utils.internal.md5
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
@ -72,7 +72,7 @@ fun File.toExternalImage(): ExternalImage {
return ExternalImage( return ExternalImage(
width = image.getWidth(0), width = image.getWidth(0),
height = image.getHeight(0), height = image.getHeight(0),
md5 = MiraiPlatformUtils.md5(this.inputStream()), // dont change md5 = this.inputStream().md5(), // dont change
imageFormat = image.formatName, imageFormat = image.formatName,
input = this.inputStream(), input = this.inputStream(),
filename = this.name filename = this.name

View File

@ -31,6 +31,9 @@ import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
actual typealias Throws = kotlin.jvm.Throws
@MiraiExperimentalAPI
class DefaultLoginSolver( class DefaultLoginSolver(
private val input: suspend () -> String, private val input: suspend () -> String,
private val overrideLogger: MiraiLogger? = null private val overrideLogger: MiraiLogger? = null
@ -92,6 +95,31 @@ class DefaultLoginSolver(
} }
} }
/**
* 验证码, 设备锁解决器
*/
actual abstract class LoginSolver {
actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
actual companion object {
actual val Default: LoginSolver
@OptIn(MiraiExperimentalAPI::class)
get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") })
}
}
///////////////////////////////
//////////////// internal
///////////////////////////////
internal actual fun getFileBasedDeviceInfoSupplier(filename: String): ((Context) -> DeviceInfo)? {
return {
File(filename).loadAsDeviceInfo(it)
}
}
// Copied from Ktor CIO // Copied from Ktor CIO
private fun File.writeChannel( private fun File.writeChannel(
coroutineContext: CoroutineContext = Dispatchers.IO coroutineContext: CoroutineContext = Dispatchers.IO
@ -103,7 +131,6 @@ private fun File.writeChannel(
} }
}.channel }.channel
private val loginSolverLock = Mutex() private val loginSolverLock = Mutex()
/** /**
@ -159,113 +186,3 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub
} }
} }
} }
@Suppress("ClassName", "PropertyName")
actual open class BotConfiguration actual constructor() {
/**
* 日志记录器
*/
actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") }
/**
* 网络层日志构造器
*/
actual var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") }
/**
* 设备信息覆盖. 默认使用随机的设备信息.
*/
actual var deviceInfo: ((Context) -> DeviceInfo)? = null
/**
* [CoroutineContext]
*/
actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
/**
* 心跳周期. 过长会导致被服务器断开连接.
*/
actual var heartbeatPeriodMillis: Long = 60.secondsToMillis
/**
* 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/
actual var heartbeatTimeoutMillis: Long = 2.secondsToMillis
/**
* 心跳失败后的第一次重连前的等待时间.
*/
actual var firstReconnectDelayMillis: Long = 5.secondsToMillis
/**
* 重连失败后, 继续尝试的每次等待时间
*/
actual var reconnectPeriodMillis: Long = 5.secondsToMillis
/**
* 最多尝试多少次重连
*/
actual var reconnectionRetryTimes: Int = Int.MAX_VALUE
/**
* 验证码处理器
*/
actual var loginSolver: LoginSolver = LoginSolver.Default
actual companion object {
/**
* 默认的配置实例
*/
@JvmStatic
actual val Default = BotConfiguration()
}
@Suppress("NOTHING_TO_INLINE")
@BotConfigurationDsl
inline operator fun FileBasedDeviceInfo.unaryPlus() {
deviceInfo = { File(filepath).loadAsDeviceInfo(it) }
}
@Suppress("NOTHING_TO_INLINE")
@BotConfigurationDsl
inline operator fun FileBasedDeviceInfo.ByDeviceDotJson.unaryPlus() {
deviceInfo = { File("device.json").loadAsDeviceInfo(it) }
}
actual operator fun _NoNetworkLog.unaryPlus() {
networkLoggerSupplier = supplier
}
/**
* 不记录网络层的 log.
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
*/
@BotConfigurationDsl
actual val NoNetworkLog: _NoNetworkLog
get() = _NoNetworkLog
@BotConfigurationDsl
actual object _NoNetworkLog {
internal val supplier = { _: BotNetworkHandler -> SilentLogger }
}
}
/**
* 使用文件系统存储设备信息.
*/
@BotConfigurationDsl
inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: String) {
/**
* 使用 "device.json" 存储设备信息
*/
@BotConfigurationDsl
companion object ByDeviceDotJson
}
/**
* 验证码, 设备锁解决器
*/
actual abstract class LoginSolver {
actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String?
actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String?
actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
actual companion object {
actual val Default: LoginSolver
get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") })
}
}

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:JvmMultifileClass
@file:JvmName("Utils")
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") @file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
@ -15,7 +17,6 @@ import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -31,88 +32,3 @@ import java.util.zip.Inflater
*/ */
actual val currentTimeMillis: Long actual val currentTimeMillis: Long
get() = System.currentTimeMillis() get() = System.currentTimeMillis()
@MiraiInternalAPI
actual object MiraiPlatformUtils {
actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
ByteArrayOutputStream().use { output ->
inflater.setInput(data, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val deflater = Deflater()
deflater.setInput(data, offset, length)
deflater.finish()
ByteArrayPool.useInstance {
return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() }
}
}
actual fun gzip(data: ByteArray, offset: Int, length: Int): ByteArray {
ByteArrayOutputStream().use { buf ->
GZIPOutputStream(buf).use { gzip ->
data.inputStream(offset, length).use { t -> t.copyTo(gzip) }
}
buf.flush()
return buf.toByteArray()
}
}
actual fun ungzip(data: ByteArray, offset: Int, length: Int): ByteArray {
return GZIPInputStream(data.inputStream(offset, length)).use { it.readBytes() }
}
actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray {
data.checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest()
}
actual inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@OptIn(KtorExperimentalAPI::class)
actual val Http: HttpClient = HttpClient(CIO)
/**
* Localhost 解析
*/
actual fun localIpAddress(): String = runCatching {
Inet4Address.getLocalHost().hostAddress
}.getOrElse { "192.168.1.123" }
fun md5(stream: InputStream): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
stream.use { input ->
object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
}.use { output ->
input.copyTo(output)
}
}
return digest.digest()
}
}

View File

@ -12,19 +12,16 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress import net.mamoe.mirai.utils.internal.md5
import net.mamoe.mirai.utils.MiraiPlatformUtils.md5
import net.mamoe.mirai.utils.io.getRandomByteArray
import net.mamoe.mirai.utils.io.getRandomString
import java.io.File import java.io.File
import kotlin.random.Random
import kotlin.random.nextInt
/** /**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存. * 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/ */
@OptIn(UnstableDefault::class)
fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo { fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
if (!this.exists() || this.length() == 0L) { if (!this.exists() || this.length() == 0L) {
return SystemDeviceInfo(context).also { return SystemDeviceInfo(context).also {
@ -55,9 +52,11 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
override val brand: ByteArray = "mamoe".toByteArray() override val brand: ByteArray = "mamoe".toByteArray()
override val model: ByteArray = "mirai".toByteArray() override val model: ByteArray = "mirai".toByteArray()
override val bootloader: ByteArray = "unknown".toByteArray() override val bootloader: ByteArray = "unknown".toByteArray()
override val fingerprint: ByteArray = "mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray() override val fingerprint: ByteArray =
override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray() "mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray()
override val procVersion: ByteArray = "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray() override val bootId: ByteArray = ExternalImage.generateUUID(getRandomByteArray(16).md5()).toByteArray()
override val procVersion: ByteArray =
"Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray()
override val baseBand: ByteArray = byteArrayOf() override val baseBand: ByteArray = byteArrayOf()
override val version: Version = Version override val version: Version = Version
override val simInfo: ByteArray = "T-Mobile".toByteArray() override val simInfo: ByteArray = "T-Mobile".toByteArray()
@ -65,9 +64,8 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
override val macAddress: ByteArray = "02:00:00:00:00:00".toByteArray() override val macAddress: ByteArray = "02:00:00:00:00:00".toByteArray()
override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray() override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray()
override val wifiSSID: ByteArray? = "<unknown ssid>".toByteArray() override val wifiSSID: ByteArray? = "<unknown ssid>".toByteArray()
override val imsiMd5: ByteArray = md5(getRandomByteArray(16)) override val imsiMd5: ByteArray = getRandomByteArray(16).md5()
override val imei: String = getRandomString(15, '0'..'9') override val imei: String = getRandomString(15, '0'..'9')
override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
override val androidId: ByteArray get() = display override val androidId: ByteArray get() = display
override val apn: ByteArray = "wifi".toByteArray() override val apn: ByteArray = "wifi".toByteArray()
@ -79,3 +77,26 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
override val sdk: Int = 29 override val sdk: Int = 29
} }
} }
/**
* 生成长度为 [length], 元素为随机 `0..255` [ByteArray]
*/
internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
/**
* 随机生成长度为 [length] [String].
*/
internal fun getRandomString(length: Int): String =
getRandomString(length, 'a'..'z', 'A'..'Z', '0'..'9')
/**
* 根据所给 [charRange] 随机生成长度为 [length] [String].
*/
internal fun getRandomString(length: Int, charRange: CharRange): String =
String(CharArray(length) { charRange.random() })
/**
* 根据所给 [charRanges] 随机生成长度为 [length] [String].
*/
internal fun getRandomString(length: Int, vararg charRanges: CharRange): String =
String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() })

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils.internal
private var isAddSuppressedSupported: Boolean = true private var isAddSuppressedSupported: Boolean = true

View File

@ -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