mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-11-08 16:20:02 +00:00
Add user- and channel-based cooldowns (#4)
This commit is contained in:
parent
d62985feb4
commit
0db778f841
5 changed files with 166 additions and 47 deletions
|
@ -44,6 +44,6 @@ func handleMessage(c *irc.Client, m *irc.Message, event *string) {
|
|||
}
|
||||
|
||||
// Lock command
|
||||
timerStore.AddCooldown(r.MatcherID())
|
||||
r.SetCooldown(m)
|
||||
}
|
||||
}
|
||||
|
|
96
rule.go
96
rule.go
|
@ -16,6 +16,8 @@ type rule struct {
|
|||
Actions []*ruleAction `yaml:"actions"`
|
||||
|
||||
Cooldown *time.Duration `yaml:"cooldown"`
|
||||
ChannelCooldown *time.Duration `yaml:"channel_cooldown"`
|
||||
UserCooldown *time.Duration `yaml:"user_cooldown"`
|
||||
SkipCooldownFor []string `yaml:"skip_cooldown_for"`
|
||||
|
||||
MatchChannels []string `yaml:"match_channels"`
|
||||
|
@ -71,7 +73,9 @@ func (r *rule) Matches(m *irc.Message, event *string) bool {
|
|||
r.allowExecuteBadgeBlacklist,
|
||||
r.allowExecuteBadgeWhitelist,
|
||||
r.allowExecuteDisableOnPermit,
|
||||
r.allowExecuteCooldown,
|
||||
r.allowExecuteRuleCooldown,
|
||||
r.allowExecuteChannelCooldown,
|
||||
r.allowExecuteUserCooldown,
|
||||
r.allowExecuteDisableOnTemplate,
|
||||
r.allowExecuteDisableOnOffline,
|
||||
} {
|
||||
|
@ -84,6 +88,20 @@ func (r *rule) Matches(m *irc.Message, event *string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (r *rule) SetCooldown(m *irc.Message) {
|
||||
if r.Cooldown != nil {
|
||||
timerStore.AddCooldown(timerTypeCooldown, "", r.MatcherID())
|
||||
}
|
||||
|
||||
if r.ChannelCooldown != nil && len(m.Params) > 0 {
|
||||
timerStore.AddCooldown(timerTypeCooldown, m.Params[0], r.MatcherID())
|
||||
}
|
||||
|
||||
if r.UserCooldown != nil {
|
||||
timerStore.AddCooldown(timerTypeCooldown, m.User, r.MatcherID())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteBadgeBlacklist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
for _, b := range r.DisableOn {
|
||||
if badges.Has(b) {
|
||||
|
@ -110,6 +128,25 @@ func (r *rule) allowExecuteBadgeWhitelist(logger *log.Entry, m *irc.Message, eve
|
|||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteChannelCooldown(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.ChannelCooldown == nil || len(m.Params) < 1 {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if !timerStore.InCooldown(timerTypeCooldown, m.Params[0], r.MatcherID(), *r.ChannelCooldown) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range r.SkipCooldownFor {
|
||||
if badges.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if len(r.MatchChannels) == 0 {
|
||||
// No match criteria set, does not speak against matching
|
||||
|
@ -124,25 +161,6 @@ func (r *rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, e
|
|||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteCooldown(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.Cooldown == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if !timerStore.InCooldown(r.MatcherID(), *r.Cooldown) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range r.SkipCooldownFor {
|
||||
if badges.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteDisable(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.Disable == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
|
@ -274,6 +292,44 @@ func (r *rule) allowExecuteMessageMatcherWhitelist(logger *log.Entry, m *irc.Mes
|
|||
return true
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteRuleCooldown(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.Cooldown == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if !timerStore.InCooldown(timerTypeCooldown, "", r.MatcherID(), *r.Cooldown) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range r.SkipCooldownFor {
|
||||
if badges.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteUserCooldown(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if r.UserCooldown == nil {
|
||||
// No match criteria set, does not speak against matching
|
||||
return true
|
||||
}
|
||||
|
||||
if !timerStore.InCooldown(timerTypeCooldown, m.User, r.MatcherID(), *r.UserCooldown) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range r.SkipCooldownFor {
|
||||
if badges.Has(b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *rule) allowExecuteUserWhitelist(logger *log.Entry, m *irc.Message, event *string, badges badgeCollection) bool {
|
||||
if len(r.MatchUsers) == 0 {
|
||||
// No match criteria set, does not speak against matching
|
||||
|
|
88
rule_test.go
88
rule_test.go
|
@ -58,25 +58,6 @@ func TestAllowExecuteChannelWhitelist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteCooldown(t *testing.T) {
|
||||
r := &rule{Cooldown: func(i time.Duration) *time.Duration { return &i }(time.Minute), SkipCooldownFor: []string{badgeBroadcaster}}
|
||||
|
||||
if !r.allowExecuteCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||
t.Error("Initial call was not allowed")
|
||||
}
|
||||
|
||||
// Add cooldown
|
||||
timerStore.AddCooldown(r.MatcherID())
|
||||
|
||||
if r.allowExecuteCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||
t.Error("Call after cooldown added was allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteCooldown(testLogger, nil, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with skip badge was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteDisable(t *testing.T) {
|
||||
for exp, r := range map[bool]*rule{
|
||||
true: {Disable: testPtrBool(false)},
|
||||
|
@ -105,6 +86,31 @@ func TestAllowExecuteDisableOnOffline(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteChannelCooldown(t *testing.T) {
|
||||
r := &rule{ChannelCooldown: func(i time.Duration) *time.Duration { return &i }(time.Minute), SkipCooldownFor: []string{badgeBroadcaster}}
|
||||
c1 := irc.MustParseMessage(":amy!amy@foo.example.com PRIVMSG #mychannel :Testing")
|
||||
c2 := irc.MustParseMessage(":amy!amy@foo.example.com PRIVMSG #otherchannel :Testing")
|
||||
|
||||
if !r.allowExecuteChannelCooldown(testLogger, c1, nil, badgeCollection{}) {
|
||||
t.Error("Initial call was not allowed")
|
||||
}
|
||||
|
||||
// Add cooldown
|
||||
timerStore.AddCooldown(timerTypeCooldown, c1.Params[0], r.MatcherID())
|
||||
|
||||
if r.allowExecuteChannelCooldown(testLogger, c1, nil, badgeCollection{}) {
|
||||
t.Error("Call after cooldown added was allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteChannelCooldown(testLogger, c1, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with skip badge was not allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteChannelCooldown(testLogger, c2, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with different channel was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteDisableOnPermit(t *testing.T) {
|
||||
r := &rule{DisableOnPermit: testPtrBool(true)}
|
||||
|
||||
|
@ -175,6 +181,50 @@ func TestAllowExecuteMessageMatcherWhitelist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteRuleCooldown(t *testing.T) {
|
||||
r := &rule{Cooldown: func(i time.Duration) *time.Duration { return &i }(time.Minute), SkipCooldownFor: []string{badgeBroadcaster}}
|
||||
|
||||
if !r.allowExecuteRuleCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||
t.Error("Initial call was not allowed")
|
||||
}
|
||||
|
||||
// Add cooldown
|
||||
timerStore.AddCooldown(timerTypeCooldown, "", r.MatcherID())
|
||||
|
||||
if r.allowExecuteRuleCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||
t.Error("Call after cooldown added was allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteRuleCooldown(testLogger, nil, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with skip badge was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteUserCooldown(t *testing.T) {
|
||||
r := &rule{UserCooldown: func(i time.Duration) *time.Duration { return &i }(time.Minute), SkipCooldownFor: []string{badgeBroadcaster}}
|
||||
c1 := irc.MustParseMessage(":ben!ben@foo.example.com PRIVMSG #mychannel :Testing")
|
||||
c2 := irc.MustParseMessage(":amy!amy@foo.example.com PRIVMSG #mychannel :Testing")
|
||||
|
||||
if !r.allowExecuteUserCooldown(testLogger, c1, nil, badgeCollection{}) {
|
||||
t.Error("Initial call was not allowed")
|
||||
}
|
||||
|
||||
// Add cooldown
|
||||
timerStore.AddCooldown(timerTypeCooldown, c1.User, r.MatcherID())
|
||||
|
||||
if r.allowExecuteUserCooldown(testLogger, c1, nil, badgeCollection{}) {
|
||||
t.Error("Call after cooldown added was allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteUserCooldown(testLogger, c1, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with skip badge was not allowed")
|
||||
}
|
||||
|
||||
if !r.allowExecuteUserCooldown(testLogger, c2, nil, badgeCollection{badgeBroadcaster: testBadgeLevel0}) {
|
||||
t.Error("Call in cooldown with different user was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowExecuteUserWhitelist(t *testing.T) {
|
||||
r := &rule{MatchUsers: []string{"amy"}}
|
||||
|
||||
|
|
12
timers.go
12
timers.go
|
@ -36,17 +36,17 @@ func newTimer() *timer {
|
|||
|
||||
// Cooldown timer
|
||||
|
||||
func (t *timer) AddCooldown(ruleID string) {
|
||||
t.add(timerTypeCooldown, t.getCooldownTimerKey(ruleID))
|
||||
func (t *timer) AddCooldown(tt timerType, limiter, ruleID string) {
|
||||
t.add(timerTypeCooldown, t.getCooldownTimerKey(tt, limiter, ruleID))
|
||||
}
|
||||
|
||||
func (t *timer) InCooldown(ruleID string, cooldown time.Duration) bool {
|
||||
return t.has(t.getCooldownTimerKey(ruleID), cooldown)
|
||||
func (t *timer) InCooldown(tt timerType, limiter, ruleID string, cooldown time.Duration) bool {
|
||||
return t.has(t.getCooldownTimerKey(tt, limiter, ruleID), cooldown)
|
||||
}
|
||||
|
||||
func (t timer) getCooldownTimerKey(ruleID string) string {
|
||||
func (t timer) getCooldownTimerKey(tt timerType, limiter, ruleID string) string {
|
||||
h := sha256.New()
|
||||
fmt.Fprintf(h, "%d:%s", timerTypeCooldown, ruleID)
|
||||
fmt.Fprintf(h, "%d:%s:%s", tt, limiter, ruleID)
|
||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
|
|
15
wiki/Home.md
15
wiki/Home.md
|
@ -75,9 +75,22 @@ rules: # See below for examples
|
|||
- whisper_to: '{{ .username }}' # String, username to send to, applies templating
|
||||
whisper_message: 'Ohai!' # String, message to send, applies templating
|
||||
|
||||
# Add a cooldown to the command (not to trigger counters twice, ...)
|
||||
# Add a cooldown to the rule in general (not to trigger counters twice, ...)
|
||||
# Using this will prevent the rule to be executed in all matching channels
|
||||
# as long as the cooldown is active.
|
||||
cooldown: 1s # Duration value: 1s / 1m / 1h
|
||||
|
||||
# Add a cooldown to the rule per channel (not to trigger counters twice, ...)
|
||||
# Using this will prevent the rule to be executed in the channel it was triggered
|
||||
# which means other channels are not affected.
|
||||
channel_cooldown: 1s # Duration value: 1s / 1m / 1h
|
||||
|
||||
# Add a cooldown to the rule per user (not to trigger counters twice, ...)
|
||||
# Using this will prevent the rule to be executed for the user which triggered it
|
||||
# in any of the matching channels, which means other users can trigger the command
|
||||
# while that particular user cannot
|
||||
user_cooldown: 1s # Duration value: 1s / 1m / 1h
|
||||
|
||||
# Do not apply cooldown for these badges
|
||||
skip_cooldown_for: [broadcaster, moderator]
|
||||
|
||||
|
|
Loading…
Reference in a new issue