This commit is contained in:
Him188 2020-02-06 13:58:57 +08:00
parent 9aa1d7d395
commit 388e1a5b7f
36 changed files with 181 additions and 433 deletions

View File

@ -1,5 +1,5 @@
#Thu Oct 03 14:28:43 CST 2019
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists

View File

@ -41,7 +41,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
override val coroutineContext: CoroutineContext = bot.coroutineContext + CoroutineExceptionHandler { _, throwable ->
throwable.logStacktrace("Exception in NetworkHandler")
bot.logger.error("Exception in NetworkHandler", throwable)
}
private lateinit var channel: PlatformSocket
@ -106,6 +106,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>(6000) // it's slow
}
@UseExperimental(MiraiExperimentalAPI::class)
override suspend fun init() {
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
@ -113,6 +114,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
}
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
//val msg = MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<MessageSvc.PbGetMsg.Response>()
//println(msg.contentToString())
bot.qqs.delegate.clear()
@ -199,16 +202,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} catch (e: Exception) {
groupInfo[it.groupCode] = -1
bot.logger.info("${it.groupCode}的列表拉取失败, 将采用动态加入")
println(e.message)
println(e.logStacktrace())
bot.logger.error(e)
}
}
}
bot.logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}")
} catch (e: Exception) {
bot.logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
println(e.message)
println(e.logStacktrace())
bot.logger.error(e)
}
//===log===//
@ -239,8 +240,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
}
bot.logger.info("====================Mirai Bot List初始化完毕====================")
return
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
bot.firstLoginSucceed = true
}
@ -483,7 +484,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private val packetReceiveLock: Mutex = Mutex()
/**
* 发送一个包, 但不期待任何返回.-
* 发送一个包, 但不期待任何返回.
*/
suspend fun OutgoingPacket.sendWithoutExpect() {
bot.logger.info("Send: ${this.commandName}")
@ -494,6 +495,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
/**
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
*
* @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry
*/
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 0): E {
require(timeoutMillis > 0) { "timeoutMillis must > 0" }

View File

@ -1,20 +1,17 @@
package net.mamoe.mirai.qqandroid.network.http
import io.ktor.client.HttpClient
import io.ktor.client.request.*
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.client.request.post
import io.ktor.client.response.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.URLProtocol
import io.ktor.http.setCookie
import io.ktor.http.userAgent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.io.readRemaining
import kotlinx.coroutines.withContext
import net.mamoe.mirai.qqandroid.QQAndroidBot
import kotlinx.io.core.readBytes
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.mirai.utils.io.readRemainingBytes
import net.mamoe.mirai.utils.io.toUHexString
/**
@ -49,7 +46,7 @@ internal suspend fun HttpClient.getPTLoginCookies(
println(res.status)
println(res.setCookie())
println(res.content.readRemaining().readRemainingBytes().toUHexString())
println(res.content.readRemaining().readBytes().toUHexString())
return "done";
}

View File

@ -10,7 +10,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeIntLVPacket
import net.mamoe.mirai.utils.io.writeQQ
internal class OutgoingPacket constructor(
name: String?,
@ -250,7 +249,7 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
writeShort(client.protocolVersion)
writeShort(commandId.toShort())
writeShort(1) // const??
writeQQ(client.uin)
writeInt(client.uin.toInt())
writeByte(3) // originally const
writeByte(encryptMethod.id.toByte())
writeByte(0) // const8_always_0

View File

@ -23,7 +23,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: ByteArray) {
writeShort(1) // _ip_ver
writeInt(Random.nextInt())
writeInt(uin.toInt())
writeTime()
writeInt(currentTimeMillis.toInt())
writeFully(ip)
writeShort(0)
} shouldEqualsTo 20
@ -100,7 +100,7 @@ fun BytePacketBuilder.t106(
writeLong(uin)
}
writeTime()
writeInt(currentTimeMillis.toInt())
writeFully(ByteArray(4)) // ip // no need to write actual ip
writeByte(n5_always_1.toByte())
writeFully(passwordMd5)

View File

@ -1,15 +1,15 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.encode
import kotlinx.io.core.*
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.toByteArray
import kotlinx.serialization.toUtf8Bytes
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.*
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetTroopListReqV2Simplify
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.ModifyGroupCardReq
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.stUinInfo
@ -22,7 +22,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.utils.daysToSeconds
import net.mamoe.mirai.utils.io.encodeToGBKString
import net.mamoe.mirai.utils.io.encodeToString
internal object TroopManagement {
@ -355,7 +354,7 @@ internal object TroopManagement {
gender = 0,
dwuin = member.id,
dwFlag = 31,
sName = newName.toUtf8Bytes().encodeToGBKString(),
sName = newName.toUtf8Bytes().encodeToString(charset = CharsetGBK),
sPhone = "",
sEmail = "",
sRemark = ""

View File

@ -22,15 +22,15 @@ import net.mamoe.mirai.utils.md5
/**
* OicqRequest
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
@Suppress("FunctionName")
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketResponse>("wtlogin.login") {
private const val subAppId = 537062845L
/**
* 提交验证码
*/
object SubCommand2 {
private const val appId = 16L
private const val subAppId = 537062845L
fun SubmitSliderCaptcha(
client: QQAndroidClient,
ticket: String
@ -66,10 +66,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
}
object SubCommand20 {
private const val appId = 16L
private const val subAppId = 537062845L
@UseExperimental(MiraiInternalAPI::class)
operator fun invoke(
client: QQAndroidClient,
t402: ByteArray
@ -91,9 +88,6 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
* 提交 SMS
*/
object SubCommand7 {
private const val appId = 16L
private const val subAppId = 537062845L
@UseExperimental(MiraiInternalAPI::class)
operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
@ -297,7 +291,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
}
}
class DeviceLockLogin(val t402: ByteArray, val t403: ByteArray) : LoginPacketResponse() {
class DeviceLockLogin(val t402: ByteArray) : LoginPacketResponse() {
override fun toString(): String = "LoginPacket.LoginPacketResponse.DeviceLockLogin"
}
}
@ -320,7 +314,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
2 -> onSolveLoginCaptcha(tlvMap, bot)
160 /*-96*/ -> onUnsafeDeviceLogin(tlvMap)
204 /*-52*/ -> onSMSVerifyNeeded(tlvMap, bot)
else -> tlvMap[0x149]?.let { bot.client.analysisTlv149(it) } ?: error("unknown login result type: $type")
else -> tlvMap[0x149]?.let { analysisTlv149(it) } ?: error("unknown login result type: $type")
}
}
@ -331,11 +325,11 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
): LoginPacketResponse.DeviceLockLogin {
bot.client.t104 = tlvMap.getOrFail(0x104)
// println("403 " + tlvMap[0x403]?.toUHexString())
return LoginPacketResponse.DeviceLockLogin(tlvMap[0x402]!!, tlvMap.getOrFail(0x403))
return LoginPacketResponse.DeviceLockLogin(tlvMap.getOrFail(0x402))
}
private fun onUnsafeDeviceLogin(tlvMap: TlvMap): LoginPacketResponse.UnsafeLogin {
return LoginPacketResponse.UnsafeLogin(tlvMap.getOrFail(0x204).toReadPacket().readRemainingBytes().encodeToString())
return LoginPacketResponse.UnsafeLogin(tlvMap.getOrFail(0x204).toReadPacket().readBytes().encodeToString())
}
private fun onErrorMessage(tlvMap: TlvMap): LoginPacketResponse.Error {
@ -369,7 +363,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
imageData.discardExact(2)//image Length
val sign = imageData.readBytes(signInfoLength.toInt())
return LoginPacketResponse.Captcha.Picture(
data = imageData.readRemainingBytes().toIoBuffer(),
data = imageData.readBytes().toIoBuffer(),
sign = sign
)
} else error("UNKNOWN CAPTCHA QUESTION: $question")
@ -652,11 +646,6 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
pwdFlag = readByte().toInt() == 1
}
/**
*/
private fun QQAndroidClient.analysisTlv528(t528: ByteArray) = t528.read {
}
/**
* 设置 [QQAndroidClient.uin]
*/
@ -696,7 +685,7 @@ internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketRespo
/**
* 错误消息
*/
private fun QQAndroidClient.analysisTlv149(t149: ByteArray): LoginPacketResponse.Error {
private fun analysisTlv149(t149: ByteArray): LoginPacketResponse.Error {
return t149.read {
discardExact(2) //type

View File

@ -114,6 +114,16 @@ fun ByteReadPacket.decodeMultiClientToServerPackets() {
println()
}
fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) =
debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys {
when (keyLength) {
1 -> it.key.toUByte().contentToString()
2 -> it.key.toUShort().contentToString()
4 -> it.key.toUInt().contentToString()
else -> error("Expecting 1, 2 or 4 for keyLength")
}
}.entries.joinToString(prefix = "{", postfix = "}", separator = "\n"))
fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed", { buildPacket { writeInt(it.size + 4); writeFully(it) } }) {
val flag1 = readInt()
println("flag1=" + flag1.contentToString())
@ -139,7 +149,7 @@ fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed
println("uin=" + readString(readInt() - 4))
println("// 解密 body")
val encrypted = readRemainingBytes()
val encrypted = readBytes()
val decrypted = encrypted.tryDecryptOrNull()
if (decrypted == null) {

View File

@ -198,6 +198,10 @@ private fun ByteReadPacket.parseOicqResponse(body: ByteReadPacket.() -> Unit) {
}
}
fun ByteReadPacket.readIoBuffer(
n: Int = remaining.toInt()//not that safe but adequate
): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) }
/**
* 解析 SSO 层包装
*/

View File

@ -1,17 +1,18 @@
package net.mamoe.mirai.utils.cryptor
import net.mamoe.mirai.utils.MiraiDebugAPI
import java.lang.reflect.Field
import kotlin.reflect.full.allSuperclasses
@MiraiDebugAPI
actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String {
val newPrefix = prefix
return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +
this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") }
.distinctBy { it.name }
.filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" }
.joinToStringPrefixed(
prefix = newPrefix
prefix = prefix
) {
it.isAccessible = true
if (filter != null) {
@ -22,7 +23,7 @@ actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: Strin
it.name + "=" + kotlin.runCatching {
val value = it.get(this)
if (value == this) "<this>"
else value.contentToString(newPrefix)
else value.contentToString(prefix)
}.getOrElse { "<!>" }
} + "\n$prefix}"
}

View File

@ -134,8 +134,6 @@ abstract class Bot : CoroutineScope {
* 不建议调用这个函数.
*
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login]
*
* @throws LoginFailedException
*/
abstract suspend fun login()
// endregion

View File

@ -4,7 +4,6 @@
package net.mamoe.mirai
import net.mamoe.mirai.utils.LoginFailedException
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@ -15,8 +14,6 @@ import kotlin.jvm.JvmName
//Contacts
/**
* 登录, 返回 [this]
*
* @throws LoginFailedException
*/
suspend inline fun <B: Bot> B.alsoLogin(): B = also { login() }

View File

@ -7,6 +7,7 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmStatic
/**
* 验证码, 设备锁解决器
*/
abstract class LoginSolver {
abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String?

View File

@ -1,8 +0,0 @@
package net.mamoe.mirai.utils
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.io.getRandomByteArray
fun md5(str: String): ByteArray = md5(str.toByteArray())

View File

@ -80,7 +80,7 @@ class ExternalImage(
suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) {
is Group -> contact.uploadImage(this).sendTo(contact)
is QQ -> contact.uploadImage(this).sendTo(contact)
else -> assertUnreachable()
else -> error("unreachable")
}
/**
@ -92,7 +92,7 @@ suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) {
suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) {
is Group -> contact.uploadImage(this)
is QQ -> contact.uploadImage(this)
else -> assertUnreachable()
else -> error("unreachable")
}
/**

View File

@ -1,8 +0,0 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.data.LoginResult
class LoginFailedException(
val result: LoginResult,
message: String = "Login failed with reason $result"
) : RuntimeException(message)

View File

@ -1,3 +1,5 @@
@file:Suppress("unused")
package net.mamoe.mirai.utils
import net.mamoe.mirai.Bot
@ -139,7 +141,7 @@ interface MiraiLogger {
/**
* 当前平台的默认的日志记录器.
* _JVM 控制台_ 端的实现为 [println]
* _Android_ 端的实现为 [android.util.Log]
* _Android_ 端的实现为 `android.util.Log`
*
* 不应该直接构造这个类的实例. 请使用 [DefaultLogger], 或使用默认的顶层日志记录 [MiraiLogger.Companion]
*/

View File

@ -1,5 +1,11 @@
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
/**
* 要求 [this] 最小为 [min].
*/

View File

@ -1,5 +1,11 @@
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
/**
* 图片文件过大
*/

View File

@ -1,10 +1,11 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.util.date.GMTDate
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.io.core.toByteArray
/**
* 时间戳
@ -36,6 +37,8 @@ expect fun ByteArray.unzip(offset: Int = 0, length: Int = this.size - offset): B
*/
expect fun md5(byteArray: ByteArray): ByteArray
inline fun md5(str: String): ByteArray = md5(str.toByteArray())
/**
* hostname 解析 ipv4
*/
@ -53,7 +56,7 @@ expect val Http: HttpClient
expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher
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(length >= 0) { "length shouldn't be negative: $length" }
require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }

View File

@ -1,4 +1,4 @@
@file:Suppress("unused", "FunctionName")
@file:Suppress("unused", "FunctionName", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils
@ -16,7 +16,7 @@ import kotlin.coroutines.EmptyCoroutineContext
* val image: Deferred<Image> by suspendLazy{ /* intializer */ }
* ```
*/
fun <R> CoroutineScope.suspendLazy(context: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R): Lazy<Deferred<R>> =
inline fun <R> CoroutineScope.suspendLazy(context: CoroutineContext = EmptyCoroutineContext, noinline initializer: suspend () -> R): Lazy<Deferred<R>> =
SuspendLazy(this, context, initializer)
/**

View File

@ -1,9 +0,0 @@
package net.mamoe.mirai.utils
/**
* 仅用于测试时标记, 未来会删除
*
* @author Him188moe
*/
@Suppress("unused")
internal annotation class Tested(val date: String = "")

View File

@ -1,5 +1,10 @@
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.time.seconds
// 临时使用, 待 Kotlin Duration 稳定后使用 Duration.

View File

@ -1,7 +0,0 @@
package net.mamoe.mirai.utils
/**
* 表示这里是不可到达的位置.
*/
@Suppress("NOTHING_TO_INLINE")
internal inline fun assertUnreachable(): Nothing = error("This clause should not be reached")

View File

@ -1,66 +0,0 @@
package net.mamoe.mirai.utils.cryptor
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.toReadPacket
/**
* [ByteArray] 解密器
*/
interface DecrypterByteArray : Decrypter {
val value: ByteArray
override fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket = input.decryptBy(value, offset, length)
}
/**
* [IoBuffer] 解密器
*/
interface DecrypterIoBuffer : Decrypter {
val value: IoBuffer
override fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket = input.decryptBy(value, offset, length)
}
/**
* 连接在一起的解密器
*/
inline class LinkedDecrypter(inline val block: (input: ByteReadPacket, offset: Int, length: Int) -> ByteReadPacket) : Decrypter {
override fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket = block(input, offset, length)
}
object NoDecrypter : Decrypter, DecrypterType<NoDecrypter> {
override fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket {
if (offset == 0 && length == input.remaining.toInt()) {
return input
}
ByteArrayPool.useInstance { buffer ->
input.readFully(buffer, offset, length)
return buffer.toReadPacket()
}
}
}
fun Decrypter.decrypt(input: ByteReadPacket): ByteReadPacket = this.decrypt(input, 0, input.remaining.toInt())
/**
* 解密器
*/
interface Decrypter {
// do not write with default args. NoSuchMethodError when inline classes override this function
fun decrypt(input: ByteReadPacket, offset: Int, length: Int): ByteReadPacket
/**
* 连接后将会先用 this 解密, 再用 [another] 解密
*/
operator fun plus(another: Decrypter): Decrypter =
LinkedDecrypter { input: ByteReadPacket, offset: Int, length: Int -> another.decrypt(this.decrypt(input, offset, length)) }
}
interface DecrypterType<D : Decrypter>
inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) =
this.encryptAndWrite(key.value, encoder)

View File

@ -6,6 +6,9 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import kotlinx.io.core.readULong
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.*
import kotlin.jvm.JvmStatic
@ -24,12 +27,14 @@ import kotlin.jvm.JvmStatic
*
* https://www.jianshu.com/p/f888907adaeb
*/
@MiraiDebugAPI
fun ProtoFieldId(serializedId: UInt): ProtoFieldId =
ProtoFieldId(
protoFieldNumber(serializedId),
protoType(serializedId)
)
@MiraiDebugAPI
data class ProtoFieldId(
val fieldNumber: Int,
val type: ProtoType
@ -38,6 +43,7 @@ data class ProtoFieldId(
}
@Suppress("SpellCheckingInspection")
@MiraiDebugAPI
enum class ProtoType(val value: Byte, private val typeName: String) {
/**
* int32, int64, uint32, uint64, sint32, sint64, bool, enum
@ -82,6 +88,7 @@ enum class ProtoType(val value: Byte, private val typeName: String) {
*
* serializedId = (fieldNumber << 3) | wireType
*/
@MiraiDebugAPI
fun protoType(number: UInt): ProtoType =
ProtoType.valueOf(number.toInt().shl(29).ushr(29).toByte())
@ -90,9 +97,10 @@ fun protoType(number: UInt): ProtoType =
*
* serializedId = (fieldNumber << 3) | wireType
*/
@MiraiDebugAPI
fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3)
@MiraiDebugAPI
class ProtoMap(map: MutableMap<ProtoFieldId, Any>) : MutableMap<ProtoFieldId, Any> by map {
companion object {
@JvmStatic
@ -120,6 +128,7 @@ class ProtoMap(map: MutableMap<ProtoFieldId, Any>) : MutableMap<ProtoFieldId, An
/**
* 将所有元素加入转换为多行的字符串表示.
*/
@MiraiDebugAPI
fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String {
return this.joinToString(prefix = "$prefix${ProtoMap.indent}", separator = "\n$prefix${ProtoMap.indent}", transform = transform)
}
@ -135,6 +144,7 @@ fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharS
* `data class`: 调用其 [toString]
* 其他类型: 反射获取它和它的所有来自 Mirai super 类型的所有自有属性并递归调用 [contentToString]. 嵌套结构将会以缩进表示
*/
@MiraiDebugAPI("Extremely slow")
fun Any?.contentToString(prefix: String = ""): String = when (this) {
is Unit -> "Unit"
is UInt -> "0x" + this.toUHexString("") + "($this)"
@ -222,8 +232,11 @@ fun Any?.contentToString(prefix: String = ""): String = when (this) {
}
}
@MiraiExperimentalAPI("Extremely slow")
@MiraiDebugAPI("Extremely slow")
expect fun Any.contentToStringReflectively(prefix: String = "", filter: ((String, Any?) -> Boolean)? = null): String
@MiraiDebugAPI
@Suppress("UNCHECKED_CAST")
fun ByteReadPacket.readProtoMap(length: Long = this.remaining): ProtoMap {
val map = ProtoMap(mutableMapOf())

View File

@ -1,8 +1,11 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils.io
import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlinx.io.core.use
@ -10,6 +13,8 @@ import net.mamoe.mirai.utils.checkOffsetAndLength
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic
@ -80,12 +85,9 @@ fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: In
}
@Suppress("NOTHING_TO_INLINE")
inline fun ByteArray.encodeToString(): String = String(this)
inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset)
fun ByteArray.encodeToGBKString(): String = String(this, 0, this.size, Charset.forName("GBK"))
fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) =
inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) =
ByteReadPacket(this, offset = offset, length = length)
@UseExperimental(ExperimentalContracts::class)
@ -94,6 +96,4 @@ inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R {
callsInPlace(t, InvocationKind.EXACTLY_ONCE)
}
return this.toReadPacket().use(t)
}
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
}

View File

@ -1,3 +1,7 @@
@file:Suppress("NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils.io
import kotlinx.io.core.*
@ -9,29 +13,34 @@ import net.mamoe.mirai.utils.withSwitch
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.js.JsName
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@MiraiDebugAPI("Unsatble")
object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch()
fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
@MiraiDebugAPI("Unstable")
inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
@MiraiDebugAPI("Low efficiency.")
fun debugPrintln(any: Any?) = DebugLogger.debug(any)
inline fun debugPrintln(any: Any?) = DebugLogger.debug(any)
@MiraiDebugAPI("Low efficiency.")
fun String.debugPrintThis(name: String): String {
inline fun String.debugPrintThis(name: String): String {
DebugLogger.debug("$name=$this")
return this
}
@MiraiDebugAPI("Low efficiency.")
fun ByteArray.debugPrintThis(name: String): ByteArray {
inline fun ByteArray.debugPrintThis(name: String): ByteArray {
DebugLogger.debug(name + "=" + this.toUHexString())
return this
}
@MiraiDebugAPI("Low efficiency.")
fun IoBuffer.debugPrintThis(name: String): IoBuffer {
inline fun IoBuffer.debugPrintThis(name: String): IoBuffer {
ByteArrayPool.useInstance {
val count = this.readAvailable(it)
DebugLogger.debug(name + "=" + it.toUHexString(offset = 0, length = count))
@ -49,12 +58,12 @@ inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer {
}
@MiraiDebugAPI("Low efficiency.")
fun Input.debugDiscardExact(n: Number, name: String = "") {
inline fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
}
@MiraiDebugAPI("Low efficiency.")
fun ByteReadPacket.debugPrintThis(name: String = ""): ByteReadPacket {
inline fun ByteReadPacket.debugPrintThis(name: String = ""): ByteReadPacket {
ByteArrayPool.useInstance {
val count = this.readAvailable(it)
DebugLogger.debug("ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count))

View File

@ -1,4 +1,6 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils.io
@ -7,8 +9,10 @@ import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.assertUnreachable
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.contentToString
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
@ -34,6 +38,7 @@ fun ByteReadPacket.transferTo(outputStream: OutputStream) {
}
}
@MiraiInternalAPI
inline fun <R> ByteReadPacket.useBytes(
n: Int = remaining.toInt(),//not that safe but adequate
block: (data: ByteArray, length: Int) -> R
@ -42,51 +47,24 @@ inline fun <R> ByteReadPacket.useBytes(
block(it, n)
}
fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
fun ByteReadPacket.readIoBuffer(
n: Int = remaining.toInt()//not that safe but adequate
): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) }
fun ByteReadPacket.readPacket(
inline fun ByteReadPacket.readPacket(
n: Int = remaining.toInt()//not that safe but adequate
): ByteReadPacket = this.readBytes(n).toReadPacket()
fun ByteReadPacket.readIoBuffer(n: Short) = this.readIoBuffer(n.toInt())
inline fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray())
fun Input.readIP(): String = buildString(4 + 3) {
repeat(4) {
val byte = readUByte()
this.append(byte.toString())
if (it != 3) this.append(".")
}
}
inline fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray())
fun Input.readQQ(): Long = this.readUInt().toLong()
fun Input.readGroup(): Long = this.readUInt().toLong()
fun Input.readGroupId(): Long = this.readUInt().toLong()
fun Input.readGroupCode(): Long = this.readUInt().toLong()
inline fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().toInt())
fun Input.readUVarIntLVString(): String = String(this.readUVarIntByteArray())
fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray())
fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray())
fun Input.readUVarIntByteArray(): ByteArray = this.readBytes(this.readUVarInt().toInt())
fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().toInt())
fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
private inline fun <R> inline(block: () -> R): R = block()
typealias TlvMap = MutableMap<Int, ByteArray>
fun TlvMap.getOrFail(tag: Int): ByteArray {
inline fun TlvMap.getOrFail(tag: Int): ByteArray {
return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)")
}
@ -94,10 +72,12 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr
return this[tag] ?: error(lazyMessage(tag))
}
fun Input.readTLVMap(tagSize: Int = 2): TlvMap = readTLVMap(true, tagSize)
@MiraiDebugAPI
inline fun Input.readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = readTLVMap(true, tagSize, suppressDuplication)
@MiraiDebugAPI
@Suppress("DuplicatedCode")
fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): TlvMap {
fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap {
val map = mutableMapOf<Int, ByteArray>()
var key = 0
@ -119,20 +99,22 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): TlvMap {
}.toUByte() != UByte.MAX_VALUE) {
if (map.containsKey(key)) {
DebugLogger.error(
@Suppress("IMPLICIT_CAST_TO_ANY")
"""
if (!suppressDuplication) {
DebugLogger.error(
@Suppress("IMPLICIT_CAST_TO_ANY")
"""
Error readTLVMap:
duplicated key ${when (tagSize) {
1 -> key.toByte()
2 -> key.toShort()
4 -> key
else -> assertUnreachable()
}.contentToString()}
1 -> key.toByte()
2 -> key.toShort()
4 -> key
else -> error("unreachable")
}.contentToString()}
map=${map.contentToString()}
duplicating value=${this.readUShortLVByteArray().toUHexString()}
""".trimIndent()
)
)
}
} else {
try {
map[key] = this.readUShortLVByteArray()
@ -147,98 +129,10 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): TlvMap {
return map
}
/**
* 读扁平的 tag-UVarInt map. 重复的 tag 将只保留最后一个
*
* tag: UByte
* value: UVarint
*/
@Suppress("DuplicatedCode")
fun Input.readFlatTUVarIntMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap<UInt, UInt> {
val map = mutableMapOf<UInt, UInt>()
var type: UShort = 0u
while (inline {
try {
type = when (tagSize) {
1 -> readUByte().toUShort()
2 -> readUShort()
else -> error("Unsupported tag size: $tagSize")
}
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
type
}.toUByte() != UByte.MAX_VALUE) {
if (map.containsKey(type.toUInt())) {
map[type.toUInt()] = this.readUVarInt()
} else {
map[type.toUInt()] = this.readUVarInt()
}
}
return map
}
fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) =
debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys {
when (keyLength) {
1 -> it.key.toUByte().contentToString()
2 -> it.key.toUShort().contentToString()
4 -> it.key.toUInt().contentToString()
else -> illegalArgument("Expecting 1, 2 or 4 for keyLength")
}
}.entries.joinToString(prefix = "{", postfix = "}", separator = "\n"))
internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported")
internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed")
@JvmName("printTLVStringMap")
fun Map<Int, String>.printTLVMap(name: String = "") =
debugPrintln("TLVMap $name= " + this.mapKeys { it.key.toInt().toUShort().toUHexString() })
fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset)
fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset)
inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
@JvmSynthetic
fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)
@JvmSynthetic
fun Input.readStringUntil(stopSignalExclude: UByte, expectingEOF: Boolean = false): String = readStringUntil(stopSignalExclude.toByte(), expectingEOF)
@JvmName("readStringUntil0")
fun Input.readStringUntil(stopSignalExclude: Byte, expectingEOF: Boolean = false): String {
ByteArrayPool.useInstance {
var count = 0
val buffer = byteArrayOf(1)
while (readAvailable(buffer, 1) == 1) {
if (buffer[0] == stopSignalExclude) {
return buffer.encodeToString()
}
it[count++] = buffer[0]
}
if (!expectingEOF) {
throw EOFException("Early EOF")
}
return buffer.encodeToString()
}
}
private const val TRUE_BYTE_VALUE: Byte = 1
fun Input.readBoolean(): Boolean = this.readByte() == TRUE_BYTE_VALUE
fun Input.readLVNumber(): Number {
return when (this.readShort().toInt()) {
1 -> this.readByte()
2 -> this.readShort()
4 -> this.readInt()
8 -> this.readLong()
else -> throw UnsupportedOperationException()
}
}
inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset)

View File

@ -1,27 +1,14 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils.io
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.coerceAtMostOrFail
import net.mamoe.mirai.utils.cryptor.encryptBy
import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.mirai.utils.deviceName
import kotlin.random.Random
import kotlin.random.nextInt
fun BytePacketBuilder.writeZero(count: Int) {
require(count != 0) { "Trying to write zero with count 0, you made a mistake?" }
require(count > 0) { "writeZero: count must > 0" }
repeat(count) { this.writeByte(0) }
}
fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) // same bit rep.
fun BytePacketBuilder.writeGroup(groupId: Long) = this.writeUInt(groupId.toUInt())
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLength: Int) {
if (array.size <= maxLength) {
@ -35,7 +22,7 @@ fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLe
}
}
fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int {
inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int {
this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray)
return byteArray.size
@ -59,18 +46,7 @@ inline fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset
return length.toInt()
}
inline fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long) = {it}, builder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(builder).build().use {
if (tag != null) writeUByte(tag)
writeUVarInt(lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFL))
writePacket(it)
}
fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray())
fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray())
fun BytePacketBuilder.writeTime() = this.writeInt(currentTimeMillis.toInt())
inline fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray())
fun BytePacketBuilder.writeHex(uHex: String) {
uHex.split(" ").forEach {
@ -79,76 +55,8 @@ fun BytePacketBuilder.writeHex(uHex: String) {
}
}
}
fun BytePacketBuilder.writeTLV(tag: UByte, values: UByteArray) {
writeUByte(tag)
writeUVarInt(values.size.toUInt())
writeFully(values)
}
fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) {
writeUByte(tag)
writeUVarInt(values.size.toUInt())
writeFully(values)
}
fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) {
this.writeUByte(tag)
this.writeFully(uHex.hexToUBytes())
}
fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue)
fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) {
writeUByte(tag)
writeUByte(value)
}
fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) {
this.writeUByte(tag)
this.writeUByte(value)
}
fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) {
this.writeUByte(tag)
this.writeUVarInt(value)
}
fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) {
this.writeUByte(tag)
this.writeFully(value)
}
fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) {
this.writeUByte(tag)
this.writeFully(value)
}
/**
* 会使用 [ByteArrayPool] 缓存
*/
inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) =
BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) }
inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance {
key.readFully(it, 0, key.readRemaining)
encryptAndWrite(it, encoder)
}
inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
@Tested
fun BytePacketBuilder.writeDeviceName(random: Boolean) {
val deviceName: String = if (random) {
"DESKTOP-" + String(ByteArray(7) {
(if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt())
else Random.nextInt('1'.toInt()..'9'.toInt())).toByte()
})
} else {
deviceName
}
this.writeShort((deviceName.length + 2).toShort())
this.writeShort(deviceName.length.toShort())
this.writeStringUtf8(deviceName)
}
BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) }

View File

@ -16,8 +16,8 @@ import kotlin.jvm.JvmSynthetic
*
* Source project: [Nukkit](http://github.com/nukkit/nukkit)
*
* @author MagicDroidX of Nukkit Project
* @author lmlstarqaq of Nukkit Project
* @author MagicDroidX from Nukkit Project
* @author lmlstarqaq from Nukkit Project
*/
internal fun encodeZigZag32(signedInt: Int): Long {
@ -42,10 +42,6 @@ internal fun decodeZigZag64(signedLong: Long): Long {
}
fun Input.readVarInt(): Int {
return decodeZigZag32(this.readUVarInt())
}
inline class UVarInt(
val data: UInt
)

View File

@ -1,9 +1,17 @@
@file:Suppress("NOTHING_TO_INLINE")
@file:JvmMultifileClass
@file:JvmName("Utils")
package net.mamoe.mirai.utils
fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
fun <K, V> Map<K, V>.firstValueOrNull(): V? = this.entries.firstOrNull()?.value
inline fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
fun <K, V> Map<K, V>.firstKey(): K = this.entries.first().key
inline fun <K, V> Map<K, V>.firstValueOrNull(): V? = this.entries.firstOrNull()?.value
fun <K, V> Map<K, V>.firstKeyOrNull(): K? = this.entries.firstOrNull()?.key
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

@ -28,7 +28,7 @@ import kotlin.coroutines.CoroutineContext
actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
class DefaultLoginSolver : LoginSolver() {
internal class DefaultLoginSolver : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? = loginSolverLock.withLock {
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
withContext(Dispatchers.IO) {

View File

@ -1,11 +1,13 @@
package net.mamoe.mirai.utils.cryptor
import net.mamoe.mirai.utils.MiraiDebugAPI
import java.lang.reflect.Field
import kotlin.reflect.full.allSuperclasses
val FIELD_TRY_SET_ACCESSIBLE = Field::class.java.declaredMethods.firstOrNull { it.name == "trySetAccessible" }
@MiraiDebugAPI
actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String {
val newPrefix = prefix + ProtoMap.indent
return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" +

View File

@ -16,7 +16,6 @@ import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.qqandroid.QQAndroid
import net.mamoe.mirai.utils.LoginFailedException
import net.mamoe.mirai.utils.LoginSolver
import java.lang.ref.WeakReference
@ -67,7 +66,7 @@ class MiraiService : Service() {
try {
login()
mCallback?.get()?.onSuccess()
} catch (e: LoginFailedException) {
} catch (e: Exception) {
mCallback?.get()?.onFailed()
}
}

View File

@ -77,10 +77,7 @@ public interface BlockingBot {
* 登录.
* <p>
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login]
*
* @throws net.mamoe.mirai.utils.LoginFailedException
*/
@SuppressWarnings("JavaDoc")
void login();
// endregion