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