diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 53bf3196..6d0ee1c2 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5ef43e0e..f4f463fc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,13 +1,13 @@
[versions]
-kotlin = "2.0.0-RC2"
+kotlin = "2.0.0"
kordex = "1.8.0-SNAPSHOT"
kmongo = "4.11.0"
-coroutines = "1.8.0"
+coroutines = "1.8.1"
serialization = "1.6.3"
-ktor = "2.3.10"
-kord = "0.13.1"
-api = "3.31.0"
-ksp = "2.0.0-RC2-1.0.20"
+ktor = "2.3.11"
+kord = "0.14.0-SNAPSHOT"
+api = "3.32.0"
+ksp = "2.0.0-1.0.21"
lavakord = "6.2.0"
[libraries]
diff --git a/music/build.gradle.kts b/music/build.gradle.kts
index 89427517..068c9bae 100644
--- a/music/build.gradle.kts
+++ b/music/build.gradle.kts
@@ -1,3 +1,3 @@
subprojects {
- version = "3.6.1-SNAPSHOT"
+ version = "3.7.0-SNAPSHOT"
}
diff --git a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/context/PlayMessageAction.kt b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/context/PlayMessageAction.kt
index f0e40c14..fca1c625 100644
--- a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/context/PlayMessageAction.kt
+++ b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/context/PlayMessageAction.kt
@@ -31,6 +31,10 @@ class PlayMessageActionArguments(override val query: String) : QueueOptions {
override val force: Boolean = false
override val top: Boolean = false
override val searchProvider: QueueOptions.SearchProvider? = null
+ override val shuffle: Boolean? = null
+ override val loop: Boolean? = null
+ override val loopQueue: Boolean? = null
+
}
private suspend fun EphemeralMessageCommandContext<*>.queue(
diff --git a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/AddCommand.kt b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/AddCommand.kt
index 12b41119..424be9bc 100644
--- a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/AddCommand.kt
+++ b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/AddCommand.kt
@@ -23,6 +23,10 @@ class PlaylistAddArguments : PlaylistArguments(), QueueOptions {
}
override val top: Boolean = false
override val force: Boolean = false
+ override val shuffle: Boolean? = null
+ override val loop: Boolean? = null
+ override val loopQueue: Boolean? = null
+
}
fun PlaylistModule.addCommand() = ephemeralSubCommand(::PlaylistAddArguments) {
diff --git a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/Common.kt b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/Common.kt
index 86487958..44e1d705 100644
--- a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/Common.kt
+++ b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/Common.kt
@@ -21,49 +21,57 @@ import org.litote.kmongo.eq
import org.litote.kmongo.or
import org.litote.kmongo.util.KMongoUtil
-abstract class PlaylistArguments(val onlyMine:Boolean = true) : Arguments() {
- val name by string {
- name = "name"
- description = "The name of the playlist"
+interface PlaylistOptions {
+ val name: String
+}
- validate {
- getPlaylistOrNull(context.getUser()!!, value) ?: context.notFound(value)
- }
+abstract class PlaylistArguments(onlyMine: Boolean = true) : Arguments(), PlaylistOptions {
+ override val name by playlistName(onlyMine)
+}
- autoComplete {
- val genericFilter = if (onlyMine) {
- Playlist::authorId eq user.id
- } else {
- or(Playlist::public eq true, Playlist::authorId eq user.id)
- }
- val input = focusedOption.value
- val names = PlaylistDatabase.collection.find(
- and(genericFilter,
- KMongoUtil.toBson("{name: /$input/i}"))
- ).toFlow()
- .take(25)
- .toList()
- suggestString {
- names.forEach { choice(it.name, it.name) }
- }
- }
- }
+fun Arguments.playlistName(onlyMine: Boolean) = string {
+ name = "name"
+ description = "The name of the playlist"
- private suspend fun CommandContext.notFound(value: String): Nothing {
- throw DiscordRelayedException(translate("command.playlist.unknown_playlist", arrayOf(value)))
+ validate {
+ getPlaylistOrNull(context.getUser()!!, value) ?: context.notFound(value)
}
- suspend fun getPlaylistOrNull(userBehavior: UserBehavior, name: String) =
- PlaylistDatabase.collection.findOne(
+ autoComplete {
+ val genericFilter = if (onlyMine) {
+ Playlist::authorId eq user.id
+ } else {
+ or(Playlist::public eq true, Playlist::authorId eq user.id)
+ }
+ val input = focusedOption.value
+ val names = PlaylistDatabase.collection.find(
and(
- Playlist::name eq name,
- or(Playlist::public eq true, Playlist::authorId eq userBehavior.id)
+ genericFilter,
+ KMongoUtil.toBson("{name: /$input/i}")
)
- )
+ ).toFlow()
+ .take(25)
+ .toList()
+ suggestString {
+ names.forEach { choice(it.name, it.name) }
+ }
+ }
}
-suspend fun EphemeralSlashCommandContext.getPlaylist() =
- arguments.getPlaylistOrNull(user, arguments.name) ?: error("Could not load playlist")
+private suspend fun CommandContext.notFound(value: String): Nothing {
+ throw DiscordRelayedException(translate("command.playlist.unknown_playlist", arrayOf(value)))
+}
+
+private suspend fun getPlaylistOrNull(userBehavior: UserBehavior, name: String) =
+ PlaylistDatabase.collection.findOne(
+ and(
+ Playlist::name eq name,
+ or(Playlist::public eq true, Playlist::authorId eq userBehavior.id)
+ )
+ )
+suspend fun EphemeralSlashCommandContext.getPlaylist()
+ where T : Arguments, T : PlaylistOptions =
+ getPlaylistOrNull(user, arguments.name) ?: error("Could not load playlist")
class PlaylistModule(context: PluginContext) : SubCommandModule(context) {
diff --git a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/LoadCommand.kt b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/LoadCommand.kt
index 20b16e89..5bba677b 100644
--- a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/LoadCommand.kt
+++ b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/LoadCommand.kt
@@ -1,10 +1,13 @@
package dev.schlaubi.mikmusic.playlist.commands
import dev.schlaubi.mikmusic.checks.joinSameChannelCheck
+import dev.schlaubi.mikmusic.player.queue.SchedulingArguments
import dev.schlaubi.mikmusic.playlist.PlaylistDatabase
import dev.schlaubi.mikmusic.util.mapToQueuedTrack
-class LoadArguments : PlaylistArguments(onlyMine = false)
+class LoadArguments : SchedulingArguments(), PlaylistOptions {
+ override val name by playlistName(onlyMine = false)
+}
fun PlaylistModule.loadCommand() = ephemeralSubCommand(::LoadArguments) {
name = "load"
@@ -20,7 +23,8 @@ fun PlaylistModule.loadCommand() = ephemeralSubCommand(::LoadArguments) {
musicPlayer.queueTrack(
force = false, onTop = false,
- tracks = playlist.getTracks(musicPlayer.node).mapToQueuedTrack(user)
+ tracks = playlist.getTracks(musicPlayer.node).mapToQueuedTrack(user),
+ schedulingOptions = arguments
)
respond {
diff --git a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/SaveCommand.kt b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/SaveCommand.kt
index bd3b5b4a..7595848d 100644
--- a/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/SaveCommand.kt
+++ b/music/commands/src/main/kotlin/dev/schlaubi/mikmusic/playlist/commands/SaveCommand.kt
@@ -31,6 +31,9 @@ class PlaylistSaveArguments : Arguments(), QueueOptions {
override val force: Boolean = false
override val top: Boolean = false
override val searchProvider: QueueOptions.SearchProvider? = null
+ override val shuffle: Boolean? = null
+ override val loop: Boolean? = null
+ override val loopQueue: Boolean? = null
}
fun PlaylistModule.saveCommand() = ephemeralSubCommand(::PlaylistSaveArguments) {
diff --git a/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/MusicPlayer.kt b/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/MusicPlayer.kt
index c4b68743..182826cb 100644
--- a/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/MusicPlayer.kt
+++ b/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/MusicPlayer.kt
@@ -27,6 +27,7 @@ import dev.schlaubi.lavakord.rest.getPlayer
import dev.schlaubi.lavakord.rest.updatePlayer
import dev.schlaubi.mikmusic.core.settings.MusicSettingsDatabase
import dev.schlaubi.mikmusic.musicchannel.updateMessage
+import dev.schlaubi.mikmusic.player.queue.SchedulingOptions
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -147,11 +148,16 @@ class MusicPlayer(val link: Link, private val guild: GuildBehavior) : Link by li
onTop: Boolean,
tracks: Collection,
position: Duration? = null,
+ schedulingOptions: SchedulingOptions? = null
) = lock.withLock {
val isFirst = nextSongIsFirst
require(isFirst || position == null) { "Can only specify position if nextSong is first" }
queue.addTracks(tracks, onTop || force)
+ queue.shuffle = schedulingOptions?.shuffle ?: queue.shuffle
+ loopQueue = schedulingOptions?.loopQueue ?: loopQueue
+ repeat = schedulingOptions?.loop ?: repeat
+
if ((force || isFirst) && !dontQueue) {
startNextSong(position = position)
waitForPlayerUpdate()
@@ -310,14 +316,14 @@ class MusicPlayer(val link: Link, private val guild: GuildBehavior) : Link by li
// called under lock
private suspend fun startNextSong(lastSong: Track? = null, position: Duration? = null) {
updateSponsorBlock()
- val nextTrack: QueuedTrack? = when {
- lastSong != null && repeat -> playingTrack!!
- else -> queue.poll()
- }
- if (nextTrack == null) {
+ if (queue.isEmpty()) {
updateMusicChannelMessage()
return
}
+ val nextTrack: QueuedTrack = when {
+ lastSong != null && repeat -> playingTrack!!
+ else -> queue.poll()
+ }
playingTrack = nextTrack
link.player.playTrack(nextTrack.track) {
diff --git a/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/queue/TrackFinder.kt b/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/queue/TrackFinder.kt
index a9fbf741..22699ffd 100644
--- a/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/queue/TrackFinder.kt
+++ b/music/player/src/main/kotlin/dev/schlaubi/mikmusic/player/queue/TrackFinder.kt
@@ -6,6 +6,7 @@ import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSla
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceEnum
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.optionalEnumChoice
import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean
+import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalBoolean
import dev.arbjerg.lavalink.protocol.v4.Exception
import dev.arbjerg.lavalink.protocol.v4.LoadResult
import dev.kord.rest.builder.message.embed
@@ -22,7 +23,13 @@ private val LOG = KotlinLogging.logger { }
private val urlProtocol = "^https?://".toRegex()
-interface QueueOptions {
+interface SchedulingOptions {
+ val shuffle: Boolean?
+ val loop: Boolean?
+ val loopQueue: Boolean?
+}
+
+interface QueueOptions : SchedulingOptions {
val query: String
val force: Boolean
val top: Boolean
@@ -34,7 +41,24 @@ interface QueueOptions {
}
}
-abstract class QueueArguments : Arguments(), QueueOptions {
+abstract class SchedulingArguments : Arguments(), SchedulingOptions {
+ override val shuffle: Boolean? by optionalBoolean {
+ name = "shuffle"
+ description = "scheduler.options.shuffle.description"
+ }
+
+ override val loop: Boolean? by optionalBoolean {
+ name = "loop"
+ description = "scheduler.options.loop.description"
+ }
+
+ override val loopQueue: Boolean? by optionalBoolean {
+ name = "loop-queue"
+ description = "scheduler.options.loop_queue.description"
+ }
+}
+
+abstract class QueueArguments : SchedulingArguments(), QueueOptions {
override val query by autoCompletedYouTubeQuery("The query to play")
override val force by defaultingBoolean {
name = "force"
@@ -167,7 +191,8 @@ suspend fun CommandContext.queueTracks(
musicPlayer.queueTrack(
arguments.force,
arguments.top,
- searchResult.tracks.map { SimpleQueuedTrack(it, getUser()!!.id) }
+ searchResult.tracks.map { SimpleQueuedTrack(it, getUser()!!.id) },
+ schedulingOptions = arguments
)
}
}