mirror of
https://github.com/dalbodeule/chibot-chzzk-bot.git
synced 2025-06-09 07:18:22 +00:00
[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:
parent
d92ad1cc51
commit
a896269087
@ -1,12 +1,9 @@
|
|||||||
package space.mori.chzzk_bot.chatbot.chzzk
|
package space.mori.chzzk_bot.chatbot.chzzk
|
||||||
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.future.await
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
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.services.UserService
|
||||||
import space.mori.chzzk_bot.common.utils.*
|
import space.mori.chzzk_bot.common.utils.*
|
||||||
import xyz.r2turntrue.chzzk4j.ChzzkClient
|
import xyz.r2turntrue.chzzk4j.ChzzkClient
|
||||||
import xyz.r2turntrue.chzzk4j.auth.ChzzkSimpleUserLoginAdapter
|
|
||||||
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionBuilder
|
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionBuilder
|
||||||
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionSubscriptionType
|
import xyz.r2turntrue.chzzk4j.session.ChzzkSessionSubscriptionType
|
||||||
import xyz.r2turntrue.chzzk4j.session.ChzzkUserSession
|
import xyz.r2turntrue.chzzk4j.session.ChzzkUserSession
|
||||||
import xyz.r2turntrue.chzzk4j.session.event.SessionChatMessageEvent
|
import xyz.r2turntrue.chzzk4j.session.event.SessionChatMessageEvent
|
||||||
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
|
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
|
||||||
|
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveStatus
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@ -53,8 +50,8 @@ object ChzzkHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlers.forEach { handler ->
|
handlers.forEach { handler ->
|
||||||
val streamInfo = getStreamInfo(handler.channel.channelId)
|
val streamInfo = Connector.getLive(handler.channel.channelId)
|
||||||
if (streamInfo.content?.status == "OPEN") handler.isActive(true, streamInfo)
|
if (streamInfo?.isOnline == true) handler.isActive(true, streamInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatcher.subscribe(UserRegisterEvent::class) {
|
dispatcher.subscribe(UserRegisterEvent::class) {
|
||||||
@ -113,15 +110,15 @@ object ChzzkHandler {
|
|||||||
handlers.forEach {
|
handlers.forEach {
|
||||||
if (!running) return@forEach
|
if (!running) return@forEach
|
||||||
try {
|
try {
|
||||||
val streamInfo = getStreamInfo(it.channel.channelId)
|
val streamInfo = Connector.getLive(it.channel.channelId)
|
||||||
if (streamInfo.content?.status == "OPEN" && !it.isActive) {
|
if (streamInfo?.isOnline == true && !it.isActive) {
|
||||||
try {
|
try {
|
||||||
it.isActive(true, streamInfo)
|
it.isActive(true, streamInfo)
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
logger.info("Exception: ${e.stackTraceToString()}")
|
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) {
|
} catch (e: SocketTimeoutException) {
|
||||||
logger.info("Thread 1 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
logger.info("Thread 1 Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -141,19 +138,19 @@ object ChzzkHandler {
|
|||||||
handlers.forEach {
|
handlers.forEach {
|
||||||
if (!running) return@forEach
|
if (!running) return@forEach
|
||||||
try {
|
try {
|
||||||
val streamInfo = getStreamInfo(it.channel.channelId)
|
val streamInfo = Connector.getLive(it.channel.channelId)
|
||||||
if (streamInfo.content?.status == "OPEN" && !it.isActive) {
|
if (streamInfo?.isOnline == true && !it.isActive) {
|
||||||
try {
|
try {
|
||||||
it.isActive(true, streamInfo)
|
it.isActive(true, streamInfo)
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
logger.info("Exception: ${e.stackTraceToString()}")
|
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) {
|
} 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) {
|
} catch (e: Exception) {
|
||||||
logger.info("Thread 2 Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
logger.info("Thread 1 Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
|
||||||
} finally {
|
} finally {
|
||||||
Thread.sleep(5000)
|
Thread.sleep(5000)
|
||||||
}
|
}
|
||||||
@ -204,8 +201,8 @@ class UserHandler(
|
|||||||
var streamStartTime: LocalDateTime?,
|
var streamStartTime: LocalDateTime?,
|
||||||
) {
|
) {
|
||||||
var messageHandler: MessageHandler
|
var messageHandler: MessageHandler
|
||||||
lateinit var client: ChzzkClient
|
var client: ChzzkClient
|
||||||
lateinit var listener: ChzzkUserSession
|
var listener: ChzzkUserSession
|
||||||
|
|
||||||
private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
||||||
private var _isActive: Boolean
|
private var _isActive: Boolean
|
||||||
@ -252,7 +249,7 @@ class UserHandler(
|
|||||||
internal val isActive: Boolean
|
internal val isActive: Boolean
|
||||||
get() = _isActive
|
get() = _isActive
|
||||||
|
|
||||||
internal fun isActive(value: Boolean, status: IData<IStreamInfo?>) {
|
internal fun isActive(value: Boolean, status: ChzzkLiveStatus) {
|
||||||
if(value) {
|
if(value) {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
logger.info("${user.username} is live.")
|
logger.info("${user.username} is live.")
|
||||||
@ -262,7 +259,7 @@ class UserHandler(
|
|||||||
logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}")
|
logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}")
|
||||||
listener.subscribeAsync(ChzzkSessionSubscriptionType.CHAT)
|
listener.subscribeAsync(ChzzkSessionSubscriptionType.CHAT)
|
||||||
|
|
||||||
streamStartTime = status.content?.openDate?.let { convertChzzkDateToLocalDateTime(it) }
|
streamStartTime = LocalDateTime.now()
|
||||||
|
|
||||||
if(!_isActive) {
|
if(!_isActive) {
|
||||||
_isActive = true
|
_isActive = true
|
||||||
|
@ -1,28 +1,56 @@
|
|||||||
package space.mori.chzzk_bot.chatbot.chzzk
|
package space.mori.chzzk_bot.chatbot.chzzk
|
||||||
|
|
||||||
import io.github.cdimascio.dotenv.dotenv
|
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 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.ChzzkClient
|
||||||
import xyz.r2turntrue.chzzk4j.ChzzkClientBuilder
|
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.auth.ChzzkSimpleUserLoginAdapter
|
||||||
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
|
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
|
||||||
|
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveStatus
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
val dotenv = dotenv {
|
val dotenv = dotenv {
|
||||||
ignoreIfMissing = true
|
ignoreIfMissing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
object Connector {
|
object Connector {
|
||||||
|
val adapter = ChzzkLegacyLoginAdapter(dotenv["NID_AUT"], dotenv["NID_SES"])
|
||||||
val client: ChzzkClient = ChzzkClientBuilder(dotenv["NAVER_CLIENT_ID"], dotenv["NAVER_CLIENT_SECRET"])
|
val client: ChzzkClient = ChzzkClientBuilder(dotenv["NAVER_CLIENT_ID"], dotenv["NAVER_CLIENT_SECRET"])
|
||||||
|
.withLoginAdapter(adapter)
|
||||||
.build()
|
.build()
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
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 getChannel(channelId: String): ChzzkChannel? = client.fetchChannel(channelId)
|
||||||
|
fun getLive(channelId: String): ChzzkLiveStatus? = client.fetchLiveStatus(channelId)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
logger.info("chzzk logged: ${client.isLoggedIn}")
|
logger.info("chzzk logged: ${client.isLoggedIn}")
|
||||||
|
|
||||||
client.loginAsync().join()
|
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 {
|
fun getClient(accessToken: String, refreshToken: String): ChzzkClient {
|
||||||
@ -33,4 +61,6 @@ object Connector {
|
|||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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.hooks.ListenerAdapter
|
||||||
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder
|
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder
|
||||||
import org.slf4j.LoggerFactory
|
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.chatbot.discord.commands.*
|
||||||
import space.mori.chzzk_bot.common.models.User
|
import space.mori.chzzk_bot.common.models.User
|
||||||
|
import xyz.r2turntrue.chzzk4j.types.channel.live.ChzzkLiveStatus
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
val dotenv = dotenv {
|
val dotenv = dotenv {
|
||||||
ignoreIfMissing = true
|
ignoreIfMissing = true
|
||||||
@ -33,20 +33,19 @@ class Discord: ListenerAdapter() {
|
|||||||
return bot.getGuildById(guildId)?.getTextChannelById(channelId)
|
return bot.getGuildById(guildId)?.getTextChannelById(channelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendDiscord(user: User, status: IData<IStreamInfo?>) {
|
fun sendDiscord(user: User, status: ChzzkLiveStatus) {
|
||||||
if(status.content == null) return
|
|
||||||
if(user.liveAlertMessage != null && user.liveAlertGuild != null && user.liveAlertChannel != null) {
|
if(user.liveAlertMessage != null && user.liveAlertGuild != null && user.liveAlertChannel != null) {
|
||||||
val channel = getChannel(user.liveAlertGuild ?: 0, user.liveAlertChannel ?: 0)
|
val channel = getChannel(user.liveAlertGuild ?: 0, user.liveAlertChannel ?: 0)
|
||||||
?: throw RuntimeException("${user.liveAlertChannel} is not valid.")
|
?: throw RuntimeException("${user.liveAlertChannel} is not valid.")
|
||||||
|
|
||||||
val embed = EmbedBuilder()
|
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.setDescription("${user.username} 님이 방송을 시작했습니다.")
|
||||||
embed.setTimestamp(Instant.now())
|
embed.setTimestamp(Instant.now())
|
||||||
embed.setAuthor(user.username, "https://chzzk.naver.com/live/${user.token}", status.content!!.channel.channelImageUrl)
|
embed.setAuthor(user.username, "https://chzzk.naver.com/live/${user.token}")
|
||||||
embed.addField("카테고리", status.content!!.liveCategoryValue, true)
|
embed.addField("카테고리", status.categoryType.getOrNull() ?: "Unknown", true)
|
||||||
embed.addField("태그", status.content!!.tags.joinToString(", "), true)
|
embed.addField("태그", status.tags.joinToString { "," }, true)
|
||||||
embed.setImage(status.content!!.liveImageUrl.replace("{type}", "1080"))
|
// embed.setImage(status.)
|
||||||
|
|
||||||
channel.sendMessage(
|
channel.sendMessage(
|
||||||
MessageCreateBuilder()
|
MessageCreateBuilder()
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package space.mori.chzzk_bot.common.events
|
||||||
|
|
||||||
|
data class ChzzkUserFindEvent(
|
||||||
|
val uid: String
|
||||||
|
): Event {
|
||||||
|
val TAG = javaClass.simpleName
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package space.mori.chzzk_bot.common.events
|
||||||
|
|
||||||
|
data class ChzzkUserReceiveEvent(
|
||||||
|
val find: Boolean = true,
|
||||||
|
val uid: String? = null,
|
||||||
|
val nickname: String? = null,
|
||||||
|
val isStreamOn: Boolean? = null,
|
||||||
|
val avatarUrl: String? = null,
|
||||||
|
): Event {
|
||||||
|
val TAG = javaClass.simpleName
|
||||||
|
}
|
@ -52,49 +52,6 @@ data class NicknameColor(
|
|||||||
val colorCode: String = ""
|
val colorCode: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stream info
|
|
||||||
data class IStreamInfo(
|
|
||||||
val liveId: Int = 0,
|
|
||||||
val liveTitle: String = "",
|
|
||||||
val status: String = "",
|
|
||||||
val liveImageUrl: String = "",
|
|
||||||
val defaultThumbnailImageUrl: String? = null,
|
|
||||||
val concurrentUserCount: Int = 0,
|
|
||||||
val accumulateCount: Int = 0,
|
|
||||||
val openDate: String = "",
|
|
||||||
val closeDate: String = "",
|
|
||||||
val adult: Boolean = false,
|
|
||||||
val clipActive: Boolean = false,
|
|
||||||
val tags: List<String> = emptyList(),
|
|
||||||
val chatChannelId: String = "",
|
|
||||||
val categoryType: String = "",
|
|
||||||
val liveCategory: String = "",
|
|
||||||
val liveCategoryValue: String = "",
|
|
||||||
val chatActive: Boolean = true,
|
|
||||||
val chatAvailableGroup: String = "",
|
|
||||||
val paidPromotion: Boolean = false,
|
|
||||||
val chatAvailableCondition: String = "",
|
|
||||||
val minFollowerMinute: Int = 0,
|
|
||||||
val livePlaybackJson: String = "",
|
|
||||||
val p2pQuality: List<Any> = emptyList(),
|
|
||||||
val channel: Channel = Channel(),
|
|
||||||
val livePollingStatusJson: String = "",
|
|
||||||
val userAdultStatus: String? = null,
|
|
||||||
val chatDonationRankingExposure: Boolean = true,
|
|
||||||
val adParameter: AdParameter = AdParameter()
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Channel(
|
|
||||||
val channelId: String = "",
|
|
||||||
val channelName: String = "",
|
|
||||||
val channelImageUrl: String = "",
|
|
||||||
val verifiedMark: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AdParameter(
|
|
||||||
val tag: String = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// OkHttpClient에 Interceptor 추가
|
// OkHttpClient에 Interceptor 추가
|
||||||
val client = OkHttpClient.Builder()
|
val client = OkHttpClient.Builder()
|
||||||
.addNetworkInterceptor { chain ->
|
.addNetworkInterceptor { chain ->
|
||||||
@ -127,41 +84,3 @@ fun getFollowDate(chatID: String, userId: String) : IData<IFollowContent?> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStreamInfo(userId: String) : IData<IStreamInfo?> {
|
|
||||||
val url = "https://api.chzzk.naver.com/service/v3/channels/${userId}/live-detail"
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
try {
|
|
||||||
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
|
|
||||||
val body = response.body?.string()
|
|
||||||
val follow = gson.fromJson(body, object: TypeToken<IData<IStreamInfo?>>() {})
|
|
||||||
|
|
||||||
return follow
|
|
||||||
} catch(e: Exception) {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUserInfo(userId: String): IData<Channel?> {
|
|
||||||
val url = "https://api.chzzk.naver.com/service/v1/channels/${userId}"
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
try {
|
|
||||||
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
|
|
||||||
val body = response.body?.string()
|
|
||||||
val channel = gson.fromJson(body, object: TypeToken<IData<Channel?>>() {})
|
|
||||||
|
|
||||||
return channel
|
|
||||||
} catch(e: Exception) {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -11,7 +11,10 @@ import space.mori.chzzk_bot.common.events.CoroutinesEventBus
|
|||||||
import space.mori.chzzk_bot.common.services.SongConfigService
|
import space.mori.chzzk_bot.common.services.SongConfigService
|
||||||
import space.mori.chzzk_bot.common.services.UserService
|
import space.mori.chzzk_bot.common.services.UserService
|
||||||
import space.mori.chzzk_bot.webserver.UserSession
|
import space.mori.chzzk_bot.webserver.UserSession
|
||||||
import space.mori.chzzk_bot.webserver.utils.ChzzkUserCache
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import space.mori.chzzk_bot.common.events.ChzzkUserFindEvent
|
||||||
|
import space.mori.chzzk_bot.common.events.ChzzkUserReceiveEvent
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GetUserDTO(
|
data class GetUserDTO(
|
||||||
@ -36,6 +39,16 @@ data class GetSessionDTO(
|
|||||||
fun Routing.apiRoutes() {
|
fun Routing.apiRoutes() {
|
||||||
val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
||||||
|
|
||||||
|
suspend fun getChzzkUserWithId(uid: String): ChzzkUserReceiveEvent? {
|
||||||
|
val completableDeferred = CompletableDeferred< ChzzkUserReceiveEvent>()
|
||||||
|
val user = withTimeoutOrNull(5000) {
|
||||||
|
dispatcher.post(ChzzkUserFindEvent(uid))
|
||||||
|
completableDeferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
route("/") {
|
route("/") {
|
||||||
get {
|
get {
|
||||||
call.respondText("Hello World!", status =
|
call.respondText("Hello World!", status =
|
||||||
@ -55,16 +68,16 @@ fun Routing.apiRoutes() {
|
|||||||
call.respondText("Require UID", status = HttpStatusCode.NotFound)
|
call.respondText("Require UID", status = HttpStatusCode.NotFound)
|
||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
val user = ChzzkUserCache.getCachedUser(uid)
|
val user = getChzzkUserWithId(uid)
|
||||||
if(user?.content == null) {
|
if (user?.find == false) {
|
||||||
call.respondText("User not found", status = HttpStatusCode.NotFound)
|
call.respondText("User not found", status = HttpStatusCode.NotFound)
|
||||||
return@get
|
return@get
|
||||||
} else {
|
} else {
|
||||||
call.respond(HttpStatusCode.OK, GetUserDTO(
|
call.respond(HttpStatusCode.OK, GetUserDTO(
|
||||||
user.content!!.channel.channelId,
|
user?.uid ?: "",
|
||||||
user.content!!.channel.channelName,
|
user?.nickname ?: "",
|
||||||
user.content!!.status == "OPEN",
|
user?.isStreamOn ?: false,
|
||||||
user.content!!.channel.channelImageUrl
|
user?.avatarUrl ?: ""
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +95,7 @@ fun Routing.apiRoutes() {
|
|||||||
user = UserService.saveUser("임시닉네임", session.id)
|
user = UserService.saveUser("임시닉네임", session.id)
|
||||||
}
|
}
|
||||||
val songConfig = SongConfigService.getConfig(user)
|
val songConfig = SongConfigService.getConfig(user)
|
||||||
val status = ChzzkUserCache.getCachedUser(session.id)
|
val status = getChzzkUserWithId(user.token)
|
||||||
val returnUsers = mutableListOf<GetSessionDTO>()
|
val returnUsers = mutableListOf<GetSessionDTO>()
|
||||||
|
|
||||||
if(status == null) {
|
if(status == null) {
|
||||||
@ -91,14 +104,14 @@ fun Routing.apiRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user.username == "임시닉네임") {
|
if (user.username == "임시닉네임") {
|
||||||
status.content?.channel?.let { it1 -> UserService.updateUser(user, it1.channelId, it1.channelName) }
|
status.let { stats -> UserService.updateUser(user, stats.uid ?: "", stats.nickname ?: "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
returnUsers.add(GetSessionDTO(
|
returnUsers.add(GetSessionDTO(
|
||||||
status.content?.channel?.channelId ?: user.username,
|
status.uid ?: user.token,
|
||||||
status.content?.channel?.channelName ?: user.token,
|
status.nickname ?: user.username,
|
||||||
status.content?.status == "OPEN",
|
status.isStreamOn == true,
|
||||||
status.content?.channel?.channelImageUrl ?: "",
|
status.avatarUrl ?: "",
|
||||||
songConfig.queueLimit,
|
songConfig.queueLimit,
|
||||||
songConfig.personalLimit,
|
songConfig.personalLimit,
|
||||||
songConfig.streamerOnly,
|
songConfig.streamerOnly,
|
||||||
@ -109,15 +122,15 @@ fun Routing.apiRoutes() {
|
|||||||
user.subordinates.toList()
|
user.subordinates.toList()
|
||||||
}
|
}
|
||||||
returnUsers.addAll(subordinates.map {
|
returnUsers.addAll(subordinates.map {
|
||||||
val subStatus = ChzzkUserCache.getCachedUser(it.token)
|
val subStatus = getChzzkUserWithId(user.token)
|
||||||
return@map if (subStatus?.content == null) {
|
return@map if (subStatus == null) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
GetSessionDTO(
|
GetSessionDTO(
|
||||||
subStatus.content!!.channel.channelId,
|
subStatus.uid ?: "",
|
||||||
subStatus.content!!.channel.channelName,
|
subStatus.nickname ?: "",
|
||||||
subStatus.content!!.status == "OPEN",
|
subStatus.isStreamOn == true,
|
||||||
subStatus.content!!.channel.channelImageUrl,
|
subStatus.avatarUrl ?: "",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
package space.mori.chzzk_bot.webserver.utils
|
|
||||||
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
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.common.utils.getStreamInfo
|
|
||||||
import space.mori.chzzk_bot.common.utils.getUserInfo
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
object ChzzkUserCache {
|
|
||||||
private val cache = ConcurrentHashMap<String, CachedUser>()
|
|
||||||
private const val EXP_SECONDS = 600L
|
|
||||||
private val mutex = Mutex()
|
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
|
||||||
|
|
||||||
suspend fun getCachedUser(id: String): IData<IStreamInfo?>? {
|
|
||||||
val now = Instant.now()
|
|
||||||
var user = cache[id]
|
|
||||||
|
|
||||||
if(user == null || user.timestamp.plusSeconds(EXP_SECONDS).isBefore(now)) {
|
|
||||||
mutex.withLock {
|
|
||||||
if(user == null || user.timestamp.plusSeconds(EXP_SECONDS)?.isBefore(now) != false) {
|
|
||||||
var findUser = getStreamInfo(id)
|
|
||||||
if(findUser.content == null) {
|
|
||||||
val userInfo = getUserInfo(id)
|
|
||||||
|
|
||||||
if(userInfo.content == null) return null
|
|
||||||
|
|
||||||
findUser = IData(200, null, IStreamInfo(
|
|
||||||
channel = userInfo.content!!
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
user = CachedUser(findUser)
|
|
||||||
user.let { cache[id] = user }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache[id]?.user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CachedUser(
|
|
||||||
val user: IData<IStreamInfo?>,
|
|
||||||
val timestamp: Instant = Instant.now(),
|
|
||||||
)
|
|
Loading…
x
Reference in New Issue
Block a user