From b77a3d02c5ed68a23b6572aa911b9633f4b5481f Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:47:13 +0900 Subject: [PATCH] add /user/{uid} call on web module. - add GetUserEvents. - add /user/{uid} backends. --- .../mori/chzzk_bot/chatbot/chzzk/Connector.kt | 32 ++++++++ .../chzzk_bot/common/events/GetUserEvents.kt | 17 +++++ .../chzzk_bot/webserver/routes/ApiRoutes.kt | 75 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 common/src/main/kotlin/space/mori/chzzk_bot/common/events/GetUserEvents.kt 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 7309c11..9577585 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,7 +1,19 @@ package space.mori.chzzk_bot.chatbot.chzzk import io.github.cdimascio.dotenv.dotenv +import jdk.internal.net.http.common.Pair +import jdk.internal.net.http.common.Pair.pair +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.models.User +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 @@ -15,10 +27,30 @@ 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) + fun getChannelAndUser(channelId: String): Pair? = pair(chzzk.getChannel(channelId), UserService.getUser(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 = getChannelAndUser(it.uid ?: "") + if(channel == null) dispatcher.post(GetUserEvents( + GetUserType.NOTFOUND, null, null, null, null + )) + else dispatcher.post(GetUserEvents( + GetUserType.RESPONSE, + channel.first.channelId, + channel.first.channelName, + LiveStatusService.getLiveStatus(channel.second!!)?.status ?: false, + channel.first.channelImageUrl + )) + } + } + } } } \ No newline at end of file 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 new file mode 100644 index 0000000..8b9dde9 --- /dev/null +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/events/GetUserEvents.kt @@ -0,0 +1,17 @@ +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/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 44d544c..deb9a61 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,8 +4,49 @@ 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 + +@Serializable +data class GetUserDTO( + val uid: String, + val nickname: String, + val isStreamOn: Boolean, + 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) @@ -16,4 +57,38 @@ fun Routing.apiRoutes() { call.respondText("OK", status= HttpStatusCode.OK) } } + 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