diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index ba1d693..0000000 --- a/.github/renovate.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$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 deleted file mode 100644 index d7a094d..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Build (CI) - -on: - push: - branches-ignore: - - root - - develop - - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Java - uses: actions/setup-java@v4 - - with: - java-version: 17 - distribution: temurin - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Gradle (Build) - run: "./gradlew build" - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - - with: - name: Build and Deploy Artifacts - path: build/libs/*.jar diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml deleted file mode 100644 index eb0ab25..0000000 --- a/.github/workflows/develop.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Build & Publish - -on: - push: - branches: - - develop - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Java - uses: actions/setup-java@v4 - - with: - java-version: 17 - distribution: temurin - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Gradle (Build) - run: "./gradlew build" - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - - with: - name: Build and Deploy Artifacts - path: build/libs/*.jar diff --git a/.github/workflows/root.yml b/.github/workflows/root.yml index a59dafc..2ebd1de 100644 --- a/.github/workflows/root.yml +++ b/.github/workflows/root.yml @@ -1,6 +1,7 @@ name: Build & Publish on: + workflow_dispatch: push: branches: - root @@ -8,40 +9,73 @@ on: jobs: build: runs-on: ubuntu-latest + steps: - - 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: 17 + java-version: 21 distribution: temurin + - name: "Restore Cache" + id: restore-cache + uses: https://data.forgejo.org/actions/cache/restore@v4 + + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ~/.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: Login to GitHub Container Registry - uses: docker/login-action@v3 + - name: Login to DigitalOcean Registry + uses: https://github.com/docker/login-action@v3 with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.CR_PAT }} + registry: registry.digitalocean.com + username: ${{ secrets.DOMAIL }} + password: ${{ secrets.DOKEY }} - name: Build and push - uses: docker/build-push-action@v6 + uses: https://github.com/docker/build-push-action@v6 with: context: . - platforms: linux/arm64 + platforms: linux/amd64,linux/arm64 push: true - tags: ghcr.io/notjansel/feixiao:latest + 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 + ~/.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/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..8d28a9e --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index a4abeb1..0b1ec52 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,7 +5,7 @@ - + - \ No newline at end of file + diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4787ae5..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index afc06e6..80fee11 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,5 @@ Blanketbot is a Discord bot, coded to notify you when your favorite twitch streamer goes live. To simply set it up, add the Bot to your server through the Invite link above and use the `/streamer add` command to add your favorite streamer. The bot will then notify you when the streamer goes live. If issues arise, please don't hesitate to create an issue here on GitHub. + +Info if you're looking at this from GitHub and not the Gitea Instance: This repo now is used as a mirror, Issues will still be looked at here on GitHub. For PR's, I will create a patchfile from that pr and apply it manually on the [origin repo](https://git.jansel.dev/jansel/feixiao) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e53343b..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.10.2" - distributionType = Wrapper.DistributionType.BIN -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b20ea0c..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.0" -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" } @@ -23,4 +24,12 @@ logback-groovy = { module = "io.github.virtualdogbert:logback-groovy-config", ve 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"} +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 df97d72..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.10.2-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 fc0b921..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.5.8" - id("dev.kordex.gradle.kordex") version "1.5.8" - } repositories { gradlePluginPortal() mavenCentral() diff --git a/src/main/kotlin/dev/jansel/feixiao/App.kt b/src/main/kotlin/dev/jansel/feixiao/App.kt index afdfc21..eabdacc 100644 --- a/src/main/kotlin/dev/jansel/feixiao/App.kt +++ b/src/main/kotlin/dev/jansel/feixiao/App.kt @@ -1,6 +1,3 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ package dev.jansel.feixiao import com.github.twitch4j.TwitchClient @@ -8,14 +5,14 @@ import dev.jansel.feixiao.extensions.EventHooks import dev.jansel.feixiao.extensions.StreamerCommand import dev.jansel.feixiao.utils.database import dev.jansel.feixiao.utils.token +import dev.jansel.feixiao.utils.twitch import dev.kordex.core.ExtensibleBot import dev.kordex.core.i18n.SupportedLocales import io.github.oshai.kotlinlogging.KotlinLogging -import dev.jansel.feixiao.utils.twitch var twitchClient: TwitchClient? = null -val logger = KotlinLogging.logger { } -var botRef : ExtensibleBot? = null +val logger = KotlinLogging.logger { } +var botRef: ExtensibleBot? = null suspend fun main() { botRef = ExtensibleBot(token) { 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 651df09..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 @@ -20,7 +21,7 @@ class StreamerCollection : KordExKoinComponent { suspend fun getData(channelName: String): StreamerData? = collection.findOne(StreamerData::name eq channelName) - suspend fun updateData( + suspend fun addData( guildId: Snowflake, channelId: Snowflake, streamerName: String, @@ -35,11 +36,98 @@ class StreamerCollection : KordExKoinComponent { ) } else { collection.insertOne( - StreamerData(streamerName, listOf(Server(guildId, channelId, roleId, liveMessage))) + StreamerData(streamerName, getTwitchIdByName(streamerName), listOf(Server(guildId, channelId, roleId, liveMessage))) ) } } + /** + * Update the roleId + * @param streamerName: The name of the streamer + * @param roleId: The roleId to update + * @param guildId: The guildId to update + * @param noOverload: This is needed to avoid a conflict with the other updateData function, set to true or false, doesn't matter + * @return 0 = success, 1 = no Server associated with the guildId, 2 = no StreamerData associated with the streamerName + */ + + suspend fun updateData( + streamerName: String, + roleId: Snowflake, + guildId: Snowflake, + noOverload: Boolean = false // this is needed to avoid a conflict with the other updateData function + ): Int { + val coll = collection.findOne(StreamerData::name eq streamerName) + if (coll != null) { + val temp = coll.servers.find { server -> server.guildId == guildId } + if (temp == null) return 1 + collection.updateMany( + StreamerData::name eq streamerName, + setValue( + StreamerData::servers, + coll.servers - temp + Server(guildId, temp.channelId, roleId, temp.liveMessage) + ) + ) + return 0 + } + return 2 + } + + /** + * Update the liveMessage + * @param streamerName: The name of the streamer + * @param liveMessage: The liveMessage to update + * @param guildId: The guildId to update + * @return 0 = success, 1 = no Server associated with the guildId, 2 = no StreamerData associated with the streamerName + */ + suspend fun updateData( + streamerName: String, + liveMessage: String?, + guildId: Snowflake + ): Int { + val coll = collection.findOne(StreamerData::name eq streamerName) + if (coll != null) { + val temp = coll.servers.find { server -> server.guildId == guildId } + if (temp == null) return 1 + collection.updateMany( + StreamerData::name eq streamerName, + setValue( + StreamerData::servers, + coll.servers - temp + Server(guildId, temp.channelId, temp.roleId, liveMessage) + ) + ) + return 0 + } + return 2 + } + + /** + * Update the channelId + * @param streamerName: The name of the streamer + * @param channelId: The channelId to update + * @param guildId: The guildId to update + * @return 0 = success, 1 = no Server associated with the guildId, 2 = no StreamerData associated with the streamerName + */ + suspend fun updateData( + streamerName: String, + channelId: Snowflake, + guildId: Snowflake + ): Int { + val coll = collection.findOne(StreamerData::name eq streamerName) + if (coll != null) { + val temp = coll.servers.find { server -> server.guildId == guildId } + if (temp == null) return 1 + collection.updateMany( + StreamerData::name eq streamerName, + setValue( + StreamerData::servers, + coll.servers - temp + Server(guildId, channelId, temp.roleId, temp.liveMessage) + ) + ) + return 0 + } + return 2 + } + suspend fun removeData( guildId: Snowflake, channelId: Snowflake, 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/extensions/StreamerCommand.kt b/src/main/kotlin/dev/jansel/feixiao/extensions/StreamerCommand.kt index efafbac..4f014d1 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.database.entities.StreamerData import dev.jansel.feixiao.i18n.Translations import dev.jansel.feixiao.twitchClient import dev.kord.common.entity.Permission @@ -8,12 +9,10 @@ import dev.kordex.core.checks.anyGuild import dev.kordex.core.checks.hasPermission 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.commands.converters.impl.* import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand +import org.litote.kmongo.eq class StreamerCommand : Extension() { override val name = "streaming" @@ -31,7 +30,21 @@ class StreamerCommand : Extension() { } action { val streamer = arguments.streamer - StreamerCollection().updateData( + StreamerCollection().getData(streamer)?.servers?.forEach { + if (it.guildId == guild!!.id) { + respond { + content = "Streamer already exists in this server" + } + return@action + } + } + if (arguments.role?.id == guild!!.id) { + respond { + content = "This action would implement a everyone ping, which is due to how role pings are implemented right now not possible. If you want to make an everyone ping regardless, make a role-less ping and write the everyone ping manually." + } + return@action + } + StreamerCollection().addData( guild!!.id, arguments.channel.id, streamer, @@ -54,12 +67,63 @@ class StreamerCommand : Extension() { } action { val streamer = arguments.streamer - StreamerCollection().removeData(guild!!.id, channel.id, streamer, null, null) + StreamerCollection().collection.findOne(StreamerData::name eq streamer)?.servers?.forEach { + StreamerCollection().removeData(it.guildId, it.channelId, streamer, it.roleId, it.liveMessage) + } respond { content = "Removed streamer $streamer" } } } + + publicSubCommand(::UpdateStreamerArgs) { + name = Translations.Streamer.Command.Update.name + description = Translations.Streamer.Command.Update.description + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + action { + val streamer = arguments.streamer + val data = StreamerCollection().collection.findOne(StreamerData::name eq streamer) + if (data != null) { + val servers = data.servers + val guildId = guild!!.id + val roleId = arguments.role + val channelId = arguments.channel + val message = arguments.message + val temp = servers.find { it.guildId == guildId } + if (roleId?.id== guildId) { + respond { + content = "This action would implement a everyone ping, which is due to how role pings are implemented right now not possible. If you want to make an everyone ping regardless, make a role-less ping and write the everyone ping manually." + } + return@action + } + if (temp != null) { + if (channelId != null) { + StreamerCollection().updateData(streamer, channelId.id, guildId) + } + if (roleId != null) { + StreamerCollection().updateData(streamer, roleId.id, guildId, false) + } + if (message != null) { + StreamerCollection().updateData(streamer, message, guildId) + } + respond { + content = "Updated streamer $streamer" + } + } else { + respond { + content = "No server associated with the guildId" + } + } + } else { + respond { + content = "No StreamerData associated with the streamerName" + } + } + } + } } } @@ -91,4 +155,23 @@ class StreamerCommand : Extension() { require(true) } } + + inner class UpdateStreamerArgs : Arguments() { + val streamer by string { + name = Translations.Streamer.Command.Arguments.Update.Streamer.name + description = Translations.Streamer.Command.Arguments.Update.Streamer.description + } + val channel by optionalChannel { + name = Translations.Streamer.Command.Arguments.Update.Channel.name + description = Translations.Streamer.Command.Arguments.Update.Channel.description + } + val role by optionalRole { + name = Translations.Streamer.Command.Arguments.Update.Role.name + description = Translations.Streamer.Command.Arguments.Update.Role.description + } + val message by optionalString { + name = Translations.Streamer.Command.Arguments.Update.Message.name + description = Translations.Streamer.Command.Arguments.Update.Message.description + } + } } diff --git a/src/main/kotlin/dev/jansel/feixiao/utils/Twitch.kt b/src/main/kotlin/dev/jansel/feixiao/utils/Twitch.kt index 07b3d2e..0aa7ca8 100644 --- a/src/main/kotlin/dev/jansel/feixiao/utils/Twitch.kt +++ b/src/main/kotlin/dev/jansel/feixiao/utils/Twitch.kt @@ -25,12 +25,37 @@ class Twitch : KordExKoinComponent { runBlocking { launch { val streamer = StreamerCollection().getData(it.channel.name) - val channel = botRef!!.kordRef.getChannelOf(streamer!!.servers.first().channelId) - val role = streamer.servers.first().roleId - if (role != null) { - channel?.createMessage("<@&$role> https://twitch.tv/${it.channel.name} went live streaming ${it.stream.gameName}: ${it.stream.title}") - } else { - channel?.createMessage("${it.channel.name} went live: ${it.stream.title}") + for (server in streamer!!.servers) { + val channel = botRef!!.kordRef.getChannelOf(server.channelId) + val role = server.roleId + val livemessage = server.liveMessage + + if (role != null) { + if (livemessage != null) { + channel?.createMessage( + livemessage + .replace("{name}", it.channel.name) + .replace("{category}", it.stream.gameName) + .replace("{title}", it.stream.title) + .replace("{url}", "https://twitch.tv/${it.channel.name}") + .replace("{role}", "<@&$role>") + ) + } else { + channel?.createMessage("<@&$role> https://twitch.tv/${it.channel.name} went live streaming ${it.stream.gameName}: ${it.stream.title}") + } + } else { + if (livemessage != null) { + channel?.createMessage( + livemessage + .replace("{name}", it.channel.name) + .replace("{category}", it.stream.gameName) + .replace("{title}", it.stream.title) + .replace("{url}", "https://twitch.tv/${it.channel.name}") + ) + } else { + channel?.createMessage("https://twitch.tv/${it.channel.name} went live streaming ${it.stream.gameName}: ${it.stream.title}") + } + } } } } @@ -38,5 +63,4 @@ class Twitch : KordExKoinComponent { } - } diff --git a/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt b/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt index a7c9ec5..97ece69 100644 --- a/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt +++ b/src/main/kotlin/dev/jansel/feixiao/utils/_Utils.kt @@ -1,17 +1,13 @@ package dev.jansel.feixiao.utils -import com.github.twitch4j.TwitchClientBuilder -import com.github.twitch4j.events.ChannelGoLiveEvent 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.kord.core.entity.channel.GuildMessageChannel import dev.kordex.core.builders.ExtensibleBotBuilder import dev.kordex.core.utils.env import dev.kordex.core.utils.loadModule -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.koin.dsl.bind @@ -57,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 95535d7..81c90c9 100644 --- a/src/main/resources/translations/feixiao/strings.properties +++ b/src/main/resources/translations/feixiao/strings.properties @@ -2,10 +2,16 @@ 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.update.name=update +streamer.command.update.description=Update a streamer listener's settings for this guild + streamer.command.arguments.add.streamer.name=streamer streamer.command.arguments.add.streamer.description=The streamer to add streamer.command.arguments.add.channel.name=channel @@ -14,7 +20,19 @@ 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 +streamer.command.arguments.update.streamer.name=streamer +streamer.command.arguments.update.streamer.description=The streamer to edit +streamer.command.arguments.update.channel.name=channel +streamer.command.arguments.update.channel.description=The channel to add the streamer to +streamer.command.arguments.update.role.name=role +streamer.command.arguments.update.role.description=The role to assign to the streamer +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) + + + # 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