diff --git a/build.gradle.kts b/build.gradle.kts index 6e27add..f91fbff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,9 +66,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") diff --git a/src/main/kotlin/space/mori/chzzk_bot/Connector.kt b/src/main/kotlin/space/mori/chzzk_bot/Connector.kt index b5b9f0e..e4a711e 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/Connector.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/Connector.kt @@ -7,13 +7,12 @@ 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 +23,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()) } } } \ 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 1aa0223..9ac533a 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/Main.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/Main.kt @@ -9,6 +9,7 @@ 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 logger: Logger = LoggerFactory.getLogger("main") @@ -26,7 +27,7 @@ fun main(args: Array) { if(dotenv.get("RUN_AGENT", "false").toBoolean()) { runBlocking { delay(TimeUnit.MINUTES.toMillis(1)) - discord.disable() + exitProcess(0) } } diff --git a/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt b/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt index cbb8925..bc12fe1 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/chzzk/ChzzkHandler.kt @@ -3,6 +3,7 @@ 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.models.User import space.mori.chzzk_bot.services.UserService import xyz.r2turntrue.chzzk4j.chat.ChatEventListener import xyz.r2turntrue.chzzk4j.chat.ChatMessage @@ -14,13 +15,13 @@ 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 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,7 +32,7 @@ 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 @@ -39,7 +40,9 @@ object ChzzkHandler { } } -class UserHandler(val channel: ChzzkChannel, private val logger: Logger) { +class UserHandler( + val channel: ChzzkChannel, private val logger: Logger, private val user: User +) { private lateinit var messageHandler: MessageHandler private var listener: ChzzkChat = chzzk.chat(channel.channelId) @@ -56,7 +59,7 @@ class UserHandler(val channel: ChzzkChannel, private val logger: Logger) { } override fun onChat(msg: ChatMessage) { - messageHandler.handle(msg) + messageHandler.handle(msg, user) } override fun onConnectionClosed(code: Int, reason: String?, remote: Boolean, tryingToReconnect: Boolean) { diff --git a/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt b/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt index 0f552cb..fe17cc1 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/chzzk/MessageHandler.kt @@ -1,7 +1,9 @@ package space.mori.chzzk_bot.chzzk import org.slf4j.Logger +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 @@ -12,7 +14,12 @@ class MessageHandler( private val logger: Logger, private val listener: ChzzkChat ) { - private val commands = mutableMapOf Unit>() + private val commands = mutableMapOf Unit>() + + private val counterPattern = Regex("]+)>") + private val personalCounterPattern = Regex("]+)>") + private val dailyCounterPattern = Regex("]+)>") + private val namePattern = Regex("") init { reloadCommand() @@ -24,16 +31,59 @@ class MessageHandler( 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) + 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.userId, msg.profile?.nickname ?: "") + listener.sendChat(result) } } } - internal fun handle(msg: ChatMessage) { + 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, user: User, userId: String, 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, userId, 1, user).toString() + } + + result = dailyCounterPattern.replace(result) { + val name = it.groupValues[1] + val dailyCounter = CounterService.getDailyCounterValue(name, userId, user) + + return@replace if(dailyCounter.second) + CounterService.updateDailyCounterValue(name, userId, 1, user).first.toString() + else { + isFail = true + dailyCounter.first.toString() + } + } + + if(isFail) { + result = chat.second + result = dailyCounterPattern.replace(result) { + val name = it.groupValues[1] + val dailyCounter = CounterService.getDailyCounterValue(name, userId, user) + + dailyCounter.first.toString() + } + } + + result = namePattern.replace(result, userName) + + return result } } \ No newline at end of file diff --git a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/AddCommand.kt b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/AddCommand.kt index c25ffcd..22409ef 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/AddCommand.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/AddCommand.kt @@ -20,10 +20,12 @@ object AddCommand : CommandInterface { 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 +37,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()) 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 index 18aa558..4cdd4d0 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Register.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/Register.kt @@ -40,8 +40,8 @@ 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) + ChzzkHandler.addUser(chzzkChannel, user) event.hook.sendMessage("등록이 완료되었습니다. `${chzzkChannel.channelId}` - `${chzzkChannel.channelName}`") } catch(e: Exception) { event.hook.sendMessage("에러가 발생했습니다.").queue() diff --git a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/UpdateCommand.kt b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/UpdateCommand.kt index da30620..fb92140 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/discord/commands/UpdateCommand.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/discord/commands/UpdateCommand.kt @@ -21,10 +21,12 @@ object UpdateCommand : CommandInterface { 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 +41,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() diff --git a/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt b/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt index fde8fa0..2cd8ce6 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/models/Command.kt @@ -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) : IntEntity(id) { @@ -18,4 +19,5 @@ class Command(id: EntityID) : IntEntity(id) { var user by User referencedOn Commands.user var command by Commands.command var content by Commands.content + var failContent by Commands.failContent } \ No newline at end of file diff --git a/src/main/kotlin/space/mori/chzzk_bot/models/Counter.kt b/src/main/kotlin/space/mori/chzzk_bot/models/Counter.kt new file mode 100644 index 0000000..4fbc08b --- /dev/null +++ b/src/main/kotlin/space/mori/chzzk_bot/models/Counter.kt @@ -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) : IntEntity(id) { + companion object : IntEntityClass(Counters) + + var name by Counters.name + var value by Counters.value + var user by User referencedOn Counters.user +} \ No newline at end of file diff --git a/src/main/kotlin/space/mori/chzzk_bot/models/DailyCounters.kt b/src/main/kotlin/space/mori/chzzk_bot/models/DailyCounters.kt new file mode 100644 index 0000000..04f051b --- /dev/null +++ b/src/main/kotlin/space/mori/chzzk_bot/models/DailyCounters.kt @@ -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) : IntEntity(id) { + companion object : IntEntityClass(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 +} \ No newline at end of file diff --git a/src/main/kotlin/space/mori/chzzk_bot/models/PersonalCounter.kt b/src/main/kotlin/space/mori/chzzk_bot/models/PersonalCounter.kt new file mode 100644 index 0000000..826e2b4 --- /dev/null +++ b/src/main/kotlin/space/mori/chzzk_bot/models/PersonalCounter.kt @@ -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) : IntEntity(id) { + companion object : IntEntityClass(PersonalCounters) + + var name by PersonalCounters.name + var userId by PersonalCounters.userId + var value by PersonalCounters.value + var user by User referencedOn PersonalCounters.user +} \ 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 index 444514f..9cc7f19 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/services/CommandService.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/services/CommandService.kt @@ -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 { return transaction { - return@transaction Command.find(Commands.user eq user.id) + Command.find(Commands.user eq user.id) .toList() } } diff --git a/src/main/kotlin/space/mori/chzzk_bot/services/CounterService.kt b/src/main/kotlin/space/mori/chzzk_bot/services/CounterService.kt new file mode 100644 index 0000000..d9a56eb --- /dev/null +++ b/src/main/kotlin/space/mori/chzzk_bot/services/CounterService.kt @@ -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 { + 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 { + 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) + } + } + } +} \ 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 e062523..7353fab 100644 --- a/src/main/kotlin/space/mori/chzzk_bot/services/UserService.kt +++ b/src/main/kotlin/space/mori/chzzk_bot/services/UserService.kt @@ -8,7 +8,7 @@ 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 +18,7 @@ object UserService { fun getUser(id: Int): User? { return transaction { - return@transaction User.findById(id) + User.findById(id) } } @@ -26,7 +26,7 @@ object UserService { return transaction { val users = User.find(Users.discord eq discordID) - return@transaction users.firstOrNull() + users.firstOrNull() } } @@ -34,13 +34,13 @@ object UserService { return transaction { val users = User.find(Users.token eq chzzkID) - return@transaction users.firstOrNull() + users.firstOrNull() } } fun getAllUsers(): List { return transaction { - return@transaction User.all().toList() + User.all().toList() } } } \ No newline at end of file diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json index 25b71f1..45e0f13 100644 --- a/src/main/resources/META-INF/native-image/reflect-config.json +++ b/src/main/resources/META-INF/native-image/reflect-config.json @@ -380,6 +380,10 @@ "name":"kotlinx.coroutines.CancellableContinuationImpl", "fields":[{"name":"_decisionAndIndex$volatile"}, {"name":"_parentHandle$volatile"}, {"name":"_state$volatile"}] }, +{ + "name":"kotlinx.coroutines.CompletedExceptionally", + "fields":[{"name":"_handled$volatile"}] +}, { "name":"kotlinx.coroutines.EventLoopImplBase", "fields":[{"name":"_delayed$volatile"}, {"name":"_isCompleted$volatile"}, {"name":"_queue$volatile"}] @@ -388,6 +392,10 @@ "name":"kotlinx.coroutines.JobSupport", "fields":[{"name":"_parentHandle$volatile"}, {"name":"_state$volatile"}] }, +{ + "name":"kotlinx.coroutines.JobSupport$Finishing", + "fields":[{"name":"_exceptionsHolder$volatile"}, {"name":"_isCompleting$volatile"}, {"name":"_rootCause$volatile"}] +}, { "name":"kotlinx.coroutines.internal.DispatchedContinuation", "fields":[{"name":"_reusableCancellableContinuation$volatile"}]