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

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

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

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

主题

48

回帖

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!

本版积分规则