Mirai Js
是使用 Mozilla Rhino 作为引擎的,运行于 JVM
上的 Mirai console
插件,支持热加载/卸载,支持完整的 Mirai 面向 Java 的 API 和 部分 Kotlin DSL。
myplugin.zip
├─── scripts <---- 存放 JavaScript 插件主体。
│ ├─── main.js <---- 主脚本。
│ ├─── other1.js <---- 其他脚本模块。
│ └─── other2.js <---- 其他脚本模块。
├─── resources <---- 资源文件,会被在首次加载时解压到 data/org...MiraiJs/data/<插件 ID>/ 中。
│ ├─── a.jpg
│ ├─── b.jar
│ ├─── c.jar
│ ├─── d.json
│ └─── ...
└─── config.json <---- 插件信息。
请注意:scripts
一定存在 main.js
脚本,否则这个插件将无法被加载。
{
// 插件ID,必须是独一无二的,否则会与其他插件冲突。
"id": "me.myname.testplugin",
// 插件名称,可以为空。
"name": "TestPlugin",
// 插件作者,可以为空。
"author": "PluginAuthor",
// 插件描述,可以为空。
"description": "This is a test mirai-js plugin."
}
main.js
:
importClass(net.mamoe.mirai.event.GlobalEventChannel);
importClass(net.mamoe.mirai.event.events.BotOnlineEvent);
importPackage(net.mamoe.mirai)
let bot = null;
let listener = GlobalEventChannel.INSTANCE.subscribeAlways(BotOnlineEvent, (event) => {
logger.info("Bot " + event.getBot() + " is now online!");
bot = event.getBot();
});
控制台:
> login 123456789 password
I/Bot.123456789: Logging in...
I/Bot.123456789: Online OtherClients: mirai
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] Bot Bot(123456789) is now online!
I/Bot.123456789: Login successful
得益于 Rhino 对 Java 的转换,你可以看到,Mirai Js 与 mirai 的交互和直接使用 Java 开发 mirai 是非常相似的。
上述 JS 的等价 Java 代码:
import net.mamoe.mirai.event.GlobalEventChannel;
import net.mamoe.mirai.event.Listener;
import net.mamoe.mirai.event.events.BotOnlineEvent;
public class MiraiJava {
public static Bot bot = null;
public static void main() {
Listener<BotOnlineEvent> listener = GlobalEventChannel.INSTANCE.subscribeAlways(
BotOnlineEvent.class, (event) -> {
System.out.println("Bot " + event.getBot() + " is now online!");
bot = event.getBot();
}
);
}
}
你可以在 Scripting Java - Mozilla | MDN 查看更多关于在 Rhino JavaScript 中调用 Java 的信息。
Mirai Js 支持热加载/卸载,在控制台输入 /jpm unload <插件 ID>
来卸载这个插件。
调用这个命令只是将当插件的 isUnloadFlag
设置为 true
,插件的卸载工作实际上是由插件开发者来完成的。
插件开发者需要配合用户的卸载操作,尽最大可能地在卸载后不残留资源。
-
使用
coroutine.isCancelled()
来获取isUnloadFlag
状态,当为true
时,插件应该执行卸载动作或使插件自然完成执行。 -
在调用卸载命令后,所有在插件中启动的 Kotlin 协程会取消执行并等待执行结束。
在插件中启动的协程默认都是
pluginParentJob
的子协程。
下面将列举几个正确地配合卸载的操作
-
事件订阅:减少
EventChannel.subscribeAlways
的使用,因为subscribeAlways
不可被阻断,请尽量使用subscribeOnce
和subscribe
。importPackage(net.mamoe.mirai.event) importClass(net.mamoe.mirai.event.events.GroupMessageEvent); GlobalEventChannel.subscribe(GroupMessageEvent, (event) => { if(!coroutine.isCancelled()) { doSomething(); return ListeningStatus.LISTENING; } else return ListeningStatus.STOPPED });
-
线程:为线程或根命名域中执行的循环操作添加插件终止判断。
importClass(java.lang.Thread); importClass(java.lang.Runnable); let thread = new Thread(new Runnable({ run: function() { while(otherConditions() && coroutine.isCancelled()) { doSomething(); } cleanup(); } })); thread.start();
-
协程:为协程执行的循环操作添加协程终止判断。
let job = coroutine.launch((coroutineScope) => { while(otherConditions() && job.isCancelled()) { doSomething(); } cleanup(); });
在插件目录中的 scripts
文件夹下,允许存在多个文件。加载插件时,会先编译所有的 JS 文件,然后加载 main.js
。
除了 main.js
,其他 JS 文件会被当做 模块(module),只有在使用它们时才会加载运行。
通过 module(filename)
来加载并使用模块,在模块中通过为 module.exports
赋值来导出模块中的对象。
仿照了 NodeJS 中的
require
和module.exports
的写法。
main.js
let sayhello = module("hello");
sayhello.greet("LiHua");
hello.js
module.exports = {
template: "Hello {name}. Nice to meet you!",
matcher: new RegExp("\\{name\\}", "gm"),
greet: function(name) {
logger.info(this.template.replace(this.matcher, name));
}
}
控制台:
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] Hello LiHua. Nice to meet you!
模块嵌套也是允许的,你可以在一个模块中引用其他模块。
使用 module()
获取模块对象实际上是获得了它的引用,并不是拷贝或者新建了一个对象。
对 module()
获取的模块对象的修改实际上就是对模块本身的修改。
例如:
main.js
let repeatModule = module("repeat");
command.register("stopRepeat", (sender, args) => {
repeatModule.status = false;
});
repeat.js
let coroutineStatus = {
counter: 0,
status: true
}
let job = coroutine.launch((coroutineScope) => {
while(!job.isCancelled() && coroutineStatus.status) {
logger.info("work in module's coroutine, now " + (++coroutineStatus.counter));
coroutine.delay(2000);
}
logger.info("coroutine stopped!");
});
module.exports = coroutineStatus;
控制台:
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] work in module's coroutine, now 1
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] work in module's coroutine, now 2
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] work in module's coroutine, now 3
> stopRepeat
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] coroutine stopped!
只有在首次调用 module()
时会加载运行对应的 JS 文件,再次调用相同的模块时会使用先前的缓存。
main.js
let mod = module("module1");
mod.count = 114514;
logger.info(mod.count);
let refMod = module("module1");
logger.info(refMod.count);
module1.js
module.exports = {
count: 0
}
控制台
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] 114514.0
I/org.itxtech.miraijs.MiraiJs: [TestPlugin] 114514.0
模块 JS 文件的根命名域不可无限阻塞(如循环执行),否则模块调用者将也会阻塞。