com.simbot.component.mirai.MiraiApplication.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of component-mirai Show documentation
Show all versions of component-mirai Show documentation
a simple-robot component for mirai
/*
*
* Copyright (c) 2020. ForteScarlet All rights reserved.
* Project component-mirai
* File MiraiApplication.kt
*
* You can contact the author through the following channels:
* github https://github.com/ForteScarlet
* gitee https://gitee.com/ForteScarlet
* email [email protected]
* QQ 1149159218
* The Mirai code is copyrighted by mamoe-mirai
* you can see mirai at https://github.com/mamoe/mirai
*
*
*/
package com.simbot.component.mirai
import com.forte.plusutils.consoleplus.console.ColorsBuilder
import com.forte.plusutils.consoleplus.console.colors.FontColorTypes
import com.forte.qqrobot.*
import com.forte.qqrobot.beans.messages.msgget.MsgGet
import com.forte.qqrobot.bot.BotInfo
import com.forte.qqrobot.bot.BotManager
import com.forte.qqrobot.depend.DependCenter
import com.forte.qqrobot.exception.BotVerifyException
import com.forte.qqrobot.listener.invoker.AtDetection
import com.forte.qqrobot.listener.invoker.ListenerFilter
import com.forte.qqrobot.listener.invoker.ListenerManager
import com.forte.qqrobot.log.QQLog
import com.forte.qqrobot.sender.senderlist.RootSenderList
import com.simbot.component.mirai.messages.*
import com.simbot.component.mirai.utils.ListenRegisterUtil
import kotlinx.coroutines.*
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.data.At
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong
import java.util.function.Function
import kotlin.reflect.KClass
/**
* mirai的context
*/
public class MiraiContext(
sender: MiraiBotSender,
setter: MiraiBotSender,
getter: MiraiBotSender,
manager: BotManager,
msgParser: MsgParser,
processor: MsgProcessor,
dependCenter: DependCenter,
config: MiraiConfiguration,
application: MiraiApplication
) : SimpleRobotContext(
sender,
setter,
getter,
manager,
msgParser,
processor,
dependCenter,
config,
application
)
/**
* mirai app
*/
@Suppress("unused")
public interface MiraiApp : Application {
override fun before(configuration: MiraiConfiguration)
}
/**
*
* Mirai组件启动器
*
* @author ForteScarlet <\[email][email protected]>
* @since JDK1.8
**/
@Suppress("jol")
public class MiraiApplication :
BaseApplication() {
private lateinit var botThread: Thread
@Volatile
private var doClosed: Boolean = false
@Deprecated("just see getRootSenderFunction", ReplaceWith("null"))
override fun getSetter(msgGet: MsgGet?, botManager: BotManager?): MiraiBotSender? = null
@Deprecated("just see getRootSenderFunction", ReplaceWith("null"))
override fun getSender(msgGet: MsgGet?, botManager: BotManager?): MiraiBotSender? = null
@Deprecated("just see getRootSenderFunction", ReplaceWith("null"))
override fun getGetter(msgGet: MsgGet?, botManager: BotManager?): MiraiBotSender? = null
/** 配置类实例 */
private val config: MiraiConfiguration = MiraiConfiguration()
/**
* 是否注册额外监听成功
*/
private var registeredSpecialListenerSuccess: Boolean = false
/**
* 开发者实现的获取Config对象实例的方法
* 此方法将会最先被执行,并会将值保存,使用时可使用[getConf] 方法获取
*/
override fun getConfiguration(): MiraiConfiguration = config
/**
* 开发者实现的资源初始化
* 此方法将会在所有的初始化方法最后执行
* 增加一个参数
* 此资源配置将会在配置之后执行
*/
override fun resourceInit(config: MiraiConfiguration) {
registerMiraiEvent()
}
/**
* 开发者实现的资源初始化
* 此方法将会在所有的无配置初始化方法最后执行
* 将会在用户配置之前执行
*/
override fun resourceInit() {
// add shutdown hook for close.
val app = this
Runtime.getRuntime().addShutdownHook(Thread {
app.close()
})
registerMiraiAtFilter()
}
/**
* 注册Mirai的at判断,追加当为[MiraiMessageGet]的时候的判断
*/
private fun registerMiraiAtFilter() {
ListenerFilter.updateAtDetectionFunction { old ->
Function { msg ->
if (msg is MiraiMessageGet<*>) {
AtDetection {
(msg.message.find { msg -> msg is At } as? At)?.target == msg.event.bot.id
}
} else {
old.apply(msg)
}
}
}
}
/**
* 注册mirai可提供的额外事件
*/
private fun registerMiraiEvent() {
val specialListeners: Map> = mapOf(
// 头像变更事件
MiraiEvents.friendAvatarChangedEvent to FriendAvatarChanged::class,
// 昵称变更事件
MiraiEvents.friendNicknameChangedEvent to FriendNicknameChanged::class,
// 输入状态变更事件
MiraiEvents.friendInputStatusChangedEvent to FriendInputStatusChanged::class,
// bot离线事件
MiraiEvents.botOfflineEvent to BotOffline::class,
// bot重新登录事件
MiraiEvents.botReloginEvent to BotRelogin::class,
// 群名称变更事件
MiraiEvents.groupNameChangedEvent to GroupNameChanged::class,
// 群员群昵称变更事件
MiraiEvents.memberRemarkChangedEvent to MemberRemarkChanged::class,
// 群员群头衔变更事件
MiraiEvents.memberSpecialTitleChangedEvent to MemberSpecialTitleChanged::class,
)
if (!ListenRegisterUtil.usable) {
val show = specialListeners.values.joinToString(",\n", transform = {
it.simpleName as CharSequence
})
QQLog.warning("mirai.event.register.unavailable", show)
QQLog.debug("mirai.event.register.unavailable", ListenRegisterUtil.cause, "")
return
}
registeredSpecialListenerSuccess = true
// 注册额外的事件
specialListeners.forEach {
ListenRegisterUtil.registerListen(it.key, it.value)
}
}
/**
* 根据 [getSender], [getSetter], [getGetter] 三个函数构建一个RootSenderList
* 参数分别为一个[BotManager]和一个[MsgGet]对象
* 如果组件不是分为三个部分而构建,则可以考虑重写此函数
* 此函数最终会被送入组件实现的[runServer]中
*
* @return RootSenderList构建函数
*/
override fun getRootSenderFunction(botManager: BotManager): Function {
val cacheMaps = dependCenter.get(CacheMaps::class.java)
val senderRunner = dependCenter.get(SenderRunner::class.java)
return Function {
// var bot: Bot
var contact: Contact? = null
if (it is MiraiMessageGet<*>) {
contact = it.contact
}
MultipleMiraiBotSender(
contact,
it,
botManager,
cacheMaps,
senderRunner,
registeredSpecialListenerSuccess,
conf
)
}
}
/**
* 获取一个组件专属的SimpleRobotContext对象
* @param defaultSenders 函数[.getDefaultSenders]的最终返回值
* @param manager botManager对象
* @param msgParser 消息字符串转化函数
* @param processor 消息处理器
* @param dependCenter 依赖中心
* @return 组件的Context对象实例
*/
override fun getComponentContext(
defaultSenders: DefaultSenders,
manager: BotManager,
msgParser: MsgParser, processor: MsgProcessor,
dependCenter: DependCenter, config: MiraiConfiguration
): MiraiContext = MiraiContext(
defaultSenders.sender,
defaultSenders.getter,
defaultSenders.setter,
manager,
msgParser,
processor,
dependCenter,
config,
this
)
@Deprecated("just see getDefaultSenders", ReplaceWith("null"))
override fun getDefaultSetter(botManager: BotManager?): MiraiBotSender? = null
@Deprecated("just see getDefaultSenders", ReplaceWith("null"))
override fun getDefaultGetter(botManager: BotManager?): MiraiBotSender? = null
@Deprecated("just see getDefaultSenders", ReplaceWith("null"))
override fun getDefaultSender(botManager: BotManager?): MiraiBotSender? = null
/**
* 构建一个默认的RootSenderList
* 参数分别为一个BotManager和一个MsgGet对象
* 如果组件不是分为三个部分而构建,则可以考虑重写此函数
* @return RootSenderList构建函数
*/
override fun getDefaultSenders(botManager: BotManager): DefaultSenders {
val cacheMaps = dependCenter.get(CacheMaps::class.java)
val senderRunner = dependCenter.get(SenderRunner::class.java)
val defaultSender =
DefaultMiraiBotSender(null, cacheMaps, senderRunner, registeredSpecialListenerSuccess, botManager, conf)
return DefaultSenders(defaultSender, defaultSender, defaultSender)
}
/**
* 启动一个服务,这有可能是http或者是ws的监听服务
* @param dependCenter 依赖中心
* @param manager 监听管理器
* @param msgProcessor 送信解析器
* @return
*/
override fun runServer(
dependCenter: DependCenter,
manager: ListenerManager,
msgProcessor: MsgProcessor,
msgParser: MsgParser
): String {
val cacheMaps = dependCenter.get(CacheMaps::class.java)
val senderRunner = dependCenter.get(SenderRunner::class.java)
// 启动服务,即注册监听
MiraiBots.startListen(msgProcessor, cacheMaps, senderRunner, registeredSpecialListenerSuccess)
if (config.autoRelogin) {
// 如果要自动重启,此处注册自动重启事件
MiraiBots.forEach { _, bot ->
val id: Long = bot.id
// 注册被动下线事件
bot.subscribeAlways(priority = Listener.EventPriority.HIGHEST) {
val app = this@MiraiApplication
if (this.bot.id == id && !app.doClosed) {
QQLog.debug("mirai.relogin.off", this.cause, id)
bot.login()
QQLog.debug("mirai.relogin.on")
}
}
}
}
val reloginRegularly: Long = config.reloginRegularly
// 在一条新线程中执行挂起,防止程序终止
botThread =
if (reloginRegularly <= 0) {
MiraiBotLivingThread(this)
} else {
if (config.reloginForce) {
MiraiBotLivingThreadWithForceReloginCheck(this, reloginRegularly)
} else {
MiraiBotLivingThreadWithReloginCheck(this, reloginRegularly)
}
}.apply { start() }
return "mirai bot server"
}
/**
* 验证账号
* @param code 用户账号,不可为null
* @param info 用于验证的bot,使用启动的path作为密码来执行登录
*/
override fun verifyBot(code: String, info: BotInfo): BotInfo {
val cacheMaps = dependCenter.get(CacheMaps::class.java)
val senderRunner = dependCenter.get(SenderRunner::class.java)
// 验证账号, 构建一个BotInfo
// 如果验证失败,会抛出异常
try {
QQLog.debug("验证账号$code...")
return MiraiBotInfo(
code,
info.path,
conf.botConfiguration(code),
cacheMaps,
senderRunner,
registeredSpecialListenerSuccess
)
} catch (e: Exception) {
throw BotVerifyException("failed", e, code, e.localizedMessage)
}
}
/**
* 字符串转化为MsgGet的方法,最终会被转化为[MsgParser]函数,
* 此函数返回值永远为null
*/
@Deprecated("no, just null", ReplaceWith("null"))
override fun msgParse(str: String?): MsgGet? = null
/**
* 当关闭的时候执行的方法,会退出所有的bot,然后执行父类的close
*/
override fun doClose() {
doClosed = true
MiraiBots.closeAll()
val timeUnit = TimeUnit.SECONDS
val waitTime = 10L
// join 10 seconds
QQLog.info("mirai.bot.thread.shutdown", waitTime, timeUnit.name.toLowerCase())
// interrupt并等待
runCatching {
synchronized(lock) {
lock.notify()
}
}.exceptionOrNull()?.let { QQLog.error(it) }
botThread.interrupt()
botThread.join(TimeUnit.SECONDS.toMillis(waitTime))
if (botThread.isAlive) {
QQLog.warning("mirai.bot.thread.mandatoryTermination")
runCatching {
// 强制终止
@Suppress("DEPRECATION")
botThread.stop()
}.exceptionOrNull()?.let { QQLog.error(it) }
}
runCatching { botThread.interrupt() }.exceptionOrNull()?.let { QQLog.error(it) }
}
}
internal val lock = Object()
/**
* Mirai的bot存活线程。
*/
internal class MiraiBotLivingThread(private val app: MiraiApplication) : Thread("mirai-bot-living-thread") {
override fun run() {
try {
while (!(isInterrupted || app.isClosed)) {
synchronized(lock) {
lock.wait()
}
}
QQLog.info("{0}", ColorsBuilder.getInstance().add("bots all shutdown.", FontColorTypes.RED).build())
} catch (e: InterruptedException) {
// ignored.
}
}
}
/**
* Mirai的bot存活线程。会检测bot是否存活并重启。
*/
internal class MiraiBotLivingThreadWithReloginCheck(private val app: MiraiApplication, private val checkLong: Long) :
Thread("mirai-bot-living-with-online-check-thread") {
private val lastCheckTime: AtomicLong = AtomicLong(System.currentTimeMillis())
override fun run() {
try {
while (!(isInterrupted || app.isClosed)) {
val lastTime: Long = lastCheckTime.get()
val nowTime: Long = System.currentTimeMillis()
// 时间差
val diff: Long = nowTime - lastTime
// diff >= check time
if (diff >= checkLong) {
lastCheckTime.set(nowTime)
QQLog.debug("mirai.onlineCheck.relogin.check")
MiraiBots.instances.filter {
it.isActive.apply {
QQLog.debug("mirai.onlineCheck.relogin.check.info", it.id.toString(), this)
}
}.map {
it.async {
QQLog.debug("mirai.onlineCheck.relogin.do", it.id.toString())
it.login()
QQLog.debug("mirai.onlineCheck.relogin.done", it.id.toString())
}
}.forEach {
runBlocking {
it.await()
}
}
QQLog.debug("mirai.onlineCheck.relogin.end")
} else {
// sleep diff
sleep(diff)
}
}
QQLog.info("{0}", ColorsBuilder.getInstance().add("bots all shutdown.", FontColorTypes.RED).build())
} catch (e: InterruptedException) {
// ignored.
}
}
}
/**
* Mirai的bot存活线程。会检测bot是否存活并重启。
*/
internal class MiraiBotLivingThreadWithForceReloginCheck(
private val app: MiraiApplication,
private val checkLong: Long
) :
Thread("mirai-bot-living-with-online-check-thread") {
private val lastCheckTime: AtomicLong = AtomicLong(System.currentTimeMillis())
override fun run() {
try {
while (!(isInterrupted || app.isClosed)) {
val lastTime: Long = lastCheckTime.get()
val nowTime: Long = System.currentTimeMillis()
// 时间差
val diff: Long = nowTime - lastTime
// diff >= check time
if (diff >= checkLong) {
lastCheckTime.set(nowTime)
QQLog.debug("mirai.onlineCheck.relogin.check")
kotlin.runCatching {
QQLog.debug("mirai.onlineCheck.relogin.force.do")
BotRuntime.getRuntime().botManager.refreshBot()
}.exceptionOrNull()?.let { e ->
QQLog.error("mirai.onlineCheck.relogin.force.failed", e)
} ?: run {
QQLog.debug("mirai.onlineCheck.relogin.end")
}
} else {
// sleep diff
sleep(diff)
}
}
QQLog.info("{0}", ColorsBuilder.getInstance().add("bots all shutdown.", FontColorTypes.RED).build())
} catch (e: InterruptedException) {
// ignored.
}
}
}