package main import ( "fmt" "log" "os" "os/exec" "os/signal" "syscall" "time" "git.thegrind.dev/thegrind/papibot/pkg/opusframes" dg "github.com/bwmarrin/discordgo" "github.com/javif89/dotenv" ) var commandHandlers = map[string]func(s *dg.Session, i *dg.InteractionCreate){ "play": playCommand, } func main() { env := dotenv.Load(".env") botToken := env.Get("DISCORD_BOT_TOKEN") log.Println("Starting bot") var discord *dg.Session var err error discord, err = dg.New(fmt.Sprintf("Bot %s", botToken)) if err != nil { log.Println("Error starting the bot", err) return } discord.AddHandler(ready) discord.Identify.Intents = dg.IntentsGuilds | dg.IntentsGuildMessages | dg.IntentsGuildVoiceStates err = discord.Open() if err != nil { log.Println("Error opening Discord session: ", err) } // Register slash commands discord.AddHandler(handleSlashCommand) 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...") log.Println("Removing commands") appId := env.Get("DISCORD_APP_ID") cmds, _ := discord.ApplicationCommands(appId, "338782945110392832") for _, cmd := range cmds { discord.ApplicationCommandDelete(appId, "338782945110392832", cmd.ID) } discord.Close() log.Println("Goodbye!") } func playCommand(s *dg.Session, i *dg.InteractionCreate) { log.Println("Handling play command") options := i.ApplicationCommandData().Options url := options[0].StringValue() s.InteractionRespond(i.Interaction, &dg.InteractionResponse{ Type: dg.InteractionResponseChannelMessageWithSource, Data: &dg.InteractionResponseData{ Content: fmt.Sprintf("Playing dat music baybeee"), }, }) downloadVideo(url) // Get the channel msgServer, err := s.State.Guild(i.GuildID) if err != nil { log.Println("Failed to get server for action") } var voiceChannelId string for _, vs := range msgServer.VoiceStates { if vs.UserID == i.Member.User.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") playOnVoiceChannel(voiceChannel) voiceChannel.Speaking(false) log.Println("Disconnecting from voice channel") time.Sleep(time.Second * 2) voiceChannel.Disconnect() log.Println("Disconnected") } func playOnVoiceChannel(voiceChannel *dg.VoiceConnection) { log.Println("Decoding") start := time.Now() frames, err := opusframes.Decode("vid.of") if err != nil { log.Println("Decoding error: ", err) return } duration := time.Since(start) log.Printf("Decoding took: %s", duration) // for _, f := range frames { // log.Printf("Got frame. Size: %d", len(f)) // } voiceChannel.Speaking(true) time.Sleep(time.Second * 2) log.Println("Playing sound") for _, p := range frames { log.Printf("Sending packet: %d bytes", len(p)) voiceChannel.OpusSend <- p } } func handleSlashCommand(s *dg.Session, i *dg.InteractionCreate) { log.Println("Command received?") if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { h(s, i) } } func ready(s *dg.Session, event *dg.Ready) { commands := []*dg.ApplicationCommand{ { Name: "play", Description: "Play a song from youtube, spotify, apple music, etc", Options: []*dg.ApplicationCommandOption{ { Type: dg.ApplicationCommandOptionString, Name: "url", Description: "URL to the song", Required: true, }, }, }, } env := dotenv.Load(".env") appId := env.Get("DISCORD_APP_ID") log.Println("Bot is ready") log.Println("Bulk registering commands") _, err := s.ApplicationCommandBulkOverwrite(appId, "338782945110392832", commands) if err != nil { log.Fatal(err) } log.Println("Done") } func downloadVideo(url string) error { log.Printf("Downloading: %s", url) os.Remove("vid.webm") cmd := exec.Command( "yt-dlp", "-i", "-q", "-f", "bestaudio", "-o", "vid.%(ext)s", url) err := cmd.Run() log.Println("Downloaded") start := time.Now() opusframes.Encode("vid.webm", "vid.of") duration := time.Since(start) log.Printf("Encoding took: %s", duration) return err }