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