mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-12-02 20:02:53 +00:00
313 lines
5.8 KiB
Go
313 lines
5.8 KiB
Go
package bot
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/diamondburned/arikawa/discord"
|
|
"github.com/diamondburned/arikawa/gateway"
|
|
)
|
|
|
|
func (ctx *Context) callCmd(ev interface{}) error {
|
|
evT := reflect.TypeOf(ev)
|
|
|
|
if evT != typeMessageCreate {
|
|
var callers []reflect.Value
|
|
var isAdmin *bool // i want to die
|
|
|
|
for _, cmd := range ctx.Commands {
|
|
if cmd.event == evT {
|
|
if cmd.Flag.Is(AdminOnly) &&
|
|
!ctx.eventIsAdmin(ev, &isAdmin) {
|
|
|
|
continue
|
|
}
|
|
|
|
callers = append(callers, cmd.value)
|
|
}
|
|
}
|
|
|
|
for _, sub := range ctx.Subcommands {
|
|
if sub.Flag.Is(AdminOnly) &&
|
|
!ctx.eventIsAdmin(ev, &isAdmin) {
|
|
|
|
continue
|
|
}
|
|
|
|
for _, cmd := range sub.Commands {
|
|
if cmd.event == evT {
|
|
if cmd.Flag.Is(AdminOnly) &&
|
|
!ctx.eventIsAdmin(ev, &isAdmin) {
|
|
|
|
continue
|
|
}
|
|
|
|
callers = append(callers, cmd.value)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, c := range callers {
|
|
if err := callWith(c, ev); err != nil {
|
|
ctx.ErrorLogger(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// safe assertion always
|
|
mc := ev.(*gateway.MessageCreateEvent)
|
|
|
|
// check if prefix
|
|
if !strings.HasPrefix(mc.Content, ctx.Prefix) {
|
|
// not a command, ignore
|
|
return nil
|
|
}
|
|
|
|
// trim the prefix before splitting, this way multi-words prefices work
|
|
content := mc.Content[len(ctx.Prefix):]
|
|
|
|
if content == "" {
|
|
return nil // just the prefix only
|
|
}
|
|
|
|
// parse arguments
|
|
args, err := ParseArgs(content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) < 1 {
|
|
return nil // ???
|
|
}
|
|
|
|
var cmd *CommandContext
|
|
var start int // arg starts from $start
|
|
|
|
// Search for the command
|
|
for _, c := range ctx.Commands {
|
|
if c.name == args[0] {
|
|
cmd = c
|
|
start = 1
|
|
break
|
|
}
|
|
}
|
|
|
|
// Can't find command, look for subcommands of len(args) has a 2nd
|
|
// entry.
|
|
if cmd == nil && len(args) > 1 {
|
|
for _, s := range ctx.Subcommands {
|
|
if s.name != args[0] {
|
|
continue
|
|
}
|
|
|
|
for _, c := range s.Commands {
|
|
if c.name == args[1] {
|
|
cmd = c
|
|
start = 2
|
|
break
|
|
}
|
|
}
|
|
|
|
if cmd == nil {
|
|
return &ErrUnknownCommand{
|
|
Command: args[1],
|
|
Parent: args[0],
|
|
Prefix: ctx.Prefix,
|
|
ctx: s.Commands,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if cmd == nil || start == 0 {
|
|
return &ErrUnknownCommand{
|
|
Command: args[0],
|
|
Prefix: ctx.Prefix,
|
|
ctx: ctx.Commands,
|
|
}
|
|
}
|
|
|
|
// Start converting
|
|
var argv []reflect.Value
|
|
|
|
// Check manual parser
|
|
if cmd.parseType != nil {
|
|
// Create a zero value instance of this
|
|
v := reflect.New(cmd.parseType)
|
|
|
|
// Call the manual parse method
|
|
ret := cmd.parseMethod.Func.Call([]reflect.Value{
|
|
v, reflect.ValueOf(args),
|
|
})
|
|
|
|
// Check the method returns for error
|
|
if err := errorReturns(ret); err != nil {
|
|
// TODO: maybe wrap this?
|
|
return err
|
|
}
|
|
|
|
// Add the pointer to the argument into argv
|
|
argv = append(argv, v)
|
|
goto Call
|
|
}
|
|
|
|
// Here's an edge case: when the handler takes no arguments, we allow that
|
|
// anyway, as they might've used the raw content.
|
|
if len(cmd.arguments) == 0 {
|
|
goto Call
|
|
}
|
|
|
|
// Not enough arguments given
|
|
if len(args[start:]) != len(cmd.arguments) {
|
|
return &ErrInvalidUsage{
|
|
Args: args,
|
|
Prefix: ctx.Prefix,
|
|
Index: len(cmd.arguments) - start,
|
|
Err: "Not enough arguments given",
|
|
ctx: cmd,
|
|
}
|
|
}
|
|
|
|
argv = make([]reflect.Value, len(cmd.arguments))
|
|
|
|
for i := start; i < len(args); i++ {
|
|
v, err := cmd.arguments[i-start](args[i])
|
|
if err != nil {
|
|
return &ErrInvalidUsage{
|
|
Args: args,
|
|
Prefix: ctx.Prefix,
|
|
Index: i,
|
|
Err: err.Error(),
|
|
ctx: cmd,
|
|
}
|
|
}
|
|
|
|
argv[i-start] = v
|
|
}
|
|
|
|
Call:
|
|
// call the function and parse the error return value
|
|
return callWith(cmd.value, ev, argv...)
|
|
}
|
|
|
|
func (ctx *Context) eventIsAdmin(ev interface{}, is **bool) bool {
|
|
if *is != nil {
|
|
return **is
|
|
}
|
|
|
|
var channelID = reflectChannelID(ev)
|
|
if !channelID.Valid() {
|
|
return false
|
|
}
|
|
|
|
var userID = reflectUserID(ev)
|
|
if !userID.Valid() {
|
|
return false
|
|
}
|
|
|
|
var res bool
|
|
|
|
p, err := ctx.State.Permissions(channelID, userID)
|
|
if err == nil && p.Has(discord.PermissionAdministrator) {
|
|
res = true
|
|
}
|
|
|
|
*is = &res
|
|
return res
|
|
}
|
|
|
|
func callWith(caller reflect.Value, ev interface{}, values ...reflect.Value) error {
|
|
return errorReturns(caller.Call(append(
|
|
[]reflect.Value{reflect.ValueOf(ev)},
|
|
values...,
|
|
)))
|
|
}
|
|
|
|
var ParseArgs = func(args string) ([]string, error) {
|
|
// TODO: make modular
|
|
// TODO: actual tokenizer+parser
|
|
r := csv.NewReader(strings.NewReader(args))
|
|
r.Comma = ' '
|
|
|
|
return r.Read()
|
|
}
|
|
|
|
func errorReturns(returns []reflect.Value) error {
|
|
// assume first is always error, since we checked for this in parseCommands
|
|
v := returns[0].Interface()
|
|
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
return v.(error)
|
|
}
|
|
|
|
func reflectChannelID(_struct interface{}) discord.Snowflake {
|
|
return _reflectID(reflect.ValueOf(_struct), "Channel")
|
|
}
|
|
|
|
func reflectGuildID(_struct interface{}) discord.Snowflake {
|
|
return _reflectID(reflect.ValueOf(_struct), "Guild")
|
|
}
|
|
|
|
func reflectUserID(_struct interface{}) discord.Snowflake {
|
|
return _reflectID(reflect.ValueOf(_struct), "User")
|
|
}
|
|
|
|
func _reflectID(v reflect.Value, thing string) discord.Snowflake {
|
|
if !v.IsValid() {
|
|
return 0
|
|
}
|
|
|
|
t := v.Type()
|
|
|
|
if t.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
|
|
// Recheck after dereferring
|
|
if !v.IsValid() {
|
|
return 0
|
|
}
|
|
|
|
t = v.Type()
|
|
}
|
|
|
|
if t.Kind() != reflect.Struct {
|
|
return 0
|
|
}
|
|
|
|
numFields := t.NumField()
|
|
|
|
for i := 0; i < numFields; i++ {
|
|
field := t.Field(i)
|
|
fType := field.Type
|
|
|
|
if fType.Kind() == reflect.Ptr {
|
|
fType = fType.Elem()
|
|
}
|
|
|
|
switch fType.Kind() {
|
|
case reflect.Struct:
|
|
if chID := _reflectID(v.Field(i), thing); chID.Valid() {
|
|
return chID
|
|
}
|
|
case reflect.Int64:
|
|
if field.Name == thing+"ID" {
|
|
// grab value real quick
|
|
return discord.Snowflake(v.Field(i).Int())
|
|
}
|
|
|
|
// Special case where the struct name has Channel in it
|
|
if field.Name == "ID" && strings.Contains(t.Name(), thing) {
|
|
return discord.Snowflake(v.Field(i).Int())
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|