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.
271 lines
8.6 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|