Compare commits

..

No commits in common. "root" and "refactor/translations-and-latest-kordex" have entirely different histories.

30 changed files with 206 additions and 524 deletions

36
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,36 @@
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@v3
with:
name: Build and Deploy Artifacts
path: build/libs/*.jar

33
.github/workflows/develop.yml vendored Normal file
View file

@ -0,0 +1,33 @@
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@v3
with:
name: Build and Deploy Artifacts
path: build/libs/*.jar

View file

@ -1,7 +1,6 @@
name: Build & Publish name: Build & Publish
on: on:
workflow_dispatch:
push: push:
branches: branches:
- root - root
@ -9,73 +8,40 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - uses: actions/checkout@v4
uses: https://github.com/actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: https://github.com/docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with:
cache-image: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Set up Java - name: Set up Java
uses: https://github.com/actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 21 java-version: 17
distribution: temurin 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 - name: Grant execute permission for gradlew
run: chmod +x 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) - name: Gradle (Build)
run: "./gradlew build" run: "./gradlew build"
- name: Login to DigitalOcean Registry - name: Login to GitHub Container Registry
uses: https://github.com/docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: registry.digitalocean.com registry: ghcr.io
username: ${{ secrets.DOMAIL }} username: ${{ github.repository_owner }}
password: ${{ secrets.DOKEY }} password: ${{ secrets.CR_PAT }}
- name: Build and push - name: Build and push
uses: https://github.com/docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/arm64
push: true push: true
tags: registry.digitalocean.com/jansel/feixiao:latest,registry.digitalocean.com/jansel/feixiao:${{ steps.date.outputs.date }} tags: ghcr.io/notjansel/feixiao:latest
- 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 }}

9
.idea/Feixiao.iml generated
View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

2
.idea/compiler.xml generated
View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="21"> <bytecodeTargetLevel target="13">
<module name="ext-common" target="1.8" /> <module name="ext-common" target="1.8" />
<module name="ext-common.main" target="1.8" /> <module name="ext-common.main" target="1.8" />
<module name="ext-common.test" target="1.8" /> <module name="ext-common.test" target="1.8" />

2
.idea/kotlinc.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="KotlinJpsPluginSettings"> <component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.21" /> <option name="version" value="2.0.21" />
</component> </component>
</project> </project>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-7d6c680f:19397de9638:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

2
.idea/misc.xml generated
View file

@ -5,7 +5,7 @@
<file type="web" url="file://$PROJECT_DIR$/../ext-common" /> <file type="web" url="file://$PROJECT_DIR$/../ext-common" />
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_13" project-jdk-name="azul-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$/../.." dumb="true">
<excludeFolder url="file://$MODULE_DIR$/../../.kotlin" />
</content>
</component>
</module>

View file

@ -5,5 +5,3 @@
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. 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. 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)

View file

@ -2,17 +2,17 @@ import dev.kordex.gradle.plugins.docker.file.*
import dev.kordex.gradle.plugins.kordex.DataCollection import dev.kordex.gradle.plugins.kordex.DataCollection
plugins { plugins {
distribution kotlin("jvm")
kotlin("plugin.serialization")
alias(libs.plugins.kotlin.jvm) id("com.github.johnrengelman.shadow")
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kordex.plugin) id("dev.kordex.gradle.docker")
alias(libs.plugins.kordex.docker) id("dev.kordex.gradle.kordex")
} }
group = "dev.jansel" group = "dev.jansel"
version = "1.2-SNAPSHOT" version = "1.0-SNAPSHOT"
dependencies { dependencies {
@ -31,26 +31,9 @@ dependencies {
implementation(libs.logging) 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 { kordEx {
kordExVersion = "2.3.1-SNAPSHOT" kordExVersion = "2.3.1-SNAPSHOT"
jvmTarget = 21 jvmTarget = 21
ignoreIncompatibleKotlinVersion = true
bot { bot {
// See https://docs.kordex.dev/data-collection.html // See https://docs.kordex.dev/data-collection.html
@ -75,51 +58,37 @@ docker {
// Each function (aside from comment/emptyLine) corresponds to a Dockerfile instruction. // Each function (aside from comment/emptyLine) corresponds to a Dockerfile instruction.
// See: https://docs.docker.com/reference/dockerfile/ // See: https://docs.docker.com/reference/dockerfile/
from("openjdk:21-jdk-slim") from("azul/zulu-openjdk-alpine:21-jre-headless-latest")
emptyLine() emptyLine()
comment("Create required directories")
runShell("mkdir -p /bot/plugins") runShell("mkdir -p /bot/plugins")
runShell("mkdir -p /bot/data") runShell("mkdir -p /bot/data")
runShell("mkdir -p /dist/out")
emptyLine()
copy("build/libs/$name-*-all.jar", "/bot/bot.jar")
emptyLine() emptyLine()
// Add volumes for locations that you need to persist. This is important! // 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/data") // Storage for data files
volume("/bot/plugins") // Plugin ZIP/JAR location volume("/bot/plugins") // Plugin ZIP/JAR location
emptyLine() 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") workdir("/bot")
emptyLine() emptyLine()
comment("Run the distribution start script") entryPointExec(
entryPointExec("/dist/out/${project.name}-${project.version}/bin/$name") "java", "-Xms2G", "-Xmx2G",
"-jar", "/bot/bot.jar"
)
} }
} }
tasks.wrapper {
gradleVersion = "8.11.1"
distributionType = Wrapper.DistributionType.BIN
}

View file

@ -1,17 +1,16 @@
[versions] [versions]
kotlin = "2.1.21" # 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.25" groovy = "3.0.22"
jansi = "2.4.2" jansi = "2.4.1"
kx-ser = "1.8.1" kx-ser = "1.7.3"
logback = "1.5.18" logback = "1.5.12"
logback-groovy = "1.14.5" logback-groovy = "1.14.5"
logging = "7.0.7" logging = "7.0.0"
twitch4j = "1.25.0" twitch4j = "1.22.0"
events4j = "0.12.2" events4j = "0.12.2"
kx-coroutines = "1.10.2" kx-coroutines = "1.9.0"
kmongo = "5.2.1" kmongo = "4.9.0"
kordex-gradle = "1.7.1"
[libraries] [libraries]
groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
@ -25,11 +24,3 @@ logging = { module = "io.github.oshai:kotlin-logging", version.ref = "logging" }
twitch4j = { module = "com.github.twitch4j:twitch4j", version.ref = "twitch4j" } twitch4j = { module = "com.github.twitch4j:twitch4j", version.ref = "twitch4j" }
events4j = { module = "com.github.philippheuer.events4j:events4j-handler-reactor", 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"} 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" }

Binary file not shown.

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

9
gradlew vendored
View file

@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # 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\n' "$PWD" ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -114,7 +115,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH="\\\"\\\"" CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@ -205,7 +206,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # 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 # * 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. # treated as '${Hostname}' itself on the command line.
@ -213,7 +214,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.

4
gradlew.bat vendored
View file

@ -70,11 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH= set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%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" %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View file

@ -1,15 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch"
]
}
],
"prHourlyLimit": 0
}

View file

@ -1,10 +1,20 @@
pluginManagement { 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 { repositories {
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
maven("https://releases-repo.kordex.dev")
maven("https://snapshots-repo.kordex.dev") maven("https://snapshots-repo.kordex.dev")
maven("https://releases-repo.kordex.dev")
} }
} }

View file

@ -1,23 +1,32 @@
/*
* This Kotlin source file was generated by the Gradle 'init' task.
*/
package dev.jansel.feixiao package dev.jansel.feixiao
import com.github.philippheuer.events4j.reactor.ReactorEventHandler
import com.github.twitch4j.TwitchClient 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.EventHooks
import dev.jansel.feixiao.extensions.StreamerCommand import dev.jansel.feixiao.extensions.StreamerCommand
import dev.jansel.feixiao.utils.database import dev.jansel.feixiao.utils.database
import dev.jansel.feixiao.utils.token import dev.jansel.feixiao.utils.token
import dev.jansel.feixiao.utils.twitch 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.core.ExtensibleBot
import dev.kordex.core.i18n.SupportedLocales import dev.kordex.core.i18n.SupportedLocales
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
var twitchClient: TwitchClient? = null var twitchClient: TwitchClient? = null
val logger = KotlinLogging.logger { } val logger = KotlinLogging.logger { }
var botRef: ExtensibleBot? = null
suspend fun main() { suspend fun main() {
botRef = ExtensibleBot(token) { val bot = ExtensibleBot(token) {
database(true) database(true)
twitch(true)
extensions { extensions {
add(::EventHooks) add(::EventHooks)
add(::StreamerCommand) add(::StreamerCommand)
@ -26,8 +35,55 @@ suspend fun main() {
applicationCommandLocale(SupportedLocales.ENGLISH, SupportedLocales.GERMAN) applicationCommandLocale(SupportedLocales.ENGLISH, SupportedLocales.GERMAN)
} }
} }
twitchClient = TwitchClientBuilder.builder()
.withEnableHelix(true)
.withClientId(twitchcid)
.withClientSecret(twitchcs)
.withDefaultEventHandler(ReactorEventHandler::class.java)
.build()
botRef!!.start() twitchClient!!.eventManager.onEvent(ChannelGoLiveEvent::class.java) {
logger.info { "${it.channel.name} went live!" }
runBlocking {
launch {
val streamer = StreamerCollection().getData(it.channel.name)
for (server in streamer!!.servers) {
val channel = bot.kordRef.getChannelOf<GuildMessageChannel>(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}")
}
}
}
}
}
}
bot.start()
} }

View file

@ -1,14 +1,8 @@
package dev.jansel.feixiao.database 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.collections.MetaCollection
import dev.jansel.feixiao.database.entities.MetaData import dev.jansel.feixiao.database.entities.MetaData
import dev.jansel.feixiao.database.migrations.v1 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 dev.kordex.core.koin.KordExKoinComponent
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.koin.core.component.inject import org.koin.core.component.inject
@ -21,13 +15,6 @@ object Migrator : KordExKoinComponent {
suspend fun migrate() { suspend fun migrate() {
logger.info { "Starting main database migration" } 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() var meta = mainMetaCollection.get()
@ -48,7 +35,6 @@ object Migrator : KordExKoinComponent {
try { try {
when (nextVersion) { when (nextVersion) {
1 -> ::v1 1 -> ::v1
2 -> ::v2
else -> break else -> break
}(db.mongo) }(db.mongo)

View file

@ -3,7 +3,6 @@ package dev.jansel.feixiao.database.collections
import dev.jansel.feixiao.database.Database import dev.jansel.feixiao.database.Database
import dev.jansel.feixiao.database.entities.Server import dev.jansel.feixiao.database.entities.Server
import dev.jansel.feixiao.database.entities.StreamerData import dev.jansel.feixiao.database.entities.StreamerData
import dev.jansel.feixiao.utils.getTwitchIdByName
import dev.kord.common.entity.Snowflake import dev.kord.common.entity.Snowflake
import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.koin.KordExKoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@ -21,7 +20,7 @@ class StreamerCollection : KordExKoinComponent {
suspend fun getData(channelName: String): StreamerData? = suspend fun getData(channelName: String): StreamerData? =
collection.findOne(StreamerData::name eq channelName) collection.findOne(StreamerData::name eq channelName)
suspend fun addData( suspend fun updateData(
guildId: Snowflake, guildId: Snowflake,
channelId: Snowflake, channelId: Snowflake,
streamerName: String, streamerName: String,
@ -36,98 +35,11 @@ class StreamerCollection : KordExKoinComponent {
) )
} else { } else {
collection.insertOne( collection.insertOne(
StreamerData(streamerName, getTwitchIdByName(streamerName), listOf(Server(guildId, channelId, roleId, liveMessage))) StreamerData(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( suspend fun removeData(
guildId: Snowflake, guildId: Snowflake,
channelId: Snowflake, channelId: Snowflake,

View file

@ -6,7 +6,6 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class StreamerData( data class StreamerData(
val name: String, val name: String,
val id: String?,
val servers: List<Server> val servers: List<Server>
) )

View file

@ -1,16 +0,0 @@
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>("streamerData").findOne(StreamerData::id eq null)?.let {
db.getCollection<StreamerData>("streamerData").updateOne(
StreamerData::name eq it.name,
setValue(StreamerData::id, getTwitchIdByName(it.name))
)
}
}

View file

@ -4,17 +4,10 @@ import dev.jansel.feixiao.database.collections.StreamerCollection
import dev.jansel.feixiao.database.entities.StreamerData import dev.jansel.feixiao.database.entities.StreamerData
import dev.jansel.feixiao.logger import dev.jansel.feixiao.logger
import dev.jansel.feixiao.twitchClient 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
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kord.core.event.gateway.ReadyEvent import dev.kord.core.event.gateway.ReadyEvent
import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.Extension
import dev.kordex.core.extensions.event import dev.kordex.core.extensions.event
import org.litote.kmongo.eq import org.litote.kmongo.eq
import org.litote.kmongo.setValue
class EventHooks : Extension() { class EventHooks : Extension() {
override val name = "eventhooks" override val name = "eventhooks"
@ -23,17 +16,12 @@ class EventHooks : Extension() {
event<ReadyEvent> { event<ReadyEvent> {
action { action {
logger.info { "Bot is ready!" } logger.info { "Bot is ready!" }
val onlineLog =
kord.getGuildOrNull(tserverid)?.getChannelOf<GuildMessageChannel>(tchannelid)
onlineLog?.createMessage("Bot Online!")
kord.editPresence { listening("the database") } kord.editPresence { listening("the database") }
// check every entry in the database and enable the stream event listener if a server is listening to the streamer // 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 { StreamerCollection().collection.find().toList().forEach {
if (it.servers.isNotEmpty()) { if (it.servers.isNotEmpty()) {
val currentName = getTwitchNameById(it.id!!) twitchClient!!.clientHelper.enableStreamEventListener(it.name)
twitchClient!!.clientHelper.enableStreamEventListener(currentName) logger.info { "Enabled stream event listener for ${it.name}" }
logger.info { "Enabled stream event listener for $currentName" }
StreamerCollection().collection.updateOne(StreamerData::name eq it.name, setValue(StreamerData::name, currentName))
} else { } else {
logger.info { "No servers are listening to ${it.name}, deleting from the database..." } logger.info { "No servers are listening to ${it.name}, deleting from the database..." }
StreamerCollection().collection.deleteMany(StreamerData::name eq it.name) StreamerCollection().collection.deleteMany(StreamerData::name eq it.name)

View file

@ -1,7 +1,6 @@
package dev.jansel.feixiao.extensions package dev.jansel.feixiao.extensions
import dev.jansel.feixiao.database.collections.StreamerCollection import dev.jansel.feixiao.database.collections.StreamerCollection
import dev.jansel.feixiao.database.entities.StreamerData
import dev.jansel.feixiao.i18n.Translations import dev.jansel.feixiao.i18n.Translations
import dev.jansel.feixiao.twitchClient import dev.jansel.feixiao.twitchClient
import dev.kord.common.entity.Permission import dev.kord.common.entity.Permission
@ -9,10 +8,12 @@ import dev.kordex.core.checks.anyGuild
import dev.kordex.core.checks.hasPermission import dev.kordex.core.checks.hasPermission
import dev.kordex.core.commands.Arguments import dev.kordex.core.commands.Arguments
import dev.kordex.core.commands.application.slash.publicSubCommand import dev.kordex.core.commands.application.slash.publicSubCommand
import dev.kordex.core.commands.converters.impl.* 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.Extension
import dev.kordex.core.extensions.publicSlashCommand import dev.kordex.core.extensions.publicSlashCommand
import org.litote.kmongo.eq
class StreamerCommand : Extension() { class StreamerCommand : Extension() {
override val name = "streaming" override val name = "streaming"
@ -30,21 +31,7 @@ class StreamerCommand : Extension() {
} }
action { action {
val streamer = arguments.streamer val streamer = arguments.streamer
StreamerCollection().getData(streamer)?.servers?.forEach { StreamerCollection().updateData(
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, guild!!.id,
arguments.channel.id, arguments.channel.id,
streamer, streamer,
@ -67,63 +54,12 @@ class StreamerCommand : Extension() {
} }
action { action {
val streamer = arguments.streamer val streamer = arguments.streamer
StreamerCollection().collection.findOne(StreamerData::name eq streamer)?.servers?.forEach { StreamerCollection().removeData(guild!!.id, channel.id, streamer, null, null)
StreamerCollection().removeData(it.guildId, it.channelId, streamer, it.roleId, it.liveMessage)
}
respond { respond {
content = "Removed streamer $streamer" 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"
}
}
}
}
} }
} }
@ -155,23 +91,4 @@ class StreamerCommand : Extension() {
require(true) 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
}
}
} }

View file

@ -1,66 +0,0 @@
package dev.jansel.feixiao.utils
import com.github.philippheuer.events4j.reactor.ReactorEventHandler
import com.github.twitch4j.TwitchClientBuilder
import com.github.twitch4j.events.ChannelGoLiveEvent
import dev.jansel.feixiao.botRef
import dev.jansel.feixiao.database.collections.StreamerCollection
import dev.jansel.feixiao.twitchClient
import dev.kord.core.entity.channel.GuildMessageChannel
import dev.kordex.core.koin.KordExKoinComponent
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class Twitch : KordExKoinComponent {
suspend fun init() {
twitchClient = TwitchClientBuilder.builder()
.withEnableHelix(true)
.withDefaultEventHandler(ReactorEventHandler::class.java)
.withClientId(twitchcid)
.withClientSecret(twitchcs)
.build()
twitchClient!!.eventManager.onEvent(ChannelGoLiveEvent::class.java) {
dev.jansel.feixiao.logger.info { "${it.channel.name} went live!" }
runBlocking {
launch {
val streamer = StreamerCollection().getData(it.channel.name)
for (server in streamer!!.servers) {
val channel = botRef!!.kordRef.getChannelOf<GuildMessageChannel>(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}")
}
}
}
}
}
}
}
}

View file

@ -3,7 +3,6 @@ package dev.jansel.feixiao.utils
import dev.jansel.feixiao.database.Database import dev.jansel.feixiao.database.Database
import dev.jansel.feixiao.database.collections.MetaCollection import dev.jansel.feixiao.database.collections.MetaCollection
import dev.jansel.feixiao.database.collections.StreamerCollection import dev.jansel.feixiao.database.collections.StreamerCollection
import dev.jansel.feixiao.twitchClient
import dev.kord.common.entity.Snowflake import dev.kord.common.entity.Snowflake
import dev.kordex.core.builders.ExtensibleBotBuilder import dev.kordex.core.builders.ExtensibleBotBuilder
import dev.kordex.core.utils.env import dev.kordex.core.utils.env
@ -38,38 +37,3 @@ suspend inline fun ExtensibleBotBuilder.database(migrate: Boolean) {
} }
} }
} }
suspend inline fun ExtensibleBotBuilder.twitch(active: Boolean) {
hooks {
beforeKoinSetup {
loadModule {
single { Twitch() } bind Twitch::class
}
if (active) {
Twitch().init()
}
}
}
}
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
}

View file

@ -2,16 +2,10 @@
streamer.command.name=streamer streamer.command.name=streamer
streamer.command.description=A bundle of Streamer commands streamer.command.description=A bundle of Streamer commands
streamer.command.add.name=add streamer.command.add.name=add
streamer.command.add.description=Add a new streamer to the listener streamer.command.add.description=Add a new streamer to the listener
streamer.command.remove.name=remove streamer.command.remove.name=remove
streamer.command.remove.description=Remove a streamer from the listener 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.name=streamer
streamer.command.arguments.add.streamer.description=The streamer to add streamer.command.arguments.add.streamer.description=The streamer to add
streamer.command.arguments.add.channel.name=channel streamer.command.arguments.add.channel.name=channel
@ -20,19 +14,7 @@ streamer.command.arguments.add.role.name=role
streamer.command.arguments.add.role.description=The role to assign to the streamer 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.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.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.name=streamer
streamer.command.arguments.remove.description=The streamer to remove 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... # more to come...