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...