feat: support multiple radio stations
This commit is contained in:
		
							parent
							
								
									83843de4ee
								
							
						
					
					
						commit
						e5cf23619d
					
				
					 1 changed files with 87 additions and 26 deletions
				
			
		
							
								
								
									
										113
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										113
									
								
								main.go
									
										
									
									
									
								
							| 
						 | 
					@ -17,12 +17,30 @@ 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{
 | 
				
			||||||
	logger   *slog.Logger
 | 
							"europafm": {
 | 
				
			||||||
	debug    bool
 | 
								Name:        "Europa FM",
 | 
				
			||||||
	guildID  string // For testing in a specific guild
 | 
								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
 | 
				
			||||||
 | 
						debug   bool
 | 
				
			||||||
 | 
						guildID string // For testing in a specific guild
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
| 
						 | 
					@ -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