feat(music): add /music toggle and gate playback behind it

Music could be started and controlled with no way to turn it off for
a server. Adds a music_settings table (mirrors the same one
velox-dashboard-api's dashboard toggle reads/writes), a /music toggle
command gated to Manage Server, and checks in /play, /queue, and
/volume so they refuse to run when a server has music turned off.
This commit is contained in:
2026-08-29 03:49:52 +01:00
parent 95ba434194
commit 691b505175
12 changed files with 240 additions and 6 deletions
+2
View File
@@ -33,6 +33,7 @@ var AllCommands = []*discordgo.ApplicationCommand{
cmdmusic.Play,
cmdmusic.Queue,
cmdmusic.Volume,
cmdmusic.Music,
meeting.Meeting,
moderation.Moderation,
timezone.Timezone,
@@ -62,6 +63,7 @@ var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCre
"play": cmdmusic.PlayHandler,
"queue": cmdmusic.QueueHandler,
"volume": cmdmusic.VolumeHandler,
"music": cmdmusic.MusicHandler,
"config": config.ConfigHandler,
}
+5
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"net/url"
"strings"
"velox-bot/internal/commands/music/shared"
"velox-bot/internal/music"
"github.com/bwmarrin/discordgo"
@@ -23,6 +24,10 @@ var Play = &discordgo.ApplicationCommand{
}
func PlayHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !shared.RequireMusicEnabled(s, i) {
return
}
data := i.ApplicationCommandData()
if len(data.Options) == 0 {
return
+4
View File
@@ -3,6 +3,7 @@ package public
import (
"fmt"
"strings"
"velox-bot/internal/commands/music/shared"
"velox-bot/internal/music"
"github.com/bwmarrin/discordgo"
@@ -14,6 +15,9 @@ var Queue = &discordgo.ApplicationCommand{
}
func QueueHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !shared.RequireMusicEnabled(s, i) {
return
}
if !memberHasDJRole(s, i.GuildID, i.Member) {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
+63
View File
@@ -0,0 +1,63 @@
package public
import (
"context"
"velox-bot/internal/commands/music/shared"
"velox-bot/internal/db/services"
"github.com/bwmarrin/discordgo"
)
var Music = &discordgo.ApplicationCommand{
Name: "music",
Description: "Music settings",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "toggle",
Description: "Turn the music player on or off for this server",
},
},
}
func MusicHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.GuildID == "" {
shared.RespondEphemeral(s, i, "This command can only be used in a server.")
return
}
if !shared.RequireManageGuild(s, i) {
return
}
if !shared.RequireMusicService(s, i) {
return
}
data := i.ApplicationCommandData()
if len(data.Options) == 0 {
shared.RespondEphemeral(s, i, "Missing subcommand.")
return
}
switch data.Options[0].Name {
case "toggle":
handleToggle(s, i)
}
}
func handleToggle(s *discordgo.Session, i *discordgo.InteractionCreate) {
guildID, ok := shared.ParseGuildID(s, i)
if !ok {
return
}
enabled, err := services.Global.MusicSettings.ToggleMusic(context.Background(), guildID)
if err != nil {
shared.RespondEphemeral(s, i, "Failed to toggle music playback.")
return
}
status := "disabled"
if enabled {
status = "enabled"
}
shared.RespondEphemeral(s, i, "Music playback "+status+" for this server.")
}
+4 -1
View File
@@ -2,6 +2,7 @@ package public
import (
"fmt"
"velox-bot/internal/commands/music/shared"
"velox-bot/internal/music"
"github.com/bwmarrin/discordgo"
@@ -23,6 +24,9 @@ var Volume = &discordgo.ApplicationCommand{
}
func VolumeHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !shared.RequireMusicEnabled(s, i) {
return
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
@@ -60,4 +64,3 @@ func VolumeHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
}
func ptrFloat(v float64) *float64 { return &v }
+4 -3
View File
@@ -7,9 +7,10 @@ import (
)
var (
Play *discordgo.ApplicationCommand = public.Play
Queue *discordgo.ApplicationCommand = public.Queue
Play *discordgo.ApplicationCommand = public.Play
Queue *discordgo.ApplicationCommand = public.Queue
Volume *discordgo.ApplicationCommand = public.Volume
Music *discordgo.ApplicationCommand = public.Music
)
func PlayHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.PlayHandler(s, i) }
@@ -17,4 +18,4 @@ func QueueHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
public.QueueHandler(s, i)
}
func VolumeHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.VolumeHandler(s, i) }
func MusicHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.MusicHandler(s, i) }
+61
View File
@@ -0,0 +1,61 @@
package shared
import (
"context"
"strconv"
"velox-bot/internal/db/services"
"github.com/bwmarrin/discordgo"
)
func RequireMusicEnabled(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
guildID, ok := ParseGuildID(s, i)
if !ok {
return false
}
enabled, err := services.Global.MusicSettings.IsMusicEnabled(context.Background(), guildID)
if err != nil {
RespondEphemeral(s, i, "Failed to load music settings.")
return false
}
if !enabled {
RespondEphemeral(s, i, "Music is disabled on this server. Use /music toggle to enable it.")
return false
}
return true
}
func RequireManageGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 {
RespondEphemeral(s, i, "You need the **Manage Server** permission to use this.")
return false
}
return true
}
func RequireMusicService(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
if services.Global == nil || services.Global.MusicSettings == nil {
RespondEphemeral(s, i, "Music service is not available.")
return false
}
return true
}
func ParseGuildID(s *discordgo.Session, i *discordgo.InteractionCreate) (int64, bool) {
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
if err != nil {
RespondEphemeral(s, i, "Invalid guild ID.")
return 0, false
}
return guildID, true
}
func RespondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: msg,
Flags: discordgo.MessageFlagsEphemeral,
},
})
}