feat: support multiple radio stations
This commit is contained in:
parent
83843de4ee
commit
e5cf23619d
1 changed files with 87 additions and 26 deletions
107
main.go
107
main.go
|
@ -17,9 +17,27 @@ import (
|
||||||
"github.com/lmittmann/tint"
|
"github.com/lmittmann/tint"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Station represents a radio station
|
||||||
|
type Station struct {
|
||||||
|
Name string // Display name
|
||||||
|
StreamURL string // URL for the audio stream
|
||||||
|
Description string // Optional description
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
token string
|
token string
|
||||||
radioURL = "https://atres-live.europafm.com/live/europafm/bitrate_1.m3u8"
|
stations = map[string]Station{
|
||||||
|
"europafm": {
|
||||||
|
Name: "Europa FM",
|
||||||
|
StreamURL: "https://atres-live.europafm.com/live/europafm/bitrate_1.m3u8",
|
||||||
|
Description: "La radio de éxitos musicales",
|
||||||
|
},
|
||||||
|
"cope": {
|
||||||
|
Name: "COPE",
|
||||||
|
StreamURL: "http://flucast31-h-cloud.flumotion.com/cope/net1.mp3",
|
||||||
|
Description: "A tope con la",
|
||||||
|
},
|
||||||
|
}
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
debug bool
|
debug bool
|
||||||
guildID string // For testing in a specific guild
|
guildID string // For testing in a specific guild
|
||||||
|
@ -97,8 +115,8 @@ func main() {
|
||||||
"guild_id", i.GuildID)
|
"guild_id", i.GuildID)
|
||||||
|
|
||||||
switch cmdName {
|
switch cmdName {
|
||||||
case "europafm":
|
case "radio":
|
||||||
handleEuropaFMCommand(s, i)
|
handleRadioCommand(s, i)
|
||||||
default:
|
default:
|
||||||
logger.Warn("Unknown command received", "command", cmdName)
|
logger.Warn("Unknown command received", "command", cmdName)
|
||||||
}
|
}
|
||||||
|
@ -184,18 +202,27 @@ func main() {
|
||||||
|
|
||||||
// Define the command
|
// Define the command
|
||||||
cmd := &discordgo.ApplicationCommand{
|
cmd := &discordgo.ApplicationCommand{
|
||||||
Name: "europafm",
|
Name: "radio",
|
||||||
Description: "Control EuropaFM radio streaming",
|
Description: "Controla la transmisión de radio",
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
{
|
{
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
Name: "play",
|
Name: "play",
|
||||||
Description: "Start streaming EuropaFM radio in your voice channel",
|
Description: "Comienza a transmitir una radio en tu canal de voz",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "station",
|
||||||
|
Description: "Estación de radio a reproducir",
|
||||||
|
Required: true,
|
||||||
|
Choices: getStationChoices(),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
Name: "stop",
|
Name: "stop",
|
||||||
Description: "Stop streaming and leave the voice channel",
|
Description: "Detiene la transmisión y abandona el canal de voz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -237,7 +264,9 @@ func main() {
|
||||||
|
|
||||||
// Disconnect from all voice channels
|
// Disconnect from all voice channels
|
||||||
for _, vc := range dg.VoiceConnections {
|
for _, vc := range dg.VoiceConnections {
|
||||||
vc.Disconnect()
|
if vc.Disconnect() != nil {
|
||||||
|
logger.Error("Error disconnecting from voice channel", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the Discord session
|
// Close the Discord session
|
||||||
|
@ -259,7 +288,7 @@ func logRawInteraction(s *discordgo.Session, e *discordgo.Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detailed logging for interactions
|
// Detailed logging for interactions
|
||||||
func logInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func logInteraction(_ *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
if !debug {
|
if !debug {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -275,11 +304,11 @@ func logInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
"user", i.User != nil)
|
"user", i.User != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func handleRadioCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
options := i.ApplicationCommandData().Options
|
options := i.ApplicationCommandData().Options
|
||||||
if len(options) == 0 {
|
if len(options) == 0 {
|
||||||
logger.Warn("Received europafm command with no subcommand", "interaction_id", i.ID)
|
logger.Warn("Received radio command with no subcommand", "interaction_id", i.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +320,7 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
Content: "Processing your request...",
|
Content: "Procesando tu solicitud...",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -302,15 +331,31 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
|
|
||||||
switch subCommand {
|
switch subCommand {
|
||||||
case "play":
|
case "play":
|
||||||
|
// Check if station is specified
|
||||||
|
stationName := "europafm" // Default value
|
||||||
|
if len(options[0].Options) > 0 {
|
||||||
|
stationName = options[0].Options[0].StringValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get station URL
|
||||||
|
station, ok := stations[stationName]
|
||||||
|
if !ok {
|
||||||
|
logger.Warn("Unknown station requested", "station", stationName)
|
||||||
|
followupMsg(s, i, "Estación de radio desconocida.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = logger.With("station", stationName, "station_name", station.Name)
|
||||||
|
|
||||||
// Find the guild for that channel
|
// Find the guild for that channel
|
||||||
guildID := i.GuildID
|
guildID := i.GuildID
|
||||||
logger := logger.With("guild_id", guildID)
|
logger = logger.With("guild_id", guildID)
|
||||||
|
|
||||||
// Find the voice channel the user is in
|
// Find the voice channel the user is in
|
||||||
vs, err := findUserVoiceState(ctx, s, guildID, i.Member.User.ID)
|
vs, err := findUserVoiceState(ctx, s, guildID, i.Member.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("User not in voice channel", "error", err)
|
logger.Warn("User not in voice channel", "error", err)
|
||||||
followupMsg(s, i, "You need to be in a voice channel to use this command.")
|
followupMsg(s, i, "Necesitas estar en un canal de voz para usar este comando.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +372,7 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
vc, err := s.ChannelVoiceJoin(guildID, vs.ChannelID, false, true)
|
vc, err := s.ChannelVoiceJoin(guildID, vs.ChannelID, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error joining voice channel", "error", err)
|
logger.Error("Error joining voice channel", "error", err)
|
||||||
followupMsg(s, i, "Error joining voice channel. Please try again.")
|
followupMsg(s, i, "Error al unirse al canal de voz. Por favor, inténtalo de nuevo.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +380,7 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Update message
|
// Update message
|
||||||
followupMsg(s, i, "Now streaming EuropaFM radio in your voice channel!")
|
followupMsg(s, i, fmt.Sprintf("¡Ahora transmitiendo la radio %s en tu canal de voz!", station.Name))
|
||||||
|
|
||||||
// Start streaming - this is a blocking call, so we run it in a goroutine
|
// Start streaming - this is a blocking call, so we run it in a goroutine
|
||||||
logger.Info("Starting audio stream")
|
logger.Info("Starting audio stream")
|
||||||
|
@ -347,11 +392,11 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
defer logger.Info("Audio streaming ended")
|
defer logger.Info("Audio streaming ended")
|
||||||
|
|
||||||
// Start playing audio with enhanced error logging
|
// Start playing audio with enhanced error logging
|
||||||
err := playAudioStream(vc, radioURL, stopChan, logger)
|
err := playAudioStream(vc, station.StreamURL, stopChan, logger)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Audio streaming error", "error", err)
|
logger.Error("Audio streaming error", "error", err)
|
||||||
followupMsg(s, i, "The audio stream has ended unexpectedly. Please try again later.")
|
followupMsg(s, i, "La transmisión de audio ha terminado inesperadamente. Por favor, inténtalo más tarde.")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -360,13 +405,15 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
|
||||||
vc, err := findExistingVoiceConnection(ctx, s, i.GuildID)
|
vc, err := findExistingVoiceConnection(ctx, s, i.GuildID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info("No active voice connection found", "error", err)
|
logger.Info("No active voice connection found", "error", err)
|
||||||
followupMsg(s, i, "Not currently streaming in any channel.")
|
followupMsg(s, i, "No hay transmisión activa en ningún canal.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Disconnecting from voice channel")
|
logger.Info("Disconnecting from voice channel")
|
||||||
vc.Disconnect()
|
if vc.Disconnect() != nil {
|
||||||
followupMsg(s, i, "Disconnected from voice channel.")
|
logger.Error("Error disconnecting from voice channel", "error", err)
|
||||||
|
}
|
||||||
|
followupMsg(s, i, "Desconectado del canal de voz.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +427,7 @@ func followupMsg(s *discordgo.Session, i *discordgo.InteractionCreate, content s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findUserVoiceState(ctx context.Context, s *discordgo.Session, guildID, userID string) (*discordgo.VoiceState, error) {
|
func findUserVoiceState(_ context.Context, s *discordgo.Session, guildID, userID string) (*discordgo.VoiceState, error) {
|
||||||
guild, err := s.State.Guild(guildID)
|
guild, err := s.State.Guild(guildID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error getting guild", "guild_id", guildID, "error", err)
|
logger.Error("Error getting guild", "guild_id", guildID, "error", err)
|
||||||
|
@ -396,7 +443,7 @@ func findUserVoiceState(ctx context.Context, s *discordgo.Session, guildID, user
|
||||||
return nil, fmt.Errorf("user not in any voice channel")
|
return nil, fmt.Errorf("user not in any voice channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findExistingVoiceConnection(ctx context.Context, s *discordgo.Session, guildID string) (*discordgo.VoiceConnection, error) {
|
func findExistingVoiceConnection(_ context.Context, s *discordgo.Session, guildID string) (*discordgo.VoiceConnection, error) {
|
||||||
for _, vc := range s.VoiceConnections {
|
for _, vc := range s.VoiceConnections {
|
||||||
if vc.GuildID == guildID {
|
if vc.GuildID == guildID {
|
||||||
return vc, nil
|
return vc, nil
|
||||||
|
@ -480,3 +527,17 @@ func playAudioStream(vc *discordgo.VoiceConnection, url string, stopChan chan bo
|
||||||
|
|
||||||
return fmt.Errorf("audio stream ended unexpectedly")
|
return fmt.Errorf("audio stream ended unexpectedly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getStationChoices generates the choices for the command options based on the stations map
|
||||||
|
func getStationChoices() []*discordgo.ApplicationCommandOptionChoice {
|
||||||
|
choices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(stations))
|
||||||
|
|
||||||
|
for key, station := range stations {
|
||||||
|
choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
|
||||||
|
Name: station.Name,
|
||||||
|
Value: key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return choices
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue