更新消息处理逻辑

master
pan 4 years ago
parent 6091c574f4
commit 153f38cf10
  1. 5
      README.md
  2. 2
      src/main/kotlin/com/pqh/qqbot/TestController.kt
  3. 158
      src/main/kotlin/com/pqh/qqbot/TestQQBot.kt
  4. 5
      src/main/resources/application.yaml
  5. 41
      src/main/resources/logback-spring.xml
  6. 19
      src/test/kotlin/com/pqh/qqbot/AppTest.kt
  7. 9
      src/test/kotlin/com/pqh/qqbot/QqbotApplicationTests.kt

@ -20,9 +20,10 @@ mirai 既可以作为项目中的 QQ 协议支持库, 也可以作为单独的
订阅指令:群用户向机器人订阅消息的请求指令。 订阅指令:群用户向机器人订阅消息的请求指令。
一条合法的指令由 `>>>`+`一级指令`+`二级指令`+`占位符`+`参数`组成,比如查询pid(P站图片id)的指令是`>>>query pixiv -p 80353815` 一条合法的指令由 `>>>`+`一级指令`+`二级指令`+`占位符`+`参数`组成,比如查询pid(P站图片id)的指令是`>>>query pixiv -p 80353815`
模块:指令集调用的处理器,应用依赖的核心逻辑单元,称之为模块。模块本身不依赖于QQ BOT运行环境,是可独立运行的应用 模块:指令集调用的处理器,应用依赖的核心逻辑单元,称之为模块。模块本身不依赖于QQ BOT运行环境,是可独立运行的应用
目前计划支持`kotlin`和`java`开发的模块。模块内部**封装**了群用户查询消息的**处理逻辑**,这里的处理逻辑实际上是通过`HTTP/HTTPS`协议向服务端爬取消息,进行解析并返回的过程,所以模块暂统称为`爬虫模块`,每个爬虫模块必须提供**输入**和**输出**,输入指解析QQ BOT发送的指令,而响应给QQ BOT的处理结果就是输出。 目前计划支持`kotlin`和`java`开发的模块。模块内部**封装**了群用户查询消息的**处理逻辑**,这里的处理逻辑实际上是通过`HTTP/HTTPS`协议向服务端爬取消息,进行解析并返回的过程,所以模块暂统称为`爬虫模块`,每个爬虫模块必须提供**输入**和**输出**,输入指解析QQ BOT发送的指令,而响应给QQ BOT的处理结果就是输出。
RSSHub:一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。RSSHub 借助于开源社区的力量快速发展中,目前已适配数百家网站的上千项内容
RSSHub:一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。RSSHub 借助于开源社区的力量快速发展中,目前已适配数百家网站的上千项内容。
* ### 查询指令速查表: * ### 查询指令速查表:

@ -3,8 +3,6 @@ package com.pqh.qqbot
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController

@ -1,18 +1,17 @@
package com.pqh.qqbot package com.pqh.qqbot
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.*
import io.ktor.util.hex import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.* import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.closeAndJoin import net.mamoe.mirai.closeAndJoin
import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.GroupMessageEvent import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.sourceId import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.message.sourceTime import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
@ -21,45 +20,48 @@ import org.springframework.beans.factory.annotation.Value
import org.springframework.scheduling.annotation.Async import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.util.ResourceUtils
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import java.time.Duration
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import kotlin.collections.HashMap
import javax.annotation.PostConstruct
const val mongoDBName: String = "MongoDB" const val mongoDBName: String = "MongoDB"
class CustomLogger(val logger: Logger,override val identity: String?) :MiraiLoggerPlatformBase(){ class CustomLogger(val logger: Logger, override val identity: String?) : MiraiLoggerPlatformBase() {
override fun debug0(message: String?, e: Throwable?) { override fun debug0(message: String?, e: Throwable?) {
logger.debug(message,e) val s = Thread.currentThread().getStackTrace()
logger.debug("(${s.get(4)})-${message}", e)
} }
override fun error0(message: String?, e: Throwable?) { override fun error0(message: String?, e: Throwable?) {
logger.error(message,e) val s = Thread.currentThread().getStackTrace()
logger.error("(${s.get(4)})-${message}", e)
} }
override fun info0(message: String?, e: Throwable?) { override fun info0(message: String?, e: Throwable?) {
logger.info(message,e) val s = Thread.currentThread().getStackTrace()
logger.info("(${s.get(4)})-${message}", e)
} }
override fun verbose0(message: String?, e: Throwable?) { override fun verbose0(message: String?, e: Throwable?) {
logger.trace(message,e) val s = Thread.currentThread().getStackTrace()
logger.trace("(${s.get(4)})-${message}", e)
} }
override fun warning0(message: String?, e: Throwable?) { override fun warning0(message: String?, e: Throwable?) {
logger.warn(message,e) val s = Thread.currentThread().getStackTrace()
logger.warn("(${s.get(4)})-${message}", e)
} }
} }
@Service @Service
class TestQQBot { class TestQQBot {
val logger=LoggerFactory.getLogger(TestQQBot::class.java) val logger = LoggerFactory.getLogger(TestQQBot::class.java)
/** /**
* 程序入口 * 程序入口
*/ */
@ -68,7 +70,8 @@ class TestQQBot {
@Qualifier(mongoDBName) @Qualifier(mongoDBName)
lateinit var db: DB lateinit var db: DB
val maxCount = 3 //消息缓存,引用回复
val map = HashMap<String, GroupMessageEvent>()
data class Account(val qqId: Long, val adminQQId: Long, val pwd: String, val forward: Long) data class Account(val qqId: Long, val adminQQId: Long, val pwd: String, val forward: Long)
@ -92,13 +95,14 @@ class TestQQBot {
GlobalScope.launch { GlobalScope.launch {
val config = BotConfiguration.Default val config = BotConfiguration.Default
config.fileBasedDeviceInfo("qqbot.json") config.fileBasedDeviceInfo("qqbot.json")
config.botLoggerSupplier = { CustomLogger(logger, "Bot ${it.id}") }
config.networkLoggerSupplier = { CustomLogger(logger, "Net ${it.id}") }
try {
val bot = Bot(account.qqId, account.pwd, config) val bot = Bot(account.qqId, account.pwd, config)
bot.logger.follower=CustomLogger(logger,"Bot ${bot.id}")
bot.logger.info("日志log")
bot.alsoLogin() bot.alsoLogin()
val group = bot.getGroup(account.forward) val group = bot.getGroup(account.forward)
val admin = bot.getFriend(account.adminQQId)
bot.subscribeMessages { bot.subscribeMessages {
sentBy(account.adminQQId) { sentBy(account.adminQQId) {
case(AdminCommand.CLOSE.name) { case(AdminCommand.CLOSE.name) {
@ -108,57 +112,79 @@ class TestQQBot {
} }
always { always {
if(message.isContentNotEmpty()) { if (message.isContentNotEmpty()) {
db.saveToDB(this, account) db.saveToDB(this, account)
if (this is GroupMessageEvent&&sender.group!=group) { bot.logger.info("消息id:${message.id}:${message.internalId}")
GlobalScope.launch { //处理除指定群以外的群消息
var title = sender.group.name if (this is GroupMessageEvent && sender.group != group) {
val s = group.sendMessage(PlainText("转发${this.group.name}${senderName}发送的消息\n") + message)
logger.info("转发消息id:${s.sourceId}:${s.sourceInternalId}")
map.put("${s.sourceId}:${s.sourceInternalId}", this)
}
//处理指定群消息
else if (this is GroupMessageEvent && sender.group.id == group.id) {
val quoteReply = message.get(QuoteReply.Key)
val key = "${quoteReply?.source?.id}:${quoteReply?.source?.internalId}"
if (quoteReply != null && map.containsKey(key)) {
val messageEvent = map.get(key)!!
buildMessageChain {
message.forEach { message.forEach {
if (it.isPlain()) { if (!(it is QuoteReply || it is At)) {
if (it.content.length > 5) { +it
title = it.content.substring(0, 5)
} }
return@forEach
} }
}.let {
if (it.isContentNotEmpty()) {
it.sendTo(messageEvent.sender)
} }
var count = 0
while (count++ < maxCount) {
try {
val msg=buildForwardMessage(displayStrategy = ForwardTitle(title)) {
sender.id named senderName at time says message
} }
msg.sendTo(group)
break
} catch (e: IllegalStateException) {
bot.logger.error("机器人转发信息发生异常:${e}\n再尝试发送一次")
delay(Duration.ofSeconds(3).toMillis())
} }
} }
//处理非管理员的私聊信息或者临时会话
else if (this is FriendMessageEvent && this.sender.id != admin.id || this is TempMessageEvent) {
admin.sendMessage(buildMessageChain {
+PlainText("来自QQ(${sender.id})[${senderName}]发送的信息:")
}.plus(message))
}
//处理来自管理员私聊的信息
else if (this is FriendMessageEvent && this.sender.id == admin.id) {
val targetMsg = message.get(PlainText.Key)
val split = "@"
if (targetMsg != null && targetMsg.isContentNotEmpty() && targetMsg.content.split("\r")[0].matches(Regex("\\d+${split}\\d+"))) {
val s = targetMsg.content.split("\r")[0].split(split)
bot.getGroup(s[0].toLong()).get(s[1].toLong()).sendMessage(
buildMessageChain {
message.filterIndexed { index, i ->
index > 2
}.forEach {
+it
}
}
)
} }
}else if(this is GroupMessageEvent&&sender.group==group){
bot.logger.info("不能套娃")
}else{
bot.logger.info("${this.javaClass}类型不转发")
} }
}else{ } else {
bot.logger.info("信息为空不转发") bot.logger.info("信息为空不转发")
} }
} }
}
} catch (e: Exception) {
logger.error("初始化机器人发生异常信息:${e}")
}
} }
} }
} }
/** /**
* 保存QQ消息图片 * 保存QQ消息图片
*/ */
@Component @Component
class SaveImg { class SaveImg {
val logger=LoggerFactory.getLogger(SaveImg::class.java) val logger = LoggerFactory.getLogger(SaveImg::class.java)
@Value("\${qq-image}") @Value("\${qq-image}")
lateinit var staticLocations: String lateinit var staticLocations: String
@ -171,6 +197,7 @@ class TestQQBot {
@KtorExperimentalAPI @KtorExperimentalAPI
fun saveImage(logger: MiraiLogger, url: String): String? { fun saveImage(logger: MiraiLogger, url: String): String? {
val u = URL(url) val u = URL(url)
try {
val content = u.readBytes() val content = u.readBytes()
val dir = File(staticLocations) val dir = File(staticLocations)
val fileName: String? val fileName: String?
@ -185,6 +212,11 @@ class TestQQBot {
logger.debug("图片保存失败") logger.debug("图片保存失败")
} }
return fileName return fileName
} catch (e: Exception) {
logger.info("保存图片发生异常信息:${e}")
return null
}
} }
//获取图片类型 //获取图片类型
@ -201,20 +233,20 @@ class TestQQBot {
} }
} }
} }
} }
/** /**
* 存储QQ消息到数据库 * 存储QQ消息到数据库
*/ */
interface DB { interface DB {
suspend fun saveToDB(event: MessageEvent, account: Account) suspend fun saveToDB(event: MessageEvent, account: TestQQBot.Account)
} }
/** /**
* mongoDB保存QQ消息 * mongoDB保存QQ消息
*/ */
@Component(mongoDBName) @Component(mongoDBName)
class MongoDB : DB { class MongoDB : DB {
@Autowired @Autowired
lateinit var messageRepository: MessageRepository lateinit var messageRepository: MessageRepository
@ -226,7 +258,7 @@ class TestQQBot {
lateinit var saveImg: SaveImg lateinit var saveImg: SaveImg
@KtorExperimentalAPI @KtorExperimentalAPI
override suspend fun saveToDB(event: MessageEvent, account: Account) { override suspend fun saveToDB(event: MessageEvent, account: TestQQBot.Account) {
val m = messageRepository.save(MySqlMessage(senderId = event.sender.id, time = event.time, botId = event.bot.id)) val m = messageRepository.save(MySqlMessage(senderId = event.sender.id, time = event.time, botId = event.bot.id))
@ -261,7 +293,6 @@ class TestQQBot {
} }
} }
}
} }
@ -269,3 +300,4 @@ class TestQQBot {

@ -1,8 +1,3 @@
#日志配置
logging:
level:
root: info
spring: spring:
#数据库配置 #数据库配置
data: data:

@ -2,17 +2,10 @@
<include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="APP_NAME" value="Logback"/> <property name="APP_NAME" value="qqbot"/>
<property name="LOG_HOME_PATH" value="logs"/> <property name="LOG_HOME_PATH" value="logs"/>
<property name="DEBUG_LOG_FILE" value="${LOG_HOME_PATH}/debug/${APP_NAME}_debug" /> <property name="DEBUG_LOG_FILE" value="${LOG_HOME_PATH}/debug/${APP_NAME}"/>
<property name="log.charset" value="UTF-8" /> <property name="log.charset" value="UTF-8" />
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 --> <!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } - [%t] %class:%L - %m%n" /> <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } - [%t] %class:%L - %m%n" />
@ -25,7 +18,7 @@
</encoder> </encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${DEBUG_LOG_FILE}.%d{yyyy-MM-dd}.log</FileNamePattern> <FileNamePattern>${DEBUG_LOG_FILE}_debug.%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>60</MaxHistory> <MaxHistory>60</MaxHistory>
</rollingPolicy> </rollingPolicy>
@ -34,20 +27,38 @@
</filter> </filter>
</appender> </appender>
<appender name="QQBOT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="${log.charset}">
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${DEBUG_LOG_FILE}_trace.%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>60</MaxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>TRACE</level>
</filter>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- <withJansi>true</withJansi>--> <!-- <withJansi>true</withJansi>-->
<encoder charset="${log.charset}"> <encoder charset="${log.charset}">
<pattern>${CONSOLE_LOG_PATTERN}</pattern> <pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder> </encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level> <level>INFO</level>
</filter> </filter>
</appender> </appender>
<root level="DEBUG"> <root level="TRACE">
<appender-ref ref="DEBUG_FILE" /> <appender-ref ref="QQBOT_FILE"/>
<appender-ref ref="CONSOLE" /> <appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="CONSOLE"/>
</root> </root>
</configuration> </configuration>

@ -1,5 +1,9 @@
package com.pqh.qqbot package com.pqh.qqbot
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.BotConfiguration
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class AppTest { class AppTest {
@ -19,5 +23,20 @@ class AppTest {
} }
} }
}
suspend fun main(arg: Array<String>) {
println("参数长度:${arg.size}")
if (arg.size == 2) {
println("请输入账号")
val id = arg[0].toLong()
println("请输入密码")
val pwd = arg[1]
println("账号:${id},密码:${pwd}")
val config = BotConfiguration.Default
config.fileBasedDeviceInfo("qqbot.json")
GlobalScope.launch {
Bot(id, pwd, config).login()
}.join()
}
} }

@ -11,18 +11,11 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.test.web.client.getForEntity import org.springframework.boot.test.web.client.getForEntity
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.http.RequestEntity.get
import org.springframework.http.RequestEntity.post
import org.springframework.http.ResponseEntity.status
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultHandlers import org.springframework.test.web.servlet.result.MockMvcResultHandlers
import org.springframework.util.ResourceUtils import org.springframework.util.ResourceUtils
import java.io.File import java.io.File
import java.net.URI
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -76,6 +69,8 @@ class QqbotApplicationTests(@Autowired val restTemplate: TestRestTemplate) {
@Test @Test
fun testLog(){ fun testLog(){
logger.info("日志log") logger.info("日志log")
logger.debug("日志log")
logger.trace("日志log")
} }
} }

Loading…
Cancel
Save