diff --git a/state/event_dispatcher.go b/state/event_dispatcher.go index fe32a5e..5251d55 100644 --- a/state/event_dispatcher.go +++ b/state/event_dispatcher.go @@ -4,23 +4,14 @@ import ( "github.com/diamondburned/arikawa/gateway" ) -func (s *State) handleEvent(ev interface{}) { - if s.PreHandler != nil { - s.PreHandler.Call(ev) - } - s.Handler.Call(ev) -} - func (s *State) handleReady(ev *gateway.ReadyEvent) { - s.handleEvent(ev) - for _, g := range ev.Guilds { // store this so we know when we need to dispatch a belated // GuildReadyEvent if g.Unavailable { s.unreadyGuilds.Add(g.ID) } else { - s.handleEvent(&GuildReadyEvent{ + s.Handler.Call(&GuildReadyEvent{ GuildCreateEvent: &g, }) } @@ -28,47 +19,39 @@ func (s *State) handleReady(ev *gateway.ReadyEvent) { } func (s *State) handleGuildCreate(ev *gateway.GuildCreateEvent) { - // before we dispatch the specific events, we can already call the handlers - // that subscribed to the generic version - s.handleEvent(ev) - // this guild was unavailable, but has come back online if s.unavailableGuilds.Delete(ev.ID) { - s.handleEvent(&GuildAvailableEvent{ + s.Handler.Call(&GuildAvailableEvent{ GuildCreateEvent: ev, }) // the guild was already unavailable when connecting to the gateway // we can dispatch a belated GuildReadyEvent } else if s.unreadyGuilds.Delete(ev.ID) { - s.handleEvent(&GuildReadyEvent{ + s.Handler.Call(&GuildReadyEvent{ GuildCreateEvent: ev, }) } else { // we don't know this guild, hence we just joined it - s.handleEvent(&GuildJoinEvent{ + s.Handler.Call(&GuildJoinEvent{ GuildCreateEvent: ev, }) } } func (s *State) handleGuildDelete(ev *gateway.GuildDeleteEvent) { - // before we dispatch the specific events, we can already call the handlers - // that subscribed to the generic version - s.handleEvent(ev) - // store this so we can later dispatch a GuildAvailableEvent, once the // guild becomes available again. if ev.Unavailable { s.unavailableGuilds.Add(ev.ID) - s.handleEvent(&GuildUnavailableEvent{ + s.Handler.Call(&GuildUnavailableEvent{ GuildDeleteEvent: ev, }) } else { // it might have been unavailable before we left s.unavailableGuilds.Delete(ev.ID) - s.handleEvent(&GuildLeaveEvent{ + s.Handler.Call(&GuildLeaveEvent{ GuildDeleteEvent: ev, }) } diff --git a/state/state.go b/state/state.go index 7a3ad47..3e77d8c 100644 --- a/state/state.go +++ b/state/state.go @@ -20,6 +20,42 @@ var ( MaxFetchGuilds uint = 100 ) +// State is the cache to store events coming from Discord as well as data from +// API calls. +// +// Store +// +// The state basically provides abstractions on top of the API and the state +// storage (Store). The state storage is effectively a set of interfaces which +// allow arbitrary backends to be implemented. +// +// The default storage backend is a typical in-memory structure consisting of +// maps and slices. Custom backend implementations could embed this storage +// backend as an in-memory fallback. A good example of this would be embedding +// the default store for messages only, while handling everything else in Redis. +// +// The package also provides a no-op store (NoopStore) that implementations +// could embed. This no-op store will always return an error, which makes the +// state fetch information from the API. The setters are all no-ops, so the +// fetched data won't be updated. +// +// Handler +// +// The state uses its own handler over session's to make all handlers run after +// the state updates itself. A PreHandler is exposed in any case the user needs +// the handlers to run before the state updates itself. Refer to that field's +// documentation. +// +// The state also provides extra events and overrides to make up for Discord's +// inconsistencies in data. The following are known instances of such. +// +// The Guild Create event is split up to make the state's Guild Available, Guild +// Ready and Guild Join events. Refer to these events' documentations for more +// information. +// +// The Message Create and Message Update events with the Member field provided +// will have the User field copied from Author. This is because the User field +// will be empty, while the Member structure expects it to be there. type State struct { *session.Session Store @@ -41,7 +77,7 @@ type State struct { // Command handler with inherited methods. Ran after PreHandler. You should // most of the time use this instead of Session's, to avoid race conditions - // with the State + // with the State. *handler.Handler unhooker func() diff --git a/state/state_events.go b/state/state_events.go index facf4a8..689aec9 100644 --- a/state/state_events.go +++ b/state/state_events.go @@ -7,23 +7,42 @@ import ( "github.com/diamondburned/arikawa/gateway" ) -func (s *State) hookSession() error { - s.unhooker = s.Session.AddHandler(func(iface interface{}) { - s.onEvent(iface) +func (s *State) hookSession() { + s.unhooker = s.Session.AddHandler(func(event interface{}) { + // Call the pre-handler before the state handler. + if s.PreHandler != nil { + s.PreHandler.Call(event) + } - switch e := iface.(type) { + // Run the state handler. + s.onEvent(event) + + // Always call the event on function exit. Extra events constructed by + // handlers will be called before the main event, but that's fine. + defer s.Handler.Call(event) + + switch e := event.(type) { case *gateway.ReadyEvent: s.handleReady(e) + return case *gateway.GuildCreateEvent: s.handleGuildCreate(e) + return case *gateway.GuildDeleteEvent: s.handleGuildDelete(e) - default: - s.handleEvent(iface) + return + + // https://github.com/discord/discord-api-docs/commit/01665c4 + case *gateway.MessageCreateEvent: + if e.Member != nil { + e.Member.User = e.Author + } + case *gateway.MessageUpdateEvent: + if e.Member != nil { + e.Member.User = e.Author + } } }) - - return nil } func (s *State) onEvent(iface interface{}) {