diff --git a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt index 67e432e..8607685 100644 --- a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt +++ b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkHandler.kt @@ -14,8 +14,7 @@ import space.mori.chzzk_bot.common.models.User import space.mori.chzzk_bot.common.services.LiveStatusService import space.mori.chzzk_bot.common.services.TimerConfigService import space.mori.chzzk_bot.common.services.UserService -import space.mori.chzzk_bot.common.utils.convertChzzkDateToLocalDateTime -import space.mori.chzzk_bot.common.utils.getUptime +import space.mori.chzzk_bot.common.utils.* import xyz.r2turntrue.chzzk4j.chat.ChatEventListener import xyz.r2turntrue.chzzk4j.chat.ChatMessage import xyz.r2turntrue.chzzk4j.chat.ChzzkChat @@ -42,7 +41,7 @@ object ChzzkHandler { handlers.forEach { handler -> val streamInfo = getStreamInfo(handler.listener.channelId) - if (streamInfo.content.status == "OPEN") handler.isActive(true, streamInfo) + if (streamInfo.content?.status == "OPEN") handler.isActive(true, streamInfo) } } @@ -76,8 +75,8 @@ object ChzzkHandler { if (!running) return@forEach try { val streamInfo = getStreamInfo(it.channel.channelId) - if (streamInfo.content.status == "OPEN" && !it.isActive) it.isActive(true, streamInfo) - if (streamInfo.content.status == "CLOSE" && it.isActive) it.isActive(false, streamInfo) + if (streamInfo.content?.status == "OPEN" && !it.isActive) it.isActive(true, streamInfo) + if (streamInfo.content?.status == "CLOSE" && it.isActive) it.isActive(false, streamInfo) } catch(e: SocketTimeoutException) { logger.info("Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}") } catch (e: Exception) { @@ -152,14 +151,14 @@ class UserHandler( internal val isActive: Boolean get() = _isActive - internal fun isActive(value: Boolean, status: IData) { + internal fun isActive(value: Boolean, status: IData) { if(value) { logger.info("${user.username} is live.") logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}") listener.connectBlocking() - streamStartTime = convertChzzkDateToLocalDateTime(status.content.openDate) + streamStartTime = status.content?.openDate?.let { convertChzzkDateToLocalDateTime(it) } CoroutineScope(Dispatchers.Default).launch { when(TimerConfigService.getConfig(UserService.getUser(channel.channelId)!!)?.option) { diff --git a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/Connector.kt b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/Connector.kt index b6f11a6..7309c11 100644 --- a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/Connector.kt +++ b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/Connector.kt @@ -1,16 +1,7 @@ package space.mori.chzzk_bot.chatbot.chzzk import io.github.cdimascio.dotenv.dotenv -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.koin.java.KoinJavaComponent.inject import org.slf4j.LoggerFactory -import space.mori.chzzk_bot.common.events.CoroutinesEventBus -import space.mori.chzzk_bot.common.events.GetUserEvents -import space.mori.chzzk_bot.common.events.GetUserType -import space.mori.chzzk_bot.common.services.LiveStatusService -import space.mori.chzzk_bot.common.services.UserService import xyz.r2turntrue.chzzk4j.Chzzk import xyz.r2turntrue.chzzk4j.ChzzkBuilder import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel @@ -24,34 +15,10 @@ object Connector { .withAuthorization(dotenv["NID_AUT"], dotenv["NID_SES"]) .build() private val logger = LoggerFactory.getLogger(this::class.java) - private val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java) fun getChannel(channelId: String): ChzzkChannel? = chzzk.getChannel(channelId) - init { logger.info("chzzk logged: ${chzzk.isLoggedIn} / ${chzzk.loggedUser?.nickname ?: "----"}") - dispatcher.subscribe(GetUserEvents::class) { - if (it.type == GetUserType.REQUEST) { - CoroutineScope(Dispatchers.Default).launch { - val channel = getChannel(it.uid ?: "") - if(channel == null) dispatcher.post(GetUserEvents( - GetUserType.NOTFOUND, null, null, null, null - )) - else { - val user = UserService.getUser(channel.channelId) - dispatcher.post( - GetUserEvents( - GetUserType.RESPONSE, - channel.channelId, - channel.channelName, - LiveStatusService.getLiveStatus(user!!)?.status ?: false, - channel.channelImageUrl - ) - ) - } - } - } - } } } \ No newline at end of file diff --git a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/MessageHandler.kt b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/MessageHandler.kt index 8caba6e..cff8cd0 100644 --- a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/MessageHandler.kt +++ b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/MessageHandler.kt @@ -7,6 +7,7 @@ import org.koin.java.KoinJavaComponent.inject import space.mori.chzzk_bot.common.events.* import space.mori.chzzk_bot.common.models.User import space.mori.chzzk_bot.common.services.* +import space.mori.chzzk_bot.common.utils.getFollowDate import space.mori.chzzk_bot.common.utils.getUptime import space.mori.chzzk_bot.common.utils.getYoutubeVideo import xyz.r2turntrue.chzzk4j.chat.ChatMessage @@ -280,7 +281,7 @@ class MessageHandler( result = followPattern.replace(result) { _ -> try { val followingDate = getFollowDate(listener.chatId, msg.userId) - .content.streamingProperty.following?.followDate + .content?.streamingProperty?.following?.followDate val period = followingDate?.let { val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") diff --git a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/discord/Discord.kt b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/discord/Discord.kt index f637de9..c71ee7c 100644 --- a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/discord/Discord.kt +++ b/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/discord/Discord.kt @@ -12,8 +12,8 @@ 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.chatbot.chzzk.IData -import space.mori.chzzk_bot.chatbot.chzzk.IStreamInfo +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 space.mori.chzzk_bot.common.services.ManagerService @@ -33,18 +33,19 @@ class Discord: ListenerAdapter() { internal fun getChannel(guildId: Long, channelId: Long) = bot.getGuildById(guildId)?.getTextChannelById(channelId) - fun sendDiscord(user: User, status: IData) { + fun sendDiscord(user: User, status: IData) { + if(status.content == null) return if(user.liveAlertMessage != "" && user.liveAlertGuild != null && user.liveAlertChannel != null) { val channel = getChannel(user.liveAlertGuild!!, user.liveAlertChannel!!) ?: 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.content!!.liveTitle, "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}", 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")) channel.sendMessage( MessageCreateBuilder() diff --git a/common/src/main/kotlin/space/mori/chzzk_bot/common/events/GetUserEvents.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/events/GetUserEvents.kt deleted file mode 100644 index 8b9dde9..0000000 --- a/common/src/main/kotlin/space/mori/chzzk_bot/common/events/GetUserEvents.kt +++ /dev/null @@ -1,17 +0,0 @@ -package space.mori.chzzk_bot.common.events - -enum class GetUserType(var value: Int) { - REQUEST(0), - RESPONSE(1), - NOTFOUND(2) -} - -class GetUserEvents( - val type: GetUserType, - val uid: String?, - val nickname: String?, - val isStreamOn: Boolean?, - val avatarUrl: String?, -): Event { - var TAG = javaClass.simpleName -} \ No newline at end of file diff --git a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkApis.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/utils/ChzzkApis.kt similarity index 96% rename from chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkApis.kt rename to common/src/main/kotlin/space/mori/chzzk_bot/common/utils/ChzzkApis.kt index 09806a8..fdbf636 100644 --- a/chatbot/src/main/kotlin/space/mori/chzzk_bot/chatbot/chzzk/ChzzkApis.kt +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/utils/ChzzkApis.kt @@ -1,4 +1,4 @@ -package space.mori.chzzk_bot.chatbot.chzzk +package space.mori.chzzk_bot.common.utils import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -108,7 +108,7 @@ val client = OkHttpClient.Builder() .build() val gson = Gson() -fun getFollowDate(chatID: String, userId: String) : IData { +fun getFollowDate(chatID: String, userId: String) : IData { val url = "https://comm-api.game.naver.com/nng_main/v1/chats/$chatID/users/$userId/profile-card?chatType=STREAMING" val request = Request.Builder() .url(url) @@ -118,7 +118,7 @@ fun getFollowDate(chatID: String, userId: String) : IData { try { if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}") val body = response.body?.string() - val follow = gson.fromJson(body, object: TypeToken>() {}) + val follow = gson.fromJson(body, object: TypeToken>() {}) return follow } catch(e: Exception) { @@ -128,7 +128,7 @@ fun getFollowDate(chatID: String, userId: String) : IData { } } -fun getStreamInfo(userId: String) : IData { +fun getStreamInfo(userId: String) : IData { val url = "https://api.chzzk.naver.com/service/v2/channels/${userId}/live-detail" val request = Request.Builder() .url(url) @@ -138,7 +138,7 @@ fun getStreamInfo(userId: String) : IData { try { if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}") val body = response.body?.string() - val follow = gson.fromJson(body, object: TypeToken>() {}) + val follow = gson.fromJson(body, object: TypeToken>() {}) return follow } catch(e: Exception) { diff --git a/common/src/main/kotlin/space/mori/chzzk_bot/common/utils/getYoutube.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/utils/getYoutube.kt index c3b5969..8464e2f 100644 --- a/common/src/main/kotlin/space/mori/chzzk_bot/common/utils/getYoutube.kt +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/utils/getYoutube.kt @@ -1,10 +1,8 @@ package space.mori.chzzk_bot.common.utils -import com.google.gson.Gson import com.google.gson.JsonObject import io.github.cdimascio.dotenv.dotenv import okhttp3.HttpUrl -import okhttp3.OkHttpClient import okhttp3.Request import java.io.IOException @@ -18,9 +16,6 @@ data class YoutubeVideo( val regex = ".*(?:youtu.be/|v/|u/\\w/|embed/|watch\\?v=|&v=)([^#&?]*).*".toRegex() val durationRegex = """PT(\d+H)?(\d+M)?(\d+S)?""".toRegex() -val client = OkHttpClient() -val gson = Gson() - val dotenv = dotenv { ignoreIfMissing = true } diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt index deb9a61..d10b5ea 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiRoutes.kt @@ -4,16 +4,8 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import org.koin.java.KoinJavaComponent.inject -import space.mori.chzzk_bot.common.events.CoroutinesEventBus -import space.mori.chzzk_bot.common.events.GetUserEvents -import space.mori.chzzk_bot.common.events.GetUserType -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentLinkedQueue +import space.mori.chzzk_bot.common.utils.getStreamInfo @Serializable data class GetUserDTO( @@ -23,33 +15,11 @@ data class GetUserDTO( val avatarUrl: String, ) -fun GetUserEvents.toDTO(): GetUserDTO { - return GetUserDTO( - this.uid!!, - this.nickname!!, - this.isStreamOn!!, - this.avatarUrl!! - ) -} - fun Routing.apiRoutes() { - val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java) - val callMap = ConcurrentHashMap>() - - fun addCall(uid: String, call: ApplicationCall) { - callMap.computeIfAbsent(uid) { ConcurrentLinkedQueue() }.add(call) - } - - fun removeCall(uid: String, call: ApplicationCall) { - callMap[uid]?.remove(call) - if(callMap[uid]?.isEmpty() == true) { - callMap.remove(uid) - } - } - route("/") { get { - call.respondText("Hello World!", status = HttpStatusCode.OK) + call.respondText("Hello World!", status = + HttpStatusCode.OK) } } route("/health") { @@ -57,38 +27,31 @@ fun Routing.apiRoutes() { call.respondText("OK", status= HttpStatusCode.OK) } } + + route("/user/{uid}") { + get { + val uid = call.parameters["uid"] + if(uid == null) { + call.respondText("Require UID", status = HttpStatusCode.NotFound) + return@get + } + val user = getStreamInfo(uid) + if(user.content == null) { + call.respondText("User not found", status = HttpStatusCode.NotFound) + return@get + } else { + call.respond(HttpStatusCode.OK, GetUserDTO( + user.content!!.channel.channelId, + user.content!!.channel.channelName, + user.content!!.status == "OPEN", + user.content!!.channel.channelImageUrl + )) + } + } + } route("/user") { get { call.respondText("Require UID", status = HttpStatusCode.NotFound) } - get("{uid}") { - val uid = call.parameters["uid"] - if(uid != null) { - addCall(uid, call) - if(!callMap.containsKey(uid)) { - CoroutineScope(Dispatchers.Default).launch { - dispatcher.post(GetUserEvents(GetUserType.REQUEST, null, null, null, null)) - } - } - } - } - } - - dispatcher.subscribe(GetUserEvents::class) { - if(it.type == GetUserType.REQUEST) return@subscribe - - CoroutineScope(Dispatchers.Default). launch { - if (it.type == GetUserType.NOTFOUND) { - callMap[it.uid]?.forEach { call -> - call.respondText("User not found", status = HttpStatusCode.NotFound) - removeCall(it.uid ?: "", call) - } - return@launch - } - callMap[it.uid]?.forEach { call -> - call.respond(HttpStatusCode.OK, it.toDTO()) - removeCall(it.uid ?: "", call) - } - } } } \ No newline at end of file