From 87d27a87c8ad3c282096405b63cdd4f9742bb9af Mon Sep 17 00:00:00 2001 From: Vin <166316975+ftrapture@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:33:38 +0530 Subject: [PATCH 1/4] Disabled html escaping --- commands/decode.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/commands/decode.go b/commands/decode.go index 5469505..32ae4ed 100644 --- a/commands/decode.go +++ b/commands/decode.go @@ -45,7 +45,7 @@ func (c *Commands) Decode(data discord.SlashCommandInteractionData, e *handler.C } var decodedData []byte if decoded != nil { - decodedData, _ = json.MarshalIndent(decoded, "", " ") + decodedData = MarshalNoEscape(decoded) } msg := jsonMessage(content, decodedData) @@ -70,7 +70,7 @@ func (c *Commands) Decode(data discord.SlashCommandInteractionData, e *handler.C return err } - decodedData, _ := json.MarshalIndent(decoded, "", " ") + decodedData := MarshalNoEscape(decoded) msg := jsonMessage("", decodedData) @@ -103,3 +103,12 @@ func jsonMessage(msg string, jsonData []byte) message { Files: files, } } + +func MarshalNoEscape(v any) []byte { + b := &bytes.Buffer{} + e := json.NewEncoder(b) + e.SetEscapeHTML(false) + e.SetIndent("", " ") + _ = e.Encode(v) + return b.Bytes() +} From 219adfb271893199d745cf5ba36f87c2e890a28f Mon Sep 17 00:00:00 2001 From: Vin <166316975+ftrapture@users.noreply.github.com> Date: Sat, 16 Aug 2025 11:57:32 +0530 Subject: [PATCH 2/4] same applies for resolve command --- commands/resolve.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/commands/resolve.go b/commands/resolve.go index a718a04..5ad8cc0 100644 --- a/commands/resolve.go +++ b/commands/resolve.go @@ -49,13 +49,7 @@ func (c *Commands) Resolve(data discord.SlashCommandInteractionData, e *handler. ) switch result.LoadType { case lavalink.LoadTypeTrack, lavalink.LoadTypePlaylist, lavalink.LoadTypeSearch: - decodedData, err := json.MarshalIndent(result.Data, "", " ") - if err != nil { - _, err = e.UpdateInteractionResponse(discord.MessageUpdate{ - Content: json.Ptr(fmt.Sprintf("failed to resolve identifier: %s", err)), - }) - return err - } + decodedData := MarshalNoEscape(result.Data) if len(decodedData) > 1900 { files = append(files, &discord.File{ From 73fe5638e89967ed686352f94ed3e112451cda7c Mon Sep 17 00:00:00 2001 From: topi314 Date: Sat, 16 Aug 2025 23:11:33 +0200 Subject: [PATCH 3/4] use no html escape marshal in other places too --- commands/decode.go | 2 +- commands/info.go | 8 +------- commands/now_playing.go | 9 +-------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/commands/decode.go b/commands/decode.go index 32ae4ed..1fc7867 100644 --- a/commands/decode.go +++ b/commands/decode.go @@ -105,7 +105,7 @@ func jsonMessage(msg string, jsonData []byte) message { } func MarshalNoEscape(v any) []byte { - b := &bytes.Buffer{} + b := new(bytes.Buffer) e := json.NewEncoder(b) e.SetEscapeHTML(false) e.SetIndent("", " ") diff --git a/commands/info.go b/commands/info.go index 39f8e29..f97a79d 100644 --- a/commands/info.go +++ b/commands/info.go @@ -9,7 +9,6 @@ import ( "github.com/disgoorg/disgo/handler" "github.com/disgoorg/disgolink/v3/disgolink" "github.com/disgoorg/disgolink/v3/lavalink" - "github.com/disgoorg/json" ) var info = discord.SlashCommandCreate{ @@ -75,12 +74,7 @@ func (c *Commands) InfoLavalink(data discord.SlashCommandInteractionData, e *han nodeInfos[node.Config().Name] = *nodeInfo }) - rawInfo, err := json.MarshalIndent(nodeInfos, "", " ") - if err != nil { - return e.CreateMessage(discord.MessageCreate{ - Content: "Failed to marshal lavalink info: " + err.Error(), - }) - } + rawInfo := MarshalNoEscape(nodeInfos) return e.CreateMessage(discord.MessageCreate{ Embeds: []discord.Embed{ diff --git a/commands/now_playing.go b/commands/now_playing.go index 9598bc2..f932fbe 100644 --- a/commands/now_playing.go +++ b/commands/now_playing.go @@ -6,7 +6,6 @@ import ( "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/handler" - "github.com/disgoorg/json" "github.com/lavalink-devs/lavalink-bot/internal/res" ) @@ -32,13 +31,7 @@ func (c *Commands) NowPlaying(data discord.SlashCommandInteractionData, e *handl var files []*discord.File if data.Bool("raw") { - decodedData, err := json.MarshalIndent(track, "", " ") - if err != nil { - return e.CreateMessage(discord.MessageCreate{ - Content: fmt.Sprintf("Failed to marshal track: %s", err), - Flags: discord.MessageFlagEphemeral, - }) - } + decodedData := MarshalNoEscape(track) files = append(files, &discord.File{ Name: "track.json", Reader: bytes.NewReader(decodedData), From 1c76ca7d64c85d7112aea4e43f92bc55a69bf9df Mon Sep 17 00:00:00 2001 From: ftrapture Date: Mon, 18 Aug 2025 23:23:41 +0530 Subject: [PATCH 4/4] implemented rest of the commands --- commands/clear.go | 22 ++++++++++++ commands/disconnect.go | 4 +++ commands/forward.go | 49 +++++++++++++++++++++++++++ commands/join.go | 39 +++++++++++++++++++++ commands/move.go | 77 ++++++++++++++++++++++++++++++++++++++++++ commands/music.go | 12 +++++++ commands/queue.go | 2 +- commands/remove.go | 69 +++++++++++++++++++++++++++++++++++++ commands/restart.go | 27 +++++++++++++++ commands/rewind.go | 49 +++++++++++++++++++++++++++ commands/seek.go | 5 ++- commands/swap.go | 66 ++++++++++++++++++++++++++++++++++++ handlers/lavalink.go | 15 ++++---- lavalinkbot/queue.go | 43 ++++++++++++++++++++++- main.go | 19 ++++++----- 15 files changed, 481 insertions(+), 17 deletions(-) create mode 100644 commands/clear.go create mode 100644 commands/forward.go create mode 100644 commands/join.go create mode 100644 commands/move.go create mode 100644 commands/remove.go create mode 100644 commands/restart.go create mode 100644 commands/rewind.go create mode 100644 commands/swap.go diff --git a/commands/clear.go b/commands/clear.go new file mode 100644 index 0000000..a0871fa --- /dev/null +++ b/commands/clear.go @@ -0,0 +1,22 @@ +package commands + +import ( + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" +) + +func (c *Commands) Clear(_ discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + _, tracks := c.MusicQueue.Get(*e.GuildID()) + if len(tracks) == 0 { + return e.CreateMessage(discord.MessageCreate{ + Content: "Queue is already empty", + Flags: discord.MessageFlagEphemeral, + }) + } + + c.MusicQueue.Clear(*e.GuildID()) + + return e.CreateMessage(discord.MessageCreate{ + Content: "🗑️ Cleared the queue", + }) +} diff --git a/commands/disconnect.go b/commands/disconnect.go index 96a2b2c..ffab646 100644 --- a/commands/disconnect.go +++ b/commands/disconnect.go @@ -12,6 +12,10 @@ func (c *Commands) Disconnect(_ discord.SlashCommandInteractionData, e *handler. ctx, cancel := context.WithTimeout(e.Ctx, 10*time.Second) defer cancel() + player := c.Lavalink.ExistingPlayer(*e.GuildID()) + if player != nil { + player.Destroy(ctx) + } if err := c.Client.UpdateVoiceState(ctx, *e.GuildID(), nil, false, false); err != nil { return e.CreateMessage(discord.MessageCreate{ Content: "Failed to disconnect player", diff --git a/commands/forward.go b/commands/forward.go new file mode 100644 index 0000000..3fc35fe --- /dev/null +++ b/commands/forward.go @@ -0,0 +1,49 @@ +package commands + +import ( + "context" + "time" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" + "github.com/disgoorg/disgolink/v3/lavalink" + + "github.com/lavalink-devs/lavalink-bot/internal/res" +) + +func (c *Commands) Forward(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + ctx, cancel := context.WithTimeout(e.Ctx, 10*time.Second) + defer cancel() + player := c.Lavalink.ExistingPlayer(*e.GuildID()) + + amt := data.Int("amount") + unit, ok := data.OptInt("unit") + if !ok { + unit = int(lavalink.Second) + } + + cPos := int(player.State().Position) + nPos := cPos + (amt * unit) + + if nPos >= int(player.Track().Info.Length) { + nPos = int(player.Track().Info.Length) - 1 + } + + if nPos == cPos { + return e.CreateMessage(discord.MessageCreate{ + Content: "The player is already at position **`" + res.FormatDuration(lavalink.Duration(cPos)) + "`**", + Flags: discord.MessageFlagEphemeral, + }) + } + + if err := player.Update(ctx, lavalink.WithPosition(lavalink.Duration(nPos))); err != nil { + return e.CreateMessage(discord.MessageCreate{ + Content: "Failed to forward the track.", + Flags: discord.MessageFlagEphemeral, + }) + } + + return e.CreateMessage(discord.MessageCreate{ + Content: "⏩ Forwarded to **`" + res.FormatDuration(lavalink.Duration(nPos)) + "`**", + }) +} diff --git a/commands/join.go b/commands/join.go new file mode 100644 index 0000000..16db2b0 --- /dev/null +++ b/commands/join.go @@ -0,0 +1,39 @@ +package commands + +import ( + "context" + "fmt" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" + "github.com/disgoorg/snowflake/v2" +) + +func (c *Commands) Join(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + var vcID snowflake.ID + + ch, ok := data.OptChannel("channel") + if ok && ch.Type == discord.ChannelTypeGuildVoice { + vcID = ch.ID + } else { + vs, ok := c.Client.Caches().VoiceState(*e.GuildID(), e.User().ID) + if !ok || vs.ChannelID == nil { + return e.CreateMessage(discord.MessageCreate{ + Content: "You must be in a voice channel or mention one", + Flags: discord.MessageFlagEphemeral, + }) + } + vcID = *vs.ChannelID + } + + if err := c.Client.UpdateVoiceState(context.Background(), *e.GuildID(), &vcID, false, false); err != nil { + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("Failed to join voice channel: %s", err), + Flags: discord.MessageFlagEphemeral, + }) + } + + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("Joined <#%s>", vcID.String()), + }) +} diff --git a/commands/move.go b/commands/move.go new file mode 100644 index 0000000..c31ad22 --- /dev/null +++ b/commands/move.go @@ -0,0 +1,77 @@ +package commands + +import ( + "fmt" + "strconv" + "strings" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" +) + +func (c *Commands) MoveAutocomplete(e *handler.AutocompleteEvent) error { + _, tracks := c.MusicQueue.Get(*e.GuildID()) + if len(tracks) == 0 { + return e.AutocompleteResult(nil) + } + + query := e.Data.String("from") + choices := make([]discord.AutocompleteChoice, 0, len(tracks)) + for i, track := range tracks { + name := fmt.Sprintf("%d: %s - %s", i+1, track.Info.Title, track.Info.Author) + if len(name) > 100 { + name = name[:97] + "..." + } + choices = append(choices, discord.AutocompleteChoiceString{ + Name: name, + Value: strconv.Itoa(i + 1), + }) + } + + if query != "" { + f := make([]discord.AutocompleteChoice, 0) + q := strings.ToLower(query) + for _, choice := range choices { + if strings.Contains(strings.ToLower(choice.(discord.AutocompleteChoiceString).Name), q) { + f = append(f, choice) + } + } + choices = f + } + + if len(choices) > 25 { + choices = choices[:25] + } + + return e.AutocompleteResult(choices) +} + +func (c *Commands) Move(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + from := data.Int("from") + to := data.Int("to") + + _, tracks := c.MusicQueue.Get(*e.GuildID()) + if len(tracks) < 2 { + return e.CreateMessage(discord.MessageCreate{ + Content: "Queue is empty", + }) + } + + if from < 1 || from > len(tracks) || to < 1 || to > len(tracks) { + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("Index ust be between 1 and %d", len(tracks)), + }) + } + + if from == to { + return e.CreateMessage(discord.MessageCreate{ + Content: "Both indexes are same", + }) + } + + c.MusicQueue.Move(*e.GuildID(), from-1, to-1) + + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("Moved **%s** from position %d → %d", tracks[from-1].Info.Title, from, to), + }) +} diff --git a/commands/music.go b/commands/music.go index 9e80554..6193cb0 100644 --- a/commands/music.go +++ b/commands/music.go @@ -266,6 +266,18 @@ var music = discord.SlashCommandCreate{ }, }, }, + discord.ApplicationCommandOptionSubCommand{ + Name: "join", + Description: "Joins a voice channel", + Options: []discord.ApplicationCommandOption{ + discord.ApplicationCommandOptionChannel{ + Name: "channel", + Description: "The voice channel to join", + Required: false, + ChannelTypes: []discord.ChannelType{discord.ChannelTypeGuildVoice}, + }, + }, + }, discord.ApplicationCommandOptionSubCommand{ Name: "volume", Description: "Sets the volume of the player", diff --git a/commands/queue.go b/commands/queue.go index 0ba8c97..ffca55c 100644 --- a/commands/queue.go +++ b/commands/queue.go @@ -13,7 +13,7 @@ func (c *Commands) Queue(_ discord.SlashCommandInteractionData, e *handler.Comma _, tracks := c.MusicQueue.Get(*e.GuildID()) if len(tracks) == 0 { return e.CreateMessage(discord.MessageCreate{ - Content: fmt.Sprintf("No tracks in queue"), + Content: "No tracks in queue", }) } diff --git a/commands/remove.go b/commands/remove.go new file mode 100644 index 0000000..ca93285 --- /dev/null +++ b/commands/remove.go @@ -0,0 +1,69 @@ +package commands + +import ( + "fmt" + "strconv" + "strings" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" +) + +func (c *Commands) RemoveAutocomplete(e *handler.AutocompleteEvent) error { + _, tracks := c.MusicQueue.Get(*e.GuildID()) + if len(tracks) == 0 { + return e.AutocompleteResult(nil) + } + + query := e.Data.String("index") + choices := make([]discord.AutocompleteChoice, 0, len(tracks)) + for i, track := range tracks { + name := fmt.Sprintf("%d: %s - %s", i+1, track.Info.Title, track.Info.Author) + if len(name) > 100 { + name = name[:97] + "..." + } + choices = append(choices, discord.AutocompleteChoiceString{ + Name: name, + Value: strconv.Itoa(i + 1), + }) + } + if query != "" { + f := make([]discord.AutocompleteChoice, 0) + q := strings.ToLower(query) + for _, choice := range choices { + choiceName := choice.(discord.AutocompleteChoiceString).Name + if strings.Contains(strings.ToLower(choiceName), q) { + f = append(f, choice) + } + } + choices = f + } + if len(choices) > 25 { + choices = choices[:25] + } + + return e.AutocompleteResult(choices) +} + +func (c *Commands) Remove(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + index := int(data.Int("index")) + + _, tracks := c.MusicQueue.Get(*e.GuildID()) + if len(tracks) == 0 { + return e.CreateMessage(discord.MessageCreate{ + Content: "Queue is empty", + }) + } + + if index < 1 || index > len(tracks) { + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("Index must be between 1 and %d", len(tracks)), + }) + } + + c.MusicQueue.Remove(*e.GuildID(), index-1, index) + + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("🗑️ Removed track from **#%d** queue", index), + }) +} diff --git a/commands/restart.go b/commands/restart.go new file mode 100644 index 0000000..7684c53 --- /dev/null +++ b/commands/restart.go @@ -0,0 +1,27 @@ +package commands + +import ( + "context" + "time" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" + "github.com/disgoorg/disgolink/v3/lavalink" +) + +func (c *Commands) Restart(_ discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + ctx, cancel := context.WithTimeout(e.Ctx, 10*time.Second) + defer cancel() + player := c.Lavalink.ExistingPlayer(*e.GuildID()) + + if err := player.Update(ctx, lavalink.WithPosition(0)); err != nil { + return e.CreateMessage(discord.MessageCreate{ + Content: "Failed to restart the track.", + Flags: discord.MessageFlagEphemeral, + }) + } + + return e.CreateMessage(discord.MessageCreate{ + Content: "🔄 Replaying the current track.", + }) +} diff --git a/commands/rewind.go b/commands/rewind.go new file mode 100644 index 0000000..0c51851 --- /dev/null +++ b/commands/rewind.go @@ -0,0 +1,49 @@ +package commands + +import ( + "context" + "time" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" + "github.com/disgoorg/disgolink/v3/lavalink" + + "github.com/lavalink-devs/lavalink-bot/internal/res" +) + +func (c *Commands) Rewind(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + ctx, cancel := context.WithTimeout(e.Ctx, 10*time.Second) + defer cancel() + player := c.Lavalink.ExistingPlayer(*e.GuildID()) + + amt := data.Int("amount") + unit, ok := data.OptInt("unit") + if !ok { + unit = int(lavalink.Second) + } + + cPos := int(player.State().Position) + nPos := cPos - (amt * unit) + + if nPos < 0 { + nPos = 0 + } + + if nPos == cPos { + return e.CreateMessage(discord.MessageCreate{ + Content: "The player is already at position **`" + res.FormatDuration(lavalink.Duration(cPos)) + "`**", + Flags: discord.MessageFlagEphemeral, + }) + } + + if err := player.Update(ctx, lavalink.WithPosition(lavalink.Duration(nPos))); err != nil { + return e.CreateMessage(discord.MessageCreate{ + Content: "Failed to rewind the track.", + Flags: discord.MessageFlagEphemeral, + }) + } + + return e.CreateMessage(discord.MessageCreate{ + Content: "⏪ Rewound to `" + res.FormatDuration(lavalink.Duration(nPos)) + "`", + }) +} diff --git a/commands/seek.go b/commands/seek.go index 7a69ebd..4434358 100644 --- a/commands/seek.go +++ b/commands/seek.go @@ -24,6 +24,9 @@ func (c *Commands) Seek(data discord.SlashCommandInteractionData, e *handler.Com } newPosition := lavalink.Duration(position * duration) + if newPosition < 0 { + newPosition = 0 + } if err := player.Update(ctx, lavalink.WithPosition(newPosition)); err != nil { return e.CreateMessage(discord.MessageCreate{ Content: "Failed to seek to position", @@ -31,6 +34,6 @@ func (c *Commands) Seek(data discord.SlashCommandInteractionData, e *handler.Com } return e.CreateMessage(discord.MessageCreate{ - Content: fmt.Sprintf("⏩ Seeked to %s", res.FormatDuration(newPosition)), + Content: fmt.Sprintf("Seeked to **`%s`**", res.FormatDuration(newPosition)), }) } diff --git a/commands/swap.go b/commands/swap.go new file mode 100644 index 0000000..a1b1020 --- /dev/null +++ b/commands/swap.go @@ -0,0 +1,66 @@ +package commands + +import ( + "fmt" + "strconv" + "strings" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/handler" +) + +func (c *Commands) SwapAutocomplete(e *handler.AutocompleteEvent) error { + _, tracks := c.MusicQueue.Get(*e.GuildID()) + if len(tracks) == 0 { + return e.AutocompleteResult(nil) + } + + query := e.Data.String("first") + choices := make([]discord.AutocompleteChoice, 0, len(tracks)) + + for i, track := range tracks { + name := fmt.Sprintf("%d: %s - %s", i+1, track.Info.Title, track.Info.Author) + if len(name) > 100 { + name = name[:97] + "..." + } + choices = append(choices, discord.AutocompleteChoiceString{ + Name: name, + Value: strconv.Itoa(i + 1), + }) + } + + if query != "" { + f := make([]discord.AutocompleteChoice, 0) + q := strings.ToLower(query) + for _, choice := range choices { + choiceName := choice.(discord.AutocompleteChoiceString).Name + if strings.Contains(strings.ToLower(choiceName), q) { + f = append(f, choice) + } + } + choices = f + } + + if len(choices) > 25 { + choices = choices[:25] + } + + return e.AutocompleteResult(choices) +} + +func (c *Commands) Swap(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error { + first := data.Int("first") + second := data.Int("second") + + ok := c.MusicQueue.Swap(*e.GuildID(), int(first-1), int(second-1)) + if !ok { + return e.CreateMessage(discord.MessageCreate{ + Content: "Invalid index was provided or no more tracks in queue", + Flags: discord.MessageFlagEphemeral, + }) + } + + return e.CreateMessage(discord.MessageCreate{ + Content: fmt.Sprintf("🔄 Swapped track %d with track %d", first, second), + }) +} diff --git a/handlers/lavalink.go b/handlers/lavalink.go index 0061234..5c3c8c5 100644 --- a/handlers/lavalink.go +++ b/handlers/lavalink.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "sync/atomic" "time" "github.com/disgoorg/disgo/discord" @@ -23,13 +24,13 @@ func (h *Handlers) OnVoiceStateUpdate(event *events.GuildVoiceStateUpdate) { if !ok || event.OldVoiceState.ChannelID == nil { return } - var voiceStates int + var voiceStates int32 h.Client.Caches().VoiceStatesForEach(event.VoiceState.GuildID, func(vs discord.VoiceState) { - if *vs.ChannelID == *event.OldVoiceState.ChannelID { - voiceStates++ + if vs.ChannelID != nil && *vs.ChannelID == *event.OldVoiceState.ChannelID { + atomic.AddInt32(&voiceStates, 1) } }) - if voiceStates <= 1 { + if atomic.LoadInt32(&voiceStates) <= 1 { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := h.Client.UpdateVoiceState(ctx, event.VoiceState.GuildID, nil, false, false); err != nil { @@ -80,15 +81,17 @@ func (h *Handlers) OnTrackStart(p disgolink.Player, event lavalink.TrackStartEve } func (h *Handlers) OnTrackEnd(p disgolink.Player, event lavalink.TrackEndEvent) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() if !event.Reason.MayStartNext() { + p.Destroy(ctx) return } track, ok := h.MusicQueue.Next(p.GuildID()) if !ok { + p.Destroy(ctx) return } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() if err := p.Update(ctx, lavalink.WithTrack(track)); err != nil { channelID := h.MusicQueue.ChannelID(p.GuildID()) if channelID == 0 { diff --git a/lavalinkbot/queue.go b/lavalinkbot/queue.go index 9a8bbe4..181e479 100644 --- a/lavalinkbot/queue.go +++ b/lavalinkbot/queue.go @@ -62,6 +62,30 @@ func (q *PlayerManager) ChannelID(guildID snowflake.ID) snowflake.ID { return qu.channelID } +func (q *PlayerManager) Move(guildID snowflake.ID, from, to int) { + q.mu.Lock() + defer q.mu.Unlock() + + qq, ok := q.queues[guildID] + if !ok { + return + } + + if from < 0 || from >= len(qq.tracks) || to < 0 || to >= len(qq.tracks) { + return + } + + track := qq.tracks[from] + + qq.tracks = append(qq.tracks[:from], qq.tracks[from+1:]...) + + if to > from { + to-- + } + + qq.tracks = append(qq.tracks[:to], append([]lavalink.Track{track}, qq.tracks[to:]...)...) +} + func (q *PlayerManager) Add(guildID snowflake.ID, channelID snowflake.ID, tracks ...lavalink.Track) { q.mu.Lock() defer q.mu.Unlock() @@ -108,7 +132,7 @@ func (q *PlayerManager) Shuffle(guildID snowflake.ID) bool { return false } - if len(q.queues[guildID].tracks) >= 1 { + if len(qq.tracks) <= 1 { return false } @@ -120,6 +144,23 @@ func (q *PlayerManager) Shuffle(guildID snowflake.ID) bool { return true } +func (q *PlayerManager) Swap(guildID snowflake.ID, i, j int) bool { + q.mu.Lock() + defer q.mu.Unlock() + + qq, ok := q.queues[guildID] + if !ok { + return false + } + + if i < 0 || j < 0 || i >= len(qq.tracks) || j >= len(qq.tracks) { + return false + } + + qq.tracks[i], qq.tracks[j] = qq.tracks[j], qq.tracks[i] + return true +} + func (q *PlayerManager) SetRepeatMode(guildID snowflake.ID, mode RepeatMode) { q.mu.Lock() defer q.mu.Unlock() diff --git a/main.go b/main.go index 8f3c4ba..7b3aa99 100644 --- a/main.go +++ b/main.go @@ -78,6 +78,7 @@ func main() { r.Route("/music", func(r handler.Router) { r.SlashCommand("/play", cmds.Play) + r.SlashCommand("/join", cmds.Join) r.SlashCommand("/play-track", cmds.PlayTrack) r.SlashCommand("/tts", cmds.TTS) r.Autocomplete("/play", cmds.PlayAutocomplete) @@ -96,14 +97,16 @@ func main() { r.SlashCommand("/repeat", cmds.Repeat) r.SlashCommand("/queue", cmds.Queue) r.SlashCommand("/now-playing", cmds.NowPlaying) - // r.SlashCommand("/lyrics", cmds.Lyrics) - // r.SlashCommand("/remove", cmds.Remove) - // r.SlashCommand("/move", cmds.Move) - // r.SlashCommand("/swap", cmds.Swap) - // r.SlashCommand("/clear", cmds.Clear) - // r.SlashCommand("/rewind", cmds.Rewind) - // r.SlashCommand("/forward", cmds.Forward) - // r.SlashCommand("/restart", cmds.Restart) + r.Autocomplete("/remove", cmds.RemoveAutocomplete) + r.SlashCommand("/remove", cmds.Remove) + r.Autocomplete("/move", cmds.MoveAutocomplete) + r.SlashCommand("/move", cmds.Move) + r.Autocomplete("/swap", cmds.SwapAutocomplete) + r.SlashCommand("/swap", cmds.Swap) + r.SlashCommand("/clear", cmds.Clear) + r.SlashCommand("/rewind", cmds.Rewind) + r.SlashCommand("/forward", cmds.Forward) + r.SlashCommand("/restart", cmds.Restart) r.SlashCommand("/effects", cmds.Effects) r.SlashCommand("/sponsorblock/show", cmds.ShowSponsorblock) r.SlashCommand("/sponsorblock/set", cmds.SetSponsorblock)