mirai/docs/Plugins.md
2020-09-03 19:23:32 +08:00

11 KiB
Raw Blame History

Mirai Console Backend - Plugins

Mirai Console 运行在 JVM,支持使用 KotlinJava 语言编写的插件。

通用的插件接口 - Plugin

所有 Console 插件都必须实现 Plugin 接口。

解释 插件:只要实现了 Plugin 接口的类和对象都可以叫做「Mirai Console 插件」,简称 「插件」。
为了便捷,内含 Plugin 实现的一个 JAR 文件也可以被称为「插件」。

基础的 Plugin 很通用,它只拥有很少的成员:

interface Plugin : CommandOwner { // CommandOwner 是空的 interface
    val isEnabled: Boolean
    val loader: PluginLoader<*, *> // 能处理这个 Plugin 的 PluginLoader
}

Plugin 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 实现 PluginLoader)。

JVM 平台插件接口 - JvmPlugin

所有的 JVM 插件都必须实现 JvmPlugin(否则不会被 JarPluginLoader 加载)。
Mirai Console 提供一些基础的实现,即 AbstractJvmPlugin,并将 JvmPlugin 分为 KotlinPluginJavaPlugin

主类和描述

Kotlin 使用者的插件主类应继承 KotlinPlugin
其他 JVM 语言(如 Java使用者的插件主类应继承 JavaPlugin

描述

插件描述需要在主类构造器传递给 super。因此插件不需要 plugin.yml, plugin.xml 等配置文件来指示信息。

Mirai Console 使用 ServiceLoader 加载插件。推荐的做法是使用 AutoService(如何使用 ServiceLoader 加载插件)。

插件名

插件名仅取决于 PluginDescription 提供的 name,与主类类名等其他信息无关。

实现 Kotlin 插件主类

一个 Kotlin 插件的主类通常需:

  • 继承 KotlinPlugin
  • 访问权限为 public 或默认 (不指定)
@AutoService(JvmPlugin::class) // 让 Console 知道这个 object 是一个插件主类.
object SchedulePlugin : KotlinPlugin(
    SimpleJvmPluginDescription( // 插件的描述, name 和 version 是必须的
        name = "Schedule",
        version = "1.0.0",
        // author, description, ...
    )
) {
    // ...
}

实现 Java 插件

一个 Java 插件的主类通常需:

(推荐) 静态初始化:

@AutoService(JvmPlugin.class)
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 初始化(仅在某些静态初始化不可用的情况下使用):

@AutoService(JvmPlugin.class)
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 作用域的管理

如要启动一个协程,正确的做法是:

// object MyPluginMain : KotlinPlugin()

MyPluginMain.launch {
    // job
} 

Java 线程管理

TODOMirai Console 暂未支持自动的线程管理。请手动在 onDisable() 时关闭启动的线程。

访问数据目录和配置目录

JvmPlugin 实现接口 PluginFileExtensions。插件可通过 resolveDataFileresolveConfigFile 等方法取得数据目录或配置目录下的文件。

可以在任何时刻使用这些方法。

详见 PluginFileExtensions

物理目录路径

$root 表示 Mirai Console 运行路径,$name 表示插件名 插件数据目录一般在 $root/plugins/

访问 JAR 包内资源文件

JvmPlugin 实现接口 ResourceContainer。插件可通过 getResourcegetResourceAsStream 等取得 JAR 包内资源文件。

可以在任何时刻使用这些方法。

详见 ResourceContainer

读取 PluginDataPluginConfig

本节基于章节 PluginData 的内容。 在阅读本节前建议先阅读上述基础章节。

JvmPlugin 实现接口 AutoSavePluginDataHolder,提供:

Kotlin

  • public fun <T : PluginData> T.reload()
  • public fun <T : PluginConfig> T.reload()

Java

  • public fun reloadPluginData(PluginData)
  • public fun reloadPluginData(PluginConfig)

仅可在插件 onEnable() 时及其之后才能使用这些方法。
在插件 onDisable() 之后不能使用这些方法。