diff --git a/README.md b/README.md index 9023210..44744c3 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ that information in a nice format. Below is a list of examples and other projects using Discordgo. Please submit an issue if you would like your project added or removed from this list -- [Basic - New](https://github.com/bwmarrin/discordgo/tree/develop/example/new_basic) A basic example using the easy New() helper function -- [Basic - API](https://github.com/bwmarrin/discordgo/tree/develop/example/api_basic) A basic example using the low level API functions. +- [Basic - New](https://github.com/bwmarrin/discordgo/tree/develop/examples/new_basic) A basic example using the easy New() helper function +- [Basic - API](https://github.com/bwmarrin/discordgo/tree/develop/examples/api_basic) A basic example using the low level API functions. - [Bruxism](https://github.com/iopred/bruxism) A chat bot for YouTube and Discord - [GoGerard](https://github.com/GoGerard/GoGerard) A modern bot for Discord - [Digo](https://github.com/sethdmoore/digo) A pluggable bot for your Discord server diff --git a/example/api_basic/api_basic.go b/examples/api_basic/api_basic.go similarity index 100% rename from example/api_basic/api_basic.go rename to examples/api_basic/api_basic.go diff --git a/example/new_basic/new_basic.go b/examples/new_basic/new_basic.go similarity index 100% rename from example/new_basic/new_basic.go rename to examples/new_basic/new_basic.go diff --git a/message.go b/message.go new file mode 100644 index 0000000..6a349bd --- /dev/null +++ b/message.go @@ -0,0 +1,49 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains code related to the Message struct + +package discordgo + +import ( + "fmt" + "strings" +) + +// A Message stores all data related to a specific Discord message. +type Message struct { + ID string `json:"id"` + Author User `json:"author"` + Content string `json:"content"` + Attachments []*Attachment `json:"attachments"` + Tts bool `json:"tts"` + Embeds []*Embed `json:"embeds"` + Timestamp string `json:"timestamp"` + MentionEveryone bool `json:"mention_everyone"` + EditedTimestamp string `json:"edited_timestamp"` + Mentions []*User `json:"mentions"` + ChannelID string `json:"channel_id"` +} + +// An Attachment stores data for message attachments. +type Attachment struct { //TODO figure this out +} + +// An Embed stores data for message embeds. +type Embed struct { // TODO figure this out +} + +// ContentWithMentionsReplaced will replace all @ mentions with the +// username of the mention. +func (m *Message) ContentWithMentionsReplaced() string { + content := m.Content + for _, user := range m.Mentions { + content = strings.Replace(content, fmt.Sprintf("<@%s>", user.ID), + fmt.Sprintf("@%s", user.Username), -1) + } + return content +} diff --git a/structs.go b/structs.go index e0dc088..0606d8a 100644 --- a/structs.go +++ b/structs.go @@ -13,9 +13,7 @@ package discordgo import ( "encoding/json" - "fmt" "net" - "strings" "sync" "time" @@ -27,9 +25,8 @@ import ( // Debug : If set to ture debug logging will be displayed. type Session struct { // General configurable settings. - Token string // Authentication token for this session - Debug bool // Debug for printing JSON request/responses - AutoMention bool // if set to True, ChannelSendMessage will auto mention <@ID> + Token string // Authentication token for this session + Debug bool // Debug for printing JSON request/responses // Settable Callback functions for Websocket Events OnEvent func(*Session, *Event) @@ -78,13 +75,14 @@ type Session struct { // Everything below here is used for Voice testing. // This stuff is almost guarenteed to change a lot // and is even a bit hackish right now. + Voice *voice VwsConn *websocket.Conn // new for voice VSessionID string VToken string VEndpoint string VGuildID string VChannelID string - Vop2 VoiceOP2 + Vop2 voiceOP2 UDPConn *net.UDPConn // Managed state object, updated with events. @@ -101,39 +99,6 @@ type Session struct { listenChan chan struct{} } -// A Message stores all data related to a specific Discord message. -type Message struct { - ID string `json:"id"` - Author User `json:"author"` - Content string `json:"content"` - Attachments []*Attachment `json:"attachments"` - Tts bool `json:"tts"` - Embeds []*Embed `json:"embeds"` - Timestamp string `json:"timestamp"` - MentionEveryone bool `json:"mention_everyone"` - EditedTimestamp string `json:"edited_timestamp"` - Mentions []*User `json:"mentions"` - ChannelID string `json:"channel_id"` -} - -// ContentWithMentionsReplaced will replace all @ mentions with the -// username of the mention. -func (m *Message) ContentWithMentionsReplaced() string { - content := m.Content - for _, user := range m.Mentions { - content = strings.Replace(content, fmt.Sprintf("<@%s>", user.ID), fmt.Sprintf("@%s", user.Username), -1) - } - return content -} - -// An Attachment stores data for message attachments. -type Attachment struct { //TODO figure this out -} - -// An Embed stores data for message embeds. -type Embed struct { // TODO figure this out -} - // A VoiceRegion stores data for a specific voice region server. type VoiceRegion struct { ID string `json:"id"` @@ -302,7 +267,7 @@ type Settings struct { type Event struct { Type string `json:"t"` State int `json:"s"` - Operation int `json:"o"` + Operation int `json:"op"` Direction int `json:"dir"` RawData json.RawMessage `json:"d"` } diff --git a/tests/discordgo_test.go b/tests/discordgo_test.go new file mode 100644 index 0000000..d736cd9 --- /dev/null +++ b/tests/discordgo_test.go @@ -0,0 +1,127 @@ +package discordgo_test + +import ( + "os" + "runtime" + "testing" + "time" + + . "github.com/bwmarrin/discordgo" +) + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////// VARS NEEDED FOR TESTING +var ( + dg *Session // Stores global discordgo session + + envToken string = os.Getenv("DG_TOKEN") // Token to use when authenticating + envUsername string = os.Getenv("DG_USERNAME") // Username to use when authenticating + envPassword string = os.Getenv("DG_PASSWORD") // Password to use when authenticating + envGuild string = os.Getenv("DG_GUILD") // Guild ID to use for tests + envChannel string = os.Getenv("DG_CHANNEL") // Channel ID to use for tests + envUser string = os.Getenv("DG_USER") // User ID to use for tests + envAdmin string = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests +) + +////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////// HELPER FUNCTIONS USED FOR TESTING + +// This waits x time for the check bool to be the want bool +func waitBoolEqual(timeout time.Duration, check *bool, want bool) bool { + + start := time.Now() + for { + if *check == want { + return true + } + + if time.Since(start) > timeout { + return false + } + + runtime.Gosched() + } +} + +// Checks if we're connected to Discord +func isConnected() bool { + + if dg == nil { + return false + } + + if dg.Token == "" { + return false + } + + // Need a way to see if the ws connection is nil + + if !waitBoolEqual(10*time.Second, &dg.DataReady, true) { + return false + } + + return true +} + +////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////// START OF TESTS + +// TestNew tests the New() function without any arguments. This should return +// a valid Session{} struct and no errors. +func TestNew(t *testing.T) { + + _, err := New() + if err != nil { + t.Errorf("New() returned error: %+v", err) + } +} + +// TestNewUserPass tests the New() function with a username and password. +// This should return a valid Session{}, a valid Session.Token, and open +// a websocket connection to Discord. +func TestNewUserPass(t *testing.T) { + + if isConnected() { + t.Skip("Skipping New(username,password), already connected.") + } + + if envUsername == "" || envPassword == "" { + t.Skip("Skipping New(username,password), DG_USERNAME or DG_PASSWORD not set") + return + } + // Not testing yet. +} + +// TestNewToken tests the New() function with a Token. This should return +// the same as the TestNewUserPass function. +func TestNewToken(t *testing.T) { + + if isConnected() { + t.Skip("Skipping New(token), already connected.") + } + + if envToken == "" { + t.Skip("Skipping New(token), DG_TOKEN not set") + } + + d, err := New(envToken) + if err != nil { + t.Fatalf("New(envToken) returned error: %+v", err) + } + + if d == nil { + t.Fatal("New(envToken), d is nil, should be Session{}") + } + + if d.Token == "" { + t.Fatal("New(envToken), d.Token is empty, should be a valid Token.") + } + + if !waitBoolEqual(10*time.Second, &d.DataReady, true) { + t.Fatal("New(envToken), d.DataReady is false after 10 seconds. Should be true.") + } + + t.Log("Successfully connected to Discord.") + dg = d + +} diff --git a/voice.go b/voice.go index 7345a91..ea6754d 100644 --- a/voice.go +++ b/voice.go @@ -18,23 +18,44 @@ import ( "fmt" "net" "strings" + "sync" "time" "github.com/gorilla/websocket" ) -// A VEvent is the initial structure for voice websocket events. I think -// I can reuse the data websocket structure here. -type VEvent struct { - Type string `json:"t"` - State int `json:"s"` - Operation int `json:"op"` - RawData json.RawMessage `json:"d"` +// A Voice struct holds all data and functions related to Discord Voice support. +// NOTE: This is not used right at this moment, but it will be used soon. +type voice struct { + Ready bool + WS *voiceWS + UDP *voiceUDP + + SessionID string + Token string + Endpoint string + GuildID string + ChannelID string + OP2 *voiceOP2 } -// A VoiceOP2 stores the data for voice operation 2 websocket events +type voiceWS struct { + Ready bool + Chan chan struct{} + Lock sync.Mutex + Conn *websocket.Conn +} + +type voiceUDP struct { + Ready bool + Chan chan struct{} + Lock sync.Mutex + Conn *net.UDPConn +} + +// A voiceOP2 stores the data for voice operation 2 websocket events // which is sort of like the voice READY packet -type VoiceOP2 struct { +type voiceOP2 struct { SSRC uint32 `json:"ssrc"` Port int `json:"port"` Modes []string `json:"modes"` @@ -117,7 +138,7 @@ func (s *Session) VoiceEvent(messageType int, message []byte) (err error) { printJSON(message) } - var e VEvent + var e Event if err := json.Unmarshal(message, &e); err != nil { return err } @@ -125,7 +146,7 @@ func (s *Session) VoiceEvent(messageType int, message []byte) (err error) { switch e.Operation { case 2: // READY packet - var st VoiceOP2 + var st voiceOP2 if err := json.Unmarshal(e.RawData, &st); err != nil { fmt.Println(e.Type, err) printJSON(e.RawData) // TODO: Better error logginEventg