Commit 35e9138c authored by ryoii's avatar ryoii

Merge remote-tracking branch 'origin'

parents 2b60130b 8ea7027f
...@@ -2,11 +2,34 @@ ...@@ -2,11 +2,34 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.15.0` 2020/2/14
### mirai-core
- 新增事件: `BotReloginEvent``BotOfflineEvent.Dropped`
- `AtAll` 现在实现 `Message.Key`
- 新增 `BotConfiguration` DSL, 支持自动将设备信息存储在文件系统等
- 新增 `MessageSource.quote(Member)`
- 更好的网络层连接逻辑
- 密码错误后不再重试登录
- 掉线后尝试快速重连, 失败则普通重连 (#47)
- 有原因的登录失败时将抛出特定异常: `LoginFailedException`
- 默认心跳时间调整为 60s
### mirai-core-qqandroid
- 解决一些验证码无法识别的问题
- 忽略一些不需要处理的事件(机器人主动操作触发的事件)
## `0.14.0` 2020/2/13 ## `0.14.0` 2020/2/13
### mirai-core ### mirai-core
- **支持 at 全体成员: `AtAll`** - **支持 at 全体成员: `AtAll`**
### mirai-core-qqandroid ### mirai-core-qqandroid
- **支持 `AtAll` 的发送和解析** - **支持 `AtAll` 的发送和解析**
- **修复某些情况下禁言处理异常** - **修复某些情况下禁言处理异常**
......
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.14.0 mirai_version=0.15.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
......
...@@ -67,11 +67,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -67,11 +67,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private val packetReceiveLock: Mutex = Mutex() private val packetReceiveLock: Mutex = Mutex()
private fun startPacketReceiverJobOrGet(): Job { private fun startPacketReceiverJobOrKill(cancelCause: CancellationException? = null): Job {
val job = _packetReceiverJob _packetReceiverJob?.cancel(cancelCause)
if (job != null && job.isActive && channel.isOpen) {
return job
}
return this.launch(CoroutineName("Incoming Packet Receiver")) { return this.launch(CoroutineName("Incoming Packet Receiver")) {
while (channel.isOpen) { while (channel.isOpen) {
...@@ -103,8 +100,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -103,8 +100,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
channel = PlatformSocket() channel = PlatformSocket()
// TODO: 2020/2/14 连接多个服务器 // TODO: 2020/2/14 连接多个服务器
channel.connect("113.96.13.208", 8080) withTimeoutOrNull(3000) {
startPacketReceiverJobOrGet() channel.connect("113.96.13.208", 8080)
} ?: error("timeout connecting server")
startPacketReceiverJobOrKill(CancellationException("reconnect"))
// logger.info("Trying login") // logger.info("Trying login")
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect() var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
...@@ -532,5 +531,5 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -532,5 +531,5 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
super.close(cause) super.close(cause)
} }
override suspend fun awaitDisconnection() = _packetReceiverJob?.join() ?: Unit override suspend fun awaitDisconnection() = supervisor.join()
} }
\ No newline at end of file
...@@ -35,6 +35,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory ...@@ -35,6 +35,7 @@ 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.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.readString
...@@ -250,7 +251,7 @@ internal class OnlinePush { ...@@ -250,7 +251,7 @@ internal class OnlinePush {
) )
} }
else -> { else -> {
bot.network.logger.debug{"Unknown server messages $message"} bot.network.logger.debug { "Unknown server messages $message" }
return NoPacket return NoPacket
} }
} }
......
...@@ -124,7 +124,7 @@ fun ByteReadPacket.decodeMultiClientToServerPackets() { ...@@ -124,7 +124,7 @@ fun ByteReadPacket.decodeMultiClientToServerPackets() {
} }
fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) = fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) =
debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys { DebugLogger.debug("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys {
when (keyLength) { when (keyLength) {
1 -> it.key.toUByte().contentToString() 1 -> it.key.toUByte().contentToString()
2 -> it.key.toUShort().contentToString() 2 -> it.key.toUShort().contentToString()
......
/*
* 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 kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* 在各平台实现的默认的验证码处理器.
*/
actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): 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")
}
}
@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 = 60.secondsToMillis
/**
* 最多尝试多少次重连
*/
actual var reconnectionRetryTimes: Int = 3
/**
* 验证码处理器
*/
actual var loginSolver: LoginSolver = defaultLoginSolver
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
}
\ No newline at end of file
...@@ -16,40 +16,40 @@ import android.util.Log ...@@ -16,40 +16,40 @@ import android.util.Log
* 不应该直接构造这个类的实例. 需使用 [DefaultLogger] * 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
*/ */
actual open class PlatformLogger actual constructor(override val identity: String?) : MiraiLoggerPlatformBase() { actual open class PlatformLogger actual constructor(override val identity: String?) : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) { override fun verbose0(message: String?) {
Log.v(identity, any?.toString() ?: "") Log.v(identity, message ?: "")
} }
override fun verbose0(message: String?, e: Throwable?) { override fun verbose0(message: String?, e: Throwable?) {
Log.v(identity, message ?: "", e) Log.v(identity, message ?: "", e)
} }
override fun debug0(any: Any?) { override fun debug0(message: String?) {
Log.d(identity, any?.toString() ?: "") Log.d(identity, message ?: "")
} }
override fun debug0(message: String?, e: Throwable?) { override fun debug0(message: String?, e: Throwable?) {
Log.d(identity, message ?: "", e) Log.d(identity, message ?: "", e)
} }
override fun info0(any: Any?) { override fun info0(message: String?) {
Log.i(identity, any?.toString() ?: "") Log.i(identity, message ?: "")
} }
override fun info0(message: String?, e: Throwable?) { override fun info0(message: String?, e: Throwable?) {
Log.i(identity, message ?: "", e) Log.i(identity, message ?: "", e)
} }
override fun warning0(any: Any?) { override fun warning0(message: String?) {
Log.w(identity, any?.toString() ?: "") Log.w(identity, message ?: "")
} }
override fun warning0(message: String?, e: Throwable?) { override fun warning0(message: String?, e: Throwable?) {
Log.w(identity, message ?: "", e) Log.w(identity, message ?: "", e)
} }
override fun error0(any: Any?) { override fun error0(message: String?) {
Log.e(identity, any?.toString() ?: "") Log.e(identity, message ?: "")
} }
override fun error0(message: String?, e: Throwable?) { override fun error0(message: String?, e: Throwable?) {
......
...@@ -14,13 +14,40 @@ import android.net.wifi.WifiManager ...@@ -14,13 +14,40 @@ import android.net.wifi.WifiManager
import android.os.Build import android.os.Build
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import java.io.File import java.io.File
/**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/
@UseExperimental(UnstableDefault::class)
fun File.loadAsDeviceInfo(context: Context): DeviceInfo {
if (!this.exists() || this.length() == 0L) {
return SystemDeviceInfo(context).also {
this.writeText(Json.plain.stringify(SystemDeviceInfo.serializer(), it))
}
}
return Json.nonstrict.parse(DeviceInfoData.serializer(), this.readText()).also {
it.context = context
}
}
/** /**
* 部分引用指向 [Build]. * 部分引用指向 [Build].
* 部分需要权限, 若无权限则会使用默认值. * 部分需要权限, 若无权限则会使用默认值.
*/ */
actual open class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) { @Serializable
actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
actual constructor(context: Context) : this() {
this.context = context
}
@Transient
final override lateinit var context: Context
override val display: ByteArray get() = Build.DISPLAY.toByteArray() override val display: ByteArray get() = Build.DISPLAY.toByteArray()
override val product: ByteArray get() = Build.PRODUCT.toByteArray() override val product: ByteArray get() = Build.PRODUCT.toByteArray()
override val device: ByteArray get() = Build.DEVICE.toByteArray() override val device: ByteArray get() = Build.DEVICE.toByteArray()
...@@ -88,7 +115,8 @@ actual open class SystemDeviceInfo actual constructor(context: Context) : Device ...@@ -88,7 +115,8 @@ actual open class SystemDeviceInfo actual constructor(context: Context) : Device
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()
object Version : DeviceInfo.Version { @Serializable
actual object Version : DeviceInfo.Version {
override val incremental: ByteArray get() = Build.VERSION.INCREMENTAL.toByteArray() override val incremental: ByteArray get() = Build.VERSION.INCREMENTAL.toByteArray()
override val release: ByteArray get() = Build.VERSION.RELEASE.toByteArray() override val release: ByteArray get() = Build.VERSION.RELEASE.toByteArray()
override val codename: ByteArray get() = Build.VERSION.CODENAME.toByteArray() override val codename: ByteArray get() = Build.VERSION.CODENAME.toByteArray()
......
/*
* 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 kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
/**
* 在各平台实现的默认的验证码处理器.
*/
actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
error("should be implemented manually by you")
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
error("should be implemented manually by you")
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
error("should be implemented manually by you")
}
}
\ No newline at end of file
...@@ -14,6 +14,8 @@ package net.mamoe.mirai.message.data ...@@ -14,6 +14,8 @@ package net.mamoe.mirai.message.data
* 消息源只用于 [QuoteReply] * 消息源只用于 [QuoteReply]
* *
* `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg` * `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg`
*
* @see MessageSource.quote 引用这条消息, 创建 [MessageChain]
*/ */
interface MessageSource : Message { interface MessageSource : Message {
companion object : Message.Key<MessageSource> companion object : Message.Key<MessageSource>
......
...@@ -33,4 +33,13 @@ fun MessageChain.quote(sender: Member): MessageChain { ...@@ -33,4 +33,13 @@ fun MessageChain.quote(sender: Member): MessageChain {
return QuoteReply(it) + sender.at() + " " // required return QuoteReply(it) + sender.at() + " " // required
} }
error("cannot find MessageSource") error("cannot find MessageSource")
}
/**
* 引用这条消息.
* 返回 `[QuoteReply] + [At] + [PlainText]`(必要的结构)
*/
fun MessageSource.quote(sender: Member): MessageChain {
@UseExperimental(MiraiInternalAPI::class)
return QuoteReply(this) + sender.at() + " " // required
} }
\ No newline at end of file
...@@ -13,7 +13,6 @@ import kotlinx.io.core.IoBuffer ...@@ -13,7 +13,6 @@ import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
/** /**
...@@ -33,58 +32,74 @@ abstract class LoginSolver { ...@@ -33,58 +32,74 @@ abstract class LoginSolver {
expect var defaultLoginSolver: LoginSolver expect var defaultLoginSolver: LoginSolver
/** /**
* 网络和连接配置 * [Bot] 配置
*/ */
class BotConfiguration { @Suppress("PropertyName")
expect open class BotConfiguration() {
/** /**
* 日志记录器 * 日志记录器
*/ */
var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") } var botLoggerSupplier: ((Bot) -> MiraiLogger)
/** /**
* 网络层日志构造器 * 网络层日志构造器
*/ */
var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") } var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger)
/** /**
* 设备信息覆盖 * 设备信息覆盖. 默认使用随机的设备信息.
*/ */
var deviceInfo: ((Context) -> DeviceInfo)? = null var deviceInfo: ((Context) -> DeviceInfo)?
/** /**
* 父 [CoroutineContext] * 父 [CoroutineContext]
*/ */
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext var parentCoroutineContext: CoroutineContext
/** /**
* 心跳周期. 过长会导致被服务器断开连接. * 心跳周期. 过长会导致被服务器断开连接.
*/ */
var heartbeatPeriodMillis: Long = 60.secondsToMillis var heartbeatPeriodMillis: Long
/** /**
* 每次心跳时等待结果的时间. * 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 5s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/ */
var heartbeatTimeoutMillis: Long = 2.secondsToMillis var heartbeatTimeoutMillis: Long
/** /**
* 心跳失败后的第一次重连前的等待时间. * 心跳失败后的第一次重连前的等待时间.
*/ */
var firstReconnectDelayMillis: Long = 5.secondsToMillis var firstReconnectDelayMillis: Long
/** /**
* 重连失败后, 继续尝试的每次等待时间 * 重连失败后, 继续尝试的每次等待时间
*/ */
var reconnectPeriodMillis: Long = 60.secondsToMillis var reconnectPeriodMillis: Long
/** /**
* 最多尝试多少次重连 * 最多尝试多少次重连
*/ */
var reconnectionRetryTimes: Int = 3 var reconnectionRetryTimes: Int
/** /**
* 验证码处理器 * 验证码处理器
*/ */
var loginSolver: LoginSolver = defaultLoginSolver var loginSolver: LoginSolver
companion object { companion object {
/** /**
* 默认的配置实例 * 默认的配置实例
*/ */
@JvmStatic @JvmStatic
val Default = BotConfiguration() val Default: BotConfiguration
} }
}
\ No newline at end of file operator fun _NoNetworkLog.unaryPlus()
/**
* 不记录网络层的 log.
* 网络层 log 包含包接收, 包发送, 和一些调试用的记录.
*/
@BotConfigurationDsl
val NoNetworkLog: _NoNetworkLog
@Suppress("ClassName")
object _NoNetworkLog
}
@DslMarker
annotation class BotConfigurationDsl
\ No newline at end of file
...@@ -11,16 +11,15 @@ package net.mamoe.mirai.utils ...@@ -11,16 +11,15 @@ package net.mamoe.mirai.utils
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.utils.cryptor.contentToString
/** /**
* 设备信息. 可通过继承 [SystemDeviceInfo] 来在默认的基础上修改 * 设备信息. 可通过继承 [SystemDeviceInfo] 来在默认的基础上修改
*/ */
abstract class DeviceInfo internal constructor( abstract class DeviceInfo {
context: Context @Transient
) { abstract val context: Context
val context: Context by context.unsafeWeakRef()
abstract val display: ByteArray abstract val display: ByteArray
abstract val product: ByteArray abstract val product: ByteArray
...@@ -95,6 +94,45 @@ abstract class DeviceInfo internal constructor( ...@@ -95,6 +94,45 @@ abstract class DeviceInfo internal constructor(
} }
} }
@Serializable
class DeviceInfoData(
override val display: ByteArray,
override val product: ByteArray,
override val device: ByteArray,
override val board: ByteArray,
override val brand: ByteArray,
override val model: ByteArray,
override val bootloader: ByteArray,
override val fingerprint: ByteArray,
override val bootId: ByteArray,
override val procVersion: ByteArray,
override val baseBand: ByteArray,
override val version: VersionData,
override val simInfo: ByteArray,
override val osType: ByteArray,
override val macAddress: ByteArray,
override val wifiBSSID: ByteArray?,
override val wifiSSID: ByteArray?,
override val imsiMd5: ByteArray,
override val imei: String,
override val apn: ByteArray
) : DeviceInfo() {
@Transient
override lateinit var context: Context
@UseExperimental(ExperimentalUnsignedTypes::class)
override val ipAddress: ByteArray
get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf()
override val androidId: ByteArray get() = display
@Serializable
class VersionData(
override val incremental: ByteArray = SystemDeviceInfo.Version.incremental,
override val release: ByteArray = SystemDeviceInfo.Version.release,
override val codename: ByteArray = SystemDeviceInfo.Version.codename,
override val sdk: Int = SystemDeviceInfo.Version.sdk
) : Version
}
/** /**
* Defaults "%4;7t>;28<fc.5*6".toByteArray() * Defaults "%4;7t>;28<fc.5*6".toByteArray()
*/ */
......
...@@ -9,13 +9,15 @@ ...@@ -9,13 +9,15 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.DeviceInfo
/** /**
* 通过本机信息来获取设备信息. * 通过本机信息来获取设备信息.
* *
* Android: 获取手机信息, 与 QQ 官方相同. * Android: 获取手机信息, 与 QQ 官方相同.
* JVM: 部分为常量, 部分为随机 * JVM: 部分为常量, 部分为随机
*/ */
open expect class SystemDeviceInfo(context: Context) : DeviceInfo expect open class SystemDeviceInfo : DeviceInfo {
\ No newline at end of file constructor()
constructor(context: Context)
object Version : DeviceInfo.Version
}
\ No newline at end of file
...@@ -15,11 +15,13 @@ package net.mamoe.mirai.utils.io ...@@ -15,11 +15,13 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
import net.mamoe.mirai.utils.withSwitch
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.js.JsName
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -30,9 +32,6 @@ val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwit ...@@ -30,9 +32,6 @@ val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwit
@MiraiDebugAPI("Unstable") @MiraiDebugAPI("Unstable")
inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
@MiraiDebugAPI("Low efficiency.")
inline fun debugPrintln(any: Any?) = DebugLogger.debug(any)
@MiraiDebugAPI("Low efficiency.") @MiraiDebugAPI("Low efficiency.")
inline fun String.debugPrintThis(name: String): String { inline fun String.debugPrintThis(name: String): String {
DebugLogger.debug("$name=$this") DebugLogger.debug("$name=$this")
......
...@@ -22,12 +22,14 @@ import kotlinx.coroutines.withContext ...@@ -22,12 +22,14 @@ import kotlinx.coroutines.withContext
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler
import java.awt.Image import java.awt.Image
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import java.io.RandomAccessFile import java.io.RandomAccessFile
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/** /**
* 平台默认的验证码识别器. * 平台默认的验证码识别器.
...@@ -157,3 +159,98 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub ...@@ -157,3 +159,98 @@ 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 = 60.secondsToMillis
/**
* 最多尝试多少次重连
*/
actual var reconnectionRetryTimes: Int = 3
/**
* 验证码处理器
*/
actual var loginSolver: LoginSolver = defaultLoginSolver
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
}
\ No newline at end of file
...@@ -18,37 +18,37 @@ import java.util.* ...@@ -18,37 +18,37 @@ import java.util.*
actual open class PlatformLogger @JvmOverloads internal actual constructor( actual open class PlatformLogger @JvmOverloads internal actual constructor(
override val identity: String? override val identity: String?
) : MiraiLoggerPlatformBase() { ) : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) = println(any, LoggerTextFormat.RESET) override fun verbose0(message: String?) = println(message, LoggerTextFormat.RESET)
override fun verbose0(message: String?, e: Throwable?) { override fun verbose0(message: String?, e: Throwable?) {
if (message != null) verbose(message.toString()) if (message != null) verbose(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun info0(any: Any?) = println(any, LoggerTextFormat.LIGHT_GREEN) override fun info0(message: String?) = println(message, LoggerTextFormat.LIGHT_GREEN)
override fun info0(message: String?, e: Throwable?) { override fun info0(message: String?, e: Throwable?) {
if (message != null) info(message.toString()) if (message != null) info(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun warning0(any: Any?) = println(any, LoggerTextFormat.LIGHT_RED) override fun warning0(message: String?) = println(message, LoggerTextFormat.LIGHT_RED)
override fun warning0(message: String?, e: Throwable?) { override fun warning0(message: String?, e: Throwable?) {
if (message != null) warning(message.toString()) if (message != null) warning(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun error0(any: Any?) = println(any, LoggerTextFormat.RED) override fun error0(message: String?) = println(message, LoggerTextFormat.RED)
override fun error0(message: String?, e: Throwable?) { override fun error0(message: String?, e: Throwable?) {
if (message != null) error(message.toString()) if (message != null) error(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
override fun debug0(any: Any?) = println(any, LoggerTextFormat.LIGHT_CYAN) override fun debug0(message: String?) = println(message, LoggerTextFormat.LIGHT_CYAN)
override fun debug0(message: String?, e: Throwable?) { override fun debug0(message: String?, e: Throwable?) {
if (message != null) debug(message.toString()) if (message != null) debug(message.toString())
e?.printStackTrace() e?.printStackTrace()
} }
private fun println(value: Any?, color: LoggerTextFormat) { private fun println(value: String?, color: LoggerTextFormat) {
val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date()) val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date())
if (identity == null) { if (identity == null) {
...@@ -62,6 +62,7 @@ actual open class PlatformLogger @JvmOverloads internal actual constructor( ...@@ -62,6 +62,7 @@ actual open class PlatformLogger @JvmOverloads internal actual constructor(
/** /**
* @author NaturalHG * @author NaturalHG
*/ */
@Suppress("unused")
internal enum class LoggerTextFormat(private val format: String) { internal enum class LoggerTextFormat(private val format: String) {
RESET("\u001b[0m"), RESET("\u001b[0m"),
......
...@@ -10,38 +10,68 @@ ...@@ -10,38 +10,68 @@
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import net.mamoe.mirai.utils.io.getRandomByteArray import net.mamoe.mirai.utils.io.getRandomByteArray
import net.mamoe.mirai.utils.io.getRandomString import net.mamoe.mirai.utils.io.getRandomString
import java.io.File
/**
* 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.
*/
@UseExperimental(UnstableDefault::class)
fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo {
if (!this.exists() || this.length() == 0L) {
return SystemDeviceInfo(context).also {
this.writeText(Json.plain.stringify(SystemDeviceInfo.serializer(), it))
}
}
return Json.nonstrict.parse(DeviceInfoData.serializer(), this.readText()).also {
it.context = context
}
}
@Serializable
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
actual open class SystemDeviceInfo actual constructor(context: Context) : DeviceInfo(context) { actual open class SystemDeviceInfo actual constructor() : DeviceInfo() {
override val display: ByteArray get() = "MIRAI.200122.001".toByteArray() actual constructor(context: Context) : this() {
override val product: ByteArray get() = "mirai".toByteArray() this.context = context
override val device: ByteArray get() = "mirai".toByteArray() }
override val board: ByteArray get() = "mirai".toByteArray()
override val brand: ByteArray get() = "mamoe".toByteArray() @Transient
override val model: ByteArray get() = "mirai".toByteArray() final override lateinit var context: Context
override val bootloader: ByteArray get() = "unknown".toByteArray()
override val fingerprint: ByteArray get() = "mamoe/mirai/mirai:10/MIRAI.200122.001/5891938:user/release-keys".toByteArray() override val display: ByteArray = "MIRAI.200122.001".toByteArray()
override val product: ByteArray = "mirai".toByteArray()
override val device: ByteArray = "mirai".toByteArray()
override val board: ByteArray = "mirai".toByteArray()
override val brand: ByteArray = "mamoe".toByteArray()
override val model: ByteArray = "mirai".toByteArray()
override val bootloader: ByteArray = "unknown".toByteArray()
override val fingerprint: ByteArray = "mamoe/mirai/mirai:10/MIRAI.200122.001/${getRandomString(7, '0'..'9')}:user/release-keys".toByteArray()
override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray() override val bootId: ByteArray = ExternalImage.generateUUID(md5(getRandomByteArray(16))).toByteArray()
override val procVersion: ByteArray get() = "Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com)".toByteArray() override val procVersion: ByteArray = "Linux version 3.0.31-${getRandomString(8)} (android-build@xxx.xxx.xxx.xxx.com)".toByteArray()
override val baseBand: ByteArray get() = byteArrayOf() override val baseBand: ByteArray = byteArrayOf()
override val version: DeviceInfo.Version get() = Version override val version: Version = Version
override val simInfo: ByteArray get() = "T-Mobile".toByteArray() override val simInfo: ByteArray = "T-Mobile".toByteArray()
override val osType: ByteArray get() = "android".toByteArray() override val osType: ByteArray = "android".toByteArray()
override val macAddress: ByteArray get() = "02:00:00:00:00:00".toByteArray() override val macAddress: ByteArray = "02:00:00:00:00:00".toByteArray()
override val wifiBSSID: ByteArray? get() = "02:00:00:00:00:00".toByteArray() override val wifiBSSID: ByteArray? = "02:00:00:00:00:00".toByteArray()
override val wifiSSID: ByteArray? get() = "<unknown ssid>".toByteArray() override val wifiSSID: ByteArray? = "<unknown ssid>".toByteArray()
override val imsiMd5: ByteArray get() = md5(getRandomByteArray(16)) override val imsiMd5: ByteArray = md5(getRandomByteArray(16))
override val imei: String get() = 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 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 get() = "wifi".toByteArray() override val apn: ByteArray = "wifi".toByteArray()
object Version : DeviceInfo.Version { @Serializable
override val incremental: ByteArray get() = "5891938".toByteArray() actual object Version : DeviceInfo.Version {
override val release: ByteArray get() = "10".toByteArray() override val incremental: ByteArray = "5891938".toByteArray()
override val codename: ByteArray get() = "REL".toByteArray() override val release: ByteArray = "10".toByteArray()
override val sdk: Int get() = 29 override val codename: ByteArray = "REL".toByteArray()
override val sdk: Int = 29
} }
} }
\ No newline at end of file
...@@ -27,6 +27,7 @@ import net.mamoe.mirai.message.data.firstOrNull ...@@ -27,6 +27,7 @@ import net.mamoe.mirai.message.data.firstOrNull
import net.mamoe.mirai.message.sendAsImageTo import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.qqandroid.Bot import net.mamoe.mirai.qqandroid.Bot
import net.mamoe.mirai.qqandroid.QQAndroid import net.mamoe.mirai.qqandroid.QQAndroid
import net.mamoe.mirai.utils.FileBasedDeviceInfo
import java.io.File import java.io.File
private fun readTestAccount(): BotAccount? { private fun readTestAccount(): BotAccount? {
...@@ -51,7 +52,7 @@ suspend fun main() { ...@@ -51,7 +52,7 @@ suspend fun main() {
"123456" "123456"
) { ) {
// 覆盖默认的配置 // 覆盖默认的配置
+FileBasedDeviceInfo // 使用 "device.json" 保存设备信息
// networkLoggerSupplier = { SilentLogger } // 禁用网络层输出 // networkLoggerSupplier = { SilentLogger } // 禁用网络层输出
}.alsoLogin() }.alsoLogin()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment