forked from pothtonswer/discordmuffin
Merge branch 'develop' into guilds
This commit is contained in:
commit
0ea10a300b
10 changed files with 416 additions and 490 deletions
85
discord.go
85
discord.go
|
@ -13,7 +13,10 @@
|
|||
// Package discordgo provides Discord binding for Go
|
||||
package discordgo
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/)
|
||||
const VERSION = "0.11.0-alpha"
|
||||
|
@ -118,3 +121,83 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// AddHandler allows you to add an event handler that will be fired anytime
|
||||
// the given event is triggered.
|
||||
func (s *Session) AddHandler(handler interface{}) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
handlerType := reflect.TypeOf(handler)
|
||||
|
||||
if handlerType.NumIn() != 2 {
|
||||
panic("Unable to add event handler, handler must be of the type func(*discordgo.Session, *discordgo.EventType).")
|
||||
}
|
||||
|
||||
if handlerType.In(0) != reflect.TypeOf(s) {
|
||||
panic("Unable to add event handler, first argument must be of type *discordgo.Session.")
|
||||
}
|
||||
|
||||
if s.handlers == nil {
|
||||
s.Unlock()
|
||||
s.initialize()
|
||||
s.Lock()
|
||||
}
|
||||
|
||||
eventType := handlerType.In(1)
|
||||
|
||||
// Support handlers of type interface{}, this is a special handler, which is triggered on every event.
|
||||
if eventType.Kind() == reflect.Interface {
|
||||
eventType = nil
|
||||
}
|
||||
|
||||
handlers := s.handlers[eventType]
|
||||
if handlers == nil {
|
||||
handlers = []reflect.Value{}
|
||||
}
|
||||
|
||||
handlers = append(handlers, reflect.ValueOf(handler))
|
||||
s.handlers[eventType] = handlers
|
||||
}
|
||||
|
||||
func (s *Session) handle(event interface{}) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}
|
||||
|
||||
if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok {
|
||||
for _, handler := range handlers {
|
||||
handler.Call(handlerParameters)
|
||||
}
|
||||
}
|
||||
|
||||
if handlers, ok := s.handlers[nil]; ok {
|
||||
for _, handler := range handlers {
|
||||
handler.Call(handlerParameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initialize adds all internal handlers and state tracking handlers.
|
||||
func (s *Session) initialize() {
|
||||
s.Lock()
|
||||
s.handlers = map[interface{}][]reflect.Value{}
|
||||
s.Unlock()
|
||||
|
||||
s.AddHandler(s.onEvent)
|
||||
s.AddHandler(s.onReady)
|
||||
s.AddHandler(s.onVoiceServerUpdate)
|
||||
s.AddHandler(s.onVoiceStateUpdate)
|
||||
s.AddHandler(s.State.onInterface)
|
||||
}
|
||||
|
||||
// onEvent handles events that are unhandled or errored while unmarshalling
|
||||
func (s *Session) onEvent(se *Session, e *Event) {
|
||||
printEvent(e)
|
||||
}
|
||||
|
||||
// onReady handles the ready event.
|
||||
func (s *Session) onReady(se *Session, r *Ready) {
|
||||
go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
|
||||
}
|
||||
|
|
|
@ -224,3 +224,39 @@ func TestOpenClose(t *testing.T) {
|
|||
t.Fatalf("TestClose, d.Close failed: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlers(t *testing.T) {
|
||||
testHandlerCalled := false
|
||||
testHandler := func(s *Session, t *testing.T) {
|
||||
testHandlerCalled = true
|
||||
}
|
||||
|
||||
interfaceHandlerCalled := false
|
||||
interfaceHandler := func(s *Session, i interface{}) {
|
||||
interfaceHandlerCalled = true
|
||||
}
|
||||
|
||||
bogusHandlerCalled := false
|
||||
bogusHandler := func(s *Session, se *Session) {
|
||||
bogusHandlerCalled = true
|
||||
}
|
||||
|
||||
d := Session{}
|
||||
d.AddHandler(testHandler)
|
||||
d.AddHandler(interfaceHandler)
|
||||
d.AddHandler(bogusHandler)
|
||||
|
||||
d.handle(t)
|
||||
|
||||
if !testHandlerCalled {
|
||||
t.Fatalf("testHandler was not called.")
|
||||
}
|
||||
|
||||
if !interfaceHandlerCalled {
|
||||
t.Fatalf("interfaceHandler was not called.")
|
||||
}
|
||||
|
||||
if bogusHandlerCalled {
|
||||
t.Fatalf("bogusHandler was called.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ var (
|
|||
USER_AVATAR = func(uID, aID string) string { return USERS + uID + "/avatars/" + aID + ".jpg" }
|
||||
USER_SETTINGS = func(uID string) string { return USERS + uID + "/settings" }
|
||||
USER_GUILDS = func(uID string) string { return USERS + uID + "/guilds" }
|
||||
USER_GUILD = func(uID, gID string) string { return USERS + uID + "/guilds/" + gID }
|
||||
USER_CHANNELS = func(uID string) string { return USERS + uID + "/channels" }
|
||||
USER_DEVICES = func(uID string) string { return USERS + uID + "/devices" }
|
||||
USER_CONNECTIONS = func(uID string) string { return USERS + uID + "/connections" }
|
||||
|
|
100
events.go
Normal file
100
events.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package discordgo
|
||||
|
||||
// Connect is an empty struct for an event.
|
||||
type Connect struct{}
|
||||
|
||||
// Disconnect is an empty struct for an event.
|
||||
type Disconnect struct{}
|
||||
|
||||
// MessageCreate is a wrapper struct for an event.
|
||||
type MessageCreate struct {
|
||||
*Message
|
||||
}
|
||||
|
||||
// MessageUpdate is a wrapper struct for an event.
|
||||
type MessageUpdate struct {
|
||||
*Message
|
||||
}
|
||||
|
||||
// MessageDelete is a wrapper struct for an event.
|
||||
type MessageDelete struct {
|
||||
*Message
|
||||
}
|
||||
|
||||
// ChannelCreate is a wrapper struct for an event.
|
||||
type ChannelCreate struct {
|
||||
*Channel
|
||||
}
|
||||
|
||||
// ChannelUpdate is a wrapper struct for an event.
|
||||
type ChannelUpdate struct {
|
||||
*Channel
|
||||
}
|
||||
|
||||
// ChannelDelete is a wrapper struct for an event.
|
||||
type ChannelDelete struct {
|
||||
*Channel
|
||||
}
|
||||
|
||||
// GuildCreate is a wrapper struct for an event.
|
||||
type GuildCreate struct {
|
||||
*Guild
|
||||
}
|
||||
|
||||
// GuildUpdate is a wrapper struct for an event.
|
||||
type GuildUpdate struct {
|
||||
*Guild
|
||||
}
|
||||
|
||||
// GuildDelete is a wrapper struct for an event.
|
||||
type GuildDelete struct {
|
||||
*Guild
|
||||
}
|
||||
|
||||
// GuildBanAdd is a wrapper struct for an event.
|
||||
type GuildBanAdd struct {
|
||||
*GuildBan
|
||||
}
|
||||
|
||||
// GuildBanRemove is a wrapper struct for an event.
|
||||
type GuildBanRemove struct {
|
||||
*GuildBan
|
||||
}
|
||||
|
||||
// GuildMemberAdd is a wrapper struct for an event.
|
||||
type GuildMemberAdd struct {
|
||||
*Member
|
||||
}
|
||||
|
||||
// GuildMemberUpdate is a wrapper struct for an event.
|
||||
type GuildMemberUpdate struct {
|
||||
*Member
|
||||
}
|
||||
|
||||
// GuildMemberRemove is a wrapper struct for an event.
|
||||
type GuildMemberRemove struct {
|
||||
*Member
|
||||
}
|
||||
|
||||
// GuildRoleCreate is a wrapper struct for an event.
|
||||
type GuildRoleCreate struct {
|
||||
*GuildRole
|
||||
}
|
||||
|
||||
// GuildRoleUpdate is a wrapper struct for an event.
|
||||
type GuildRoleUpdate struct {
|
||||
*GuildRole
|
||||
}
|
||||
|
||||
// VoiceStateUpdate is a wrapper struct for an event.
|
||||
type VoiceStateUpdate struct {
|
||||
*VoiceState
|
||||
}
|
||||
|
||||
// UserUpdate is a wrapper struct for an event.
|
||||
type UserUpdate struct {
|
||||
*UserUpdate
|
||||
}
|
||||
|
||||
// UserSettingsUpdate is a map for an event.
|
||||
type UserSettingsUpdate map[string]interface{}
|
|
@ -23,9 +23,10 @@ func main() {
|
|||
|
||||
// Create a new Discord Session interface and set a handler for the
|
||||
// OnMessageCreate event that happens for every new message on any channel
|
||||
dg := discordgo.Session{
|
||||
OnMessageCreate: messageCreate,
|
||||
}
|
||||
dg := discordgo.Session{}
|
||||
|
||||
// Register messageCreate as a callback for the messageCreate events.
|
||||
dg.AddHandler(messageCreate)
|
||||
|
||||
// Login to the Discord server and store the authentication token
|
||||
err = dg.Login(os.Args[1], os.Args[2])
|
||||
|
@ -46,9 +47,9 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
// This function will be called (due to above assignment) every time a new
|
||||
// This function will be called (due to AddHandler above) every time a new
|
||||
// message is created on any channel that the autenticated user has access to.
|
||||
func messageCreate(s *discordgo.Session, m *discordgo.Message) {
|
||||
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
|
||||
// Print message to stdout.
|
||||
fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content)
|
||||
|
|
|
@ -28,8 +28,8 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
// Register messageCreate as a callback for the OnMessageCreate event.
|
||||
dg.OnMessageCreate = messageCreate
|
||||
// Register messageCreate as a callback for the messageCreate events.
|
||||
dg.AddHandler(messageCreate)
|
||||
|
||||
// Open the websocket and begin listening.
|
||||
dg.Open()
|
||||
|
@ -40,9 +40,9 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
// This function will be called (due to above assignment) every time a new
|
||||
// This function will be called (due to AddHandler above) every time a new
|
||||
// message is created on any channel that the autenticated user has access to.
|
||||
func messageCreate(s *discordgo.Session, m *discordgo.Message) {
|
||||
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
|
||||
// Print message to stdout.
|
||||
fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content)
|
||||
|
|
44
restapi.go
44
restapi.go
|
@ -390,7 +390,7 @@ func (s *Session) GuildEdit(guildID, name string) (st *Guild, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// GuildDelete deletes or leaves a Guild.
|
||||
// GuildDelete deletes a Guild.
|
||||
// guildID : The ID of a Guild
|
||||
func (s *Session) GuildDelete(guildID string) (st *Guild, err error) {
|
||||
|
||||
|
@ -403,6 +403,14 @@ func (s *Session) GuildDelete(guildID string) (st *Guild, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// GuildLeave leaves a Guild.
|
||||
// guildID : The ID of a Guild
|
||||
func (s *Session) GuildLeave(guildID string) (err error) {
|
||||
|
||||
_, err = s.Request("DELETE", USER_GUILD("@me", guildID), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// GuildBans returns an array of User structures for all bans of a
|
||||
// given guild.
|
||||
// guildID : The ID of a Guild.
|
||||
|
@ -786,17 +794,17 @@ func (s *Session) ChannelMessageAck(channelID, messageID string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// ChannelMessageSend sends a message to the given channel.
|
||||
// channelMessageSend sends a message to the given channel.
|
||||
// channelID : The ID of a Channel.
|
||||
// content : The message to send.
|
||||
// NOTE, mention and tts parameters may be added in 2.x branch.
|
||||
func (s *Session) ChannelMessageSend(channelID string, content string) (st *Message, err error) {
|
||||
// tts : Whether to send the message with TTS.
|
||||
func (s *Session) channelMessageSend(channelID, content string, tts bool) (st *Message, err error) {
|
||||
|
||||
// TODO: nonce string ?
|
||||
data := struct {
|
||||
Content string `json:"content"`
|
||||
TTS bool `json:"tts"`
|
||||
}{content, false}
|
||||
}{content, tts}
|
||||
|
||||
// Send the message to the given channel
|
||||
response, err := s.Request("POST", CHANNEL_MESSAGES(channelID), data)
|
||||
|
@ -808,6 +816,22 @@ func (s *Session) ChannelMessageSend(channelID string, content string) (st *Mess
|
|||
return
|
||||
}
|
||||
|
||||
// ChannelMessageSend sends a message to the given channel.
|
||||
// channelID : The ID of a Channel.
|
||||
// content : The message to send.
|
||||
func (s *Session) ChannelMessageSend(channelID string, content string) (st *Message, err error) {
|
||||
|
||||
return s.channelMessageSend(channelID, content, false)
|
||||
}
|
||||
|
||||
// ChannelMessageSendTTS sends a message to the given channel with Text to Speech.
|
||||
// channelID : The ID of a Channel.
|
||||
// content : The message to send.
|
||||
func (s *Session) ChannelMessageSendTTS(channelID string, content string) (st *Message, err error) {
|
||||
|
||||
return s.channelMessageSend(channelID, content, true)
|
||||
}
|
||||
|
||||
// ChannelMessageEdit edits an existing message, replacing it entirely with
|
||||
// the given content.
|
||||
// channeld : The ID of a Channel
|
||||
|
@ -847,9 +871,15 @@ func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (st *Mess
|
|||
return nil, err
|
||||
}
|
||||
|
||||
io.Copy(writer, r)
|
||||
_, err = io.Copy(writer, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bodywriter.Close()
|
||||
err = bodywriter.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := s.request("POST", CHANNEL_MESSAGES(channelID), bodywriter.FormDataContentType(), body.Bytes())
|
||||
if err != nil {
|
||||
|
|
50
state.go
50
state.go
|
@ -35,6 +35,7 @@ func (s *State) OnReady(r *Ready) error {
|
|||
if s == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
|
@ -48,6 +49,7 @@ func (s *State) GuildAdd(guild *Guild) error {
|
|||
if s == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
|
@ -73,6 +75,7 @@ func (s *State) GuildRemove(guild *Guild) error {
|
|||
if s == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
|
@ -94,6 +97,7 @@ func (s *State) Guild(guildID string) (*Guild, error) {
|
|||
if s == nil {
|
||||
return nil, ErrNilState
|
||||
}
|
||||
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
|
@ -294,6 +298,7 @@ func (s *State) PrivateChannel(channelID string) (*Channel, error) {
|
|||
if s == nil {
|
||||
return nil, ErrNilState
|
||||
}
|
||||
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
|
@ -429,6 +434,7 @@ func (s *State) MessageRemove(message *Message) error {
|
|||
if s == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
|
||||
c, err := s.Channel(message.ChannelID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -452,6 +458,7 @@ func (s *State) Message(channelID, messageID string) (*Message, error) {
|
|||
if s == nil {
|
||||
return nil, ErrNilState
|
||||
}
|
||||
|
||||
c, err := s.Channel(channelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -468,3 +475,46 @@ func (s *State) Message(channelID, messageID string) (*Message, error) {
|
|||
|
||||
return nil, errors.New("Message not found.")
|
||||
}
|
||||
|
||||
// onInterface handles all events related to states.
|
||||
func (s *State) onInterface(se *Session, i interface{}) (err error) {
|
||||
if s == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
if !se.StateEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch t := i.(type) {
|
||||
case *Ready:
|
||||
err = s.OnReady(t)
|
||||
case *GuildCreate:
|
||||
err = s.GuildAdd(t.Guild)
|
||||
case *GuildUpdate:
|
||||
err = s.GuildAdd(t.Guild)
|
||||
case *GuildDelete:
|
||||
err = s.GuildRemove(t.Guild)
|
||||
case *GuildMemberAdd:
|
||||
err = s.MemberAdd(t.Member)
|
||||
case *GuildMemberUpdate:
|
||||
err = s.MemberAdd(t.Member)
|
||||
case *GuildMemberRemove:
|
||||
err = s.MemberRemove(t.Member)
|
||||
case *GuildEmojisUpdate:
|
||||
err = s.EmojisAdd(t.GuildID, t.Emojis)
|
||||
case *ChannelCreate:
|
||||
err = s.ChannelAdd(t.Channel)
|
||||
case *ChannelUpdate:
|
||||
err = s.ChannelAdd(t.Channel)
|
||||
case *ChannelDelete:
|
||||
err = s.ChannelRemove(t.Channel)
|
||||
case *MessageCreate:
|
||||
err = s.MessageAdd(t.Message)
|
||||
case *MessageUpdate:
|
||||
err = s.MessageAdd(t.Message)
|
||||
case *MessageDelete:
|
||||
err = s.MessageRemove(t.Message)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
102
structs.go
102
structs.go
|
@ -13,84 +13,66 @@ package discordgo
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// A Session represents a connection to the Discord REST API.
|
||||
// token : The authentication token returned from Discord
|
||||
// Debug : If set to ture debug logging will be displayed.
|
||||
// A Session represents a connection to the Discord API.
|
||||
type Session struct {
|
||||
sync.RWMutex
|
||||
|
||||
// General configurable settings.
|
||||
Token string // Authentication token for this session
|
||||
Debug bool // Debug for printing JSON request/responses
|
||||
|
||||
// Settable Callback functions for Internal Events
|
||||
// OnConnect is called when the websocket connection opens.
|
||||
OnConnect func(*Session)
|
||||
// OnDisconnect is called when the websocket connection closes.
|
||||
// This is a good handler to add reconnection logic to.
|
||||
OnDisconnect func(*Session)
|
||||
// Authentication token for this session
|
||||
Token string
|
||||
|
||||
// Settable Callback functions for Websocket Events
|
||||
OnEvent func(*Session, *Event)
|
||||
OnReady func(*Session, *Ready)
|
||||
OnTypingStart func(*Session, *TypingStart)
|
||||
OnMessageCreate func(*Session, *Message)
|
||||
OnMessageUpdate func(*Session, *Message)
|
||||
OnMessageDelete func(*Session, *Message)
|
||||
OnMessageAck func(*Session, *MessageAck)
|
||||
OnUserUpdate func(*Session, *User)
|
||||
OnPresenceUpdate func(*Session, *PresenceUpdate)
|
||||
OnVoiceServerUpdate func(*Session, *VoiceServerUpdate)
|
||||
OnVoiceStateUpdate func(*Session, *VoiceState)
|
||||
OnChannelCreate func(*Session, *Channel)
|
||||
OnChannelUpdate func(*Session, *Channel)
|
||||
OnChannelDelete func(*Session, *Channel)
|
||||
OnGuildCreate func(*Session, *Guild)
|
||||
OnGuildUpdate func(*Session, *Guild)
|
||||
OnGuildDelete func(*Session, *Guild)
|
||||
OnGuildMemberAdd func(*Session, *Member)
|
||||
OnGuildMemberRemove func(*Session, *Member)
|
||||
OnGuildMemberDelete func(*Session, *Member)
|
||||
OnGuildMemberUpdate func(*Session, *Member)
|
||||
OnGuildRoleCreate func(*Session, *GuildRole)
|
||||
OnGuildRoleUpdate func(*Session, *GuildRole)
|
||||
OnGuildRoleDelete func(*Session, *GuildRoleDelete)
|
||||
OnGuildIntegrationsUpdate func(*Session, *GuildIntegrationsUpdate)
|
||||
OnGuildBanAdd func(*Session, *GuildBan)
|
||||
OnGuildBanRemove func(*Session, *GuildBan)
|
||||
OnGuildEmojisUpdate func(*Session, *GuildEmojisUpdate)
|
||||
OnUserSettingsUpdate func(*Session, map[string]interface{}) // TODO: Find better way?
|
||||
|
||||
// Exposed but should not be modified by User.
|
||||
SessionID string // from websocket READY packet
|
||||
DataReady bool // Set to true when Data Websocket is ready
|
||||
VoiceReady bool // Set to true when Voice Websocket is ready
|
||||
UDPReady bool // Set to true when UDP Connection is ready
|
||||
|
||||
// The websocket connection.
|
||||
wsConn *websocket.Conn
|
||||
|
||||
// Stores all details related to voice connections
|
||||
Voice *Voice
|
||||
|
||||
// Managed state object, updated with events.
|
||||
State *State
|
||||
StateEnabled bool
|
||||
|
||||
// When nil, the session is not listening.
|
||||
listening chan interface{}
|
||||
// Debug for printing JSON request/responses
|
||||
Debug bool
|
||||
|
||||
// Should the session reconnect the websocket on errors.
|
||||
ShouldReconnectOnError bool
|
||||
|
||||
// Should the session request compressed websocket data.
|
||||
Compress bool
|
||||
|
||||
// Should state tracking be enabled.
|
||||
// State tracking is the best way for getting the the users
|
||||
// active guilds and the members of the guilds.
|
||||
StateEnabled bool
|
||||
|
||||
// Exposed but should not be modified by User.
|
||||
|
||||
// Whether the Data Websocket is ready
|
||||
DataReady bool
|
||||
|
||||
// Whether the Voice Websocket is ready
|
||||
VoiceReady bool
|
||||
|
||||
// Whether the UDP Connection is ready
|
||||
UDPReady bool
|
||||
|
||||
// Stores all details related to voice connections
|
||||
Voice *Voice
|
||||
|
||||
// Managed state object, updated internally with events when
|
||||
// StateEnabled is true.
|
||||
State *State
|
||||
|
||||
// This is a mapping of event structs to a reflected value
|
||||
// for event handlers.
|
||||
// We store the reflected value instead of the function
|
||||
// reference as it is more performant, instead of re-reflecting
|
||||
// the function each event.
|
||||
handlers map[interface{}][]reflect.Value
|
||||
|
||||
// The websocket connection.
|
||||
wsConn *websocket.Conn
|
||||
|
||||
// When nil, the session is not listening.
|
||||
listening chan interface{}
|
||||
}
|
||||
|
||||
// A VoiceRegion stores data for a specific voice region server.
|
||||
|
|
473
wsapi.go
473
wsapi.go
|
@ -18,6 +18,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
|
@ -86,9 +87,7 @@ func (s *Session) Open() (err error) {
|
|||
|
||||
s.Unlock()
|
||||
|
||||
if s.OnConnect != nil {
|
||||
s.OnConnect(s)
|
||||
}
|
||||
s.handle(&Connect{})
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -112,9 +111,7 @@ func (s *Session) Close() (err error) {
|
|||
|
||||
s.Unlock()
|
||||
|
||||
if s.OnDisconnect != nil {
|
||||
s.OnDisconnect(s)
|
||||
}
|
||||
s.handle(&Disconnect{})
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -247,15 +244,36 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Not sure how needed this is and where it would be best to call it.
|
||||
// somewhere.
|
||||
|
||||
func unmarshalEvent(event *Event, i interface{}) (err error) {
|
||||
if err = unmarshal(event.RawData, i); err != nil {
|
||||
fmt.Println("Unable to unmarshal event data.")
|
||||
printEvent(event)
|
||||
}
|
||||
return
|
||||
// eventToInterface is a mapping of Discord WSAPI events to their
|
||||
// DiscordGo event container.
|
||||
var eventToInterface = map[string]interface{}{
|
||||
"CHANNEL_CREATE": ChannelCreate{},
|
||||
"CHANNEL_UPDATE": ChannelUpdate{},
|
||||
"CHANNEL_DELETE": ChannelDelete{},
|
||||
"GUILD_CREATE": GuildCreate{},
|
||||
"GUILD_UPDATE": GuildUpdate{},
|
||||
"GUILD_DELETE": GuildDelete{},
|
||||
"GUILD_BAN_ADD": GuildBanAdd{},
|
||||
"GUILD_BAN_REMOVE": GuildBanRemove{},
|
||||
"GUILD_MEMBER_ADD": GuildMemberAdd{},
|
||||
"GUILD_MEMBER_UPDATE": GuildMemberUpdate{},
|
||||
"GUILD_MEMBER_REMOVE": GuildMemberRemove{},
|
||||
"GUILD_ROLE_CREATE": GuildRoleCreate{},
|
||||
"GUILD_ROLE_UPDATE": GuildRoleUpdate{},
|
||||
"GUILD_ROLE_DELETE": GuildRoleDelete{},
|
||||
"GUILD_INTEGRATIONS_UPDATE": GuildIntegrationsUpdate{},
|
||||
"GUILD_EMOJIS_UPDATE": GuildEmojisUpdate{},
|
||||
"MESSAGE_ACK": MessageAck{},
|
||||
"MESSAGE_CREATE": MessageCreate{},
|
||||
"MESSAGE_UPDATE": MessageUpdate{},
|
||||
"MESSAGE_DELETE": MessageDelete{},
|
||||
"PRESENCE_UPDATE": PresenceUpdate{},
|
||||
"READY": Ready{},
|
||||
"USER_UPDATE": UserUpdate{},
|
||||
"USER_SETTINGS_UPDATE": UserSettingsUpdate{},
|
||||
"TYPING_START": TypingStart{},
|
||||
"VOICE_SERVER_UPDATE": VoiceServerUpdate{},
|
||||
"VOICE_STATE_UPDATE": VoiceStateUpdate{},
|
||||
}
|
||||
|
||||
// Front line handler for all Websocket Events. Determines the
|
||||
|
@ -266,6 +284,14 @@ func unmarshalEvent(event *Event, i interface{}) (err error) {
|
|||
// 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) {
|
||||
s.RLock()
|
||||
if s.handlers == nil {
|
||||
s.RUnlock()
|
||||
s.initialize()
|
||||
} else {
|
||||
s.RUnlock()
|
||||
}
|
||||
|
||||
var err error
|
||||
var reader io.Reader
|
||||
reader = bytes.NewBuffer(message)
|
||||
|
@ -296,406 +322,23 @@ func (s *Session) event(messageType int, message []byte) {
|
|||
printEvent(e)
|
||||
}
|
||||
|
||||
switch e.Type {
|
||||
case "READY":
|
||||
var st *Ready
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
go s.heartbeat(s.wsConn, s.listening, st.HeartbeatInterval)
|
||||
if s.StateEnabled {
|
||||
err := s.State.OnReady(st)
|
||||
if err != nil {
|
||||
fmt.Println("error: ", err)
|
||||
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 = unmarshal(e.RawData, i); err != nil {
|
||||
fmt.Println("Unable to unmarshal event data.")
|
||||
i = e
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Unknown event.")
|
||||
i = e
|
||||
}
|
||||
|
||||
}
|
||||
if s.OnReady != nil {
|
||||
s.OnReady(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnReady != nil {
|
||||
return
|
||||
}
|
||||
case "VOICE_SERVER_UPDATE":
|
||||
if s.Voice == nil && s.OnVoiceServerUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *VoiceServerUpdate
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.Voice != nil {
|
||||
s.onVoiceServerUpdate(st)
|
||||
}
|
||||
if s.OnVoiceServerUpdate != nil {
|
||||
s.OnVoiceServerUpdate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnVoiceServerUpdate != nil {
|
||||
return
|
||||
}
|
||||
case "VOICE_STATE_UPDATE":
|
||||
if s.Voice == nil && s.OnVoiceStateUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *VoiceState
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.Voice != nil {
|
||||
s.onVoiceStateUpdate(st)
|
||||
}
|
||||
if s.OnVoiceStateUpdate != nil {
|
||||
s.OnVoiceStateUpdate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnVoiceStateUpdate != nil {
|
||||
return
|
||||
}
|
||||
case "USER_UPDATE":
|
||||
if s.OnUserUpdate != nil {
|
||||
var st *User
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnUserUpdate(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "PRESENCE_UPDATE":
|
||||
if s.OnPresenceUpdate != nil {
|
||||
var st *PresenceUpdate
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnPresenceUpdate(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "TYPING_START":
|
||||
if s.OnTypingStart != nil {
|
||||
var st *TypingStart
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnTypingStart(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
/* Never seen this come in but saw it in another Library.
|
||||
case "MESSAGE_ACK":
|
||||
if s.OnMessageAck != nil {
|
||||
}
|
||||
*/
|
||||
case "MESSAGE_CREATE":
|
||||
stateEnabled := s.StateEnabled && s.State.MaxMessageCount > 0
|
||||
if !stateEnabled && s.OnMessageCreate == nil {
|
||||
break
|
||||
}
|
||||
var st *Message
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if stateEnabled {
|
||||
err := s.State.MessageAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnMessageCreate != nil {
|
||||
s.OnMessageCreate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnMessageCreate != nil {
|
||||
return
|
||||
}
|
||||
case "MESSAGE_UPDATE":
|
||||
stateEnabled := s.StateEnabled && s.State.MaxMessageCount > 0
|
||||
if !stateEnabled && s.OnMessageUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *Message
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if stateEnabled {
|
||||
err := s.State.MessageAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnMessageUpdate != nil {
|
||||
s.OnMessageUpdate(s, st)
|
||||
}
|
||||
}
|
||||
return
|
||||
case "MESSAGE_DELETE":
|
||||
stateEnabled := s.StateEnabled && s.State.MaxMessageCount > 0
|
||||
if !stateEnabled && s.OnMessageDelete == nil {
|
||||
break
|
||||
}
|
||||
var st *Message
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if stateEnabled {
|
||||
err := s.State.MessageRemove(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnMessageDelete != nil {
|
||||
s.OnMessageDelete(s, st)
|
||||
}
|
||||
}
|
||||
return
|
||||
case "MESSAGE_ACK":
|
||||
if s.OnMessageAck != nil {
|
||||
var st *MessageAck
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnMessageAck(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "CHANNEL_CREATE":
|
||||
if !s.StateEnabled && s.OnChannelCreate == nil {
|
||||
break
|
||||
}
|
||||
var st *Channel
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.ChannelAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnChannelCreate != nil {
|
||||
s.OnChannelCreate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnChannelCreate != nil {
|
||||
return
|
||||
}
|
||||
case "CHANNEL_UPDATE":
|
||||
if !s.StateEnabled && s.OnChannelUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *Channel
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.ChannelAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnChannelUpdate != nil {
|
||||
s.OnChannelUpdate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnChannelUpdate != nil {
|
||||
return
|
||||
}
|
||||
case "CHANNEL_DELETE":
|
||||
if !s.StateEnabled && s.OnChannelDelete == nil {
|
||||
break
|
||||
}
|
||||
var st *Channel
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.ChannelRemove(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnChannelDelete != nil {
|
||||
s.OnChannelDelete(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnChannelDelete != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_CREATE":
|
||||
if !s.StateEnabled && s.OnGuildCreate == nil {
|
||||
break
|
||||
}
|
||||
var st *Guild
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.GuildAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildCreate != nil {
|
||||
s.OnGuildCreate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildCreate != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_UPDATE":
|
||||
if !s.StateEnabled && s.OnGuildUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *Guild
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.GuildAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildCreate != nil {
|
||||
s.OnGuildUpdate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildUpdate != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_DELETE":
|
||||
if !s.StateEnabled && s.OnGuildDelete == nil {
|
||||
break
|
||||
}
|
||||
var st *Guild
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.GuildRemove(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildDelete != nil {
|
||||
s.OnGuildDelete(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildDelete != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_MEMBER_ADD":
|
||||
if !s.StateEnabled && s.OnGuildMemberAdd == nil {
|
||||
break
|
||||
}
|
||||
var st *Member
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.MemberAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildMemberAdd != nil {
|
||||
s.OnGuildMemberAdd(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildMemberAdd != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_MEMBER_REMOVE":
|
||||
if !s.StateEnabled && s.OnGuildMemberRemove == nil {
|
||||
break
|
||||
}
|
||||
var st *Member
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.MemberRemove(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildMemberRemove != nil {
|
||||
s.OnGuildMemberRemove(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildMemberRemove != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_MEMBER_UPDATE":
|
||||
if !s.StateEnabled && s.OnGuildMemberUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *Member
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.MemberAdd(st)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildMemberUpdate != nil {
|
||||
s.OnGuildMemberUpdate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildMemberUpdate != nil {
|
||||
return
|
||||
}
|
||||
case "GUILD_ROLE_CREATE":
|
||||
if s.OnGuildRoleCreate != nil {
|
||||
var st *GuildRole
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnGuildRoleCreate(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "GUILD_ROLE_UPDATE":
|
||||
if s.OnGuildRoleUpdate != nil {
|
||||
var st *GuildRole
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnGuildRoleUpdate(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "GUILD_ROLE_DELETE":
|
||||
if s.OnGuildRoleDelete != nil {
|
||||
var st *GuildRoleDelete
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnGuildRoleDelete(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "GUILD_INTEGRATIONS_UPDATE":
|
||||
if s.OnGuildIntegrationsUpdate != nil {
|
||||
var st *GuildIntegrationsUpdate
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnGuildIntegrationsUpdate(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "GUILD_BAN_ADD":
|
||||
if s.OnGuildBanAdd != nil {
|
||||
var st *GuildBan
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnGuildBanAdd(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "GUILD_BAN_REMOVE":
|
||||
if s.OnGuildBanRemove != nil {
|
||||
var st *GuildBan
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnGuildBanRemove(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
case "GUILD_EMOJIS_UPDATE":
|
||||
if !s.StateEnabled && s.OnGuildEmojisUpdate == nil {
|
||||
break
|
||||
}
|
||||
var st *GuildEmojisUpdate
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
if s.StateEnabled {
|
||||
err := s.State.EmojisAdd(st.GuildID, st.Emojis)
|
||||
if err != nil {
|
||||
fmt.Println("error :", err)
|
||||
}
|
||||
}
|
||||
if s.OnGuildEmojisUpdate != nil {
|
||||
s.OnGuildEmojisUpdate(s, st)
|
||||
}
|
||||
}
|
||||
if s.OnGuildEmojisUpdate != nil {
|
||||
return
|
||||
}
|
||||
case "USER_SETTINGS_UPDATE":
|
||||
if s.OnUserSettingsUpdate != nil {
|
||||
var st map[string]interface{}
|
||||
if err = unmarshalEvent(e, &st); err == nil {
|
||||
s.OnUserSettingsUpdate(s, st)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
fmt.Println("Unknown Event.")
|
||||
printEvent(e)
|
||||
}
|
||||
|
||||
// if still here, send to generic OnEvent
|
||||
if s.OnEvent != nil {
|
||||
s.OnEvent(s, e)
|
||||
return
|
||||
}
|
||||
s.handle(i)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -765,7 +408,7 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (err error)
|
|||
// onVoiceStateUpdate handles Voice State Update events on the data
|
||||
// websocket. This comes immediately after the call to VoiceChannelJoin
|
||||
// for the session user.
|
||||
func (s *Session) onVoiceStateUpdate(st *VoiceState) {
|
||||
func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
|
||||
|
||||
// Need to have this happen at login and store it in the Session
|
||||
// TODO : This should be done upon connecting to Discord, or
|
||||
|
@ -791,7 +434,7 @@ func (s *Session) onVoiceStateUpdate(st *VoiceState) {
|
|||
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
|
||||
// This event tells us the information needed to open a voice websocket
|
||||
// connection and should happen after the VOICE_STATE event.
|
||||
func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
|
||||
func (s *Session) onVoiceServerUpdate(se *Session, st *VoiceServerUpdate) {
|
||||
|
||||
// Store values for later use
|
||||
s.Voice.token = st.Token
|
||||
|
|
Loading…
Reference in a new issue