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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Station represents a radio station
 | 
			
		||||
type Station struct {
 | 
			
		||||
	Name        string // Display name
 | 
			
		||||
	StreamURL   string // URL for the audio stream
 | 
			
		||||
	Description string // Optional description
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	token    string
 | 
			
		||||
	radioURL = "https://atres-live.europafm.com/live/europafm/bitrate_1.m3u8"
 | 
			
		||||
	logger   *slog.Logger
 | 
			
		||||
	debug    bool
 | 
			
		||||
	guildID  string // For testing in a specific guild
 | 
			
		||||
	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
 | 
			
		||||
	debug   bool
 | 
			
		||||
	guildID string // For testing in a specific guild
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
| 
						 | 
				
			
			@ -97,8 +115,8 @@ func main() {
 | 
			
		|||
			"guild_id", i.GuildID)
 | 
			
		||||
 | 
			
		||||
		switch cmdName {
 | 
			
		||||
		case "europafm":
 | 
			
		||||
			handleEuropaFMCommand(s, i)
 | 
			
		||||
		case "radio":
 | 
			
		||||
			handleRadioCommand(s, i)
 | 
			
		||||
		default:
 | 
			
		||||
			logger.Warn("Unknown command received", "command", cmdName)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -184,18 +202,27 @@ func main() {
 | 
			
		|||
 | 
			
		||||
	// Define the command
 | 
			
		||||
	cmd := &discordgo.ApplicationCommand{
 | 
			
		||||
		Name:        "europafm",
 | 
			
		||||
		Description: "Control EuropaFM radio streaming",
 | 
			
		||||
		Name:        "radio",
 | 
			
		||||
		Description: "Controla la transmisión de radio",
 | 
			
		||||
		Options: []*discordgo.ApplicationCommandOption{
 | 
			
		||||
			{
 | 
			
		||||
				Type:        discordgo.ApplicationCommandOptionSubCommand,
 | 
			
		||||
				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,
 | 
			
		||||
				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
 | 
			
		||||
	for _, vc := range dg.VoiceConnections {
 | 
			
		||||
		vc.Disconnect()
 | 
			
		||||
		if vc.Disconnect() != nil {
 | 
			
		||||
			logger.Error("Error disconnecting from voice channel", "error", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Close the Discord session
 | 
			
		||||
| 
						 | 
				
			
			@ -259,7 +288,7 @@ func logRawInteraction(s *discordgo.Session, e *discordgo.Event) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Detailed logging for interactions
 | 
			
		||||
func logInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
 | 
			
		||||
func logInteraction(_ *discordgo.Session, i *discordgo.InteractionCreate) {
 | 
			
		||||
	if !debug {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -275,11 +304,11 @@ func logInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
 | 
			
		|||
		"user", i.User != nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
 | 
			
		||||
func handleRadioCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	options := i.ApplicationCommandData().Options
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -291,7 +320,7 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
 | 
			
		|||
	err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
 | 
			
		||||
		Type: discordgo.InteractionResponseChannelMessageWithSource,
 | 
			
		||||
		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 {
 | 
			
		||||
	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
 | 
			
		||||
		guildID := i.GuildID
 | 
			
		||||
		logger := logger.With("guild_id", guildID)
 | 
			
		||||
		logger = logger.With("guild_id", guildID)
 | 
			
		||||
 | 
			
		||||
		// Find the voice channel the user is in
 | 
			
		||||
		vs, err := findUserVoiceState(ctx, s, guildID, i.Member.User.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -327,7 +372,7 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
 | 
			
		|||
		vc, err := s.ChannelVoiceJoin(guildID, vs.ChannelID, false, true)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -335,7 +380,7 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
 | 
			
		|||
		time.Sleep(2 * time.Second)
 | 
			
		||||
 | 
			
		||||
		// 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
 | 
			
		||||
		logger.Info("Starting audio stream")
 | 
			
		||||
| 
						 | 
				
			
			@ -347,11 +392,11 @@ func handleEuropaFMCommand(s *discordgo.Session, i *discordgo.InteractionCreate)
 | 
			
		|||
			defer logger.Info("Audio streaming ended")
 | 
			
		||||
 | 
			
		||||
			// Start playing audio with enhanced error logging
 | 
			
		||||
			err := playAudioStream(vc, radioURL, stopChan, logger)
 | 
			
		||||
			err := playAudioStream(vc, station.StreamURL, stopChan, logger)
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				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)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logger.Info("Disconnecting from voice channel")
 | 
			
		||||
		vc.Disconnect()
 | 
			
		||||
		followupMsg(s, i, "Disconnected from voice channel.")
 | 
			
		||||
		if vc.Disconnect() != nil {
 | 
			
		||||
			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)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		if vc.GuildID == guildID {
 | 
			
		||||
			return vc, nil
 | 
			
		||||
| 
						 | 
				
			
			@ -480,3 +527,17 @@ func playAudioStream(vc *discordgo.VoiceConnection, url string, stopChan chan bo
 | 
			
		|||
 | 
			
		||||
	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