Sayori is a dead simple command router based on discordgo with

  • custom guild prefix support
  • message parsing
  • multiple command aliases
  • subcommands
  • middlewares

Getting Started


You can install the latest release of Sayori by using:

go get

Then include Sayori in your application:

import sayori ""
package main

import (
	sayori ""

func main() {
    dg, err := discordgo.New("Bot " + "<mydiscordtoken>")
    if err != nil {
    router := sayori.New(dg)
    echo := sayori.NewRoute(&Prefix{}).Do(&Echo{}).On("echo", "e")
    err = router.Open()
    if err != nil {

See /examples for detailed usage.


This project is licensed under the BSD 3-Clause License - see the file for details


  • Inspired by rfrouter and dgrouter.




This section is empty.


This section is empty.


This section is empty.


type CmdContext

type CmdContext struct {
	Ses    *discordgo.Session
	Msg    *discordgo.Message
	Prefix string
	Alias  []string
	Args   []string
	Err    error

    CmdContext is an aux structure for storing invocation values extracted from a given Context to reduce boilerplate.

    This need not be manually initialized; simply call CmdFromContext.

    func CmdFromContext

    func CmdFromContext(ctx context.Context) *CmdContext

      CmdFromContext derives all Command invocation values from given Context.

      type CmdParser

      type CmdParser interface {
      	Parse(string) ([]string, error)

        CmdParser parses the content of a Discord message into a string slice.

        Optionally implemented by Handler

        type Handler

        type Handler interface {
        	Handle(ctx context.Context) error

          Handler is bound to a route and will be called when handling Discord's Message Create events.

          Can optionally implement CmdParser and Resolver, but is not required.

          If Handler does not implement Resolver, any returned error will be ignored.

          type Middlewarer

          type Middlewarer interface {
          	Do(ctx context.Context) error

            Middlewarer allows execution of a handler before Handle is executed.

            Do accepts a context and returns an error. If error is nil, will execute the next Middlewarer or Handle. Otherwise, it will enter the Resolve function (if implemented)

            Context mutated from within a middleware will only persist within scope.

            type Prefixer

            type Prefixer interface {
            	Load(guildID string) (string, bool)
            	Default() string

              Prefixer identifies the prefix based on the guildID and removes the prefix of the command string if matched.

              Load fetches a prefix that matches the guildID and returns the prefix mapped to the guildID with an ok bool.

              Default returns the default prefix

              type Resolver

              type Resolver interface {
              	Resolve(ctx context.Context)

                Resolver is an optional interface that can be satisfied by a command. It is used for handling any errors returned from Handler.

                Optionally implemented by Handler

                type Route

                type Route struct {
                	// contains filtered or unexported fields

                  Route represents a command which consumes a DiscordGo MessageCreate event under the hood.

                  Routes can be modified after they are added to the router, and are not goroutine safe.

                  func NewRoute

                  func NewRoute(p Prefixer) *Route

                    NewRoute returns a new Route.

                    If Prefixer is nil, the route's prefix will be assumed to be empty. The Prefixer will only be run if a Route is added to the Router as a root-level route, and not a subroute.

                    func NewSubroute

                    func NewSubroute() *Route

                      NewSubroute returns a new Route without a Prefixer. Is equivalent to NewRoute(nil).

                      func (*Route) Do

                      func (r *Route) Do(h Handler) *Route

                        Do execution of the provided Handler when there is a Message Create event.

                        A Commander can optionally implement CmdParser for custom parsing of message content. Handling errors and cleanup of other resources will be handled by Resolve if Resolver is implemented, otherwise skipped.

                        No-ops if Handler is nil.

                        If Do is called multiple times, the previous Do call will be overwritten.

                        func (*Route) GetRoute

                        func (r *Route) GetRoute(a string) (routes []Route, ok bool)

                          GetRoute returns immediate subroute(s) matching the given subroute alias.

                          func (*Route) Has

                          func (r *Route) Has(subroutes ...*Route) *Route

                            Has binds subroutes to the current route. Subroutes with duplicate aliases will be prioritized in order of last added to first added. Added routes will be shallow copied to discourage future modification of subroutes.

                            func (*Route) HasAlias

                            func (r *Route) HasAlias(a string) bool

                              HasAlias returns if the given string is a case-insensitive alias of the current route

                              func (*Route) IsDefault

                              func (r *Route) IsDefault() bool

                                IsDefault returns true if a route has no aliases assigned. If the route has no Prefixer bound and IsDefault returns true, the Handler will always be executed when Discord produces a DiscordGo MessageCreate event.


                                A default route will only be executed if it is not a subroute. Any subroutes it might contain will be ignored.

                                func (*Route) On

                                func (r *Route) On(aliases ...string) *Route

                                  On adds new identifiers for a Route. By default, aliases with whitespaces will not be matched unless a Commander also implements the CmdParser interface

                                  If a subroute returns true with IsDefault, it will be auto-selected when searching for the proper route.

                                  func (*Route) Use

                                  func (r *Route) Use(middlewares ...Middlewarer) *Route

                                    Use adds middlewares to the route. Middlewares are executed in order of which they were added, and will always run immediately before the core handler. Middleware errors can be handled by Resolve.

                                    type Router

                                    type Router struct {
                                    	S *discordgo.Session

                                      Router maps commands to handlers.

                                      func New

                                      func New(s *discordgo.Session) *Router

                                        New returns a new Router.

                                        func (*Router) Close

                                        func (r *Router) Close() error

                                          Close closes a websocket and stops all listening/heartbeat goroutines.

                                          func (*Router) Has

                                          func (r *Router) Has(route *Route) func()

                                            Has binds a Route to the Router.

                                            func (*Router) HasDefault

                                            func (r *Router) HasDefault(h interface{}) func()

                                              HasDefault binds a default DiscordGo event handler to the builder. It is useful when there is a handler that consumes something other than a MessageCreate event.

                                              It returns a function that will remove the handler when executed.

                                              func (*Router) HasOnce

                                              func (r *Router) HasOnce(route *Route) func()

                                                HasOnce binds binds a Route to the Router, but the route will only fire at most once.

                                                func (*Router) HasOnceDefault

                                                func (r *Router) HasOnceDefault(h interface{}) func()

                                                  HasOnceDefault binds a default DiscordGo event handler to the builder. It is useful when there is a handler that consumes something other than a MessageCreate event. The added handler will be removed upon the first execution.

                                                  It returns a function that will remove the handler when executed.

                                                  func (*Router) Open

                                                  func (r *Router) Open() error

                                                    Open creates a websocket connection to Discord. See:


                                                    Path Synopsis