Jelajahi Sumber

Merge branch 'develop' into guilds

Chris Rhodes 9 tahun lalu
induk
melakukan
0ea10a300b
10 mengubah file dengan 414 tambahan dan 488 penghapusan
  1. 84 1
      discord.go
  2. 36 0
      discord_test.go
  3. 1 0
      endpoints.go
  4. 100 0
      events.go
  5. 6 5
      examples/api_basic/api_basic.go
  6. 4 4
      examples/new_basic/new_basic.go
  7. 37 7
      restapi.go
  8. 50 0
      state.go
  9. 40 58
      structs.go
  10. 56 413
      wsapi.go

+ 84 - 1
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)
+}

+ 36 - 0
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.")
+	}
+}

+ 1 - 0
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" }

+ 100 - 0
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{}

+ 6 - 5
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)

+ 4 - 4
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)

+ 37 - 7
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 - 0
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
+}

+ 40 - 58
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)
-
-	// 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?
+
+	// Authentication token for this session
+	Token string
+
+	// 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.
-	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
+	// 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 with events.
-	State        *State
-	StateEnabled bool
+	// Managed state object, updated internally with events when
+	// StateEnabled is true.
+	State *State
 
-	// When nil, the session is not listening.
-	listening chan 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
 
-	// Should the session reconnect the websocket on errors.
-	ShouldReconnectOnError bool
+	// The websocket connection.
+	wsConn *websocket.Conn
 
-	// Should the session request compressed websocket data.
-	Compress bool
+	// When nil, the session is not listening.
+	listening chan interface{}
 }
 
 // A VoiceRegion stores data for a specific voice region server.

+ 56 - 413
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()
 
-			}
-			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)
-			}
+		// 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
 		}
-		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)
+	} else {
+		fmt.Println("Unknown event.")
+		i = 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