Merge branch 'develop' of https://github.com/iopred/discordgo into develop

This commit is contained in:
Chris Rhodes 2016-04-29 19:03:46 -07:00
commit aff0ab3fd1
14 changed files with 488 additions and 112 deletions

View file

@ -237,5 +237,9 @@ func (s *Session) initialize() {
// onReady handles the ready event.
func (s *Session) onReady(se *Session, r *Ready) {
// Store the SessionID within the Session struct.
s.sessionID = r.SessionID
// Start the heartbeat to keep the connection alive.
go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
}

142
docs/GettingStarted.md Normal file
View file

@ -0,0 +1,142 @@
# Getting Started
This page is dedicated to helping you get started on your way to making the
next great Discord bot or client with DiscordGo. Once you've done that please
don't forget to submit it to the
[Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) list :).
**First, lets cover a few topics so you can make the best choices on how to
move forward from here.**
### Master vs Develop
**When installing DiscordGo you will need to decide if you want to use the current
master branch or the bleeding edge development branch.**
* The **master** branch represents the latest released version of DiscordGo. This
branch will always have a stable and tested version of the library. Each
release is tagged and you can easily download a specific release and view the
release notes on the github [releases](https://github.com/bwmarrin/discordgo/releases)
page.
* The **develop** branch is where all development happens and almost always has
new features over the master branch. However breaking changes are frequently
added the develop branch and sometimes bugs are introduced. Bugs get fixed
and the breaking changes get documented before pushing to master.
*So, what should you use?*
Due to the how frequently the Discord API is changing there is a high chance
that the *master* branch may be lacking important features. Because of that, if
you can accept the constant changing nature of the *develop* branch and the
chance that it may occasionally contain bugs then it is the recommended branch
to use. Otherwise, if you want to tail behind development slightly and have a
more stable package with documented releases then please use the *master*
branch instead.
### Client vs Bot
You probably already know the answer to this but now is a good time to decide
if your goal is to write a client application or a bot. DiscordGo aims to fully
support both client applications and bots but there are some differences
between the two that you should understand.
#### Client Application
A client application is a program that is intended to be used by a normal user
as a replacement for the official clients that Discord provides. An example of
this would be a terminal client used to read and send messages with your normal
user account or possibly a new desktop client that provides a different set of
features than the official desktop client that Discord already provides.
Client applications work with normal user accounts and you can login with an
email address and password or a special authentication token. However, normal
user accounts are not allowed to perform any type of automation and doing so can
cause the account to be banned from Discord. Also normal user accounts do not
support multi-server voice connections and some other features that are
exclusive to Bot accounts only.
To create a new user account (if you have not done so already) visit the
[Discord](https://discordapp.com/) website and click on the
**Try Discord Now, It's Free** button then follow the steps to setup your
new account.
#### Bot Application
A bot application is a special program that interacts with the Discord servers
to perform some form of automation or provide some type of service. Examples
are things like number trivia games, music streaming, channel moderation,
sending reminders, playing loud airhorn sounds, comic generators, YouTube
integration, Twitch integration.. You're *almost* only limited by your imagination.
Bot applications require the use of a special Bot account. These accounts are
tied to your personal user account. Bot accounts cannot login with the normal
user clients and they cannot join servers the same way a user does. They do not
have access to some user client specific features however they gain access to
many Bot specific features.
To create a new bot account first create yourself a normal user account on
Discord then visit the [My Applications](https://discordapp.com/developers/applications/me)
page and click on the **New Application** box. Follow the prompts from there
to finish creating your account.
**More information about Bots vs Client accounts can be found [here](https://discordapp.com/developers/docs/topics/oauth2#bot-vs-user-accounts)**
# Requirements
DiscordGo requires Go version 1.4 or higher. It has been tested to compile and
run successfully on Debian Linux 8, FreeBSD 10, and Windows 7. It is expected
that it should work anywhere Go 1.4 or higher works. If you run into problems
please let us know :)
You must already have a working Go environment setup to use DiscordGo. If you
are new to Go and have not yet installed and tested it on your computer then
please visit [this page](https://golang.org/doc/install) first then I highly
recommend you walk though [A Tour of Go](https://tour.golang.org/welcome/1) to
help get your familiar with the Go language. Also checkout the relevent Go plugin
for your editor - they are hugely helpful when developing Go code.
* Vim - [vim-go](https://github.com/fatih/vim-go)
* Sublime - [GoSublime](https://github.com/DisposaBoy/GoSublime)
* Atom - [go-plus](https://atom.io/packages/go-plus)
* Visual Studio - [vscode-go](https://github.com/Microsoft/vscode-go)
# Install DiscordGo
Like any other Go package the fist step is to `go get` the package. This will
always pull the latest released version from the master branch. Then run
`go install` to compile and install the libraries on your system.
#### Linux/BSD
Run go get to download the package to your GOPATH/src folder.
```sh
go get github.com/bwmarrin/discordgo
```
If you want to use the develop branch, follow these steps next.
```sh
cd $GOPATH/src/github.com/bwmarrin/discordgo
git checkout develop
```
Finally, compile and install the package into the GOPATH/pkg folder. This isn't
absolutely required but doing this will allow the Go plugin for your editor to
provide autocomplete for all DiscordGo functions.
```sh
cd $GOPATH/src/github.com/bwmarrin/discordgo
go install
```
#### Windows
Placeholder.
# Next...
More coming soon.

BIN
docs/img/discordgo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

33
docs/index.md Normal file
View file

@ -0,0 +1,33 @@
## DiscordGo
<hr>
<img align="right" src="http://bwmarrin.github.io/discordgo/img/discordgo.png">
[Go](https://golang.org/) (golang) interface for the [Discord](https://discordapp.com/)
chat service. Provides both low-level direct bindings to the
Discord API and helper functions that allow you to make custom clients and chat
bot applications easily.
[Discord](https://discordapp.com/) is an all-in-one voice and text chat for
gamers that's free, secure, and works on both your desktop and phone.
### Why DiscordGo?
* High Performance
* Minimal Memory & CPU Load
* Low-level bindings to Discord REST API Endpoints
* Support for the data websocket interface
* Multi-Server voice connections (send and receive)
* State tracking and caching
### Learn More
* Check out the [Getting Started](GettingStarted) section
* Read the reference docs on [Godoc](https://godoc.org/github.com/bwmarrin/discordgo) or [GoWalker](https://gowalker.org/github.com/bwmarrin/discordgo)
* Try the [examples](https://github.com/bwmarrin/discordgo/tree/master/examples)
* Explore [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo)
### Join Us!
Both of the below links take you to chat channels where you can get more
information and support for DiscordGo. There's also a chance to make some
friends :)
* Join the [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) chat server dedicated to Go programming.
* Join the [Discord API](https://discord.gg/0SBTUU1wZTWT6sqd) chat server dedicated to the Discord API.

View file

@ -50,6 +50,12 @@ type Connect struct{}
// Disconnect is an empty struct for an event.
type Disconnect struct{}
// RateLimit is a struct for the RateLimited event
type RateLimit struct {
*TooManyRequests
URL string
}
// MessageCreate is a wrapper struct for an event.
type MessageCreate struct {
*Message

105
logging.go Normal file
View file

@ -0,0 +1,105 @@
// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. 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 discordgo package logging
package discordgo
import (
"bytes"
"encoding/json"
"fmt"
"log"
"runtime"
"strings"
)
const (
// Critical Errors that could lead to data loss or panic
// Only errors that would not be returned to a calling function
LogError int = iota
// Very abnormal events.
// Errors that are also returend to a calling function.
LogWarning
// Normal non-error activity
// Generally, not overly spammy events
LogInformational
// Very detailed non-error activity
// All HTTP/Websocket packets.
// Very spammy and will impact performance
LogDebug
)
// msglog provides package wide logging consistancy for discordgo
// the format, a... portion this command follows that of fmt.Printf
// msgL : LogLevel of the message
// caller : 1 + the number of callers away from the message source
// format : Printf style message format
// a ... : comma seperated list of values to pass
func msglog(msgL, caller int, format string, a ...interface{}) {
pc, file, line, _ := runtime.Caller(caller)
files := strings.Split(file, "/")
file = files[len(files)-1]
name := runtime.FuncForPC(pc).Name()
fns := strings.Split(name, ".")
name = fns[len(fns)-1]
msg := fmt.Sprintf(format, a...)
log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg)
}
// helper function that wraps msglog for the Session struct
// This adds a check to insure the message is only logged
// if the session log level is equal or higher than the
// message log level
func (s *Session) log(msgL int, format string, a ...interface{}) {
if s.Debug { // Deprecated
s.LogLevel = LogDebug
}
if msgL > s.LogLevel {
return
}
msglog(msgL, 2, format, a...)
}
// helper function that wraps msglog for the VoiceConnection struct
// This adds a check to insure the message is only logged
// if the voice connection log level is equal or higher than the
// message log level
func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) {
if v.Debug { // Deprecated
v.LogLevel = LogDebug
}
if msgL > v.LogLevel {
return
}
msglog(msgL, 2, format, a...)
}
// printJSON is a helper function to display JSON data in a easy to read format.
func printJSON(body []byte) {
var prettyJSON bytes.Buffer
error := json.Indent(&prettyJSON, body, "", "\t")
if error != nil {
log.Print("JSON parse error: ", error)
}
log.Println(string(prettyJSON.Bytes()))
}

17
mkdocs.yml Normal file
View file

@ -0,0 +1,17 @@
site_name: DiscordGo
site_author: Bruce Marriner
site_url: http://bwmarrin.github.io/discordgo/
repo_url: https://github.com/bwmarrin/discordgo
dev_addr: 0.0.0.0:8000
theme: yeti
markdown_extensions:
- smarty
- toc:
permalink: True
- sane_lists
pages:
- 'Home': 'index.md'
- 'Getting Started': 'GettingStarted.md'

View file

@ -25,6 +25,8 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
)
@ -49,6 +51,26 @@ func (s *Session) Request(method, urlStr string, data interface{}) (response []b
// request makes a (GET/POST/...) Requests to Discord REST API.
func (s *Session) request(method, urlStr, contentType string, b []byte) (response []byte, err error) {
// rate limit mutex for this url
// TODO: review for performance improvements
// ideally we just ignore endpoints that we've never
// received a 429 on. But this simple method works and
// is a lot less complex :) It also might even be more
// performat due to less checks and maps.
var mu *sync.Mutex
s.rateLimit.Lock()
if s.rateLimit.url == nil {
s.rateLimit.url = make(map[string]*sync.Mutex)
}
bu := strings.Split(urlStr, "?")
mu, _ = s.rateLimit.url[bu[0]]
if mu == nil {
mu = new(sync.Mutex)
s.rateLimit.url[urlStr] = mu
}
s.rateLimit.Unlock()
if s.Debug {
log.Printf("API REQUEST %8s :: %s\n", method, urlStr)
log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b))
@ -77,7 +99,9 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons
client := &http.Client{Timeout: (20 * time.Second)}
mu.Lock()
resp, err := client.Do(req)
mu.Unlock()
if err != nil {
return
}
@ -111,13 +135,22 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons
// TODO check for 401 response, invalidate token if we get one.
case 429: // TOO MANY REQUESTS - Rate limiting
rl := RateLimit{}
rl := TooManyRequests{}
err = json.Unmarshal(response, &rl)
if err != nil {
err = fmt.Errorf("Request unmarshal rate limit error : %+v", err)
s.log(LogError, "rate limit unmarshal error, %s", err)
return
}
s.log(LogInformational, "Rate Limiting %s, retry in %d", urlStr, rl.RetryAfter)
s.handle(RateLimit{TooManyRequests: &rl, URL: urlStr})
mu.Lock()
time.Sleep(rl.RetryAfter)
mu.Unlock()
// we can make the above smarter
// this method can cause longer delays then required
response, err = s.request(method, urlStr, contentType, b)
default: // Error condition
@ -990,7 +1023,7 @@ func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID
// messageID : the ID of a Message
func (s *Session) ChannelMessageAck(channelID, messageID string) (err error) {
_, err = s.Request("POST", CHANNEL_MESSAGE_ACK(channelID, messageID), nil)
_, err = s.request("POST", CHANNEL_MESSAGE_ACK(channelID, messageID), "", nil)
return
}

View file

@ -31,7 +31,7 @@ func TestUserAvatar(t *testing.T) {
a, err := dg.UserAvatar("@me")
if err != nil {
if err.Error() == `HTTP 404 NOT FOUND, {"message": ""}` {
if err.Error() == `HTTP 404 NOT FOUND, {"message": "404: Not Found"}` {
t.Skip("Skipped, @me doesn't have an Avatar")
}
t.Errorf(err.Error())

View file

@ -325,6 +325,9 @@ func (s *State) Channel(channelID string) (*Channel, error) {
return nil, ErrNilState
}
s.RLock()
defer s.RUnlock()
if c, ok := s.channelMap[channelID]; ok {
return c, nil
}

View file

@ -30,7 +30,8 @@ type Session struct {
Token string
// Debug for printing JSON request/responses
Debug bool
Debug bool // Deprecated, will be removed.
LogLevel int
// Should the session reconnect the websocket on errors.
ShouldReconnectOnError bool
@ -74,6 +75,26 @@ type Session struct {
// When nil, the session is not listening.
listening chan interface{}
// used to deal with rate limits
// may switch to slices later
// TODO: performance test map vs slices
rateLimit rateLimitMutex
// sequence tracks the current gateway api websocket sequence number
sequence int
// stores sessions current Discord Gateway
gateway string
// stores session ID of current Gateway connection
sessionID string
}
type rateLimitMutex struct {
sync.Mutex
url map[string]*sync.Mutex
bucket map[string]*sync.Mutex // TODO :)
}
// A VoiceRegion stores data for a specific voice region server.
@ -272,10 +293,9 @@ type FriendSourceFlags struct {
// An Event provides a basic initial struct for all websocket event.
type Event struct {
Type string `json:"t"`
State int `json:"s"`
Operation int `json:"op"`
Direction int `json:"dir"`
Sequence int `json:"s"`
Type string `json:"t"`
RawData json.RawMessage `json:"d"`
Struct interface{} `json:"-"`
}
@ -304,8 +324,9 @@ type Relationship struct {
ID string `json:"id"`
}
// A RateLimit struct holds information related to a specific rate limit.
type RateLimit struct {
// A TooManyRequests struct holds information received from Discord
// when receiving a HTTP 429 response.
type TooManyRequests struct {
Bucket string `json:"bucket"`
Message string `json:"message"`
RetryAfter time.Duration `json:"retry_after"`

35
util.go
View file

@ -1,35 +0,0 @@
// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. 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 utility functions for the discordgo package. These
// functions are not exported and are likely to change substantially in
// the future to match specific needs of the discordgo package itself.
package discordgo
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
// printEvent prints out a WSAPI event.
func printEvent(e *Event) {
log.Println(fmt.Sprintf("Event. Type: %s, State: %d Operation: %d Direction: %d", e.Type, e.State, e.Operation, e.Direction))
printJSON(e.RawData)
}
// printJSON is a helper function to display JSON data in a easy to read format.
func printJSON(body []byte) {
var prettyJSON bytes.Buffer
error := json.Indent(&prettyJSON, body, "", "\t")
if error != nil {
log.Print("JSON parse error: ", error)
}
log.Println(string(prettyJSON.Bytes()))
}

View file

@ -32,7 +32,8 @@ import (
type VoiceConnection struct {
sync.RWMutex
Debug bool // If true, print extra logging
Debug bool // If true, print extra logging -- DEPRECATED
LogLevel int
Ready bool // If true, voice is ready to send/receive audio
UserID string
GuildID string
@ -125,6 +126,7 @@ func (v *VoiceConnection) Disconnect() (err error) {
// Close websocket and udp connections
v.Close()
v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID)
delete(v.session.VoiceConnections, v.GuildID)
return
@ -427,6 +429,7 @@ func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struc
var err error
ticker := time.NewTicker(i * time.Millisecond)
for {
v.log(LogDebug, "Sending heartbeat packet")
err = wsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())})
if err != nil {
log.Println("wsHeartbeat send error: ", err)

160
wsapi.go
View file

@ -26,6 +26,8 @@ import (
"github.com/gorilla/websocket"
)
var GATEWAY_VERSION int = 4
type handshakeProperties struct {
OS string `json:"$os"`
Browser string `json:"$browser"`
@ -35,7 +37,6 @@ type handshakeProperties struct {
}
type handshakeData struct {
Version int `json:"v"`
Token string `json:"token"`
Properties handshakeProperties `json:"properties"`
LargeThreshold int `json:"large_threshold"`
@ -49,6 +50,9 @@ type handshakeOp struct {
// Open opens a websocket connection to Discord.
func (s *Session) Open() (err error) {
s.log(LogInformational, "called")
s.Lock()
defer func() {
if err != nil {
@ -56,7 +60,10 @@ func (s *Session) Open() (err error) {
}
}()
if s.VoiceConnections == nil {
s.log(LogInformational, "creating new VoiceConnections map")
s.VoiceConnections = make(map[string]*VoiceConnection)
}
if s.wsConn != nil {
err = errors.New("Web socket already opened.")
@ -64,25 +71,42 @@ func (s *Session) Open() (err error) {
}
// Get the gateway to use for the Websocket connection
g, err := s.Gateway()
if s.gateway == "" {
s.gateway, err = s.Gateway()
if err != nil {
return
}
// Add the version and encoding to the URL
s.gateway = fmt.Sprintf("%s?v=%v&encoding=json", s.gateway, GATEWAY_VERSION)
}
header := http.Header{}
header.Add("accept-encoding", "zlib")
// TODO: See if there's a use for the http response.
// conn, response, err := websocket.DefaultDialer.Dial(session.Gateway, nil)
s.wsConn, _, err = websocket.DefaultDialer.Dial(g, header)
s.log(LogInformational, "connecting to gateway %s", s.gateway)
s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header)
if err != nil {
s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err)
s.gateway = "" // clear cached gateway
// TODO: should we add a retry block here?
return
}
err = s.wsConn.WriteJSON(handshakeOp{2, handshakeData{3, s.Token, handshakeProperties{runtime.GOOS, "Discordgo v" + VERSION, "", "", ""}, 250, s.Compress}})
if s.sessionID != "" && s.sequence > 0 {
s.log(LogInformational, "sending resume packet to gateway")
// TODO: RESUME
}
//else {
s.log(LogInformational, "sending identify packet to gateway")
err = s.wsConn.WriteJSON(handshakeOp{2, handshakeData{s.Token, handshakeProperties{runtime.GOOS, "Discordgo v" + VERSION, "", "", ""}, 250, s.Compress}})
if err != nil {
s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err)
return
}
//}
// Create listening outside of listen, as it needs to happen inside the mutex
// lock.
@ -163,7 +187,10 @@ func (s *Session) listen(wsConn *websocket.Conn, listening <-chan interface{}) {
case <-listening:
return
default:
go s.event(messageType, message)
// TODO make s.event a variable that points to a function
// this way it will be possible for an end-user to write
// a completely custom event handler if needed.
go s.onEvent(messageType, message)
}
}
}
@ -189,7 +216,7 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
var err error
ticker := time.NewTicker(i * time.Millisecond)
for {
err = wsConn.WriteJSON(heartbeatOp{1, int(time.Now().Unix())})
err = wsConn.WriteJSON(heartbeatOp{1, s.sequence})
if err != nil {
log.Println("Error sending heartbeat:", err)
return
@ -241,73 +268,104 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) {
return
}
// Front line handler for all Websocket Events. Determines the
// event type and passes the message along to the next handler.
// onEvent is the "event handler" for all messages received on the
// Discord Gateway API websocket connection.
//
// If you use the AddHandler() function to register a handler for a
// specific event this function will pass the event along to that handler.
//
// If you use the AddHandler() function to register a handler for the
// "OnEvent" event then all events will be passed to that handler.
//
// TODO: You may also register a custom event handler entirely using...
func (s *Session) onEvent(messageType int, message []byte) {
// event is the front line handler for all events. This needs to be
// broken up into smaller functions to be more idiomatic Go.
// Events will be handled by any implemented handler in Session.
// All unhandled events will then be handled by OnEvent.
func (s *Session) event(messageType int, message []byte) {
var err error
var reader io.Reader
reader = bytes.NewBuffer(message)
// If this is a compressed message, uncompress it.
if messageType == 2 {
z, err1 := zlib.NewReader(reader)
if err1 != nil {
log.Println(fmt.Sprintf("Error uncompressing message type %d: %s", messageType, err1))
z, err := zlib.NewReader(reader)
if err != nil {
s.log(LogError, "error uncompressing websocket message, %s", err)
return
}
defer func() {
err := z.Close()
if err != nil {
log.Println("error closing zlib:", err)
s.log(LogWarning, "error closing zlib, %s", err)
}
}()
reader = z
}
// Decode the event into an Event struct.
var e *Event
decoder := json.NewDecoder(reader)
if err = decoder.Decode(&e); err != nil {
log.Println(fmt.Sprintf("Error decoding message type %d: %s", messageType, err))
s.log(LogError, "error decoding websocket message, %s", err)
return
}
if s.Debug {
printEvent(e)
s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s", e.Operation, e.Sequence, e.Type, string(e.RawData))
}
// Ping request.
// Must respond with a heartbeat packet within 5 seconds
if e.Operation == 1 {
s.log(LogInformational, "sending heartbeat in response to Op1")
err = s.wsConn.WriteJSON(heartbeatOp{1, s.sequence})
if err != nil {
s.log(LogError, "error sending heartbeat in response to Op1")
return
}
}
// Do not try to Dispatch a non-Dispatch Message
if e.Operation != 0 {
// But we probably should be doing something with them.
// TEMP
s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message))
return
}
// Store the message sequence
s.sequence = e.Sequence
// Map event to registered event handlers and pass it along
// to any registered functions
i := eventToInterface[e.Type]
if i != nil {
// Create a new instance of the event type.
i = reflect.New(reflect.TypeOf(i)).Interface()
// Attempt to unmarshal our event.
// If there is an error we should handle the event itself.
if err = json.Unmarshal(e.RawData, i); err != nil {
log.Printf("error unmarshalling %s event, %s\n", e.Type, err)
// Ready events must fire, even if they are empty.
if e.Type != "READY" {
i = nil
s.log(LogError, "error unmarshalling %s event, %s", e.Type, err)
}
}
} else {
log.Println("Unknown event.")
i = nil
}
if i != nil {
// Send event to any registered event handlers for it's type.
// Because the above doesn't cancel this, in case of an error
// the struct could be partially populated or at default values.
// However, most errors are due to a single field and I feel
// it's better to pass along what we received than nothing at all.
// TODO: Think about that decision :)
// Either way, READY events must fire, even with errors.
s.handle(i)
} else {
s.log(LogWarning, "unknown event, %#v", e)
}
// Emit event to the OnEvent handler
e.Struct = i
s.handle(e)
return
}
// ------------------------------------------------------------------------------------------------
@ -364,8 +422,6 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi
deaf: deaf,
mute: mute,
session: s,
connected: make(chan bool),
sessionRecv: make(chan string),
}
// Store voice in VoiceConnections map for this GuildID
@ -375,6 +431,7 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi
data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}}
err = s.wsConn.WriteJSON(data)
if err != nil {
s.log(LogInformational, "Deleting VoiceConnection %s", gID)
delete(s.VoiceConnections, gID)
return
}
@ -383,6 +440,7 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi
err = voice.waitUntilConnected()
if err != nil {
voice.Close()
s.log(LogInformational, "Deleting VoiceConnection %s", gID)
delete(s.VoiceConnections, gID)
return
}
@ -421,9 +479,6 @@ func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
// Store the SessionID for later use.
voice.UserID = self.ID // TODO: Review
voice.sessionID = st.SessionID
// TODO: Consider this...
// voice.sessionRecv <- st.SessionID
}
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
@ -440,29 +495,18 @@ func (s *Session) onVoiceServerUpdate(se *Session, st *VoiceServerUpdate) {
return
}
// If currently connected to voice ws/udp, then disconnect.
// Has no effect if not connected.
voice.Close()
// Store values for later use
voice.token = st.Token
voice.endpoint = st.Endpoint
voice.GuildID = st.GuildID
// If currently connected to voice ws/udp, then disconnect.
// Has no effect if not connected.
// voice.Close()
// Wait for the sessionID from onVoiceStateUpdate
// voice.sessionID = <-voice.sessionRecv
// TODO review above
// wouldn't this cause a huge problem, if it's just a guild server
// update.. ?
// I could add a timeout loop of some sort and also check if the
// sessionID doesn't or does exist already...
// something.. a bit smarter.
// We now have enough information to open a voice websocket conenction
// so, that's what the next call does.
// Open a conenction to the voice server
err := voice.open()
if err != nil {
log.Println("onVoiceServerUpdate Voice.Open error: ", err)
// TODO better logging
s.log(LogError, "onVoiceServerUpdate voice.open, ", err)
}
}