set.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. package cmd
  4. import (
  5. "log"
  6. "sort"
  7. "strings"
  8. "github.com/monkeybird/autimaat/irc"
  9. "github.com/monkeybird/autimaat/irc/proto"
  10. )
  11. // AuthFunc returns true if the given hostmask defines a whitelisted user.
  12. // This function is used by the command dispatcher to ensure the user is
  13. // allowed to execute a given, restricted command.
  14. type AuthFunc func(string) bool
  15. // Set defines a set of bound commands.
  16. type Set struct {
  17. authenticate AuthFunc
  18. data List
  19. prefix string
  20. }
  21. // New creates a new, empty set for the given prefix and auth handler.
  22. // The auth handler is used to ensure a caller is allowed to run a
  23. // restricted command. This can be nil, which will outright deny access
  24. // to all commands which have the restricted flag set.
  25. func New(prefix string, authenticate AuthFunc) *Set {
  26. if authenticate == nil {
  27. authenticate = func(string) bool { return false }
  28. }
  29. return &Set{
  30. prefix: prefix,
  31. authenticate: authenticate,
  32. }
  33. }
  34. // Dispatch accepts the given message and issues command calls if applicable.
  35. // Returns false if no command call was issued.
  36. func (s *Set) Dispatch(w irc.ResponseWriter, r *irc.Request) bool {
  37. // We are only interested in requests with the correct prefix.
  38. if !r.IsPrivMsg() || !strings.HasPrefix(r.Data, s.prefix) {
  39. return false
  40. }
  41. // Split message data into command name and individual arguments.
  42. name, args := split(r.Data[len(s.prefix):])
  43. if len(name) == 0 {
  44. return false
  45. }
  46. // Find the command instance.
  47. cmd := s.data.Find(name)
  48. if cmd == nil {
  49. return false
  50. }
  51. // Ensure the caller is authorized to run this command.
  52. if cmd.Restricted && !s.authenticate(r.SenderMask) {
  53. proto.PrivMsg(w, r.SenderName, TextAccessDenied, cmd.Name)
  54. return false
  55. }
  56. // Ensure we have enough parameters.
  57. if cmd.RequiredParamCount() > len(args) {
  58. proto.PrivMsg(w, r.SenderName, TextMissingParameters, cmd.Name)
  59. return false
  60. }
  61. var params ParamList
  62. // Process and validate each parameter value.
  63. if len(cmd.Params) > 0 {
  64. params = make(ParamList, 0, len(cmd.Params))
  65. for i := 0; i < len(args) && i < len(cmd.Params); i++ {
  66. if cmd.Params[i].validate(args[i]) {
  67. params = append(params, Param{Value: args[i]})
  68. continue
  69. }
  70. proto.PrivMsg(w, r.SenderName, TextInvalidParameter,
  71. cmd.Name, cmd.Params[i].Name)
  72. return false
  73. }
  74. }
  75. go func() {
  76. // Ensure command handlers don't bring the entire bot down
  77. // when a panic occurs.
  78. defer func() {
  79. x := recover()
  80. if x != nil {
  81. log.Printf("Command error: %v", x)
  82. log.Printf("> %#v", r)
  83. }
  84. }()
  85. cmd.Handler(w, r, params)
  86. }()
  87. return true
  88. }
  89. // Bind binds the given command.
  90. func (s *Set) Bind(name string, restricted bool, handler Handler) *Command {
  91. cmd := newCommand(name, restricted, handler)
  92. s.data = append(s.data, cmd)
  93. sort.Sort(s.data)
  94. return cmd
  95. }
  96. // Unbind removes the given command.
  97. func (s *Set) Unbind(name string) {
  98. idx := s.data.Index(name)
  99. if idx > -1 {
  100. copy(s.data[idx:], s.data[idx+1:])
  101. s.data[len(s.data)-1] = nil
  102. s.data = s.data[:len(s.data)-1]
  103. }
  104. }
  105. // split splits the given string into a command name and individual
  106. // parameters. It ensures there are no empty entries from the parameter list.
  107. func split(data string) (string, []string) {
  108. set := strings.Fields(data)
  109. for i := 0; i < len(set); i++ {
  110. if len(set[i]) == 0 {
  111. copy(set[i:], set[i+1:])
  112. set = set[:len(set)-1]
  113. i--
  114. }
  115. }
  116. if len(set) == 0 {
  117. return "", nil
  118. }
  119. return set[0], set[1:]
  120. }