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"`
}
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 {
return nil
return false, nil
}
return errors.Wrap(
return false, errors.Wrap(
c.WriteMessage(&irc.Message{
Command: "PRIVMSG",
Params: []string{

View file

@ -17,28 +17,28 @@ type ActorCounter struct {
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 {
return nil
return false, nil
}
counterName, err := formatMessage(*a.Counter, m, r, nil)
if err != nil {
return errors.Wrap(err, "preparing response")
return false, errors.Wrap(err, "preparing response")
}
if a.CounterSet != nil {
parseValue, err := formatMessage(*a.CounterSet, m, r, 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
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),
"set counter",
)
@ -49,7 +49,7 @@ func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
counterStep = *a.CounterStep
}
return errors.Wrap(
return false, errors.Wrap(
store.UpdateCounter(counterName, counterStep, false),
"update counter",
)

View file

@ -16,9 +16,9 @@ type ActorDelay struct {
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 {
return nil
return false, nil
}
totalDelay := a.Delay
@ -27,7 +27,7 @@ func (a ActorDelay) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
}
time.Sleep(totalDelay)
return nil
return false, nil
}
func (a ActorDelay) IsAsync() bool { return false }

View file

@ -15,17 +15,17 @@ type ActorDelete struct {
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 {
return nil
return false, nil
}
msgID, ok := m.Tags.GetTag("id")
if !ok || msgID == "" {
return nil
return false, nil
}
return errors.Wrap(
return false, errors.Wrap(
c.WriteMessage(&irc.Message{
Command: "PRIVMSG",
Params: []string{

View file

@ -13,22 +13,22 @@ type ActorRaw struct {
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 {
return nil
return false, nil
}
rawMsg, err := formatMessage(*a.RawMessage, m, r, nil)
if err != nil {
return errors.Wrap(err, "preparing raw message")
return false, errors.Wrap(err, "preparing raw message")
}
msg, err := irc.ParseMessage(rawMsg)
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),
"sending raw message",
)

View file

@ -15,18 +15,18 @@ type ActorRespond struct {
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 {
return nil
return false, nil
}
msg, err := formatMessage(*a.Respond, m, r, nil)
if err != 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 {
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),
"sending response",
)

View file

@ -19,16 +19,16 @@ type ActorScript struct {
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 {
return nil
return false, nil
}
var command []string
for _, arg := range a.Command {
tmp, err := formatMessage(arg, m, r, 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)
@ -49,7 +49,7 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
"tags": m.Tags,
"username": m.User,
}); 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
@ -59,12 +59,12 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
cmd.Stdout = stdout
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "running command")
return false, errors.Wrap(err, "running command")
}
if stdout.Len() == 0 {
// Script was successful but did not yield actions
return nil
return false, nil
}
var (
@ -74,16 +74,18 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
decoder.DisallowUnknownFields()
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 {
if err := triggerActions(c, m, r, action); err != nil {
return errors.Wrap(err, "execute returned action")
apc, err := triggerActions(c, m, r, 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 }

View file

@ -15,18 +15,18 @@ type ActorSetVariable struct {
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 == "" {
return nil
return false, nil
}
varName, err := formatMessage(a.Variable, m, r, nil)
if err != nil {
return errors.Wrap(err, "preparing variable name")
return false, errors.Wrap(err, "preparing variable name")
}
if a.Clear {
return errors.Wrap(
return false, errors.Wrap(
store.RemoveVariable(varName),
"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)
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),
"setting variable",
)

View file

@ -16,12 +16,12 @@ type ActorTimeout struct {
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 {
return nil
return false, nil
}
return errors.Wrap(
return false, errors.Wrap(
c.WriteMessage(&irc.Message{
Command: "PRIVMSG",
Params: []string{

View file

@ -16,19 +16,19 @@ type ActorWhisper struct {
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 {
return nil
return false, nil
}
to, err := formatMessage(*a.WhisperTo, m, r, 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)
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
@ -36,7 +36,7 @@ func (a ActorWhisper) Execute(c *irc.Client, m *irc.Message, r *Rule) error {
channel = fmt.Sprintf("#%s", config.Channels[0])
}
return errors.Wrap(
return false, errors.Wrap(
c.WriteMessage(&irc.Message{
Command: "PRIVMSG",
Params: []string{

View file

@ -11,7 +11,7 @@ import (
type (
Actor interface {
// 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
// in a Go routine as of long runtime. Normally it should return false
// except in very specific cases
@ -35,7 +35,7 @@ func registerAction(af ActorCreationFunc) {
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()
defer availableActionsLock.RUnlock()
@ -52,30 +52,38 @@ func triggerActions(c *irc.Client, m *irc.Message, rule *Rule, ra *RuleAction) e
if a.IsAsync() {
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")
}
}()
continue
}
if err := a.Execute(c, m, rule); err != nil {
return errors.Wrap(err, "execute action")
apc, err := a.Execute(c, m, rule)
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) {
for _, r := range config.GetMatchingRules(m, event) {
var preventCooldown bool
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")
}
preventCooldown = preventCooldown || apc
}
// Lock command
r.setCooldown(m)
if !preventCooldown {
r.setCooldown(m)
}
}
}