diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..6fcd41c
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,34 @@
+#image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/gradle
+image: gradle
+
+stages:
+ - build
+ - publish
+
+variables:
+ GRADLE_OPTS: "-Dorg.gradle.daemon=false"
+ GIT_SUBMODULE_STRATEGY: recursive
+
+before_script:
+ - export GRADLE_USER_HOME=`pwd`/.gradle
+ - rm -f .gradle/caches/modules-2/modules-2.lock
+ - rm -fr .gradle/caches/*/plugin-resolution/
+
+cache:
+ paths:
+ - .gradle/wrapper
+ - .gradle/caches
+ - build
+
+build:
+ stage: build
+ script:
+ - gradle shadowJar
+ except:
+ - tags
+
+publish:
+ stage: publish
+ script:
+ - gradle publish
+
diff --git a/README.md b/README.md
index 877eb26..951962a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,46 @@
-# Botendo
+# Botendo version 5
+
+"5th times the charm" ~ me
+
A Discord music bot, written in Kotlin using the kord library.
+
+
+## Contributors
+
+
+
+
+
+## Commands
+
+- info -- Show basic infos about the bot
+- play -- Play a song
+- stop -- Stop playing a song and leave the vc
+
+## How to self-host
+
+1. Download the latest release from the Package Registry ("Packages and registries" > "Package Registry")
+ 1. It should be called something like this: "Botendo-X.X.X-xxxxxxxx-prod.jar" (replace "X.X.X" with the latest
+ version and xxxxxxxx" with the commit its based on.)
+ 2. If you want to run an early version, which may be (very) unsable, you can run a development version. Just use an
+ entry ending in "-dev.jar"
+2. Place it anywhere you want.
+3. Run the following command:
+ > java -jar Botendo-X.X.X-xxxxxxxx-prod.jar
+4. The bot should start and create a config file named "credentials.nils"
+5. Open it and put in your credentials.
+ 1. token: your Discord bot token
+ 2. lavaip: the IP of your LavaLink instance e.g.: ("ws://192.168.178.1:2333")
+ 3. lavapw: your password for the LavaLink instance
+6. Rerun the command
+ > java -jar Botendo-X.X.X-xxxxxxxx-prod.jar
+7. The bot should now be up and running.
+
+## How to set up workspace
+
+Install IntellJ and import the project from git.
+Done.
diff --git a/build.gradle.kts b/build.gradle.kts
index 29f7dea..b0f98f5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -34,6 +34,7 @@ group = "de.limited_dev.botendo"
version = System.getenv("CI_COMMIT_TAG")?.let { "$it-${System.getenv("CI_COMMIT_SHORT_SHA")}-prod" }
?: System.getenv("CI_COMMIT_SHORT_SHA")?.let { "$it-dev" }
?: "DevelopmentBuild"
+
val kordver = "0.8.1"
val lavaver = "3.8.0"
val coroutinesver = "1.1.0"
diff --git a/src/main/kotlin/de/limited_dev/botendo/Bot.kt b/src/main/kotlin/de/limited_dev/botendo/Bot.kt
index 8e02011..b33179f 100644
--- a/src/main/kotlin/de/limited_dev/botendo/Bot.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/Bot.kt
@@ -22,9 +22,9 @@ package de.limited_dev.botendo
import de.limited_dev.botendo.commands.slash.component.SlashCommandManager
import de.limited_dev.botendo.commands.slash.component.options.CommandOption
import de.limited_dev.botendo.commands.slash.component.options.OptionType
+import de.limited_dev.botendo.util.CredentialManager
import de.limited_dev.botendo.util.Logger
import de.limited_dev.botendo.util.MessageUtil
-import de.limited_dev.botendo.util.TokenManager
import dev.kord.core.Kord
import dev.kord.core.event.interaction.GuildChatInputCommandInteractionCreateEvent
import dev.kord.core.on
@@ -44,10 +44,10 @@ object Bot {
Logger.out("Starting Bot...")
//Load config
- TokenManager.load()
+ CredentialManager.load()
//Don't run the bot when there is no bot token in config
- if (TokenManager.token == "empty") {
+ if (CredentialManager.token == "empty") {
Logger.out("The config does not contain a bot token")
return
}
@@ -56,13 +56,13 @@ object Bot {
SlashCommandManager.register()
//Add Bot token to kord
- kord = Kord(TokenManager.token)
+ kord = Kord(CredentialManager.token)
//Register Command Listener
kord.on {
val response = interaction.deferPublicResponse()
val command = interaction.command
- Logger.out("Command /${command.rootName}")
+ Logger.out("Command /${command.rootName} with ${command.options.size} Option${if (command.options.size == 1) "" else "s"}")
for (c in SlashCommandManager.commands) {
if (c.name != command.rootName)
continue
@@ -128,10 +128,8 @@ object Bot {
//Add LavaLink
lava = kord.lavakord()
- //Add the LavaLink node
- lava.addNode("ws://192.168.178.2:2333", "youshallnotpass")
-
-
+ //Add the LavaLink node from config
+ lava.addNode(CredentialManager.linkip, CredentialManager.linkpw)
Logger.out("Logging in")
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/SlashCommandManager.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/SlashCommandManager.kt
index dcc3a67..a69f7e1 100644
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/SlashCommandManager.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/SlashCommandManager.kt
@@ -19,8 +19,7 @@
package de.limited_dev.botendo.commands.slash.component
-import de.limited_dev.botendo.commands.slash.music.PlayCommand
-import de.limited_dev.botendo.commands.slash.music.StopCommand
+import de.limited_dev.botendo.commands.slash.music.*
import de.limited_dev.botendo.commands.slash.util.InfoCommand
object SlashCommandManager {
@@ -31,5 +30,8 @@ object SlashCommandManager {
commands.add(InfoCommand())
commands.add(PlayCommand())
commands.add(StopCommand())
+ commands.add(NowPlayingCommand())
+ commands.add(QueueCommand())
+ commands.add(SkipCommand())
}
}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/options/CommandOption.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/options/CommandOption.kt
index 9d816ba..abeef23 100644
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/options/CommandOption.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/component/options/CommandOption.kt
@@ -19,5 +19,4 @@
package de.limited_dev.botendo.commands.slash.component.options
-data class CommandOption(val name: String, val description: String, val required: Boolean, val type: OptionType) {
-}
+data class CommandOption(val name: String, val description: String, val required: Boolean, val type: OptionType)
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/NowPlayingCommand.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/NowPlayingCommand.kt
new file mode 100644
index 0000000..81928e1
--- /dev/null
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/NowPlayingCommand.kt
@@ -0,0 +1,73 @@
+/*
+ * Botendo
+ * Copyright (C) 2023 limited_dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package de.limited_dev.botendo.commands.slash.music
+
+import de.limited_dev.botendo.Bot
+import de.limited_dev.botendo.commands.slash.component.SlashCommand
+import de.limited_dev.botendo.commands.slash.component.options.CommandOption
+import de.limited_dev.botendo.util.MessageUtil
+import de.limited_dev.botendo.util.TimeUtil
+import de.limited_dev.botendo.util.UrlUtil
+import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import dev.schlaubi.lavakord.audio.Link
+
+class NowPlayingCommand : SlashCommand("nowplaying", "Show what's currently playing", null) {
+ override suspend fun onSlashCommand(
+ interaction: GuildChatInputCommandInteraction,
+ response: DeferredPublicMessageInteractionResponseBehavior,
+ args: Map
+ ) {
+ val guildId = interaction.guildId
+ val link = Bot.lava.getLink(guildId.toString())
+ val player = link.player
+ if (link.state != Link.State.CONNECTED) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Not connected",
+ "I'm not in a VC and therefore, I am not playing anything."
+ )
+ return
+ }
+ val track = player.playingTrack
+ if (track == null) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Not Playing",
+ "I'm not playing anything currently"
+ )
+ return
+ }
+ MessageUtil.sendEmbedForInteractionWithImage(
+ interaction,
+ response,
+ "Currently playing",
+ "**${track.title}**\n*Now Playing*\nby ${track.author} :: ${
+ TimeUtil.getTimeFormatedRaw(
+ track.length.inWholeMilliseconds
+ )
+ }\n" +
+ ">>>${track.uri}",
+ "https://img.youtube.com/vi/" + UrlUtil.getYtThumbnailUrl(track.uri!!) + "/maxresdefault.jpg"
+ )
+ }
+}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/PlayCommand.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/PlayCommand.kt
index bb8a009..7338ae0 100644
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/PlayCommand.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/PlayCommand.kt
@@ -19,10 +19,11 @@
package de.limited_dev.botendo.commands.slash.music
-import de.limited_dev.botendo.Bot
+import de.limited_dev.botendo.Bot.lava
import de.limited_dev.botendo.commands.slash.component.SlashCommand
import de.limited_dev.botendo.commands.slash.component.options.CommandOption
import de.limited_dev.botendo.commands.slash.component.options.OptionType
+import de.limited_dev.botendo.commands.slash.music.component.MusicManager
import de.limited_dev.botendo.util.MessageUtil
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
@@ -39,7 +40,7 @@ class PlayCommand : SlashCommand(
args: Map
) {
val guildId = interaction.guildId
- val link = Bot.lava.getLink(guildId.toString())
+ val link = lava.getLink(guildId.toString())
val voiceState = interaction.user.getVoiceStateOrNull()
if (voiceState == null) {
@@ -56,6 +57,14 @@ class PlayCommand : SlashCommand(
if (link.state != Link.State.CONNECTED) {
link.connectAudio(channelId!!.value)
+ } else if (link.state == Link.State.CONNECTED && link.lastChannelId != channelId!!.value) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "You are not in my VC",
+ "We are not in the same VC and therefore, you cannot play any music"
+ )
+ return
}
val query = args[this.options!![0]]
@@ -64,5 +73,7 @@ class PlayCommand : SlashCommand(
} else {
"ytsearch:$query"
}
+
+ MusicManager.addToQueue(interaction, response, link, search)
}
}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/QueueCommand.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/QueueCommand.kt
new file mode 100644
index 0000000..56cb05f
--- /dev/null
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/QueueCommand.kt
@@ -0,0 +1,71 @@
+/*
+ * Botendo
+ * Copyright (C) 2023 limited_dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package de.limited_dev.botendo.commands.slash.music
+
+import de.limited_dev.botendo.Bot
+import de.limited_dev.botendo.commands.slash.component.SlashCommand
+import de.limited_dev.botendo.commands.slash.component.options.CommandOption
+import de.limited_dev.botendo.commands.slash.music.component.MusicManager
+import de.limited_dev.botendo.util.MessageUtil
+import de.limited_dev.botendo.util.TimeUtil
+import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import dev.schlaubi.lavakord.audio.Link
+
+class QueueCommand : SlashCommand("queue", "Show whats up next", null) {
+ override suspend fun onSlashCommand(
+ interaction: GuildChatInputCommandInteraction,
+ response: DeferredPublicMessageInteractionResponseBehavior,
+ args: Map
+ ) {
+ val guildId = interaction.guildId
+ val link = Bot.lava.getLink(guildId.toString())
+ val player = link.player
+ if (link.state != Link.State.CONNECTED) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Not connected",
+ "I'm not in a VC and therefore, I am not playing anything."
+ )
+ return
+ }
+ val track = player.playingTrack
+ if (track == null) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Queue empty",
+ "I'm not playing anything currently"
+ )
+ return
+ }
+ val gts = MusicManager.getGuildTrackScheduler(interaction.guild.asGuild(), player)
+ val q = gts.getQueue()
+ var desc =
+ """${"**" + track.title + " - " + TimeUtil.getTimeFormatedShortend(track.length.inWholeMilliseconds)} (${track.author})**""" + "\n"
+ for ((i, tr) in q.withIndex()) {
+ if (i >= 14)
+ continue
+ desc += tr.info.title + " - " + TimeUtil.getTimeFormatedShortend(tr.info.length) + " (" + tr.info.author + ")\n"
+ }
+ MessageUtil.sendEmbedForInteraction(interaction, response, "Queue", desc)
+ }
+}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/SkipCommand.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/SkipCommand.kt
new file mode 100644
index 0000000..48ef34f
--- /dev/null
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/SkipCommand.kt
@@ -0,0 +1,97 @@
+/*
+ * Botendo
+ * Copyright (C) 2023 limited_dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package de.limited_dev.botendo.commands.slash.music
+
+import de.limited_dev.botendo.Bot
+import de.limited_dev.botendo.commands.slash.component.SlashCommand
+import de.limited_dev.botendo.commands.slash.component.options.CommandOption
+import de.limited_dev.botendo.commands.slash.music.component.MusicManager
+import de.limited_dev.botendo.util.MessageUtil
+import de.limited_dev.botendo.util.TimeUtil
+import de.limited_dev.botendo.util.UrlUtil
+import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import dev.schlaubi.lavakord.audio.Link
+
+class SkipCommand : SlashCommand("skip", "Skip to the next song in queue", null) {
+ override suspend fun onSlashCommand(
+ interaction: GuildChatInputCommandInteraction,
+ response: DeferredPublicMessageInteractionResponseBehavior,
+ args: Map
+ ) {
+ val guildId = interaction.guildId
+ val link = Bot.lava.getLink(guildId.toString())
+ val player = link.player
+ val voiceState = interaction.user.getVoiceStateOrNull()
+ if (voiceState == null) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "You are not connected to a VC",
+ "Please connect to a VC"
+ )
+ return
+ }
+
+ val channelId = voiceState.channelId
+ if (link.state != Link.State.CONNECTED) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Not connected",
+ "I'm not in a VC and therefore, I am not playing anything."
+ )
+ return
+ } else if (link.state == Link.State.CONNECTED && link.lastChannelId != channelId!!.value) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "You are not in my VC",
+ "We are not in the same VC and therefore, you cannot control the music"
+ )
+ return
+ }
+ var track = player.playingTrack
+ if (track == null) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Not playing",
+ "I'm not playing anything currently"
+ )
+ return
+ }
+ val gts = MusicManager.getGuildTrackScheduler(interaction.guild.asGuild(), player)
+ track = gts.getHead().toTrack()
+ gts.playNext()
+ MessageUtil.sendEmbedForInteractionWithImage(
+ interaction,
+ response,
+ "Skipped song; now playing",
+ "**${track.title}**\n*Now Playing*\nby ${track.author} :: ${
+ TimeUtil.getTimeFormatedRaw(
+ track.length.inWholeMilliseconds
+ )
+ }\n" +
+ ">>>${track.uri}",
+ "https://img.youtube.com/vi/" + UrlUtil.getYtThumbnailUrl(track.uri!!) + "/maxresdefault.jpg"
+ )
+ }
+}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/StopCommand.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/StopCommand.kt
index 8bc0933..785a12f 100644
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/StopCommand.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/StopCommand.kt
@@ -22,9 +22,11 @@ package de.limited_dev.botendo.commands.slash.music
import de.limited_dev.botendo.Bot
import de.limited_dev.botendo.commands.slash.component.SlashCommand
import de.limited_dev.botendo.commands.slash.component.options.CommandOption
+import de.limited_dev.botendo.commands.slash.music.component.MusicManager
import de.limited_dev.botendo.util.MessageUtil
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import dev.schlaubi.lavakord.audio.Link
class StopCommand : SlashCommand("stop", "Stop playing and start leavin'", null) {
override suspend fun onSlashCommand(
@@ -35,8 +37,39 @@ class StopCommand : SlashCommand("stop", "Stop playing and start leavin'", null)
val guildId = interaction.guildId
val link = Bot.lava.getLink(guildId.toString())
val player = link.player
+
+ val voiceState = interaction.user.getVoiceStateOrNull()
+ if (voiceState == null) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "You are not connected to a VC",
+ "Please connect to my VC"
+ )
+ return
+ }
+
+ val channelId = voiceState.channelId
+ if (link.state != Link.State.CONNECTED) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "Not connected",
+ "I'm not in a VC and therefore, I am not playing anything."
+ )
+ return
+ } else if (link.state == Link.State.CONNECTED && link.lastChannelId != channelId!!.value) {
+ MessageUtil.sendEmbedForInteraction(
+ interaction,
+ response,
+ "You are not in my VC",
+ "We are not in the same VC"
+ )
+ return
+ }
player.stopTrack()
link.destroy()
+ MusicManager.getGuildTrackScheduler(interaction.getGuild(), player).clear()
MessageUtil.sendEmbedForInteraction(interaction, response, "I stopped and left", "just like your girlfriend")
}
}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/GuildTrackScheduler.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/GuildTrackScheduler.kt
new file mode 100644
index 0000000..c9ca8e9
--- /dev/null
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/GuildTrackScheduler.kt
@@ -0,0 +1,112 @@
+/*
+ * Botendo
+ * Copyright (C) 2023 limited_dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package de.limited_dev.botendo.commands.slash.music.component
+
+import de.limited_dev.botendo.util.Logger
+import dev.schlaubi.lavakord.audio.TrackEndEvent
+import dev.schlaubi.lavakord.audio.TrackExceptionEvent
+import dev.schlaubi.lavakord.audio.TrackStuckEvent
+import dev.schlaubi.lavakord.audio.on
+import dev.schlaubi.lavakord.audio.player.Player
+import dev.schlaubi.lavakord.rest.TrackResponse
+import java.util.concurrent.BlockingQueue
+import java.util.concurrent.LinkedBlockingQueue
+
+class GuildTrackScheduler(val pl: Player) {
+
+ private var queue: BlockingQueue = LinkedBlockingQueue()
+ var repeating = false
+ private var hasRegisteredEvents = false
+
+ ///Add a track to queue and start playing, if there is no song currently playing
+ suspend fun queue(track: TrackResponse.PartialTrack) {
+ if (this.pl.playingTrack == null) {
+ play(track)
+ } else {
+ queue.offer(track)
+ }
+ }
+
+ suspend fun playNext() {
+ if (!queue.isEmpty())
+ this.pl.playTrack(queue.poll())
+ else
+ this.pl.stopTrack()
+ }
+
+ private suspend fun play(tr: TrackResponse.PartialTrack) {
+ this.pl.playTrack(tr)
+ }
+
+ fun addEvents() {
+ if (hasRegisteredEvents)
+ return
+ hasRegisteredEvents = true
+
+ Logger.out("Adding track events to GuildTrackScheduler...")
+
+ pl.on {
+ onTrackEnd(this)
+ }
+
+ pl.on {
+ onTrackStuck(this)
+ }
+
+ pl.on {
+ onTrackExc(this)
+ }
+ }
+
+ private suspend fun onTrackEnd(e: TrackEndEvent) {
+ if (e.reason.mayStartNext) {
+ if (repeating) {
+ this.pl.playTrack(e.track.copy())
+ return
+ }
+ Logger.out("Track has ended; Playing next...")
+ playNext()
+ }
+ }
+
+ private suspend fun onTrackStuck(e: TrackStuckEvent) {
+ Logger.out("Track is stuck, retrying...")
+ this.pl.playTrack(e.track.copy())
+ }
+
+ private suspend fun onTrackExc(e: TrackExceptionEvent) {
+ Logger.out("Track had an exception, retrying...")
+ this.pl.playTrack(e.track.copy())
+ }
+
+ fun clear() {
+ Logger.out("Clearing queue...")
+ repeating = false
+ queue.clear()
+ }
+
+ fun getQueue(): List {
+ return queue.toList()
+ }
+
+ fun getHead(): TrackResponse.PartialTrack {
+ return queue.first()
+ }
+}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/MusicManager.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/MusicManager.kt
index 25d0efa..8fc4867 100644
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/MusicManager.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/MusicManager.kt
@@ -21,21 +21,26 @@ package de.limited_dev.botendo.commands.slash.music.component
import de.limited_dev.botendo.util.MessageUtil
import de.limited_dev.botendo.util.TimeUtil
+import de.limited_dev.botendo.util.UrlUtil
+import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
+import dev.kord.core.entity.Guild
import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
import dev.schlaubi.lavakord.audio.Link
-import dev.schlaubi.lavakord.audio.TrackEndEvent
-import dev.schlaubi.lavakord.audio.on
+import dev.schlaubi.lavakord.audio.player.Player
import dev.schlaubi.lavakord.rest.TrackResponse
import dev.schlaubi.lavakord.rest.loadItem
object MusicManager {
- private var musicManagerMap: MutableMap? = null
+ private var musicManagerMap: MutableMap = mutableMapOf()
- init {
- musicManagerMap = HashMap()
+ fun getGuildTrackScheduler(guild: Guild, player: Player): GuildTrackScheduler {
+ return musicManagerMap.computeIfAbsent(guild.id) {
+ GuildTrackScheduler(player)
+ }
}
+
suspend fun addToQueue(
interaction: GuildChatInputCommandInteraction,
response: DeferredPublicMessageInteractionResponseBehavior,
@@ -54,60 +59,68 @@ object MusicManager {
) {
val player = link.player
val item = link.loadItem(search)
+ val gts = getGuildTrackScheduler(interaction.getGuild(), player)
- player.on {
-
- }
+ gts.addEvents()
when (item.loadType) {
TrackResponse.LoadType.TRACK_LOADED -> {
- player.playTrack(item.track)
+ gts.queue(item.track)
if (!silent)
- MessageUtil.sendEmbedForInteraction(
- interaction, response,
- "Playing track from link",
+ MessageUtil.sendEmbedForInteractionWithImage(
+ interaction,
+ response,
+ "Queuing track from link",
"**${item.track.info.title}**\n*Queue*\nby ${item.track.info.author} :: ${
TimeUtil.getTimeFormatedRaw(
item.track.info.length
)
- }"
+ }\n" +
+ ">>>${item.track.info.uri}",
+ "https://img.youtube.com/vi/" + UrlUtil.getYtThumbnailUrl(item.track.info.uri) + "/maxresdefault.jpg"
)
}
TrackResponse.LoadType.PLAYLIST_LOADED -> {
- val l = item.tracks.asReversed()
- for (p in l) {
- player.playTrack(p)
+ val l = item.tracks.reversed()
+ for (partialTrack in l) {
+ gts.queue(partialTrack)
}
if (!silent)
- MessageUtil.sendEmbedForInteraction(
- interaction, response,
- "Playing playlist from link",
+ MessageUtil.sendEmbedForInteractionWithImage(
+ interaction,
+ response,
+ "Queuing playlist from link",
"**${item.tracks.first().info.title}**\n*${item.playlistInfo.name}*\nby ${item.tracks.first().info.author} :: ${
TimeUtil.getTimeFormatedRaw(
item.tracks.first().info.length
)
- }"
+ }\n" +
+ ">>>${item.tracks.first().info.uri}",
+ "https://img.youtube.com/vi/" + UrlUtil.getYtThumbnailUrl(item.tracks.first().info.uri) + "/maxresdefault.jpg"
)
}
TrackResponse.LoadType.SEARCH_RESULT -> {
- player.playTrack(item.tracks.first())
+ gts.queue(item.tracks.first())
if (!silent)
- MessageUtil.sendEmbedForInteraction(
- interaction, response,
- "Playing from query",
+ MessageUtil.sendEmbedForInteractionWithImage(
+ interaction,
+ response,
+ "Queuing track from query",
"**${item.tracks.first().info.title}**\n*Queue*\nby ${item.tracks.first().info.author} :: ${
TimeUtil.getTimeFormatedRaw(
item.tracks.first().info.length
)
- }"
+ }\n" +
+ ">>>${item.tracks.first().info.uri}",
+ "https://img.youtube.com/vi/" + UrlUtil.getYtThumbnailUrl(item.tracks.first().info.uri) + "/maxresdefault.jpg"
)
}
TrackResponse.LoadType.NO_MATCHES -> {
if (!silent)
- MessageUtil.sendEmbedForInteraction(interaction, response, "Not found", "There were not matches.")
+ MessageUtil.sendEmbedForInteraction(interaction, response, "Not found", "There were no matches.")
}
TrackResponse.LoadType.LOAD_FAILED -> {
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/TrackScheduler.kt b/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/TrackScheduler.kt
deleted file mode 100644
index 1e07d70..0000000
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/TrackScheduler.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Botendo
- * Copyright (C) 2023 limited_dev
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package de.limited_dev.botendo.commands.slash.music.component
-
-class TrackScheduler {
-}
diff --git a/src/main/kotlin/de/limited_dev/botendo/util/TokenManager.kt b/src/main/kotlin/de/limited_dev/botendo/util/CredentialManager.kt
similarity index 78%
rename from src/main/kotlin/de/limited_dev/botendo/util/TokenManager.kt
rename to src/main/kotlin/de/limited_dev/botendo/util/CredentialManager.kt
index 0763a4a..3ee45c8 100644
--- a/src/main/kotlin/de/limited_dev/botendo/util/TokenManager.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/util/CredentialManager.kt
@@ -22,11 +22,13 @@ package de.limited_dev.botendo.util
import java.io.*
import java.util.*
-object TokenManager {
- private const val filename = "token.nils"
+object CredentialManager {
+ private const val filename = "credentials.nils"
var token: String = "empty"
+ var linkip: String = "empty"
+ var linkpw: String = "empty"
- ///Load the bot token, generate a config if there is none
+ ///Load the needed credentials, generate a config if there is none
fun load() {
val configFile = File(filename)
if (!configFile.exists()) {
@@ -38,6 +40,8 @@ object TokenManager {
val prop = Properties()
prop.load(input)
token = if (prop.getProperty("token").equals("empty")) "empty" else prop.getProperty("token")
+ linkip = if (prop.getProperty("lavaip").equals("empty")) "empty" else prop.getProperty("lavaip")
+ linkpw = if (prop.getProperty("lavapw").equals("empty")) "empty" else prop.getProperty("lavapw")
input.close()
} catch (e: IOException) {
e.printStackTrace()
@@ -58,6 +62,8 @@ object TokenManager {
val output: OutputStream = FileOutputStream(filename)
val prop = Properties()
prop.setProperty("token", "empty")
+ prop.setProperty("lavaip", "empty")
+ prop.setProperty("lavapw", "empty")
prop.store(output, null)
output.close()
} catch (e: IOException) {
diff --git a/src/main/kotlin/de/limited_dev/botendo/util/MessageUtil.kt b/src/main/kotlin/de/limited_dev/botendo/util/MessageUtil.kt
index 92313f6..d2a00e5 100644
--- a/src/main/kotlin/de/limited_dev/botendo/util/MessageUtil.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/util/MessageUtil.kt
@@ -65,7 +65,7 @@ object MessageUtil {
}
}
- fun getEmbed(title: String, description: String, source: String): EmbedBuilder {
+ private fun getEmbed(title: String, description: String, source: String): EmbedBuilder {
val now: LocalDateTime = LocalDateTime.now()
val ebb = EmbedBuilder()
ebb.title = title
@@ -75,7 +75,12 @@ object MessageUtil {
return ebb
}
- fun getEmbedWithImage(title: String, description: String, source: String, thumbnailUrl: String): EmbedBuilder {
+ private fun getEmbedWithImage(
+ title: String,
+ description: String,
+ source: String,
+ thumbnailUrl: String
+ ): EmbedBuilder {
val ebb = getEmbed(title, description, source)
ebb.thumbnail = EmbedBuilder.Thumbnail()
ebb.thumbnail!!.url = thumbnailUrl
diff --git a/src/main/kotlin/de/limited_dev/botendo/util/Status.kt b/src/main/kotlin/de/limited_dev/botendo/util/Status.kt
index 23203bf..98972b7 100644
--- a/src/main/kotlin/de/limited_dev/botendo/util/Status.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/util/Status.kt
@@ -22,8 +22,9 @@ package de.limited_dev.botendo.util
import de.limited_dev.botendo.Bot
object Status {
+ //TODO: impl.
suspend fun updateStatus() {
- Bot.kord!!.editPresence {
+ Bot.kord.editPresence {
this.playing("")
}
}
diff --git a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/GuildMusicManager.kt b/src/main/kotlin/de/limited_dev/botendo/util/UrlUtil.kt
similarity index 79%
rename from src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/GuildMusicManager.kt
rename to src/main/kotlin/de/limited_dev/botendo/util/UrlUtil.kt
index 24695e5..14e95a6 100644
--- a/src/main/kotlin/de/limited_dev/botendo/commands/slash/music/component/GuildMusicManager.kt
+++ b/src/main/kotlin/de/limited_dev/botendo/util/UrlUtil.kt
@@ -17,7 +17,10 @@
*
*/
-package de.limited_dev.botendo.commands.slash.music.component
+package de.limited_dev.botendo.util
-class GuildMusicManager {
+object UrlUtil {
+ fun getYtThumbnailUrl(uri: String): String {
+ return uri.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]
+ }
}