Compare commits

..

1 commit

20 changed files with 134 additions and 205 deletions

View file

@ -1,81 +1,75 @@
name: Build & Publish
on:
workflow_dispatch:
push:
branches:
- root
schedule:
- cron: "0 6 * * *"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: https://github.com/actions/checkout@v4
- name: Disable SSL verify (Temporary Fix)
run: git config --global http.sslVerify false
- uses: actions/checkout@v4
- name: Set up QEMU
uses: https://github.com/docker/setup-qemu-action@v3
with:
cache-image: false
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v3
- name: Set up Java
uses: https://github.com/actions/setup-java@v4
uses: actions/setup-java@v4
with:
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 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
- name: Gradle Cache
uses: actions/cache/restore@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.m2/repository
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
key: ${{ runner.os }}-gradle-Feixiao
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Gradle (Build)
run: "./gradlew build"
- name: Gradle Cache
uses: 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

2
.idea/kotlinc.xml generated
View file

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

View file

@ -2,17 +2,17 @@ import dev.kordex.gradle.plugins.docker.file.*
import dev.kordex.gradle.plugins.kordex.DataCollection
plugins {
distribution
kotlin("jvm")
kotlin("plugin.serialization")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
id("com.github.johnrengelman.shadow")
alias(libs.plugins.kordex.plugin)
alias(libs.plugins.kordex.docker)
id("dev.kordex.gradle.docker")
id("dev.kordex.gradle.kordex")
}
group = "dev.jansel"
version = "1.2-SNAPSHOT"
version = "1.0-SNAPSHOT"
dependencies {
@ -31,26 +31,9 @@ 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
@ -75,51 +58,37 @@ docker {
// Each function (aside from comment/emptyLine) corresponds to a Dockerfile instruction.
// See: https://docs.docker.com/reference/dockerfile/
from("openjdk:21-jdk-slim")
from("azul/zulu-openjdk-alpine:21-jre-headless-latest")
emptyLine()
comment("Create required directories")
runShell("mkdir -p /bot/plugins")
runShell("mkdir -p /bot/data")
runShell("mkdir -p /dist/out")
emptyLine()
copy("build/libs/$name-*-all.jar", "/bot/bot.jar")
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()
comment("Run the distribution start script")
entryPointExec("/dist/out/${project.name}-${project.version}/bin/$name")
entryPointExec(
"java", "-Xms2G", "-Xmx2G",
"-jar", "/bot/bot.jar"
)
}
}
tasks.wrapper {
gradleVersion = "8.11.1"
distributionType = Wrapper.DistributionType.BIN
}

View file

@ -1,17 +1,16 @@
[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"
jansi = "2.4.2"
kx-ser = "1.8.1"
logback = "1.5.18"
groovy = "3.0.23"
jansi = "2.4.1"
kx-ser = "1.7.3"
logback = "1.5.12"
logback-groovy = "1.14.5"
logging = "7.0.7"
twitch4j = "1.25.0"
logging = "7.0.3"
twitch4j = "1.23.0"
events4j = "0.12.2"
kx-coroutines = "1.10.2"
kmongo = "5.2.1"
kordex-gradle = "1.7.1"
kx-coroutines = "1.9.0"
kmongo = "5.1.0"
[libraries]
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" }
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" }

Binary file not shown.

View file

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

9
gradlew vendored
View file

@ -86,7 +86,8 @@ 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\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.
MAX_FD=maximum
@ -114,7 +115,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@ -205,7 +206,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# 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.
# * 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.
@ -213,7 +214,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.

4
gradlew.bat vendored
View file

@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@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
@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,4 +1,14 @@
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()

View file

@ -1,14 +1,8 @@
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
@ -21,13 +15,6 @@ 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()
@ -48,7 +35,6 @@ object Migrator : KordExKoinComponent {
try {
when (nextVersion) {
1 -> ::v1
2 -> ::v2
else -> break
}(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.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
@ -36,7 +35,7 @@ class StreamerCollection : KordExKoinComponent {
)
} else {
collection.insertOne(
StreamerData(streamerName, getTwitchIdByName(streamerName), listOf(Server(guildId, channelId, roleId, liveMessage)))
StreamerData(streamerName, listOf(Server(guildId, channelId, roleId, liveMessage)))
)
}
}

View file

@ -6,7 +6,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class StreamerData(
val name: String,
val id: String?,
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,8 +4,6 @@ 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
@ -14,7 +12,6 @@ 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"
@ -30,10 +27,8 @@ 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()) {
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))
twitchClient!!.clientHelper.enableStreamEventListener(it.name)
logger.info { "Enabled stream event listener for ${it.name}" }
} else {
logger.info { "No servers are listening to ${it.name}, deleting from the database..." }
StreamerCollection().collection.deleteMany(StreamerData::name eq it.name)

View file

@ -0,0 +1,29 @@
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
}
}

View file

@ -3,7 +3,6 @@ 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
@ -53,23 +52,3 @@ 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
}

View file

@ -33,6 +33,14 @@ 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...