[refactor] user and live stream handling logic

Replaced ChzzkUserCache with event-based user fetching for cleaner architecture. Integrated new ChzzkUserFindEvent and ChzzkUserReceiveEvent to handle user data retrieval. Removed old utility methods and streamlined live stream status checks with updated APIs.
This commit is contained in:
dalbodeule
2025-05-15 04:57:17 +09:00
parent d92ad1cc51
commit a896269087
8 changed files with 105 additions and 179 deletions

View File

@@ -1,12 +1,9 @@
package space.mori.chzzk_bot.chatbot.chzzk
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import org.koin.java.KoinJavaComponent.inject
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@@ -21,12 +18,12 @@ import space.mori.chzzk_bot.common.services.TimerConfigService
import space.mori.chzzk_bot.common.services.UserService
import space.mori.chzzk_bot.common.utils.*
import xyz.r2turntrue.chzzk4j.ChzzkClient
import xyz.r2turntrue.chzzk4j.auth.ChzzkSimpleUserLoginAdapter
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionBuilder
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionSubscriptionType
import xyz.r2turntrue.chzzk4j.session.ChzzkUserSession
import xyz.r2turntrue.chzzk4j.session.event.SessionChatMessageEvent
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveStatus
import java.lang.Exception
import java.net.SocketTimeoutException
import java.time.LocalDateTime
@@ -53,8 +50,8 @@ object ChzzkHandler {
}
handlers.forEach { handler ->
val streamInfo = getStreamInfo(handler.channel.channelId)
if (streamInfo.content?.status == "OPEN") handler.isActive(true, streamInfo)
val streamInfo = Connector.getLive(handler.channel.channelId)
if (streamInfo?.isOnline == true) handler.isActive(true, streamInfo)
}
dispatcher.subscribe(UserRegisterEvent::class) {
@@ -113,15 +110,15 @@ object ChzzkHandler {
handlers.forEach {
if (!running) return@forEach
try {
val streamInfo = getStreamInfo(it.channel.channelId)
if (streamInfo.content?.status == "OPEN" && !it.isActive) {
val streamInfo = Connector.getLive(it.channel.channelId)
if (streamInfo?.isOnline == true && !it.isActive) {
try {
it.isActive(true, streamInfo)
} catch(e: Exception) {
logger.info("Exception: ${e.stackTraceToString()}")
}
}
if (streamInfo.content?.status == "CLOSE" && it.isActive) it.isActive(false, streamInfo)
if (streamInfo?.isOnline == false && it.isActive) it.isActive(false, streamInfo)
} catch (e: SocketTimeoutException) {
logger.info("Thread 1 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
} catch (e: Exception) {
@@ -141,19 +138,19 @@ object ChzzkHandler {
handlers.forEach {
if (!running) return@forEach
try {
val streamInfo = getStreamInfo(it.channel.channelId)
if (streamInfo.content?.status == "OPEN" && !it.isActive) {
val streamInfo = Connector.getLive(it.channel.channelId)
if (streamInfo?.isOnline == true && !it.isActive) {
try {
it.isActive(true, streamInfo)
} catch(e: Exception) {
logger.info("Exception: ${e.stackTraceToString()}")
}
}
if (streamInfo.content?.status == "CLOSE" && it.isActive) it.isActive(false, streamInfo)
if (streamInfo?.isOnline == false && it.isActive) it.isActive(false, streamInfo)
} catch (e: SocketTimeoutException) {
logger.info("Thread 2 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
logger.info("Thread 1 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
} catch (e: Exception) {
logger.info("Thread 2 Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
logger.info("Thread 1 Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
} finally {
Thread.sleep(5000)
}
@@ -204,8 +201,8 @@ class UserHandler(
var streamStartTime: LocalDateTime?,
) {
var messageHandler: MessageHandler
lateinit var client: ChzzkClient
lateinit var listener: ChzzkUserSession
var client: ChzzkClient
var listener: ChzzkUserSession
private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
private var _isActive: Boolean
@@ -252,7 +249,7 @@ class UserHandler(
internal val isActive: Boolean
get() = _isActive
internal fun isActive(value: Boolean, status: IData<IStreamInfo?>) {
internal fun isActive(value: Boolean, status: ChzzkLiveStatus) {
if(value) {
CoroutineScope(Dispatchers.Default).launch {
logger.info("${user.username} is live.")
@@ -262,7 +259,7 @@ class UserHandler(
logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}")
listener.subscribeAsync(ChzzkSessionSubscriptionType.CHAT)
streamStartTime = status.content?.openDate?.let { convertChzzkDateToLocalDateTime(it) }
streamStartTime = LocalDateTime.now()
if(!_isActive) {
_isActive = true

View File

@@ -1,28 +1,56 @@
package space.mori.chzzk_bot.chatbot.chzzk
import io.github.cdimascio.dotenv.dotenv
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.common.events.ChzzkUserFindEvent
import space.mori.chzzk_bot.common.events.ChzzkUserReceiveEvent
import space.mori.chzzk_bot.common.events.CoroutinesEventBus
import xyz.r2turntrue.chzzk4j.ChzzkClient
import xyz.r2turntrue.chzzk4j.ChzzkClientBuilder
import xyz.r2turntrue.chzzk4j.auth.ChzzkOauthLoginAdapter
import xyz.r2turntrue.chzzk4j.auth.ChzzkLegacyLoginAdapter
import xyz.r2turntrue.chzzk4j.auth.ChzzkSimpleUserLoginAdapter
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveStatus
import kotlin.getValue
val dotenv = dotenv {
ignoreIfMissing = true
}
@OptIn(DelicateCoroutinesApi::class)
object Connector {
val adapter = ChzzkLegacyLoginAdapter(dotenv["NID_AUT"], dotenv["NID_SES"])
val client: ChzzkClient = ChzzkClientBuilder(dotenv["NAVER_CLIENT_ID"], dotenv["NAVER_CLIENT_SECRET"])
.withLoginAdapter(adapter)
.build()
private val logger = LoggerFactory.getLogger(this::class.java)
private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
fun getChannel(channelId: String): ChzzkChannel? = client.fetchChannel(channelId)
fun getLive(channelId: String): ChzzkLiveStatus? = client.fetchLiveStatus(channelId)
init {
logger.info("chzzk logged: ${client.isLoggedIn}")
client.loginAsync().join()
dispatcher.subscribe(ChzzkUserFindEvent::class) { event ->
GlobalScope.launch {
val user = getChannel(event.uid)
dispatcher.post(ChzzkUserReceiveEvent(
find = user != null,
uid = user?.channelId,
nickname = user?.channelName,
isStreamOn = user?.isBroadcasting,
avatarUrl = user?.channelImageUrl
))
}
}
}
fun getClient(accessToken: String, refreshToken: String): ChzzkClient {
@@ -33,4 +61,6 @@ object Connector {
return client
}
}

View File

@@ -12,11 +12,11 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import net.dv8tion.jda.api.hooks.ListenerAdapter
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.common.utils.IData
import space.mori.chzzk_bot.common.utils.IStreamInfo
import space.mori.chzzk_bot.chatbot.discord.commands.*
import space.mori.chzzk_bot.common.models.User
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveStatus
import java.time.Instant
import kotlin.jvm.optionals.getOrNull
val dotenv = dotenv {
ignoreIfMissing = true
@@ -33,20 +33,19 @@ class Discord: ListenerAdapter() {
return bot.getGuildById(guildId)?.getTextChannelById(channelId)
}
fun sendDiscord(user: User, status: IData<IStreamInfo?>) {
if(status.content == null) return
fun sendDiscord(user: User, status: ChzzkLiveStatus) {
if(user.liveAlertMessage != null && user.liveAlertGuild != null && user.liveAlertChannel != null) {
val channel = getChannel(user.liveAlertGuild ?: 0, user.liveAlertChannel ?: 0)
?: throw RuntimeException("${user.liveAlertChannel} is not valid.")
val embed = EmbedBuilder()
embed.setTitle(status.content!!.liveTitle, "https://chzzk.naver.com/live/${user.token}")
embed.setTitle(status.title, "https://chzzk.naver.com/live/${user.token}")
embed.setDescription("${user.username} 님이 방송을 시작했습니다.")
embed.setTimestamp(Instant.now())
embed.setAuthor(user.username, "https://chzzk.naver.com/live/${user.token}", status.content!!.channel.channelImageUrl)
embed.addField("카테고리", status.content!!.liveCategoryValue, true)
embed.addField("태그", status.content!!.tags.joinToString(", "), true)
embed.setImage(status.content!!.liveImageUrl.replace("{type}", "1080"))
embed.setAuthor(user.username, "https://chzzk.naver.com/live/${user.token}")
embed.addField("카테고리", status.categoryType.getOrNull() ?: "Unknown", true)
embed.addField("태그", status.tags.joinToString { "," }, true)
// embed.setImage(status.)
channel.sendMessage(
MessageCreateBuilder()