feat: added basic music controls, added music, added gitlab-ci.yml updated README.md

This commit is contained in:
limited_dev 2023-03-28 23:34:10 +02:00
parent a9c4fe5da3
commit 8280620091
18 changed files with 553 additions and 73 deletions

34
.gitlab-ci.yml Normal file
View file

@ -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

View file

@ -1,2 +1,46 @@
# Botendo # Botendo version 5
"5th times the charm" ~ me
A Discord music bot, written in Kotlin using the kord library. A Discord music bot, written in Kotlin using the kord library.
<div class="aside">
<img src="https://img.shields.io/badge/100%25-Selfmade-success" alt="100% Selfmade"/>
<img src="https://img.shields.io/badge/0%25-optimized-orange" alt="0% optimized"/>
<img src="https://img.shields.io/badge/fuck%20it-ship%20it-orange" alt="fuck it, ship it"/>
</div>
## Contributors
<div class="aside">
<img src="https://img.shields.io/badge/limited__dev-Owner%20%26%20Developer-blue" alt="limited_dev: Owner & Developer"/>
</div>
## 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.

View file

@ -34,6 +34,7 @@ group = "de.limited_dev.botendo"
version = System.getenv("CI_COMMIT_TAG")?.let { "$it-${System.getenv("CI_COMMIT_SHORT_SHA")}-prod" } version = System.getenv("CI_COMMIT_TAG")?.let { "$it-${System.getenv("CI_COMMIT_SHORT_SHA")}-prod" }
?: System.getenv("CI_COMMIT_SHORT_SHA")?.let { "$it-dev" } ?: System.getenv("CI_COMMIT_SHORT_SHA")?.let { "$it-dev" }
?: "DevelopmentBuild" ?: "DevelopmentBuild"
val kordver = "0.8.1" val kordver = "0.8.1"
val lavaver = "3.8.0" val lavaver = "3.8.0"
val coroutinesver = "1.1.0" val coroutinesver = "1.1.0"

View file

@ -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.SlashCommandManager
import de.limited_dev.botendo.commands.slash.component.options.CommandOption 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.component.options.OptionType
import de.limited_dev.botendo.util.CredentialManager
import de.limited_dev.botendo.util.Logger import de.limited_dev.botendo.util.Logger
import de.limited_dev.botendo.util.MessageUtil import de.limited_dev.botendo.util.MessageUtil
import de.limited_dev.botendo.util.TokenManager
import dev.kord.core.Kord import dev.kord.core.Kord
import dev.kord.core.event.interaction.GuildChatInputCommandInteractionCreateEvent import dev.kord.core.event.interaction.GuildChatInputCommandInteractionCreateEvent
import dev.kord.core.on import dev.kord.core.on
@ -44,10 +44,10 @@ object Bot {
Logger.out("Starting Bot...") Logger.out("Starting Bot...")
//Load config //Load config
TokenManager.load() CredentialManager.load()
//Don't run the bot when there is no bot token in config //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") Logger.out("The config does not contain a bot token")
return return
} }
@ -56,13 +56,13 @@ object Bot {
SlashCommandManager.register() SlashCommandManager.register()
//Add Bot token to kord //Add Bot token to kord
kord = Kord(TokenManager.token) kord = Kord(CredentialManager.token)
//Register Command Listener //Register Command Listener
kord.on<GuildChatInputCommandInteractionCreateEvent> { kord.on<GuildChatInputCommandInteractionCreateEvent> {
val response = interaction.deferPublicResponse() val response = interaction.deferPublicResponse()
val command = interaction.command 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) { for (c in SlashCommandManager.commands) {
if (c.name != command.rootName) if (c.name != command.rootName)
continue continue
@ -128,10 +128,8 @@ object Bot {
//Add LavaLink //Add LavaLink
lava = kord.lavakord() lava = kord.lavakord()
//Add the LavaLink node //Add the LavaLink node from config
lava.addNode("ws://192.168.178.2:2333", "youshallnotpass") lava.addNode(CredentialManager.linkip, CredentialManager.linkpw)
Logger.out("Logging in") Logger.out("Logging in")

View file

@ -19,8 +19,7 @@
package de.limited_dev.botendo.commands.slash.component 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.*
import de.limited_dev.botendo.commands.slash.music.StopCommand
import de.limited_dev.botendo.commands.slash.util.InfoCommand import de.limited_dev.botendo.commands.slash.util.InfoCommand
object SlashCommandManager { object SlashCommandManager {
@ -31,5 +30,8 @@ object SlashCommandManager {
commands.add(InfoCommand()) commands.add(InfoCommand())
commands.add(PlayCommand()) commands.add(PlayCommand())
commands.add(StopCommand()) commands.add(StopCommand())
commands.add(NowPlayingCommand())
commands.add(QueueCommand())
commands.add(SkipCommand())
} }
} }

View file

@ -19,5 +19,4 @@
package de.limited_dev.botendo.commands.slash.component.options 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)
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
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<CommandOption, String>
) {
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"
)
}
}

View file

@ -19,10 +19,11 @@
package de.limited_dev.botendo.commands.slash.music 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.SlashCommand
import de.limited_dev.botendo.commands.slash.component.options.CommandOption 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.component.options.OptionType
import de.limited_dev.botendo.commands.slash.music.component.MusicManager
import de.limited_dev.botendo.util.MessageUtil import de.limited_dev.botendo.util.MessageUtil
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
@ -39,7 +40,7 @@ class PlayCommand : SlashCommand(
args: Map<CommandOption, String> args: Map<CommandOption, String>
) { ) {
val guildId = interaction.guildId val guildId = interaction.guildId
val link = Bot.lava.getLink(guildId.toString()) val link = lava.getLink(guildId.toString())
val voiceState = interaction.user.getVoiceStateOrNull() val voiceState = interaction.user.getVoiceStateOrNull()
if (voiceState == null) { if (voiceState == null) {
@ -56,6 +57,14 @@ class PlayCommand : SlashCommand(
if (link.state != Link.State.CONNECTED) { if (link.state != Link.State.CONNECTED) {
link.connectAudio(channelId!!.value) 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]] val query = args[this.options!![0]]
@ -64,5 +73,7 @@ class PlayCommand : SlashCommand(
} else { } else {
"ytsearch:$query" "ytsearch:$query"
} }
MusicManager.addToQueue(interaction, response, link, search)
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
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<CommandOption, String>
) {
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)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
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<CommandOption, String>
) {
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"
)
}
}

View file

@ -22,9 +22,11 @@ package de.limited_dev.botendo.commands.slash.music
import de.limited_dev.botendo.Bot import de.limited_dev.botendo.Bot
import de.limited_dev.botendo.commands.slash.component.SlashCommand 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.CommandOption
import de.limited_dev.botendo.commands.slash.music.component.MusicManager
import de.limited_dev.botendo.util.MessageUtil import de.limited_dev.botendo.util.MessageUtil
import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
import dev.schlaubi.lavakord.audio.Link
class StopCommand : SlashCommand("stop", "Stop playing and start leavin'", null) { class StopCommand : SlashCommand("stop", "Stop playing and start leavin'", null) {
override suspend fun onSlashCommand( override suspend fun onSlashCommand(
@ -35,8 +37,39 @@ class StopCommand : SlashCommand("stop", "Stop playing and start leavin'", null)
val guildId = interaction.guildId val guildId = interaction.guildId
val link = Bot.lava.getLink(guildId.toString()) val link = Bot.lava.getLink(guildId.toString())
val player = link.player 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() player.stopTrack()
link.destroy() link.destroy()
MusicManager.getGuildTrackScheduler(interaction.getGuild(), player).clear()
MessageUtil.sendEmbedForInteraction(interaction, response, "I stopped and left", "just like your girlfriend") MessageUtil.sendEmbedForInteraction(interaction, response, "I stopped and left", "just like your girlfriend")
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
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<TrackResponse.PartialTrack> = 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<TrackEndEvent> {
onTrackEnd(this)
}
pl.on<TrackStuckEvent> {
onTrackStuck(this)
}
pl.on<TrackExceptionEvent> {
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<TrackResponse.PartialTrack> {
return queue.toList()
}
fun getHead(): TrackResponse.PartialTrack {
return queue.first()
}
}

View file

@ -21,20 +21,25 @@ package de.limited_dev.botendo.commands.slash.music.component
import de.limited_dev.botendo.util.MessageUtil import de.limited_dev.botendo.util.MessageUtil
import de.limited_dev.botendo.util.TimeUtil 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.behavior.interaction.response.DeferredPublicMessageInteractionResponseBehavior
import dev.kord.core.entity.Guild
import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
import dev.schlaubi.lavakord.audio.Link import dev.schlaubi.lavakord.audio.Link
import dev.schlaubi.lavakord.audio.TrackEndEvent import dev.schlaubi.lavakord.audio.player.Player
import dev.schlaubi.lavakord.audio.on
import dev.schlaubi.lavakord.rest.TrackResponse import dev.schlaubi.lavakord.rest.TrackResponse
import dev.schlaubi.lavakord.rest.loadItem import dev.schlaubi.lavakord.rest.loadItem
object MusicManager { object MusicManager {
private var musicManagerMap: MutableMap<Long, GuildMusicManager>? = null private var musicManagerMap: MutableMap<Snowflake, GuildTrackScheduler> = mutableMapOf()
init { fun getGuildTrackScheduler(guild: Guild, player: Player): GuildTrackScheduler {
musicManagerMap = HashMap() return musicManagerMap.computeIfAbsent(guild.id) {
GuildTrackScheduler(player)
} }
}
suspend fun addToQueue( suspend fun addToQueue(
interaction: GuildChatInputCommandInteraction, interaction: GuildChatInputCommandInteraction,
@ -54,60 +59,68 @@ object MusicManager {
) { ) {
val player = link.player val player = link.player
val item = link.loadItem(search) val item = link.loadItem(search)
val gts = getGuildTrackScheduler(interaction.getGuild(), player)
player.on<TrackEndEvent> { gts.addEvents()
}
when (item.loadType) { when (item.loadType) {
TrackResponse.LoadType.TRACK_LOADED -> { TrackResponse.LoadType.TRACK_LOADED -> {
player.playTrack(item.track) gts.queue(item.track)
if (!silent) if (!silent)
MessageUtil.sendEmbedForInteraction( MessageUtil.sendEmbedForInteractionWithImage(
interaction, response, interaction,
"Playing track from link", response,
"Queuing track from link",
"**${item.track.info.title}**\n*Queue*\nby ${item.track.info.author} :: ${ "**${item.track.info.title}**\n*Queue*\nby ${item.track.info.author} :: ${
TimeUtil.getTimeFormatedRaw( TimeUtil.getTimeFormatedRaw(
item.track.info.length 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 -> { TrackResponse.LoadType.PLAYLIST_LOADED -> {
val l = item.tracks.asReversed() val l = item.tracks.reversed()
for (p in l) { for (partialTrack in l) {
player.playTrack(p) gts.queue(partialTrack)
} }
if (!silent) if (!silent)
MessageUtil.sendEmbedForInteraction( MessageUtil.sendEmbedForInteractionWithImage(
interaction, response, interaction,
"Playing playlist from link", response,
"Queuing playlist from link",
"**${item.tracks.first().info.title}**\n*${item.playlistInfo.name}*\nby ${item.tracks.first().info.author} :: ${ "**${item.tracks.first().info.title}**\n*${item.playlistInfo.name}*\nby ${item.tracks.first().info.author} :: ${
TimeUtil.getTimeFormatedRaw( TimeUtil.getTimeFormatedRaw(
item.tracks.first().info.length 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 -> { TrackResponse.LoadType.SEARCH_RESULT -> {
player.playTrack(item.tracks.first()) gts.queue(item.tracks.first())
if (!silent) if (!silent)
MessageUtil.sendEmbedForInteraction( MessageUtil.sendEmbedForInteractionWithImage(
interaction, response, interaction,
"Playing from query", response,
"Queuing track from query",
"**${item.tracks.first().info.title}**\n*Queue*\nby ${item.tracks.first().info.author} :: ${ "**${item.tracks.first().info.title}**\n*Queue*\nby ${item.tracks.first().info.author} :: ${
TimeUtil.getTimeFormatedRaw( TimeUtil.getTimeFormatedRaw(
item.tracks.first().info.length 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 -> { TrackResponse.LoadType.NO_MATCHES -> {
if (!silent) 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 -> { TrackResponse.LoadType.LOAD_FAILED -> {

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
package de.limited_dev.botendo.commands.slash.music.component
class TrackScheduler {
}

View file

@ -22,11 +22,13 @@ package de.limited_dev.botendo.util
import java.io.* import java.io.*
import java.util.* import java.util.*
object TokenManager { object CredentialManager {
private const val filename = "token.nils" private const val filename = "credentials.nils"
var token: String = "empty" 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() { fun load() {
val configFile = File(filename) val configFile = File(filename)
if (!configFile.exists()) { if (!configFile.exists()) {
@ -38,6 +40,8 @@ object TokenManager {
val prop = Properties() val prop = Properties()
prop.load(input) prop.load(input)
token = if (prop.getProperty("token").equals("empty")) "empty" else prop.getProperty("token") 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() input.close()
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
@ -58,6 +62,8 @@ object TokenManager {
val output: OutputStream = FileOutputStream(filename) val output: OutputStream = FileOutputStream(filename)
val prop = Properties() val prop = Properties()
prop.setProperty("token", "empty") prop.setProperty("token", "empty")
prop.setProperty("lavaip", "empty")
prop.setProperty("lavapw", "empty")
prop.store(output, null) prop.store(output, null)
output.close() output.close()
} catch (e: IOException) { } catch (e: IOException) {

View file

@ -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 now: LocalDateTime = LocalDateTime.now()
val ebb = EmbedBuilder() val ebb = EmbedBuilder()
ebb.title = title ebb.title = title
@ -75,7 +75,12 @@ object MessageUtil {
return ebb 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) val ebb = getEmbed(title, description, source)
ebb.thumbnail = EmbedBuilder.Thumbnail() ebb.thumbnail = EmbedBuilder.Thumbnail()
ebb.thumbnail!!.url = thumbnailUrl ebb.thumbnail!!.url = thumbnailUrl

View file

@ -22,8 +22,9 @@ package de.limited_dev.botendo.util
import de.limited_dev.botendo.Bot import de.limited_dev.botendo.Bot
object Status { object Status {
//TODO: impl.
suspend fun updateStatus() { suspend fun updateStatus() {
Bot.kord!!.editPresence { Bot.kord.editPresence {
this.playing("") this.playing("")
} }
} }

View file

@ -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]
}
} }