Skip to content

Commit

Permalink
add SlashCommand, UserCommand, MessageCommand, ButtonComponent & Sele…
Browse files Browse the repository at this point in the history
…ctMenuComponent methods on handler.Mux
  • Loading branch information
topi314 committed Mar 15, 2024
1 parent 0361f5d commit 96b42c2
Show file tree
Hide file tree
Showing 9 changed files with 663 additions and 13 deletions.
61 changes: 59 additions & 2 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package handler

import (
"errors"
"slices"
"strings"

"github.com/disgoorg/snowflake/v2"
Expand Down Expand Up @@ -48,10 +49,11 @@ type handlerHolder[T any] struct {
pattern string
handler T
t discord.InteractionType
t2 []int
}

func (h *handlerHolder[T]) Match(path string, t discord.InteractionType) bool {
if h.t != t {
func (h *handlerHolder[T]) Match(path string, t discord.InteractionType, t2 int) bool {
if h.t != t || (len(h.t2) > 0 && !slices.Contains(h.t2, t2)) {
return false
}
parts := splitPath(path)
Expand Down Expand Up @@ -85,6 +87,39 @@ func (h *handlerHolder[T]) Handle(path string, event *InteractionEvent) error {
Vars: event.Vars,
Ctx: event.Ctx,
})
case SlashCommandHandler:
commandInteraction := event.Interaction.(discord.ApplicationCommandInteraction)
return handler(commandInteraction.Data.(discord.SlashCommandInteractionData), &CommandEvent{
ApplicationCommandInteractionCreate: &events.ApplicationCommandInteractionCreate{
GenericEvent: event.GenericEvent,
ApplicationCommandInteraction: commandInteraction,
Respond: event.Respond,
},
Vars: event.Vars,
Ctx: event.Ctx,
})
case UserCommandHandler:
commandInteraction := event.Interaction.(discord.ApplicationCommandInteraction)
return handler(commandInteraction.Data.(discord.UserCommandInteractionData), &CommandEvent{
ApplicationCommandInteractionCreate: &events.ApplicationCommandInteractionCreate{
GenericEvent: event.GenericEvent,
ApplicationCommandInteraction: commandInteraction,
Respond: event.Respond,
},
Vars: event.Vars,
Ctx: event.Ctx,
})
case MessageCommandHandler:
commandInteraction := event.Interaction.(discord.ApplicationCommandInteraction)
return handler(commandInteraction.Data.(discord.MessageCommandInteractionData), &CommandEvent{
ApplicationCommandInteractionCreate: &events.ApplicationCommandInteractionCreate{
GenericEvent: event.GenericEvent,
ApplicationCommandInteraction: commandInteraction,
Respond: event.Respond,
},
Vars: event.Vars,
Ctx: event.Ctx,
})
case AutocompleteHandler:
return handler(&AutocompleteEvent{
AutocompleteInteractionCreate: &events.AutocompleteInteractionCreate{
Expand All @@ -105,6 +140,28 @@ func (h *handlerHolder[T]) Handle(path string, event *InteractionEvent) error {
Vars: event.Vars,
Ctx: event.Ctx,
})
case ButtonComponentHandler:
componentInteraction := event.Interaction.(discord.ComponentInteraction)
return handler(componentInteraction.Data.(discord.ButtonInteractionData), &ComponentEvent{
ComponentInteractionCreate: &events.ComponentInteractionCreate{
GenericEvent: event.GenericEvent,
ComponentInteraction: componentInteraction,
Respond: event.Respond,
},
Vars: event.Vars,
Ctx: event.Ctx,
})
case SelectMenuComponentHandler:
componentInteraction := event.Interaction.(discord.ComponentInteraction)
return handler(componentInteraction.Data.(discord.SelectMenuInteractionData), &ComponentEvent{
ComponentInteractionCreate: &events.ComponentInteractionCreate{
GenericEvent: event.GenericEvent,
ComponentInteraction: componentInteraction,
Respond: event.Respond,
},
Vars: event.Vars,
Ctx: event.Ctx,
})
case ModalHandler:
return handler(&ModalEvent{
ModalSubmitInteractionCreate: &events.ModalSubmitInteractionCreate{
Expand Down
76 changes: 73 additions & 3 deletions handler/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (r *Mux) OnEvent(event bot.Event) {
}

// Match returns true if the given path matches the Route.
func (r *Mux) Match(path string, t discord.InteractionType) bool {
func (r *Mux) Match(path string, t discord.InteractionType, t2 int) bool {
if r.pattern != "" {
parts := splitPath(path)
patternParts := splitPath(r.pattern)
Expand All @@ -99,7 +99,7 @@ func (r *Mux) Match(path string, t discord.InteractionType) bool {
}

for _, matcher := range r.routes {
if matcher.Match(path, t) {
if matcher.Match(path, t, t2) {
return true
}
}
Expand All @@ -111,8 +111,17 @@ func (r *Mux) Handle(path string, event *InteractionEvent) error {
handlerChain := Handler(func(event *InteractionEvent) error {
path = parseVariables(path, r.pattern, event.Vars)

t := event.Type()
var t2 int
switch i := event.Interaction.(type) {
case discord.ApplicationCommandInteraction:
t2 = int(i.Data.Type())
case discord.ComponentInteraction:
t2 = int(i.Data.Type())
}

for _, route := range r.routes {
if route.Match(path, event.Type()) {
if route.Match(path, t, t2) {
return route.Handle(path, event)
}
}
Expand Down Expand Up @@ -189,6 +198,39 @@ func (r *Mux) Command(pattern string, h CommandHandler) {
})
}

// SlashCommand registers the given SlashCommandHandler to the current Router.
func (r *Mux) SlashCommand(pattern string, h SlashCommandHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[SlashCommandHandler]{
pattern: pattern,
handler: h,
t: discord.InteractionTypeApplicationCommand,
t2: []int{discord.ApplicationCommandTypeSlash},
})
}

// UserCommand registers the given UserCommandHandler to the current Router.
func (r *Mux) UserCommand(pattern string, h UserCommandHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[UserCommandHandler]{
pattern: pattern,
handler: h,
t: discord.InteractionTypeApplicationCommand,
t2: []int{discord.ApplicationCommandTypeUser},
})
}

// MessageCommand registers the given MessageCommandHandler to the current Router.
func (r *Mux) MessageCommand(pattern string, h MessageCommandHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[MessageCommandHandler]{
pattern: pattern,
handler: h,
t: discord.InteractionTypeApplicationCommand,
t2: []int{discord.ApplicationCommandTypeMessage},
})
}

// Autocomplete registers the given AutocompleteHandler to the current Router.
func (r *Mux) Autocomplete(pattern string, h AutocompleteHandler) {
checkPattern(pattern)
Expand All @@ -209,6 +251,34 @@ func (r *Mux) Component(pattern string, h ComponentHandler) {
})
}

// ButtonComponent registers the given ButtonComponentHandler to the current Router.
func (r *Mux) ButtonComponent(pattern string, h ButtonComponentHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[ButtonComponentHandler]{
pattern: pattern,
handler: h,
t: discord.InteractionTypeComponent,
t2: []int{discord.ComponentTypeButton},
})
}

// SelectMenuComponent registers the given SelectMenuComponentHandler to the current Router.
func (r *Mux) SelectMenuComponent(pattern string, h SelectMenuComponentHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[SelectMenuComponentHandler]{
pattern: pattern,
handler: h,
t: discord.InteractionTypeComponent,
t2: []int{
discord.ComponentTypeStringSelectMenu,
discord.ComponentTypeUserSelectMenu,
discord.ComponentTypeRoleSelectMenu,
discord.ComponentTypeMentionableSelectMenu,
discord.ComponentTypeChannelSelectMenu,
},
})
}

// Modal registers the given ModalHandler to the current Router.
func (r *Mux) Modal(pattern string, h ModalHandler) {
checkPattern(pattern)
Expand Down
158 changes: 158 additions & 0 deletions handler/mux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package handler

import (
"os"
"testing"

"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgo/rest"
"github.com/stretchr/testify/assert"
)

func NewRecorder() *InteractionResponseRecorder {
return &InteractionResponseRecorder{}
}

type InteractionResponseRecorder struct {
Response *discord.InteractionResponse
}

func (i *InteractionResponseRecorder) Respond(responseType discord.InteractionResponseType, data discord.InteractionResponseData, opts ...rest.RequestOpt) error {
i.Response = &discord.InteractionResponse{
Type: responseType,
Data: data,
}
return nil
}

func TestCommandMux(t *testing.T) {
slashData, err := os.ReadFile("testdata/command/slash_command.json")
assert.NoError(t, err)

userData, err := os.ReadFile("testdata/command/user_command.json")
assert.NoError(t, err)

messageData, err := os.ReadFile("testdata/command/message_command.json")
assert.NoError(t, err)

data := []struct {
data []byte
expected *discord.InteractionResponse
}{
{
data: slashData,
expected: &discord.InteractionResponse{
Type: discord.InteractionResponseTypeCreateMessage,
Data: discord.MessageCreate{
Content: "bar",
},
},
},
{
data: userData,
expected: &discord.InteractionResponse{
Type: discord.InteractionResponseTypeCreateMessage,
Data: discord.MessageCreate{
Content: "bar2",
},
},
},
{
data: messageData,
expected: &discord.InteractionResponse{
Type: discord.InteractionResponseTypeCreateMessage,
Data: discord.MessageCreate{
Content: "bar3",
},
},
},
}

mux := New()
mux.SlashCommand("/foo", func(data discord.SlashCommandInteractionData, e *CommandEvent) error {
return e.CreateMessage(discord.MessageCreate{
Content: "bar",
})
})
mux.UserCommand("/foo", func(data discord.UserCommandInteractionData, e *CommandEvent) error {
return e.CreateMessage(discord.MessageCreate{
Content: "bar2",
})
})
mux.MessageCommand("/foo", func(data discord.MessageCommandInteractionData, e *CommandEvent) error {
return e.CreateMessage(discord.MessageCreate{
Content: "bar3",
})
})

for _, d := range data {
interaction, err := discord.UnmarshalInteraction(d.data)
assert.NoError(t, err)

recorder := NewRecorder()
mux.OnEvent(&events.InteractionCreate{
GenericEvent: events.NewGenericEvent(nil, 0, 0),
Interaction: interaction,
Respond: recorder.Respond,
})
assert.Equal(t, d.expected, recorder.Response)
}
}

func TestComponentMux(t *testing.T) {
buttonData, err := os.ReadFile("testdata/component/button_component.json")
assert.NoError(t, err)

selectMenuData, err := os.ReadFile("testdata/component/select_menu_component.json")
assert.NoError(t, err)

data := []struct {
data []byte
expected *discord.InteractionResponse
}{
{
data: buttonData,
expected: &discord.InteractionResponse{
Type: discord.InteractionResponseTypeCreateMessage,
Data: discord.MessageCreate{
Content: "bar",
},
},
},
{
data: selectMenuData,
expected: &discord.InteractionResponse{
Type: discord.InteractionResponseTypeCreateMessage,
Data: discord.MessageCreate{
Content: "bar2",
},
},
},
}

mux := New()
mux.ButtonComponent("/foo", func(data discord.ButtonInteractionData, e *ComponentEvent) error {
return e.CreateMessage(discord.MessageCreate{
Content: "bar",
})
})
mux.SelectMenuComponent("/foo", func(data discord.SelectMenuInteractionData, e *ComponentEvent) error {
return e.CreateMessage(discord.MessageCreate{
Content: "bar2",
})
})

for _, d := range data {
interaction, err := discord.UnmarshalInteraction(d.data)
assert.NoError(t, err)

recorder := NewRecorder()
mux.OnEvent(&events.InteractionCreate{
GenericEvent: events.NewGenericEvent(nil, 0, 0),
Interaction: interaction,
Respond: recorder.Respond,
})
assert.Equal(t, d.expected, recorder.Response)
}
}
Loading

0 comments on commit 96b42c2

Please sign in to comment.