From f223c87353526b38e893544c64831dfd7e6da6e8 Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Tue, 4 Feb 2025 00:06:55 -0500 Subject: [PATCH] Starting this bad boy again. No api keys on the repo --- .env.example | 3 + .gitignore | 6 ++ README.md | 13 ++++ convert.sh | 14 +++++ go.mod | 16 +++++ go.sum | 18 ++++++ main.go | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 236 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100755 convert.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5a55221 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +DISCORD_APP_ID= +DISCORD_PUBLIC_KEY= +DISCORD_BOT_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6151927 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +testfile.wav +play.opus +output.opus +.env +*.wav +*.opus \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..efb41fb --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Papi Bot + +Currently in development. + +## Current testing steps + +Copy .env.example to .env + +Populate the discord bot keys + +`go run .` + +type `!` in any channel in the discord while you're in a voice channel. \ No newline at end of file diff --git a/convert.sh b/convert.sh new file mode 100755 index 0000000..88bff58 --- /dev/null +++ b/convert.sh @@ -0,0 +1,14 @@ +# ffmpeg -i testfile.wav -c:a libopus -ar 48000 -ac 2 -f opus -map_metadata -1 -vn -y play.opus +# ffmpeg -i testfile.wav \ +# -ac 2 -ar 48000 \ +# -b:a 96k \ +# -f opus \ +# -y \ +# -vbr off \ +# -frame_duration 20 \ +# pipe:1 > output.opus + +# Version used by the code sample i found +ffmpeg -i testfile.wav -hide_banner -loglevel quiet -f data -map 0:a -ar 48k -ac 2 -acodec libopus -b:a 128k pipe:1 > testfile.raw + +opusinfo output.opus \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1b8d797 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module git.thegrind.dev/thegrind/papibot + +go 1.22.2 + +require ( + github.com/bwmarrin/discordgo v0.28.1 + github.com/javif89/dotenv v0.0.6 +) + +require ( + github.com/gorilla/websocket v1.4.2 // indirect + github.com/jogramming/dca v0.0.0-20210930103944-155f5e5f0cc7 // indirect + github.com/jonas747/ogg v0.0.0-20161220051205-b4f6f4cf3757 // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4a8cbe7 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= +github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/javif89/dotenv v0.0.6 h1:xSXuDwEREMZi29iU5eUcbw8B+cWA3zafGBn1WfnZ06k= +github.com/javif89/dotenv v0.0.6/go.mod h1:4HS1Vewf6uMVysiBuaKRwEwFBuKUG0L2c+VH8jxQn18= +github.com/jogramming/dca v0.0.0-20210930103944-155f5e5f0cc7 h1:3nT2dRfYYlVBofXRG2WECKHDN5nwL22oyeBTClAONzU= +github.com/jogramming/dca v0.0.0-20210930103944-155f5e5f0cc7/go.mod h1:dxkp/IJD9cJBPedO7O+wWDidThTNKcl5/AkIbvLV5mE= +github.com/jonas747/ogg v0.0.0-20161220051205-b4f6f4cf3757 h1:Kyv+zTfWIGRNaz/4+lS+CxvuKVZSKFz/6G8E3BKKBRs= +github.com/jonas747/ogg v0.0.0-20161220051205-b4f6f4cf3757/go.mod h1:cZnNmdLiLpihzgIVqiaQppi9Ts3D4qF/M45//yW35nI= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2c3f395 --- /dev/null +++ b/main.go @@ -0,0 +1,166 @@ +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") +} \ No newline at end of file