You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
qqbot/src/main/kotlin/com/pqh/qqbot/TestQQBot.kt

271 lines
8.6 KiB

package com.pqh.qqbot
import io.ktor.util.KtorExperimentalAPI
import io.ktor.util.hex
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.closeAndJoin
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.sourceId
import net.mamoe.mirai.message.sourceTime
import net.mamoe.mirai.utils.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.stereotype.Service
import org.springframework.util.ResourceUtils
import java.io.File
import java.net.URL
import java.time.Duration
import java.util.*
import java.util.concurrent.TimeUnit
import javax.annotation.PostConstruct
const val mongoDBName: String = "MongoDB"
class CustomLogger(val logger: Logger,override val identity: String?) :MiraiLoggerPlatformBase(){
override fun debug0(message: String?, e: Throwable?) {
logger.debug(message,e)
}
override fun error0(message: String?, e: Throwable?) {
logger.error(message,e)
}
override fun info0(message: String?, e: Throwable?) {
logger.info(message,e)
}
override fun verbose0(message: String?, e: Throwable?) {
logger.trace(message,e)
}
override fun warning0(message: String?, e: Throwable?) {
logger.warn(message,e)
}
}
@Service
class TestQQBot {
val logger=LoggerFactory.getLogger(TestQQBot::class.java)
/**
* 程序入口
*/
@Autowired
@Qualifier(mongoDBName)
lateinit var db: DB
val maxCount = 3
data class Account(val qqId: Long, val adminQQId: Long, val pwd: String, val forward: Long)
//管理员操作指令
enum class AdminCommand {
//关闭机器人
CLOSE
}
//转发信息卡片标题
class ForwardTitle(val title: String) : ForwardMessage.DisplayStrategy() {
override fun generateTitle(forward: ForwardMessage): String = title
}
/**
* 初始化QQ机器人
*/
@Async
fun initBot(account: Account) {
GlobalScope.launch {
val config = BotConfiguration.Default
config.fileBasedDeviceInfo("qqbot.json")
val bot = Bot(account.qqId, account.pwd, config)
bot.logger.follower=CustomLogger(logger,"Bot ${bot.id}")
bot.logger.info("日志log")
bot.alsoLogin()
val group = bot.getGroup(account.forward)
bot.subscribeMessages {
sentBy(account.adminQQId) {
case(AdminCommand.CLOSE.name) {
bot.logger.info("执行关闭机器人指令")
bot.closeAndJoin()
}
}
always {
if(message.isContentNotEmpty()) {
db.saveToDB(this, account)
if (this is GroupMessageEvent&&sender.group!=group) {
GlobalScope.launch {
var title = sender.group.name
message.forEach {
if (it.isPlain()) {
if (it.content.length > 5) {
title = it.content.substring(0, 5)
}
return@forEach
}
}
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 GroupMessageEvent&&sender.group==group){
bot.logger.info("不能套娃")
}else{
bot.logger.info("${this.javaClass}类型不转发")
}
}else{
bot.logger.info("信息为空不转发")
}
}
}
}
}
/**
* 保存QQ消息图片
*/
@Component
class SaveImg {
val logger=LoggerFactory.getLogger(SaveImg::class.java)
@Value("\${qq-image}")
lateinit var staticLocations: String
// 截取url作为文件名
val regexFileName = Regex("\\d+-\\d+-[0-9A-Z]+")
//保存图片
@KtorExperimentalAPI
fun saveImage(logger: MiraiLogger, url: String): String? {
val u = URL(url)
val content = u.readBytes()
val dir = File(staticLocations)
val fileName: String?
if (dir.exists() || dir.mkdirs()) {
val f = regexFileName.find(url)?.value
fileName = f ?: UUID.randomUUID().toString()
val filepath = File(dir, fileName).absolutePath.plus(checkFileType(content))
File(filepath).writeBytes(content)
logger.debug("图片保存到本地目录${filepath}")
} else {
fileName = null
logger.debug("图片保存失败")
}
return fileName
}
//获取图片类型
@KtorExperimentalAPI
fun checkFileType(bytes: ByteArray): String {
return when {
"FFD8FF" == hex(bytes.copyOfRange(0, 3)).toUpperCase() -> ".jpg"
"89504E47" == hex(bytes.copyOfRange(0, 4)).toUpperCase() -> ".png"
"47494638" == hex(bytes.copyOfRange(0, 4)).toUpperCase() -> ".gif"
else -> {
logger.info("无法识别文件头:${hex(bytes.copyOfRange(0, 10))}")
""
}
}
}
}
/**
* 存储QQ消息到数据库
*/
interface DB {
suspend fun saveToDB(event: MessageEvent, account: Account)
}
/**
* mongoDB保存QQ消息
*/
@Component(mongoDBName)
class MongoDB : DB {
@Autowired
lateinit var messageRepository: MessageRepository
@Autowired
lateinit var subRepository: MessageSubRepository
@Autowired
lateinit var saveImg: SaveImg
@KtorExperimentalAPI
override suspend fun saveToDB(event: MessageEvent, account: Account) {
val m = messageRepository.save(MySqlMessage(senderId = event.sender.id, time = event.time, botId = event.bot.id))
val logger = event.bot.logger
if (m.id != null) {
event.message.forEach {
val subMessage = when (it) {
is Image -> {
logger.debug("接收图片,查询下载链接")
val imageUrl = it.queryUrl()
logger.debug("图片下载链接:${imageUrl}")
val path = saveImg.saveImage(event.bot.logger, imageUrl)
if (path != null) {
MySqlSubMessage(m.id, path, Image.Key.typeName)
} else {
null
}
}
is PlainText -> {
MySqlSubMessage(m.id, it.content, PlainText.Key.typeName)
}
else -> {
logger.debug("消息类型:${it.javaClass}不处理")
null
}
}
if (subMessage != null) {
subRepository.save(subMessage)
}
}
}
}
}
}