适用于新人的 mirai 帮助文档【备份】
-
已弃用
该文档现已弃用,新的文档正在编写中,敬请期待
https://wiki.mrxiaom.top/mirai旧文档 Github: MrXiaoM/mirai-docs
旧文档地址: https://mirai-docs.doomteam.fun/
一切内容以 Github 为准
希望论坛字数限制够放得下文档
如果你想实时收到更新通知,请点击右边第二个 铃铛按钮 来关注本贴mirai-docs
面向初学者 的
mirai
非官方帮助文档在本文档中,我将会按照顺序一步一步地教学,
就像打怪升级一样目前正在编写和整理中,敬请期待
注:
初学者
指new miraier
,刚接触mirai
的人观前须知
请先阅读《提问的智慧 (How To Ask Questions The Smart Way)》以确保你在遇到简单的问题时能够
STFW
(到网上搜索) 以及RTFM
(读软件给出的帮助手册) 来解决问题而不是去问别人占用别人的时间,如果你的问题能搜索得到,那你得到的回复很可能是别人搜索了然后发你的,鲁迅曾经说过:“无端空耗别人的时间其实是无异于谋财害命的。”
所以在没到网上搜索之前不要提问!不要提问!不要提问!
在遇到网上搜索/读手册解决不了 (前提是要读过,要实践过确实不行) 再去以正确的方式提问,比如提供尽可能完整的信息,包括但不限于系统版本/所使用的软件或组件版本/进行的详细操作/输出日志或者弹出信息等等,而不是你问在吗,别人回答在,你问问题,别人找你要信息,你发信息,别人觉得信息不够推断不出来你的问题然后再进一步找你要信息…… 直接一步到位把信息提供全面难道不好吗?
如果你有问题,可以在本仓库 发布 issue (没有
Github
账号?点这里注册)。也可以向我的邮箱
coolxiaom95@gmail.com
发送邮件求助,或者加我的QQ 2431208142 (我没设加好友验证,申请添加好友之后请直接说明来意和详细描述问题)。
不管是在哪个渠道联系我,我看到你的消息之后将会在我空闲时尽快回复。
确认你的水平
在查阅这份文档之前,请确保你已掌握
kotlin
或者java
两门语言中的其中一门如果你已掌握一门语言并能使用这门语言进行网络操作,很抱歉目前我没有对这方面的研究,你可以去查看官方文档
如果你是完全不会编程的人类,你只需要查阅用户文档
如果你不是人类…… 能看得懂简体中文并能理解句子的意思的话大概也可以看得懂吧
开始吧
适合不会编程的新手: 用户文档
如果你还不会如何安装和登录机器人,也请查阅用户文档
使用
kotlin
或java
来编写mirai
衍生软件: 开发文档使用其他语言来编写
mirai
衍生软件:暂无文档,建议查阅官方文档在这个文档发布之前,已经有很多前辈编写了
mirai-api-http
或者onebot-kotlin
的其他语言实现,你可以在 mirai 官方开发文档 找到相应语言的社区SDK以便快速开始开发如果你对更新 BlocklyMirai 有兴趣,可以查看 BlocklyMirai 帮助文档
更新
不定期更新,如果你有意愿更新文档,PRs welcome
赞助
本文档不接受赞助。
如果你喜欢这个文档并有意资助,开发组比我更需要赞助,请重定向到 【官方公告】关于论坛赞助/资金流向公示/可持续发展等
计划
- 开发文档以循循渐进的形式写,从部署到登录到事件到消息等等
- 不使用社区 SDK 的非 jvm 语言与 mirai-api-http 交互教程。因为社区 SDK 太多了很难讲明白 XD
- 考虑到 GitBook 日常前端崩溃,所以在本文档基本上完成之后,会在 MiraiForum 技术交流板块发一贴来备份以方便难以访问 Github 的用户查阅
-
用户文档
一般来说,鉴于图形界面类型的 mirai-console 不稳定,我推荐使用纯控制台版本。
检查 Java
首先,你需要确保运行
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 教程 | 菜鸟教程 (推荐)
-
开发文档
本文档只讲比较基础/浅层的部分,若需深入还是需要去看官方的开发文档
文档编写者 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
如果你使用
mirai-core
,就代表你想让最后编译出来的 jar 可以直接打开运行,不需要mirai-console
,但需要这代表着你的项目很大程度上和论坛里大部分的插件不兼容,因为没有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]
- 使用 [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
这是个内置的消息链工具类,
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
自带的语法解析 - 可以直接获取发送人等信息
缺点:
- 需要分配权限
- 对复杂语法无能为力
- Java写起来比Kotlin(看起来)更繁琐
下面的例子展示了如何实现一个简单的复读的指令
// 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 是完全免费且开放源代码的软件,仅供学习和娱乐用途使用
- mirai 不会通过任何方式强制收取费用,或对使用者提出物质条件
- mirai 由整个开源社区维护,并不是属于某个个体的作品,所有贡献者都享有其作品的著作权。
mirai
采用AGPLv3
协议开源。为了整个社区的良性发展,我们强烈建议您做到以下几点:- 间接接触(包括但不限于使用
Http API
或 跨进程技术)到mirai
的软件使用AGPLv3
开源 - 不鼓励,不支持一切商业使用
鉴于项目的特殊性,开发团队可能在任何时间停止更新或删除项目。
mirai 的形象图及项目图标都拥有著作权保护。
在未经过允许的情况下,任何人都不可以使用形象图和图标,有关 mirai 名称来历的介绍原文,用于商业用途或是放置在项目首页,或其他未许可的行为。
衍生软件需声明引用
- 若引用 mirai 发布的软件包而不修改 mirai,则衍生项目需在描述的任意部位提及使用 mirai。
- 若修改 mirai 源代码再发布,或参考 mirai 内部实现发布另一个项目,则衍生项目必须在文章首部或 'mirai' 相关内容首次出现的位置明确声明来源于本仓库 (
https://github.com/mamoe/mirai
)。不得扭曲或隐藏免费且开源的事实。
—— 来自 mamoe/mirai 的 README.md
-
MAH文档
不会/不想学 jvm 语言吗? 试着使用 http 接口吧
mirai-api-http 简称 mah,是 project-mirai 编写的面向其他语言的官方接口,只要你所使用的编程语言能够访问网络,基本上都能够使用 mah。许多 mirai 的社区 SDK 也使用了 mah 作为接口。不要使用 jvm 语言来通过网络连接到 mah,影响运行效率,基本上是脱裤子放屁的行为
本篇文档仅使用通俗语言教如何通过 mah 来监听事件、获取联系人信息、通过 mirai 码发送消息等等,不会贴出任何代码,只会说应该使用什么方法,访问什么地址,需要传入什么参数之类的。
开坑!鸽了!以后有空再写!
安装并配置 MAH
// TODO
连接到 MAH
// TODO
-
BlocklyMirai 帮助
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 的仓库里找到)
具体的积木块开发文档之后会写
-
-
-
好!(请增添发帖内容,不能少于 5 个字符)
-
新文档已更新滑块验证、code=45、签名服务等相关问题的解决方法。