diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..ba1d693 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "commitBody": "[skip ci]", + "baseBranches": ["refactor/depUpdates"], + "prHourlyLimit": 0, + "packageRules": [ + { + "matchUpdateTypes": ["patch", "pin", "digest"], + "automerge": true + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b2d159..d7a094d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: run: "./gradlew build" - name: Upload build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Build and Deploy Artifacts diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index f2c1dbc..eb0ab25 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -26,7 +26,7 @@ jobs: run: "./gradlew build" - name: Upload build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Build and Deploy Artifacts diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index 9943979..a59dafc 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -31,7 +31,7 @@ jobs: run: "./gradlew build" - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io @@ -39,7 +39,7 @@ jobs: password: ${{ secrets.CR_PAT }} - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . platforms: linux/arm64 diff --git a/.idea/Feixiao.iml b/.idea/Feixiao.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Feixiao.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index d4b7acc..c224ad5 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4787ae5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/feixiao.iml b/.idea/modules/feixiao.iml new file mode 100644 index 0000000..4ad6139 --- /dev/null +++ b/.idea/modules/feixiao.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f3a0b17..e53343b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(libs.kx.ser) implementation(libs.kx.coroutines) implementation(libs.twitch4j) + implementation(libs.events4j) implementation(libs.kmongo) // Logging dependencies @@ -31,16 +32,20 @@ dependencies { } kordEx { - kordExVersion = "2.2.1-SNAPSHOT" - + kordExVersion = "2.3.1-SNAPSHOT" jvmTarget = 21 bot { // See https://docs.kordex.dev/data-collection.html - dataCollection(DataCollection.Standard) + dataCollection(DataCollection.None) mainClass = "dev.jansel.feixiao.AppKt" } + + i18n { + classPackage = "dev.jansel.feixiao.i18n" + translationBundle = "feixiao.strings" + } } // Automatically generate a Dockerfile. Set `generateOnBuild` to `false` if you'd prefer to manually run the diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce4033d..b20ea0c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] -kotlin = "2.0.20" # Note: Plugin versions must be updated in the settings.gradle.kts too +kotlin = "2.0.21" # Note: Plugin versions must be updated in the settings.gradle.kts too -groovy = "3.0.22" +groovy = "3.0.23" jansi = "2.4.1" kx-ser = "1.7.3" logback = "1.5.12" logback-groovy = "1.14.5" logging = "7.0.0" -twitch4j = "1.22.0" +twitch4j = "1.23.0" events4j = "0.12.2" kx-coroutines = "1.9.0" -kmongo = "4.9.0" +kmongo = "5.1.0" [libraries] groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } @@ -22,5 +22,5 @@ logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logback-groovy = { module = "io.github.virtualdogbert:logback-groovy-config", version.ref = "logback-groovy" } logging = { module = "io.github.oshai:kotlin-logging", version.ref = "logging" } twitch4j = { module = "com.github.twitch4j:twitch4j", version.ref = "twitch4j" } -events4j = { module = "com.github.philippheuer.events4j:events4j-handler-simple", version.ref = "events4j" } +events4j = { module = "com.github.philippheuer.events4j:events4j-handler-reactor", version.ref = "events4j" } kmongo = { module="org.litote.kmongo:kmongo-coroutine-serialization", version.ref = "kmongo"} diff --git a/settings.gradle.kts b/settings.gradle.kts index 3e1187a..fc0b921 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,14 +1,20 @@ pluginManagement { plugins { // Update this in libs.version.toml when you change it here. - kotlin("jvm") version "2.0.20" - kotlin("plugin.serialization") version "2.0.20" + kotlin("jvm") version "2.0.21" + kotlin("plugin.serialization") version "2.0.21" - id("com.github.jakemarsden.git-hooks") version "0.0.2" id("com.github.johnrengelman.shadow") version "8.1.1" - id("dev.kordex.gradle.docker") version "1.4.2" - id("dev.kordex.gradle.kordex") version "1.4.2" + id("dev.kordex.gradle.docker") version "1.5.8" + id("dev.kordex.gradle.kordex") version "1.5.8" + } + repositories { + gradlePluginPortal() + mavenCentral() + + maven("https://releases-repo.kordex.dev") + maven("https://snapshots-repo.kordex.dev") } } diff --git a/src/main/kotlin/dev/jansel/feixiao/App.kt b/src/main/kotlin/dev/jansel/feixiao/App.kt index a2495b1..16167c9 100644 --- a/src/main/kotlin/dev/jansel/feixiao/App.kt +++ b/src/main/kotlin/dev/jansel/feixiao/App.kt @@ -3,16 +3,20 @@ */ package dev.jansel.feixiao +import com.github.philippheuer.events4j.reactor.ReactorEventHandler import com.github.twitch4j.TwitchClient import com.github.twitch4j.TwitchClientBuilder import com.github.twitch4j.events.ChannelGoLiveEvent import dev.jansel.feixiao.database.collections.StreamerCollection import dev.jansel.feixiao.extensions.EventHooks import dev.jansel.feixiao.extensions.StreamerCommand -import dev.jansel.feixiao.utils.* +import dev.jansel.feixiao.utils.database +import dev.jansel.feixiao.utils.token +import dev.jansel.feixiao.utils.twitchcid +import dev.jansel.feixiao.utils.twitchcs import dev.kord.core.entity.channel.GuildMessageChannel import dev.kordex.core.ExtensibleBot -import dev.kordex.data.api.DataCollection +import dev.kordex.core.i18n.SupportedLocales import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -25,11 +29,13 @@ suspend fun main() { botRef = ExtensibleBot(token) { database(true) twitch(true) - dataCollectionMode = DataCollection.None extensions { add(::EventHooks) add(::StreamerCommand) } + i18n { + applicationCommandLocale(SupportedLocales.ENGLISH, SupportedLocales.GERMAN) + } } botRef!!.start() diff --git a/src/main/kotlin/dev/jansel/feixiao/database/collections/StreamerCollection.kt b/src/main/kotlin/dev/jansel/feixiao/database/collections/StreamerCollection.kt index 8df3691..651df09 100644 --- a/src/main/kotlin/dev/jansel/feixiao/database/collections/StreamerCollection.kt +++ b/src/main/kotlin/dev/jansel/feixiao/database/collections/StreamerCollection.kt @@ -20,26 +20,38 @@ class StreamerCollection : KordExKoinComponent { suspend fun getData(channelName: String): StreamerData? = collection.findOne(StreamerData::name eq channelName) - suspend fun updateData(guildId: Snowflake, channelId: Snowflake, streamerName: String, roleId: Snowflake?) { + suspend fun updateData( + guildId: Snowflake, + channelId: Snowflake, + streamerName: String, + roleId: Snowflake?, + liveMessage: String? + ) { val coll = collection.findOne(StreamerData::name eq streamerName) if (coll != null) { collection.updateOne( StreamerData::name eq streamerName, - setValue(StreamerData::servers, coll.servers + listOf(Server(guildId, channelId, roleId))) + setValue(StreamerData::servers, coll.servers + listOf(Server(guildId, channelId, roleId, liveMessage))) ) } else { collection.insertOne( - StreamerData(streamerName, listOf(Server(guildId, channelId, roleId))) + StreamerData(streamerName, listOf(Server(guildId, channelId, roleId, liveMessage))) ) } } - suspend fun removeData(guildId: Snowflake, channelId: Snowflake, streamerName: String, roleId: Snowflake?) { + suspend fun removeData( + guildId: Snowflake, + channelId: Snowflake, + streamerName: String, + roleId: Snowflake?, + liveMessage: String? + ) { val coll = collection.findOne(StreamerData::name eq streamerName) if (coll != null) { collection.updateOne( StreamerData::name eq streamerName, - setValue(StreamerData::servers, coll.servers - Server(guildId, channelId, roleId)) + setValue(StreamerData::servers, coll.servers - Server(guildId, channelId, roleId, liveMessage)) ) } } diff --git a/src/main/kotlin/dev/jansel/feixiao/database/entities/StreamerData.kt b/src/main/kotlin/dev/jansel/feixiao/database/entities/StreamerData.kt index 66de7f2..3425532 100644 --- a/src/main/kotlin/dev/jansel/feixiao/database/entities/StreamerData.kt +++ b/src/main/kotlin/dev/jansel/feixiao/database/entities/StreamerData.kt @@ -13,5 +13,6 @@ data class StreamerData( data class Server( val guildId: Snowflake, val channelId: Snowflake, - val roleId: Snowflake? + val roleId: Snowflake?, + val liveMessage: String? ) diff --git a/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt b/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt index 2a76f6d..5fb0e0d 100644 --- a/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt +++ b/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt @@ -1,11 +1,17 @@ package dev.jansel.feixiao.extensions import dev.jansel.feixiao.database.collections.StreamerCollection +import dev.jansel.feixiao.database.entities.StreamerData import dev.jansel.feixiao.logger import dev.jansel.feixiao.twitchClient +import dev.jansel.feixiao.utils.tchannelid +import dev.jansel.feixiao.utils.tserverid +import dev.kord.core.behavior.getChannelOf +import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.gateway.ReadyEvent import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.event +import org.litote.kmongo.eq class EventHooks : Extension() { override val name = "eventhooks" @@ -14,11 +20,19 @@ class EventHooks : Extension() { event { action { logger.info { "Bot is ready!" } + val onlineLog = + kord.getGuildOrNull(tserverid)?.getChannelOf(tchannelid) + onlineLog?.createMessage("Bot Online!") kord.editPresence { listening("the database") } - // check every entry in the database and enable the stream event listener + // check every entry in the database and enable the stream event listener if a server is listening to the streamer StreamerCollection().collection.find().toList().forEach { - twitchClient!!.clientHelper.enableStreamEventListener(it.name) - logger.info { "Enabled stream event listener for ${it.name}" } + if (it.servers.isNotEmpty()) { + twitchClient!!.clientHelper.enableStreamEventListener(it.name) + logger.info { "Enabled stream event listener for ${it.name}" } + } else { + logger.info { "No servers are listening to ${it.name}, deleting from the database..." } + StreamerCollection().collection.deleteMany(StreamerData::name eq it.name) + } } } } diff --git a/src/main/kotlin/dev/jansel/feixiao/extensions/StreamerCommand.kt b/src/main/kotlin/dev/jansel/feixiao/extensions/StreamerCommand.kt index a2522a4..efafbac 100644 --- a/src/main/kotlin/dev/jansel/feixiao/extensions/StreamerCommand.kt +++ b/src/main/kotlin/dev/jansel/feixiao/extensions/StreamerCommand.kt @@ -1,6 +1,7 @@ package dev.jansel.feixiao.extensions import dev.jansel.feixiao.database.collections.StreamerCollection +import dev.jansel.feixiao.i18n.Translations import dev.jansel.feixiao.twitchClient import dev.kord.common.entity.Permission import dev.kordex.core.checks.anyGuild @@ -9,6 +10,7 @@ import dev.kordex.core.commands.Arguments import dev.kordex.core.commands.application.slash.publicSubCommand import dev.kordex.core.commands.converters.impl.channel import dev.kordex.core.commands.converters.impl.optionalRole +import dev.kordex.core.commands.converters.impl.optionalString import dev.kordex.core.commands.converters.impl.string import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand @@ -17,19 +19,25 @@ class StreamerCommand : Extension() { override val name = "streaming" override suspend fun setup() { publicSlashCommand { - name = "streamer" - description = "Streamer commands" + name = Translations.Streamer.Command.name + description = Translations.Streamer.Command.description publicSubCommand(::AddStreamerArgs) { - name = "add" - description = "Add a streamer to the listener of this server" + name = Translations.Streamer.Command.Add.name + description = Translations.Streamer.Command.Add.description check { anyGuild() hasPermission(Permission.ManageGuild) } action { val streamer = arguments.streamer - StreamerCollection().updateData(guild!!.id, arguments.channel.id, streamer, arguments.role?.id) + StreamerCollection().updateData( + guild!!.id, + arguments.channel.id, + streamer, + arguments.role?.id, + arguments.message + ) twitchClient!!.clientHelper.enableStreamEventListener(streamer) respond { content = "Added streamer $streamer" @@ -38,15 +46,15 @@ class StreamerCommand : Extension() { } publicSubCommand(::RemoveStreamerArgs) { - name = "remove" - description = "Remove a streamer from the listener of this server" + name = Translations.Streamer.Command.Remove.name + description = Translations.Streamer.Command.Remove.description check { anyGuild() hasPermission(Permission.ManageGuild) } action { val streamer = arguments.streamer - StreamerCollection().removeData(guild!!.id, channel.id, streamer, null) + StreamerCollection().removeData(guild!!.id, channel.id, streamer, null, null) respond { content = "Removed streamer $streamer" } @@ -57,25 +65,29 @@ class StreamerCommand : Extension() { inner class AddStreamerArgs : Arguments() { val streamer by string { - name = "streamer" - description = "The streamer to add" + name = Translations.Streamer.Command.Arguments.Add.Streamer.name + description = Translations.Streamer.Command.Arguments.Add.Streamer.description require(true) } val channel by channel { - name = "announcechannel" - description = "Channel where the bot will send a message when the streamer goes live" + name = Translations.Streamer.Command.Arguments.Add.Channel.name + description = Translations.Streamer.Command.Arguments.Add.Channel.description require(true) } val role by optionalRole { - name = "role" - description = "Role to ping when the streamer goes live" + name = Translations.Streamer.Command.Arguments.Add.Role.name + description = Translations.Streamer.Command.Arguments.Add.Role.description + } + val message by optionalString { + name = Translations.Streamer.Command.Arguments.Add.Message.name + description = Translations.Streamer.Command.Arguments.Add.Message.description } } inner class RemoveStreamerArgs : Arguments() { val streamer by string { - name = "streamer" - description = "The streamer to remove" + name = Translations.Streamer.Command.Arguments.Remove.name + description = Translations.Streamer.Command.Arguments.Remove.description require(true) } } diff --git a/src/main/resources/translations/feixiao/strings.properties b/src/main/resources/translations/feixiao/strings.properties new file mode 100644 index 0000000..95535d7 --- /dev/null +++ b/src/main/resources/translations/feixiao/strings.properties @@ -0,0 +1,20 @@ +# Streamer Command + +streamer.command.name=streamer +streamer.command.description=A bundle of Streamer commands +streamer.command.add.name=add +streamer.command.add.description=Add a new streamer to the listener +streamer.command.remove.name=remove +streamer.command.remove.description=Remove a streamer from the listener +streamer.command.arguments.add.streamer.name=streamer +streamer.command.arguments.add.streamer.description=The streamer to add +streamer.command.arguments.add.channel.name=channel +streamer.command.arguments.add.channel.description=The channel to add the streamer to +streamer.command.arguments.add.role.name=role +streamer.command.arguments.add.role.description=The role to assign to the streamer +streamer.command.arguments.add.message.name=message +streamer.command.arguments.add.message.description=Custom Announce message. Placeholders (in curly braces): url, name, title, category, role (if set) +streamer.command.arguments.remove.name=streamer +streamer.command.arguments.remove.description=The streamer to remove + +# more to come...