Fix /user/{uid} endoints.

- ChzzkApis.kt moved to common
- ChzzkApis.kt response data is nullable data.
- if require {uid}'s data, getStreamInfo function called.
This commit is contained in:
dalbodeule 2024-08-04 20:27:54 +09:00
parent 514ab14c3c
commit 6da0662e2a
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
8 changed files with 47 additions and 138 deletions

View File

@ -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<IStreamInfo>) {
internal fun isActive(value: Boolean, status: IData<IStreamInfo?>) {
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) {

View File

@ -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
)
)
}
}
}
}
}
}

View File

@ -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")

View File

@ -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<IStreamInfo>) {
fun sendDiscord(user: User, status: IData<IStreamInfo?>) {
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()

View File

@ -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
}

View File

@ -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<IFollowContent> {
fun getFollowDate(chatID: String, userId: String) : IData<IFollowContent?> {
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<IFollowContent> {
try {
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
val body = response.body?.string()
val follow = gson.fromJson(body, object: TypeToken<IData<IFollowContent>>() {})
val follow = gson.fromJson(body, object: TypeToken<IData<IFollowContent?>>() {})
return follow
} catch(e: Exception) {
@ -128,7 +128,7 @@ fun getFollowDate(chatID: String, userId: String) : IData<IFollowContent> {
}
}
fun getStreamInfo(userId: String) : IData<IStreamInfo> {
fun getStreamInfo(userId: String) : IData<IStreamInfo?> {
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<IStreamInfo> {
try {
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
val body = response.body?.string()
val follow = gson.fromJson(body, object: TypeToken<IData<IStreamInfo>>() {})
val follow = gson.fromJson(body, object: TypeToken<IData<IStreamInfo?>>() {})
return follow
} catch(e: Exception) {

View File

@ -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
}

View File

@ -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<String, ConcurrentLinkedQueue<ApplicationCall>>()
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)
}
}
}
}