Skip to content

Commit

Permalink
move to radix trie based routing (epic)
Browse files Browse the repository at this point in the history
  • Loading branch information
Karitham committed Dec 25, 2021
1 parent 1140fe9 commit 3c59d50
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 90 deletions.
2 changes: 1 addition & 1 deletion _example/bongo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func main() {
}

m := corde.NewMux(pk, appID, token)
m.Mount(corde.SlashCommand("bongo"), bongoHandler)
m.Command("bongo", bongoHandler)

g := corde.GuildOpt(corde.SnowflakeFromString(os.Getenv("DISCORD_GUILD_ID")))
if err := m.RegisterCommand(command, g); err != nil {
Expand Down
45 changes: 7 additions & 38 deletions _example/moderate-myself/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,6 @@ var command = corde.Command{
Type: corde.OPTION_SUB_COMMAND,
Description: "list existing slash commands",
},
{
Name: "remove",
Type: corde.OPTION_SUB_COMMAND,
Description: "remove slash commands",
Options: []corde.Option{
{
Name: "name",
Type: corde.OPTION_STRING,
Description: "name of the slash command you wish to remove",
Required: true,
},
},
},
},
}

Expand All @@ -54,10 +41,9 @@ func main() {
selectedID := 0

m := corde.NewMux(pk, appID, token)
m.Mount(corde.SlashCommand("cmd/list"), list(m, g))
m.Mount(corde.SlashCommand("cmd/remove"), rm(m, g))
m.Mount(corde.ButtonInteraction("btn-cmd/list/next"), btnNext(m, g, mu, &selectedID))
m.Mount(corde.ButtonInteraction("btn-cmd/list/remove"), btnRemove(m, g, mu, &selectedID))
m.Command("cmd/list", list(m, g))
m.Button("cmd/list/next", btnNext(m, g, mu, &selectedID))
m.Button("cmd/list/remove", btnRemove(m, g, mu, &selectedID))

if err := m.RegisterCommand(command, g); err != nil {
log.Fatalln("error registering command: ", err)
Expand All @@ -70,17 +56,17 @@ func main() {

var nextBtn = corde.Component{
Type: corde.COMPONENT_BUTTON,
CustomID: "btn-cmd/list/next",
CustomID: "cmd/list/next",
Style: corde.BUTTON_SECONDARY,
Label: "next",
Label: "Next",
Emoji: &corde.Emoji{Name: "➡️"},
}

var delBtn = corde.Component{
Type: corde.COMPONENT_BUTTON,
CustomID: "btn-cmd/list/remove",
CustomID: "cmd/list/remove",
Style: corde.BUTTON_DANGER,
Label: "remove",
Label: "Delete",
Emoji: &corde.Emoji{Name: "🗑️"},
}

Expand All @@ -95,23 +81,6 @@ func list(m *corde.Mux, g func(*corde.CommandsOpt)) func(corde.ResponseWriter, *
}
}

func rm(m *corde.Mux, g func(*corde.CommandsOpt)) func(corde.ResponseWriter, *corde.Interaction) {
return func(w corde.ResponseWriter, i *corde.Interaction) {
n := i.Data.Options.String("name")

c, _ := m.GetCommands(g)
for _, c := range c {
if c.Name == n {
m.DeleteCommand(c.ID, g)
w.Respond(corde.NewResp().Content("No command named %s found.", n).Ephemeral().B())
return
}
}

w.Respond(corde.NewResp().Contentf("No command named %s found.", n).Ephemeral().B())
}
}

func btnNext(m *corde.Mux, g func(*corde.CommandsOpt), mu *sync.Mutex, selectedID *int) func(corde.ResponseWriter, *corde.Interaction) {
return func(w corde.ResponseWriter, i *corde.Interaction) {
mu.Lock()
Expand Down
6 changes: 3 additions & 3 deletions _example/todo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ func main() {
}

m := corde.NewMux(pk, appID, token)
m.Mount(corde.SlashCommand("todo/add"), t.addHandler)
m.Mount(corde.SlashCommand("todo/rm"), t.removeHandler)
m.Mount(corde.SlashCommand("todo/list"), t.listHandler)
m.Command("todo/add", t.addHandler)
m.Command("todo/rm", t.removeHandler)
m.Command("todo/list", t.listHandler)

g := corde.GuildOpt(corde.SnowflakeFromString(os.Getenv("DISCORD_GUILD_ID")))
m.BulkRegisterCommand(commands, g)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ module github.com/Karitham/corde
go 1.18

require github.com/karitham/go-genial v0.2.0

require github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3 h1:cC+usowX2qUArGE+BauZSn2qCxHqBXhBmA3CkFMULBg=
github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3/go.mod h1:YPJ4C/zdh9bNuHZCccyEX3xnq7vtjMw2Lu8RgBohgRk=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/karitham/go-genial v0.2.0 h1:AHb2iBbzOBS8kc3E0QiZR3Z6txdBS9u0IDhGGketskE=
github.com/karitham/go-genial v0.2.0/go.mod h1:HBN0CvV0hsPCMegF7wvmUK3l8OnNzs8XVxt350NI5eE=
107 changes: 59 additions & 48 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,20 @@ import (
"net/http"
"sync"
"time"
)

type InteractionCommand struct {
Type InteractionType `json:"type"`
Route string `json:"name"`
}

func SlashCommand(route string) InteractionCommand {
return InteractionCommand{Type: APPLICATION_COMMAND, Route: route}
}
radix "github.com/akrennmair/go-radix"
)

func ButtonInteraction(customID string) InteractionCommand {
return InteractionCommand{Type: MESSAGE_COMPONENT, Route: customID}
type routes struct {
command *radix.Tree[Handler]
autocomplete *radix.Tree[Handler]
component *radix.Tree[Handler]
}

// Mux is a discord gateway muxer, which handles the routing
type Mux struct {
rMu *sync.RWMutex
routes map[InteractionCommand]Handler
routes routes
PublicKey string // the hex public key provided by discord
BasePath string // base route path, default is "/"
OnNotFound Handler
Expand All @@ -33,12 +28,6 @@ type Mux struct {
BotToken string
}

// Routes return the discord routes mounted on the mux
// DO NOT EDIT THOSE, IN RISK OF HAVING ROUTING ISSUES
func (m *Mux) Routes() map[InteractionCommand]Handler {
return m.routes
}

// Lock the mux, to be able to mount or unmount routes
func (m *Mux) Lock() {
m.rMu.Lock()
Expand All @@ -49,17 +38,53 @@ func (m *Mux) Unlock() {
m.rMu.Unlock()
}

func (m *Mux) Mount(command InteractionCommand, handler Handler) {
func (m *Mux) Mount(typ InteractionType, route string, handler Handler) {
m.rMu.Lock()
defer m.rMu.Unlock()
m.routes[command] = handler

switch typ {
case APPLICATION_COMMAND:
m.routes.command.Insert(route, &handler)
case APPLICATION_COMMAND_AUTOCOMPLETE:
m.routes.autocomplete.Insert(route, &handler)
case MESSAGE_COMPONENT:
m.routes.component.Insert(route, &handler)
}
}

// Button mounts a button route on the mux
func (m *Mux) Button(route string, handler Handler) {
m.rMu.Lock()
defer m.rMu.Unlock()
m.routes.component.Insert(route, &handler)
}

// Autocomplete mounts an autocomplete route on the mux
func (m *Mux) Autocomplete(route string, handler Handler) {
m.rMu.Lock()
defer m.rMu.Unlock()
m.routes.autocomplete.Insert(route, &handler)
}

// Command mounts a slash command route on the mux
func (m *Mux) Command(route string, handler Handler) {
m.rMu.Lock()
defer m.rMu.Unlock()
m.routes.command.Insert(route, &handler)
}

// NewMux returns a new mux for routing slash commands
//
// When you mount a command on the mux, it's prefix based routed,
// which means you can route to a button like `/list/next/456132153` having mounted `/list/next`
func NewMux(publicKey string, appID Snowflake, botToken string) *Mux {
return &Mux{
rMu: &sync.RWMutex{},
routes: make(map[InteractionCommand]Handler),
rMu: &sync.RWMutex{},
routes: routes{
command: radix.New[Handler](),
autocomplete: radix.New[Handler](),
component: radix.New[Handler](),
},
PublicKey: publicKey,
BasePath: "/",
OnNotFound: func(_ ResponseWriter, i *Interaction) {
Expand Down Expand Up @@ -116,42 +141,28 @@ func (m *Mux) routeReq(r ResponseWriter, i *Interaction) {
case PING:
r.pong()
case MESSAGE_COMPONENT:
if h, ok := m.routes[InteractionCommand{Type: i.Type, Route: i.Data.CustomID}]; ok {
h(r, i)
if _, h, ok := m.routes.component.LongestPrefix(i.Data.CustomID); ok {
(*h)(r, i)
return
}

for optName := range i.Data.Options {
nr := InteractionCommand{Type: i.Type, Route: i.Data.Name + "/" + i.Data.CustomID}

if handler, ok := m.routes[nr]; ok {
i.Data.Name += "/" + optName
handler(r, i)
return
}
}

m.OnNotFound(r, i)
case APPLICATION_COMMAND:
fallthrough
case APPLICATION_COMMAND_AUTOCOMPLETE:
if h, ok := m.routes[InteractionCommand{Type: i.Type, Route: i.Data.Name}]; ok {
h(r, i)
if _, h, ok := m.routes.command.LongestPrefix(i.Data.Name); ok {
(*h)(r, i)
return
}

for optName := range i.Data.Options {
nr := InteractionCommand{Type: i.Type, Route: i.Data.Name + "/" + optName}

if handler, ok := m.routes[nr]; ok {
i.Data.Name += "/" + optName
handler(r, i)
nr := i.Data.Name + "/" + optName
if _, h, ok := m.routes.command.LongestPrefix(nr); ok {
i.Data.Name = nr
(*h)(r, i)
return
}
}

m.OnNotFound(r, i)
case APPLICATION_COMMAND_AUTOCOMPLETE:
log.Println("unimplemented autocomplete")
r.Respond(NewResp().Ephemeral().Content("unimplemented autocomplete").B())
}
m.OnNotFound(r, i)
}

// reqOpts applies functions on an http request.
Expand Down

0 comments on commit 3c59d50

Please sign in to comment.