54 Commits

Author SHA1 Message Date
dalbodeule
43b6869100 register command in chat some fix 2024-06-29 21:11:19 +09:00
dalbodeule
ba12fd655b register discord command bug fix 2024-06-24 19:41:48 +09:00
dalbodeule
b1d69e90ef add command manage commands. 2024-06-24 19:06:24 +09:00
dalbodeule
a9aa5188f9 if stream end, send message and can't use commands.(live image url fix) 2024-06-17 19:43:19 +09:00
dalbodeule
e9a2e6b918 fix some bugs 6(live image url fix) 2024-06-17 18:21:47 +09:00
dalbodeule
035a6dc6cd fix some bugs 5(alert final fix) 2024-06-17 18:19:40 +09:00
dalbodeule
a774418259 fix some bugs 4(change useragent) 2024-06-17 17:53:27 +09:00
dalbodeule
e31efc0212 fix some bugs 3(streamInfo add errorhandler) 2024-06-17 17:35:48 +09:00
dalbodeule
eae675eaf6 fix some bugs 2(streamInfo get time fix) 2024-06-17 17:02:43 +09:00
dalbodeule
be84a73828 fix some bugs 2024-06-17 16:47:11 +09:00
dalbodeule
fbb0e50379 add alert commands 2024-06-17 16:21:34 +09:00
dalbodeule
3f60348ace stream info add 2024-06-17 16:10:34 +09:00
dalbodeule
09bb485a13 stream info(getStreamInfo fun) add 2024-06-16 22:07:15 +09:00
dalbodeule
c22c70398f follow period message fix done. 2024-06-16 13:25:05 +09:00
dalbodeule
8af0c3ac33 follow period message handler end. 2024-06-16 12:38:05 +09:00
dalbodeule
557600f812 follow period message handler add.
but not yet activated...
2024-06-14 16:03:09 +09:00
dalbodeule
e85561dd74 async connect... 2024-06-14 13:58:25 +09:00
dalbodeule
1a1b02506f arm7 give up 2024-06-14 13:02:33 +09:00
dalbodeule
5a3cdbd45a give up graalvm native image. 2024-06-14 12:40:30 +09:00
dalbodeule
ceb730a933 command error fix(enable http/https) 2024-06-14 10:28:08 +09:00
dalbodeule
f795e51845 dotenv error fix(ignoreIfMissing=true) 2024-06-14 01:44:54 +09:00
dalbodeule
9edf2d44ee fix build.yml (x22) 2024-06-14 01:33:38 +09:00
dalbodeule
0cd8a274e0 fix build.yml (x21) 2024-06-14 01:03:25 +09:00
dalbodeule
a98758532f fix build.yml (x20) 2024-06-14 00:46:43 +09:00
dalbodeule
22f47737df fix build.yml (x19) 2024-06-14 00:20:01 +09:00
dalbodeule
f5a97348be fix build.yml (x18) 2024-06-14 00:15:09 +09:00
dalbodeule
152752218b fix build.yml (x17) 2024-06-14 00:07:34 +09:00
dalbodeule
618a31c121 fix build.yml (x16) 2024-06-14 00:05:18 +09:00
dalbodeule
d85abcbe43 fix build.yml (x15) 2024-06-13 23:56:32 +09:00
dalbodeule
8675d0bd85 fix build.yml (x14) 2024-06-13 23:52:05 +09:00
dalbodeule
1c52466024 fix build.yml (x13) 2024-06-13 23:51:37 +09:00
dalbodeule
87f8cb0248 fix build.yml (x12) 2024-06-13 23:48:01 +09:00
dalbodeule
ea05474deb fix build.yml (x11) 2024-06-13 23:45:01 +09:00
dalbodeule
3c5ce73734 fix build.yml (x10) 2024-06-13 23:42:55 +09:00
dalbodeule
a65772f575 fix build.yml (x9) 2024-06-13 22:38:00 +09:00
dalbodeule
0481681dd4 fix build.yml (x8) 2024-06-13 22:35:30 +09:00
dalbodeule
d717b297f3 fix build.yml (x7) 2024-06-13 20:06:22 +09:00
dalbodeule
74645ee5da fix build.yml (x6) 2024-06-13 20:05:11 +09:00
dalbodeule
77545c3b74 fix build.yml (x5) 2024-06-13 20:02:23 +09:00
dalbodeule
9a2e14ad21 fix build.yml (x4) 2024-06-13 19:43:09 +09:00
dalbodeule
d9c7636ec0 fix build.yml (x3) 2024-06-13 19:40:57 +09:00
dalbodeule
be4b9ff3ec fix build.yml (x2) 2024-06-13 19:32:08 +09:00
dalbodeule
d4497b5a13 fix dockerfile again (x1) 2024-06-13 19:24:35 +09:00
dalbodeule
20f6d84040 add counter, counter handlers 2024-06-13 14:43:49 +09:00
dalbodeule
4da72f194e fix dockerfile again(x6) 2024-06-13 02:23:35 +09:00
dalbodeule
acfa2b4b02 fix dockerfile again(x5) 2024-06-13 02:11:30 +09:00
dalbodeule
3e398ba840 fix dockerfile again(x4) 2024-06-13 01:59:17 +09:00
dalbodeule
54edd46f71 fix dockerfile again(x3) 2024-06-13 01:55:32 +09:00
dalbodeule
7ad3013d47 fix dockerfile again(x2) 2024-06-13 01:53:09 +09:00
dalbodeule
94fa1eff61 fix dockerfile 2024-06-13 01:46:39 +09:00
dalbodeule
907b8e8d2a fix github workflows with remove arm7 2024-06-13 01:40:25 +09:00
dalbodeule
086e7f9392 fix github workflows with datetime 2024-06-13 01:38:19 +09:00
dalbodeule
5cc376f22f add github workflows 2024-06-13 01:33:30 +09:00
dalbodeule
639ed5faed add dockerfile, add get-metadata.sh, docker-build.sh 2024-06-13 01:33:11 +09:00
32 changed files with 782 additions and 680 deletions

53
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: build
# event trigger: main 브랜치에 push 이벤트 발생 시 jobs가 실행된다.
on:
push:
branches: [ "main" ]
# 권한 설정
permissions:
contents: read
# jobs 정의
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up JDK 21
uses: graalvm/setup-graalvm@v1
with:
java-version: '21'
distribution: 'graalvm'
- name: Build with Gradle
run: ./gradlew build
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set datetime variable
id: vars
run: echo "DATETIME=$(date +'%Y%m%d%H%M')" >> $GITHUB_ENV
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/chzzkbot:${{ env.DATETIME }}
${{ secrets.DOCKER_USERNAME }}/chzzkbot:latest

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# Stage 1: Build the JAR file
FROM gradle:jdk21 as build
WORKDIR /app
# Copy the Gradle files and source code
COPY build.gradle.kts settings.gradle.kts gradlew gradle.properties ./
COPY gradle gradle
COPY src src
# Build the project using Gradle
RUN ./gradlew build
# Stage 2: Run the JAR file using JDK 21
FROM openjdk:21-jdk
WORKDIR /app
# Copy the JAR file from the build stage
COPY --from=build /app/build/libs/*.jar app.jar
# Set the entry point
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -23,30 +23,6 @@ application {
mainClass.set("${"${project.group}.${project.name}".lowercase()}.MainKt")
}
graalvmNative {
agent {
trackReflectionMetadata.set(true)
metadataCopy {
outputDirectories.add("src/main/resources/META-INF/native-image")
mergeWithExisting.set(true)
}
}
binaries {
binaries.all {
resources.autodetect()
}
named("main") {
useFatJar.set(true)
sharedLibrary.set(false)
buildArgs.add("--initialize-at-build-time=org.hibernate.*")
}
}
metadataRepository {
enabled.set(true)
}
}
repositories {
mavenCentral()
}
@@ -57,7 +33,7 @@ dependencies {
exclude(module = "opus-java")
}
// https://mvnrepository.com/artifact/io.github.R2turnTrue/chzzk4j
implementation("io.github.R2turnTrue:chzzk4j:0.0.7")
implementation("io.github.R2turnTrue:chzzk4j:0.0.8")
implementation("ch.qos.logback:logback-classic:1.4.14")
@@ -66,9 +42,9 @@ dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.exposed/exposed-dao
implementation("org.jetbrains.exposed:exposed-dao:0.51.1")
// https://mvnrepository.com/artifact/org.jetbrains.exposed/exposed-jdbc
runtimeOnly("org.jetbrains.exposed:exposed-jdbc:0.51.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.51.1")
// https://mvnrepository.com/artifact/org.jetbrains.exposed/exposed-kotlin-datetime
runtimeOnly("org.jetbrains.exposed:exposed-kotlin-datetime:0.51.1")
implementation("org.jetbrains.exposed:exposed-java-time:0.51.1")
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation("com.zaxxer:HikariCP:5.1.0")
@@ -77,8 +53,8 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation("org.jetbrains.kotlin:kotlin-reflect:2.0.0")
// https://mvnrepository.com/artifact/org.reflections/reflections
implementation("org.reflections:reflections:0.10.2")
implementation("com.google.code.gson:gson:2.11.0")
// https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client
implementation("org.mariadb.jdbc:mariadb-java-client:3.4.0")
@@ -101,4 +77,4 @@ tasks.withType<Jar> {
})
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
}

4
docker-build.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
current_time=$(date +"%Y%m%d%H%M")
docker build -t dalbodeule/chzzkbot:latest -t dalbodeule/chzzkbot:$current_time --push .

View File

@@ -2,18 +2,13 @@ package space.mori.chzzk_bot
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.github.cdimascio.dotenv.dotenv
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.models.Commands
import space.mori.chzzk_bot.models.Users
import space.mori.chzzk_bot.models.*
object Connector {
private val dotenv = dotenv()
val hikariConfig = HikariConfig().apply {
private val hikariConfig = HikariConfig().apply {
jdbcUrl = dotenv["DB_URL"]
driverClassName = "org.mariadb.jdbc.Driver"
username = dotenv["DB_USER"]
@@ -24,12 +19,10 @@ object Connector {
init {
Database.connect(dataSource)
val tables = listOf(Users, Commands)
val tables = listOf(Users, Commands, Counters, DailyCounters, PersonalCounters)
transaction {
tables.forEach { table ->
SchemaUtils.createMissingTablesAndColumns(table)
}
SchemaUtils.createMissingTablesAndColumns(* tables.toTypedArray())
}
}
}

View File

@@ -9,29 +9,34 @@ import space.mori.chzzk_bot.chzzk.ChzzkHandler
import space.mori.chzzk_bot.discord.Discord
import space.mori.chzzk_bot.chzzk.Connector as ChzzkConnector
import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess
val dotenv = dotenv()
val dotenv = dotenv {
ignoreIfMissing = true
}
val logger: Logger = LoggerFactory.getLogger("main")
val discord = Discord()
val connector = Connector
val chzzkConnector = ChzzkConnector
val chzzkHandler = ChzzkHandler
fun main(args: Array<String>) {
val discord = Discord()
val connector = Connector
val chzzkConnector = ChzzkConnector
val chzzkHandler = ChzzkHandler
discord.enable()
chzzkHandler.enable()
chzzkHandler.runStreamInfo()
if(dotenv.get("RUN_AGENT", "false").toBoolean()) {
runBlocking {
delay(TimeUnit.MINUTES.toMillis(1))
discord.disable()
exitProcess(0)
}
}
Runtime.getRuntime().addShutdownHook(Thread {
logger.info("Shutting down...")
chzzkHandler.stopStreamInfo()
chzzkHandler.disable()
discord.disable()
connector.dataSource.close()

View File

@@ -0,0 +1,142 @@
package space.mori.chzzk_bot.chzzk
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
data class IData<T>(
val code: Int = 200,
val message: String? = null,
val content: T
)
// Follows
data class IFollowContent(
val userIdHash: String = "",
val nickname: String = "",
val profileImageUrl: String = "",
val userRoleCode: String = "",
val badge: Badge? = null,
val title: Title? = null,
val verifiedMark: Boolean = false,
val activityBadges: List<String> = emptyList(),
val streamingProperty: StreamingProperty = StreamingProperty()
)
data class Badge(
val imageUrl: String = ""
)
data class Title(
val name: String = "",
val color: String = ""
)
data class StreamingProperty(
val following: Following? = Following(),
val nicknameColor: NicknameColor = NicknameColor()
)
data class Following(
val followDate: String? = null
)
data class NicknameColor(
val colorCode: String = ""
)
// Stream info
data class IStreamInfo(
val liveId: Int = 0,
val liveTitle: String = "",
val status: String = "",
val liveImageUrl: String = "",
val defaultThumbnailImageUrl: String? = null,
val concurrentUserCount: Int = 0,
val accumulateCount: Int = 0,
val openDate: String = "",
val closeDate: String = "",
val adult: Boolean = false,
val clipActive: Boolean = false,
val tags: List<String> = emptyList(),
val chatChannelId: String = "",
val categoryType: String = "",
val liveCategory: String = "",
val liveCategoryValue: String = "",
val chatActive: Boolean = true,
val chatAvailableGroup: String = "",
val paidPromotion: Boolean = false,
val chatAvailableCondition: String = "",
val minFollowerMinute: Int = 0,
val livePlaybackJson: String = "",
val p2pQuality: List<Any> = emptyList(),
val channel: Channel = Channel(),
val livePollingStatusJson: String = "",
val userAdultStatus: String? = null,
val chatDonationRankingExposure: Boolean = true,
val adParameter: AdParameter = AdParameter()
)
data class Channel(
val channelId: String = "",
val channelName: String = "",
val channelImageUrl: String = "",
val verifiedMark: Boolean = false
)
data class AdParameter(
val tag: String = ""
)
// OkHttpClient에 Interceptor 추가
val client = OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
chain.proceed(
chain.request()
.newBuilder()
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.build()
)
}
.build()
val gson = Gson()
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)
.build()
client.newCall(request).execute().use { response ->
try {
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
val body = response.body?.string()
val follow = gson.fromJson(body, object: TypeToken<IData<IFollowContent>>() {})
return follow
} catch(e: Exception) {
throw e
}
}
}
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)
.build()
client.newCall(request).execute().use { response ->
try {
if(!response.isSuccessful) throw IOException("Unexpected code ${response.code}")
val body = response.body?.string()
val follow = gson.fromJson(body, object: TypeToken<IData<IStreamInfo>>() {})
return follow
} catch(e: Exception) {
throw e
}
}
}

View File

@@ -1,26 +1,33 @@
package space.mori.chzzk_bot.chzzk
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.chzzk.Connector.chzzk
import space.mori.chzzk_bot.discord
import space.mori.chzzk_bot.models.User
import space.mori.chzzk_bot.services.UserService
import xyz.r2turntrue.chzzk4j.chat.ChatEventListener
import xyz.r2turntrue.chzzk4j.chat.ChatMessage
import xyz.r2turntrue.chzzk4j.chat.ChzzkChat
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
import java.lang.Exception
import java.net.SocketTimeoutException
import java.time.Instant
object ChzzkHandler {
private val handlers = mutableListOf<UserHandler>()
private val logger = LoggerFactory.getLogger(this::class.java)
@Volatile private var running: Boolean = false
internal fun addUser(chzzkChannel: ChzzkChannel) {
handlers.add(UserHandler(chzzkChannel, logger))
internal fun addUser(chzzkChannel: ChzzkChannel, user: User) {
handlers.add(UserHandler(chzzkChannel, logger, user))
}
internal fun enable() {
UserService.getAllUsers().map {
chzzk.getChannel(it.token)?.let { token -> addUser(token)}
chzzk.getChannel(it.token)?.let { token -> addUser(token, it)}
}
}
@@ -31,15 +38,57 @@ object ChzzkHandler {
}
internal fun reloadCommand(chzzkChannel: ChzzkChannel) {
val handler = handlers.firstOrNull { it.channel == chzzkChannel }
val handler = handlers.firstOrNull { it.channel.channelId == chzzkChannel.channelId }
if (handler != null)
handler.reloadCommand()
else
throw RuntimeException("${chzzkChannel.channelName} doesn't have handler")
}
internal fun reloadUser(chzzkChannel: ChzzkChannel, user: User) {
val handler = handlers.firstOrNull { it.channel.channelId == chzzkChannel.channelId }
if (handler != null)
handler.reloadUser(user)
else
throw RuntimeException("${chzzkChannel.channelName} doesn't have handler")
}
internal fun runStreamInfo() {
running = true
val thread = Thread({
while(running) {
handlers.forEach {
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 == "CLOSED" && it.isActive) it.isActive(false, streamInfo)
} catch(e: SocketTimeoutException) {
logger.info("Timeout: ${it.channel.channelName} / ${e.stackTraceToString()}")
} catch (e: Exception) {
logger.info("Exception: ${it.channel.channelName} / ${e.stackTraceToString()}")
} finally {
Thread.sleep(5000)
}
}
Thread.sleep(60000)
}
}, "Chzzk-StreamInfo")
thread.start()
}
internal fun stopStreamInfo() {
running = false
}
}
class UserHandler(val channel: ChzzkChannel, private val logger: Logger) {
class UserHandler(
val channel: ChzzkChannel,
private val logger: Logger,
private var user: User,
private var _isActive: Boolean = false
) {
private lateinit var messageHandler: MessageHandler
private var listener: ChzzkChat = chzzk.chat(channel.channelId)
@@ -56,7 +105,8 @@ class UserHandler(val channel: ChzzkChannel, private val logger: Logger) {
}
override fun onChat(msg: ChatMessage) {
messageHandler.handle(msg)
if(!_isActive) return
messageHandler.handle(msg, user)
}
override fun onConnectionClosed(code: Int, reason: String?, remote: Boolean, tryingToReconnect: Boolean) {
@@ -68,14 +118,54 @@ class UserHandler(val channel: ChzzkChannel, private val logger: Logger) {
init {
logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}")
listener.connectBlocking()
listener.connectAsync()
}
internal fun disable() {
listener.closeBlocking()
listener.closeAsync()
}
internal fun reloadCommand() {
messageHandler.reloadCommand()
}
internal fun reloadUser(user: User) {
this.user = user
}
internal val isActive: Boolean
get() = _isActive
internal fun isActive(value: Boolean, status: IData<IStreamInfo>) {
_isActive = value
if(value) {
logger.info("${user.username} is live.")
if(user.liveAlertMessage != "" && user.liveAlertGuild != null && user.liveAlertChannel != null) {
val channel = discord.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.setDescription("${user.username} 님이 방송을 시작했습니다.")
embed.setUrl(status.content.channel.channelImageUrl)
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"))
channel.sendMessage(
MessageCreateBuilder()
.setContent(user.liveAlertMessage)
.setEmbeds(embed.build())
.build()
).queue()
listener.sendChat("${user.username} 님의 방송이 감지되었습니다.")
}
} else {
logger.info("${user.username} is offline.")
listener.sendChat("${user.username} 님! 방송 수고하셨습니다.")
}
}
}

View File

@@ -1,13 +1,12 @@
package space.mori.chzzk_bot.chzzk
import io.github.cdimascio.dotenv.dotenv
import org.slf4j.LoggerFactory
import xyz.r2turntrue.chzzk4j.Chzzk
import xyz.r2turntrue.chzzk4j.ChzzkBuilder
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
import space.mori.chzzk_bot.dotenv
object Connector {
private val dotenv = dotenv()
val chzzk: Chzzk = ChzzkBuilder()
.withAuthorization(dotenv["NID_AUT"], dotenv["NID_SES"])
.build()

View File

@@ -1,18 +1,33 @@
package space.mori.chzzk_bot.chzzk
import org.slf4j.Logger
import space.mori.chzzk_bot.models.Command
import space.mori.chzzk_bot.models.User
import space.mori.chzzk_bot.services.CommandService
import space.mori.chzzk_bot.services.CounterService
import space.mori.chzzk_bot.services.UserService
import xyz.r2turntrue.chzzk4j.chat.ChatMessage
import xyz.r2turntrue.chzzk4j.chat.ChzzkChat
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.Period
import java.time.format.DateTimeFormatter
import java.util.*
class MessageHandler(
private val channel: ChzzkChannel,
private val logger: Logger,
private val listener: ChzzkChat
) {
private val commands = mutableMapOf<String, () -> Unit>()
private val commands = mutableMapOf<String, (msg: ChatMessage, user: User) -> Unit>()
private val counterPattern = Regex("<counter:([^>]+)>")
private val personalCounterPattern = Regex("<counter_personal:([^>]+)>")
private val dailyCounterPattern = Regex("<daily_counter:([^>]+)>")
private val namePattern = Regex("<name>")
private val followPattern = Regex("<following>")
init {
reloadCommand()
@@ -22,18 +37,142 @@ class MessageHandler(
val user = UserService.getUser(channel.channelId)
?: throw RuntimeException("User not found. it's bug? ${channel.channelName} - ${channel.channelId}")
val commands = CommandService.getCommands(user)
val manageCommands = mapOf("!명령어추가" to this::manageAddCommand, "!명령어삭제" to this::manageRemoveCommand, "!명령어수정" to this::manageUpdateCommand)
manageCommands.forEach { (commandName, command) ->
this.commands[commandName] = command
}
commands.map {
this.commands.put(it.command.lowercase()) {
logger.debug("${channel.channelName} - ${it.command} - ${it.content}")
listener.sendChat(it.content)
this.commands.put(it.command.lowercase()) { msg, user ->
logger.debug("${channel.channelName} - ${it.command} - ${it.content}/${it.failContent}")
val result = replaceCounters(Pair(it.content, it.failContent), user, msg, listener, msg.profile?.nickname ?: "")
listener.sendChat(result)
}
}
}
internal fun handle(msg: ChatMessage) {
private fun manageAddCommand(msg: ChatMessage, user: User) {
if (msg.profile?.userRoleCode != "streaming_channel_manager" && msg.profile?.userRoleCode != "streamer") {
listener.sendChat("매니저만 명령어를 추가할 수 있습니다.")
return
}
val parts = msg.content.split(" ", limit = 3)
if (parts.size < 3) {
listener.sendChat("명령어 추가 형식은 '!명령어추가 명령어 내용'입니다.")
return
}
if (commands.containsKey(parts[0])) {
listener.sendChat("${parts[1]} 명령어는 이미 있는 명령어입니다.")
return
}
val command = parts[1]
val content = parts[2]
CommandService.saveCommand(user, command, content, "")
listener.sendChat("명령어 '$command' 추가되었습니다.")
}
private fun manageUpdateCommand(msg: ChatMessage, user: User) {
if (msg.profile?.userRoleCode != "streaming_channel_manager" && msg.profile?.userRoleCode != "streamer") {
listener.sendChat("매니저만 명령어를 추가할 수 있습니다.")
return
}
val parts = msg.content.split(" ", limit = 3)
if (parts.size < 3) {
listener.sendChat("명령어 수정 형식은 '!명령어수정 명령어 내용'입니다.")
return
}
if (!commands.containsKey(parts[0])) {
listener.sendChat("${parts[1]} 명령어는 없는 명령어입니다.")
return
}
val command = parts[1]
val content = parts[2]
CommandService.updateCommand(user, command, content, "")
listener.sendChat("명령어 '$command' 수정되었습니다.")
}
private fun manageRemoveCommand(msg: ChatMessage, user: User) {
if (msg.profile?.userRoleCode != "streaming_channel_manager" && msg.profile?.userRoleCode != "streamer") {
listener.sendChat("매니저만 명령어를 삭제할 수 있습니다.")
return
}
val parts = msg.content.split(" ", limit = 2)
if (parts.size < 2) {
listener.sendChat("명령어 삭제 형식은 '!명령어삭제 명령어'입니다.")
return
}
val command = parts[1]
CommandService.removeCommand(user, command)
listener.sendChat("명령어 '$command' 삭제되었습니다.")
}
internal fun handle(msg: ChatMessage, user: User) {
val commandKey = msg.content.split(' ')[0]
commands[commandKey.lowercase()]?.let { it() }
commands[commandKey.lowercase()]?.let { it(msg, user) }
}
private fun replaceCounters(chat: Pair<String, String>, user: User, msg: ChatMessage, listener: ChzzkChat, userName: String): String {
var result = chat.first
var isFail = false
result = counterPattern.replace(result) {
val name = it.groupValues[1]
CounterService.updateCounterValue(name, 1, user).toString()
}
result = personalCounterPattern.replace(result) {
val name = it.groupValues[1]
CounterService.updatePersonalCounterValue(name, msg.userId, 1, user).toString()
}
result = dailyCounterPattern.replace(result) {
val name = it.groupValues[1]
val dailyCounter = CounterService.getDailyCounterValue(name, msg.userId, user)
return@replace if(dailyCounter.second)
CounterService.updateDailyCounterValue(name, msg.userId, 1, user).first.toString()
else {
isFail = true
dailyCounter.first.toString()
}
}
if(isFail && chat.second != "") {
result = chat.second
result = dailyCounterPattern.replace(result) {
val name = it.groupValues[1]
val dailyCounter = CounterService.getDailyCounterValue(name, msg.userId, user)
dailyCounter.first.toString()
}
}
result = followPattern.replace(result) {
val following = getFollowDate(listener.chatId, msg.userId)
val dateString: String = following.content.streamingProperty.following?.followDate ?: SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
Date()
)
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
// 문자열을 LocalDate 객체로 변환
val pastDate = LocalDate.parse(dateString, formatter)
val period = Period.between(pastDate, today)
period.days.toString()
}
if(isFail) {
return chat.second
}
result = namePattern.replace(result, userName)
return result
}
}

View File

@@ -3,33 +3,9 @@ package space.mori.chzzk_bot.discord
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.build.CommandData
import org.reflections.Reflections
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Command
interface CommandInterface {
val name: String
fun run(event: SlashCommandInteractionEvent, bot: JDA): Unit
val command: CommandData
}
fun getCommands(): List<CommandInterface> {
val commandList = mutableListOf<CommandInterface>()
val packageName = "space.mori.chzzk_bot.discord.commands"
val reflections = Reflections(packageName)
val annotatedClasses = reflections.getTypesAnnotatedWith(Command::class.java)
for(clazz in annotatedClasses) {
val obj = clazz.kotlin.objectInstance
if(obj is CommandInterface) {
commandList.add(obj)
} else {
throw IllegalStateException("${clazz.name} is not a CommandInterface")
}
}
return commandList.toList()
}

View File

@@ -6,19 +6,29 @@ import net.dv8tion.jda.api.entities.Activity
import net.dv8tion.jda.api.entities.Guild
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.dotenv
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.discord.commands.*
class Discord: ListenerAdapter() {
private lateinit var bot: JDA
private var guild: Guild? = null
private val logger = LoggerFactory.getLogger(this::class.java)
private val commands = getCommands()
private val commands = listOf(
AddCommand,
AlertCommand,
PingCommand,
RegisterCommand,
RemoveCommand,
UpdateCommand,
)
override fun onSlashCommandInteraction(event: SlashCommandInteractionEvent) {
event.deferReply().queue()
commands.find { it.name == event.name }?.run(event, bot)
val handler = commands.find { it.name == event.name }
logger.debug("Handler: ${handler?.name ?: "undefined"} command")
handler?.run(event, bot)
}
internal fun enable() {
@@ -33,7 +43,10 @@ class Discord: ListenerAdapter() {
bot.updateCommands()
.addCommands(* commands.map { it.command }.toTypedArray())
.onSuccess { logger.info("Command update success!") }
.onSuccess {
logger.info("Command update success!")
logger.debug("Command list: ${commands.joinToString("/ ") { it.name }}")
}
.queue()
@@ -57,4 +70,7 @@ class Discord: ListenerAdapter() {
logger.debug(e.stackTraceToString())
}
}
}
internal fun getChannel(guildId: Long, channelId: Long) =
bot.getGuildById(guildId)?.getTextChannelById(channelId)
}

View File

@@ -8,22 +8,22 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.chzzk.ChzzkHandler
import space.mori.chzzk_bot.chzzk.Connector
import space.mori.chzzk_bot.discord.Command
import space.mori.chzzk_bot.discord.CommandInterface
import space.mori.chzzk_bot.services.CommandService
import space.mori.chzzk_bot.services.UserService
@Command
object AddCommand : CommandInterface {
private val logger = LoggerFactory.getLogger(this::class.java)
override val name: String = "add"
override val command = Commands.slash(name, "명령어를 추가합니다.")
.addOptions(OptionData(OptionType.STRING, "label", "작동할 명령어를 입력하세요.", true))
.addOptions(OptionData(OptionType.STRING, "content", "표시될 텍스트를 입력하세요.", true))
.addOptions(OptionData(OptionType.STRING, "fail_content", "카운터 업데이트 실패시 표시될 텍스트를 입력하세요.", false))
override fun run(event: SlashCommandInteractionEvent, bot: JDA) {
val label = event.getOption("label")?.asString
val content = event.getOption("content")?.asString
val failContent = event.getOption("fail_content")?.asString
if(label == null || content == null) {
event.hook.sendMessage("명령어와 텍스트는 필수 입력입니다.").queue()
@@ -35,14 +35,21 @@ object AddCommand : CommandInterface {
event.hook.sendMessage("치지직 계정을 찾을 수 없습니다.").queue()
return
}
val commands = CommandService.getCommands(user)
if (commands.any { it.command == label }) {
event.hook.sendMessage("$label 명령어는 이미 있습니다! 업데이트 명령어를 써주세요.").queue()
return
}
val chzzkChannel = Connector.getChannel(user.token)
try {
CommandService.saveCommand(user, label, content)
CommandService.saveCommand(user, label, content, failContent ?: "")
try {
ChzzkHandler.reloadCommand(chzzkChannel!!)
} catch (_: Exception) {}
event.hook.sendMessage("등록이 완료되었습니다. $label = $content").queue()
event.hook.sendMessage("등록이 완료되었습니다. $label = $content/$failContent").queue()
} catch (e: Exception) {
event.hook.sendMessage("에러가 발생했습니다.").queue()
logger.debug(e.stackTraceToString())

View File

@@ -0,0 +1,44 @@
package space.mori.chzzk_bot.discord.commands
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.OptionType
import net.dv8tion.jda.api.interactions.commands.build.Commands
import net.dv8tion.jda.api.interactions.commands.build.OptionData
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.chzzk.ChzzkHandler
import space.mori.chzzk_bot.chzzk.Connector
import space.mori.chzzk_bot.discord.CommandInterface
import space.mori.chzzk_bot.services.UserService
object AlertCommand : CommandInterface {
private val logger = LoggerFactory.getLogger(this::class.java)
override val name: String = "alert"
override val command = Commands.slash(name, "명령어를 추가합니다.")
.addOptions(OptionData(OptionType.CHANNEL, "channel", "알림을 보낼 채널을 입력하세요."))
.addOptions(OptionData(OptionType.STRING, "content", "표시될 텍스트를 입력하세요. 비워두면 알람이 취소됩니다."))
override fun run(event: SlashCommandInteractionEvent, bot: JDA) {
val channel = event.getOption("channel")?.asChannel
val content = event.getOption("content")?.asString
val user = UserService.getUser(event.user.idLong)
if(user == null) {
event.hook.sendMessage("치지직 계정을 찾을 수 없습니다.").queue()
return
}
val chzzkChannel = Connector.getChannel(user.token)
try {
val newUser = UserService.updateLiveAlert(user.id.value, channel?.guild?.idLong ?: 0L, channel?.idLong ?: 0L, content ?: "")
try {
ChzzkHandler.reloadUser(chzzkChannel!!, newUser)
} catch (_: Exception) {}
event.hook.sendMessage("업데이트가 완료되었습니다.").queue()
} catch (e: Exception) {
event.hook.sendMessage("에러가 발생했습니다.").queue()
logger.debug(e.stackTraceToString())
}
}
}

View File

@@ -4,11 +4,9 @@ import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.build.Commands
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.discord.Command
import space.mori.chzzk_bot.discord.CommandInterface
@Command()
object Ping: CommandInterface {
object PingCommand: CommandInterface {
private val logger = LoggerFactory.getLogger(this::class.java)
override val name = "ping"
override val command = Commands.slash(name, "봇이 살아있을까요?")

View File

@@ -8,12 +8,10 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.chzzk.ChzzkHandler
import space.mori.chzzk_bot.chzzk.Connector
import space.mori.chzzk_bot.discord.Command
import space.mori.chzzk_bot.discord.CommandInterface
import space.mori.chzzk_bot.services.UserService
@Command
object Register: CommandInterface {
object RegisterCommand: CommandInterface {
private val logger = LoggerFactory.getLogger(this::class.java)
override val name = "register"
override val command = Commands.slash(name, "치지직 계정을 등록합니다.")
@@ -40,9 +38,9 @@ object Register: CommandInterface {
}
try {
UserService.saveUser(chzzkChannel.channelName, chzzkChannel.channelId, event.user.idLong)
ChzzkHandler.addUser(chzzkChannel)
val user = UserService.saveUser(chzzkChannel.channelName, chzzkChannel.channelId, event.user.idLong)
event.hook.sendMessage("등록이 완료되었습니다. `${chzzkChannel.channelId}` - `${chzzkChannel.channelName}`")
ChzzkHandler.addUser(chzzkChannel, user)
} catch(e: Exception) {
event.hook.sendMessage("에러가 발생했습니다.").queue()
logger.debug(e.stackTraceToString())

View File

@@ -8,12 +8,10 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.chzzk.ChzzkHandler
import space.mori.chzzk_bot.chzzk.Connector
import space.mori.chzzk_bot.discord.Command
import space.mori.chzzk_bot.discord.CommandInterface
import space.mori.chzzk_bot.services.CommandService
import space.mori.chzzk_bot.services.UserService
@Command
object RemoveCommand : CommandInterface {
private val logger = LoggerFactory.getLogger(this::class.java)
override val name: String = "remove"

View File

@@ -8,23 +8,22 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData
import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.chzzk.ChzzkHandler
import space.mori.chzzk_bot.chzzk.Connector
import space.mori.chzzk_bot.discord.Command
import space.mori.chzzk_bot.discord.CommandInterface
import space.mori.chzzk_bot.services.CommandService
import space.mori.chzzk_bot.services.UserService
import xyz.r2turntrue.chzzk4j.types.channel.ChzzkChannel
@Command
object UpdateCommand : CommandInterface {
private val logger = LoggerFactory.getLogger(this::class.java)
override val name: String = "update"
override val command = Commands.slash(name, "명령어를 수정합니다.")
.addOptions(OptionData(OptionType.STRING, "label", "수정할 명령어를 입력하세요.", true))
.addOptions(OptionData(OptionType.STRING, "content", "표시될 텍스트를 입력하세요.", true))
.addOptions(OptionData(OptionType.STRING, "fail_content", "카운터 업데이트 실패시 표시될 텍스트를 입력하세요.", false))
override fun run(event: SlashCommandInteractionEvent, bot: JDA) {
val label = event.getOption("label")?.asString
val content = event.getOption("content")?.asString
val failContent = event.getOption("fail_content")?.asString
if(label == null || content == null) {
event.hook.sendMessage("명령어와 텍스트는 필수 입력입니다.").queue()
@@ -39,10 +38,8 @@ object UpdateCommand : CommandInterface {
val chzzkChannel = Connector.getChannel(user.token)
try {
CommandService.updateCommand(user, label, content)
try {
ChzzkHandler.reloadCommand(chzzkChannel!!)
} catch (_: Exception) {}
CommandService.updateCommand(user, label, content, failContent ?: "")
chzzkChannel?.let { ChzzkHandler.reloadCommand(it) }
event.hook.sendMessage("등록이 완료되었습니다. $label = $content").queue()
} catch (e: Exception) {
event.hook.sendMessage("에러가 발생했습니다.").queue()

View File

@@ -10,6 +10,7 @@ object Commands: IntIdTable("commands") {
val user = reference("user", Users, onDelete = ReferenceOption.CASCADE)
val command = varchar("command", 255)
val content = text("content")
val failContent = text("fail_content")
}
class Command(id: EntityID<Int>) : IntEntity(id) {
@@ -18,4 +19,5 @@ class Command(id: EntityID<Int>) : IntEntity(id) {
var user by User referencedOn Commands.user
var command by Commands.command
var content by Commands.content
var failContent by Commands.failContent
}

View File

@@ -0,0 +1,20 @@
package space.mori.chzzk_bot.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
object Counters: IntIdTable("counters") {
val name = varchar("name", 255)
val value = integer("value")
val user = reference("streamer", Users)
}
class Counter(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Counter>(Counters)
var name by Counters.name
var value by Counters.value
var user by User referencedOn Counters.user
}

View File

@@ -0,0 +1,25 @@
package space.mori.chzzk_bot.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.date
object DailyCounters: IntIdTable("daily_counters") {
val name = varchar("name", 255)
val userId = varchar("user_id", 64)
val value = integer("value")
val updatedAt = date("updated_at")
val user = reference("streamer", Users)
}
class DailyCounter(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<DailyCounter>(DailyCounters)
var name by DailyCounters.name
var userId by DailyCounters.userId
var value by DailyCounters.value
var updatedAt by DailyCounters.updatedAt
var user by User referencedOn DailyCounters.user
}

View File

@@ -0,0 +1,22 @@
package space.mori.chzzk_bot.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
object PersonalCounters: IntIdTable("personal_counters") {
val name = varchar("name", 255)
val userId = varchar("user_id", 64)
val value = integer("value")
val user = reference("streamer", Users)
}
class PersonalCounter(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<PersonalCounter>(PersonalCounters)
var name by PersonalCounters.name
var userId by PersonalCounters.userId
var value by PersonalCounters.value
var user by User referencedOn PersonalCounters.user
}

View File

@@ -10,6 +10,9 @@ object Users: IntIdTable("users") {
val username = varchar("username", 255)
val token = varchar("token", 64)
val discord = long("discord")
val liveAlertGuild = long("live_alert_guild").nullable()
val liveAlertChannel = long("live_alert_channel").nullable()
val liveAlertMessage = text("live_alert_message").nullable()
}
class User(id: EntityID<Int>) : IntEntity(id) {
@@ -18,4 +21,7 @@ class User(id: EntityID<Int>) : IntEntity(id) {
var username by Users.username
var token by Users.token
var discord by Users.discord
var liveAlertGuild by Users.liveAlertGuild
var liveAlertChannel by Users.liveAlertChannel
var liveAlertMessage by Users.liveAlertMessage
}

View File

@@ -9,12 +9,13 @@ import space.mori.chzzk_bot.models.Commands
import space.mori.chzzk_bot.models.User
object CommandService {
fun saveCommand(user: User, command: String, content: String): Command {
fun saveCommand(user: User, command: String, content: String, failContent: String): Command {
return transaction {
return@transaction Command.new {
this.user = user
this.command = command
this.content = content
this.failContent = failContent
}
}
}
@@ -26,31 +27,32 @@ object CommandService {
commandRow ?: throw RuntimeException("Command not found! $command")
commandRow.delete()
return@transaction commandRow
commandRow
}
}
fun updateCommand(user: User, command: String, content: String): Command {
fun updateCommand(user: User, command: String, content: String, failContent: String): Command {
return transaction {
val updated = Commands.update({Commands.user eq user.id and(Commands.command eq command)}) {
it[Commands.content] = content
it[Commands.failContent] = failContent
}
if(updated == 0) throw RuntimeException("Command not found! $command")
return@transaction Command.find(Commands.user eq user.id and(Commands.command eq command)).first()
Command.find(Commands.user eq user.id and(Commands.command eq command)).first()
}
}
fun getCommand(id: Int): Command? {
return transaction {
return@transaction Command.findById(id)
Command.findById(id)
}
}
fun getCommands(user: User): List<Command> {
return transaction {
return@transaction Command.find(Commands.user eq user.id)
Command.find(Commands.user eq user.id)
.toList()
}
}

View File

@@ -0,0 +1,105 @@
package space.mori.chzzk_bot.services
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import space.mori.chzzk_bot.models.*
import java.time.LocalDate
object CounterService {
fun getCounterValue(name: String, user: User): Int {
return transaction {
Counter.find {
(Counters.name eq name) and (Counters.user eq user.id)
}.singleOrNull()?.value ?: 0
}
}
fun updateCounterValue(name: String, increment: Int, user: User): Int {
return transaction {
val counter = Counter.find {
(Counters.name eq name) and (Counters.user eq user.id) }.singleOrNull()
return@transaction if (counter != null) {
counter.value += increment
counter.value
} else {
val newCounter = Counter.new {
this.name = name
this.value = increment
this.user = user
}
newCounter.value
}
}
}
fun getPersonalCounterValue(name: String, userId: String, user: User): Int {
return transaction {
PersonalCounter.find {
(PersonalCounters.name eq name) and (PersonalCounters.userId eq userId) and (PersonalCounters.user eq user.id)
}.singleOrNull()?.value ?: 0
}
}
fun updatePersonalCounterValue(name: String, userId: String, increment: Int, user: User): Int {
return transaction {
val counter = PersonalCounter.find {
(PersonalCounters.name eq name) and (PersonalCounters.userId eq userId) and (PersonalCounters.user eq user.id)
}.singleOrNull()
return@transaction if (counter != null) {
counter.value += increment
counter.value
} else {
val newCounter = PersonalCounter.new {
this.name = name
this.value = increment
this.userId = userId
this.user = user
}
newCounter.value
}
}
}
fun getDailyCounterValue(name: String, userId: String, user: User): Pair<Int, Boolean> {
val today = LocalDate.now()
return transaction {
val counter = DailyCounter.find {
(DailyCounters.name eq name) and (DailyCounters.userId eq userId) and (DailyCounters.user eq user.id)
}.singleOrNull()
Pair(counter?.value ?: 0, counter?.updatedAt != today)
}
}
fun updateDailyCounterValue(name: String, userId: String, increment: Int, user: User): Pair<Int, Boolean> {
val today = LocalDate.now()
return transaction {
val counter = DailyCounter.find {
(DailyCounters.name eq name) and (DailyCounters.userId eq userId) and (DailyCounters.user eq user.id)
}.singleOrNull()
println("$counter")
if(counter == null) {
val newCounter = DailyCounter.new {
this.name = name
this.value = increment
this.userId = userId
this.updatedAt = today
this.user = user
}
return@transaction Pair(newCounter.value, true)
}
return@transaction if(counter.updatedAt == today)
Pair(counter.value, false)
else {
counter.value += increment
Pair(counter.value, true)
}
}
}
}

View File

@@ -2,13 +2,14 @@ package space.mori.chzzk_bot.services
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import space.mori.chzzk_bot.models.User
import space.mori.chzzk_bot.models.Users
object UserService {
fun saveUser(username: String, token: String, discordID: Long): User {
return transaction {
return@transaction User.new {
User.new {
this.username = username
this.token = token
this.discord = discordID
@@ -18,7 +19,7 @@ object UserService {
fun getUser(id: Int): User? {
return transaction {
return@transaction User.findById(id)
User.findById(id)
}
}
@@ -26,7 +27,7 @@ object UserService {
return transaction {
val users = User.find(Users.discord eq discordID)
return@transaction users.firstOrNull()
users.firstOrNull()
}
}
@@ -34,13 +35,28 @@ object UserService {
return transaction {
val users = User.find(Users.token eq chzzkID)
return@transaction users.firstOrNull()
users.firstOrNull()
}
}
fun getAllUsers(): List<User> {
return transaction {
return@transaction User.all().toList()
User.all().toList()
}
}
fun updateLiveAlert(id: Int, guildId: Long, channelId: Long, alertMessage: String?): User {
return transaction {
val updated = Users.update({ Users.id eq id }) {
it[liveAlertGuild] = guildId
it[liveAlertChannel] = channelId
it[liveAlertMessage] = alertMessage ?: ""
}
if(updated == 0) throw RuntimeException("User not found! $id")
val users = User.find { Users.id eq id }
return@transaction users.first()
}
}
}

View File

@@ -1,18 +0,0 @@
[
{
"name":"java.lang.Boolean",
"methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.lang.String",
"methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }]
},
{
"name":"java.lang.System",
"methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }]
},
{
"name":"space.mori.chzzk_bot.MainKt",
"methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }]
}
]

View File

@@ -1,7 +0,0 @@
[
{
"type":"agent-extracted",
"classes":[
]
}
]

View File

@@ -1,5 +0,0 @@
[
{
"interfaces":["java.sql.Connection"]
}
]

View File

@@ -1,467 +0,0 @@
[
{
"name":"[B"
},
{
"name":"[Lcom.fasterxml.jackson.databind.AbstractTypeResolver;"
},
{
"name":"[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;"
},
{
"name":"[Ljava.lang.String;"
},
{
"name":"[Ljava.sql.Statement;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.Guild;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.Member;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.Role;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.ScheduledEvent;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.User;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.emoji.RichCustomEmoji;"
},
{
"name":"[Lnet.dv8tion.jda.api.entities.sticker.GuildSticker;"
},
{
"name":"[Lnet.dv8tion.jda.api.managers.AudioManager;"
},
{
"name":"[Lsun.security.pkcs.SignerInfo;"
},
{
"name":"android.os.Build$VERSION"
},
{
"name":"apple.security.AppleProvider",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.BasicConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.joran.SerializedModelConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.util.DefaultJoranConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.AESCipher$General",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.ARCFOURCipher",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.DESCipher",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.DESedeCipher",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.DHParameters",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.HmacCore$HmacSHA384",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.sun.crypto.provider.TlsMasterSecretGenerator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.zaxxer.hikari.HikariConfig",
"allDeclaredFields":true,
"queryAllPublicMethods":true,
"methods":[{"name":"getAllowPoolSuspension","parameterTypes":[] }, {"name":"getAutoCommit","parameterTypes":[] }, {"name":"getCatalog","parameterTypes":[] }, {"name":"getConnectionInitSql","parameterTypes":[] }, {"name":"getConnectionTestQuery","parameterTypes":[] }, {"name":"getConnectionTimeout","parameterTypes":[] }, {"name":"getDataSource","parameterTypes":[] }, {"name":"getDataSourceClassName","parameterTypes":[] }, {"name":"getDataSourceJNDI","parameterTypes":[] }, {"name":"getDataSourceProperties","parameterTypes":[] }, {"name":"getDriverClassName","parameterTypes":[] }, {"name":"getExceptionOverrideClassName","parameterTypes":[] }, {"name":"getHealthCheckProperties","parameterTypes":[] }, {"name":"getHealthCheckRegistry","parameterTypes":[] }, {"name":"getIdleTimeout","parameterTypes":[] }, {"name":"getInitializationFailTimeout","parameterTypes":[] }, {"name":"getIsolateInternalQueries","parameterTypes":[] }, {"name":"getJdbcUrl","parameterTypes":[] }, {"name":"getKeepaliveTime","parameterTypes":[] }, {"name":"getLeakDetectionThreshold","parameterTypes":[] }, {"name":"getMaxLifetime","parameterTypes":[] }, {"name":"getMaximumPoolSize","parameterTypes":[] }, {"name":"getMetricRegistry","parameterTypes":[] }, {"name":"getMetricsTrackerFactory","parameterTypes":[] }, {"name":"getMinimumIdle","parameterTypes":[] }, {"name":"getPassword","parameterTypes":[] }, {"name":"getPoolName","parameterTypes":[] }, {"name":"getReadOnly","parameterTypes":[] }, {"name":"getRegisterMbeans","parameterTypes":[] }, {"name":"getScheduledExecutor","parameterTypes":[] }, {"name":"getSchema","parameterTypes":[] }, {"name":"getThreadFactory","parameterTypes":[] }, {"name":"getTransactionIsolation","parameterTypes":[] }, {"name":"getUsername","parameterTypes":[] }, {"name":"getValidationTimeout","parameterTypes":[] }, {"name":"isAllowPoolSuspension","parameterTypes":[] }, {"name":"isAutoCommit","parameterTypes":[] }, {"name":"isIsolateInternalQueries","parameterTypes":[] }, {"name":"isReadOnly","parameterTypes":[] }, {"name":"isRegisterMbeans","parameterTypes":[] }, {"name":"setAllowPoolSuspension","parameterTypes":["boolean"] }, {"name":"setAutoCommit","parameterTypes":["boolean"] }, {"name":"setCatalog","parameterTypes":["java.lang.String"] }, {"name":"setClass","parameterTypes":["java.lang.Class"] }, {"name":"setConnectionInitSql","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTestQuery","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTimeout","parameterTypes":["long"] }, {"name":"setDataSource","parameterTypes":["javax.sql.DataSource"] }, {"name":"setDataSourceClassName","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceJNDI","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceProperties","parameterTypes":["java.util.Properties"] }, {"name":"setDriverClassName","parameterTypes":["java.lang.String"] }, {"name":"setExceptionOverrideClassName","parameterTypes":["java.lang.String"] }, {"name":"setHealthCheckProperties","parameterTypes":["java.util.Properties"] }, {"name":"setHealthCheckRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setIdleTimeout","parameterTypes":["long"] }, {"name":"setInitializationFailTimeout","parameterTypes":["long"] }, {"name":"setIsolateInternalQueries","parameterTypes":["boolean"] }, {"name":"setJdbcUrl","parameterTypes":["java.lang.String"] }, {"name":"setKeepaliveTime","parameterTypes":["long"] }, {"name":"setLeakDetectionThreshold","parameterTypes":["long"] }, {"name":"setMaxLifetime","parameterTypes":["long"] }, {"name":"setMaximumPoolSize","parameterTypes":["int"] }, {"name":"setMetricRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setMetricsTrackerFactory","parameterTypes":["com.zaxxer.hikari.metrics.MetricsTrackerFactory"] }, {"name":"setMinimumIdle","parameterTypes":["int"] }, {"name":"setPassword","parameterTypes":["java.lang.String"] }, {"name":"setPoolName","parameterTypes":["java.lang.String"] }, {"name":"setReadOnly","parameterTypes":["boolean"] }, {"name":"setRegisterMbeans","parameterTypes":["boolean"] }, {"name":"setScheduledExecutor","parameterTypes":["java.util.concurrent.ScheduledExecutorService"] }, {"name":"setSchema","parameterTypes":["java.lang.String"] }, {"name":"setThreadFactory","parameterTypes":["java.util.concurrent.ThreadFactory"] }, {"name":"setTransactionIsolation","parameterTypes":["java.lang.String"] }, {"name":"setUsername","parameterTypes":["java.lang.String"] }, {"name":"setValidationTimeout","parameterTypes":["long"] }]
},
{
"name":"com.zaxxer.hikari.pool.PoolBase",
"fields":[{"name":"catalog"}]
},
{
"name":"com.zaxxer.hikari.pool.PoolEntry",
"fields":[{"name":"state"}]
},
{
"name":"java.io.FilePermission"
},
{
"name":"java.lang.ClassValue"
},
{
"name":"java.lang.Module"
},
{
"name":"java.lang.RuntimePermission"
},
{
"name":"java.lang.String"
},
{
"name":"java.lang.StringBuilder"
},
{
"name":"java.lang.Thread",
"fields":[{"name":"threadLocalRandomProbe"}]
},
{
"name":"java.lang.invoke.CallSite"
},
{
"name":"java.net.NetPermission"
},
{
"name":"java.net.SocketPermission"
},
{
"name":"java.net.URLPermission",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
},
{
"name":"java.nio.Buffer"
},
{
"name":"java.security.AlgorithmParametersSpi"
},
{
"name":"java.security.AllPermission"
},
{
"name":"java.security.KeyStoreSpi"
},
{
"name":"java.security.SecureRandomParameters"
},
{
"name":"java.security.SecurityPermission"
},
{
"name":"java.security.interfaces.ECPrivateKey"
},
{
"name":"java.security.interfaces.ECPublicKey"
},
{
"name":"java.security.interfaces.RSAPrivateKey"
},
{
"name":"java.security.interfaces.RSAPublicKey"
},
{
"name":"java.sql.Date"
},
{
"name":"java.util.Date"
},
{
"name":"java.util.List",
"methods":[{"name":"copyOf","parameterTypes":["java.util.Collection"] }]
},
{
"name":"java.util.Optional",
"methods":[{"name":"isEmpty","parameterTypes":[] }]
},
{
"name":"java.util.PropertyPermission"
},
{
"name":"java.util.concurrent.ForkJoinTask",
"fields":[{"name":"aux"}, {"name":"status"}]
},
{
"name":"java.util.concurrent.atomic.AtomicBoolean",
"fields":[{"name":"value"}]
},
{
"name":"java.util.concurrent.atomic.AtomicReference",
"fields":[{"name":"value"}]
},
{
"name":"java.util.concurrent.atomic.Striped64",
"fields":[{"name":"base"}, {"name":"cellsBusy"}]
},
{
"name":"java.util.function.Function"
},
{
"name":"java.util.zip.DeflaterInputStream"
},
{
"name":"javax.net.ssl.SNIHostName",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"javax.net.ssl.SSLParameters",
"methods":[{"name":"setServerNames","parameterTypes":["java.util.List"] }]
},
{
"name":"javax.security.auth.x500.X500Principal",
"fields":[{"name":"thisX500Name"}],
"methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
},
{
"name":"javax.smartcardio.CardPermission"
},
{
"name":"jdk.internal.misc.Unsafe"
},
{
"name":"kotlin.Metadata",
"queryAllDeclaredMethods":true,
"methods":[{"name":"bv","parameterTypes":[] }, {"name":"d1","parameterTypes":[] }, {"name":"d2","parameterTypes":[] }, {"name":"k","parameterTypes":[] }, {"name":"mv","parameterTypes":[] }, {"name":"pn","parameterTypes":[] }, {"name":"xi","parameterTypes":[] }, {"name":"xs","parameterTypes":[] }]
},
{
"name":"kotlin.SafePublicationLazyImpl",
"fields":[{"name":"_value"}]
},
{
"name":"kotlin.jvm.internal.DefaultConstructorMarker"
},
{
"name":"kotlin.reflect.jvm.internal.ReflectionFactoryImpl",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"kotlinx.coroutines.CancellableContinuationImpl",
"fields":[{"name":"_decisionAndIndex$volatile"}, {"name":"_parentHandle$volatile"}, {"name":"_state$volatile"}]
},
{
"name":"kotlinx.coroutines.EventLoopImplBase",
"fields":[{"name":"_delayed$volatile"}, {"name":"_isCompleted$volatile"}, {"name":"_queue$volatile"}]
},
{
"name":"kotlinx.coroutines.JobSupport",
"fields":[{"name":"_parentHandle$volatile"}, {"name":"_state$volatile"}]
},
{
"name":"kotlinx.coroutines.internal.DispatchedContinuation",
"fields":[{"name":"_reusableCancellableContinuation$volatile"}]
},
{
"name":"kotlinx.coroutines.internal.LimitedDispatcher",
"fields":[{"name":"runningWorkers$volatile"}]
},
{
"name":"kotlinx.coroutines.internal.LockFreeLinkedListNode",
"fields":[{"name":"_next$volatile"}, {"name":"_prev$volatile"}, {"name":"_removedRef$volatile"}]
},
{
"name":"kotlinx.coroutines.internal.LockFreeTaskQueue",
"fields":[{"name":"_cur$volatile"}]
},
{
"name":"kotlinx.coroutines.internal.LockFreeTaskQueueCore",
"fields":[{"name":"_next$volatile"}, {"name":"_state$volatile"}]
},
{
"name":"kotlinx.coroutines.internal.ThreadSafeHeap",
"fields":[{"name":"_size$volatile"}]
},
{
"name":"kotlinx.coroutines.scheduling.CoroutineScheduler",
"fields":[{"name":"_isTerminated$volatile"}, {"name":"controlState$volatile"}, {"name":"parkedWorkersStack$volatile"}]
},
{
"name":"net.dv8tion.jda.api.hooks.ListenerAdapter",
"methods":[{"name":"onGatewayPing","parameterTypes":["net.dv8tion.jda.api.events.GatewayPingEvent"] }, {"name":"onGenericGuild","parameterTypes":["net.dv8tion.jda.api.events.guild.GenericGuildEvent"] }, {"name":"onGenericSession","parameterTypes":["net.dv8tion.jda.api.events.session.GenericSessionEvent"] }, {"name":"onGuildReady","parameterTypes":["net.dv8tion.jda.api.events.guild.GuildReadyEvent"] }, {"name":"onHttpRequest","parameterTypes":["net.dv8tion.jda.api.events.http.HttpRequestEvent"] }, {"name":"onReady","parameterTypes":["net.dv8tion.jda.api.events.session.ReadyEvent"] }, {"name":"onShutdown","parameterTypes":["net.dv8tion.jda.api.events.session.ShutdownEvent"] }, {"name":"onStatusChange","parameterTypes":["net.dv8tion.jda.api.events.StatusChangeEvent"] }]
},
{
"name":"net.dv8tion.jda.internal.utils.FallbackLogger",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.mariadb.jdbc.Configuration",
"allDeclaredFields":true
},
{
"name":"org.mariadb.jdbc.Configuration$Builder",
"allDeclaredFields":true
},
{
"name":"org.mariadb.jdbc.Driver",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.slf4j.LoggerFactory"
},
{
"name":"org.slf4j.spi.SLF4JServiceProvider"
},
{
"name":"space.mori.chzzk_bot.discord.CommandInterface"
},
{
"name":"space.mori.chzzk_bot.discord.commands.Ping",
"fields":[{"name":"INSTANCE"}]
},
{
"name":"sun.security.pkcs12.PKCS12KeyStore",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.DSA$SHA224withDSA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.DSA$SHA256withDSA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.NativePRNG",
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
},
{
"name":"sun.security.provider.SHA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.SHA2$SHA224",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.SHA2$SHA256",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.SHA5$SHA384",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.SHA5$SHA512",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.X509Factory",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.certpath.PKIXCertPathValidator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.rsa.PSSParameters",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.rsa.RSAKeyFactory$Legacy",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.rsa.RSAPSSSignature",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.rsa.RSASignature$SHA224withRSA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.rsa.RSASignature$SHA256withRSA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.ssl.SSLContextImpl$TLSContext",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.util.ObjectIdentifier"
},
{
"name":"sun.security.x509.AuthorityInfoAccessExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.AuthorityKeyIdentifierExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.BasicConstraintsExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.CRLDistributionPointsExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.CertificateExtensions"
},
{
"name":"sun.security.x509.CertificatePoliciesExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.ExtendedKeyUsageExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.IssuerAlternativeNameExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.KeyUsageExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.NetscapeCertTypeExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.PrivateKeyUsageExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.SubjectAlternativeNameExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
},
{
"name":"sun.security.x509.SubjectKeyIdentifierExtension",
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
}
]

View File

@@ -1,49 +0,0 @@
{
"resources":{
"includes":[{
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
}, {
"pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
}, {
"pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.sql.Driver\\E"
}, {
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition\\E"
}, {
"pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.util.ModuleVisibilityHelper\\E"
}, {
"pattern":"\\QMETA-INF/services/org.jetbrains.exposed.sql.DatabaseConnectionAutoRegistration\\E"
}, {
"pattern":"\\QMETA-INF/services/org.jetbrains.exposed.sql.statements.GlobalStatementInterceptor\\E"
}, {
"pattern":"\\QMETA-INF/services/org.mariadb.jdbc.plugin.Codec\\E"
}, {
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
}, {
"pattern":"\\Qlogback-test.scmo\\E"
}, {
"pattern":"\\Qlogback-test.xml\\E"
}, {
"pattern":"\\Qlogback.scmo\\E"
}, {
"pattern":"\\Qlogback.xml\\E"
}, {
"pattern":"\\Qmariadb.properties\\E"
}, {
"pattern":"\\Qspace/mori/chzzk_bot/discord/commands\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfkc.nrm\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/uprops.icu\\E"
}, {
"pattern":"java.base:\\Qsun/net/idn/uidna.spp\\E"
}]},
"bundles":[]
}

View File

@@ -1,8 +0,0 @@
{
"types":[
],
"lambdaCapturingTypes":[
],
"proxies":[
]
}