package discordbot import ( "errors" "strings" "github.com/bwmarrin/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 LegacyRouter 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 NewLegacyRouter() *LegacyRouter { r := new(LegacyRouter) 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 *LegacyRouter) SetCommandPrefix(cmdPrefix string) { r.prefix = cmdPrefix + " " } func (r *LegacyRouter) SetAdminRouteCallback(callback IsAdminRouteHandler) { r.isAdminRoute = callback } func (r *LegacyRouter) 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 *LegacyRouter) Register(routeType RouteType, name, desc string, callback Handler) (*Route, error) { func (r *LegacyRouter) 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 *LegacyRouter) 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 *LegacyRouter) 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) }