add Chisu playlist functions

- add Playlist, Playlist settings configs.
- add "!노래추가" command
This commit is contained in:
dalbodeule
2024-08-04 13:50:05 +09:00
parent 1be1b69425
commit dc81bb09f2
9 changed files with 281 additions and 8 deletions

View File

@@ -31,6 +31,9 @@ dependencies {
// https://mvnrepository.com/artifact/io.github.cdimascio/dotenv-kotlin
implementation("io.github.cdimascio:dotenv-kotlin:6.4.1")
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation("com.squareup.okhttp3:okhttp:4.12.0")
testImplementation(kotlin("test"))
}

View File

@@ -0,0 +1,20 @@
package space.mori.chzzk_bot.common.events
enum class SongType(var value: Int) {
ADD(0),
REMOVE(1),
NEXT(2),
STREAM_OFF(50)
}
class SongEvent(
val uid: String,
val type: SongType,
val req_uid: String?,
val name: String?,
val author: String?,
val time: Int?,
): Event {
var TAG = javaClass.simpleName
}

View File

@@ -0,0 +1,18 @@
package space.mori.chzzk_bot.common.models
import org.jetbrains.exposed.dao.IntEntity
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.ReferenceOption
object SongConfigs: IntIdTable("song_config") {
val user = reference("user", Users, onDelete = ReferenceOption.CASCADE)
val option = integer("option")
}
class SongConfig(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<TimerConfig>(TimerConfigs)
var user by User referencedOn TimerConfigs.user
var option by TimerConfigs.option
}

View File

@@ -0,0 +1,30 @@
package space.mori.chzzk_bot.common.models
import org.jetbrains.exposed.dao.IntEntity
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
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 author = text("author")
val time = integer("time")
val created_at = datetime("created_at")
}
class SongList(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<SongList>(SongLists)
var url by SongLists.url
var name by SongLists.name
var author by SongLists.author
var time by SongLists.time
var created_at by SongLists.created_at
var user by User referencedOn SongLists.user
var uid by SongLists.uid
}

View File

@@ -0,0 +1,53 @@
package space.mori.chzzk_bot.common.services
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import space.mori.chzzk_bot.common.models.SongList
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) {
return transaction {
SongList.new {
this.user = user
this.uid = uid
this.url = url
this.name = name
this.author = author
this.time = time
}
}
}
fun getSong(user: User, uid: String): List<SongList> {
return transaction {
SongList.find(
(SongLists.user eq user.id) and
(SongLists.uid eq uid)
).toList()
}
}
fun getSong(user: User): List<SongList> {
return transaction {
SongList.find(SongLists.user eq user.id).toList()
}
}
fun deleteSong(user: User, uid: String, name: String): SongList {
return transaction {
val songRow = SongList.find(
(SongLists.user eq user.id) and
(SongLists.uid eq uid) and
(SongLists.name eq name)
).firstOrNull()
songRow ?: throw RuntimeException("Song not found! ${user.username} / $uid / $name")
songRow.delete()
songRow
}
}
}

View File

@@ -0,0 +1,91 @@
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
data class YoutubeVideo(
val url: String,
val name: String,
val author: String,
val length: Int
)
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
}
fun getYoutubeVideoId(url: String): String? {
val matchResult = regex.find(url)
return matchResult?.groups?.get(1)?.value
}
fun parseDuration(duration: String): Int {
val matchResult = durationRegex.find(duration)
val (hours, minutes, seconds) = matchResult?.destructured ?: return 0
val hourInSec = hours.dropLast(1).toIntOrNull()?.times(3600) ?: 0
val minutesInSec = minutes.dropLast(1).toIntOrNull()?.times(60) ?: 0
val totalSeconds = seconds.dropLast(1).toIntOrNull() ?: 0
return hourInSec + minutesInSec + totalSeconds
}
fun getYoutubeVideo(url: String): YoutubeVideo? {
val videoId = getYoutubeVideoId(url)
val api = HttpUrl.Builder()
.scheme("https")
.host("www.googleapis.com")
.addPathSegment("youtube")
.addPathSegment("v3")
.addPathSegment("videos")
.addQueryParameter("id", videoId)
.addQueryParameter("key", dotenv["YOUTUBE_API_KEY"])
.addQueryParameter("part", "snippet")
.build()
val request = Request.Builder()
.url(api)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val responseBody = response.body?.string()
val json = gson.fromJson(responseBody, JsonObject::class.java)
val items = json.getAsJsonArray("items")
if (items == null || items.size() == 0) return null
val item = items[0].asJsonObject
val snippet = item.getAsJsonObject("snippet")
val contentDetail = item.asJsonObject.getAsJsonObject("contentDetail")
val status = contentDetail.getAsJsonObject("status")
if (!status.get("embeddable").asBoolean) return null
val duration = contentDetail.get("duration").asString
val length = parseDuration(duration)
return YoutubeVideo(
"https://www.youtube.com/watch?v=$videoId",
snippet.get("title").asString,
snippet.get("channelTitle").asString,
length
)
}
}