【科技圆桌】原生Mirai实现QQ机器人
前言Mirai 是继CQ之用的最多的机器人框架,目前主要支持两种方式运行:
[*]Mirai开发者提供 mirai console 作为宿主程序,开发者的代码已插件的形式运行
[*]把 mirai 作为一个普通的 java 库使用,开发者完全掌控整个程序
我选择了后者,原因如下:
[*]懒,不想因为插件更新了每次都要手动下载、配置
[*]懒,自己打包一个容器显然更方便
[*]懒,GitHub整个CI,代码写完就自动部署
当然,使用原生 Mirai 的缺点也很明显:不能使用其他人开发的插件本贴完全针对原生 Mirai ,如果你没有动手实现自己想要的功能的愿望,那么本文不适合你目标
这个帖子的目标,就是帮助有编程基础的同学从第一行代码开始实现一个 涩图机器人
https://i.imgtg.com/2022/11/01/RrtPi.png
准备库和我一样懒的话就打开GitHub,克隆个 project-mirai/mirai-hello-worldmirai是跑在 JVM 上的,这里我使用 Gradle 作为构建工具因此你需要准保好:
[*]正确安装 JDK(其实就是去官网下个压缩包,加个环境变量)
[*]安装 Gradle(同上)
[*]一个好的网络环境(这个我也没办法帮你,自己解决)
首先找到一个干净的文件夹,打开命令行,初始化项目,如果不懂选项的意思,照着我的选就行了
$ gradle init
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) 2
Select implementation language:
1: C++
2: Groovy
3: Java
4: Kotlin
5: Scala
6: Swift
Enter selection (default: Java) 4
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) 2
Select test framework:
1: JUnit 4
2: TestNG
3: Spock
4: JUnit Jupiter
Enter selection (default: JUnit 4)
Project name (default: demo): 随你
Source package (default: demo): me.bot
BUILD SUCCESSFUL
2 actionable tasks: 2 executed
打开自己喜欢的编辑器,当然,开发 Java , IDEA 天下第一!编辑 build.gradle.kts
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
plugins {
application
kotlin("jvm") version "1.7.20" id("io.ktor.plugin") version "2.1.3"
kotlin("plugin.serialization") version "1.7.20"
id("com.github.johnrengelman.shadow") version "7.1.2"
}
group = "robot"
version = "0.1.0"
repositories {
mavenCentral()
}
tasks.withType(KotlinJvmCompile::class.java) {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
// Mirai 库
api("net.mamoe:mirai-core-api:2.12.3")
runtimeOnly("net.mamoe:mirai-core:2.12.3")
// 日志库
implementation("org.slf4j:slf4j-log4j12:2.0.3")
// Json 序列化
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
// Ktor Http 请求 implementation("io.ktor:ktor-client-core:2.1.3")
implementation("io.ktor:ktor-client-cio:2.1.3")
}
tasks.jar {
manifest {
attributes(
"Main-Class" to "me.bot.Application",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
)
}
}
截至我写完这篇文章,Mirai的最新版是 2.12.3,你可以在这里看到最新版:Releases · mamoe/mirai同样的,kotlin 也推荐最新版,此外,buildSrc 之类的不在本文范围内编辑 src/main/resources/log4j.properties
log4j.rootLogger=INFO,stdout
log4j.logger.mirai=INFO
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t%m%n
然后我们先写个 Hello world 吧删除 src/main/kotlin/Application.kt 下的文件,然后编辑 src/main/kotlin/Application.kt
package me.bot
object Application {
private val logger = LoggerFactory.getLogger(this.javaClass)
@JvmStatic
fun main(args: Array<String>): Unit = runBlocking {
logger.info("Hello world!")
}
}
运行看看是不是打印了 "Hello world"如果是的,那么环境已经准备成功了 先发个图吧这一楼我们实现机器人的登录和发消息要启动QQ机器人,当然需要一个QQ号,然后登录就一句话object Application {
private val logger = LoggerFactory.getLogger(this.javaClass)
@JvmStatic
fun main(args: Array<String>): Unit = runBlocking {
// 配置日志,为了不报错,可以不搞
logger.asMiraiLogger()
// 账号、密码
val bot = BotFactory.newBot(100001L, "MaHuaTengSiMa") {
// 设备消息,以文本形式自动生成 `device.json`
fileBasedDeviceInfo()
// 协议,我这里使用的是 安卓平板 协议
protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD
}.alsoLogin()
}
}
然后让机器人发点东西吧,准备一张图放到项目根目录,或者只发条消息也行
bot.getFriend(114514L) // 接收消息的 QQ 号
?.also { master -> master.sendMessage("QQ机器人已就位" + Face(Face.KE_AI)) }
?.also { master -> master.sendImage(File("文件名.png")) }
?: bot.logger.warning("未找到 master")
不懂,但是感觉很nb 获取一张 Setu要实现随机 Setu 必然是 LSP,咳,就必然需要大量图源,幸运的是已经有LSP大佬做好了这里我们使用 lolicon.app 的接口(一看域名就是个 hentai),链接是 随机色图 (lolicon.app)当然你也可以去 lolicon.app 看看https://i.imgtg.com/2022/11/02/RyyDD.md.png这里只讲基础的,看上图那么多就行了如果你直接打开会返回一个Json,类似这样{
"error": "",
"data": [
{
"pid": 91736941,
"p": 0,
"uid": 5101014,
"title": "commission",
"author": "ColdNoodle",
"r18": false,
"width": 900,
"height": 1500,
"tags": [
"女の子",
"女孩子"
],
"ext": "jpg",
"uploadDate": 1628117887000,
"urls": {
"original": "https://i.pixiv.re/img-original/img/2021/08/05/07/58/07/91736941_p0.jpg"
}
}
]
}也可以使用 POST 请求自定义一些选项。这里我准备实现一下配置:
[*]r18 = 0
[*]tag = 纳西妲 或者 白丝萝莉
[*]proxy = false 服务器在国外,不需要代理
定义数据
首先定义请求体@Serializable
data class RequestData(
val r18: Int,
val tag: List<List<String>>,
val proxy: Boolean
)
没啥可说的,看单词就能知道是啥意思然后是响应体,这个依照上楼的例子对照着写就行了@Serializable
data class ResponseData(
val error: String,
val data: List<SetuInfo>
)
@Serializable
data class SetuInfo(
val pid: Long,
val p: Int,
val uid: Long,
val title: String,
val author: String,
val r18: Int,
val width: Int,
val height: Int,
val tags: List<String>,
val ext: String,
val uploadDate: Long,
val urls: Map<String, String>
)
有点多,但是也很简单的。
获取 Setu简简单单,看注释就能懂// 获取一张涩图的信息
suspend fun getSetuInfo(): List<SetuInfo>? {
// 发送 POST 请求
val request = HttpClient(CIO).post("https://api.lolicon.app/setu/v2") {
// 请求体是一个 JSON
contentType(ContentType.Application.Json)
setBody(RequestData(
r18 = 0, // 我是正人君子
tag = listOf(
listOf("纳西妲"), // 嘿嘿嘿,嘿嘿嘿.......
listOf("白丝", "萝莉")
),
proxy = false
))
}
// 接收数据
val response = request.body<ResponseData>()
// 如果 data 不为空就返回 setu
return response.data.ifEmpty { null }?.first()
}
suspend fun getPixivImage(url: String): InputStream {
val request = HttpClient(CIO).get(url) {
// 伪造请求头
header("Referer", "https://www.pixiv.net/")
header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.26")
}
return request.body()
}
最后,我们把这张图发出去!
object Application {
private val logger = LoggerFactory.getLogger(this.javaClass)
@JvmStatic
fun main(args: Array<String>): Unit = runBlocking {
// 配置日志,为了不报错,可以不搞
logger.asMiraiLogger()
// 账号、密码
val bot = BotFactory.newBot(100001L, "MaHuaTengSiMa") {
// 设备消息,以文本形式自动生成 `device.json`
fileBasedDeviceInfo()
// 协议,我这里使用的是 安卓平板 协议
protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD
}.alsoLogin()
// 监听好友的消息
bot.eventChannel.subscribeAlways<FriendMessageEvent> {
// 如果是指定的好友发出的
if (sender.id == 114514L) {
getSetuInfo()?.run {
val setu = getPixivImage(urls["original"]!!)
// 获取图片,发出去
subject.sendImage(setu)
}
}
}
}
}【完】
页:
[1]