Skip to content

Commit

Permalink
Merge pull request #109 from iopred/map
Browse files Browse the repository at this point in the history
BREAKING CHANGE -- Mapped Event Handlers
  • Loading branch information
bwmarrin committed Feb 16, 2016
2 parents 0dea869 + b083ce0 commit 28236b4
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 481 deletions.
83 changes: 82 additions & 1 deletion discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -118,3 +121,81 @@ func New(args ...interface{}) (s *Session, err error) {

return
}

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 changes: 36 additions & 0 deletions discord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
}
100 changes: 100 additions & 0 deletions events.go
Original file line number Diff line number Diff line change
@@ -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{}
11 changes: 6 additions & 5 deletions examples/api_basic/api_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions examples/new_basic/new_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand Down
45 changes: 45 additions & 0 deletions state.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (s *State) OnReady(r *Ready) error {
if s == nil {
return ErrNilState
}

s.Lock()
defer s.Unlock()

Expand All @@ -48,6 +49,7 @@ func (s *State) GuildAdd(guild *Guild) error {
if s == nil {
return ErrNilState
}

s.Lock()
defer s.Unlock()

Expand All @@ -73,6 +75,7 @@ func (s *State) GuildRemove(guild *Guild) error {
if s == nil {
return ErrNilState
}

s.Lock()
defer s.Unlock()

Expand All @@ -94,6 +97,7 @@ func (s *State) Guild(guildID string) (*Guild, error) {
if s == nil {
return nil, ErrNilState
}

s.RLock()
defer s.RUnlock()

Expand Down Expand Up @@ -294,6 +298,7 @@ func (s *State) PrivateChannel(channelID string) (*Channel, error) {
if s == nil {
return nil, ErrNilState
}

s.RLock()
defer s.RUnlock()

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -468,3 +475,41 @@ 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{}) {
if s == nil || !se.StateEnabled {
return
}

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)
}
}
Loading

0 comments on commit 28236b4

Please sign in to comment.