From 4b7fe25b21ca3bd73c785e4cdd3166492fa1025d Mon Sep 17 00:00:00 2001
From: dalbodeule <11470513+dalbodeule@users.noreply.github.com>
Date: Wed, 12 Jun 2024 22:19:19 +0900
Subject: [PATCH] add chzzk chat handler, command handler
---
.idea/sqldialects.xml | 3 +
build.gradle.kts | 1 +
.../kotlin/space/mori/chzzk_bot/Connector.kt | 7 +-
src/main/kotlin/space/mori/chzzk_bot/Main.kt | 14 +++-
.../mori/chzzk_bot/chzzk/ChzzkHandler.kt | 69 +++++++++++++++++++
.../space/mori/chzzk_bot/chzzk/Connector.kt | 9 ++-
.../mori/chzzk_bot/chzzk/MessageHandler.kt | 34 +++++++++
.../space/mori/chzzk_bot/discord/Discord.kt | 4 +-
.../mori/chzzk_bot/discord/commands/Ping.kt | 4 +-
.../chzzk_bot/discord/commands/Register.kt | 51 ++++++++++++++
.../space/mori/chzzk_bot/models/Command.kt | 21 ++++++
.../mori/chzzk_bot/services/CommandService.kt | 32 +++++++++
.../mori/chzzk_bot/services/UserService.kt | 39 ++++++++---
src/main/resources/logback.xml | 27 ++++++++
14 files changed, 299 insertions(+), 16 deletions(-)
create mode 100644 src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt
create mode 100644 src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt
create mode 100644 src/main/kotlin/space/mori/chzzk_bot/discord/commands/Register.kt
create mode 100644 src/main/kotlin/space/mori/chzzk_bot/models/Command.kt
create mode 100644 src/main/kotlin/space/mori/chzzk_bot/services/CommandService.kt
create mode 100644 src/main/resources/logback.xml
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
index 63772a3..257aa19 100644
--- a/.idea/sqldialects.xml
+++ b/.idea/sqldialects.xml
@@ -3,4 +3,7 @@
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 90c8074..6e27add 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -58,6 +58,7 @@ dependencies {
}
// https://mvnrepository.com/artifact/io.github.R2turnTrue/chzzk4j
implementation("io.github.R2turnTrue:chzzk4j:0.0.7")
+
implementation("ch.qos.logback:logback-classic:1.4.14")
// https://mvnrepository.com/artifact/org.jetbrains.exposed/exposed-core
diff --git a/src/main/kotlin/space/mori/chzzk_bot/Connector.kt b/src/main/kotlin/space/mori/chzzk_bot/Connector.kt
index 970b3e0..b5b9f0e 100644
--- a/src/main/kotlin/space/mori/chzzk_bot/Connector.kt
+++ b/src/main/kotlin/space/mori/chzzk_bot/Connector.kt
@@ -6,6 +6,8 @@ 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
object Connector {
@@ -22,9 +24,12 @@ object Connector {
init {
Database.connect(dataSource)
+ val tables = listOf(Users, Commands)
transaction {
- SchemaUtils.createMissingTablesAndColumns(Users)
+ tables.forEach { table ->
+ SchemaUtils.createMissingTablesAndColumns(table)
+ }
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/Main.kt b/src/main/kotlin/space/mori/chzzk_bot/Main.kt
index da597a0..1aa0223 100644
--- a/src/main/kotlin/space/mori/chzzk_bot/Main.kt
+++ b/src/main/kotlin/space/mori/chzzk_bot/Main.kt
@@ -5,6 +5,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.slf4j.Logger
import org.slf4j.LoggerFactory
+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
@@ -15,10 +16,12 @@ val logger: Logger = LoggerFactory.getLogger("main")
fun main(args: Array) {
val discord = Discord()
- Connector
- ChzzkConnector
+ val connector = Connector
+ val chzzkConnector = ChzzkConnector
+ val chzzkHandler = ChzzkHandler
discord.enable()
+ chzzkHandler.enable()
if(dotenv.get("RUN_AGENT", "false").toBoolean()) {
runBlocking {
@@ -26,4 +29,11 @@ fun main(args: Array) {
discord.disable()
}
}
+
+ Runtime.getRuntime().addShutdownHook(Thread {
+ logger.info("Shutting down...")
+ chzzkHandler.disable()
+ discord.disable()
+ connector.dataSource.close()
+ })
}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt b/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt
new file mode 100644
index 0000000..783cb2c
--- /dev/null
+++ b/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt
@@ -0,0 +1,69 @@
+package space.mori.chzzk_bot.chzzk
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import space.mori.chzzk_bot.chzzk.Connector.chzzk
+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
+
+object ChzzkHandler {
+ private val handlers = mutableListOf()
+ private val logger = LoggerFactory.getLogger(this::class.java)
+
+ internal fun addUser(chzzkChannel: ChzzkChannel) {
+ handlers.add(UserHandler(chzzkChannel, logger))
+ }
+
+ internal fun enable() {
+ UserService.getAllUsers().map {
+ chzzk.getChannel(it.token)?.let { token -> addUser(token)}
+ }
+ }
+
+ internal fun disable() {
+ handlers.forEach { handler ->
+ handler.disable()
+ }
+ }
+}
+
+class UserHandler(private val channel: ChzzkChannel, private val logger: Logger) {
+ private lateinit var messageHandler: MessageHandler
+
+ private var listener: ChzzkChat = chzzk.chat(channel.channelId)
+ .withAutoReconnect(true)
+ .withChatListener(object : ChatEventListener {
+ override fun onConnect(chat: ChzzkChat, isReconnecting: Boolean) {
+ logger.info("ChzzkChat connected. ${channel.channelName} - ${channel.channelId} / reconnected: $isReconnecting")
+ messageHandler = MessageHandler(channel, logger, chat)
+ }
+
+ override fun onError(ex: Exception) {
+ logger.info("ChzzkChat error. ${channel.channelName} - ${channel.channelId}")
+ logger.debug(ex.stackTraceToString())
+ }
+
+ override fun onChat(msg: ChatMessage) {
+ messageHandler.handle(msg)
+ }
+
+ override fun onConnectionClosed(code: Int, reason: String?, remote: Boolean, tryingToReconnect: Boolean) {
+ logger.info("ChzzkChat closed. ${channel.channelName} - ${channel.channelId}")
+ logger.info("Reason: $reason / $tryingToReconnect")
+ }
+ })
+ .build()
+
+ init {
+ logger.info("ChzzkChat connecting... ${channel.channelName} - ${channel.channelId}")
+ listener.connectBlocking()
+ }
+
+ internal fun disable() {
+ listener.closeBlocking()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/chzzk/Connector.kt b/src/main/kotlin/space/mori/chzzk_bot/chzzk/Connector.kt
index 84b54a2..d7a8633 100644
--- a/src/main/kotlin/space/mori/chzzk_bot/chzzk/Connector.kt
+++ b/src/main/kotlin/space/mori/chzzk_bot/chzzk/Connector.kt
@@ -1,14 +1,21 @@
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
object Connector {
private val dotenv = dotenv()
val chzzk: Chzzk = ChzzkBuilder()
.withAuthorization(dotenv["NID_AUT"], dotenv["NID_SES"])
.build()
+ private val logger = LoggerFactory.getLogger(this::class.java)
- fun getChannel(channelId: String) = chzzk.getChannel(channelId)
+ fun getChannel(channelId: String): ChzzkChannel? = chzzk.getChannel(channelId)
+
+ init {
+ logger.info("chzzk logged: ${chzzk.isLoggedIn} / ${chzzk.loggedUser?.nickname ?: "----"}")
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt b/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt
new file mode 100644
index 0000000..bb4261c
--- /dev/null
+++ b/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt
@@ -0,0 +1,34 @@
+package space.mori.chzzk_bot.chzzk
+
+import org.slf4j.Logger
+import space.mori.chzzk_bot.services.CommandService
+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
+class MessageHandler(
+ private val channel: ChzzkChannel,
+ private val logger: Logger,
+ private val listener: ChzzkChat
+) {
+ private val commands = mutableMapOf Unit>()
+
+ init {
+ val user = UserService.getUser(channel.channelId)
+ ?: throw RuntimeException("User not found. it's bug? ${channel.channelName} - ${channel.channelId}")
+ val commands = CommandService.getCommands(user)
+
+ commands.map {
+ this.commands.put(it.command.lowercase()) {
+ logger.debug("${channel.channelName} - ${it.command} - ${it.content}")
+ listener.sendChat(it.content)
+ }
+ }
+ }
+
+ internal fun handle(msg: ChatMessage) {
+ val commandKey = msg.content.split(' ')[0]
+
+ commands[commandKey.lowercase()]?.let { it() }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/discord/Discord.kt b/src/main/kotlin/space/mori/chzzk_bot/discord/Discord.kt
index 5780f59..4964876 100644
--- a/src/main/kotlin/space/mori/chzzk_bot/discord/Discord.kt
+++ b/src/main/kotlin/space/mori/chzzk_bot/discord/Discord.kt
@@ -6,13 +6,13 @@ 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 space.mori.chzzk_bot.logger
-import kotlin.concurrent.thread
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()
diff --git a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Ping.kt b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Ping.kt
index 81b7c43..9b217e6 100644
--- a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Ping.kt
+++ b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Ping.kt
@@ -3,13 +3,15 @@ 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.build.Commands
+import org.slf4j.LoggerFactory
import space.mori.chzzk_bot.discord.Command
import space.mori.chzzk_bot.discord.CommandInterface
@Command()
object Ping: CommandInterface {
- override val command = Commands.slash("ping", "봇이 살아있을까요?")
+ private val logger = LoggerFactory.getLogger(this::class.java)
override val name = "ping"
+ override val command = Commands.slash(name, "봇이 살아있을까요?")
override fun run(event: SlashCommandInteractionEvent, bot: JDA) {
event.hook.sendMessage("${event.user.asMention} Pong!").queue()
diff --git a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Register.kt b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Register.kt
new file mode 100644
index 0000000..8e23a86
--- /dev/null
+++ b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Register.kt
@@ -0,0 +1,51 @@
+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.Command
+import space.mori.chzzk_bot.discord.CommandInterface
+import space.mori.chzzk_bot.services.UserService
+
+@Command
+object Register: CommandInterface {
+ private val logger = LoggerFactory.getLogger(this::class.java)
+ override val name = "register"
+ override val command = Commands.slash(name, "치지직 계정을 등록합니다.")
+ .addOptions(
+ OptionData(
+ OptionType.STRING,
+ "chzzk_id",
+ "36da10b7c35800f298e9c565a396bafd 형식으로 입력해주세요.",
+ true
+ )
+ )
+
+ override fun run(event: SlashCommandInteractionEvent, bot: JDA) {
+ val chzzkID = event.getOption("chzzk_id")?.asString
+ if(chzzkID == null) {
+ event.hook.sendMessage("치지직 계정은 필수 입력입니다.").queue()
+ return
+ }
+
+ val chzzkChannel = Connector.getChannel(chzzkID)
+ if (chzzkChannel == null) {
+ event.hook.sendMessage("치지직 계정을 찾을 수 없습니다.").queue()
+ return
+ }
+
+ try {
+ UserService.saveUser(chzzkChannel.channelName, chzzkChannel.channelId, event.user.idLong)
+ ChzzkHandler.addUser(chzzkChannel)
+ event.hook.sendMessage("등록이 완료되었습니다. ${chzzkChannel.channelId} - ${chzzkChannel.channelName}")
+ } catch(e: Exception) {
+ event.hook.sendMessage("에러가 발생했습니다.").queue()
+ logger.debug(e.stackTraceToString())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt b/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt
new file mode 100644
index 0000000..fde8fa0
--- /dev/null
+++ b/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt
@@ -0,0 +1,21 @@
+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.ReferenceOption
+
+object Commands: IntIdTable("commands") {
+ val user = reference("user", Users, onDelete = ReferenceOption.CASCADE)
+ val command = varchar("command", 255)
+ val content = text("content")
+}
+
+class Command(id: EntityID) : IntEntity(id) {
+ companion object : IntEntityClass(Commands)
+
+ var user by User referencedOn Commands.user
+ var command by Commands.command
+ var content by Commands.content
+}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/services/CommandService.kt b/src/main/kotlin/space/mori/chzzk_bot/services/CommandService.kt
new file mode 100644
index 0000000..0e3b4ea
--- /dev/null
+++ b/src/main/kotlin/space/mori/chzzk_bot/services/CommandService.kt
@@ -0,0 +1,32 @@
+package space.mori.chzzk_bot.services
+
+import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
+import org.jetbrains.exposed.sql.transactions.transaction
+import space.mori.chzzk_bot.models.Command
+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 {
+ return transaction {
+ return@transaction Command.new {
+ this.user = user
+ this.command = command
+ this.content = content
+ }
+ }
+ }
+
+ fun getCommand(id: Int): Command? {
+ return transaction {
+ return@transaction Command.findById(id)
+ }
+ }
+
+ fun getCommands(user: User): List {
+ return transaction {
+ return@transaction Command.find(Commands.user eq user.id)
+ .toList()
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/space/mori/chzzk_bot/services/UserService.kt b/src/main/kotlin/space/mori/chzzk_bot/services/UserService.kt
index ac92abb..e062523 100644
--- a/src/main/kotlin/space/mori/chzzk_bot/services/UserService.kt
+++ b/src/main/kotlin/space/mori/chzzk_bot/services/UserService.kt
@@ -1,25 +1,46 @@
package space.mori.chzzk_bot.services
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
+import org.jetbrains.exposed.sql.transactions.transaction
import space.mori.chzzk_bot.models.User
import space.mori.chzzk_bot.models.Users
-class UserService {
- fun saveUser(user: User) {
- User.new {
- username = user.username
- token = user.token
- discord = user.discord
+object UserService {
+ fun saveUser(username: String, token: String, discordID: Long): User {
+ return transaction {
+ return@transaction User.new {
+ this.username = username
+ this.token = token
+ this.discord = discordID
+ }
}
}
fun getUser(id: Int): User? {
- return User.findById(id)
+ return transaction {
+ return@transaction User.findById(id)
+ }
}
fun getUser(discordID: Long): User? {
- val users = User.find(Users.discord eq discordID)
+ return transaction {
+ val users = User.find(Users.discord eq discordID)
- return users.firstOrNull()
+ return@transaction users.firstOrNull()
+ }
+ }
+
+ fun getUser(chzzkID: String): User? {
+ return transaction {
+ val users = User.find(Users.token eq chzzkID)
+
+ return@transaction users.firstOrNull()
+ }
+ }
+
+ fun getAllUsers(): List {
+ return transaction {
+ return@transaction User.all().toList()
+ }
}
}
\ No newline at end of file
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..366d123
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file