1package main
2
3import (
4	"flag"
5	"fmt"
6	"time"
7
8	"github.com/bwmarrin/discordgo"
9	"github.com/pion/rtp"
10	"github.com/pion/webrtc/v3/pkg/media"
11	"github.com/pion/webrtc/v3/pkg/media/oggwriter"
12)
13
14// Variables used for command line parameters
15var (
16	Token     string
17	ChannelID string
18	GuildID   string
19)
20
21func init() {
22	flag.StringVar(&Token, "t", "", "Bot Token")
23	flag.StringVar(&GuildID, "g", "", "Guild in which voice channel exists")
24	flag.StringVar(&ChannelID, "c", "", "Voice channel to connect to")
25	flag.Parse()
26}
27
28func createPionRTPPacket(p *discordgo.Packet) *rtp.Packet {
29	return &rtp.Packet{
30		Header: rtp.Header{
31			Version: 2,
32			// Taken from Discord voice docs
33			PayloadType:    0x78,
34			SequenceNumber: p.Sequence,
35			Timestamp:      p.Timestamp,
36			SSRC:           p.SSRC,
37		},
38		Payload: p.Opus,
39	}
40}
41
42func handleVoice(c chan *discordgo.Packet) {
43	files := make(map[uint32]media.Writer)
44	for p := range c {
45		file, ok := files[p.SSRC]
46		if !ok {
47			var err error
48			file, err = oggwriter.New(fmt.Sprintf("%d.ogg", p.SSRC), 48000, 2)
49			if err != nil {
50				fmt.Printf("failed to create file %d.ogg, giving up on recording: %v\n", p.SSRC, err)
51				return
52			}
53			files[p.SSRC] = file
54		}
55		// Construct pion RTP packet from DiscordGo's type.
56		rtp := createPionRTPPacket(p)
57		err := file.WriteRTP(rtp)
58		if err != nil {
59			fmt.Printf("failed to write to file %d.ogg, giving up on recording: %v\n", p.SSRC, err)
60		}
61	}
62
63	// Once we made it here, we're done listening for packets. Close all files
64	for _, f := range files {
65		f.Close()
66	}
67}
68
69func main() {
70	s, err := discordgo.New("Bot " + Token)
71	if err != nil {
72		fmt.Println("error creating Discord session:", err)
73		return
74	}
75	defer s.Close()
76
77	// We only really care about receiving voice state updates.
78	s.Identify.Intents = discordgo.IntentsGuildVoiceStates
79
80	err = s.Open()
81	if err != nil {
82		fmt.Println("error opening connection:", err)
83		return
84	}
85
86	v, err := s.ChannelVoiceJoin(GuildID, ChannelID, true, false)
87	if err != nil {
88		fmt.Println("failed to join voice channel:", err)
89		return
90	}
91
92	go func() {
93		time.Sleep(10 * time.Second)
94		close(v.OpusRecv)
95		v.Close()
96	}()
97
98	handleVoice(v.OpusRecv)
99}
100