|
@@ -0,0 +1,217 @@
|
|
|
+package mux
|
|
|
+
|
|
|
+import (
|
|
|
+ "errors"
|
|
|
+ "strings"
|
|
|
+
|
|
|
+ "git.mgmcomp.net/thisnthat/discordgo"
|
|
|
+)
|
|
|
+
|
|
|
+// RouteOptions holds information about a specific message route handler
|
|
|
+type RouteOptions struct {
|
|
|
+ Name string // match name that should trigger this route handler
|
|
|
+ Description string // short description of this route
|
|
|
+ Usage string // How to use the route
|
|
|
+ Category string // The category to put the route under
|
|
|
+ Type RouteType // The type of route this is
|
|
|
+ Callback Handler // route handler function to call
|
|
|
+ Hidden bool // The route is hidden from the help menu'
|
|
|
+ Admin bool // The route is only visible to admins
|
|
|
+}
|
|
|
+
|
|
|
+type route struct {
|
|
|
+ Name string
|
|
|
+ Description string
|
|
|
+ Usage string
|
|
|
+ CategoryID int
|
|
|
+ Type RouteType
|
|
|
+ Run Handler
|
|
|
+ Hidden bool
|
|
|
+ Admin bool
|
|
|
+}
|
|
|
+
|
|
|
+type category struct {
|
|
|
+ Name string
|
|
|
+ Description string
|
|
|
+ ID int
|
|
|
+}
|
|
|
+
|
|
|
+// RouteType is the type of route being created
|
|
|
+type RouteType int
|
|
|
+
|
|
|
+const (
|
|
|
+ // MessageRoute is a route for handling OnMessageCreate events
|
|
|
+ MessageRoute RouteType = 1001
|
|
|
+)
|
|
|
+
|
|
|
+// Handler is the function signature required for a message route handler.
|
|
|
+type Handler func(*discordgo.Session, *discordgo.Message, *Context)
|
|
|
+type IsAdminRouteHandler func(*discordgo.Session, *discordgo.Message, *Context) bool
|
|
|
+
|
|
|
+// Context holds a bit of extra data we pass along to route handlers
|
|
|
+// This way processing some of this only needs to happen once.
|
|
|
+type Context struct {
|
|
|
+ Fields []string
|
|
|
+ Args []string
|
|
|
+ Content string
|
|
|
+ Method Method
|
|
|
+}
|
|
|
+
|
|
|
+// Method is the method a command was received
|
|
|
+type Method int
|
|
|
+
|
|
|
+const (
|
|
|
+ // DirectMethod Discord DM to the bot
|
|
|
+ DirectMethod Method = 1001
|
|
|
+ // MentionMethod is a message started with a @mention to the bot
|
|
|
+ MentionMethod Method = 1002
|
|
|
+ // PrefixMethod is a message started with a command prefix
|
|
|
+ PrefixMethod Method = 1003
|
|
|
+)
|
|
|
+
|
|
|
+// Router is the main struct for all mux methods.
|
|
|
+type Router struct {
|
|
|
+ routes []*route
|
|
|
+ helpRoute *route
|
|
|
+ prefix string
|
|
|
+ categories []*category
|
|
|
+ isAdminRoute IsAdminRouteHandler // the admin check handler func to call
|
|
|
+ forcePrefixMethod bool
|
|
|
+}
|
|
|
+
|
|
|
+// New returns a new Discord message route mux
|
|
|
+func New() *Router {
|
|
|
+ r := &Router{}
|
|
|
+
|
|
|
+ r.isAdminRoute = isAdminRouteDefault
|
|
|
+
|
|
|
+ helpRoute := &route{}
|
|
|
+ helpRoute.Name = "help"
|
|
|
+ helpRoute.Usage = "help"
|
|
|
+ helpRoute.Description = "Get Help"
|
|
|
+ helpRoute.Run = r.helpCommandHandler
|
|
|
+
|
|
|
+ r.helpRoute = helpRoute
|
|
|
+
|
|
|
+ return r
|
|
|
+}
|
|
|
+
|
|
|
+// SetCommandPrefix will set the prefix used for message commands
|
|
|
+func (r *Router) SetCommandPrefix(cmdPrefix string) {
|
|
|
+ r.prefix = cmdPrefix + " "
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Router) SetAdminRouteCallback(callback IsAdminRouteHandler) {
|
|
|
+ r.isAdminRoute = callback
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Router) ForcePrefixMethod(force bool) {
|
|
|
+ r.forcePrefixMethod = force
|
|
|
+}
|
|
|
+
|
|
|
+// ErrRegisterRouteNameRequired is returned when registering a new route without a name
|
|
|
+var ErrRegisterRouteNameRequired = errors.New("all routes must have a name")
|
|
|
+
|
|
|
+// ErrRegisterInvalidRouteType is returned when registering a new route with an invalid type
|
|
|
+var ErrRegisterInvalidRouteType = errors.New("invalid route type")
|
|
|
+
|
|
|
+// ErrRegisterCallbackRequired is returned when registering a new route without a callback
|
|
|
+var ErrRegisterCallbackRequired = errors.New("a valid callback must be provided")
|
|
|
+
|
|
|
+// Register allows you to register a route
|
|
|
+//func (r *Router) Register(routeType RouteType, name, desc string, callback Handler) (*Route, error) {
|
|
|
+func (r *Router) Register(name string, options RouteOptions) error {
|
|
|
+ if name == "" {
|
|
|
+ return ErrRegisterRouteNameRequired
|
|
|
+ }
|
|
|
+
|
|
|
+ if !isValidRouteType(options.Type) {
|
|
|
+ return ErrRegisterInvalidRouteType
|
|
|
+ }
|
|
|
+
|
|
|
+ if options.Callback == nil {
|
|
|
+ return ErrRegisterCallbackRequired
|
|
|
+ }
|
|
|
+
|
|
|
+ route := route{}
|
|
|
+ route.Type = options.Type
|
|
|
+ route.Name = name
|
|
|
+ route.Description = options.Description
|
|
|
+ route.Usage = options.Usage
|
|
|
+ route.Run = options.Callback
|
|
|
+ route.Hidden = options.Hidden
|
|
|
+ route.Admin = options.Admin
|
|
|
+
|
|
|
+ if options.Category != "" {
|
|
|
+ for _, category := range r.categories {
|
|
|
+ if category.Name == options.Category {
|
|
|
+ route.CategoryID = category.ID
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ r.routes = append(r.routes, &route)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// ErrAddCategoryNameRequired is returned when registering a new route without a name
|
|
|
+var ErrAddCategoryNameRequired = errors.New("all categories must have a name")
|
|
|
+
|
|
|
+// AddCategory allows you to a a router category
|
|
|
+func (r *Router) AddCategory(name, description string) error {
|
|
|
+ if name == "" {
|
|
|
+ return ErrAddCategoryNameRequired
|
|
|
+ }
|
|
|
+
|
|
|
+ category := category{}
|
|
|
+ category.Name = name
|
|
|
+ category.Description = description
|
|
|
+ category.ID = len(r.categories) + 1
|
|
|
+ r.categories = append(r.categories, &category)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func isValidRouteType(routeType RouteType) bool {
|
|
|
+ switch routeType {
|
|
|
+ case MessageRoute:
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Router) findRoute(searchString string, routeType RouteType) (*route, []string, []string) {
|
|
|
+ fields := strings.Fields(searchString)
|
|
|
+
|
|
|
+ if len(fields) == 0 {
|
|
|
+ return nil, nil, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ commandKey := fields[0]
|
|
|
+
|
|
|
+ for _, route := range r.routes {
|
|
|
+ if route.Type != routeType {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if strings.EqualFold(commandKey, route.Name) {
|
|
|
+ return route, fields[0:], fields[1:]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil, nil, nil
|
|
|
+}
|
|
|
+
|
|
|
+// Default callback for determining if the caller is an admin
|
|
|
+func isAdminRouteDefault(s *discordgo.Session, m *discordgo.Message, ctx *Context) bool {
|
|
|
+ channelPermissions, err := s.UserChannelPermissions(m.Author.ID, m.ChannelID)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return (channelPermissions&discordgo.PermissionAdministrator > 0)
|
|
|
+}
|