找回密码
 注册SCIFIFANS!
首页 科技专区 编程开发 【科技圆桌】原生Mirai实现QQ机器人

【科技圆桌】原生Mirai实现QQ机器人

9
回复
3549
查看
[ 复制链接 ]

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
<

用邮箱/用户名来登录sci-fifans!

您需要 登录 才可以下载或查看,没有账号?注册SCIFIFANS!

x
前言

Mirai 是继CQ之用的最多的机器人框架,目前主要支持两种方式运行:
  • Mirai开发者提供 mirai console 作为宿主程序,开发者的代码已插件的形式运行
  • 把 mirai 作为一个普通的 java 库使用,开发者完全掌控整个程序

我选择了后者,原因如下:
  • 懒,不想因为插件更新了每次都要手动下载、配置
  • 懒,自己打包一个容器显然更方便
  • 懒,GitHub整个CI,代码写完就自动部署

当然,使用原生 Mirai 的缺点也很明显:不能使用其他人开发的插件
本贴完全针对原生 Mirai ,如果你没有动手实现自己想要的功能的愿望,那么本文不适合你
目标
这个帖子的目标,就是帮助有编程基础的同学从第一行代码开始实现一个 涩图机器人


【科技圆桌】原生Mirai实现QQ机器人

【科技圆桌】原生Mirai实现QQ机器人

使用道具 举报

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
准备库
和我一样懒的话就打开GitHub,克隆个 project-mirai/mirai-hello-world
mirai是跑在 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) [1..4] 2
Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 4
Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 2
Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..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"
如果是的,那么环境已经准备成功了

使用道具 举报

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
先发个图吧
这一楼我们实现机器人的登录和发消息
要启动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")




使用道具 举报

该用户从未签到

14

主题

49

回帖

339

积分

LV.2

积分
339
不懂,但是感觉很nb
啊这 
2022-11-4 10:36
其实很简单哦,会玩 我的世界 就会用233 会玩我的世界就安装了Java,安装了Java就能跑 
2022-11-2 22:05
于数据中湮灭,于信息中重生

使用道具 举报

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
获取一张 Setu
要实现随机 Setu 必然是 LSP,咳,就必然需要大量图源,幸运的是已经有LSP大佬做好了
这里我们使用 lolicon.app 的接口(一看域名就是个 hentai),链接是 随机色图 (lolicon.app)
当然你也可以去 lolicon.app 看看
这里只讲基础的,看上图那么多就行了
如果你直接打开会返回一个Json,类似这样
  1. {
  2.     "error": "",
  3.     "data": [
  4.         {
  5.             "pid": 91736941,
  6.             "p": 0,
  7.             "uid": 5101014,
  8.             "title": "commission",
  9.             "author": "ColdNoodle",
  10.             "r18": false,
  11.             "width": 900,
  12.             "height": 1500,
  13.             "tags": [
  14.                 "女の子",
  15.                 "女孩子"
  16.             ],
  17.             "ext": "jpg",
  18.             "uploadDate": 1628117887000,
  19.             "urls": {
  20.                 "original": "https://i.pixiv.re/img-original/img/2021/08/05/07/58/07/91736941_p0.jpg"
  21.             }
  22.         }
  23.     ]
  24. }
复制代码
也可以使用 POST 请求自定义一些选项。这里我准备实现一下配置:
  • r18 = 0
  • tag = 纳西妲 或者 白丝萝莉

  • proxy = false 服务器在国外,不需要代理


使用道具 举报

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
定义数据
首先定义请求体
  1. @Serializable
  2. data class RequestData(
  3.     val r18: Int,
  4.     val tag: List<List<String>>,
  5.     val proxy: Boolean
  6. )
复制代码

没啥可说的,看单词就能知道是啥意思
然后是响应体,这个依照上楼的例子对照着写就行了
  1. @Serializable
  2. data class ResponseData(
  3.     val error: String,
  4.     val data: List<SetuInfo>
  5. )

  6. @Serializable
  7. data class SetuInfo(
  8.     val pid: Long,
  9.     val p: Int,
  10.     val uid: Long,
  11.     val title: String,
  12.     val author: String,
  13.     val r18: Int,
  14.     val width: Int,
  15.     val height: Int,
  16.     val tags: List<String>,
  17.     val ext: String,
  18.     val uploadDate: Long,
  19.     val urls: Map<String, String>
  20. )
复制代码

有点多,但是也很简单的。


使用道具 举报

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
获取 Setu
简简单单,看注释就能懂
  1. // 获取一张涩图的信息
  2. suspend fun getSetuInfo(): List<SetuInfo>? {
  3.     // 发送 POST 请求
  4.     val request = HttpClient(CIO).post("https://api.lolicon.app/setu/v2") {
  5.         // 请求体是一个 JSON
  6.         contentType(ContentType.Application.Json)
  7.         setBody(RequestData(
  8.             r18 = 0, // 我是正人君子
  9.             tag = listOf(
  10.                 listOf("纳西妲"), // 嘿嘿嘿,嘿嘿嘿.......
  11.                 listOf("白丝", "萝莉")
  12.             ),
  13.             proxy = false
  14.         ))
  15.     }
  16.     // 接收数据
  17.     val response = request.body<ResponseData>()
  18.    
  19.     // 如果 data 不为空就返回 setu
  20.     return response.data.ifEmpty { null }?.first()
  21. }

  22. suspend fun getPixivImage(url: String): InputStream {
  23.     val request = HttpClient(CIO).get(url) {
  24.         // 伪造请求头
  25.         header("Referer", "https://www.pixiv.net/")
  26.         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")
  27.     }
  28.     return request.body()
  29. }
复制代码

使用道具 举报

该用户从未签到

10

主题

51

回帖

404

积分

Judgement

积分
404
最后,我们把这张图发出去!


  1. object Application {
  2.     private val logger = LoggerFactory.getLogger(this.javaClass)

  3.     @JvmStatic
  4.     fun main(args: Array<String>): Unit = runBlocking {
  5.         // 配置日志,为了不报错,可以不搞
  6.         logger.asMiraiLogger()
  7.         
  8.         // 账号、密码
  9.         val bot = BotFactory.newBot(100001L, "MaHuaTengSiMa") {
  10.             // 设备消息,以文本形式自动生成 `device.json`
  11.             fileBasedDeviceInfo()
  12.             // 协议,我这里使用的是 安卓平板 协议
  13.             protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD
  14.         }.alsoLogin()
  15.         // 监听好友的消息
  16.         bot.eventChannel.subscribeAlways<FriendMessageEvent> {
  17.             // 如果是指定的好友发出的
  18.             if (sender.id == 114514L) {
  19.                 getSetuInfo()?.run {
  20.                     val setu = getPixivImage(urls["original"]!!)
  21.                     // 获取图片,发出去
  22.                     subject.sendImage(setu)
  23.                 }
  24.             }
  25.         }
  26.     }
  27. }
复制代码
【完】

使用道具 举报

您需要登录后才可以回帖 登录 | 注册SCIFIFANS!

本版积分规则