feat(music): stable queue identity, remove/reorder, and repeat commands
TrackEntry gets a QueueID generated once when a track is queued and kept for its whole life there, and musicrepo.ReplaceQueue now upserts by that ID instead of deleting and reinserting the whole queue on every sync (which churned every track's database id constantly, even ones that hadn't moved, and would've made remove/reorder commands target the wrong track). RemoveFromQueue and Reorder are new Manager functions built on top of that stable identity. Also adds repeat_song/ repeat_queue to the persisted now-playing state, and makes the two existing toggle functions actually sync it, they never did before.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/disgoorg/disgolink/v3/disgolink"
|
||||
"github.com/disgoorg/disgolink/v3/lavalink"
|
||||
"github.com/disgoorg/snowflake/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type TrackEntry struct {
|
||||
@@ -21,6 +22,14 @@ type TrackEntry struct {
|
||||
RequestedBy string
|
||||
RequesterID string
|
||||
StartedAt time.Time
|
||||
|
||||
// QueueID is generated once, when the track is first added to the
|
||||
// queue, and never changes for the rest of its life there - it's
|
||||
// what lets syncState upsert this track's row across syncs instead
|
||||
// of deleting and recreating it (and its DB id) every single time,
|
||||
// which is what remove/reorder commands need to reliably target a
|
||||
// specific track.
|
||||
QueueID string
|
||||
}
|
||||
|
||||
type guildPlayer struct {
|
||||
@@ -213,6 +222,8 @@ func syncState(guidID string) {
|
||||
copy(queue, gp.Queue)
|
||||
paused := gp.Paused
|
||||
volume := gp.Volume
|
||||
repeatSong := gp.RepeatSong
|
||||
repeatQueue := gp.RepeatQueue
|
||||
manager.mu.Unlock()
|
||||
|
||||
gID, err := strconv.ParseInt(guidID, 10, 64)
|
||||
@@ -245,7 +256,7 @@ func syncState(guidID string) {
|
||||
length := int(current.Track.Info.Length / 1000)
|
||||
startedAt := current.StartedAt
|
||||
|
||||
err = manager.repo.SetNowPlaying(context.Background(), gID, title, uri, length, startedAt, paused, volume)
|
||||
err = manager.repo.SetNowPlaying(context.Background(), gID, title, uri, length, startedAt, paused, volume, repeatSong, repeatQueue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -262,6 +273,7 @@ func syncState(guidID string) {
|
||||
Title: entry.Track.Info.Title,
|
||||
URL: uri,
|
||||
RequestedBy: reqID,
|
||||
ClientID: entry.QueueID,
|
||||
})
|
||||
}
|
||||
err = manager.repo.ReplaceQueue(context.Background(), gID, queueEntries)
|
||||
@@ -346,6 +358,7 @@ func EnqueueAndPlay(guildID, voiceChannelID, textChannelID, query, requester, re
|
||||
Track: t,
|
||||
RequestedBy: requester,
|
||||
RequesterID: requesterID,
|
||||
QueueID: uuid.New().String(),
|
||||
}
|
||||
}
|
||||
firstEntry := entries[0]
|
||||
@@ -551,12 +564,15 @@ func ToggleRepeatSong(guildID string) (bool, bool) {
|
||||
}
|
||||
gp := getOrCreateGuildPlayer(guildID)
|
||||
manager.mu.Lock()
|
||||
defer manager.mu.Unlock()
|
||||
gp.RepeatSong = !gp.RepeatSong
|
||||
if gp.RepeatSong {
|
||||
gp.RepeatQueue = false
|
||||
}
|
||||
return gp.RepeatSong, gp.RepeatQueue
|
||||
repeatSong, repeatQueue := gp.RepeatSong, gp.RepeatQueue
|
||||
manager.mu.Unlock()
|
||||
|
||||
syncState(guildID)
|
||||
return repeatSong, repeatQueue
|
||||
}
|
||||
|
||||
func ToggleRepeatQueue(guildID string) (bool, bool) {
|
||||
@@ -565,12 +581,15 @@ func ToggleRepeatQueue(guildID string) (bool, bool) {
|
||||
}
|
||||
gp := getOrCreateGuildPlayer(guildID)
|
||||
manager.mu.Lock()
|
||||
defer manager.mu.Unlock()
|
||||
gp.RepeatQueue = !gp.RepeatQueue
|
||||
if gp.RepeatQueue {
|
||||
gp.RepeatSong = false
|
||||
}
|
||||
return gp.RepeatSong, gp.RepeatQueue
|
||||
repeatSong, repeatQueue := gp.RepeatSong, gp.RepeatQueue
|
||||
manager.mu.Unlock()
|
||||
|
||||
syncState(guildID)
|
||||
return repeatSong, repeatQueue
|
||||
}
|
||||
|
||||
func repeatFlags(guildID string) (bool, bool) {
|
||||
@@ -630,6 +649,72 @@ func SetVolume(guildID string, volume int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveFromQueue(guildID, queueID string) error {
|
||||
if manager == nil {
|
||||
return fmt.Errorf("music manager not initialized")
|
||||
}
|
||||
gp := getOrCreateGuildPlayer(guildID)
|
||||
|
||||
manager.mu.Lock()
|
||||
idx := -1
|
||||
for i, q := range gp.Queue {
|
||||
if q.QueueID == queueID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
manager.mu.Unlock()
|
||||
return fmt.Errorf("queue not found")
|
||||
}
|
||||
|
||||
if idx == 0 {
|
||||
manager.mu.Unlock()
|
||||
return Skip(guildID)
|
||||
}
|
||||
|
||||
gp.Queue = append(gp.Queue[:idx], gp.Queue[idx+1:]...)
|
||||
manager.mu.Unlock()
|
||||
|
||||
syncState(guildID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Reorder(guildID, queueID string, newPosition int) error {
|
||||
if manager == nil {
|
||||
return fmt.Errorf("music manager not initialized")
|
||||
}
|
||||
gp := getOrCreateGuildPlayer(guildID)
|
||||
|
||||
manager.mu.Lock()
|
||||
idx := -1
|
||||
for i, q := range gp.Queue {
|
||||
if q.QueueID == queueID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
manager.mu.Unlock()
|
||||
return fmt.Errorf("queue not found")
|
||||
}
|
||||
|
||||
if idx == 0 {
|
||||
manager.mu.Unlock()
|
||||
return Skip(guildID)
|
||||
}
|
||||
|
||||
entry := gp.Queue[idx]
|
||||
gp.Queue = append(gp.Queue[:idx], gp.Queue[idx+1:]...)
|
||||
|
||||
pos := max(1, min(newPosition, len(gp.Queue)))
|
||||
gp.Queue = append(gp.Queue[:pos], append([]TrackEntry{entry}, gp.Queue[pos:]...)...)
|
||||
manager.mu.Unlock()
|
||||
|
||||
syncState(guildID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CurrentNowPlayingMessageID(guildID string) string {
|
||||
if manager == nil {
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user