package main import ( "fmt" "io" "log" "os" "os/signal" "os/exec" "runtime" "strings" "syscall" "time" dg "github.com/bwmarrin/discordgo" "github.com/javif89/dotenv" ) var commandPrefix string = "!" func main() { env := dotenv.Load(".env") botToken := env.Get("DISCORD_BOT_TOKEN") log.Println("Starting bot") discord, err := dg.New(fmt.Sprintf("Bot %s", botToken)) if err != nil { log.Println("Error starting the bot", err) return } discord.AddHandler(ready) discord.AddHandler(handleCommand) // This will receive an event when a message is sent discord.Identify.Intents = dg.IntentsGuilds | dg.IntentsGuildMessages | dg.IntentsGuildVoiceStates err = discord.Open() if err != nil { log.Println("Error opening Discord session: ", err) } log.Println("Bot is running. Press CTRL+C to exit.") sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-sc log.Println("Bot is exiting...") discord.Close() log.Println("Goodbye!") } func ready(s *dg.Session, event *dg.Ready) { log.Println("Bot is ready") } func handleCommand(s *dg.Session, msg *dg.MessageCreate) { // Ignore any message sent by the bot itself if msg.Author.ID == s.State.User.ID { return } if !strings.HasPrefix(msg.Content, commandPrefix) { return } // Get the channel msgChannel, err := s.State.Channel(msg.ChannelID) if err != nil { log.Println("Could not find the channel the message came from") return } msgServer, err := s.State.Guild(msgChannel.GuildID) if err != nil { log.Println("Failed to get server for message") } var voiceChannelId string for _, vs := range msgServer.VoiceStates { if vs.UserID == msg.Author.ID { voiceChannelId = vs.ChannelID } } voiceChannel, err := s.ChannelVoiceJoin(msgServer.ID, voiceChannelId, false, true) if err != nil { log.Println("Failed to join voice channel") } log.Println("Joined channel. Playing sound") voiceChannel.Speaking(true) time.Sleep(time.Second * 1) log.Println("Starting ffmpeg stream") // I got this implementation from: // https://github.com/nhooyr/botatouille/blob/7e1cd9d5a8d517fd43fd11599b2a62bf832a5c96/cmd/botatouille/music/music.go#L62-L104 // after hours of searching. ffmpeg := exec.Command( "ffmpeg", "-i", "testfile.wav", "-hide_banner", "-loglevel", "quiet", "-i", "testfile.wav", "-f", "data", "-map", "0:a", "-ar", "48k", "-ac", "2", "-acodec", "libopus", "-b:a", "128k", "pipe:1") ffmpegOut, err := ffmpeg.StdoutPipe() if err != nil { log.Fatal(err) } framesChan := make(chan []byte, 100000) go func() { for { voiceChannel.OpusSend <- <-framesChan } }() runtime.LockOSThread() err = ffmpeg.Start() if err != nil { log.Fatal(err) } packets := [][]byte{} for { // I read in the RFC that frames will not be bigger than this size p := make([]byte, 960) n, err := ffmpegOut.Read(p) if err != nil { log.Printf("Bytes: %d", n) if err == io.EOF { log.Println("Done streaming") break } log.Fatal(err) } log.Printf("Sending opus frame: %d bytes", n) packets = append(packets, p[:n]) } log.Println("Iterating through packets") for _, p := range packets { log.Printf("%d bytes", len(p)) } for _, p := range packets { log.Printf("Sending packet: %d bytes", len(p)) voiceChannel.OpusSend <- p } voiceChannel.Speaking(false) log.Println("Disconnecting from voice channel") time.Sleep(time.Second * 2) voiceChannel.Disconnect() log.Println("Disconnected") }