github.com/starshine-sys/bcr@v0.21.0/execute.go (about)

     1  package bcr
     2  
     3  import (
     4  	"errors"
     5  	"flag"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/diamondburned/arikawa/v3/discord"
    11  	"github.com/spf13/pflag"
    12  )
    13  
    14  var errCommandRun = errors.New("command run in layer")
    15  
    16  // Execute executes the command router
    17  func (r *Router) Execute(ctx *Context) (err error) {
    18  	err = r.execInner(ctx, r.cmds, &r.cmdMu)
    19  	if err == errCommandRun {
    20  		return nil
    21  	}
    22  	return err
    23  }
    24  
    25  func (r *Router) execInner(ctx *Context, cmds map[string]*Command, mu *sync.RWMutex) (err error) {
    26  	var (
    27  		c  *Command
    28  		ok bool
    29  	)
    30  	mu.RLock()
    31  	// check if a command matches, if not, return
    32  	if c, ok = cmds[ctx.Command]; !ok {
    33  		mu.RUnlock()
    34  		return
    35  	}
    36  	mu.RUnlock()
    37  
    38  	// append the current command to FullCommandPath, for help strings
    39  	ctx.FullCommandPath = append(ctx.FullCommandPath, ctx.Command)
    40  	// check if the second argument is `help` or `usage`, if so, show the command's help
    41  	err = ctx.tryHelp()
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	// if the command has subcommands, try those
    47  	if c.subCmds != nil && len(ctx.Args) > 0 {
    48  		if _, ok = c.subCmds[ctx.Peek()]; ok {
    49  			ctx.Command = ctx.Pop()
    50  			err = r.execInner(ctx, c.subCmds, &c.subMu)
    51  			// return all errors, including errCommandRun, so further layers stop executing as well
    52  			if err != nil {
    53  				return err
    54  			}
    55  		}
    56  	}
    57  
    58  	// set the context's Cmd field to the command
    59  	ctx.Cmd = c
    60  
    61  	// if the command is guild-only or needs extra permissions, and this isn't a guild channel, error
    62  	if (c.GuildOnly || c.Permissions != 0) && ctx.Message.GuildID == 0 {
    63  		_, err = ctx.Send(":x: This command cannot be run in DMs.")
    64  		if err != nil {
    65  			return err
    66  		}
    67  		return errCommandRun
    68  	}
    69  
    70  	// check if the command can be blacklisted
    71  	if r.BlacklistFunc != nil && c.Blacklistable {
    72  		// if the channel's blacklisted, return
    73  		if r.BlacklistFunc(ctx) {
    74  			return errCommandRun
    75  		}
    76  	}
    77  
    78  	// if the command requires bot owner to use, and the user isn't a bot owner, error
    79  	if !ctx.checkOwner() {
    80  		_, err = ctx.Send(":x: This command can only be used by a bot owner.")
    81  		if err != nil {
    82  			return err
    83  		}
    84  		return errCommandRun
    85  	}
    86  
    87  	if c.GuildPermissions != 0 {
    88  		if ctx.Guild == nil || ctx.Member == nil {
    89  			_, err = ctx.Send(":x: This command cannot be used in DMs.")
    90  			return errCommandRun
    91  		}
    92  		if !ctx.GuildPerms().Has(c.GuildPermissions) {
    93  			_, err = ctx.Sendf(":x: You are not allowed to use this command. You are missing the following permissions:\n> ```%v```", strings.Join(PermStrings(c.GuildPermissions), ", "))
    94  			// if there's an error, return it
    95  			if err != nil {
    96  				return err
    97  			}
    98  			// but if not, return errCommandRun so we don't try running more
    99  			return errCommandRun
   100  		}
   101  	}
   102  
   103  	if c.Permissions != 0 {
   104  		if ctx.Guild == nil || ctx.Channel == nil || ctx.Member == nil {
   105  			_, err = ctx.Send(":x: This command cannot be used in DMs.")
   106  			return errCommandRun
   107  		}
   108  		if !discord.CalcOverwrites(*ctx.Guild, *ctx.Channel, *ctx.Member).Has(c.Permissions) {
   109  			_, err = ctx.Sendf(":x: You are not allowed to use this command. You are missing the following permissions:\n> ```%v```", strings.Join(PermStrings(c.Permissions), ", "))
   110  			// if there's an error, return it
   111  			if err != nil {
   112  				return err
   113  			}
   114  			// but if not, return errCommandRun so we don't try running more
   115  			return errCommandRun
   116  		}
   117  	}
   118  
   119  	// if the command has a custom permission handler, check it
   120  	if c.CustomPermissions != nil {
   121  		b, err := c.CustomPermissions.Check(ctx)
   122  		// if it errored, send that error and return
   123  		if err != nil {
   124  			_, err = ctx.Send(fmt.Sprintf(":x: An internal error occurred when checking your permissions.\nThe following permission(s) could not be checked:\n> ```%s```", c.CustomPermissions))
   125  			if err != nil {
   126  				return err
   127  			}
   128  			return errCommandRun
   129  		}
   130  
   131  		// else if it returned false, show that error and return
   132  		if !b {
   133  			_, err = ctx.Send(fmt.Sprintf(":x: You are not allowed to use this command. You are missing the following permission(s):\n> ```%s```", c.CustomPermissions))
   134  			if err != nil {
   135  				return err
   136  			}
   137  			return errCommandRun
   138  		}
   139  	}
   140  
   141  	// check for a cooldown
   142  	if r.cooldowns.Get(strings.Join(ctx.FullCommandPath, "-"), ctx.Author.ID, ctx.Channel.ID) {
   143  		_, err = ctx.Sendf(":x: This command can only be run once every %v.", c.Cooldown)
   144  		if err != nil {
   145  			return err
   146  		}
   147  		return errCommandRun
   148  	}
   149  
   150  	// if the command has any flags set, parse those
   151  	if c.Flags != nil {
   152  		ctx.Flags = c.Flags(pflag.NewFlagSet("", pflag.ContinueOnError))
   153  		ctx.Flags.ParseErrorsWhitelist.UnknownFlags = true
   154  
   155  		err = ctx.Flags.Parse(ctx.Args)
   156  		if err != nil {
   157  			_, err = ctx.Send(":x: There was an error parsing your input. Try checking this command's help.")
   158  			return
   159  		}
   160  		ctx.Args = ctx.Flags.Args()
   161  	} else if c.stdFlags != nil {
   162  		// otherwise we parse stdlib flags (set if cmd.Options is not nil)
   163  		fs := flag.NewFlagSet("", flag.ContinueOnError)
   164  
   165  		ctx, fs = c.stdFlags(ctx, fs)
   166  
   167  		err = fs.Parse(ctx.Args)
   168  		if err != nil {
   169  			_, err = ctx.Send(":x: There was an error parsing your input. Try checking this command's help.")
   170  			return
   171  		}
   172  
   173  		ctx.Args = fs.Args()
   174  	}
   175  
   176  	// check arguments
   177  	err = ctx.argCheck()
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	if c.Command != nil {
   183  		err = c.Command(ctx)
   184  	} else {
   185  		err = c.SlashCommand(ctx)
   186  	}
   187  	if err != nil {
   188  		return err
   189  	}
   190  	// if there's a cooldown, set it
   191  	if c.Cooldown != 0 {
   192  		r.cooldowns.Set(strings.Join(ctx.FullCommandPath, "-"), ctx.Author.ID, ctx.Channel.ID, c.Cooldown)
   193  	}
   194  
   195  	// return with errCommandRun, which indicates to an outer layer (if any) that it should stop execution
   196  	return errCommandRun
   197  }