From 64af0e5f4ac73665985aaf5d0b9366c0aef5d699 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sat, 13 Feb 2016 18:28:29 -0800 Subject: [PATCH 01/10] Support TTS. Closes #67 --- restapi.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/restapi.go b/restapi.go index ffb5587..d1860bc 100644 --- a/restapi.go +++ b/restapi.go @@ -759,17 +759,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) @@ -781,6 +781,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 From fb6ae92555b6a3a90a926fb2b9b03c067b57c0d1 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sun, 14 Feb 2016 13:17:20 -0800 Subject: [PATCH 02/10] Add basic support for mapped event handlers. --- discord.go | 62 ++++- state.go | 28 ++ structs.go | 2 + wsapi.go | 737 ++++++++++++++++++++++++----------------------------- 4 files changed, 425 insertions(+), 404 deletions(-) diff --git a/discord.go b/discord.go index 68d2a7e..1ad8c7b 100644 --- a/discord.go +++ b/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,60 @@ func New(args ...interface{}) (s *Session, err error) { return } + +func (s *Session) AddHandler(handler interface{}) { + 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.") + } + + eventType := handlerType.In(1) + + if s.Handlers == nil { + s.initialize() + } + + handlers := s.Handlers[eventType] + if handlers == nil { + handlers = []interface{}{} + } + + handlers = append(handlers, handler) + s.Handlers[eventType] = handlers +} + +func (s *Session) Handle(event interface{}) (handled bool) { + eventType := reflect.TypeOf(event) + + handlers, ok := s.Handlers[eventType] + if !ok { + return + } + + for _, handler := range handlers { + reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}) + handled = true + } + + return +} + +// initialize adds internal handlers such as onEvent and state tracking +// handlers. +func (s *Session) initialize() { + s.Handlers = map[interface{}][]interface{}{} + s.AddHandler(s.ready) + s.AddHandler(s.State.ready) + s.AddHandler(s.State.messageCreate) + s.AddHandler(s.State.messageUpdate) + s.AddHandler(s.State.messageDelete) +} + +func (s *Session) ready(se *Session, r *Ready) { + go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) +} diff --git a/state.go b/state.go index 0ee8963..189a17e 100644 --- a/state.go +++ b/state.go @@ -468,3 +468,31 @@ func (s *State) Message(channelID, messageID string) (*Message, error) { return nil, errors.New("Message not found.") } + +// ready is an event handler. +func (s *State) ready(se *Session, r *Ready) { + if se.StateEnabled { + s.OnReady(r) + } +} + +// messageCreate is an event handler. +func (s *State) messageCreate(se *Session, m *MessageCreate) { + if se.StateEnabled { + s.MessageAdd(&m.Message) + } +} + +// messageUpdate is an event handler. +func (s *State) messageUpdate(se *Session, m *MessageUpdate) { + if se.StateEnabled { + s.MessageAdd(&m.Message) + } +} + +// messageDelete is an event handler. +func (s *State) messageDelete(se *Session, m *MessageDelete) { + if se.StateEnabled { + s.MessageRemove(&m.Message) + } +} diff --git a/structs.go b/structs.go index 7275ed4..6d5bf55 100644 --- a/structs.go +++ b/structs.go @@ -36,6 +36,8 @@ type Session struct { // This is a good handler to add reconnection logic to. OnDisconnect func(*Session) + Handlers map[interface{}][]interface{} + // Settable Callback functions for Websocket Events OnEvent func(*Session, *Event) OnReady func(*Session, *Ready) diff --git a/wsapi.go b/wsapi.go index f2d055c..ee05872 100644 --- a/wsapi.go +++ b/wsapi.go @@ -247,17 +247,6 @@ 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 -} - // Front line handler for all Websocket Events. Determines the // event type and passes the message along to the next handler. @@ -266,6 +255,10 @@ 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) { + if s.Handlers == nil { + s.initialize() + } + var err error var reader io.Reader reader = bytes.NewBuffer(message) @@ -296,405 +289,343 @@ func (s *Session) event(messageType int, message []byte) { printEvent(e) } + var i interface{} + 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) - } - - } - 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 { - } - */ + i = &Ready{} 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 - } + i = &MessageCreate{} 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 + i = &MessageUpdate{} 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) + i = &MessageDelete{} + case "PRESENCE_UPDATE": + i = &PresenceUpdate{} + case "TYPING_START": + i = &TypingStart{} } - // if still here, send to generic OnEvent - if s.OnEvent != nil { - s.OnEvent(s, e) - 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 + // } + + // /* Never seen this come in but saw it in another Library. + // case "MESSAGE_ACK": + // if s.OnMessageAck != nil { + // } + // */ + + // 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) + // } + + // Attempt to unmarshal our event. + // If there is an error (eg. we don't know how to handle it) we should handle the event itself. + if err = unmarshal(e.RawData, i); err != nil { + fmt.Println("Unable to unmarshal event data.") + printEvent(e) + i = e + } + + if !s.Handle(i) { + if i != e { + // If there was not a handler for the struct, handle the event, as long as it wasn't the + // event we were trying to handle. + s.Handle(e) + } } return From 8ffaa85b0bb7625a1db603cf255035c12c6fca3a Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sun, 14 Feb 2016 13:41:56 -0800 Subject: [PATCH 03/10] Thread safety and more events. --- discord.go | 34 ++++++++++++++------ state.go | 22 ++++++------- structs.go | 38 ---------------------- wsapi.go | 94 ++++++++++++++++++------------------------------------ 4 files changed, 66 insertions(+), 122 deletions(-) diff --git a/discord.go b/discord.go index 1ad8c7b..f683bff 100644 --- a/discord.go +++ b/discord.go @@ -123,6 +123,9 @@ func New(args ...interface{}) (s *Session, err error) { } func (s *Session) AddHandler(handler interface{}) { + s.Lock() + defer s.Unlock() + handlerType := reflect.TypeOf(handler) if handlerType.NumIn() != 2 { @@ -133,12 +136,14 @@ func (s *Session) AddHandler(handler interface{}) { panic("Unable to add event handler, first argument must be of type *discordgo.Session.") } - eventType := handlerType.In(1) - if s.Handlers == nil { + s.Unlock() s.initialize() + s.Lock() } + eventType := handlerType.In(1) + handlers := s.Handlers[eventType] if handlers == nil { handlers = []interface{}{} @@ -149,6 +154,9 @@ func (s *Session) AddHandler(handler interface{}) { } func (s *Session) Handle(event interface{}) (handled bool) { + s.RLock() + defer s.RUnlock() + eventType := reflect.TypeOf(event) handlers, ok := s.Handlers[eventType] @@ -164,17 +172,23 @@ func (s *Session) Handle(event interface{}) (handled bool) { return } -// initialize adds internal handlers such as onEvent and state tracking -// handlers. +// initialize adds all internal handlers and state tracking handlers. func (s *Session) initialize() { + s.Lock() + defer s.Unlock() + s.Handlers = map[interface{}][]interface{}{} - s.AddHandler(s.ready) - s.AddHandler(s.State.ready) - s.AddHandler(s.State.messageCreate) - s.AddHandler(s.State.messageUpdate) - s.AddHandler(s.State.messageDelete) + s.AddHandler(s.onReady) + s.AddHandler(s.onVoiceServerUpdate) + s.AddHandler(s.onVoiceStateUpdate) + + s.AddHandler(s.State.onReady) + s.AddHandler(s.State.onMessageCreate) + s.AddHandler(s.State.onMessageUpdate) + s.AddHandler(s.State.onMessageDelete) } -func (s *Session) ready(se *Session, r *Ready) { +// onReady handles the ready event. +func (s *Session) onReady(se *Session, r *Ready) { go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) } diff --git a/state.go b/state.go index 189a17e..0fe8a95 100644 --- a/state.go +++ b/state.go @@ -469,30 +469,30 @@ func (s *State) Message(channelID, messageID string) (*Message, error) { return nil, errors.New("Message not found.") } -// ready is an event handler. -func (s *State) ready(se *Session, r *Ready) { +// onReady handles the ready event. +func (s *State) onReady(se *Session, r *Ready) { if se.StateEnabled { s.OnReady(r) } } -// messageCreate is an event handler. -func (s *State) messageCreate(se *Session, m *MessageCreate) { +// onMessageCreate handles the messageCreate event. +func (s *State) onMessageCreate(se *Session, m *MessageCreate) { if se.StateEnabled { - s.MessageAdd(&m.Message) + s.MessageAdd(m.Message) } } -// messageUpdate is an event handler. -func (s *State) messageUpdate(se *Session, m *MessageUpdate) { +// onMessageUpdate handles the messageUpdate event. +func (s *State) onMessageUpdate(se *Session, m *MessageUpdate) { if se.StateEnabled { - s.MessageAdd(&m.Message) + s.MessageAdd(m.Message) } } -// messageDelete is an event handler. -func (s *State) messageDelete(se *Session, m *MessageDelete) { +// onMessageDelete handles the messageDelete event. +func (s *State) onMessageDelete(se *Session, m *MessageDelete) { if se.StateEnabled { - s.MessageRemove(&m.Message) + s.MessageRemove(m.Message) } } diff --git a/structs.go b/structs.go index 6d5bf55..1bb8279 100644 --- a/structs.go +++ b/structs.go @@ -29,46 +29,8 @@ type Session struct { 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) - Handlers map[interface{}][]interface{} - // 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 diff --git a/wsapi.go b/wsapi.go index ee05872..281fe98 100644 --- a/wsapi.go +++ b/wsapi.go @@ -86,9 +86,7 @@ func (s *Session) Open() (err error) { s.Unlock() - if s.OnConnect != nil { - s.OnConnect(s) - } + s.Handle(&Connect{}) return } @@ -112,9 +110,7 @@ func (s *Session) Close() (err error) { s.Unlock() - if s.OnDisconnect != nil { - s.OnDisconnect(s) - } + s.Handle(&Disconnect{}) return } @@ -255,8 +251,12 @@ func (s *Session) UpdateStatus(idle int, game string) (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 @@ -291,6 +291,9 @@ func (s *Session) event(messageType int, message []byte) { var i interface{} + // TODO(iopred): Figure out a clean way to do this with a map, simply + // creating a map[string]interface{} will not work, as that will reuse + // the same instance for each event. switch e.Type { case "READY": i = &Ready{} @@ -304,63 +307,28 @@ func (s *Session) event(messageType int, message []byte) { i = &PresenceUpdate{} case "TYPING_START": i = &TypingStart{} + case "VOICE_SERVER_UPDATE": + i = &VoiceServerUpdate{} + case "VOICE_STATE_UPDATE": + i = &VoiceStateUpdate{} + case "USER_UPDATE": + i = &UserUpdate{} + case "MESSAGE_ACK": + i = &MessageAck{} + case "GUILD_ROLE_CREATE": + i = &GuildRoleCreate{} + case "GUILD_ROLE_UPDATE": + i = &GuildRoleUpdate{} + case "GUILD_ROLE_DELETE": + i = &GuildRoleDelete{} + case "GUILD_INTEGRATIONS_UPDATE": + i = &GuildIntegrationsUpdate{} + case "GUILD_BAN_ADD": + i = &GuildBanAdd{} + case "GUILD_BAN_REMOVE": + i = &GuildBanRemove{} } - // 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 - // } - - // /* Never seen this come in but saw it in another Library. - // case "MESSAGE_ACK": - // if s.OnMessageAck != nil { - // } - // */ - - // 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 @@ -696,7 +664,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 @@ -722,7 +690,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 From 6fa99712efb8d774fa1d3b34347b2c0eb465b195 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sun, 14 Feb 2016 18:48:37 -0800 Subject: [PATCH 04/10] Finish converting large switch statement into a map of functions. --- discord.go | 39 ++-- events.go | 100 +++++++++ examples/api_basic/api_basic.go | 11 +- examples/new_basic/new_basic.go | 8 +- state.go | 63 +++--- wsapi.go | 352 +++++--------------------------- 6 files changed, 220 insertions(+), 353 deletions(-) create mode 100644 events.go diff --git a/discord.go b/discord.go index f683bff..dd436c8 100644 --- a/discord.go +++ b/discord.go @@ -144,6 +144,11 @@ func (s *Session) AddHandler(handler interface{}) { 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 = []interface{}{} @@ -153,39 +158,39 @@ func (s *Session) AddHandler(handler interface{}) { s.Handlers[eventType] = handlers } -func (s *Session) Handle(event interface{}) (handled bool) { +func (s *Session) handle(event interface{}) { s.RLock() defer s.RUnlock() - eventType := reflect.TypeOf(event) - - handlers, ok := s.Handlers[eventType] - if !ok { - return + if handlers, ok := s.Handlers[reflect.TypeOf(event)]; ok { + for _, handler := range handlers { + reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}) + } } - for _, handler := range handlers { - reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}) - handled = true + if handlers, ok := s.Handlers[nil]; ok { + for _, handler := range handlers { + reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}) + } } - - return } // initialize adds all internal handlers and state tracking handlers. func (s *Session) initialize() { s.Lock() - defer s.Unlock() - s.Handlers = map[interface{}][]interface{}{} + s.Unlock() + + s.AddHandler(s.onEvent) s.AddHandler(s.onReady) s.AddHandler(s.onVoiceServerUpdate) s.AddHandler(s.onVoiceStateUpdate) + s.AddHandler(s.State.onInterface) +} - s.AddHandler(s.State.onReady) - s.AddHandler(s.State.onMessageCreate) - s.AddHandler(s.State.onMessageUpdate) - s.AddHandler(s.State.onMessageDelete) +// 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. diff --git a/events.go b/events.go new file mode 100644 index 0000000..d81ecef --- /dev/null +++ b/events.go @@ -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{} diff --git a/examples/api_basic/api_basic.go b/examples/api_basic/api_basic.go index aea4e56..733cece 100644 --- a/examples/api_basic/api_basic.go +++ b/examples/api_basic/api_basic.go @@ -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) diff --git a/examples/new_basic/new_basic.go b/examples/new_basic/new_basic.go index 43492ec..6d524ac 100644 --- a/examples/new_basic/new_basic.go +++ b/examples/new_basic/new_basic.go @@ -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) diff --git a/state.go b/state.go index 0fe8a95..8074853 100644 --- a/state.go +++ b/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 @@ -469,30 +476,40 @@ func (s *State) Message(channelID, messageID string) (*Message, error) { return nil, errors.New("Message not found.") } -// onReady handles the ready event. -func (s *State) onReady(se *Session, r *Ready) { - if se.StateEnabled { - s.OnReady(r) +// onInterface handles all events related to states. +func (s *State) onInterface(se *Session, i interface{}) { + if s == nil || !se.StateEnabled { + return } -} -// onMessageCreate handles the messageCreate event. -func (s *State) onMessageCreate(se *Session, m *MessageCreate) { - if se.StateEnabled { - s.MessageAdd(m.Message) - } -} - -// onMessageUpdate handles the messageUpdate event. -func (s *State) onMessageUpdate(se *Session, m *MessageUpdate) { - if se.StateEnabled { - s.MessageAdd(m.Message) - } -} - -// onMessageDelete handles the messageDelete event. -func (s *State) onMessageDelete(se *Session, m *MessageDelete) { - if se.StateEnabled { - s.MessageRemove(m.Message) + switch t := i.(type) { + case *Ready: + s.OnReady(t) + case *GuildCreate: + s.GuildAdd(t.Guild) + case *GuildUpdate: + s.GuildAdd(t.Guild) + case *GuildDelete: + s.GuildRemove(t.Guild) + case *GuildMemberAdd: + s.MemberAdd(t.Member) + case *GuildMemberUpdate: + s.MemberAdd(t.Member) + case *GuildMemberRemove: + s.MemberRemove(t.Member) + case *GuildEmojisUpdate: + s.EmojisAdd(t.GuildID, t.Emojis) + case *ChannelCreate: + s.ChannelAdd(t.Channel) + case *ChannelUpdate: + s.ChannelAdd(t.Channel) + case *ChannelDelete: + s.ChannelRemove(t.Channel) + case *MessageCreate: + s.MessageAdd(t.Message) + case *MessageUpdate: + s.MessageAdd(t.Message) + case *MessageDelete: + s.MessageRemove(t.Message) } } diff --git a/wsapi.go b/wsapi.go index 281fe98..6584072 100644 --- a/wsapi.go +++ b/wsapi.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "net/http" + "reflect" "runtime" "time" @@ -86,7 +87,7 @@ func (s *Session) Open() (err error) { s.Unlock() - s.Handle(&Connect{}) + s.handle(&Connect{}) return } @@ -110,7 +111,7 @@ func (s *Session) Close() (err error) { s.Unlock() - s.Handle(&Disconnect{}) + s.handle(&Disconnect{}) return } @@ -243,6 +244,38 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) { 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 // event type and passes the message along to the next handler. @@ -289,312 +322,23 @@ func (s *Session) event(messageType int, message []byte) { printEvent(e) } - var i interface{} + i := eventToInterface[e.Type] + if i != nil { + // Create a new instance of the event type. + i = reflect.New(reflect.TypeOf(i)).Interface() - // TODO(iopred): Figure out a clean way to do this with a map, simply - // creating a map[string]interface{} will not work, as that will reuse - // the same instance for each event. - switch e.Type { - case "READY": - i = &Ready{} - case "MESSAGE_CREATE": - i = &MessageCreate{} - case "MESSAGE_UPDATE": - i = &MessageUpdate{} - case "MESSAGE_DELETE": - i = &MessageDelete{} - case "PRESENCE_UPDATE": - i = &PresenceUpdate{} - case "TYPING_START": - i = &TypingStart{} - case "VOICE_SERVER_UPDATE": - i = &VoiceServerUpdate{} - case "VOICE_STATE_UPDATE": - i = &VoiceStateUpdate{} - case "USER_UPDATE": - i = &UserUpdate{} - case "MESSAGE_ACK": - i = &MessageAck{} - case "GUILD_ROLE_CREATE": - i = &GuildRoleCreate{} - case "GUILD_ROLE_UPDATE": - i = &GuildRoleUpdate{} - case "GUILD_ROLE_DELETE": - i = &GuildRoleDelete{} - case "GUILD_INTEGRATIONS_UPDATE": - i = &GuildIntegrationsUpdate{} - case "GUILD_BAN_ADD": - i = &GuildBanAdd{} - case "GUILD_BAN_REMOVE": - i = &GuildBanRemove{} - } - - // 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) - // } - - // Attempt to unmarshal our event. - // If there is an error (eg. we don't know how to handle it) we should handle the event itself. - if err = unmarshal(e.RawData, i); err != nil { - fmt.Println("Unable to unmarshal event data.") - printEvent(e) + // 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.Handle(i) { - if i != e { - // If there was not a handler for the struct, handle the event, as long as it wasn't the - // event we were trying to handle. - s.Handle(e) - } - } + s.handle(i) return } From 88335b6f5470e2b195363c631c40d03637379ef6 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sun, 14 Feb 2016 19:01:45 -0800 Subject: [PATCH 05/10] Small performance improvement. Added tests. --- discord.go | 12 +++++++----- discord_test.go | 36 ++++++++++++++++++++++++++++++++++++ structs.go | 8 +++++++- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/discord.go b/discord.go index dd436c8..cf276e7 100644 --- a/discord.go +++ b/discord.go @@ -151,10 +151,10 @@ func (s *Session) AddHandler(handler interface{}) { handlers := s.Handlers[eventType] if handlers == nil { - handlers = []interface{}{} + handlers = []reflect.Value{} } - handlers = append(handlers, handler) + handlers = append(handlers, reflect.ValueOf(handler)) s.Handlers[eventType] = handlers } @@ -162,15 +162,17 @@ 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 { - reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}) + handler.Call(handlerParameters) } } if handlers, ok := s.Handlers[nil]; ok { for _, handler := range handlers { - reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}) + handler.Call(handlerParameters) } } } @@ -178,7 +180,7 @@ func (s *Session) handle(event interface{}) { // initialize adds all internal handlers and state tracking handlers. func (s *Session) initialize() { s.Lock() - s.Handlers = map[interface{}][]interface{}{} + s.Handlers = map[interface{}][]reflect.Value{} s.Unlock() s.AddHandler(s.onEvent) diff --git a/discord_test.go b/discord_test.go index 687f820..7442651 100644 --- a/discord_test.go +++ b/discord_test.go @@ -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.") + } +} diff --git a/structs.go b/structs.go index 1bb8279..8185902 100644 --- a/structs.go +++ b/structs.go @@ -13,6 +13,7 @@ package discordgo import ( "encoding/json" + "reflect" "sync" "time" @@ -29,7 +30,12 @@ type Session struct { Token string // Authentication token for this session Debug bool // Debug for printing JSON request/responses - Handlers map[interface{}][]interface{} + // 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 // Exposed but should not be modified by User. SessionID string // from websocket READY packet From b083ce00c7c4f0fb1de43dea6e2d328a9a97564c Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sun, 14 Feb 2016 21:58:29 -0800 Subject: [PATCH 06/10] Un-expose handlers. Clean up Session struct. --- discord.go | 12 +++++----- structs.go | 70 ++++++++++++++++++++++++++++++++---------------------- wsapi.go | 2 +- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/discord.go b/discord.go index cf276e7..18f7ffa 100644 --- a/discord.go +++ b/discord.go @@ -136,7 +136,7 @@ func (s *Session) AddHandler(handler interface{}) { panic("Unable to add event handler, first argument must be of type *discordgo.Session.") } - if s.Handlers == nil { + if s.handlers == nil { s.Unlock() s.initialize() s.Lock() @@ -149,13 +149,13 @@ func (s *Session) AddHandler(handler interface{}) { eventType = nil } - handlers := s.Handlers[eventType] + handlers := s.handlers[eventType] if handlers == nil { handlers = []reflect.Value{} } handlers = append(handlers, reflect.ValueOf(handler)) - s.Handlers[eventType] = handlers + s.handlers[eventType] = handlers } func (s *Session) handle(event interface{}) { @@ -164,13 +164,13 @@ func (s *Session) handle(event interface{}) { handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)} - if handlers, ok := s.Handlers[reflect.TypeOf(event)]; ok { + if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok { for _, handler := range handlers { handler.Call(handlerParameters) } } - if handlers, ok := s.Handlers[nil]; ok { + if handlers, ok := s.handlers[nil]; ok { for _, handler := range handlers { handler.Call(handlerParameters) } @@ -180,7 +180,7 @@ func (s *Session) handle(event interface{}) { // initialize adds all internal handlers and state tracking handlers. func (s *Session) initialize() { s.Lock() - s.Handlers = map[interface{}][]reflect.Value{} + s.handlers = map[interface{}][]reflect.Value{} s.Unlock() s.AddHandler(s.onEvent) diff --git a/structs.go b/structs.go index 8185902..0d10c1c 100644 --- a/structs.go +++ b/structs.go @@ -20,47 +20,59 @@ import ( "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 - // 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 + // Authentication token for this session + Token string - // 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. diff --git a/wsapi.go b/wsapi.go index 6584072..b75c7e0 100644 --- a/wsapi.go +++ b/wsapi.go @@ -285,7 +285,7 @@ var eventToInterface = map[string]interface{}{ // All unhandled events will then be handled by OnEvent. func (s *Session) event(messageType int, message []byte) { s.RLock() - if s.Handlers == nil { + if s.handlers == nil { s.RUnlock() s.initialize() } else { From 0bd7fbf6684afceaa994d8c9e2672c3900d46377 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Mon, 15 Feb 2016 18:38:15 -0800 Subject: [PATCH 07/10] Support new leave API. --- endpoints.go | 1 + restapi.go | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/endpoints.go b/endpoints.go index e366b39..03fc776 100644 --- a/endpoints.go +++ b/endpoints.go @@ -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" } diff --git a/restapi.go b/restapi.go index ffb5587..c16e7ac 100644 --- a/restapi.go +++ b/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) (st *Guild, 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. From 32dc320386e0954d46aeab6d5233ba43693e255b Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Mon, 15 Feb 2016 18:39:43 -0800 Subject: [PATCH 08/10] :ok_hand: --- restapi.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/restapi.go b/restapi.go index c16e7ac..b8de133 100644 --- a/restapi.go +++ b/restapi.go @@ -405,9 +405,9 @@ func (s *Session) GuildDelete(guildID string) (st *Guild, err error) { // GuildLeave leaves a Guild. // guildID : The ID of a Guild -func (s *Session) GuildLeave(guildID string) (st *Guild, err error) { +func (s *Session) GuildLeave(guildID string) (err error) { - _, err := s.Request("DELETE", USER_GUILD("@me", guildID), nil) + _, err = s.Request("DELETE", USER_GUILD("@me", guildID), nil) return } From aebfb09719c3478b3ddc9df546bdec042c35c138 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Mon, 15 Feb 2016 21:50:41 -0600 Subject: [PATCH 09/10] Baby Linting. --- discord.go | 2 ++ restapi.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/discord.go b/discord.go index 18f7ffa..f5852d8 100644 --- a/discord.go +++ b/discord.go @@ -122,6 +122,8 @@ 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() diff --git a/restapi.go b/restapi.go index e3cf409..2c59882 100644 --- a/restapi.go +++ b/restapi.go @@ -844,9 +844,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 { From 4895ace11d4569588a29d934e661f4dcc053706c Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Mon, 15 Feb 2016 20:07:01 -0800 Subject: [PATCH 10/10] Return state errors. Because we're good citizens. --- state.go | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/state.go b/state.go index 8074853..2bdf35f 100644 --- a/state.go +++ b/state.go @@ -477,39 +477,44 @@ func (s *State) Message(channelID, messageID string) (*Message, error) { } // onInterface handles all events related to states. -func (s *State) onInterface(se *Session, i interface{}) { - if s == nil || !se.StateEnabled { - return +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: - s.OnReady(t) + err = s.OnReady(t) case *GuildCreate: - s.GuildAdd(t.Guild) + err = s.GuildAdd(t.Guild) case *GuildUpdate: - s.GuildAdd(t.Guild) + err = s.GuildAdd(t.Guild) case *GuildDelete: - s.GuildRemove(t.Guild) + err = s.GuildRemove(t.Guild) case *GuildMemberAdd: - s.MemberAdd(t.Member) + err = s.MemberAdd(t.Member) case *GuildMemberUpdate: - s.MemberAdd(t.Member) + err = s.MemberAdd(t.Member) case *GuildMemberRemove: - s.MemberRemove(t.Member) + err = s.MemberRemove(t.Member) case *GuildEmojisUpdate: - s.EmojisAdd(t.GuildID, t.Emojis) + err = s.EmojisAdd(t.GuildID, t.Emojis) case *ChannelCreate: - s.ChannelAdd(t.Channel) + err = s.ChannelAdd(t.Channel) case *ChannelUpdate: - s.ChannelAdd(t.Channel) + err = s.ChannelAdd(t.Channel) case *ChannelDelete: - s.ChannelRemove(t.Channel) + err = s.ChannelRemove(t.Channel) case *MessageCreate: - s.MessageAdd(t.Message) + err = s.MessageAdd(t.Message) case *MessageUpdate: - s.MessageAdd(t.Message) + err = s.MessageAdd(t.Message) case *MessageDelete: - s.MessageRemove(t.Message) + err = s.MessageRemove(t.Message) } + + return }