6060)
6161
6262var (
63- RKeyCommandCooldown = func (uID int64 , cmd string ) string { return "cmd_cd:" + discordgo .StrID (uID ) + ":" + cmd }
64- RKeyCommandLock = func (uID int64 , cmd string ) string { return "cmd_lock:" + discordgo .StrID (uID ) + ":" + cmd }
63+ RKeyCommandCooldown = func (uID int64 , cmd string ) string { return "cmd_cd:" + discordgo .StrID (uID ) + ":" + cmd }
64+ RKeyCommandCooldownGuild = func (gID int64 , cmd string ) string { return "cmd_guild_cd:" + discordgo .StrID (gID ) + ":" + cmd }
65+ RKeyCommandLock = func (uID int64 , cmd string ) string { return "cmd_lock:" + discordgo .StrID (uID ) + ":" + cmd }
6566
6667 CommandExecTimeout = time .Minute
6768
@@ -98,8 +99,9 @@ type YAGCommand struct {
9899 CustomEnabled bool // Set to true to handle the enable check itself
99100 Default bool // The default enabled state of this command
100101
101- Cooldown int // Cooldown in seconds before user can use it again
102- CmdCategory * dcmd.Category
102+ Cooldown int // Cooldown in seconds before user can use it again
103+ CmdCategory * dcmd.Category
104+ GuildScopeCooldown int
103105
104106 RunInDM bool // Set to enable this commmand in DM's
105107 HideFromHelp bool // Set to hide from help
@@ -190,7 +192,7 @@ func (yc *YAGCommand) Run(data *dcmd.Data) (interface{}, error) {
190192
191193 // Log errors
192194 if cmdErr == nil {
193- err := yc .SetCooldown (data .Msg .Author .ID )
195+ err := yc .SetCooldowns (data .ContainerChain , data . Msg .Author .ID , data . Msg . GuildID )
194196 if err != nil {
195197 logger .WithError (err ).Error ("Failed setting cooldown" )
196198 }
@@ -305,6 +307,15 @@ func (yc *YAGCommand) PostCommandExecuted(settings *CommandSettings, cmdData *dc
305307 return
306308}
307309
310+ const (
311+ ReasonError = "An error occured"
312+ ReasonCommandDisabaledSettings = "Command is disabled in the settings"
313+ ReasonMissingRole = "Missing a required role for this command"
314+ ReasonIgnoredRole = "Has a ignored role for this command"
315+ ReasonUserMissingPerms = "User is missing one or more permissions to run this command"
316+ ReasonCooldown = "This command is on cooldown"
317+ )
318+
308319// checks if the specified user can execute the command, and if so returns the settings for said command
309320func (yc * YAGCommand ) checkCanExecuteCommand (data * dcmd.Data , cState * dstate.ChannelState ) (canExecute bool , resp string , settings * CommandSettings , err error ) {
310321 // Check guild specific settings if not triggered from a DM
@@ -316,7 +327,8 @@ func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.Cha
316327 guild = cState .Guild
317328
318329 if guild == nil {
319- resp = "You're not on a server?"
330+ err = errors .NewPlain ("Not on a guild" )
331+ resp = ReasonError
320332 return
321333 }
322334
@@ -329,12 +341,12 @@ func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.Cha
329341 settings , err = yc .GetSettings (data .ContainerChain , cState .ID , cop .ParentID , guild .ID )
330342 if err != nil {
331343 err = errors .WithMessage (err , "cs.GetSettings" )
332- resp = "Bot is having isssues, contact the bot owner."
344+ resp = ReasonError
333345 return
334346 }
335347
336348 if ! settings .Enabled {
337- resp = fmt . Sprintf ( "The %q command is currently disabled on this server or channel. *(Control panel to enable/disable <https://%s>)*" , yc . Name , common . ConfHost . GetString ())
349+ resp = ReasonCommandDisabaledSettings
338350 return
339351 }
340352
@@ -350,14 +362,14 @@ func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.Cha
350362 }
351363
352364 if ! found {
353- resp = "Missing a required role set up by the server admins for this command."
365+ resp = ReasonMissingRole
354366 return
355367 }
356368 }
357369
358370 for _ , ignored := range settings .IgnoreRoles {
359371 if common .ContainsInt64Slice (member .Roles , ignored ) {
360- resp = "One of your roles is set up to be ignored by the server admins."
372+ resp = ReasonIgnoredRole
361373 return
362374 }
363375 }
@@ -367,7 +379,7 @@ func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.Cha
367379 var perms int
368380 perms , err = cState .Guild .MemberPermissionsMS (true , cState .ID , member )
369381 if err != nil {
370- resp = "Unable to check permissions"
382+ resp = ReasonError
371383 return
372384 }
373385
@@ -380,7 +392,7 @@ func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.Cha
380392 }
381393
382394 if ! foundMatch {
383- resp = "Missing required permissions to use this command (" + yc . humanizedRequiredPerms () + ")"
395+ resp = ReasonUserMissingPerms
384396 return
385397 }
386398 }
@@ -391,14 +403,14 @@ func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.Cha
391403 }
392404
393405 // Check the command cooldown
394- cdLeft , err := yc .CooldownLeft (data .Msg .Author .ID )
406+ cdLeft , err := yc .LongestCooldownLeft (data .ContainerChain , data . Msg .Author .ID , data . Msg . GuildID )
395407 if err != nil {
396408 // Just pretend the cooldown is off...
397- logger . WithError ( err ). WithField ( "author" , data . Msg . Author . ID ).Error ("Failed checking command cooldown" )
409+ yc . Logger ( data ).Error ("Failed checking command cooldown" )
398410 }
399411
400412 if cdLeft > 0 {
401- resp = fmt . Sprintf ( "**%q:** You need to wait %d seconds before you can use the %q command again" , common . EscapeSpecialMentions ( data . Msg . Author . Username ), cdLeft , yc . Name )
413+ resp = ReasonCooldown
402414 return
403415 }
404416
@@ -595,34 +607,94 @@ OUTER:
595607 }
596608}
597609
598- // CooldownLeft returns the number of seconds before a command can be used again
599- func (cs * YAGCommand ) CooldownLeft (userID int64 ) (int , error ) {
600- if cs .Cooldown < 1 || common .Testing {
610+ // LongestCooldownLeft returns the longest cooldown for this command, either user scoped or guild scoped
611+ func (cs * YAGCommand ) LongestCooldownLeft (cc []* dcmd.Container , userID int64 , guildID int64 ) (int , error ) {
612+ cdUser , err := cs .UserScopeCooldownLeft (cc , userID )
613+ if err != nil {
614+ return 0 , err
615+ }
616+
617+ cdGuild , err := cs .GuildScopeCooldownLeft (cc , guildID )
618+ if err != nil {
619+ return 0 , err
620+ }
621+
622+ if cdUser > cdGuild {
623+ return cdUser , nil
624+ }
625+
626+ return cdGuild , nil
627+ }
628+
629+ // UserScopeCooldownLeft returns the number of seconds before a command can be used again by this user
630+ func (cs * YAGCommand ) UserScopeCooldownLeft (cc []* dcmd.Container , userID int64 ) (int , error ) {
631+ if cs .Cooldown < 1 {
601632 return 0 , nil
602633 }
603634
604635 var ttl int
605- err := common .RedisPool .Do (retryableredis .Cmd (& ttl , "TTL" , RKeyCommandCooldown (userID , cs .Name )))
606- if ttl < 1 {
636+ err := common .RedisPool .Do (retryableredis .Cmd (& ttl , "TTL" , RKeyCommandCooldown (userID , cs .FindNameFromContainerChain (cc ))))
637+ if err != nil {
638+ return 0 , errors .WithStackIf (err )
639+ }
640+
641+ return ttl , nil
642+ }
643+
644+ // GuildScopeCooldownLeft returns the number of seconds before a command can be used again on this server
645+ func (cs * YAGCommand ) GuildScopeCooldownLeft (cc []* dcmd.Container , guildID int64 ) (int , error ) {
646+ if cs .GuildScopeCooldown < 1 {
607647 return 0 , nil
608648 }
609649
610- return ttl , err
650+ var ttl int
651+ err := common .RedisPool .Do (retryableredis .Cmd (& ttl , "TTL" , RKeyCommandCooldownGuild (guildID , cs .FindNameFromContainerChain (cc ))))
652+ if err != nil {
653+ return 0 , errors .WithStackIf (err )
654+ }
655+
656+ return ttl , nil
657+ }
658+
659+ // SetCooldowns is a helper that serts both User and Guild cooldown
660+ func (cs * YAGCommand ) SetCooldowns (cc []* dcmd.Container , userID int64 , guildID int64 ) error {
661+ err := cs .SetCooldownUser (cc , userID )
662+ if err != nil {
663+ return errors .WithStackIf (err )
664+ }
665+
666+ err = cs .SetCooldownGuild (cc , guildID )
667+ if err != nil {
668+ return errors .WithStackIf (err )
669+ }
670+
671+ return nil
611672}
612673
613- // SetCooldown sets the cooldown of the command as it's defined in the struct
614- func (cs * YAGCommand ) SetCooldown ( userID int64 ) error {
674+ // SetCooldownUser sets the user scoped cooldown of the command as it's defined in the struct
675+ func (cs * YAGCommand ) SetCooldownUser ( cc [] * dcmd. Container , userID int64 ) error {
615676 if cs .Cooldown < 1 {
616677 return nil
617678 }
618679 now := time .Now ().Unix ()
619680
620- err := common .RedisPool .Do (retryableredis .FlatCmd (nil , "SET" , RKeyCommandCooldown (userID , cs .Name ), now , "EX" , cs .Cooldown ))
621- return err
681+ err := common .RedisPool .Do (retryableredis .FlatCmd (nil , "SET" , RKeyCommandCooldown (userID , cs .FindNameFromContainerChain (cc )), now , "EX" , cs .Cooldown ))
682+ return errors .WithStackIf (err )
683+ }
684+
685+ // SetCooldownGuild sets the guild scoped cooldown of the command as it's defined in the struct
686+ func (cs * YAGCommand ) SetCooldownGuild (cc []* dcmd.Container , guildID int64 ) error {
687+ if cs .GuildScopeCooldown < 1 {
688+ return nil
689+ }
690+
691+ now := time .Now ().Unix ()
692+ err := common .RedisPool .Do (retryableredis .FlatCmd (nil , "SET" , RKeyCommandCooldownGuild (guildID , cs .FindNameFromContainerChain (cc )), now , "EX" , cs .GuildScopeCooldown ))
693+ return errors .WithStackIf (err )
622694}
623695
624696func (yc * YAGCommand ) Logger (data * dcmd.Data ) * logrus.Entry {
625- l := logger .WithField ("cmd" , yc .Name )
697+ l := logger .WithField ("cmd" , yc .FindNameFromContainerChain ( data . ContainerChain ) )
626698 if data != nil {
627699 if data .Msg != nil {
628700 l = l .WithField ("user_n" , data .Msg .Author .Username )
@@ -632,6 +704,10 @@ func (yc *YAGCommand) Logger(data *dcmd.Data) *logrus.Entry {
632704 if data .CS != nil {
633705 l = l .WithField ("channel" , data .CS .ID )
634706 }
707+
708+ if data .GS != nil {
709+ l = l .WithField ("guild" , data .GS .ID )
710+ }
635711 }
636712
637713 return l
@@ -720,3 +796,24 @@ func removeRunningCommand(guildID, channelID, authorID int64, cmd *YAGCommand) {
720796
721797 return
722798}
799+
800+ func (yc * YAGCommand ) FindNameFromContainerChain (cc []* dcmd.Container ) string {
801+ name := ""
802+ for _ , v := range cc {
803+ if len (v .Names ) < 1 {
804+ continue
805+ }
806+
807+ if name != "" {
808+ name += " "
809+ }
810+
811+ name += v .Names [0 ]
812+ }
813+
814+ if name != "" {
815+ name += " "
816+ }
817+
818+ return name + yc .Name
819+ }
0 commit comments