package main import ( "flag" "fmt" "time" "github.com/bwmarrin/discordgo" "github.com/pion/rtp" "github.com/pion/webrtc/v3/pkg/media" "github.com/pion/webrtc/v3/pkg/media/oggwriter" ) // Variables used for command line parameters var ( Token string ChannelID string GuildID string ) func init() { flag.StringVar(&Token, "t", "", "Bot Token") flag.StringVar(&GuildID, "g", "", "Guild in which voice channel exists") flag.StringVar(&ChannelID, "c", "", "Voice channel to connect to") flag.Parse() } func createPionRTPPacket(p *discordgo.Packet) *rtp.Packet { return &rtp.Packet{ Header: rtp.Header{ Version: 2, // Taken from Discord voice docs PayloadType: 0x78, SequenceNumber: p.Sequence, Timestamp: p.Timestamp, SSRC: p.SSRC, }, Payload: p.Opus, } } func handleVoice(c chan *discordgo.Packet) { files := make(map[uint32]media.Writer) for p := range c { file, ok := files[p.SSRC] if !ok { var err error file, err = oggwriter.New(fmt.Sprintf("%d.ogg", p.SSRC), 48000, 2) if err != nil { fmt.Printf("failed to create file %d.ogg, giving up on recording: %v\n", p.SSRC, err) return } files[p.SSRC] = file } // Construct pion RTP packet from DiscordGo's type. rtp := createPionRTPPacket(p) err := file.WriteRTP(rtp) if err != nil { fmt.Printf("failed to write to file %d.ogg, giving up on recording: %v\n", p.SSRC, err) } } // Once we made it here, we're done listening for packets. Close all files for _, f := range files { f.Close() } } func main() { s, err := discordgo.New("Bot " + Token) if err != nil { fmt.Println("error creating Discord session:", err) return } defer s.Close() // We only really care about receiving voice state updates. s.Identify.Intents = discordgo.IntentsGuildVoiceStates err = s.Open() if err != nil { fmt.Println("error opening connection:", err) return } v, err := s.ChannelVoiceJoin(GuildID, ChannelID, true, false) if err != nil { fmt.Println("failed to join voice channel:", err) return } go func() { time.Sleep(10 * time.Second) close(v.OpusRecv) v.Close() }() handleVoice(v.OpusRecv) }