# Mirai Console Backend - Plugins [`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt [`PluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt [`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt [`PluginManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt [`JarPluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt [`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt [`JvmPluginDescription`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt [`AbstractJvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt [`KotlinPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt [`JavaPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPlugin.kt [`PluginData`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt [`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt [`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt [`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt [`MiraiConsoleImplementation`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt [`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt [`CompositeCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt [`SimpleCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt [`RawCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt [`CommandManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt [`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt [`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt [`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt [`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt [`ResourceContainer`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/ResourceContainer.kt [`PluginFileExtensions`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginFileExtensions.kt [`AutoSavePluginDataHolder`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt#L45 [Kotlin]: https://www.kotlincn.net/ [Java]: https://www.java.com/zh_CN/ [JVM]: https://zh.wikipedia.org/zh-cn/Java%E8%99%9A%E6%8B%9F%E6%9C%BA [JAR]: https://zh.wikipedia.org/zh-cn/JAR_(%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F) [为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件 [使用 AutoService]: QA.md#使用-autoservice Mirai Console 运行在 [JVM],支持使用 [Kotlin] 或 [Java] 语言编写的插件。 ## 通用的插件接口 - [`Plugin`] 所有 Console 插件都必须实现 [`Plugin`] 接口。 > **解释 *插件***:只要实现了 [`Plugin`] 接口的类和对象都可以叫做「Mirai Console 插件」,简称 「插件」。 > 为了便捷,内含 [`Plugin`] 实现的一个 [JAR] 文件也可以被称为「插件」。 基础的 [`Plugin`] 很通用,它只拥有很少的成员: ```kotlin interface Plugin : CommandOwner { // CommandOwner 是空的 interface val isEnabled: Boolean val loader: PluginLoader<*, *> // 能处理这个 Plugin 的 PluginLoader } ``` [`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [实现 PluginLoader](PluginLoader.md))。 ## JVM 平台插件接口 - [`JvmPlugin`] 所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JarPluginLoader`] 加载)。 Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。 ### 主类和描述 **Kotlin 使用者的插件主类应继承 [`KotlinPlugin`]。** **其他 JVM 语言(如 Java)使用者的插件主类应继承 [`JavaPlugin`]。** #### 描述 插件描述需要在主类构造器传递给 `super`。因此插件不需要 `plugin.yml`, `plugin.xml` 等配置文件来指示信息。 Mirai Console 使用 `ServiceLoader` 加载插件。 在 Kotlin,可([使用 AutoService])自动配置 service 信息。 在 Kotlin 或其他语言,手动创建 service 文件: 在 `jar` 内 `META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名). 有关插件版本号的限制: - 插件自身的版本要求遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 合格的版本例如: `1.0.0`, `1.0`, `1.0-M1`, `1.0-pre-1` - 插件依赖的版本遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 同时支持 [Apache Ivy 风格表示方法](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html). ##### 插件名 插件名仅取决于 `PluginDescription` 提供的 `name`,与主类类名等其他信息无关。 #### 实现 Kotlin 插件主类 一个 Kotlin 插件的主类通常需: - 继承 [`KotlinPlugin`] - 访问权限为 `public` 或默认 (不指定) ```kotlin @AutoService(JvmPlugin::class) // 如果选用上述自动配置的方法 object SchedulePlugin : KotlinPlugin( SimpleJvmPluginDescription( // 插件的描述, name 和 version 是必须的 name = "Schedule", version = "1.0.0", // author, description, ... ) ) { // ... } ``` #### 实现 Java 插件 一个 Java 插件的主类通常需: - 继承 [`JavaPlugin`] - 访问权限为 `public` (推荐) 静态初始化: ```java public final class JExample extends JavaPlugin { public static final JExample INSTANCE = new JExample(); // 可以像 Kotlin 一样静态初始化单例 private JExample() { super(new SimpleJvmPluginDescription( "JExample", // name "1.0.0" // version )); } } ``` 由 Console 初始化(仅在某些静态初始化不可用的情况下使用): ```java public final class JExample extends JavaPlugin { private static final JExample instance; public static JExample getInstance() { return instance; } public JExample() { // 此时必须 public super(new SimpleJvmPluginDescription( "JExample", // name "1.0.0" // version )); instance = this; } } ``` ### 依赖管理 一个插件被允许依赖于另一个插件。可在 `SimpleJvmPluginDescription` 构造时提供信息。 若插件拥有依赖,则会首先加载其依赖。但任何一个插件的 `onEnable()` 都会在所有插件的 `onLoad()` 都调用成功后再调用。 多个插件的加载是*顺序的*,意味着若一个插件的 `onLoad()` 等回调处理缓慢,后续插件的加载也会被延后,即使它们可能没有依赖关系。 因此请尽量让 `onLoad()`,`onEnable()`,`onDisable()`快速返回。 ### 插件生命周期 Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务器启动前加载,在服务器结束时卸载。([为什么不支持热加载和卸载插件?]) 插件仅可以通过如下三个回调知晓自身的加载情况。 较小概率情况: - 如果 `onLoad()` 被调用,`onEnable()` 不一定会调用。因为可能在调用后续插件的 `onLoad()` 或 `onEnable()` 时可能会出错而导致服务器被关闭。 - 如果 `onLoad()` 或 `onEnable()` 调用时抛出异常,`onDisable()` 不会被调用。(注意:这是仍处于争议状态的行为,后续可能有修改) - 如果 `onEnable()` 被成功调用,`onDisable()` 一定会调用,无论其他插件是否发生错误。 #### 加载 [`JarPluginLoader`] 调用插件的 `onLoad()`,在 `onLoad()` 正常返回后插件被认为成功加载。 由于 `onLoad()` 只会被初始化一次,插件可以在该方法内进行一些*一次性*的*初始化*任务。 **在 `onLoad()` 时插件并未处于启用状态,此时插件不能进行监听事件,加载配置等操作。** 若在 Kotlin 使用 `object`,或在 Java 使用静态初始化方式定义插件主类, `onLoad()` 与 `init` 代码块作用几乎相同。 #### 启用 [`JarPluginLoader`] 调用插件的 `onEnable()`,意为启用一个插件。 此时插件可以启动所有协程,事件监听,和其他任务。**但这些任务都应该拥有生命周期管理,详见 [任务生命周期管理](#任务生命周期管理)。** #### 禁用 [`JarPluginLoader`] 调用插件的 `onDisable()`,意为禁用一个插件。 插件的任何类和对象都不会被卸载。「禁用」仅表示停止关闭所有正在进行的任务,保存所有数据,停止处理将来的数据。 插件应正确实现「禁用」,以为用户提供完全的控制可能。 ### 任务生命周期管理 #### 协程管理 [`JvmPlugin`] 实现 `CoroutineScope`,并由 Console 内部实现提供其 `coroutineContext`。 `JvmPlugin.coroutineContext` 包含元素 `CoroutineName`, `SupervisorJob`, `CoroutineExceptionHandler`。 **所有插件启动的协程都应该受 `JvmPlugin` 作用域的管理** 如要启动一个协程,正确的做法是: ```kotlin // object MyPluginMain : KotlinPlugin() MyPluginMain.launch { // job } ``` #### Java 线程管理 *TODO*:Mirai Console 暂未支持自动的线程管理。请手动在 `onDisable()` 时关闭启动的线程。 ### 访问数据目录和配置目录 [`JvmPlugin`] 实现接口 [`PluginFileExtensions`]。插件可通过 `resolveDataFile`,`resolveConfigFile` 等方法取得数据目录或配置目录下的文件。 可以在任何时刻使用这些方法。 详见 [`PluginFileExtensions`]。 #### 物理目录路径 用 `$root` 表示 Mirai Console 运行路径,`$name` 表示插件名, 插件数据目录一般在 `$root/data/$name`,插件配置目录一般在 `$root/config/$name`。 有关数据和配置的区别可以在 [PluginData](PluginData.md) 章节查看。 ### 访问 [JAR] 包内资源文件 [`JvmPlugin`] 实现接口 [`ResourceContainer`]。插件可通过 `getResource`,`getResourceAsStream` 等取得 [JAR] 包内资源文件。 可以在任何时刻使用这些方法。 详见 [`ResourceContainer`]。 ### 读取 [`PluginData`] 或 [`PluginConfig`] > 本节基于章节 [PluginData](PluginData.md) 的内容。 > 在阅读本节前建议先阅读上述基础章节。 [`JvmPlugin`] 实现接口 [`AutoSavePluginDataHolder`],提供: Kotlin: - `public fun T.reload()` - `public fun T.reload()` Java: - `public fun reloadPluginData(PluginData)` - `public fun reloadPluginData(PluginConfig)` **仅可在插件 onEnable() 时及其之后才能使用这些方法。** **在插件 onDisable() 之后不能使用这些方法。** ### 附录:Java 插件的多线程调度器 - [`JavaPluginScheduler`] 拥有生命周期管理的简单 Java 线程池。