Add support to disable cooldown through the action module

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2021-08-12 00:12:10 +02:00
parent 0dc19d8eed
commit 2d4efb4832
Signed by: luzifer
GPG key ID: 0066F03ED215AD7D
11 changed files with 69 additions and 59 deletions

View file

@ -15,12 +15,12 @@ type ActorBan struct {
Ban *string `json:"ban" yaml:"ban"` Ban *string `json:"ban" yaml:"ban"`
} }
func (a ActorBan) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorBan) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.Ban == nil { if a.Ban == nil {
return nil return false, nil
} }
return errors.Wrap( return false, errors.Wrap(
c.WriteMessage(&irc.Message{ c.WriteMessage(&irc.Message{
Command: "PRIVMSG", Command: "PRIVMSG",
Params: []string{ Params: []string{

View file

@ -17,28 +17,28 @@ type ActorCounter struct {
Counter *string `json:"counter" yaml:"counter"` Counter *string `json:"counter" yaml:"counter"`
} }
func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.Counter == nil { if a.Counter == nil {
return nil return false, nil
} }
counterName, err := formatMessage(*a.Counter, m, r, nil) counterName, err := formatMessage(*a.Counter, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "preparing response") return false, errors.Wrap(err, "preparing response")
} }
if a.CounterSet != nil { if a.CounterSet != nil {
parseValue, err := formatMessage(*a.CounterSet, m, r, nil) parseValue, err := formatMessage(*a.CounterSet, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "execute counter value template") return false, errors.Wrap(err, "execute counter value template")
} }
counterValue, err := strconv.ParseInt(parseValue, 10, 64) //nolint:gomnd // Those numbers are static enough counterValue, err := strconv.ParseInt(parseValue, 10, 64) //nolint:gomnd // Those numbers are static enough
if err != nil { if err != nil {
return errors.Wrap(err, "parse counter value") return false, errors.Wrap(err, "parse counter value")
} }
return errors.Wrap( return false, errors.Wrap(
store.UpdateCounter(counterName, counterValue, true), store.UpdateCounter(counterName, counterValue, true),
"set counter", "set counter",
) )
@ -49,7 +49,7 @@ func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
counterStep = *a.CounterStep counterStep = *a.CounterStep
} }
return errors.Wrap( return false, errors.Wrap(
store.UpdateCounter(counterName, counterStep, false), store.UpdateCounter(counterName, counterStep, false),
"update counter", "update counter",
) )

View file

@ -16,9 +16,9 @@ type ActorDelay struct {
DelayJitter time.Duration `json:"delay_jitter" yaml:"delay_jitter"` DelayJitter time.Duration `json:"delay_jitter" yaml:"delay_jitter"`
} }
func (a ActorDelay) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorDelay) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.Delay == 0 && a.DelayJitter == 0 { if a.Delay == 0 && a.DelayJitter == 0 {
return nil return false, nil
} }
totalDelay := a.Delay totalDelay := a.Delay
@ -27,7 +27,7 @@ func (a ActorDelay) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
} }
time.Sleep(totalDelay) time.Sleep(totalDelay)
return nil return false, nil
} }
func (a ActorDelay) IsAsync() bool { return false } func (a ActorDelay) IsAsync() bool { return false }

View file

@ -15,17 +15,17 @@ type ActorDelete struct {
DeleteMessage *bool `json:"delete_message" yaml:"delete_message"` DeleteMessage *bool `json:"delete_message" yaml:"delete_message"`
} }
func (a ActorDelete) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorDelete) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.DeleteMessage == nil || !*a.DeleteMessage { if a.DeleteMessage == nil || !*a.DeleteMessage {
return nil return false, nil
} }
msgID, ok := m.Tags.GetTag("id") msgID, ok := m.Tags.GetTag("id")
if !ok || msgID == "" { if !ok || msgID == "" {
return nil return false, nil
} }
return errors.Wrap( return false, errors.Wrap(
c.WriteMessage(&irc.Message{ c.WriteMessage(&irc.Message{
Command: "PRIVMSG", Command: "PRIVMSG",
Params: []string{ Params: []string{

View file

@ -13,22 +13,22 @@ type ActorRaw struct {
RawMessage *string `json:"raw_message" yaml:"raw_message"` RawMessage *string `json:"raw_message" yaml:"raw_message"`
} }
func (a ActorRaw) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorRaw) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.RawMessage == nil { if a.RawMessage == nil {
return nil return false, nil
} }
rawMsg, err := formatMessage(*a.RawMessage, m, r, nil) rawMsg, err := formatMessage(*a.RawMessage, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "preparing raw message") return false, errors.Wrap(err, "preparing raw message")
} }
msg, err := irc.ParseMessage(rawMsg) msg, err := irc.ParseMessage(rawMsg)
if err != nil { if err != nil {
return errors.Wrap(err, "parsing raw message") return false, errors.Wrap(err, "parsing raw message")
} }
return errors.Wrap( return false, errors.Wrap(
c.WriteMessage(msg), c.WriteMessage(msg),
"sending raw message", "sending raw message",
) )

View file

@ -15,18 +15,18 @@ type ActorRespond struct {
RespondFallback *string `json:"respond_fallback" yaml:"respond_fallback"` RespondFallback *string `json:"respond_fallback" yaml:"respond_fallback"`
} }
func (a ActorRespond) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorRespond) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.Respond == nil { if a.Respond == nil {
return nil return false, nil
} }
msg, err := formatMessage(*a.Respond, m, r, nil) msg, err := formatMessage(*a.Respond, m, r, nil)
if err != nil { if err != nil {
if a.RespondFallback == nil { if a.RespondFallback == nil {
return errors.Wrap(err, "preparing response") return false, errors.Wrap(err, "preparing response")
} }
if msg, err = formatMessage(*a.RespondFallback, m, r, nil); err != nil { if msg, err = formatMessage(*a.RespondFallback, m, r, nil); err != nil {
return errors.Wrap(err, "preparing response fallback") return false, errors.Wrap(err, "preparing response fallback")
} }
} }
@ -48,7 +48,7 @@ func (a ActorRespond) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
} }
} }
return errors.Wrap( return false, errors.Wrap(
c.WriteMessage(ircMessage), c.WriteMessage(ircMessage),
"sending response", "sending response",
) )

View file

@ -19,16 +19,16 @@ type ActorScript struct {
Command []string `json:"command" yaml:"command"` Command []string `json:"command" yaml:"command"`
} }
func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if len(a.Command) == 0 { if len(a.Command) == 0 {
return nil return false, nil
} }
var command []string var command []string
for _, arg := range a.Command { for _, arg := range a.Command {
tmp, err := formatMessage(arg, m, r, nil) tmp, err := formatMessage(arg, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "execute command argument template") return false, errors.Wrap(err, "execute command argument template")
} }
command = append(command, tmp) command = append(command, tmp)
@ -49,7 +49,7 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
"tags": m.Tags, "tags": m.Tags,
"username": m.User, "username": m.User,
}); err != nil { }); err != nil {
return errors.Wrap(err, "encoding script input") return false, errors.Wrap(err, "encoding script input")
} }
cmd := exec.CommandContext(ctx, command[0], command[1:]...) // #nosec G204 // This is expected to call a command with parameters cmd := exec.CommandContext(ctx, command[0], command[1:]...) // #nosec G204 // This is expected to call a command with parameters
@ -59,12 +59,12 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
cmd.Stdout = stdout cmd.Stdout = stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return errors.Wrap(err, "running command") return false, errors.Wrap(err, "running command")
} }
if stdout.Len() == 0 { if stdout.Len() == 0 {
// Script was successful but did not yield actions // Script was successful but did not yield actions
return nil return false, nil
} }
var ( var (
@ -74,16 +74,18 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
decoder.DisallowUnknownFields() decoder.DisallowUnknownFields()
if err := decoder.Decode(&actions); err != nil { if err := decoder.Decode(&actions); err != nil {
return errors.Wrap(err, "decoding actions output") return false, errors.Wrap(err, "decoding actions output")
} }
for _, action := range actions { for _, action := range actions {
if err := triggerActions(c, m, r, action); err != nil { apc, err := triggerActions(c, m, r, action)
return errors.Wrap(err, "execute returned action") if err != nil {
return preventCooldown, errors.Wrap(err, "execute returned action")
} }
preventCooldown = preventCooldown || apc
} }
return nil return preventCooldown, nil
} }
func (a ActorScript) IsAsync() bool { return false } func (a ActorScript) IsAsync() bool { return false }

View file

@ -15,18 +15,18 @@ type ActorSetVariable struct {
Set string `json:"set" yaml:"set"` Set string `json:"set" yaml:"set"`
} }
func (a ActorSetVariable) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorSetVariable) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.Variable == "" { if a.Variable == "" {
return nil return false, nil
} }
varName, err := formatMessage(a.Variable, m, r, nil) varName, err := formatMessage(a.Variable, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "preparing variable name") return false, errors.Wrap(err, "preparing variable name")
} }
if a.Clear { if a.Clear {
return errors.Wrap( return false, errors.Wrap(
store.RemoveVariable(varName), store.RemoveVariable(varName),
"removing variable", "removing variable",
) )
@ -34,10 +34,10 @@ func (a ActorSetVariable) Execute(c *irc.Client, m *irc.Message, r *Rule) error
value, err := formatMessage(a.Set, m, r, nil) value, err := formatMessage(a.Set, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "preparing value") return false, errors.Wrap(err, "preparing value")
} }
return errors.Wrap( return false, errors.Wrap(
store.SetVariable(varName, value), store.SetVariable(varName, value),
"setting variable", "setting variable",
) )

View file

@ -16,12 +16,12 @@ type ActorTimeout struct {
Timeout *time.Duration `json:"timeout" yaml:"timeout"` Timeout *time.Duration `json:"timeout" yaml:"timeout"`
} }
func (a ActorTimeout) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorTimeout) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.Timeout == nil { if a.Timeout == nil {
return nil return false, nil
} }
return errors.Wrap( return false, errors.Wrap(
c.WriteMessage(&irc.Message{ c.WriteMessage(&irc.Message{
Command: "PRIVMSG", Command: "PRIVMSG",
Params: []string{ Params: []string{

View file

@ -16,19 +16,19 @@ type ActorWhisper struct {
WhisperTo *string `json:"whisper_to" yaml:"whisper_to"` WhisperTo *string `json:"whisper_to" yaml:"whisper_to"`
} }
func (a ActorWhisper) Execute(c *irc.Client, m *irc.Message, r *Rule) error { func (a ActorWhisper) Execute(c *irc.Client, m *irc.Message, r *Rule) (preventCooldown bool, err error) {
if a.WhisperTo == nil || a.WhisperMessage == nil { if a.WhisperTo == nil || a.WhisperMessage == nil {
return nil return false, nil
} }
to, err := formatMessage(*a.WhisperTo, m, r, nil) to, err := formatMessage(*a.WhisperTo, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "preparing whisper receiver") return false, errors.Wrap(err, "preparing whisper receiver")
} }
msg, err := formatMessage(*a.WhisperMessage, m, r, nil) msg, err := formatMessage(*a.WhisperMessage, m, r, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "preparing whisper message") return false, errors.Wrap(err, "preparing whisper message")
} }
channel := "#tmijs" // As a fallback, copied from tmi.js channel := "#tmijs" // As a fallback, copied from tmi.js
@ -36,7 +36,7 @@ func (a ActorWhisper) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
channel = fmt.Sprintf("#%s", config.Channels[0]) channel = fmt.Sprintf("#%s", config.Channels[0])
} }
return errors.Wrap( return false, errors.Wrap(
c.WriteMessage(&irc.Message{ c.WriteMessage(&irc.Message{
Command: "PRIVMSG", Command: "PRIVMSG",
Params: []string{ Params: []string{

View file

@ -11,7 +11,7 @@ import (
type ( type (
Actor interface { Actor interface {
// Execute will be called after the config was read into the Actor // Execute will be called after the config was read into the Actor
Execute(*irc.Client, *irc.Message, *Rule) error Execute(*irc.Client, *irc.Message, *Rule) (preventCooldown bool, err error)
// IsAsync may return true if the Execute function is to be executed // IsAsync may return true if the Execute function is to be executed
// in a Go routine as of long runtime. Normally it should return false // in a Go routine as of long runtime. Normally it should return false
// except in very specific cases // except in very specific cases
@ -35,7 +35,7 @@ func registerAction(af ActorCreationFunc) {
availableActions = append(availableActions, af) availableActions = append(availableActions, af)
} }
func triggerActions(c *irc.Client, m *irc.Message, rule *Rule, ra *RuleAction) error { func triggerActions(c *irc.Client, m *irc.Message, rule *Rule, ra *RuleAction) (preventCooldown bool, err error) {
availableActionsLock.RLock() availableActionsLock.RLock()
defer availableActionsLock.RUnlock() defer availableActionsLock.RUnlock()
@ -52,30 +52,38 @@ func triggerActions(c *irc.Client, m *irc.Message, rule *Rule, ra *RuleAction) e
if a.IsAsync() { if a.IsAsync() {
go func() { go func() {
if err := a.Execute(c, m, rule); err != nil { if _, err := a.Execute(c, m, rule); err != nil {
logger.WithError(err).Error("Error in async actor") logger.WithError(err).Error("Error in async actor")
} }
}() }()
continue continue
} }
if err := a.Execute(c, m, rule); err != nil { apc, err := a.Execute(c, m, rule)
return errors.Wrap(err, "execute action") if err != nil {
return preventCooldown, errors.Wrap(err, "execute action")
} }
preventCooldown = preventCooldown || apc
} }
return nil return preventCooldown, nil
} }
func handleMessage(c *irc.Client, m *irc.Message, event *string) { func handleMessage(c *irc.Client, m *irc.Message, event *string) {
for _, r := range config.GetMatchingRules(m, event) { for _, r := range config.GetMatchingRules(m, event) {
var preventCooldown bool
for _, a := range r.Actions { for _, a := range r.Actions {
if err := triggerActions(c, m, r, a); err != nil { apc, err := triggerActions(c, m, r, a)
if err != nil {
log.WithError(err).Error("Unable to trigger action") log.WithError(err).Error("Unable to trigger action")
} }
preventCooldown = preventCooldown || apc
} }
// Lock command // Lock command
if !preventCooldown {
r.setCooldown(m) r.setCooldown(m)
} }
} }
}