feat: integrate music commands with Lavalink support
- Added new music commands: play, queue, and volume. - Implemented music management using disgolink for Lavalink integration. - Updated bot initialization to include Lavalink host and password. - Enhanced interaction handling for music commands, requiring DJ role for usage. - Introduced now playing message with interactive buttons for controlling playback. - Updated dependencies in go.mod for disgolink and snowflake.
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
package music
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/disgoorg/disgolink/v3/lavalink"
|
||||
"github.com/disgoorg/snowflake/v2"
|
||||
)
|
||||
|
||||
func nowPlayingComponents(disabled bool, repeatSong bool, repeatQueue bool, paused bool) []discordgo.MessageComponent {
|
||||
repeatSongStyle := discordgo.SecondaryButton
|
||||
if repeatSong {
|
||||
repeatSongStyle = discordgo.SuccessButton
|
||||
}
|
||||
repeatQueueStyle := discordgo.SecondaryButton
|
||||
if repeatQueue {
|
||||
repeatQueueStyle = discordgo.SuccessButton
|
||||
}
|
||||
|
||||
return []discordgo.MessageComponent{
|
||||
discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{
|
||||
discordgo.Button{
|
||||
Style: discordgo.SecondaryButton,
|
||||
Emoji: &discordgo.ComponentEmoji{Name: map[bool]string{true: "▶️", false: "⏸️"}[paused]},
|
||||
CustomID: "music:toggle_pause",
|
||||
Disabled: disabled,
|
||||
},
|
||||
discordgo.Button{
|
||||
Style: discordgo.SecondaryButton,
|
||||
Emoji: &discordgo.ComponentEmoji{Name: "⏭️"},
|
||||
CustomID: "music:skip",
|
||||
Disabled: disabled,
|
||||
},
|
||||
discordgo.Button{
|
||||
Style: repeatSongStyle,
|
||||
Emoji: &discordgo.ComponentEmoji{Name: "🔂"},
|
||||
CustomID: "music:repeat_song",
|
||||
Disabled: disabled,
|
||||
},
|
||||
discordgo.Button{
|
||||
Style: repeatQueueStyle,
|
||||
Emoji: &discordgo.ComponentEmoji{Name: "🔁"},
|
||||
CustomID: "music:repeat_queue",
|
||||
Disabled: disabled,
|
||||
},
|
||||
discordgo.Button{
|
||||
Style: discordgo.DangerButton,
|
||||
Emoji: &discordgo.ComponentEmoji{Name: "✖️"},
|
||||
CustomID: "music:stop",
|
||||
Disabled: disabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func disableNowPlaying(textChannelID, messageID string) {
|
||||
if manager == nil || textChannelID == "" || messageID == "" {
|
||||
return
|
||||
}
|
||||
disabled := nowPlayingComponents(true, false, false, false)
|
||||
_, _ = manager.session.ChannelMessageEditComplex(&discordgo.MessageEdit{
|
||||
Channel: textChannelID,
|
||||
ID: messageID,
|
||||
Components: &disabled,
|
||||
})
|
||||
}
|
||||
|
||||
func postNowPlaying(guildID, textChannelID string, entry TrackEntry) {
|
||||
if manager == nil || textChannelID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
manager.mu.Lock()
|
||||
gp, ok := manager.players[guildID]
|
||||
var oldMsgID string
|
||||
if ok {
|
||||
oldMsgID = gp.NowPlayingMsgID
|
||||
}
|
||||
manager.mu.Unlock()
|
||||
|
||||
if oldMsgID != "" {
|
||||
disableNowPlaying(textChannelID, oldMsgID)
|
||||
}
|
||||
|
||||
repeatSong, repeatQueue := repeatFlags(guildID)
|
||||
paused, volume := pausedAndVolume(guildID)
|
||||
|
||||
info := entry.Track.Info
|
||||
title := info.Title
|
||||
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Author: &discordgo.MessageEmbedAuthor{
|
||||
Name: info.Author,
|
||||
IconURL: sourceIconURL(info.SourceName),
|
||||
},
|
||||
Title: title,
|
||||
Color: 0x2B2D31, // matches Discord dark-ish embed accent
|
||||
Fields: []*discordgo.MessageEmbedField{
|
||||
{Name: "Duration", Value: formatDuration(info.Length, info.IsStream), Inline: true},
|
||||
{Name: "Volume", Value: fmt.Sprintf("%d%%", volume), Inline: true},
|
||||
{Name: "Requested by", Value: entry.RequestedBy, Inline: true},
|
||||
},
|
||||
}
|
||||
|
||||
if info.URI != nil && *info.URI != "" {
|
||||
embed.URL = *info.URI
|
||||
}
|
||||
|
||||
if art := artworkURL(info); art != "" {
|
||||
embed.Thumbnail = &discordgo.MessageEmbedThumbnail{URL: art}
|
||||
}
|
||||
|
||||
msg, err := manager.session.ChannelMessageSendComplex(textChannelID, &discordgo.MessageSend{
|
||||
Embeds: []*discordgo.MessageEmbed{embed},
|
||||
Components: nowPlayingComponents(false, repeatSong, repeatQueue, paused),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
manager.mu.Lock()
|
||||
// Avoid calling getOrCreateGuildPlayer while holding manager.mu (it also locks).
|
||||
gp = manager.players[guildID]
|
||||
if gp == nil {
|
||||
gp = &guildPlayer{
|
||||
Player: manager.client.Player(snowflake.MustParse(guildID)),
|
||||
Queue: make([]TrackEntry, 0),
|
||||
}
|
||||
manager.players[guildID] = gp
|
||||
}
|
||||
gp.TextChannelID = textChannelID
|
||||
gp.NowPlayingMsgID = msg.ID
|
||||
manager.mu.Unlock()
|
||||
}
|
||||
|
||||
func artworkURL(info lavalink.TrackInfo) string {
|
||||
if info.ArtworkURL != nil && *info.ArtworkURL != "" {
|
||||
return *info.ArtworkURL
|
||||
}
|
||||
// Lavalink sets Identifier for YouTube videos; use standard thumbnail as fallback.
|
||||
if strings.EqualFold(info.SourceName, "youtube") && info.Identifier != "" {
|
||||
return "https://img.youtube.com/vi/" + info.Identifier + "/hqdefault.jpg"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatDuration(d lavalink.Duration, isStream bool) string {
|
||||
if isStream {
|
||||
return "Live"
|
||||
}
|
||||
secs := d.Seconds()
|
||||
if secs < 0 {
|
||||
secs = 0
|
||||
}
|
||||
h := secs / 3600
|
||||
m := (secs % 3600) / 60
|
||||
s := secs % 60
|
||||
if h > 0 {
|
||||
return fmt.Sprintf("%d:%02d:%02d", h, m, s)
|
||||
}
|
||||
return fmt.Sprintf("%d:%02d", m, s)
|
||||
}
|
||||
|
||||
func sourceIconURL(source string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(source)) {
|
||||
case "youtube":
|
||||
// Stable PNG favicon (Discord doesn't render .ico/.svg reliably in embeds)
|
||||
return "https://www.google.com/s2/favicons?sz=64&domain=youtube.com"
|
||||
case "soundcloud":
|
||||
return "https://www.google.com/s2/favicons?sz=64&domain=soundcloud.com"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user