Переглянути джерело

[core] Commonize BotConfiguration, simplify MPP hierarchy: (#2583)

* [core] Commonize BotConfiguration, simplify MPP hierarchy:

Move platform specific API to `AbstractBotConfiguration`

* Updates docs for AbstractBotConfiguration

* Add @since for AbstractBotConfiguration

* Mark AbstractBotConfiguration as not stable for inheritance
Him188 3 роки тому
батько
коміт
b9ee2a8931

+ 36 - 26
mirai-core-api/compatibility-validation/android/api/android.api

@@ -5425,6 +5425,41 @@ public final class net/mamoe/mirai/network/UnsupportedSmsLoginException : net/ma
 public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException {
 }
 
+public abstract class net/mamoe/mirai/utils/AbstractBotConfiguration {
+	public fun <init> ()V
+	public final fun fileBasedDeviceInfo ()V
+	public final fun fileBasedDeviceInfo (Ljava/lang/String;)V
+	public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
+	protected abstract fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
+	public final fun getCacheDir ()Ljava/io/File;
+	protected abstract fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
+	protected abstract fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1;
+	public final fun getWorkingDir ()Ljava/io/File;
+	public final fun redirectBotLogToDirectory ()V
+	public final fun redirectBotLogToDirectory (Ljava/io/File;)V
+	public final fun redirectBotLogToDirectory (Ljava/io/File;J)V
+	public final fun redirectBotLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectBotLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun redirectBotLogToFile ()V
+	public final fun redirectBotLogToFile (Ljava/io/File;)V
+	public final fun redirectBotLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectBotLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun redirectNetworkLogToDirectory ()V
+	public final fun redirectNetworkLogToDirectory (Ljava/io/File;)V
+	public final fun redirectNetworkLogToDirectory (Ljava/io/File;J)V
+	public final fun redirectNetworkLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectNetworkLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun redirectNetworkLogToFile ()V
+	public final fun redirectNetworkLogToFile (Ljava/io/File;)V
+	public final fun redirectNetworkLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	protected abstract fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
+	public final fun setCacheDir (Ljava/io/File;)V
+	protected abstract fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
+	protected abstract fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
+	public final fun setWorkingDir (Ljava/io/File;)V
+}
+
 public abstract class net/mamoe/mirai/utils/AbstractExternalResource : net/mamoe/mirai/utils/ExternalResource {
 	public fun <init> ()V
 	public fun <init> (Ljava/lang/String;)V
@@ -5448,7 +5483,7 @@ public abstract interface class net/mamoe/mirai/utils/AbstractExternalResource$R
 	public abstract fun cleanup ()V
 }
 
-public class net/mamoe/mirai/utils/BotConfiguration {
+public class net/mamoe/mirai/utils/BotConfiguration : net/mamoe/mirai/utils/AbstractBotConfiguration {
 	public static final field Companion Lnet/mamoe/mirai/utils/BotConfiguration$Companion;
 	public fun <init> ()V
 	public final fun autoReconnectOnForceOffline ()V
@@ -5457,12 +5492,8 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final fun disableAccountSecretes ()V
 	public final fun disableContactCache ()V
 	public final fun enableContactCache ()V
-	public final fun fileBasedDeviceInfo ()V
-	public final fun fileBasedDeviceInfo (Ljava/lang/String;)V
-	public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
 	public final fun getAutoReconnectOnForceOffline ()Z
 	public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
-	public final fun getCacheDir ()Ljava/io/File;
 	public final fun getContactListCache ()Lnet/mamoe/mirai/utils/BotConfiguration$ContactListCache;
 	public static final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration;
 	public final fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
@@ -5479,7 +5510,6 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final synthetic fun getReconnectPeriodMillis ()J
 	public final fun getReconnectionRetryTimes ()I
 	public final fun getStatHeartbeatPeriodMillis ()J
-	public final fun getWorkingDir ()Ljava/io/File;
 	public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public final fun isConvertLineSeparator ()Z
 	public final fun isShowingVerboseEventLog ()Z
@@ -5487,27 +5517,8 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final fun noBotLog ()V
 	public final fun noNetworkLog ()V
 	public final fun randomDeviceInfo ()V
-	public final fun redirectBotLogToDirectory ()V
-	public final fun redirectBotLogToDirectory (Ljava/io/File;)V
-	public final fun redirectBotLogToDirectory (Ljava/io/File;J)V
-	public final fun redirectBotLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectBotLogToDirectory$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public final fun redirectBotLogToFile ()V
-	public final fun redirectBotLogToFile (Ljava/io/File;)V
-	public final fun redirectBotLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectBotLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public final fun redirectNetworkLogToDirectory ()V
-	public final fun redirectNetworkLogToDirectory (Ljava/io/File;)V
-	public final fun redirectNetworkLogToDirectory (Ljava/io/File;J)V
-	public final fun redirectNetworkLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectNetworkLogToDirectory$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public final fun redirectNetworkLogToFile ()V
-	public final fun redirectNetworkLogToFile (Ljava/io/File;)V
-	public final fun redirectNetworkLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
 	public final fun setAutoReconnectOnForceOffline (Z)V
 	public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
-	public final fun setCacheDir (Ljava/io/File;)V
 	public final fun setContactListCache (Lnet/mamoe/mirai/utils/BotConfiguration$ContactListCache;)V
 	public final fun setConvertLineSeparator (Z)V
 	public final fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
@@ -5525,7 +5536,6 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final fun setReconnectionRetryTimes (I)V
 	public final fun setShowingVerboseEventLog (Z)V
 	public final fun setStatHeartbeatPeriodMillis (J)V
-	public final fun setWorkingDir (Ljava/io/File;)V
 }
 
 public final class net/mamoe/mirai/utils/BotConfiguration$Companion {

+ 36 - 26
mirai-core-api/compatibility-validation/jvm/api/jvm.api

@@ -5425,6 +5425,41 @@ public final class net/mamoe/mirai/network/UnsupportedSmsLoginException : net/ma
 public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException {
 }
 
+public abstract class net/mamoe/mirai/utils/AbstractBotConfiguration {
+	public fun <init> ()V
+	public final fun fileBasedDeviceInfo ()V
+	public final fun fileBasedDeviceInfo (Ljava/lang/String;)V
+	public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
+	protected abstract fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
+	public final fun getCacheDir ()Ljava/io/File;
+	protected abstract fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
+	protected abstract fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1;
+	public final fun getWorkingDir ()Ljava/io/File;
+	public final fun redirectBotLogToDirectory ()V
+	public final fun redirectBotLogToDirectory (Ljava/io/File;)V
+	public final fun redirectBotLogToDirectory (Ljava/io/File;J)V
+	public final fun redirectBotLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectBotLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun redirectBotLogToFile ()V
+	public final fun redirectBotLogToFile (Ljava/io/File;)V
+	public final fun redirectBotLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectBotLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun redirectNetworkLogToDirectory ()V
+	public final fun redirectNetworkLogToDirectory (Ljava/io/File;)V
+	public final fun redirectNetworkLogToDirectory (Ljava/io/File;J)V
+	public final fun redirectNetworkLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectNetworkLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun redirectNetworkLogToFile ()V
+	public final fun redirectNetworkLogToFile (Ljava/io/File;)V
+	public final fun redirectNetworkLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
+	public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	protected abstract fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
+	public final fun setCacheDir (Ljava/io/File;)V
+	protected abstract fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
+	protected abstract fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
+	public final fun setWorkingDir (Ljava/io/File;)V
+}
+
 public abstract class net/mamoe/mirai/utils/AbstractExternalResource : net/mamoe/mirai/utils/ExternalResource {
 	public fun <init> ()V
 	public fun <init> (Ljava/lang/String;)V
@@ -5448,7 +5483,7 @@ public abstract interface class net/mamoe/mirai/utils/AbstractExternalResource$R
 	public abstract fun cleanup ()V
 }
 
-public class net/mamoe/mirai/utils/BotConfiguration {
+public class net/mamoe/mirai/utils/BotConfiguration : net/mamoe/mirai/utils/AbstractBotConfiguration {
 	public static final field Companion Lnet/mamoe/mirai/utils/BotConfiguration$Companion;
 	public fun <init> ()V
 	public final fun autoReconnectOnForceOffline ()V
@@ -5457,12 +5492,8 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final fun disableAccountSecretes ()V
 	public final fun disableContactCache ()V
 	public final fun enableContactCache ()V
-	public final fun fileBasedDeviceInfo ()V
-	public final fun fileBasedDeviceInfo (Ljava/lang/String;)V
-	public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
 	public final fun getAutoReconnectOnForceOffline ()Z
 	public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
-	public final fun getCacheDir ()Ljava/io/File;
 	public final fun getContactListCache ()Lnet/mamoe/mirai/utils/BotConfiguration$ContactListCache;
 	public static final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration;
 	public final fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
@@ -5479,7 +5510,6 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final synthetic fun getReconnectPeriodMillis ()J
 	public final fun getReconnectionRetryTimes ()I
 	public final fun getStatHeartbeatPeriodMillis ()J
-	public final fun getWorkingDir ()Ljava/io/File;
 	public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 	public final fun isConvertLineSeparator ()Z
 	public final fun isShowingVerboseEventLog ()Z
@@ -5487,27 +5517,8 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final fun noBotLog ()V
 	public final fun noNetworkLog ()V
 	public final fun randomDeviceInfo ()V
-	public final fun redirectBotLogToDirectory ()V
-	public final fun redirectBotLogToDirectory (Ljava/io/File;)V
-	public final fun redirectBotLogToDirectory (Ljava/io/File;J)V
-	public final fun redirectBotLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectBotLogToDirectory$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public final fun redirectBotLogToFile ()V
-	public final fun redirectBotLogToFile (Ljava/io/File;)V
-	public final fun redirectBotLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectBotLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public final fun redirectNetworkLogToDirectory ()V
-	public final fun redirectNetworkLogToDirectory (Ljava/io/File;)V
-	public final fun redirectNetworkLogToDirectory (Ljava/io/File;J)V
-	public final fun redirectNetworkLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectNetworkLogToDirectory$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public final fun redirectNetworkLogToFile ()V
-	public final fun redirectNetworkLogToFile (Ljava/io/File;)V
-	public final fun redirectNetworkLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V
-	public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
 	public final fun setAutoReconnectOnForceOffline (Z)V
 	public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
-	public final fun setCacheDir (Ljava/io/File;)V
 	public final fun setContactListCache (Lnet/mamoe/mirai/utils/BotConfiguration$ContactListCache;)V
 	public final fun setConvertLineSeparator (Z)V
 	public final fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
@@ -5525,7 +5536,6 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public final fun setReconnectionRetryTimes (I)V
 	public final fun setShowingVerboseEventLog (Z)V
 	public final fun setStatHeartbeatPeriodMillis (J)V
-	public final fun setWorkingDir (Ljava/io/File;)V
 }
 
 public final class net/mamoe/mirai/utils/BotConfiguration$Companion {

+ 38 - 0
mirai-core-api/src/commonMain/kotlin/utils/AbstractBotConfiguration.kt

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019-2023 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/dev/LICENSE
+ */
+
+package net.mamoe.mirai.utils
+
+import net.mamoe.mirai.Bot
+import kotlin.jvm.JvmOverloads
+
+
+/**
+ * [BotConfiguration] 的平台特别配置
+ * @since 2.15
+ */
+@NotStableForInheritance
+public expect abstract class AbstractBotConfiguration @MiraiInternalApi protected constructor() {
+    protected abstract var deviceInfo: ((Bot) -> DeviceInfo)?
+    protected abstract var networkLoggerSupplier: ((Bot) -> MiraiLogger)
+    protected abstract var botLoggerSupplier: ((Bot) -> MiraiLogger)
+
+    /**
+     * 使用文件存储设备信息.
+     *
+     * 此函数只在 JVM 和 Android 有效. 在其他平台将会抛出异常.
+     * @param filepath 文件路径. 默认是相对于 `workingDir` 的文件 "device.json".
+     * @see BotConfiguration.deviceInfo
+     */
+    @JvmOverloads
+    @BotConfiguration.ConfigurationDsl
+    public fun fileBasedDeviceInfo(filepath: String = "device.json")
+
+    internal fun applyMppCopy(new: BotConfiguration)
+}

+ 121 - 54
mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt

@@ -17,17 +17,22 @@ package net.mamoe.mirai.utils
 
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.SupervisorJob
+import kotlinx.serialization.json.Json
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.BotFactory
 import net.mamoe.mirai.event.events.BotOfflineEvent
 import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.coroutines.coroutineContext
 import kotlin.jvm.*
 import kotlin.native.CName
 import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 
 /**
- * [Bot] 配置. 用于 [BotFactory.newBot]
+ * [Bot] 配置. 用于 [BotFactory.newBot].
+ *
+ * 部分平台相关配置位于 [AbstractBotConfiguration], 例如 `fileBasedDeviceInfo`.
  *
  * Kotlin 使用方法:
  * ```
@@ -50,13 +55,13 @@ import kotlin.time.Duration
  * ```
  */
 @Suppress("PropertyName")
-public expect open class BotConfiguration() { // open for Java
+public open class BotConfiguration : AbstractBotConfiguration() { // open for Java
     ///////////////////////////////////////////////////////////////////////////
     // Coroutines
     ///////////////////////////////////////////////////////////////////////////
 
     /** 父 [CoroutineContext]. [Bot] 创建后会使用 [SupervisorJob] 覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
-    public var parentCoroutineContext: CoroutineContext
+    public var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
 
     /**
      * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
@@ -112,7 +117,9 @@ public expect open class BotConfiguration() { // open for Java
      */
     @JvmSynthetic
     @ConfigurationDsl
-    public suspend inline fun inheritCoroutineContext()
+    public suspend inline fun inheritCoroutineContext() {
+        parentCoroutineContext = coroutineContext
+    }
 
 
     ///////////////////////////////////////////////////////////////////////////
@@ -120,7 +127,7 @@ public expect open class BotConfiguration() { // open for Java
     ///////////////////////////////////////////////////////////////////////////
 
     /** 连接心跳包周期. 过长会导致被服务器断开连接. */
-    public var heartbeatPeriodMillis: Long
+    public var heartbeatPeriodMillis: Long = 60.secondsToMillis
 
     /**
      * 状态心跳包周期. 过长会导致掉线.
@@ -128,13 +135,13 @@ public expect open class BotConfiguration() { // open for Java
      * @since 2.6
      * @see heartbeatStrategy
      */
-    public var statHeartbeatPeriodMillis: Long
+    public var statHeartbeatPeriodMillis: Long = 300.secondsToMillis
 
     /**
      * 心跳策略.
      * @since 2.6.3
      */
-    public var heartbeatStrategy: HeartbeatStrategy
+    public var heartbeatStrategy: HeartbeatStrategy = HeartbeatStrategy.STAT_HB
 
     /**
      * 心跳策略.
@@ -168,7 +175,7 @@ public expect open class BotConfiguration() { // open for Java
      * 每次心跳时等待结果的时间.
      * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
      */
-    public var heartbeatTimeoutMillis: Long
+    public var heartbeatTimeoutMillis: Long = 5.secondsToMillis
 
     /** 心跳失败后的第一次重连前的等待时间. */
     @Deprecated(
@@ -176,7 +183,7 @@ public expect open class BotConfiguration() { // open for Java
         level = DeprecationLevel.HIDDEN
     ) // deprecated since 2.7, error since 2.8
     @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10")
-    public var firstReconnectDelayMillis: Long
+    public var firstReconnectDelayMillis: Long = 5.secondsToMillis
 
     /** 重连失败后, 继续尝试的每次等待时间 */
     @Deprecated(
@@ -184,10 +191,10 @@ public expect open class BotConfiguration() { // open for Java
         level = DeprecationLevel.HIDDEN
     ) // deprecated since 2.7, error since 2.8
     @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10")
-    public var reconnectPeriodMillis: Long
+    public var reconnectPeriodMillis: Long = 5.secondsToMillis
 
     /** 最多尝试多少次重连 */
-    public var reconnectionRetryTimes: Int
+    public var reconnectionRetryTimes: Int = Int.MAX_VALUE
 
     /**
      * 在被挤下线时 ([BotOfflineEvent.Force]) 自动重连. 默认为 `false`.
@@ -196,7 +203,7 @@ public expect open class BotConfiguration() { // open for Java
      *
      * @since 2.1
      */
-    public var autoReconnectOnForceOffline: Boolean
+    public var autoReconnectOnForceOffline: Boolean = false
 
     /**
      * 验证码处理器
@@ -208,10 +215,10 @@ public expect open class BotConfiguration() { // open for Java
      *
      * @see LoginSolver
      */
-    public var loginSolver: LoginSolver?
+    public var loginSolver: LoginSolver? = LoginSolver.Default
 
     /** 使用协议类型 */
-    public var protocol: MiraiProtocol
+    public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE
 
     public enum class MiraiProtocol {
         /**
@@ -251,25 +258,27 @@ public expect open class BotConfiguration() { // open for Java
      * Highway 通道上传图片, 语音, 文件等资源时的协程数量.
      *
      * 每个协程的速度约为 200KB/s. 协程数量越多越快, 同时也更要求性能.
-     * 默认 [CPU 核心数][Runtime.availableProcessors].
+     * 默认 CPU 核心数.
      *
      * @since 2.2
      */
-    public var highwayUploadCoroutineCount: Int
+    public var highwayUploadCoroutineCount: Int = availableProcessors()
 
     /**
      * 设置 [autoReconnectOnForceOffline] 为 `true`, 即在被挤下线时自动重连.
      * @since 2.1
      */
     @ConfigurationDsl
-    public fun autoReconnectOnForceOffline()
+    public fun autoReconnectOnForceOffline() {
+        autoReconnectOnForceOffline = true
+    }
 
     ///////////////////////////////////////////////////////////////////////////
     // Device
     ///////////////////////////////////////////////////////////////////////////
 
     @JvmField
-    internal var accountSecrets: Boolean
+    internal var accountSecrets: Boolean = true
 
     /**
      * 禁止保存 `account.secrets`.
@@ -279,14 +288,16 @@ public expect open class BotConfiguration() { // open for Java
      *
      * @since 2.11
      */
-    public fun disableAccountSecretes()
+    public fun disableAccountSecretes() {
+        accountSecrets = false
+    }
 
     /**
      * 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息.
      * @see fileBasedDeviceInfo 使用指定文件存储设备信息
      * @see randomDeviceInfo 使用随机设备信息
      */
-    public var deviceInfo: ((Bot) -> DeviceInfo)?
+    public final override var deviceInfo: ((Bot) -> DeviceInfo)? = deviceInfoStub // allows user to set `null` manually.
 
     /**
      * 使用随机设备信息.
@@ -294,7 +305,9 @@ public expect open class BotConfiguration() { // open for Java
      * @see deviceInfo
      */
     @ConfigurationDsl
-    public fun randomDeviceInfo()
+    public fun randomDeviceInfo() {
+        deviceInfo = null
+    }
 
     /**
      * 使用特定由 [DeviceInfo] 序列化产生的 JSON 的设备信息
@@ -302,18 +315,11 @@ public expect open class BotConfiguration() { // open for Java
      * @see deviceInfo
      */
     @ConfigurationDsl
-    public fun loadDeviceInfoJson(json: String)
-
-    /**
-     * 使用文件存储设备信息.
-     *
-     * 此函数只在 JVM 和 Android 有效. 在其他平台将会抛出异常.
-     * @param filepath 文件路径. 默认是相对于 [workingDir] 的文件 "device.json".
-     * @see deviceInfo
-     */
-    @JvmOverloads
-    @ConfigurationDsl
-    public fun fileBasedDeviceInfo(filepath: String = "device.json")
+    public fun loadDeviceInfoJson(json: String) {
+        deviceInfo = {
+            DeviceInfoManager.deserialize(json, Companion.json)
+        }
+    }
 
     ///////////////////////////////////////////////////////////////////////////
     // Logging
@@ -322,33 +328,39 @@ public expect open class BotConfiguration() { // open for Java
     /**
      * 日志记录器
      *
-     * - 默认打印到标准输出, 通过 [MiraiLogger.create]
+     * - 默认打印到标准输出, 通过 [MiraiLogger.Factory.create]
      * - 忽略所有日志: [noBotLog]
      * - 重定向到一个目录: `botLoggerSupplier = { DirectoryLogger("Bot ${it.id}") }`
      * - 重定向到一个文件: `botLoggerSupplier = { SingleFileLogger("Bot ${it.id}") }`
      *
      * @see MiraiLogger
      */
-    public var botLoggerSupplier: ((Bot) -> MiraiLogger)
+    public final override var botLoggerSupplier: ((Bot) -> MiraiLogger) = {
+        MiraiLogger.Factory.create(Bot::class, "Bot ${it.id}")
+    }
 
     /**
      * 网络层日志构造器
      *
-     * - 默认打印到标准输出, 通过 [MiraiLogger.create]
+     * - 默认打印到标准输出, 通过 [MiraiLogger.Factory.create]
      * - 忽略所有日志: [noNetworkLog]
      * - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
      * - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
      *
      * @see MiraiLogger
      */
-    public var networkLoggerSupplier: ((Bot) -> MiraiLogger)
+    public final override var networkLoggerSupplier: ((Bot) -> MiraiLogger) = {
+        MiraiLogger.Factory.create(Bot::class, "Net ${it.id}")
+    }
 
     /**
      * 不显示网络日志. 不推荐.
      * @see networkLoggerSupplier 更多日志处理方式
      */
     @ConfigurationDsl
-    public fun noNetworkLog()
+    public fun noNetworkLog() {
+        networkLoggerSupplier = { _ -> SilentLogger }
+    }
 
 
     /**
@@ -356,7 +368,10 @@ public expect open class BotConfiguration() { // open for Java
      * @see botLoggerSupplier 更多日志处理方式
      */
     @ConfigurationDsl
-    public fun noBotLog()
+    public fun noBotLog() {
+        botLoggerSupplier = { _ -> SilentLogger }
+    }
+
 
     /**
      * 是否显示过于冗长的事件日志
@@ -365,17 +380,17 @@ public expect open class BotConfiguration() { // open for Java
      *
      * @since 2.8
      */
-    public var isShowingVerboseEventLog: Boolean
+    public var isShowingVerboseEventLog: Boolean = false
 
     ///////////////////////////////////////////////////////////////////////////
     // Cache
     //////////////////////////////////////////////////////////////////////////
 
     /**
-     * 联系人信息缓存配置. 将会保存在 [cacheDir] 中 `contacts` 目录
+     * 联系人信息缓存配置. 将会保存在 `cacheDir` 中 `contacts` 目录
      * @since 2.4
      */
-    public var contactListCache: ContactListCache
+    public var contactListCache: ContactListCache = ContactListCache()
 
     /**
      * 联系人信息缓存配置
@@ -388,24 +403,29 @@ public expect open class BotConfiguration() { // open for Java
         /**
          * 在有修改时自动保存间隔. 默认 60 秒. 在每次登录完成后有修改时都会立即保存一次.
          */
-        public var saveIntervalMillis: Long
+        public var saveIntervalMillis: Long = 60_000
 
         /**
          * 在有修改时自动保存间隔. 默认 60 秒. 在每次登录完成后有修改时都会立即保存一次.
          */ // was @ExperimentalTime before 2.9
-        public var saveInterval: Duration
+        public inline var saveInterval: Duration
+            @JvmSynthetic inline get() = saveIntervalMillis.milliseconds
+            @JvmSynthetic inline set(v) {
+                saveIntervalMillis = v.inWholeMilliseconds
+            }
 
         /**
          * 开启好友列表缓存.
          */
-        public var friendListCacheEnabled: Boolean
+        public var friendListCacheEnabled: Boolean = false
 
         /**
          * 开启群成员列表缓存.
          */
-        public var groupMemberListCacheEnabled: Boolean
+        public var groupMemberListCacheEnabled: Boolean = false
     }
 
+
     /**
      * 配置 [ContactListCache]
      * ```
@@ -417,21 +437,31 @@ public expect open class BotConfiguration() { // open for Java
      * @since 2.4
      */
     @JvmSynthetic
-    public inline fun contactListCache(action: ContactListCache.() -> Unit)
+    public inline fun contactListCache(action: ContactListCache.() -> Unit) {
+        action.invoke(this.contactListCache)
+    }
 
     /**
      * 禁用好友列表和群成员列表的缓存.
      * @since 2.4
      */
     @ConfigurationDsl
-    public fun disableContactCache()
+    public fun disableContactCache() {
+        contactListCache.friendListCacheEnabled = false
+        contactListCache.groupMemberListCacheEnabled = false
+    }
+
 
     /**
      * 启用好友列表和群成员列表的缓存.
      * @since 2.4
      */
     @ConfigurationDsl
-    public fun enableContactCache()
+    public fun enableContactCache() {
+        contactListCache.friendListCacheEnabled = true
+        contactListCache.groupMemberListCacheEnabled = true
+    }
+
 
     /**
      * 登录缓存.
@@ -445,14 +475,37 @@ public expect open class BotConfiguration() { // open for Java
      *
      * @since 2.6
      */
-    public var loginCacheEnabled: Boolean
+    public var loginCacheEnabled: Boolean = true
 
     ///////////////////////////////////////////////////////////////////////////
     // Misc
     ///////////////////////////////////////////////////////////////////////////
 
     @Suppress("DuplicatedCode")
-    public fun copy(): BotConfiguration
+    public fun copy(): BotConfiguration {
+        return BotConfiguration().also { new ->
+            // To structural order
+            new.parentCoroutineContext = parentCoroutineContext
+            new.heartbeatPeriodMillis = heartbeatPeriodMillis
+            new.heartbeatTimeoutMillis = heartbeatTimeoutMillis
+            new.statHeartbeatPeriodMillis = statHeartbeatPeriodMillis
+            new.heartbeatStrategy = heartbeatStrategy
+            new.reconnectionRetryTimes = reconnectionRetryTimes
+            new.autoReconnectOnForceOffline = autoReconnectOnForceOffline
+            new.loginSolver = loginSolver
+            new.protocol = protocol
+            new.highwayUploadCoroutineCount = highwayUploadCoroutineCount
+            new.accountSecrets = accountSecrets
+            new.deviceInfo = deviceInfo
+            new.botLoggerSupplier = botLoggerSupplier
+            new.networkLoggerSupplier = networkLoggerSupplier
+            new.contactListCache = contactListCache
+            new.convertLineSeparator = convertLineSeparator
+            new.isShowingVerboseEventLog = isShowingVerboseEventLog
+
+            applyMppCopy(new)
+        }
+    }
 
     /**
      * 是否处理接受到的特殊换行符, 默认为 `true`
@@ -463,17 +516,31 @@ public expect open class BotConfiguration() { // open for Java
      * @since 2.4
      */
     @get:JvmName("isConvertLineSeparator")
-    public var convertLineSeparator: Boolean
+    public var convertLineSeparator: Boolean = true
 
     /** 标注一个配置 DSL 函数 */
     @Target(AnnotationTarget.FUNCTION)
     @DslMarker
-    public annotation class ConfigurationDsl()
+    public annotation class ConfigurationDsl
 
     public companion object {
         /** 默认的配置实例. 可以进行修改 */
         @JvmStatic
-        public val Default: BotConfiguration
+        public val Default: BotConfiguration = BotConfiguration()
+
+        /**
+         * Json 序列化器, 使用 'kotlinx.serialization'
+         */
+        internal val json: Json = kotlin.runCatching {
+            Json {
+                isLenient = true
+                ignoreUnknownKeys = true
+                prettyPrint = true
+            }
+        }.getOrElse {
+            @Suppress("JSON_FORMAT_REDUNDANT_DEFAULT") // compatible for older versions
+            (Json {})
+        }
     }
 }
 

+ 21 - 518
mirai-core-api/src/jvmBaseMain/kotlin/utils/BotConfiguration.kt

@@ -7,324 +7,33 @@
  * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
-@file:Suppress("unused", "DEPRECATION_ERROR", "EXPOSED_SUPER_CLASS", "MemberVisibilityCanBePrivate")
-
-@file:JvmMultifileClass
-@file:JvmName("Utils")
-
-
 package net.mamoe.mirai.utils
 
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.serialization.json.Json
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.BotFactory
-import net.mamoe.mirai.event.events.BotOfflineEvent
 import net.mamoe.mirai.utils.DeviceInfo.Companion.loadAsDeviceInfo
 import java.io.File
 import java.io.InputStream
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.coroutines.coroutineContext
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
 
 /**
- * [Bot] 配置. 用于 [BotFactory.newBot]
- *
- * Kotlin 使用方法:
- * ```
- * val bot = BotFactory.newBot(...) {
- *    // 在这里配置 Bot
- *
- *    bogLoggerSupplier = { bot -> ... }
- *    fileBasedDeviceInfo()
- *    inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
- * }
- * ```
- *
- * Java 使用方法:
- * ```java
- * Bot bot = BotFactory.newBot(..., new BotConfiguration() {{
- *     setBogLoggerSupplier((Bot bot) -> { ... })
- *     fileBasedDeviceInfo()
- *     ...
- * }})
- * ```
+ * [BotConfiguration] 的 JVM 平台特别配置
+ * @since 2.15
  */
-@Suppress("PropertyName")
-public actual open class BotConfiguration { // open for Java
-    /**
-     * 工作目录. 默认为 "."
-     */
-    public var workingDir: File = File(".")
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Coroutines
-    ///////////////////////////////////////////////////////////////////////////
-
-    /** 父 [CoroutineContext]. [Bot] 创建后会使用 [SupervisorJob] 覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
-    public actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
-
-    /**
-     * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
-     *
-     * Bot 将会使用一个 [SupervisorJob] 覆盖 [coroutineContext] 当前协程的 [Job], 并使用当前协程的 [Job] 作为父 [Job]
-     *
-     * 用例:
-     * ```
-     * coroutineScope {
-     *   val bot = Bot(...) {
-     *     inheritCoroutineContext()
-     *   }
-     *   bot.login()
-     * } // coroutineScope 会等待 Bot 退出
-     * ```
-     *
-     *
-     * **注意**: `bot.cancel` 时将会让父 [Job] 也被 cancel.
-     * ```
-     * coroutineScope { // this: CoroutineScope
-     *   launch {
-     *     while(isActive) {
-     *       delay(500)
-     *       println("I'm alive")
-     *     }
-     *   }
-     *
-     *   val bot = Bot(...) {
-     *      inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
-     *   }
-     *   bot.login()
-     *   bot.cancel() // 取消了整个 `coroutineScope`, 因此上文不断打印 `"I'm alive"` 的协程也会被取消.
-     * }
-     * ```
-     *
-     * 因此, 此函数尤为适合在 `suspend fun main()` 中使用, 它能阻止主线程退出:
-     * ```
-     * suspend fun main() {
-     *   val bot = Bot() {
-     *     inheritCoroutineContext()
-     *   }
-     *   bot.eventChannel.subscribe { ... }
-     *
-     *   // 主线程不会退出, 直到 Bot 离线.
-     * }
-     * ```
-     *
-     * 简言之,
-     * - 若想让 [Bot] 作为 '守护进程' 运行, 则无需调用 [inheritCoroutineContext].
-     * - 若想让 [Bot] 依赖于当前协程, 让当前协程等待 [Bot] 运行, 则使用 [inheritCoroutineContext]
-     *
-     * @see parentCoroutineContext
-     */
-    @JvmSynthetic
-    @ConfigurationDsl
-    public actual suspend inline fun inheritCoroutineContext() {
-        parentCoroutineContext = coroutineContext
-    }
-
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Connection
-    ///////////////////////////////////////////////////////////////////////////
-
-    /** 连接心跳包周期. 过长会导致被服务器断开连接. */
-    public actual var heartbeatPeriodMillis: Long = 60.secondsToMillis
-
-    /**
-     * 状态心跳包周期. 过长会导致掉线.
-     * 该值会在登录时根据服务器下发的配置自动进行更新.
-     * @since 2.6
-     * @see heartbeatStrategy
-     */
-    public actual var statHeartbeatPeriodMillis: Long = 300.secondsToMillis
-
-    /**
-     * 心跳策略.
-     * @since 2.6.3
-     */
-    public actual var heartbeatStrategy: HeartbeatStrategy = HeartbeatStrategy.STAT_HB
-
-    /**
-     * 心跳策略.
-     * @since 2.6.3
-     */
-    public actual enum class HeartbeatStrategy {
-        /**
-         * 使用 2.6.0 增加的*状态心跳* (Stat Heartbeat). 通常推荐这个模式.
-         *
-         * 该模式大多数情况下更稳定. 但有些账号使用这个模式时会遇到一段时间后发送消息成功但客户端不可见的问题.
-         */
-        STAT_HB,
-
-        /**
-         * 不发送状态心跳, 而是发送*切换在线状态* (可能会导致频繁的好友或客户端上线提示, 也可能产生短暂 (几秒) 发送消息不可见的问题).
-         *
-         * 建议在 [STAT_HB] 不可用时使用 [REGISTER].
-         */
-        REGISTER,
-
-        /**
-         * 不主动维护会话. 多数账号会每 16 分钟掉线然后重连. 则会有短暂的不可用时间.
-         *
-         * 仅当 [STAT_HB] 和 [REGISTER] 都造成无法接收等问题时使用.
-         * 同时请在 [https://github.com/mamoe/mirai/issues/1209] 提交问题.
-         */
-        NONE;
-    }
+@NotStableForInheritance
+public actual abstract class AbstractBotConfiguration { // open for Java
+    protected actual abstract var deviceInfo: ((Bot) -> DeviceInfo)?
+    protected actual abstract var networkLoggerSupplier: ((Bot) -> MiraiLogger)
+    protected actual abstract var botLoggerSupplier: ((Bot) -> MiraiLogger)
 
-    /**
-     * 每次心跳时等待结果的时间.
-     * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
-     */
-    public actual var heartbeatTimeoutMillis: Long = 5.secondsToMillis
-
-    /** 心跳失败后的第一次重连前的等待时间. */
-    @Deprecated(
-        "Useless since new network. Please just remove this.",
-        level = DeprecationLevel.HIDDEN
-    ) // deprecated since 2.7, error since 2.8
-    @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10")
-    public actual var firstReconnectDelayMillis: Long = 5.secondsToMillis
-
-    /** 重连失败后, 继续尝试的每次等待时间 */
-    @Deprecated(
-        "Useless since new network. Please just remove this.",
-        level = DeprecationLevel.HIDDEN
-    ) // deprecated since 2.7, error since 2.8
-    @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10")
-    public actual var reconnectPeriodMillis: Long = 5.secondsToMillis
-
-    /** 最多尝试多少次重连 */
-    public actual var reconnectionRetryTimes: Int = Int.MAX_VALUE
-
-    /**
-     * 在被挤下线时 ([BotOfflineEvent.Force]) 自动重连. 默认为 `false`.
-     *
-     * 其他情况掉线都默认会自动重连, 详见 [BotOfflineEvent.reconnect]
-     *
-     * @since 2.1
-     */
-    public actual var autoReconnectOnForceOffline: Boolean = false
-
-    /**
-     * 验证码处理器
-     *
-     * - 在 Android 需要手动提供 [LoginSolver]
-     * - 在 JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现
-     *
-     * 详见 [LoginSolver.Default]
-     *
-     * @see LoginSolver
-     */
-    public actual var loginSolver: LoginSolver? = LoginSolver.Default
-
-    /** 使用协议类型 */
-    public actual var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE
-
-    public actual enum class MiraiProtocol {
-        /**
-         * Android 手机. 所有功能都支持.
-         */
-        ANDROID_PHONE,
-
-        /**
-         * Android 平板.
-         */
-        ANDROID_PAD,
-
-        /**
-         * Android 手表.
-         *
-         * 注意: 不支持戳一戳事件解析
-         */
-        ANDROID_WATCH,
-
-        /**
-         * iPad - 来自MiraiGo
-         *
-         * @since 2.8
-         */
-        IPAD,
-
-        /**
-         * MacOS - 来自MiraiGo
-         *
-         * @since 2.8
-         */
-        MACOS,
-
-    }
 
     /**
-     * Highway 通道上传图片, 语音, 文件等资源时的协程数量.
-     *
-     * 每个协程的速度约为 200KB/s. 协程数量越多越快, 同时也更要求性能.
-     * 默认 [CPU 核心数][Runtime.availableProcessors].
-     *
-     * @since 2.2
-     */
-    public actual var highwayUploadCoroutineCount: Int = Runtime.getRuntime().availableProcessors()
-
-    /**
-     * 设置 [autoReconnectOnForceOffline] 为 `true`, 即在被挤下线时自动重连.
-     * @since 2.1
+     * 工作目录. 默认为 "."
      */
-    @ConfigurationDsl
-    public actual fun autoReconnectOnForceOffline() {
-        autoReconnectOnForceOffline = true
-    }
+    public var workingDir: File = File(".")
 
     ///////////////////////////////////////////////////////////////////////////
     // Device
     ///////////////////////////////////////////////////////////////////////////
 
-    @JvmField
-    internal actual var accountSecrets: Boolean = true
-
-    /**
-     * 禁止保存 `account.secrets`.
-     *
-     * `account.secrets` 保存账号的会话信息。
-     * 它可加速登录过程,也可能可以减少出现验证码的次数。如果遇到一段时间后无法接收消息通知等同步问题时可尝试禁用。
-     *
-     * @since 2.11
-     */
-    public actual fun disableAccountSecretes() {
-        accountSecrets = false
-    }
-
-    /**
-     * 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息.
-     * @see fileBasedDeviceInfo 使用指定文件存储设备信息
-     * @see randomDeviceInfo 使用随机设备信息
-     */
-    public actual var deviceInfo: ((Bot) -> DeviceInfo)? = deviceInfoStub // allows user to set `null` manually.
-
-    /**
-     * 使用随机设备信息.
-     *
-     * @see deviceInfo
-     */
-    @ConfigurationDsl
-    public actual fun randomDeviceInfo() {
-        deviceInfo = null
-    }
-
-    /**
-     * 使用特定由 [DeviceInfo] 序列化产生的 JSON 的设备信息
-     *
-     * @see deviceInfo
-     */
-    @ConfigurationDsl
-    public actual fun loadDeviceInfoJson(json: String) {
-        deviceInfo = {
-            DeviceInfoManager.deserialize(json, Companion.json)
-        }
-    }
-
     /**
      * 使用文件存储设备信息.
      *
@@ -333,43 +42,17 @@ public actual open class BotConfiguration { // open for Java
      * @see deviceInfo
      */
     @JvmOverloads
-    @ConfigurationDsl
+    @BotConfiguration.ConfigurationDsl
     public actual fun fileBasedDeviceInfo(filepath: String) {
-        deviceInfo = getFileBasedDeviceInfoSupplier { workingDir.resolve(filepath) }
+        deviceInfo = {
+            workingDir.resolve(filepath).loadAsDeviceInfo(BotConfiguration.json)
+        }
     }
 
     ///////////////////////////////////////////////////////////////////////////
     // Logging
     ///////////////////////////////////////////////////////////////////////////
 
-    /**
-     * 日志记录器
-     *
-     * - 默认打印到标准输出, 通过 [MiraiLogger.create]
-     * - 忽略所有日志: [noBotLog]
-     * - 重定向到一个目录: `botLoggerSupplier = { DirectoryLogger("Bot ${it.id}") }`
-     * - 重定向到一个文件: `botLoggerSupplier = { SingleFileLogger("Bot ${it.id}") }`
-     *
-     * @see MiraiLogger
-     */
-    public actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = {
-        MiraiLogger.Factory.create(Bot::class, "Bot ${it.id}")
-    }
-
-    /**
-     * 网络层日志构造器
-     *
-     * - 默认打印到标准输出, 通过 [MiraiLogger.create]
-     * - 忽略所有日志: [noNetworkLog]
-     * - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
-     * - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
-     *
-     * @see MiraiLogger
-     */
-    public actual var networkLoggerSupplier: ((Bot) -> MiraiLogger) = {
-        MiraiLogger.Factory.create(Bot::class, "Net ${it.id}")
-    }
-
 
     /**
      * 重定向 [网络日志][networkLoggerSupplier] 到指定目录. 若目录不存在将会自动创建 ([File.mkdirs])
@@ -378,7 +61,7 @@ public actual open class BotConfiguration { // open for Java
      * @see redirectNetworkLogToDirectory
      */
     @JvmOverloads
-    @ConfigurationDsl
+    @BotConfiguration.ConfigurationDsl
     public fun redirectNetworkLogToDirectory(
         dir: File = File("logs"),
         retain: Long = 1.weeksToMillis,
@@ -395,7 +78,7 @@ public actual open class BotConfiguration { // open for Java
      * @see redirectNetworkLogToDirectory
      */
     @JvmOverloads
-    @ConfigurationDsl
+    @BotConfiguration.ConfigurationDsl
     public fun redirectNetworkLogToFile(
         file: File = File("mirai.log"),
         identity: (bot: Bot) -> String = { "Net ${it.id}" }
@@ -411,7 +94,7 @@ public actual open class BotConfiguration { // open for Java
      * @see redirectBotLogToDirectory
      */
     @JvmOverloads
-    @ConfigurationDsl
+    @BotConfiguration.ConfigurationDsl
     public fun redirectBotLogToFile(
         file: File = File("mirai.log"),
         identity: (bot: Bot) -> String = { "Bot ${it.id}" }
@@ -427,7 +110,7 @@ public actual open class BotConfiguration { // open for Java
      * @see redirectBotLogToFile
      */
     @JvmOverloads
-    @ConfigurationDsl
+    @BotConfiguration.ConfigurationDsl
     public fun redirectBotLogToDirectory(
         dir: File = File("logs"),
         retain: Long = 1.weeksToMillis,
@@ -437,33 +120,6 @@ public actual open class BotConfiguration { // open for Java
         botLoggerSupplier = { DirectoryLogger(identity(it), workingDir.resolve(dir), retain) }
     }
 
-    /**
-     * 不显示网络日志. 不推荐.
-     * @see networkLoggerSupplier 更多日志处理方式
-     */
-    @ConfigurationDsl
-    public actual fun noNetworkLog() {
-        networkLoggerSupplier = { _ -> SilentLogger }
-    }
-
-    /**
-     * 不显示 [Bot] 日志. 不推荐.
-     * @see botLoggerSupplier 更多日志处理方式
-     */
-    @ConfigurationDsl
-    public actual fun noBotLog() {
-        botLoggerSupplier = { _ -> SilentLogger }
-    }
-
-    /**
-     * 是否显示过于冗长的事件日志
-     *
-     * 默认为 `false`
-     *
-     * @since 2.8
-     */
-    public actual var isShowingVerboseEventLog: Boolean = false
-
     ///////////////////////////////////////////////////////////////////////////
     // Cache
     //////////////////////////////////////////////////////////////////////////
@@ -485,165 +141,12 @@ public actual open class BotConfiguration { // open for Java
      */
     public var cacheDir: File = File("cache")
 
-    /**
-     * 联系人信息缓存配置. 将会保存在 [cacheDir] 中 `contacts` 目录
-     * @since 2.4
-     */
-    public actual var contactListCache: ContactListCache = ContactListCache()
-
-    /**
-     * 联系人信息缓存配置
-     * @see contactListCache
-     * @see enableContactCache
-     * @see disableContactCache
-     * @since 2.4
-     */
-    public actual class ContactListCache {
-        /**
-         * 在有修改时自动保存间隔. 默认 60 秒. 在每次登录完成后有修改时都会立即保存一次.
-         */
-        public actual var saveIntervalMillis: Long = 60_000
-
-        /**
-         * 在有修改时自动保存间隔. 默认 60 秒. 在每次登录完成后有修改时都会立即保存一次.
-         */ // was @ExperimentalTime before 2.9
-        public actual inline var saveInterval: Duration
-            @JvmSynthetic inline get() = saveIntervalMillis.milliseconds
-            @JvmSynthetic inline set(v) {
-                saveIntervalMillis = v.inWholeMilliseconds
-            }
-
-        /**
-         * 开启好友列表缓存.
-         */
-        public actual var friendListCacheEnabled: Boolean = false
-
-        /**
-         * 开启群成员列表缓存.
-         */
-        public actual var groupMemberListCacheEnabled: Boolean = false
-    }
-
-    /**
-     * 配置 [ContactListCache]
-     * ```
-     * contactListCache {
-     *     saveIntervalMillis = 30_000
-     *     friendListCacheEnabled = true
-     * }
-     * ```
-     * @since 2.4
-     */
-    @JvmSynthetic
-    public actual inline fun contactListCache(action: ContactListCache.() -> Unit) {
-        action.invoke(this.contactListCache)
-    }
-
-    /**
-     * 禁用好友列表和群成员列表的缓存.
-     * @since 2.4
-     */
-    @ConfigurationDsl
-    public actual fun disableContactCache() {
-        contactListCache.friendListCacheEnabled = false
-        contactListCache.groupMemberListCacheEnabled = false
-    }
-
-    /**
-     * 启用好友列表和群成员列表的缓存.
-     * @since 2.4
-     */
-    @ConfigurationDsl
-    public actual fun enableContactCache() {
-        contactListCache.friendListCacheEnabled = true
-        contactListCache.groupMemberListCacheEnabled = true
-    }
-
-    /**
-     * 登录缓存.
-     *
-     * 开始后在密码登录成功时会保存秘钥等信息, 在下次启动时通过这些信息登录, 而不提交密码.
-     * 可以减少验证码出现的频率.
-     *
-     * 秘钥信息会由密码加密保存. 如果秘钥过期, 则会进行普通密码登录.
-     *
-     * 默认 `true` (开启).
-     *
-     * @since 2.6
-     */
-    public actual var loginCacheEnabled: Boolean = true
-
     ///////////////////////////////////////////////////////////////////////////
     // Misc
     ///////////////////////////////////////////////////////////////////////////
 
-    @Suppress("DuplicatedCode")
-    public actual fun copy(): BotConfiguration {
-        return BotConfiguration().also { new ->
-            // To structural order
-            new.workingDir = workingDir
-            @Suppress("DEPRECATION_ERROR")
-            new.parentCoroutineContext = parentCoroutineContext
-            new.heartbeatPeriodMillis = heartbeatPeriodMillis
-            new.heartbeatTimeoutMillis = heartbeatTimeoutMillis
-            new.statHeartbeatPeriodMillis = statHeartbeatPeriodMillis
-            new.heartbeatStrategy = heartbeatStrategy
-            new.reconnectionRetryTimes = reconnectionRetryTimes
-            new.autoReconnectOnForceOffline = autoReconnectOnForceOffline
-            new.loginSolver = loginSolver
-            new.protocol = protocol
-            new.highwayUploadCoroutineCount = highwayUploadCoroutineCount
-            new.accountSecrets = accountSecrets
-            new.deviceInfo = deviceInfo
-            new.botLoggerSupplier = botLoggerSupplier
-            new.networkLoggerSupplier = networkLoggerSupplier
-            new.cacheDir = cacheDir
-            new.contactListCache = contactListCache
-            new.convertLineSeparator = convertLineSeparator
-            new.isShowingVerboseEventLog = isShowingVerboseEventLog
-        }
-    }
-
-    /**
-     * 是否处理接受到的特殊换行符, 默认为 `true`
-     *
-     * - 若为 `true`, 会将收到的 `CRLF(\r\n)` 和 `CR(\r)` 替换为 `LF(\n)`
-     * - 若为 `false`, 则不做处理
-     *
-     * @since 2.4
-     */
-    @get:JvmName("isConvertLineSeparator")
-    public actual var convertLineSeparator: Boolean = true
-
-    /** 标注一个配置 DSL 函数 */
-    @Target(AnnotationTarget.FUNCTION)
-    @DslMarker
-    public actual annotation class ConfigurationDsl
-
-    public actual companion object {
-        /** 默认的配置实例. 可以进行修改 */
-        @JvmStatic
-        public actual val Default: BotConfiguration = BotConfiguration()
-
-        /**
-         * Json 序列化器, 使用 'kotlinx.serialization'
-         */
-        internal val json: Json = kotlin.runCatching {
-            Json {
-                isLenient = true
-                ignoreUnknownKeys = true
-                prettyPrint = true
-            }
-        }.getOrElse {
-            @Suppress("JSON_FORMAT_REDUNDANT_DEFAULT") // compatible for older versions
-            Json {}
-        }
-
-        internal fun BotConfiguration.getFileBasedDeviceInfoSupplier(file: () -> File): (Bot) -> DeviceInfo {
-            return {
-                @Suppress("DEPRECATION_ERROR")
-                file().loadAsDeviceInfo(json)
-            }
-        }
+    internal actual fun applyMppCopy(new: BotConfiguration) {
+        new.workingDir = workingDir
+        new.cacheDir = cacheDir
     }
-}
+}

+ 14 - 507
mirai-core-api/src/nativeMain/kotlin/utils/BotConfiguration.kt

@@ -7,317 +7,26 @@
  * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
-@file:Suppress("RedundantVisibilityModifier")
-
 package net.mamoe.mirai.utils
 
 import io.ktor.utils.io.core.*
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.serialization.json.Json
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.BotFactory
-import net.mamoe.mirai.event.events.BotOfflineEvent
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.coroutines.coroutineContext
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
 
 /**
- * [Bot] 配置. 用于 [BotFactory.newBot]
- *
- * Kotlin 使用方法:
- * ```
- * val bot = BotFactory.newBot(...) {
- *    // 在这里配置 Bot
- *
- *    bogLoggerSupplier = { bot -> ... }
- *    fileBasedDeviceInfo()
- *    inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
- * }
- * ```
- *
- * Java 使用方法:
- * ```java
- * Bot bot = BotFactory.newBot(..., new BotConfiguration() {{
- *     setBogLoggerSupplier((Bot bot) -> { ... })
- *     fileBasedDeviceInfo()
- *     ...
- * }})
- * ```
+ * [BotConfiguration] 的 Native 平台特别配置
+ * @since 2.15
  */
-@Suppress("PropertyName")
-public actual open class BotConfiguration { // open for Java
+@NotStableForInheritance
+public actual abstract class AbstractBotConfiguration { // open for Java
+    protected actual abstract var deviceInfo: ((Bot) -> DeviceInfo)?
+    protected actual abstract var networkLoggerSupplier: ((Bot) -> MiraiLogger)
+    protected actual abstract var botLoggerSupplier: ((Bot) -> MiraiLogger)
+
     /**
      * 工作目录. 默认为当前目录
      */
     public var workingDir: String = "."
 
-    ///////////////////////////////////////////////////////////////////////////
-    // Coroutines
-    ///////////////////////////////////////////////////////////////////////////
-
-    /** 父 [CoroutineContext]. [Bot] 创建后会使用 [SupervisorJob] 覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */
-    public actual var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
-
-    /**
-     * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
-     *
-     * Bot 将会使用一个 [SupervisorJob] 覆盖 [coroutineContext] 当前协程的 [Job], 并使用当前协程的 [Job] 作为父 [Job]
-     *
-     * 用例:
-     * ```
-     * coroutineScope {
-     *   val bot = Bot(...) {
-     *     inheritCoroutineContext()
-     *   }
-     *   bot.login()
-     * } // coroutineScope 会等待 Bot 退出
-     * ```
-     *
-     *
-     * **注意**: `bot.cancel` 时将会让父 [Job] 也被 cancel.
-     * ```
-     * coroutineScope { // this: CoroutineScope
-     *   launch {
-     *     while(isActive) {
-     *       delay(500)
-     *       println("I'm alive")
-     *     }
-     *   }
-     *
-     *   val bot = Bot(...) {
-     *      inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
-     *   }
-     *   bot.login()
-     *   bot.cancel() // 取消了整个 `coroutineScope`, 因此上文不断打印 `"I'm alive"` 的协程也会被取消.
-     * }
-     * ```
-     *
-     * 因此, 此函数尤为适合在 `suspend fun main()` 中使用, 它能阻止主线程退出:
-     * ```
-     * suspend fun main() {
-     *   val bot = Bot() {
-     *     inheritCoroutineContext()
-     *   }
-     *   bot.eventChannel.subscribe { ... }
-     *
-     *   // 主线程不会退出, 直到 Bot 离线.
-     * }
-     * ```
-     *
-     * 简言之,
-     * - 若想让 [Bot] 作为 '守护进程' 运行, 则无需调用 [inheritCoroutineContext].
-     * - 若想让 [Bot] 依赖于当前协程, 让当前协程等待 [Bot] 运行, 则使用 [inheritCoroutineContext]
-     *
-     * @see parentCoroutineContext
-     */
-    @ConfigurationDsl
-    public actual suspend inline fun inheritCoroutineContext() {
-        parentCoroutineContext = coroutineContext
-    }
-
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Connection
-    ///////////////////////////////////////////////////////////////////////////
-
-    /** 连接心跳包周期. 过长会导致被服务器断开连接. */
-    public actual var heartbeatPeriodMillis: Long = 60.secondsToMillis
-
-    /**
-     * 状态心跳包周期. 过长会导致掉线.
-     * 该值会在登录时根据服务器下发的配置自动进行更新.
-     * @since 2.6
-     * @see heartbeatStrategy
-     */
-    public actual var statHeartbeatPeriodMillis: Long = 300.secondsToMillis
-
-    /**
-     * 心跳策略.
-     * @since 2.6.3
-     */
-    public actual var heartbeatStrategy: HeartbeatStrategy = HeartbeatStrategy.STAT_HB
-
-    /**
-     * 心跳策略.
-     * @since 2.6.3
-     */
-    public actual enum class HeartbeatStrategy {
-        /**
-         * 使用 2.6.0 增加的*状态心跳* (Stat Heartbeat). 通常推荐这个模式.
-         *
-         * 该模式大多数情况下更稳定. 但有些账号使用这个模式时会遇到一段时间后发送消息成功但客户端不可见的问题.
-         */
-        STAT_HB,
-
-        /**
-         * 不发送状态心跳, 而是发送*切换在线状态* (可能会导致频繁的好友或客户端上线提示, 也可能产生短暂 (几秒) 发送消息不可见的问题).
-         *
-         * 建议在 [STAT_HB] 不可用时使用 [REGISTER].
-         */
-        REGISTER,
-
-        /**
-         * 不主动维护会话. 多数账号会每 16 分钟掉线然后重连. 则会有短暂的不可用时间.
-         *
-         * 仅当 [STAT_HB] 和 [REGISTER] 都造成无法接收等问题时使用.
-         * 同时请在 [https://github.com/mamoe/mirai/issues/1209] 提交问题.
-         */
-        NONE;
-    }
-
-    /**
-     * 每次心跳时等待结果的时间.
-     * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
-     */
-    public actual var heartbeatTimeoutMillis: Long = 5.secondsToMillis
-
-    /** 心跳失败后的第一次重连前的等待时间. */
-    @Deprecated(
-        "Useless since new network. Please just remove this.",
-        level = DeprecationLevel.HIDDEN
-    ) // deprecated since 2.7, error since 2.8
-    @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10")
-    public actual var firstReconnectDelayMillis: Long = 5.secondsToMillis
-
-    /** 重连失败后, 继续尝试的每次等待时间 */
-    @Deprecated(
-        "Useless since new network. Please just remove this.",
-        level = DeprecationLevel.HIDDEN
-    ) // deprecated since 2.7, error since 2.8
-    @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10")
-    public actual var reconnectPeriodMillis: Long = 5.secondsToMillis
-
-    /** 最多尝试多少次重连 */
-    public actual var reconnectionRetryTimes: Int = Int.MAX_VALUE
-
-    /**
-     * 在被挤下线时 ([BotOfflineEvent.Force]) 自动重连. 默认为 `false`.
-     *
-     * 其他情况掉线都默认会自动重连, 详见 [BotOfflineEvent.reconnect]
-     *
-     * @since 2.1
-     */
-    public actual var autoReconnectOnForceOffline: Boolean = false
-
-    /**
-     * 验证码处理器
-     *
-     * - 在 Android 需要手动提供 [LoginSolver]
-     * - 在 JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现
-     *
-     * 详见 [LoginSolver.Default]
-     *
-     * @see LoginSolver
-     */
-    public actual var loginSolver: LoginSolver? = LoginSolver.Default
-
-    /** 使用协议类型 */
-    public actual var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE
-
-    public actual enum class MiraiProtocol {
-        /**
-         * Android 手机. 所有功能都支持.
-         */
-        ANDROID_PHONE,
-
-        /**
-         * Android 平板.
-         */
-        ANDROID_PAD,
-
-        /**
-         * Android 手表.
-         *
-         * 注意: 不支持戳一戳事件解析
-         */
-        ANDROID_WATCH,
-
-        /**
-         * iPad - 来自MiraiGo
-         *
-         * @since 2.8
-         */
-        IPAD,
-
-        /**
-         * MacOS - 来自MiraiGo
-         *
-         * @since 2.8
-         */
-        MACOS,
-
-    }
-
-    /**
-     * Highway 通道上传图片, 语音, 文件等资源时的协程数量.
-     *
-     * 每个协程的速度约为 200KB/s. 协程数量越多越快, 同时也更要求性能.
-     * 默认: CPU 核心数.
-     *
-     * @since 2.2
-     */
-    public actual var highwayUploadCoroutineCount: Int = availableProcessors()
-
-    /**
-     * 设置 [autoReconnectOnForceOffline] 为 `true`, 即在被挤下线时自动重连.
-     * @since 2.1
-     */
-    @ConfigurationDsl
-    public actual fun autoReconnectOnForceOffline() {
-        autoReconnectOnForceOffline = true
-    }
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Device
-    ///////////////////////////////////////////////////////////////////////////
-
-    internal actual var accountSecrets: Boolean = true
-
-    /**
-     * 禁止保存 `account.secrets`.
-     *
-     * `account.secrets` 保存账号的会话信息。
-     * 它可加速登录过程,也可能可以减少出现验证码的次数。如果遇到一段时间后无法接收消息通知等同步问题时可尝试禁用。
-     *
-     * @since 2.11
-     */
-    public actual fun disableAccountSecretes() {
-        accountSecrets = false
-    }
-
-    /**
-     * 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息.
-     * @see fileBasedDeviceInfo 使用指定文件存储设备信息
-     * @see randomDeviceInfo 使用随机设备信息
-     */
-    public actual var deviceInfo: ((Bot) -> DeviceInfo)? = deviceInfoStub // allows user to set `null` manually.
-
-    /**
-     * 使用随机设备信息.
-     *
-     * @see deviceInfo
-     */
-    @ConfigurationDsl
-    public actual fun randomDeviceInfo() {
-        deviceInfo = null
-    }
-
-
-    /**
-     * 使用特定由 [DeviceInfo] 序列化产生的 JSON 的设备信息
-     *
-     * @see deviceInfo
-     */
-    @ConfigurationDsl
-    public actual fun loadDeviceInfoJson(json: String) {
-        deviceInfo = {
-            DeviceInfoManager.deserialize(json, Companion.json)
-        }
-    }
-
     /**
      * 使用文件存储设备信息.
      *
@@ -325,80 +34,17 @@ public actual open class BotConfiguration { // open for Java
      * @param filepath 文件路径. 默认是相对于 [workingDir] 的文件 "device.json".
      * @see deviceInfo
      */
-    @ConfigurationDsl
+    @BotConfiguration.ConfigurationDsl
     public actual fun fileBasedDeviceInfo(filepath: String) {
         deviceInfo = {
             val file = MiraiFile.create(workingDir).resolve(filepath)
             if (!file.exists()) {
-                file.writeText(DeviceInfoManager.serialize(DeviceInfo.random(), json))
+                file.writeText(DeviceInfoManager.serialize(DeviceInfo.random(), BotConfiguration.json))
             }
-            DeviceInfoManager.deserialize(file.readText(), json)
+            DeviceInfoManager.deserialize(file.readText(), BotConfiguration.json)
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // Logging
-    ///////////////////////////////////////////////////////////////////////////
-
-    /**
-     * 日志记录器
-     *
-     * - 默认打印到标准输出, 通过 [MiraiLogger.create]
-     * - 忽略所有日志: [noBotLog]
-     * - 重定向到一个目录: `botLoggerSupplier = { DirectoryLogger("Bot ${it.id}") }`
-     * - 重定向到一个文件: `botLoggerSupplier = { SingleFileLogger("Bot ${it.id}") }`
-     *
-     * @see MiraiLogger
-     */
-    public actual var botLoggerSupplier: ((Bot) -> MiraiLogger) = {
-        MiraiLogger.Factory.create(Bot::class, "Bot ${it.id}")
-    }
-
-    /**
-     * 网络层日志构造器
-     *
-     * - 默认打印到标准输出, 通过 [MiraiLogger.create]
-     * - 忽略所有日志: [noNetworkLog]
-     * - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
-     * - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
-     *
-     * @see MiraiLogger
-     */
-    public actual var networkLoggerSupplier: ((Bot) -> MiraiLogger) = {
-        MiraiLogger.Factory.create(Bot::class, "Net ${it.id}")
-    }
-
-    /**
-     * 不显示网络日志. 不推荐.
-     * @see networkLoggerSupplier 更多日志处理方式
-     */
-    @ConfigurationDsl
-    public actual fun noNetworkLog() {
-        networkLoggerSupplier = { _ -> SilentLogger }
-    }
-
-    /**
-     * 不显示 [Bot] 日志. 不推荐.
-     * @see botLoggerSupplier 更多日志处理方式
-     */
-    @ConfigurationDsl
-    public actual fun noBotLog() {
-        botLoggerSupplier = { _ -> SilentLogger }
-    }
-
-    /**
-     * 是否显示过于冗长的事件日志
-     *
-     * 默认为 `false`
-     *
-     * @since 2.8
-     */
-    public actual var isShowingVerboseEventLog: Boolean = false
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Cache
-    //////////////////////////////////////////////////////////////////////////
-
     /**
      * 缓存数据目录路径. 若 [cacheDir] 为绝对路径, 将解析该绝对路径, 否则作为相对于 [workingDir] 的路径解析.
      * 例如, `cache` 将会解析为 `$workingDir/cache`, 而 `/Users/Chisato/Desktop/bot/cache` 指代绝对路径, 将解析为绝对路径.
@@ -417,147 +63,8 @@ public actual open class BotConfiguration { // open for Java
      */
     public var cacheDir: String = "cache"
 
-    /**
-     * 联系人信息缓存配置. 将会保存在 [cacheDir] 中 `contacts` 目录
-     * @since 2.4
-     */
-    public actual var contactListCache: ContactListCache = ContactListCache()
-
-    /**
-     * 联系人信息缓存配置
-     * @see contactListCache
-     * @see enableContactCache
-     * @see disableContactCache
-     * @since 2.4
-     */
-    public actual class ContactListCache {
-        /**
-         * 在有修改时自动保存间隔. 默认 60 秒. 在每次登录完成后有修改时都会立即保存一次.
-         */
-        public actual var saveIntervalMillis: Long = 60_000
-
-        /**
-         * 在有修改时自动保存间隔. 默认 60 秒. 在每次登录完成后有修改时都会立即保存一次.
-         */ // was @ExperimentalTime before 2.9
-        public actual inline var saveInterval: Duration
-            inline get() = saveIntervalMillis.milliseconds
-            inline set(v) {
-                saveIntervalMillis = v.inWholeMilliseconds
-            }
-
-        /**
-         * 开启好友列表缓存.
-         */
-        public actual var friendListCacheEnabled: Boolean = false
-
-        /**
-         * 开启群成员列表缓存.
-         */
-        public actual var groupMemberListCacheEnabled: Boolean = false
-    }
-
-    /**
-     * 配置 [ContactListCache]
-     * ```
-     * contactListCache {
-     *     saveIntervalMillis = 30_000
-     *     friendListCacheEnabled = true
-     * }
-     * ```
-     * @since 2.4
-     */
-    public actual inline fun contactListCache(action: ContactListCache.() -> Unit) {
-        action.invoke(this.contactListCache)
-    }
-
-    /**
-     * 禁用好友列表和群成员列表的缓存.
-     * @since 2.4
-     */
-    @ConfigurationDsl
-    public actual fun disableContactCache() {
-        contactListCache.friendListCacheEnabled = false
-        contactListCache.groupMemberListCacheEnabled = false
-    }
-
-    /**
-     * 启用好友列表和群成员列表的缓存.
-     * @since 2.4
-     */
-    @ConfigurationDsl
-    public actual fun enableContactCache() {
-        contactListCache.friendListCacheEnabled = true
-        contactListCache.groupMemberListCacheEnabled = true
-    }
-
-    /**
-     * 登录缓存.
-     *
-     * 开始后在密码登录成功时会保存秘钥等信息, 在下次启动时通过这些信息登录, 而不提交密码.
-     * 可以减少验证码出现的频率.
-     *
-     * 秘钥信息会由密码加密保存. 如果秘钥过期, 则会进行普通密码登录.
-     *
-     * 默认 `true` (开启).
-     *
-     * @since 2.6
-     */
-    public actual var loginCacheEnabled: Boolean = true
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Misc
-    ///////////////////////////////////////////////////////////////////////////
-
-    @Suppress("DuplicatedCode")
-    public actual fun copy(): BotConfiguration {
-        return BotConfiguration().also { new ->
-            // To structural order
-            new.workingDir = workingDir
-            new.parentCoroutineContext = parentCoroutineContext
-            new.heartbeatPeriodMillis = heartbeatPeriodMillis
-            new.heartbeatTimeoutMillis = heartbeatTimeoutMillis
-            new.statHeartbeatPeriodMillis = statHeartbeatPeriodMillis
-            new.heartbeatStrategy = heartbeatStrategy
-            new.reconnectionRetryTimes = reconnectionRetryTimes
-            new.autoReconnectOnForceOffline = autoReconnectOnForceOffline
-            new.loginSolver = loginSolver
-            new.protocol = protocol
-            new.highwayUploadCoroutineCount = highwayUploadCoroutineCount
-            new.accountSecrets = accountSecrets
-            new.deviceInfo = deviceInfo
-            new.botLoggerSupplier = botLoggerSupplier
-            new.networkLoggerSupplier = networkLoggerSupplier
-            new.cacheDir = cacheDir
-            new.contactListCache = contactListCache
-            new.convertLineSeparator = convertLineSeparator
-            new.isShowingVerboseEventLog = isShowingVerboseEventLog
-        }
-    }
-
-    /**
-     * 是否处理接受到的特殊换行符, 默认为 `true`
-     *
-     * - 若为 `true`, 会将收到的 `CRLF(\r\n)` 和 `CR(\r)` 替换为 `LF(\n)`
-     * - 若为 `false`, 则不做处理
-     *
-     * @since 2.4
-     */
-    public actual var convertLineSeparator: Boolean = true
-
-    /** 标注一个配置 DSL 函数 */
-    @Target(AnnotationTarget.FUNCTION)
-    @DslMarker
-    public actual annotation class ConfigurationDsl
-
-    public actual companion object {
-        /** 默认的配置实例. 可以进行修改 */
-        public actual val Default: BotConfiguration = BotConfiguration()
-
-
-        private val json = Json {
-            isLenient = true
-            ignoreUnknownKeys = true
-            prettyPrint = true
-        }
+    internal actual fun applyMppCopy(new: BotConfiguration) {
+        new.workingDir = workingDir
+        new.cacheDir = cacheDir
     }
 }