commit f223c87353526b38e893544c64831dfd7e6da6e8
Author: Javier Feliz <javier0eduardo@hotmail.com>
Date:   Tue Feb 4 00:06:55 2025 -0500

    Starting this bad boy again. No api keys on the repo

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