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 }