mirror of
https://github.com/dalbodeule/chibot-chzzk-bot.git
synced 2025-06-08 23:08:20 +00:00
add Chisu playlist functions
- add Websocket - add API - version up to 1.2.0
This commit is contained in:
parent
dc81bb09f2
commit
91573a4048
@ -16,7 +16,7 @@ import space.mori.chzzk_bot.common.services.UserService
|
|||||||
object AlertCommand : CommandInterface {
|
object AlertCommand : CommandInterface {
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
override val name: String = "alert"
|
override val name: String = "alert"
|
||||||
override val command = Commands.slash(name, "명령어를 추가합니다.")
|
override val command = Commands.slash(name, "방송알람 채널을 설정합니다. / 알람 취소도 이 명령어를 이용하세요!")
|
||||||
.addOptions(OptionData(OptionType.CHANNEL, "channel", "알림을 보낼 채널을 입력하세요."))
|
.addOptions(OptionData(OptionType.CHANNEL, "channel", "알림을 보낼 채널을 입력하세요."))
|
||||||
.addOptions(OptionData(OptionType.STRING, "content", "표시될 텍스트를 입력하세요. 비워두면 알람이 취소됩니다."))
|
.addOptions(OptionData(OptionType.STRING, "content", "표시될 텍스트를 입력하세요. 비워두면 알람이 취소됩니다."))
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
|
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
|
||||||
|
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||||
|
implementation("com.google.code.gson:gson:2.11.0")
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
group = space.mori
|
group = space.mori
|
||||||
version = 1.1.2
|
version = 1.2.0
|
||||||
|
|
||||||
org.gradle.jvmargs=-Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Dfile.encoding=UTF-8
|
||||||
org.gradle.console=plain
|
org.gradle.console=plain
|
||||||
|
@ -13,6 +13,7 @@ import io.ktor.server.routing.*
|
|||||||
import io.ktor.server.websocket.*
|
import io.ktor.server.websocket.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import space.mori.chzzk_bot.webserver.routes.apiRoutes
|
import space.mori.chzzk_bot.webserver.routes.apiRoutes
|
||||||
|
import space.mori.chzzk_bot.webserver.routes.wsSongRoutes
|
||||||
import space.mori.chzzk_bot.webserver.routes.wsTimerRoutes
|
import space.mori.chzzk_bot.webserver.routes.wsTimerRoutes
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
@ -38,9 +39,10 @@ val server = embeddedServer(Netty, port = 8080) {
|
|||||||
routing {
|
routing {
|
||||||
apiRoutes()
|
apiRoutes()
|
||||||
wsTimerRoutes()
|
wsTimerRoutes()
|
||||||
|
wsSongRoutes()
|
||||||
swaggerUI("swagger-ui/index.html", "openapi/documentation.yaml") {
|
swaggerUI("swagger-ui/index.html", "openapi/documentation.yaml") {
|
||||||
options {
|
options {
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package space.mori.chzzk_bot.webserver.routes
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import space.mori.chzzk_bot.common.services.SongListService
|
||||||
|
import space.mori.chzzk_bot.common.services.UserService
|
||||||
|
|
||||||
|
fun Routing.songRoutes() {
|
||||||
|
route("/songs/{uid}") {
|
||||||
|
get {
|
||||||
|
val uid = call.parameters["uid"]
|
||||||
|
val user = uid?.let { it1 -> UserService.getUser(it1) }
|
||||||
|
if (user == null) {
|
||||||
|
call.respondText("No user found", status = HttpStatusCode.NotFound)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val songs = SongListService.getSong(user)
|
||||||
|
call.respond(songs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route("/songs") {
|
||||||
|
get {
|
||||||
|
call.respondText("Require UID", status= HttpStatusCode.BadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
package space.mori.chzzk_bot.webserver.routes
|
||||||
|
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import space.mori.chzzk_bot.common.events.*
|
||||||
|
import space.mori.chzzk_bot.common.services.UserService
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
fun Routing.wsSongRoutes() {
|
||||||
|
val sessions = ConcurrentHashMap<String, ConcurrentLinkedQueue<WebSocketServerSession>>()
|
||||||
|
val status = ConcurrentHashMap<String, TimerType>()
|
||||||
|
val logger = LoggerFactory.getLogger(this.javaClass.name)
|
||||||
|
|
||||||
|
fun addSession(uid: String, session: WebSocketServerSession) {
|
||||||
|
sessions.computeIfAbsent(uid) { ConcurrentLinkedQueue() }.add(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSession(uid: String, session: WebSocketServerSession) {
|
||||||
|
sessions[uid]?.remove(session)
|
||||||
|
if(sessions[uid]?.isEmpty() == true) {
|
||||||
|
sessions.remove(uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webSocket("/song/{uid}") {
|
||||||
|
val uid = call.parameters["uid"]
|
||||||
|
val user = uid?.let { UserService.getUser(it) }
|
||||||
|
if (uid == null) {
|
||||||
|
close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Invalid UID"))
|
||||||
|
return@webSocket
|
||||||
|
}
|
||||||
|
if (user == null) {
|
||||||
|
close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Invalid UID"))
|
||||||
|
return@webSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
addSession(uid, this)
|
||||||
|
|
||||||
|
if(status[uid] == TimerType.STREAM_OFF) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
sendSerialized(SongResponse(
|
||||||
|
SongType.STREAM_OFF.value,
|
||||||
|
uid,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (frame in incoming) {
|
||||||
|
when(frame) {
|
||||||
|
is Frame.Text -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
is Frame.Ping -> send(Frame.Pong(frame.data))
|
||||||
|
else -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e: ClosedReceiveChannelException) {
|
||||||
|
logger.error("Error in WebSocket: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
removeSession(uid, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dispatcher: CoroutinesEventBus by inject(CoroutinesEventBus::class.java)
|
||||||
|
|
||||||
|
dispatcher.subscribe(SongEvent::class) {
|
||||||
|
logger.debug("SongEvent: {} / {} {}", it.uid, it.type, it.name)
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
sessions[it.uid]?.forEach { ws ->
|
||||||
|
ws.sendSerialized(SongResponse(
|
||||||
|
it.type.value,
|
||||||
|
it.uid,
|
||||||
|
it.req_uid,
|
||||||
|
it.name,
|
||||||
|
it.author,
|
||||||
|
it.time
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SongResponse(
|
||||||
|
val type: Int,
|
||||||
|
val uid: String,
|
||||||
|
val reqUid: String?,
|
||||||
|
val name: String?,
|
||||||
|
val author: String?,
|
||||||
|
val time: Int?
|
||||||
|
)
|
@ -9,18 +9,16 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import space.mori.chzzk_bot.common.events.*
|
import space.mori.chzzk_bot.common.events.*
|
||||||
import space.mori.chzzk_bot.common.services.UserService
|
import space.mori.chzzk_bot.common.services.UserService
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
val logger: Logger = LoggerFactory.getLogger("WSTimerRoutes")
|
|
||||||
|
|
||||||
fun Routing.wsTimerRoutes() {
|
fun Routing.wsTimerRoutes() {
|
||||||
val sessions = ConcurrentHashMap<String, ConcurrentLinkedQueue<WebSocketServerSession>>()
|
val sessions = ConcurrentHashMap<String, ConcurrentLinkedQueue<WebSocketServerSession>>()
|
||||||
val status = ConcurrentHashMap<String, TimerType>()
|
val status = ConcurrentHashMap<String, TimerType>()
|
||||||
|
val logger = LoggerFactory.getLogger(this.javaClass.name)
|
||||||
|
|
||||||
fun addSession(uid: String, session: WebSocketServerSession) {
|
fun addSession(uid: String, session: WebSocketServerSession) {
|
||||||
sessions.computeIfAbsent(uid) { ConcurrentLinkedQueue() }.add(session)
|
sessions.computeIfAbsent(uid) { ConcurrentLinkedQueue() }.add(session)
|
||||||
@ -49,7 +47,7 @@ fun Routing.wsTimerRoutes() {
|
|||||||
|
|
||||||
if(status[uid] == TimerType.STREAM_OFF) {
|
if(status[uid] == TimerType.STREAM_OFF) {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
sendSerialized(TimerResponse(TimerType.STREAM_OFF.value, ""))
|
sendSerialized(TimerResponse(TimerType.STREAM_OFF.value, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user