Files

161 lines
4.4 KiB
Go

package commands
import (
"context"
"fmt"
"strings"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgolink/v4/disgolink"
"github.com/disgoorg/disgolink/v4/lavalink"
"discord-music-bot/internal/music"
)
type PlayCommand struct {
deps *Dependencies
}
func NewPlayCommand(deps *Dependencies) *PlayCommand {
return &PlayCommand{deps: deps}
}
func (c *PlayCommand) Definition() discord.ApplicationCommandCreate {
return discord.SlashCommandCreate{
Name: "play",
Description: "Plays a song or playlist, or adds it to the queue",
Options: []discord.ApplicationCommandOption{
discord.ApplicationCommandOptionString{
Name: "query",
Description: "Song name, song URL, or playlist URL",
Required: true,
},
},
}
}
func (c *PlayCommand) Execute(ctx context.Context, event *events.ApplicationCommandInteractionCreate) error {
gid, ok := guildID(event)
if !ok {
return guildOnlyError(event)
}
channelID, ok := memberVoiceChannel(c.deps.Discord, gid, event.User().ID)
if !ok {
return rejectEphemeral(event, "Join a voice channel first.")
}
if allowed, reason := ensureCanControlPlayback(c.deps.Discord, gid, event.User().ID); !allowed {
return rejectEphemeral(event, reason)
}
query := event.SlashCommandInteractionData().String("query")
if err := event.DeferCreateMessage(false); err != nil {
return fmt.Errorf("deferring response: %w", err)
}
loaded, err := loadTracks(ctx, c.deps.Lavalink, query)
if err != nil {
return c.followUp(event, err.Error())
}
if err := c.deps.Discord.UpdateVoiceState(ctx, gid, &channelID, false, false); err != nil {
return c.followUp(event, fmt.Sprintf("Could not join your voice channel: %s", err))
}
result, err := c.deps.Music.Guild(gid).EnqueueMany(ctx, loaded.tracks)
if err != nil {
return c.followUp(event, fmt.Sprintf("Could not play that track: %s", err))
}
return c.followUp(event, playSummary(loaded, result))
}
func playSummary(loaded trackLoadResult, result music.EnqueueResult) string {
if len(loaded.tracks) <= 1 {
title := "that track"
if len(loaded.tracks) == 1 {
title = loaded.tracks[0].Info.Title
}
if result.PlayingNow {
return fmt.Sprintf("Now playing: **%s**", title)
}
return fmt.Sprintf("Added to queue: **%s**", title)
}
name := loaded.playlistName
if name == "" {
name = "the playlist"
}
if result.PlayingNow {
return fmt.Sprintf("Now playing **%s** and queued %d more track(s) from **%s**.",
result.Started.Info.Title, result.Queued, name)
}
return fmt.Sprintf("Added %d tracks from **%s** to the queue.", len(loaded.tracks), name)
}
func (c *PlayCommand) followUp(event *events.ApplicationCommandInteractionCreate, content string) error {
_, err := event.Client().Rest.UpdateInteractionResponse(event.ApplicationID(), event.Token(), discord.NewMessageUpdate().WithContent(content))
return err
}
func normalizeQuery(query string) string {
if strings.HasPrefix(query, "http://") || strings.HasPrefix(query, "https://") {
return query
}
return "ytsearch:" + query
}
type trackLoadResult struct {
tracks []lavalink.Track
playlistName string
}
func loadTracks(ctx context.Context, lavalinkClient *disgolink.Client, query string) (trackLoadResult, error) {
node := lavalinkClient.BestNode()
if node == nil {
return trackLoadResult{}, fmt.Errorf("no music node is available right now")
}
type outcome struct {
result trackLoadResult
err error
}
outcomeCh := make(chan outcome, 1)
node.Rest.LoadTracksHandler(ctx, normalizeQuery(query), disgolink.NewTrackLoadingResultHandler(
func(track lavalink.Track) {
outcomeCh <- outcome{result: trackLoadResult{tracks: []lavalink.Track{track}}}
},
func(playlist lavalink.Playlist) {
if len(playlist.Tracks) == 0 {
outcomeCh <- outcome{err: fmt.Errorf("that playlist is empty")}
return
}
outcomeCh <- outcome{result: trackLoadResult{
tracks: playlist.Tracks,
playlistName: playlist.Info.Name,
}}
},
func(tracks []lavalink.Track) {
if len(tracks) == 0 {
outcomeCh <- outcome{err: fmt.Errorf("no results found for %q", query)}
return
}
outcomeCh <- outcome{result: trackLoadResult{tracks: []lavalink.Track{tracks[0]}}}
},
func() {
outcomeCh <- outcome{err: fmt.Errorf("no results found for %q", query)}
},
func(err error) {
outcomeCh <- outcome{err: fmt.Errorf("failed to load track: %w", err)}
},
))
res := <-outcomeCh
return res.result, res.err
}