diff --git a/examples/airhorn/README.md b/examples/airhorn/README.md
new file mode 100644
index 0000000..e9e49e1
--- /dev/null
+++ b/examples/airhorn/README.md
@@ -0,0 +1,41 @@
+
+Airhorn Example
+====
+
+This example demonstrates how to utilize DiscordGo to listen to an !airhorn
+command in a channel and play a sound to that users current voice channel.
+
+### Build
+
+This assumes you already have a working Go environment setup and that
+DiscordGo is correctly installed on your system.
+
+```sh
+go install github.com/bwmarrin/discordgo/examples/airhorn
+cd $GOPATH/bin
+cp ../src/github.com/bwmarrin/discordgo/examples/airhorn/airhorn.dca .
+```
+
+### Usage
+
+```
+Usage of ./airhorn:
+ -t string
+ Account Token
+```
+
+The below example shows how to start the bot.
+
+```sh
+./airhorn -t
+```
+
+### Creating sounds
+
+Airhorn bot uses DCA files that are pre-computed files that are easy to send to Discord.
+
+If you would like to create your own DCA files, please use [https://github.com/bwmarrin/dca/tree/master/cmd/dca](DCA).
+
+```sh
+./dca -i -raw >
+```
diff --git a/examples/airhorn/airhorn.dca b/examples/airhorn/airhorn.dca
new file mode 100644
index 0000000..39e7167
Binary files /dev/null and b/examples/airhorn/airhorn.dca differ
diff --git a/examples/airhorn/main.go b/examples/airhorn/main.go
new file mode 100644
index 0000000..ba0312e
--- /dev/null
+++ b/examples/airhorn/main.go
@@ -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 ")
+ 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
+}
diff --git a/examples/new_basic/main.go b/examples/new_basic/main.go
index 3d2fb97..7b2dc73 100644
--- a/examples/new_basic/main.go
+++ b/examples/new_basic/main.go
@@ -26,6 +26,7 @@ func init() {
func main() {
// Create a new Discord session using the provided login information.
+ // Use discordgo.New(Token) to just use a token for login.
dg, err := discordgo.New(Email, Password, Token)
if err != nil {
fmt.Println("error creating Discord session,", err)