开发文档
本文档只讲比较基础/浅层的部分,若需深入还是需要去看官方的开发文档
文档编写者 MrXiaoM 更倾向于 java 开发,如果本文档提供的 kotlin 代码有错请反馈,有些示例可能没有贴出 kotlin 代码,望理解
你需要先越过
mirai
最大的门槛:登录推荐使用的工具列表,欢迎补充
类型 | 工具名称 |
---|---|
集成开发环境 (IDE) | |
文本编辑器 | |
压缩/解压缩工具 | |
下载工具 | |
Git 工具 |
在给你的项目新建文件夹之后,你先要决定你要写什么:
是
mirai-core
衍生程序,还是 mirai-console
插件。用 mirai-core 意味着允许直接运行生成的 jar 或把功能嵌入到一个项目中, 用 mirai-console 意味着要用mcl启动但是允许同时加载多个 mirai-console 插件 #1
如果你使用
mirai-core
,就代表你想让最后编译出来的 jar 可以直接打开运行,不需要 mirai-console
,但需要这代表着你的项目很大程度上和论坛里大部分的插件不兼容,因为没有 mirai-console
无法把其他插件加载进去。(别问为什么你不去写个插件系统,重复造轮子没必要)如果你使用
mirai-console
,就代表你想编写插件,你想让最后编译出来的 jar 要放到 mirai-console
中的 plugins
文件夹里作为插件被加载才可使用,这样可以让你的项目很大程度上和论坛里的大部分插件兼容,可以同时让你的插件和别人的插件同时运行。当然,人不是死板的,你也可以两种混用,编写既可以让
mirai-console
加载又可以单独使用命令行启动的项目,但如果要把 mirai-core
打包进混合的“插件”内会增大单个jar包的占用空间,用mirai-console
的时候打包进去的核心就没用了;如果不打包而是从外部加载库的话还不如用 mirai-console
,这貌似多此一举,但我还是要说下可以这么搞。
如果你要新建
mirai-console
插件项目,你可以去 clone 这个模板项目 project-mirai/mirai-console-plugin-template 或者直接用 mirai-console 的 Gradle 插件 并直接从插件启用时部分开始看即可。朴素的方法:新建项目后,先将下面这老三样作为库导入到项目,
如果是编写
mirai-core
衍生程序,只导入需要第一个mirai-core-all
mirai-console
mirai-console-terminal
// 上面三个库在 Maven Central Repository 上的链接:
https://repo1.maven.org/maven2/net/mamoe/mirai-core-all
https://repo1.maven.org/maven2/net/mamoe/mirai-console
https://repo1.maven.org/maven2/net/mamoe/mirai-console-terminal
// 上面三个库的 2.8.0 版本在 Maven Central Repository 上的下载直链:
https://repo1.maven.org/maven2/net/mamoe/mirai-core-all/2.8.0/mirai-core-all-2.8.0-all.jar
https://repo1.maven.org/maven2/net/mamoe/mirai-console/2.8.0/mirai-console-2.8.0-all.jar
https://repo1.maven.org/maven2/net/mamoe/mirai-console-terminal/2.8.0/mirai-console-terminal-2.8.0-all.jar
其实
mirai-console-terminal
导不导没多大影响,看自己需求。如果你要在 maven 仓库下载 .jar 包的话,前两个必须要下载文件名里版本后面有 -all 结尾的文件,如 mirai-core-all-2.8.0-all.jar
// 你可以直接复制下面的内容来快速导入 2.8.0 到你的 gradle 项目中
// build.gradle
dependencies {
implementation 'net.mamoe:mirai-core-all:2.8.0:all'
implementation 'net.mamoe:mirai-console:2.8.0:all'
}
// build.gradle.kts
dependencies {
implementation('net.mamoe:mirai-core-all:2.8.0:all')
implementation('net.mamoe:mirai-console:2.8.0:all')
}
// 你可以直接复制下面的内容来快速导入 2.8.0 到你的 Maven POM 中
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-all</artifactId>
<version>2.8.0</version>
<classifier>all</classifier>
</dependency>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-console</artifactId>
<version>2.8.0</version>
<classifier>all</classifier>
</dependency>
使用模板项目或者 Gradle 插件大可免除这步,详见上文
既然是
mirai-console
的插件,那就需要让 mirai-console
把你编译的 jar 给认出来。首先,创建资源文件
META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin
在里面填写插件主类的路径,格式为纯文本,例子如下,整个文件中的内容只有如下所示的这一行:
top.mrxiaom.itisme.Natsuko
然后创建一个类,包名和类名如上,自己取名。让这个类继承
JavaPlugin
或者 KotlinPlugin
,再填写插件相关信息即可。在 Java 要新建一个无参数的构造函数并在里面将插件描述补上,必须要无参数的构造函数,并使用公开静态字段将其实例化。
Java:
public class Natsuko extends JavaPlugin {
public static final Natsuko INSTANCE = new Natsuko();
public Natsuko() {
super(new JvmPluginDescriptionBuilder(
// 插件ID
"top.mrxiaom.testplugin",
// 版本
"1.0.0"
)
// 插件名
.name("Natsuko")
// 作者
.author("MrXiaoM")
// 描述
.info("An example plugin for tutorial")
.build());
}
}
Kotlin:
object Natsuko : KotlinPlugin(
JvmPluginDescription(
// 插件ID
id = "top.mrxiaom.testplugin",
// 版本
version = "1.0.0",
) {
// 插件名
name("Natsuko")
// 作者
author("MrXiaoM")
// 描述
info("An example plugin for tutorial")
}
){
}
至此,你的插件已经可以编译丢到 plugins 文件夹里运行了。
在你的插件被启用时,将会调用主类的
onEnable
方法,同理在插件被加载时会调用主类的 onLoad
方法,自行重写即可。
如果你是使用
mirai-console
且不需要或者已有自动登录,你不需要看这一部分首先,想要登录就先要新建一个机器人实例,方法如下
(代码中qq号和密码均为玩梗,请勿当真)
// java:
// BotFactory.INSTANCE.newBot(qq, 密码, 选项);
Bot bot = BotFactory.INSTANCE.newBot(114514L, "1919810", new BotConfiguration() {
{
// 使用平板协议登录
setProtocol(MiraiProtocol.ANDROID_PAD);
// 指定设备信息文件路径,文件不存在将自动生成一个默认的,存在就读取
fileBasedDeviceInfo("deviceInfo_114514.json");
// 更多操作自己看代码补全吧
}
});
// kotlin:
// BotFactory.newBot(qq, 密码)
val bot = BotFactory.newBot(114514L, "1919810") {
// 使用平板协议登录
setProtocol(MiraiProtocol.ANDROID_PAD)
// 指定设备信息文件路径,文件不存在将自动生成一个默认的,存在就读取
fileBasedDeviceInfo("deviceInfo_114514.json")
// 更多操作自己看代码补全吧
}
要登录这个机器人实例,
bot.login();
就好了如果你想获取已经登录过的机器人的实例
Bot.getInstance(114514L)
但是,值得注意,不要在
onEnable
方法中直接执行Bot.getInstance()
获取Bot实例。因为 mirai-console
会先加载插件(onLoad
),加载完成插件(onEnable
)后,才会登录 QQ Bot,因此,在执行 onEnable
方法时,QQ Bot 还没有登录,获取不到实例。
监听的方式多种多样,可以监听单个事件,监听一个类里所有事件等等
但在这之前,我们需要先选择一个事件通道,你可以选择公共事件通道(在同一个mirai上登录的所有机器人都能触发)或者单机器人事件通道(只有特定的机器人能触发)。如果你的mirai上只登录了一个机器人,随便选。
获取公共事件通道:
// Java: GlobalEventChannel.INSTANCE
// Kotlin: GlobalEventChannel
获取单机器人事件通道:
// Java: bot.getEventChannel();
// Kotlin: bot.eventChannel
以上这是简单的获取方法。本节之后,本文将把事件通道统统用
channel
代替。如果需要过滤一些事件,你需要在原有事件通道的基础上加
.filter(function)
,参数 function
是返回值是 boolean,参数是 Event 的方法,可用 lambda 表达式。比如只在消息有 At 的时候才触发事件的示例通道如下// java:
// 这样写只是方便理解,实际上可以缩写成下面这句
// GlobalEventChannel.INSTANCE.filter(e -> (e instanceof MessageEvent) && ((MessageEvent)e).getMessage().contains(At.Key));
EventChannel<Event> channel = GlobalEventChannel.INSTANCE.filter(e -> {
if (e instanceof MessageEvent) {
return ((MessageEvent) e).getMessage().contains(At.Key);
}
return false;
});
// 下面这句只是例子,你完全可以忽略
channel.registerListenerHost(xwx);
// kotlin:
// 改自官方文档的例子
var channel = GlobalEventChannel.filter { e is MessageEvent && e.message.contains(At.Key) }
// 下面这句只是例子,你完全可以忽略
channel.registerListenerHost(xwx)
.filter
等方法支持链式,所以你可以在后面再追加几个过滤器。过滤器将会依次进行检查,有一个过滤器没有通过检查就不会执行后面的检查。
此外,
.filterIsInstance(事件类.class)
等价于.filter(e -> e instanceof 事件类) // java
.filter { e is 事件类 } // kotlin
选择好通道,以后本文代码中的 channel 要替换成你获取的通道,如
channel.registerListenerHost(xwx);
你选择公共事件通道时应该替换为
// java:
GlobalEventChannel.INSTANCE.registerListenerHost(xwx);
// kotlin:
GlobalEventChannel.registerListenerHost(xwx)
清楚规则,就开始吧
// java:
// channel.subscribeAlways(事件类, 方法);
// 示例:收到好友消息
channel.subscribeAlways(FriendMessageEvent.class, event -> {
// 做些什么,比如
// 你发送好友消息“你好” 给机器人,机器人就会回复你“Hello Mirai :)”
// 不要着急,有关消息发送的内容会在下一部分讲
if(event.getMessage().contentToString().equals("你好")) {
event.getSubject().sendMessage("Hello Mirai :)");
}
});
// 你也可以像这样
public void onEnable(){
channel.subscribeAlways(FriendMessageEvent.class, this::onFriendMessage);
}
private void onFriendMessage(FriendMessageEvent event){
if(event.getMessage().contentToString().equals("你好")) {
event.getSubject().sendMessage("Hello Mirai :)");
}
}
// 你也可以这样(通过 公共事件通道 获取 单机器人事件通道),并给单机器人事件通道设置事件的处理方法
public void onEnable() {
long qqBotNo = long型QQ号;
/*
GlobalEventChannel.INSTANCE.subscribeAlways(BotOnlineEvent.class, event -> {
Bot bot = Bot.getInstance(qqBotNo);
EventChannel<BotEvent> eventChannel = bot.getEventChannel();
eventChannel.subscribeAlways(FriendMessageEvent.class, this::onFriendMessage);
});
*/
/* 但是 BotOnlineEvent,每个 Bot 上线时都会触发,导致重复获取单机器人事件通道,重复设置事件的处理方法。
* 所以,可增加判断涉及到的 QQ 号,是否是自己想要的QQ号。
*/
GlobalEventChannel.INSTANCE.filterIsInstance(BotOnlineEvent.class)
.filter(e -> event.getBot().getId() == qqBotNo)
.subscribeAlways(BotOnlineEvent.class, event -> {
Bot bot = event.getBot();
EventChannel<BotEvent> eventChannel = bot.getEventChannel();
eventChannel.subscribeAlways(FriendMessageEvent.class, this::onFriendMessage);
});
}
// kotlin:
// channel.subscribeAlways<事件类> { 方法 };
channel.subscribeAlways<FriendMessageEvent> { event ->
// 此处的 this 和 event 都是事件的实例
if (message.contentToString().equals("你好")) {
subject.sendMessage("Hello Mirai :)")
}
}
这种方法可以非常大量地监听事件,当你懒得再去找通道注册部分代码的时候可以用这个方法。
先要新建一个类,使其继承
SimpleListenerHost
(推荐),或者实现ListenerHost
,然后在那个类里面写带单个参数的方法,参数的类型要是事件类型,并且方法要加上 @EventHandler
注解。如果你想要在执行事件时停止监听事件,需要返回值类型要为 ListeningStatus
并返回 ListeningStatus.STOPPED