github.com/diamondburned/arikawa@v1.3.14/bot/command.go (about) 1 package bot 2 3 import ( 4 "reflect" 5 ) 6 7 type command struct { 8 value reflect.Value // Func 9 event reflect.Type 10 isInterface bool 11 } 12 13 func newCommand(value reflect.Value, event reflect.Type) command { 14 return command{ 15 value: value, 16 event: event, 17 isInterface: event.Kind() == reflect.Interface, 18 } 19 } 20 21 func (c *command) isEvent(t reflect.Type) bool { 22 return (!c.isInterface && c.event == t) || (c.isInterface && t.Implements(c.event)) 23 } 24 25 func (c *command) call(arg0 interface{}, argv ...reflect.Value) (interface{}, error) { 26 return callWith(c.value, arg0, argv...) 27 } 28 29 func callWith(caller reflect.Value, arg0 interface{}, argv ...reflect.Value) (interface{}, error) { 30 var callargs = make([]reflect.Value, 0, 1+len(argv)) 31 32 if v, ok := arg0.(reflect.Value); ok { 33 callargs = append(callargs, v) 34 } else { 35 callargs = append(callargs, reflect.ValueOf(arg0)) 36 } 37 38 callargs = append(callargs, argv...) 39 return errorReturns(caller.Call(callargs)) 40 } 41 42 type caller interface { 43 call(arg0 interface{}, argv ...reflect.Value) (interface{}, error) 44 } 45 46 func errorReturns(returns []reflect.Value) (interface{}, error) { 47 // Handlers may return nothing. 48 if len(returns) == 0 { 49 return nil, nil 50 } 51 52 // assume first return is always error, since we checked for this in 53 // parseCommands. 54 v := returns[len(returns)-1].Interface() 55 // If the last return (error) is nil. 56 if v == nil { 57 // If we only have 1 returns, that return must be the error. The error 58 // is nil, so nil is returned. 59 if len(returns) == 1 { 60 return nil, nil 61 } 62 63 // Return the first argument as-is. The above returns[-1] check assumes 64 // 2 return values (T, error), meaning returns[0] is the T value. 65 return returns[0].Interface(), nil 66 } 67 68 // Treat the last return as an error. 69 return nil, v.(error) 70 } 71 72 // MethodContext is an internal struct containing fields to make this library 73 // work. As such, they're all unexported. Description, however, is exported for 74 // editing, and may be used to generate more informative help messages. 75 type MethodContext struct { 76 command 77 method reflect.Method // extend 78 middlewares []*MiddlewareContext 79 80 Description string 81 82 // MethodName is the name of the method. This field should NOT be changed. 83 MethodName string 84 85 // Command is the Discord command used to call the method. 86 Command string // plumb if empty 87 88 // Aliases is alternative way to call command in Discord. 89 Aliases []string 90 91 // Hidden if true will not be shown by (*Subcommand).HelpGenerate(). 92 Hidden bool 93 94 // Variadic is true if the function is a variadic one or if the last 95 // argument accepts multiple strings. 96 Variadic bool 97 98 Arguments []Argument 99 } 100 101 func parseMethod(value reflect.Value, method reflect.Method) *MethodContext { 102 methodT := value.Type() 103 numArgs := methodT.NumIn() 104 105 if numArgs == 0 { 106 // Doesn't meet the requirement for an event, continue. 107 return nil 108 } 109 110 // Check number of returns: 111 numOut := methodT.NumOut() 112 113 // Returns can either be: 114 // Nothing - func() 115 // An error - func() error 116 // An error and something else - func() (T, error) 117 if numOut > 2 { 118 return nil 119 } 120 121 // Check the last return's type if the method returns anything. 122 if numOut > 0 { 123 if i := methodT.Out(numOut - 1); i == nil || !i.Implements(typeIError) { 124 // Invalid, skip. 125 return nil 126 } 127 } 128 129 var command = MethodContext{ 130 command: newCommand(value, methodT.In(0)), 131 method: method, 132 MethodName: method.Name, 133 Variadic: methodT.IsVariadic(), 134 } 135 136 // Only set the command name if it's a MessageCreate handler. 137 if command.event == typeMessageCreate { 138 command.Command = lowerFirstLetter(command.method.Name) 139 } 140 141 if numArgs > 1 { 142 // Event handlers that aren't MessageCreate should not have arguments. 143 if command.event != typeMessageCreate { 144 return nil 145 } 146 147 // If the event type is messageCreate: 148 command.Arguments = make([]Argument, 0, numArgs-1) 149 150 // Fill up arguments. This should work with cusP and manP 151 for i := 1; i < numArgs; i++ { 152 t := methodT.In(i) 153 a, err := newArgument(t, command.Variadic) 154 if err != nil { 155 panic("error parsing argument " + t.String() + ": " + err.Error()) 156 } 157 158 command.Arguments = append(command.Arguments, *a) 159 160 // We're done if the type accepts multiple arguments. 161 if a.custom != nil || a.manual != nil { 162 command.Variadic = true // treat as variadic 163 break 164 } 165 } 166 } 167 168 return &command 169 } 170 171 func (cctx *MethodContext) addMiddleware(mw *MiddlewareContext) { 172 // Skip if mismatch type: 173 if !mw.command.isEvent(cctx.command.event) { 174 return 175 } 176 cctx.middlewares = append(cctx.middlewares, mw) 177 } 178 179 func (cctx *MethodContext) walkMiddlewares(ev reflect.Value) error { 180 for _, mw := range cctx.middlewares { 181 _, err := mw.call(ev) 182 if err != nil { 183 return err 184 } 185 } 186 return nil 187 } 188 189 func (cctx *MethodContext) Usage() []string { 190 if len(cctx.Arguments) == 0 { 191 return nil 192 } 193 194 var arguments = make([]string, len(cctx.Arguments)) 195 for i, arg := range cctx.Arguments { 196 arguments[i] = arg.String 197 } 198 199 return arguments 200 } 201 202 // SetName sets the command name. 203 func (cctx *MethodContext) SetName(name string) { 204 cctx.Command = name 205 } 206 207 type MiddlewareContext struct { 208 command 209 } 210 211 // ParseMiddleware parses a middleware function. This function panics. 212 func ParseMiddleware(mw interface{}) *MiddlewareContext { 213 value := reflect.ValueOf(mw) 214 methodT := value.Type() 215 numArgs := methodT.NumIn() 216 217 if numArgs != 1 { 218 panic("Invalid argument signature for " + methodT.String()) 219 } 220 221 // Check number of returns: 222 numOut := methodT.NumOut() 223 224 // Returns can either be: 225 // Nothing - func() 226 // An error - func() error 227 if numOut > 1 { 228 panic("Invalid return signature for " + methodT.String()) 229 } 230 231 // Check the last return's type if the method returns anything. 232 if numOut == 1 { 233 if i := methodT.Out(0); i == nil || !i.Implements(typeIError) { 234 panic("unexpected return type (not error) for " + methodT.String()) 235 } 236 } 237 238 var middleware = MiddlewareContext{ 239 command: newCommand(value, methodT.In(0)), 240 } 241 242 return &middleware 243 }