|
@@ -0,0 +1,186 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/binary"
|
|
|
+ "flag"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "os"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/bwmarrin/discordgo"
|
|
|
+)
|
|
|
+
|
|
|
+func init() {
|
|
|
+ flag.StringVar(&token, "t", "", "Account Token")
|
|
|
+ flag.Parse()
|
|
|
+}
|
|
|
+
|
|
|
+var token string
|
|
|
+var buffer = make([][]byte, 0)
|
|
|
+
|
|
|
+func main() {
|
|
|
+ if token == "" {
|
|
|
+ fmt.Println("No token provided. Please run: airhorn -t <bot token>")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load the sound file.
|
|
|
+ err := loadSound()
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error loading sound: ", err)
|
|
|
+ fmt.Println("Please copy $GOPATH/src/github.com/bwmarrin/examples/airhorn/airhorn.dca to this directory.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create a new Discord session using the provided token.
|
|
|
+ dg, err := discordgo.New(token)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error creating Discord session: ", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Register ready as a callback for the ready events.
|
|
|
+ dg.AddHandler(ready)
|
|
|
+
|
|
|
+ // Register messageCreate as a callback for the messageCreate events.
|
|
|
+ dg.AddHandler(messageCreate)
|
|
|
+
|
|
|
+ // Register guildCreate as a callback for the guildCreate events.
|
|
|
+ dg.AddHandler(guildCreate)
|
|
|
+
|
|
|
+ // Open the websocket and begin listening.
|
|
|
+ err = dg.Open()
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error opening Discord session: ", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("Airhorn is now running. Press CTRL-C to exit.")
|
|
|
+ // Simple way to keep program running until CTRL-C is pressed.
|
|
|
+ <-make(chan struct{})
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func ready(s *discordgo.Session, event *discordgo.Ready) {
|
|
|
+ // Set the playing status.
|
|
|
+ s.UpdateStatus(0, "!airhorn")
|
|
|
+}
|
|
|
+
|
|
|
+// This function will be called (due to AddHandler above) every time a new
|
|
|
+// message is created on any channel that the autenticated bot has access to.
|
|
|
+func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
|
|
+ if strings.HasPrefix(m.Content, "!airhorn") {
|
|
|
+ // Find the channel that the message came from.
|
|
|
+ c, err := s.State.Channel(m.ChannelID)
|
|
|
+ if err != nil {
|
|
|
+ // Could not find channel.
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Find the guild for that channel.
|
|
|
+ g, err := s.State.Guild(c.GuildID)
|
|
|
+ if err != nil {
|
|
|
+ // Could not find guild.
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // Look for the message sender in that guilds current voice states.
|
|
|
+ for _, vs := range g.VoiceStates {
|
|
|
+ if vs.UserID == m.Author.ID {
|
|
|
+ err = playSound(s, g.ID, vs.ChannelID)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error playing sound:", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// This function will be called (due to AddHandler above) every time a new
|
|
|
+// guild is joined.
|
|
|
+func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) {
|
|
|
+ if event.Guild.Unavailable != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, channel := range event.Guild.Channels {
|
|
|
+ if channel.ID == event.Guild.ID {
|
|
|
+ s.ChannelMessageSend(channel.ID, "Airhorn is ready! Type !airhorn while in a voice channel to play a sound.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// loadSound attempts to load an encoded sound file from disk.
|
|
|
+func loadSound() error {
|
|
|
+ file, err := os.Open("airhorn.dca")
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error opening dca file :", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ var opuslen int16
|
|
|
+
|
|
|
+ for {
|
|
|
+ // Read opus frame length from dca file.
|
|
|
+ err = binary.Read(file, binary.LittleEndian, &opuslen)
|
|
|
+
|
|
|
+ // If this is the end of the file, just return.
|
|
|
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error reading from dca file :", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read encoded pcm from dca file.
|
|
|
+ InBuf := make([]byte, opuslen)
|
|
|
+ err = binary.Read(file, binary.LittleEndian, &InBuf)
|
|
|
+
|
|
|
+ // Should not be any end of file errors
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Error reading from dca file :", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Append encoded pcm data to the buffer.
|
|
|
+ buffer = append(buffer, InBuf)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// playSound plays the current buffer to the provided channel.
|
|
|
+func playSound(s *discordgo.Session, guildID, channelID string) (err error) {
|
|
|
+ // Join the provided voice channel.
|
|
|
+ vc, err := s.ChannelVoiceJoin(guildID, channelID, false, false)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sleep for a specified amount of time before playing the sound
|
|
|
+ time.Sleep(250 * time.Millisecond)
|
|
|
+
|
|
|
+ // Start speaking.
|
|
|
+ vc.Speaking(true)
|
|
|
+
|
|
|
+ // Send the buffer data.
|
|
|
+ for _, buff := range buffer {
|
|
|
+ vc.OpusSend <- buff
|
|
|
+ }
|
|
|
+
|
|
|
+ // Stop speaking
|
|
|
+ vc.Speaking(false)
|
|
|
+
|
|
|
+ // Sleep for a specificed amount of time before ending.
|
|
|
+ time.Sleep(250 * time.Millisecond)
|
|
|
+
|
|
|
+ // Disconnect from the provided voice channel.
|
|
|
+ vc.Disconnect()
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|