legacyRouter.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package discordbot
  2. import (
  3. "errors"
  4. "strings"
  5. "github.com/bwmarrin/discordgo"
  6. )
  7. // RouteOptions holds information about a specific message route handler
  8. type RouteOptions struct {
  9. Name string // match name that should trigger this route handler
  10. Description string // short description of this route
  11. Usage string // How to use the route
  12. Category string // The category to put the route under
  13. Type RouteType // The type of route this is
  14. Callback Handler // route handler function to call
  15. Hidden bool // The route is hidden from the help menu'
  16. Admin bool // The route is only visible to admins
  17. }
  18. type route struct {
  19. Name string
  20. Description string
  21. Usage string
  22. CategoryID int
  23. Type RouteType
  24. Run Handler
  25. Hidden bool
  26. Admin bool
  27. }
  28. type category struct {
  29. Name string
  30. Description string
  31. ID int
  32. }
  33. // RouteType is the type of route being created
  34. type RouteType int
  35. const (
  36. // MessageRoute is a route for handling OnMessageCreate events
  37. MessageRoute RouteType = 1001
  38. )
  39. // Handler is the function signature required for a message route handler.
  40. type Handler func(*discordgo.Session, *discordgo.Message, *Context)
  41. type IsAdminRouteHandler func(*discordgo.Session, *discordgo.Message, *Context) bool
  42. // Context holds a bit of extra data we pass along to route handlers
  43. // This way processing some of this only needs to happen once.
  44. type Context struct {
  45. Fields []string
  46. Args []string
  47. Content string
  48. Method Method
  49. }
  50. // Method is the method a command was received
  51. type Method int
  52. const (
  53. // DirectMethod Discord DM to the bot
  54. DirectMethod Method = 1001
  55. // MentionMethod is a message started with a @mention to the bot
  56. MentionMethod Method = 1002
  57. // PrefixMethod is a message started with a command prefix
  58. PrefixMethod Method = 1003
  59. )
  60. // Router is the main struct for all mux methods.
  61. type LegacyRouter struct {
  62. routes []*route
  63. helpRoute *route
  64. prefix string
  65. categories []*category
  66. isAdminRoute IsAdminRouteHandler // the admin check handler func to call
  67. forcePrefixMethod bool
  68. }
  69. // New returns a new Discord message route mux
  70. func NewLegacyRouter() *LegacyRouter {
  71. r := new(LegacyRouter)
  72. r.isAdminRoute = isAdminRouteDefault
  73. helpRoute := &route{}
  74. helpRoute.Name = "help"
  75. helpRoute.Usage = "help"
  76. helpRoute.Description = "Get Help"
  77. helpRoute.Run = r.helpCommandHandler
  78. r.helpRoute = helpRoute
  79. return r
  80. }
  81. // SetCommandPrefix will set the prefix used for message commands
  82. func (r *LegacyRouter) SetCommandPrefix(cmdPrefix string) {
  83. r.prefix = cmdPrefix + " "
  84. }
  85. func (r *LegacyRouter) SetAdminRouteCallback(callback IsAdminRouteHandler) {
  86. r.isAdminRoute = callback
  87. }
  88. func (r *LegacyRouter) ForcePrefixMethod(force bool) {
  89. r.forcePrefixMethod = force
  90. }
  91. // ErrRegisterRouteNameRequired is returned when registering a new route without a name
  92. var ErrRegisterRouteNameRequired = errors.New("all routes must have a name")
  93. // ErrRegisterInvalidRouteType is returned when registering a new route with an invalid type
  94. var ErrRegisterInvalidRouteType = errors.New("invalid route type")
  95. // ErrRegisterCallbackRequired is returned when registering a new route without a callback
  96. var ErrRegisterCallbackRequired = errors.New("a valid callback must be provided")
  97. // Register allows you to register a route
  98. // func (r *LegacyRouter) Register(routeType RouteType, name, desc string, callback Handler) (*Route, error) {
  99. func (r *LegacyRouter) Register(name string, options RouteOptions) error {
  100. if name == "" {
  101. return ErrRegisterRouteNameRequired
  102. }
  103. if !isValidRouteType(options.Type) {
  104. return ErrRegisterInvalidRouteType
  105. }
  106. if options.Callback == nil {
  107. return ErrRegisterCallbackRequired
  108. }
  109. route := route{}
  110. route.Type = options.Type
  111. route.Name = name
  112. route.Description = options.Description
  113. route.Usage = options.Usage
  114. route.Run = options.Callback
  115. route.Hidden = options.Hidden
  116. route.Admin = options.Admin
  117. if options.Category != "" {
  118. for _, category := range r.categories {
  119. if category.Name == options.Category {
  120. route.CategoryID = category.ID
  121. break
  122. }
  123. }
  124. }
  125. r.routes = append(r.routes, &route)
  126. return nil
  127. }
  128. // ErrAddCategoryNameRequired is returned when registering a new route without a name
  129. var ErrAddCategoryNameRequired = errors.New("all categories must have a name")
  130. // AddCategory allows you to a a router category
  131. func (r *LegacyRouter) AddCategory(name, description string) error {
  132. if name == "" {
  133. return ErrAddCategoryNameRequired
  134. }
  135. category := category{}
  136. category.Name = name
  137. category.Description = description
  138. category.ID = len(r.categories) + 1
  139. r.categories = append(r.categories, &category)
  140. return nil
  141. }
  142. func isValidRouteType(routeType RouteType) bool {
  143. switch routeType {
  144. case MessageRoute:
  145. return true
  146. }
  147. return false
  148. }
  149. func (r *LegacyRouter) findRoute(searchString string, routeType RouteType) (*route, []string, []string) {
  150. fields := strings.Fields(searchString)
  151. if len(fields) == 0 {
  152. return nil, nil, nil
  153. }
  154. commandKey := fields[0]
  155. for _, route := range r.routes {
  156. if route.Type != routeType {
  157. continue
  158. }
  159. if strings.EqualFold(commandKey, route.Name) {
  160. return route, fields[0:], fields[1:]
  161. }
  162. }
  163. return nil, nil, nil
  164. }
  165. // Default callback for determining if the caller is an admin
  166. func isAdminRouteDefault(s *discordgo.Session, m *discordgo.Message, ctx *Context) bool {
  167. channelPermissions, err := s.UserChannelPermissions(m.Author.ID, m.ChannelID)
  168. if err != nil {
  169. return false
  170. }
  171. return (channelPermissions&discordgo.PermissionAdministrator > 0)
  172. }