package commands import ( "errors" "fmt" "strconv" "strings" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgolink/v4/lavalink" "github.com/disgoorg/snowflake/v2" ) func guildID(event *events.ApplicationCommandInteractionCreate) (snowflake.ID, bool) { id := event.GuildID() if id == nil { return 0, false } return *id, true } func memberVoiceChannel(discordClient *bot.Client, guild snowflake.ID, userID snowflake.ID) (snowflake.ID, bool) { state, ok := discordClient.Caches.VoiceState(guild, userID) if !ok || state.ChannelID == nil { return 0, false } return *state.ChannelID, true } func ensureCanControlPlayback(discordClient *bot.Client, guild snowflake.ID, userID snowflake.ID) (bool, string) { botChannelID, botConnected := memberVoiceChannel(discordClient, guild, discordClient.ApplicationID) if !botConnected { return true, "" } userChannelID, userConnected := memberVoiceChannel(discordClient, guild, userID) if !userConnected || userChannelID != botChannelID { return false, fmt.Sprintf("I'm currently active in <#%s>. Join that channel to control playback.", botChannelID) } return true, "" } func requirePlaybackAccess(discordClient *bot.Client, event *events.ApplicationCommandInteractionCreate) (snowflake.ID, error) { gid, ok := guildID(event) if !ok { return 0, guildOnlyError(event) } if allowed, reason := ensureCanControlPlayback(discordClient, gid, event.User().ID); !allowed { return 0, rejectEphemeral(event, reason) } return gid, nil } func reply(event *events.ApplicationCommandInteractionCreate, content string) error { return event.CreateMessage(discord.NewMessageCreate().WithContent(content)) } func replyEphemeral(event *events.ApplicationCommandInteractionCreate, content string) error { return event.CreateMessage(discord.NewMessageCreate().WithContent(content).WithEphemeral(true)) } func guildOnlyError(event *events.ApplicationCommandInteractionCreate) error { return rejectEphemeral(event, "This command only works inside a server.") } func rejectEphemeral(event *events.ApplicationCommandInteractionCreate, content string) error { if err := replyEphemeral(event, content); err != nil { return err } return errors.New(content) } func replyWithError(event *events.ApplicationCommandInteractionCreate, userMessage string, cause error) error { if err := replyEphemeral(event, userMessage); err != nil { return err } return fmt.Errorf("%s: %w", userMessage, cause) } func intPtr(v int) *int { return &v } func parsePosition(raw string) (lavalink.Duration, error) { parts := strings.Split(raw, ":") if len(parts) == 0 || len(parts) > 3 { return 0, errors.New("expected seconds, mm:ss or hh:mm:ss") } segments := make([]int64, len(parts)) for i, part := range parts { n, err := strconv.ParseInt(strings.TrimSpace(part), 10, 64) if err != nil || n < 0 { return 0, errors.New("expected seconds, mm:ss or hh:mm:ss") } segments[i] = n } var totalSeconds int64 for _, segment := range segments { totalSeconds = totalSeconds*60 + segment } return lavalink.Duration(totalSeconds) * lavalink.Second, nil }