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

This commit is contained in:
Javier Feliz 2025-02-04 00:06:55 -05:00
commit f223c87353
7 changed files with 236 additions and 0 deletions

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
DISCORD_APP_ID=
DISCORD_PUBLIC_KEY=
DISCORD_BOT_TOKEN=

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
testfile.wav
play.opus
output.opus
.env
*.wav
*.opus

13
README.md Normal file
View File

@ -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.

14
convert.sh Executable file
View File

@ -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

16
go.mod Normal file
View File

@ -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
)

18
go.sum Normal file
View File

@ -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=

166
main.go Normal file
View File

@ -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")
}