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  }