使用方式
/asetu [群号] [模式]
0 DISABLED,
1 RECALL,
2 DOWNLOAD_RECALL,
3 DOWNLOAD,
4 MUTE
禁言默认一分钟,配置文件可调阈值
RECALL 就是撤回…
/asetu [群号] [模式]
0 DISABLED,
1 RECALL,
2 DOWNLOAD_RECALL,
3 DOWNLOAD,
4 MUTE
禁言默认一分钟,配置文件可调阈值
RECALL 就是撤回…
Tips:
- 阅读顺序为从下往上
- Don't trying installing apks on android emulator or trying solving captcha on android emulator
10-29
如果你正在使用苹果系列的手机,使用最原始的方法来解决滑动验证码
...
帖子开头就写了,好好反思你到底有没有认真看过帖子
这次确认好活动时间再发了了
画了一幅屑作 从此除了这次以外再没敢登录pixiv
开始写 BlocklyMirai,立了个好的 flag 然后摸到现在都没写好
https://github.com/project-mirai/mirai-login-solver-selenium
大概是这个吧
另外我觉得这个帖子应该出现在使用交流板块较好
@limitationai
PermissionDeniedException
权限不足,无法撤回别人的消息
ArrayIndexOutOfBoundsException
可能是设计缺陷
BlocklyMirai - 不会编程人士的福音
暂无内容
积木块列表: blockly/javascript/blocks.js
根据积木块生成代码:blockly/javascript/mirai.js
积木块格式示例:
Blockly.Blocks['onenable'] = {
init: function() {
this.appendDummyInput()
.appendField("插件启用时执行");
this.appendStatementInput("content")
.setCheck(null);
this.setColour(230);
this.setTooltip("");
this.setHelpUrl("");
this.setDeletable(false);
this.contextMenu = false;
this.imports = ['net.mamoe.mirai.event.GlobalEventChannel'];
}
};
其中 onenable
是这个而积木块的 ID,数组 imports
的内容会在导出代码的时候添加到代码文件开头的 import 中。避免之后维护困难,请务必在 // BlocklyMirai START
和 // BlocklyMirai END
之间写。添加积木块之后要把积木块添加的工具箱才能给用户使用,这时需要编辑 index.html
,mirai 的工具箱分类在最后面,以 <block type="积木块ID"</block
的格式来填。
生成代码格式示例:
Blockly.Mirai['onenable'] = function(block) {
var statements_content = Blockly.Mirai.statementToCode(block, 'content');
return '@Override\n' +
Blockly.Mirai.INDENT + 'public void onEnable() {\n' +
statements_content + '\n' +
Blockly.Mirai.INDENT + Blockly.Mirai.INDENT + 'GlobalEventChannel.INSTANCE.registerListenerHost(this);\n' +
Blockly.Mirai.INDENT + '}';
};
没什么好说的,说起来太复杂了,去看帮助文档吧
在线积木块编辑器(静态网页,可以在 blockly 的仓库里找到)
具体的积木块开发文档之后会写
不会/不想学 jvm 语言吗? 试着使用 http 接口吧
mirai-api-http 简称 mah,是 project-mirai 编写的面向其他语言的官方接口,只要你所使用的编程语言能够访问网络,基本上都能够使用 mah。许多 mirai 的社区 SDK 也使用了 mah 作为接口。不要使用 jvm 语言来通过网络连接到 mah,影响运行效率,基本上是脱裤子放屁的行为
本篇文档仅使用通俗语言教如何通过 mah 来监听事件、获取联系人信息、通过 mirai 码发送消息等等,不会贴出任何代码,只会说应该使用什么方法,访问什么地址,需要传入什么参数之类的。
开坑!鸽了!以后有空再写!
// TODO
// TODO
本文档只讲比较基础/浅层的部分,若需深入还是需要去看官方的开发文档
文档编写者 MrXiaoM 更倾向于 java 开发,如果本文档提供的 kotlin 代码有错请反馈,有些示例可能没有贴出 kotlin 代码,望理解
你需要先越过 mirai
最大的门槛:登录
登录部分已经在用户文档中描述得非常详尽了,请先把机器人QQ号登录到 mirai
上再进行开发
推荐使用的工具列表,欢迎补充
<!-- 工具之间请用英文逗号 , 再加上一个空格来分隔 -->
<!-- 禁止添加 Notepad++ 等具有争议的工具 -->
类型 | 工具名称 |
---|---|
集成开发环境 (IDE) | IntelliJ IDEA |
文本编辑器 | Visual Studio Code |
压缩/解压缩工具 | 7-zip |
下载工具 | Motrix |
Git 工具 | Git, GitHub Desktop |
在给你的项目新建文件夹之后,你先要决定你要写什么:
是 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
插件,在这里不教如何写 mirai-core
衍生程序了,因为只需要直接跳过,从登录机器人部分开始看即可。
官方文档指北:配置 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 仓库 都可以搜索到。
// 上面三个库在 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'
}
// 你可以直接复制下面的内容来快速导入 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
方法,自行重写即可。
官方文档指北:加载 onLoad | 启用 onEnable | 禁用 onDisable
如果你是使用 mirai-console
且不需要或者已有自动登录,你不需要看这一部分
首先,想要登录就先要新建一个机器人实例,方法如下
(代码中qq号和密码均为玩梗,请勿当真)
官方文档指北: 创建和配置 bot
// 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 还没有登录,获取不到实例。
我们可以使用公共事件通道监听QQ机器人在线事件 BotOnlineEvent
,待 QQ Bot 在线后,再根据QQ号获取 QQ Bot 实例。详见监听一个事件
官方文档指北: 事件系统
现在,我们需要让机器人对某些动作作出回应,这时就需要去监听事件了,当然也可以用mirai-console
自带的指令模块来进行判断与回应。
监听的方式多种多样,可以监听单个事件,监听一个类里所有事件等等
但在这之前,我们需要先选择一个事件通道,你可以选择公共事件通道(在同一个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)
清楚规则,就开始吧
在这个链接里有所有事件类的一句话简述,需要监听什么事件请自取
官方文档指北: 在 EventChannel
监听事件
// 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 :)")
}
}
官方文档指北: 使用 @EventHandler
注解标注的方法监听事件
这种方法可以非常大量地监听事件,当你懒得再去找通道注册部分代码的时候可以用这个方法。
先要新建一个类,使其继承 SimpleListenerHost
(推荐),或者实现ListenerHost
,然后在那个类里面写带单个参数的方法,参数的类型要是事件类型,并且方法要加上 @EventHandler
注解。如果你想要在执行事件时停止监听事件,需要返回值类型要为 ListeningStatus
并返回 ListeningStatus.STOPPED
。代码如下所示
public class EventHost extends SimpleListenerHost{
// 所有方法类型
// T 表示任何 Event 类型.
// void onEvent(T)
// Void onEvent(T)
// ListeningStatus onEvent(T) // 禁止返回 null
@EventHandler
private void onFriendMessage(FriendMessageEvent event){
if (event.getMessage().contentToString().equals("你好")) {
event.getSubject().sendMessage("Hello Mirai :)");
}
}
}
或者 kotlin 函数加注解
object EventHost : SimpleListenerHost {
// 所有函数参数, 函数返回值都不允许标记为可空 (带有 '?' 符号)
// T 表示任何 Event 类型.
// suspend fun T.onEvent(T)
// suspend fun T.onEvent(T): ListeningStatus
// suspend fun T.onEvent(T): Nothing
// suspend fun onEvent(T)
// suspend fun onEvent(T): ListeningStatus
// suspend fun onEvent(T): Nothing
// suspend fun T.onEvent()
// suspend fun T.onEvent(): ListeningStatus
// suspend fun T.onEvent(): Nothing
// fun T.onEvent(T)
// fun T.onEvent(T): ListeningStatus
// fun T.onEvent(T): Nothing
// fun onEvent(T)
// fun onEvent(T): ListeningStatus
// fun onEvent(T): Nothing
// fun T.onEvent()
// fun T.onEvent(): ListeningStatus
// fun T.onEvent(): Nothing
// 所有 Kotlin 非 suspend 的函数都将会在 Dispatchers.IO 中调用
@EventHandler
suspend fun FriendMessageEvent.onFriendMessage() {
if (message.contentToString().equals("你好")) {
subject.sendMessage("Hello Mirai :)")
}
}
}
然后,再去注册监听器即可
// channel.registerListenerHost(类实例);
// Java: channel.registerListenerHost(new EventHost());
// Kotlin: channel.registerListenerHost(EventHost)
注解 @EventHandler 是附带参数的
@EventHandler(priority = 优先级, concurrency = 并发索引, ignoreCancelled = 是否允许事件被取消)
全都是可选参数。跟上面的例子一样,你直接 @EventHandler
都是没问题的
事件优先级 的注释:在广播时, 事件监听器的调用顺序为 (从左到右):
[HIGHEST] -> [HIGH] -> [NORMAL] -> [LOW] -> [LOWEST] -> [MONITOR]
Event.intercept()
拦截后, 优先级较低 (靠右) 的监听器将不会被调用.你已经学会怎么监听事件了,那么学一下怎么新建一个自定义事件吧!
官方文档指北:实现事件
官方文档已经描述得很详细了,而且是本仓库主 MrXiaoM 去 PR 的,就不在这里重复教了。
如果你有开发过 Bukkit 服务端插件 (Bukkit 是 Minecraft Java Edition 的衍生服务端),且把事件系统玩通透了,这部分对你来说会相对简单。
官方文档指北: 联系人
现在,我们该学习如何让机器人发送消息啦。
这里所有代码中的 bot
都是指机器人的实例,请自行新建或获取
在发送消息之前,我们需要获取到要发送到的地方,在 mirai 里消息的目的地叫做联系人。
群聊、好友、群成员都算是联系人,当前获取联系人有两种途径
// 获取好友 bot.getFriend(qq);
// 获取群聊 bot.getGroup(群号);
你肯定有注意到,一些事件里面会有 event.getGroup()
,event.getFriend()
,event.getSender()
之类的方法,它们就是用来获取联系人的
在 kotlin,可以直接用 event.group
,event.friend
,event.sender
获取到联系人之后,我这里统一把联系人用 contact
代替,记得把代码里的 contact
替换成你需要发送消息到的哪个联系人
要发送消息非常简单 contact.sendMessage(消息);
消息不仅可以用下文提到的方法生成,也可以直接用字符串,就像“监听事件”部分的例子那样。详细请见下一部分
如果你有仔细看各个联系人实例的代码补全,你会发现还有很多可以获取或者操作的内容
官方文档指北: 消息系统
在发送消息时,你可以发送消息元素、消息链或者字符串,这部分将会讲如何生成各类消息
这是个内置的消息链工具类,MessageChainBuilder msg = new MessageChainBuilder();
要往里面添加消息元素(如何实例化消息元素会在后面说到),只需要
// java:
// 在最后追加消息(Message、SignleMessage),可以追加字符串
builder.add(消息元素);
// 在某处插入消息(SignleMessage),如果要追加字符串,请追加消息元素 new PlainText("消息");
builder.add(索引, 消息元素);
// 添加列表(Collection)里所有消息,同上
builder.addall(消息元素列表);
// 同上
builder.addall(索引, 消息元素列表);
// kotlin:
val builder = MessageChainBuilder()
// 之后同java
builder.add(消息元素)
要发送给某人的时候,用 builder.build()
来构建消息链,示例如下
MessageChainBuilder builder = new MessageChainBuilder();
builder.add(new At(2431208142L));
builder.add("Hello Mirai :)");
// 构建出来的消息: @MrXiaoM Hello Mirai :)
MessageChain msg = builder.build();
// 要发送消息,上一部分说了
contact.sendMessage(msg);
// kotlin:
val builder = MessageChainBuilder()
builder.add(new At(2431208142L))
builder.add("Hello Mirai :)")
val msg = builder.build() // builder.asMessageChain() 也可以
contact.sendMessage(msg)
你可以使用消息类中的 .plus(消息)
方法来拼接消息元素,下面是例子
// @MrXiaoM Hello Mirai :)
MessageChain msg1 = new At(2431208142L).plus("Hello Mirai :)");
// 你好 @MrXiaoM
MessageChain msg2 = new PlainText("你好 ").plus(new At(2431208142L));
contact.sendMessage(msg1);
// contact.sendMessage(msg2);
// @MrXiaoM Hello Mirai :)
val msg1 = At(2431208142L).plus("Hello Mirai :)")
// 你好 @MrXiaoM
val msg2 = PlainText("你好 ").plus(new At(2431208142L))
contact.sendMessage(msg1)
// contact.sendMessage(msg2)
注意:在 java 拼接消息不能用加号,比如 At 和字符串,如果用加号,At 等消息会被转换成字符串,发送出去将不会 At 到人。你可以理解成要用 String.equals(String)
而不是 String == String
// java:
// 错误示范:
contact.sendMessage(new At(2431208142L) + "测试");
// 正确示范:
contact.sendMessage(new At(2431208142L).plus("测试"));
kotlin中也可以直接使用DSL来构建MessageChain
// kotlin:
// 复制自官方文档
val chain = buildMessageChain {
+PlainText("a")
+AtAll
+Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f")
add(At(123456)) // `+` 和 `add` 作用相同
}
在上面的部分你已经学会如何把各种消息拼起来了,消息元素的列表在这里,点击相应的链接即可查看消息元素的源代码,看看构造函数就知道如何将其实例化了。这里提一下常用例子或者比较特殊的类型,比如需要上传或者用构建器构建的。
new PlainText("正常字符串")
new At(114514L) // @某人
AtAll.INSTANCE // @全体成员
new Face(Face.呲牙) // qq自带表情,new Face(Face.ZI_YA) 是一样的
Dice.random() // 随机骰子,要自定义点数请 new Dice(6);
首先要获取到联系人,上一部分讲了,这一部分还是用 contact
代表联系人
首先你需要先用 contact.uploadImage(图片)
把图片上传到服务器,这个方法的返回值就是图片,要在消息中插入图片把它加进消息链 (MessageChain) 中即可。或者你可以直接用 contact.sendMessage(图片);
来发送。但是你会发现 uploadImage
的参数类型是 ExternalResource
,那么我们要怎么指定图片呢?代码如下,上传并发送的例子
// 文件可以是 byte[]、InputStream、File 等
// 因此你在上传网络图片时可以无需保存到本地硬盘直接上传
ExternalResource res = ExternalResource.create(new File("./sunday.jpg"));
Image image = contact.uploadImage(res);
res.close(); // 记得关闭资源
contact.sendMessage(image);
// 更多可选择操作:
// msg.add(image);
// image.plus("图片加文字");
一样,先获取到联系人,然后 contact.uploadVoice(语音文件)
把语音上传到服务器,这个方法的返回值就是图片,其他同上,代码如下
ExternalResource res = ExternalResource.create(new File("./kawaii.amr"));
// 如果你使用的 mirai 版本是2.7以前(不包括2.7),请用下面标注了 2.0+ 那句
Audio audio = contact.uploadAudio(res); // 2.7+
// Voice audio = contact.uploadVoice(res); // 2.0+
res.close(); // 记得关闭资源
contact.sendMessage(audio);
AudioSupported.uploadAudio(resource)
的注释:
语音文件支持 AMR 和 SILK 格式. 若要支持 MP3 格式, 请参考 mirai-silk-converter
当语音文件过大而被服务器拒绝上传时. (最大大小约为 1 MB)
注意: 由于服务器不一定会检查大小, 该异常就不一定会因大小超过 1MB 而抛出.
在该文档编写时 (Release 2.8.0),mirai 仅支持发送群文件
要获取群文件根目录,则需要使用如下代码,group 代表群聊(联系人)实例
// mirai 版本在2.8或以上的用第一个,否则用第二个
group.getFiles() // 2.8+
group.getFilesRoot() // 2.5+
// 上传文件,其中“进度回调”是可选参数,可不填
// 源码中的注释: 文件路径, **包含目标文件名**. 如 `/foo/bar.txt`. 若是相对目录则基于 [根目录][root] 处理.
// group.getFiles().uploadNewFile(路径, 文件, 进度回调) // 2.8+
// group.getFilesRoot().upload(文件, 进度回调) // 2.5+
ExternalResource res = ExternalResource.create("./测试文件.txt");
// 我忘了该不该在执行上传后就关闭文件,如果不放心可以使用
// ExternalResource res = ExternalResource.create("./测试文件.txt").toAutoCloseable();
// 当然这玩意是在 2.8+ 才有的
group.getFiles().uploadNewFile("测试文件.txt", res); // 2.8+
// group.getFilesRoot().upload(res); // 2.5+
至于怎么看文件列表嘛… 我在这方面没怎么研究,看源码的注释吧
2.8+ AbstractFolder,2.5+ RemoteFile
还有一种生成消息的选择,那就是 Mirai 码
用法非常简单! 因其良好的可序列化和反序列化能力,经常在第三方 SDK 中被使用
// java:
MessageChain msg = MiraiCode.deserializeMiraiCode("字符串", 联系人);
// kotlin:
var msg = "字符串".deserializeMiraiCode(联系人)
// 参数“联系人”可不填
// 但不填“联系人”可能会无法转换图片以及文件等消息
只要这样就能解析字符串中的 Mirai 码,将其转换并拼接到一个 MessageChain 中。
反过来,MessageChain 也能转换成 Mirai 码字符串,直接 message.serializeToMiraiCode()
即可。
有关 Mirai 码字符串的编写规则,在这个链接里有。以下是编写例子
// java:
MessageChain msg = MiraiCode.deserializeMiraiCode("[mirai:at:2431208142] Hello Mirai :)"); // @MrXiaoM Hello Mirai :)
group.sendMessage(msg);
// kotlin:
var msg = "[mirai:at:2431208142] Hello Mirai :)".deserializeMiraiCode() // @MrXiaoM Hello Mirai :)
group.sendMessage(msg)
官方文档指北:指令系统
使用指令模块的优缺点:
优点:
chat-command
插件作为前置,并授予相关权限)mirai-console
自带的语法解析缺点:
下面的例子展示了如何实现一个简单的复读的指令
// Kotlin:
// Plugin.kt
object Plugin: KotlinPlugin(
JvmPluginDescription(// 此处省略)
){
override fun onEnable() {
Echo.register()
}
}
// Echo.kt
object Echo: SimpleCommand(
Plugin,
primaryName = "echo",
secondaryNames = arrayOf("复读"),
description = "复读消息"
) {
@Handler // 标记这是指令处理器 // 函数名随意
suspend fun CommandSender.handle(target: User, message: String) { // 这两个参数会被作为指令参数要求
if (target.id == bot?.id) { // 判断@对象是否是bot
sendMessage(message) // 复读
}
}
}
// java:
// Plugin.java
public class Plugin extends JavaPlugin {
public static final Plugin INSTANCE = new Plugin();
private Plugin() {
super(new JvmPluginDescriptionBuilder(// 此处省略).build());
}
@Override
public void onEnable() {
CommandManager.INSTANCE.registerCommand(Echo.INSTANCE, false);
}
}
// Echo.java
public class Echo extends JSimpleCommand {
public static final Echo INSTANCE = new Echo();
private Echo() {
super(Plugin.INSTANCE, "echo", "复读");
this.setDescription("复读消息");
}
@Handler // 标记这是指令处理器 // 函数名随意
public void handle(CommandSender sender, User target, String msg) {
Bot bot = sender.getBot();
if (bot != null && target.getId() == bot.getId()) { // 判断@对象是否是bot
sender.sendMessage(msg); // 复读
}
}
}
这样在聊天环境(安装chat-command
并分配权限后)发送/echo @<bot> <message>
,bot就会复读这个message
你可能会恼火为什么这样写会拿不到 MessageSource
导致无法在回复用户的消息中使用 QuoteReply
(回复消息)
事实上你只需要把 CommandSender
改为 CommandSenderOnMessage<MessageEvent>
即可 (源码注释 CommandSender.kt#L734-L740,更详细的注释请见点开链接后翻到顶部)。
如果你只想让该命令只接收某种联系人的消息,你可以更改形参
MessageEvent
的类型或者使用以下子类
MemberCommandSenderOnMessage
代表一个真实的 群员 主动在群内发送消息执行指令
FriendCommandSenderOnMessage
代表一个真实的 好友 主动在私聊消息执行指令
TempCommandSenderOnMessage
代表一个 群员 主动在临时会话发送消息执行指令
需要注意的是,这样的话将不会响应控制台的命令。回复消息示例如下:
// kotlin
@Handler
suspend fun CommandSenderOnMessage<MessageEvent>.handle() {
val quote = fromEvent.source.quote()
sendMessage(quote.plus("你好"))
}
// java
@Handler
public void handle(CommandSenderOnMessage<MessageEvent> sender) {
QuoteReply quote = new QuoteReply(sender.getFromEvent().getSource());
sender.sendMessage(quote.plus("你好"));
}
利用事件系统和消息系统等,制作一个简单的练手作吧!
请在编写想要发布的插件之前,搜索一下有没有功能类似且更好的插件。如果有的话,除非你能比别人做得更好,否则最好不要发布出去。如果你是做练手作品,为了让自己更熟练操作 mirai 且不发布,那请随意。
以下是几个可供选择的练习题材,你可以把它们当作关卡,每个题材都去实现
另外我非常不推荐你去写复读机,很容易被别有用心的人让机器人复读奇奇怪怪的东西而导致封号 (亲身经历)
希望不会有人把一些阴间题材给 PR 上来
猜数字
开始游戏后随机生成一个数字,并告知随机数生成范围,让群员发送数字去猜,离正确答案过大过小都会有提示,最后猜到正确答案的群员胜利
群管理
关键词自动回复、自动禁言、主动退群自动拒绝加群请求、机器人无管理员权限时的异常处理等等
涩图机器人
随机发送二次元人物图片,咳咳 论坛那么多色图bot懂的都懂,怎么把它玩出花来又不违规就看你的了
问答/抢答游戏
开始游戏后机器人给出问题,谁先给出正确答案谁得分,最后得分最高的人获胜,要求在游戏结束后要有积分排行榜,群员超时无响应结束游戏等等。
网站/应用爬虫
主动或被动爬取互联网上指定站点的资源。因为各个社区定位千差万别,这里很难说要怎么搞。Github 可以搞 issues 发布提醒等 (有人搞过了),b站可以搞动态发布提醒等 (有人搞过了)
游戏对接
时qq群内机器人可以与游戏内容进行交互,比如 Minecraft 服务器,具体内容大概为玩家信息查询、聊天转发等等。甚至是让机器人干涉游戏内容或者辅助已绑定qq号的玩家找回密码等,自由发挥想象。
因为开发者往往会比普通用户的自由度和能动性会更高,所以我觉得有必要在开发文档再强调下
mirai
采用 AGPLv3
协议开源。为了整个社区的良性发展,我们强烈建议您做到以下几点:
Http API
或 跨进程技术)到 mirai
的软件使用 AGPLv3
开源鉴于项目的特殊性,开发团队可能在任何时间停止更新或删除项目。
在未经过允许的情况下,任何人都不可以使用形象图和图标,有关 mirai 名称来历的介绍原文,用于商业用途或是放置在项目首页,或其他未许可的行为。
https://github.com/mamoe/mirai
)。不得扭曲或隐藏免费且开源的事实。—— 来自 mamoe/mirai 的 README.md
一般来说,鉴于图形界面类型的 mirai-console 不稳定,我推荐使用纯控制台版本。
首先,你需要确保运行 mirai
的系统上安装有 java
。首先我们要打开命令窗口。
如果你是 Windows 系统,同时按下键盘上的微标
键和R
键,通常微标键是键盘左侧 Ctrl
和 Alt
之间图案为 微软公司图标 的按键,在弹出的“运行”框中键入cmd
,点击确定,打开的命令提示符就是命令窗口。
如果你是 Linux 系统或者 Mac 系统,请直接找到终端并打开。
在命令窗口中键入 java -version
并按下 Enter
键。若提示“不是有效的命令…”之类的消息,则代表你没有安装 java 或者环境变量未被正确配置。如果提示了包含有 version "xxx"
之类的英文,则表明 java 正常
如果你没有安装 java 推荐使用 64 位的 OpenJDK
或 OracleJDK
的 11 或以上版本,如果你不清楚哪个版本更好,哪个方便就用哪个,使用 OracleJDK
即可,安装包的下载地址如下,下载后安装即可: Java Downloads | Oracle
如果你安装了 java 仍出现“不是有效的命令…”之类的提示,请到搜索引擎搜索关键字jdk 环境变量配置
搜寻相关教程。点击这里,百度一下。
目前要启动 mirai-console
有 原始启动法 和 启动器启动法 两种方法。
使用启动器 Mirai Console Loader (简称 MCL) 可以实现自动更新等功能。
使用原始启动法更朴素,不需要经过启动器这一层,但是没有自动更新等功能。
一般来说推荐使用启动器启动法,点击这里直接转跳到该部分 论坛无法转跳
在 Windows 下: 所有操作都需要在关闭“隐藏已知文件类型的扩展名”后进行
先前往 maven 仓库 搜索并下载以下几个库,格式是.jar
,版本要对应:
mirai-core-all
mirai-console
mirai-console-terminal
前两个文件下载文件名里版本后面必须有 -all.jar 结尾才行,如 mirai-core-all-2.8.0-all.jar
// 上面三个库在 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
),并在这个文件夹内新建一个 libraries
文件夹,将下载的三个库复制到里面,回到 mirai
文件夹,新建文本文档并重命名为 启动.bat
,鼠标右键点击它并点击编辑
,将以下内容复制并粘贴进去,保存,然后双击打开 启动.bat
即可
@echo off
title Mirai
java -cp ./libraries/* net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
pause
注:如果出现了 java.security.NoSuchProviderException: JCE cannot authenticate the provider 异常
此原因为 mirai 使用 shadowJar 打包后, 没有签名导致, 解决方法为在运行时路径添加
org.bouncycastle:bcprov-jdk15on
, 并确保该库第一个加载 (即保证该库比mirai-core-all
先加载)[https://search.maven.org/search?q=g:org.bouncycastle AND a:bcprov-jdk15on](https://search.maven.org/search?q=g:org.bouncycastle AND a:bcprov-jdk15on)
这个异常并不影响 mirai
的正常运行,如果你看它不顺眼可以在上面的链接下载那个库,一样丢到 libraries
文件夹,注意,这个库在文件夹里必须要按名称排序排在第一位,如果你下载的文件都没有改过名可以不用管顺序。mcl 会自动下载这个库,所以用启动器启动法可以不用管这些零零碎碎的问题。
先前往 MCL 发布地址 下载最新版启动器,一般点击 Assets
下面的 mcl-*.*.*.zip
即可
将下载的文件解压到你记得的地方,然后双击打开文件 mcl.cmd
即可
在 Linux 系统是 执行
./mcl
,如果权限不够,就执行sudo sh ./mcl
,下同
若错误提示前面部分有 java.lang.UnsupportedClassVersionError
字样,请确保你已安装 jdk11
并且环境变量已配置正确。
如果已安装并且已正确配置环境变量,那么我们需要让 mcl 强行使用 jdk11
来启动,请鼠标右键点击 mcl.cmd
,点击编辑
。
(这个错误处理教程仅适合 Windows,Linux 用户如果出现这个问题请先百度,无法解决再来找我)
注意,编辑时不要打开中文输入法,避免输入了程序不认的中文标点符号!
将第二行 set JAVA_BINARY=java
中结尾的 java
替换为你 jdk11
里面 java.exe
的绝对路径,并且需要加上英文引号。
一般 java.exe
路径都默认在 C:\Program Files\Java\***\bin\java.exe
取决于你安装 java 时填写的路径,如果你没有修改过路径就是这个,修改过就把***以及前面的内容换成安装路径。
按照路径一个一个文件夹点进去看看有没有这个文件,有的话就填进去。
比如我的 jdk11
路径是 C:\Program Files\Java\jdk-11.0.13\bin\java.exe
修改完后的完整 mcl.cmd
内容如下,不要直接复制,仅作参考
@echo off
set JAVA_BINARY="C:\Program Files\Java\jdk-11.0.13\bin\java.exe"
%JAVA_BINARY% -jar mcl.jar %*
改好后再次双击打开文件 mcl.cmd
即可
在启动 mirai
之后,你将进行最麻烦的操作,就是登录。
登录是 mirai
最大的门槛,过了这道坎后面的路就会轻松得多 (对技术力充足的人来说)
在成功打开 mirai-console
之后,在控制台执行命令 login QQ号 密码
通常第一次登录都会提示登录失败,或者提示需要滑动验证或设备锁验证,如果你在执行命令后有提示 Login successfully
,恭喜你,这个部分没你什么事了,你可以去看下一部分的内容了。
登录失败一般有以下几种原因 (复制自官方文档 Bots.md#常见登录失败原因,请以官方文档为准)
错误信息 | 可能的原因 | 可能的解决方案 |
---|---|---|
当前版本过低 | 密码错误 | 检查密码或修改密码到 16 位以内 |
当前上网环境异常 | 设备锁 | 开启或关闭设备锁 (登录保护) |
禁止登录 | 需要处理滑块验证码 | project-mirai/mirai-login-solver-selenium |
密码错误 | 密码错误或过长 | 手机协议最大支持 16 位密码 (#993). 在官方 PC 客户端登录后修改密码 |
【重要】有关无法登录的解决方案,可以前往论坛查看。论坛的方法会更新,请随时留意
目前要处理滑动验证码,请确保你有一台已登录移动端QQ并且可扫码的设备和一台安卓手机,当然只有一台安卓手机也是可以的,在该安卓手机上下载并安装 mzdluo123/MiraiAndroid (2021/9/6更新的下载地址: https://install.appcenter.ms/users/mzdluo123/apps/miraiandroid/distribution_groups/release)
在 MiraiAndroid
上使用屏幕右上角的自动登录来进行登录,并点击通知栏提示需要验证的通知来进行滑动验证和扫码通过设备锁验证
在 MiraiAndroid
提示 Login successfully
之后,点击左上角的三条杠打开侧边栏菜单,点击工具,在选择一个bot处选中机器人QQ号,点击导出DEVICE.JSON并想办法把导出的文件发到电脑上。
接着回到 mirai-console
所在文件夹(上文“选择启动方式”中提到的 mirai
文件夹或者mcl解压路径)
如果文件夹里有 device.json
就把手机发过来的 device.json
覆盖过去。(注: 在更旧版本的 mirai
中,device.json
也叫做 deviceInfo.json
)
如果文件夹里没有 device.json
而有 bots
文件夹,那就打开 bots
文件夹里以QQ号命名的文件夹,把手机发过来的 device.json
覆盖过去。
在替换好文件之后,再次打开 mirai-console
,执行命令 login QQ号 密码
如果出现 Login successfully
就算成功了,你可以进入下一步了。
如果你身边没有安卓手机或者MiraiAndroid安装后不可用,又或者你正在使用苹果系列的手机
10-29
如果你正在使用苹果系列的手机,使用最原始的方法来解决滑动验证码https://github.com/project-mirai/mirai-login-solver-selenium
https://docs.mirai.mamoe.net/mirai-login-solver-selenium/
如果还是出现错误,请根据前文描述来排查问题。实在无法解决可以先去论坛问问。
本部分更偏向于用户,如果你是开发者请去阅读开发文档。
在登录QQ到 mirai
之后,你就可以到论坛搜寻现成的插件来享受mirai
带来的便利了
论坛里的插件发布板块:https://mirai.mamoe.net/category/11
BlocklyMirai 现已发布,你可以用它来免代码编写qq机器人,只需要跟“积木编程”一样拖动积木块,但它目前还处于 Alpha 测试阶段,积木块并不全面,你可以先了解下,之后完善了再使用。
除了论坛以外,不要忘记还有互联网中四处散落的资源,它们有些可能还没有被整合,等着你去探索。
顺便一提,iTXTech/mirai-native 可以加载酷Q插件
与大部分
酷Q
插件兼容,不支持CPK
和解包的DLL
,需获取DLL
和JSON
原文件,JSON
文件不支持注释。
至此,用户文档已到结尾,虽然不编程也能玩 mirai
,但是具有局限性。
如果你对使用 mirai
编写一个机器人感兴趣并具有极客精神,这或许是你学习编程的一个好机会,选择好你想要学的语言,从菜鸟教程开始吧!
Kotlin 语言中文站 (推荐)
Kotlin 教程 | 菜鸟教程 (推荐)