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  }