diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index be87cbd..2ebd1de 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -1,75 +1,81 @@ name: Build & Publish on: + workflow_dispatch: push: branches: - root - schedule: - - cron: "0 6 * * *" jobs: build: runs-on: ubuntu-latest + steps: - - name: Disable SSL verify (Temporary Fix) - run: git config --global http.sslVerify false - - - uses: actions/checkout@v4 + - name: Checkout code + uses: https://github.com/actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: https://github.com/docker/setup-qemu-action@v3 + with: + cache-image: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: https://github.com/docker/setup-buildx-action@v3 - name: Set up Java - uses: actions/setup-java@v4 + uses: https://github.com/actions/setup-java@v4 with: java-version: 21 distribution: temurin - - name: Get current date - id: date - run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + - name: "Restore Cache" + id: restore-cache + uses: https://data.forgejo.org/actions/cache/restore@v4 - - name: Gradle Cache - uses: actions/cache/restore@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-Feixiao - restore-keys: | - ${{ runner.os }}-gradle- + ~/.m2/repository + key: gradle-store - name: Grant execute permission for gradlew run: chmod +x gradlew + with: + dependency-graph: generate-and-submit + cache-disabled: true + + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + - name: Gradle (Build) run: "./gradlew build" - - - name: Gradle Cache - uses: actions/cache/save@v4 + + - name: Login to DigitalOcean Registry + uses: https://github.com/docker/login-action@v3 + + with: + registry: registry.digitalocean.com + username: ${{ secrets.DOMAIL }} + password: ${{ secrets.DOKEY }} + + - name: Build and push + uses: https://github.com/docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: registry.digitalocean.com/jansel/feixiao:latest,registry.digitalocean.com/jansel/feixiao:${{ steps.date.outputs.date }} + + - name: "Save Cache" + uses: https://data.forgejo.org/actions/cache/save@v4 + with: path: | ~/.gradle/caches ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-Feixiao - - - name: Login to Gitea Registry - uses: docker/login-action@v3 - - with: - registry: pi5:8125 - username: ${{ github.repository_owner }} - password: ${{ secrets.PW }} - - - name: Build the Docker image - run: docker build -t pi5:8125/jansel/feixiao:latest -t pi5:8125/jansel/feixiao:${{ steps.date.outputs.date }} . - - - name: Push the Docker image related to this workflow - run: docker push pi5:8125/jansel/feixiao:${{ steps.date.outputs.date }} - - - name: Push the latest Docker image - run: docker push pi5:8125/jansel/feixiao:latest \ No newline at end of file + ~/.m2/repository + key: ${{ steps.restore-cache.outputs.cache-primary-key }} diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index c224ad5..1e16934 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 802ad4c..1dff957 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,17 +2,17 @@ import dev.kordex.gradle.plugins.docker.file.* import dev.kordex.gradle.plugins.kordex.DataCollection plugins { - kotlin("jvm") - kotlin("plugin.serialization") + distribution - id("com.github.johnrengelman.shadow") + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) - id("dev.kordex.gradle.docker") - id("dev.kordex.gradle.kordex") + alias(libs.plugins.kordex.plugin) + alias(libs.plugins.kordex.docker) } group = "dev.jansel" -version = "1.0-SNAPSHOT" +version = "1.2-SNAPSHOT" dependencies { @@ -31,9 +31,26 @@ dependencies { implementation(libs.logging) } +// Configure distributions plugin +distributions { + main { + distributionBaseName = project.name + + contents { + // Copy the LICENSE file into the distribution + from("LICENSE") + + // Exclude src/main/dist/README.md + exclude("README.md") + } + } +} + + kordEx { kordExVersion = "2.3.1-SNAPSHOT" jvmTarget = 21 + ignoreIncompatibleKotlinVersion = true bot { // See https://docs.kordex.dev/data-collection.html @@ -58,37 +75,51 @@ docker { // Each function (aside from comment/emptyLine) corresponds to a Dockerfile instruction. // See: https://docs.docker.com/reference/dockerfile/ - from("azul/zulu-openjdk-alpine:21-jre-headless-latest") + from("openjdk:21-jdk-slim") emptyLine() + comment("Create required directories") runShell("mkdir -p /bot/plugins") runShell("mkdir -p /bot/data") - - emptyLine() - - copy("build/libs/$name-*-all.jar", "/bot/bot.jar") + runShell("mkdir -p /dist/out") emptyLine() // Add volumes for locations that you need to persist. This is important! + comment("Declare required volumes") volume("/bot/data") // Storage for data files volume("/bot/plugins") // Plugin ZIP/JAR location emptyLine() + comment("Copy the distribution files into the container") + copy("build/distributions/${project.name}-${project.version}.tar", "/dist") + + emptyLine() + + comment("Extract the distribution files, and prepare them for use") + runShell("tar -xf /dist/${project.name}-${project.version}.tar -C /dist/out") + + if (file("src/main/dist/plugins").isDirectory) { + runShell("mv /dist/out/${project.name}-${project.version}/plugins/* /bot/plugins") + } + + runShell("chmod +x /dist/out/${project.name}-${project.version}/bin/$name") + + emptyLine() + + comment("Clean up unnecessary files") + runShell("rm /dist/${project.name}-${project.version}.tar") + + emptyLine() + + comment("Set the correct working directory") workdir("/bot") emptyLine() - entryPointExec( - "java", "-Xms2G", "-Xmx2G", - "-jar", "/bot/bot.jar" - ) + comment("Run the distribution start script") + entryPointExec("/dist/out/${project.name}-${project.version}/bin/$name") } } - -tasks.wrapper { - gradleVersion = "8.11.1" - distributionType = Wrapper.DistributionType.BIN -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51def88..4766e0d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,17 @@ [versions] -kotlin = "2.0.21" # Note: Plugin versions must be updated in the settings.gradle.kts too +kotlin = "2.1.21" # Note: Plugin versions must be updated in the settings.gradle.kts too -groovy = "3.0.23" -jansi = "2.4.1" -kx-ser = "1.7.3" -logback = "1.5.12" +groovy = "3.0.25" +jansi = "2.4.2" +kx-ser = "1.8.1" +logback = "1.5.18" logback-groovy = "1.14.5" -logging = "7.0.3" -twitch4j = "1.23.0" +logging = "7.0.7" +twitch4j = "1.25.0" events4j = "0.12.2" -kx-coroutines = "1.9.0" -kmongo = "5.1.0" +kx-coroutines = "1.10.2" +kmongo = "5.2.1" +kordex-gradle = "1.7.1" [libraries] groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } @@ -24,3 +25,11 @@ 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-reactor", version.ref = "events4j" } kmongo = { module="org.litote.kmongo:kmongo-coroutine-serialization", version.ref = "kmongo" } + + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } + +kordex-docker = { id = "dev.kordex.gradle.docker", version.ref = "kordex-gradle" } +kordex-plugin = { id = "dev.kordex.gradle.kordex", version.ref = "kordex-gradle" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c8..ff23a68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/docs/privacy-policy.md b/privacy-policy.md similarity index 100% rename from docs/privacy-policy.md rename to privacy-policy.md diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..e655623 --- /dev/null +++ b/renovate.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch" + ] + } + ], + "prHourlyLimit": 0 +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 84e3ff8..ebc20a7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,14 +1,4 @@ pluginManagement { - plugins { - // Update this in libs.version.toml when you change it here. - kotlin("jvm") version "2.0.21" - kotlin("plugin.serialization") version "2.0.21" - - id("com.github.johnrengelman.shadow") version "8.1.1" - - id("dev.kordex.gradle.docker") version "1.6.0" - id("dev.kordex.gradle.kordex") version "1.6.0" - } repositories { gradlePluginPortal() mavenCentral() diff --git a/src/main/kotlin/dev/jansel/feixiao/database/Migrator.kt b/src/main/kotlin/dev/jansel/feixiao/database/Migrator.kt index 2043779..5e539eb 100644 --- a/src/main/kotlin/dev/jansel/feixiao/database/Migrator.kt +++ b/src/main/kotlin/dev/jansel/feixiao/database/Migrator.kt @@ -1,8 +1,14 @@ package dev.jansel.feixiao.database +import com.github.philippheuer.events4j.reactor.ReactorEventHandler +import com.github.twitch4j.TwitchClientBuilder import dev.jansel.feixiao.database.collections.MetaCollection import dev.jansel.feixiao.database.entities.MetaData import dev.jansel.feixiao.database.migrations.v1 +import dev.jansel.feixiao.database.migrations.v2 +import dev.jansel.feixiao.twitchClient +import dev.jansel.feixiao.utils.twitchcid +import dev.jansel.feixiao.utils.twitchcs import dev.kordex.core.koin.KordExKoinComponent import io.github.oshai.kotlinlogging.KotlinLogging import org.koin.core.component.inject @@ -15,6 +21,13 @@ object Migrator : KordExKoinComponent { suspend fun migrate() { logger.info { "Starting main database migration" } + logger.info { "Initializing Twitch client just in case" } + twitchClient = TwitchClientBuilder.builder() + .withEnableHelix(true) + .withDefaultEventHandler(ReactorEventHandler::class.java) + .withClientId(twitchcid) + .withClientSecret(twitchcs) + .build() var meta = mainMetaCollection.get() @@ -35,6 +48,7 @@ object Migrator : KordExKoinComponent { try { when (nextVersion) { 1 -> ::v1 + 2 -> ::v2 else -> break }(db.mongo) 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 1954f78..3471e94 100644 --- a/src/main/kotlin/dev/jansel/feixiao/database/collections/StreamerCollection.kt +++ b/src/main/kotlin/dev/jansel/feixiao/database/collections/StreamerCollection.kt @@ -3,6 +3,7 @@ package dev.jansel.feixiao.database.collections import dev.jansel.feixiao.database.Database import dev.jansel.feixiao.database.entities.Server import dev.jansel.feixiao.database.entities.StreamerData +import dev.jansel.feixiao.utils.getTwitchIdByName import dev.kord.common.entity.Snowflake import dev.kordex.core.koin.KordExKoinComponent import org.koin.core.component.inject @@ -35,7 +36,7 @@ class StreamerCollection : KordExKoinComponent { ) } else { collection.insertOne( - StreamerData(streamerName, listOf(Server(guildId, channelId, roleId, liveMessage))) + StreamerData(streamerName, getTwitchIdByName(streamerName), listOf(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 3425532..9e79d75 100644 --- a/src/main/kotlin/dev/jansel/feixiao/database/entities/StreamerData.kt +++ b/src/main/kotlin/dev/jansel/feixiao/database/entities/StreamerData.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class StreamerData( val name: String, + val id: String?, val servers: List ) diff --git a/src/main/kotlin/dev/jansel/feixiao/database/migrations/v2.kt b/src/main/kotlin/dev/jansel/feixiao/database/migrations/v2.kt new file mode 100644 index 0000000..df0f783 --- /dev/null +++ b/src/main/kotlin/dev/jansel/feixiao/database/migrations/v2.kt @@ -0,0 +1,16 @@ +package dev.jansel.feixiao.database.migrations + +import dev.jansel.feixiao.database.entities.StreamerData +import dev.jansel.feixiao.utils.getTwitchIdByName +import org.litote.kmongo.coroutine.CoroutineDatabase +import org.litote.kmongo.eq +import org.litote.kmongo.setValue + +suspend fun v2(db: CoroutineDatabase) { + db.getCollection("streamerData").findOne(StreamerData::id eq null)?.let { + db.getCollection("streamerData").updateOne( + StreamerData::name eq it.name, + setValue(StreamerData::id, getTwitchIdByName(it.name)) + ) + } +} diff --git a/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt b/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt index 5fb0e0d..c9a4ca6 100644 --- a/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt +++ b/src/main/kotlin/dev/jansel/feixiao/extensions/EventHooks.kt @@ -4,6 +4,8 @@ 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.getTwitchIdByName +import dev.jansel.feixiao.utils.getTwitchNameById import dev.jansel.feixiao.utils.tchannelid import dev.jansel.feixiao.utils.tserverid import dev.kord.core.behavior.getChannelOf @@ -12,6 +14,7 @@ import dev.kord.core.event.gateway.ReadyEvent import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.event import org.litote.kmongo.eq +import org.litote.kmongo.setValue class EventHooks : Extension() { override val name = "eventhooks" @@ -27,8 +30,10 @@ class EventHooks : Extension() { // 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 { if (it.servers.isNotEmpty()) { - twitchClient!!.clientHelper.enableStreamEventListener(it.name) - logger.info { "Enabled stream event listener for ${it.name}" } + val currentName = getTwitchNameById(it.id!!) + twitchClient!!.clientHelper.enableStreamEventListener(currentName) + logger.info { "Enabled stream event listener for $currentName" } + StreamerCollection().collection.updateOne(StreamerData::name eq it.name, setValue(StreamerData::name, currentName)) } 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/modals/EditStreamerModal.kt b/src/main/kotlin/dev/jansel/feixiao/modals/EditStreamerModal.kt deleted file mode 100644 index 3029d99..0000000 --- a/src/main/kotlin/dev/jansel/feixiao/modals/EditStreamerModal.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.jansel.feixiao.modals - -import dev.jansel.feixiao.i18n.Translations -import dev.kordex.core.components.forms.ModalForm -import dev.kordex.core.i18n.types.Key - -class EditStreamerModal : ModalForm() { - override var title = Translations.Streamer.Modals.Edit.title - - val streamerName = lineText { - label = Translations.Streamer.Modals.Edit.Streamername.label - placeholder = Translations.Streamer.Modals.Edit.Streamername.placeholder - } - - val channel = lineText { - label = Translations.Streamer.Modals.Edit.Channel.label - placeholder = Translations.Streamer.Modals.Edit.Channel.placeholder - } - - val role = lineText { - label = Translations.Streamer.Modals.Edit.Role.label - placeholder = Translations.Streamer.Modals.Edit.Role.placeholder - } - - val message = paragraphText { - label = Translations.Streamer.Modals.Edit.Message.label - placeholder = Translations.Streamer.Modals.Edit.Message.placeholder - } -} diff --git a/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt b/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt index c1b55da..97ece69 100644 --- a/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt +++ b/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt @@ -3,6 +3,7 @@ package dev.jansel.feixiao.utils import dev.jansel.feixiao.database.Database import dev.jansel.feixiao.database.collections.MetaCollection import dev.jansel.feixiao.database.collections.StreamerCollection +import dev.jansel.feixiao.twitchClient import dev.kord.common.entity.Snowflake import dev.kordex.core.builders.ExtensibleBotBuilder import dev.kordex.core.utils.env @@ -52,3 +53,23 @@ suspend inline fun ExtensibleBotBuilder.twitch(active: Boolean) { } } + +fun getTwitchNameById(id: String): String? { + val resultList = twitchClient!!.helix?.getUsers(null, listOf(id), null)?.execute() + resultList?.users?.forEach { user -> + if (user.id == id) { + return user.displayName + } + } + return null +} + +fun getTwitchIdByName(name: String): String? { + val resultList = twitchClient!!.helix?.getUsers(null, null, listOf(name))?.execute() + resultList?.users?.forEach { user -> + if (user.displayName == name) { + return user.id + } + } + return null +} diff --git a/src/main/resources/translations/feixiao/strings.properties b/src/main/resources/translations/feixiao/strings.properties index 2f7d47d..81c90c9 100644 --- a/src/main/resources/translations/feixiao/strings.properties +++ b/src/main/resources/translations/feixiao/strings.properties @@ -33,14 +33,6 @@ streamer.command.arguments.update.role.description=The role to assign to the str streamer.command.arguments.update.message.name=message streamer.command.arguments.update.message.description=Custom Announce message. Placeholders (in curly braces): url, name, title, category, role (if set) -streamer.modals.edit.title=Edit Streamer -streamer.modals.edit.streamername.label=Streamer Name -streamer.modals.edit.streamername.placeholder=Streamer Name -streamer.modals.edit.channel.label=Channel -streamer.modals.edit.channel.placeholder=Channel ID for the live messages. Leave empty for no change. -streamer.modals.edit.role.label=Role -streamer.modals.edit.role.placeholder=Role ID to assign to the stream pings. Leave empty for no change. -streamer.modals.edit.message.label=Message -streamer.modals.edit.message.placeholder=Message to send when the streamer goes live. Placeholders: {url}, {name}, {title}, {category}, {role} + # more to come... diff --git a/docs/terms-of-service.md b/terms-of-service.md similarity index 100% rename from docs/terms-of-service.md rename to terms-of-service.md