From 590c1203bd6cdd46588d0105316a9c4fd6fde990 Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:30:00 +0900 Subject: [PATCH] some debugs on Chisu playlist --- .../chzzk_bot/chatbot/chzzk/ChzzkHandler.kt | 42 +++-- .../chzzk_bot/chatbot/chzzk/MessageHandler.kt | 26 ++- .../chzzk_bot/common/events/SongEvents.kt | 3 +- .../mori/chzzk_bot/common/models/SongList.kt | 5 +- .../common/services/SongListService.kt | 14 +- .../mori/chzzk_bot/common/utils/getYoutube.kt | 11 +- inc.env | 1 + .../space/mori/chzzk_bot/webserver/Main.kt | 2 + .../webserver/routes/ApiSongRoutes.kt | 23 ++- .../webserver/routes/WSSongRoutes.kt | 2 +- .../main/resources/openapi/documentation.yaml | 155 +++++++++++++++++- 11 files changed, 248 insertions(+), 36 deletions(-) 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 93d034d..1984b35 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 @@ -9,9 +9,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import space.mori.chzzk_bot.chatbot.chzzk.Connector.chzzk import space.mori.chzzk_bot.chatbot.discord.Discord -import space.mori.chzzk_bot.common.events.CoroutinesEventBus -import space.mori.chzzk_bot.common.events.TimerEvent -import space.mori.chzzk_bot.common.events.TimerType +import space.mori.chzzk_bot.common.events.* import space.mori.chzzk_bot.common.models.User import space.mori.chzzk_bot.common.services.LiveStatusService import space.mori.chzzk_bot.common.services.TimerConfigService @@ -39,6 +37,11 @@ object ChzzkHandler { UserService.getAllUsers().map { chzzk.getChannel(it.token)?.let { token -> addUser(token, it) } } + + handlers.forEach { handler -> + val streamInfo = getStreamInfo(handler.listener.channelId) + if (streamInfo.content.status == "OPEN") handler.isActive(true, streamInfo) + } } fun disable() { @@ -148,7 +151,6 @@ class UserHandler( get() = _isActive internal fun isActive(value: Boolean, status: IData) { - _isActive = value if(value) { logger.info("${user.username} is live.") @@ -170,10 +172,11 @@ class UserHandler( "" )) } - - delay(5000L) - listener.sendChat("${user.username} 님! 오늘도 열심히 방송하세요!") - Discord.sendDiscord(user, status) + if(!_isActive) { + delay(5000L) + listener.sendChat("${user.username} 님! 오늘도 열심히 방송하세요!") + Discord.sendDiscord(user, status) + } } } else { logger.info("${user.username} is offline.") @@ -181,12 +184,25 @@ class UserHandler( listener.closeAsync() CoroutineScope(Dispatchers.Default).launch { - dispatcher.post(TimerEvent( - channel.channelId, - TimerType.STREAM_OFF, - "" - )) + val events = listOf( + TimerEvent( + channel.channelId, + TimerType.STREAM_OFF, + null + ), + SongEvent( + channel.channelId, + SongType.STREAM_OFF, + null, + null, + null, + null, + null + ) + ) + events.forEach { dispatcher.post(it) } } } + _isActive = value } } \ 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 137610d..bb0e816 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 @@ -36,6 +36,14 @@ class MessageHandler( init { reloadCommand() + dispatcher.subscribe(SongEvent::class) { + if(it.type == SongType.STREAM_OFF) { + val user = UserService.getUser(channel.channelId) + if(! user?.let { usr -> SongListService.getSong(usr) }.isNullOrEmpty()) { + SongListService.deleteUser(user!!) + } + } + } } internal fun reloadCommand() { @@ -199,33 +207,35 @@ class MessageHandler( val url = parts[1] val songs = SongListService.getSong(user) - if (songs.any { it.url == url }) { - listener.sendChat("같은 노래가 이미 신청되어 있습니다.") - return - } - val video = getYoutubeVideo(url) if (video == null) { listener.sendChat("유튜브에서 찾을 수 없어요!") return } + if (songs.any { it.url == video.url }) { + listener.sendChat("같은 노래가 이미 신청되어 있습니다.") + return + } + SongListService.saveSong( user, msg.userId, video.url, video.name, video.author, - video.length + video.length, + msg.profile?.nickname ?: "" ) - CoroutineScope(Dispatchers.Main).launch { + CoroutineScope(Dispatchers.Default).launch { dispatcher.post(SongEvent( user.token, SongType.ADD, msg.userId, + msg.profile?.nickname ?: "", video.name, video.author, - video.length + video.length, )) } diff --git a/common/src/main/kotlin/space/mori/chzzk_bot/common/events/SongEvents.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/events/SongEvents.kt index 50da08d..f7eac26 100644 --- a/common/src/main/kotlin/space/mori/chzzk_bot/common/events/SongEvents.kt +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/events/SongEvents.kt @@ -11,7 +11,8 @@ enum class SongType(var value: Int) { class SongEvent( val uid: String, val type: SongType, - val req_uid: String?, + val reqUid: String?, + val reqName: String?, val name: String?, val author: String?, val time: Int?, diff --git a/common/src/main/kotlin/space/mori/chzzk_bot/common/models/SongList.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/models/SongList.kt index 62e0ca8..2330baa 100644 --- a/common/src/main/kotlin/space/mori/chzzk_bot/common/models/SongList.kt +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/models/SongList.kt @@ -5,15 +5,17 @@ import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.javatime.datetime +import java.time.LocalDateTime object SongLists: IntIdTable("song_list") { val user = reference("user", Users) val uid = varchar("uid", 64) val url = varchar("url", 128) val name = text("name") + val reqName = varchar("req_name", 20) val author = text("author") val time = integer("time") - val created_at = datetime("created_at") + val created_at = datetime("created_at").default(LocalDateTime.now()) } class SongList(id: EntityID) : IntEntity(id) { @@ -27,4 +29,5 @@ class SongList(id: EntityID) : IntEntity(id) { var user by User referencedOn SongLists.user var uid by SongLists.uid + var reqName by SongLists.reqName } \ No newline at end of file diff --git a/common/src/main/kotlin/space/mori/chzzk_bot/common/services/SongListService.kt b/common/src/main/kotlin/space/mori/chzzk_bot/common/services/SongListService.kt index 2a330be..ae249ad 100644 --- a/common/src/main/kotlin/space/mori/chzzk_bot/common/services/SongListService.kt +++ b/common/src/main/kotlin/space/mori/chzzk_bot/common/services/SongListService.kt @@ -8,7 +8,7 @@ import space.mori.chzzk_bot.common.models.SongLists import space.mori.chzzk_bot.common.models.User object SongListService { - fun saveSong(user: User, uid: String, url: String, name: String, author: String, time: Int) { + fun saveSong(user: User, uid: String, url: String, name: String, author: String, time: Int, reqName: String) { return transaction { SongList.new { this.user = user @@ -17,6 +17,7 @@ object SongListService { this.name = name this.author = author this.time = time + this.reqName = reqName } } } @@ -32,7 +33,7 @@ object SongListService { fun getSong(user: User): List { return transaction { - SongList.find(SongLists.user eq user.id).toList() + SongList.find(SongLists.user eq user.id).toList().sortedBy { it.created_at } } } @@ -50,4 +51,13 @@ object SongListService { songRow } } + + fun deleteUser(user: User): Boolean { + return transaction { + val songRow = SongList.find(SongLists.user eq user.id).toList() + + songRow.forEach { it.delete() } + true + } + } } \ No newline at end of file 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 52cade0..c3b5969 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 @@ -16,7 +16,7 @@ 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 durationRegex = """PT(\d+H)?(\d+M)?(\d+S)?""".toRegex() val client = OkHttpClient() val gson = Gson() @@ -33,6 +33,7 @@ fun getYoutubeVideoId(url: String): String? { } fun parseDuration(duration: String): Int { + println(duration) val matchResult = durationRegex.find(duration) val (hours, minutes, seconds) = matchResult?.destructured ?: return 0 @@ -54,7 +55,7 @@ fun getYoutubeVideo(url: String): YoutubeVideo? { .addPathSegment("videos") .addQueryParameter("id", videoId) .addQueryParameter("key", dotenv["YOUTUBE_API_KEY"]) - .addQueryParameter("part", "snippet") + .addQueryParameter("part", "snippet,contentDetails,status") .build() @@ -71,10 +72,12 @@ fun getYoutubeVideo(url: String): YoutubeVideo? { if (items == null || items.size() == 0) return null + println(json) + val item = items[0].asJsonObject val snippet = item.getAsJsonObject("snippet") - val contentDetail = item.asJsonObject.getAsJsonObject("contentDetail") - val status = contentDetail.getAsJsonObject("status") + val contentDetail = item.getAsJsonObject("contentDetails") + val status = item.getAsJsonObject("status") if (!status.get("embeddable").asBoolean) return null diff --git a/inc.env b/inc.env index 0c7848a..be0bb8b 100644 --- a/inc.env +++ b/inc.env @@ -4,5 +4,6 @@ DB_URL=jdbc:mariadb://localhost:3306/chzzk DB_USER=chzzk DB_PASS=chzzk RUN_AGENT=false +YOUTUBE_API_KEY= NID_AUT= NID_SES= \ No newline at end of file diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt index fcc3ab0..e1885ec 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt @@ -13,6 +13,7 @@ import io.ktor.server.routing.* import io.ktor.server.websocket.* import kotlinx.serialization.json.Json import space.mori.chzzk_bot.webserver.routes.apiRoutes +import space.mori.chzzk_bot.webserver.routes.apiSongRoutes import space.mori.chzzk_bot.webserver.routes.wsSongRoutes import space.mori.chzzk_bot.webserver.routes.wsTimerRoutes import java.time.Duration @@ -38,6 +39,7 @@ val server = embeddedServer(Netty, port = 8080) { } routing { apiRoutes() + apiSongRoutes() wsTimerRoutes() wsSongRoutes() swaggerUI("swagger-ui/index.html", "openapi/documentation.yaml") { diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiSongRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiSongRoutes.kt index c00e8ab..694744d 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiSongRoutes.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiSongRoutes.kt @@ -4,10 +4,29 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlinx.serialization.Serializable +import space.mori.chzzk_bot.common.models.SongList import space.mori.chzzk_bot.common.services.SongListService import space.mori.chzzk_bot.common.services.UserService -fun Routing.songRoutes() { +@Serializable +data class SongsDTO( + val url: String, + val name: String, + val author: String, + val time: Int, + val reqName: String +) + +fun SongList.toDTO(): SongsDTO = SongsDTO( + this.url, + this.name, + this.author, + this.time, + this.reqName +) + +fun Routing.apiSongRoutes() { route("/songs/{uid}") { get { val uid = call.parameters["uid"] @@ -18,7 +37,7 @@ fun Routing.songRoutes() { } val songs = SongListService.getSong(user) - call.respond(songs) + call.respond(HttpStatusCode.OK, songs.map { it.toDTO() }) } } route("/songs") { diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/WSSongRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/WSSongRoutes.kt index b5d6053..621f9b3 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/WSSongRoutes.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/WSSongRoutes.kt @@ -86,7 +86,7 @@ fun Routing.wsSongRoutes() { ws.sendSerialized(SongResponse( it.type.value, it.uid, - it.req_uid, + it.reqUid, it.name, it.author, it.time diff --git a/webserver/src/main/resources/openapi/documentation.yaml b/webserver/src/main/resources/openapi/documentation.yaml index 544971c..5decdd7 100644 --- a/webserver/src/main/resources/openapi/documentation.yaml +++ b/webserver/src/main/resources/openapi/documentation.yaml @@ -8,8 +8,7 @@ servers: paths: /: get: - summary: "Webroot" - description: "Main page of this api" + description: "" responses: "200": description: "OK" @@ -22,7 +21,7 @@ paths: value: "Hello World!" /health: get: - description: "Health Check endpoint" + description: "" responses: "200": description: "OK" @@ -32,4 +31,152 @@ paths: type: "string" examples: Example#1: - value: "OK" \ No newline at end of file + value: "OK" + /song/{uid}: + get: + description: "" + parameters: + - name: "uid" + in: "path" + required: true + schema: + type: "string" + - name: "Connection" + in: "header" + required: true + description: "Websocket Connection parameter" + schema: + type: "string" + - name: "Upgrade" + in: "header" + required: true + description: "Websocket Upgrade parameter" + schema: + type: "string" + - name: "Sec-WebSocket-Key" + in: "header" + required: true + description: "Websocket Sec-WebSocket-Key parameter" + schema: + type: "string" + responses: + "101": + description: "Switching Protocols" + headers: + Connection: + required: true + schema: + type: "string" + Upgrade: + required: true + schema: + type: "string" + Sec-WebSocket-Accept: + required: true + schema: + type: "string" + /songs: + get: + description: "" + responses: + "400": + description: "Bad Request" + content: + text/plain: + schema: + type: "string" + examples: + Example#1: + value: "Require UID" + /songs/{uid}: + get: + description: "" + parameters: + - name: "uid" + in: "path" + required: true + schema: + type: "string" + responses: + "404": + description: "Not Found" + content: + text/plain: + schema: + type: "string" + examples: + Example#1: + value: "No user found" + "200": + description: "OK" + content: + '*/*': + schema: + type: "array" + items: + $ref: "#/components/schemas/SongList" + /timer/{uid}: + get: + description: "" + parameters: + - name: "uid" + in: "path" + required: true + schema: + type: "string" + - name: "Connection" + in: "header" + required: true + description: "Websocket Connection parameter" + schema: + type: "string" + - name: "Upgrade" + in: "header" + required: true + description: "Websocket Upgrade parameter" + schema: + type: "string" + - name: "Sec-WebSocket-Key" + in: "header" + required: true + description: "Websocket Sec-WebSocket-Key parameter" + schema: + type: "string" + responses: + "101": + description: "Switching Protocols" + headers: + Connection: + required: true + schema: + type: "string" + Upgrade: + required: true + schema: + type: "string" + Sec-WebSocket-Accept: + required: true + schema: + type: "string" +components: + schemas: + Object: + type: "object" + properties: {} + ResultRow: + type: "object" + properties: + fieldIndex: + type: "object" + required: + - "fieldIndex" + SongList: + type: "object" + properties: + writeValues: + $ref: "#/components/schemas/Object" + _readValues: + $ref: "#/components/schemas/ResultRow" + required: + - "id" + - "writeValues" \ No newline at end of file