Ctrl K
ComunidadGuías…

actions.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"errors"
	"fmt"
	"sync"

	"github.com/Luzifer/go_helpers/fieldcollection"
	log "github.com/sirupsen/logrus"
	"gopkg.in/irc.v4"

	"github.com/Luzifer/twitch-bot/v3/internal/locker"
	"github.com/Luzifer/twitch-bot/v3/pkg/event"
	"github.com/Luzifer/twitch-bot/v3/plugins"
)

var (
	availableActions     = make(map[string]plugins.ActorCreationFunc)
	availableActionsLock = new(sync.RWMutex)
)

// Compile-time assertion
var _ plugins.ActorRegistrationFunc = registerAction

func getActorByName(name string) (plugins.Actor, error) {
	availableActionsLock.RLock()
	defer availableActionsLock.RUnlock()

	acf, ok := availableActions[name]
	if !ok {
		return nil, fmt.Errorf("undefined actor %q called", name)
	}

	return acf(), nil
}

func registerAction(name string, acf plugins.ActorCreationFunc) {
	availableActionsLock.Lock()
	defer availableActionsLock.Unlock()

	if _, ok := availableActions[name]; ok {
		log.WithField("name", name).Fatal("Duplicate registration of actor")
	}

	availableActions[name] = acf
}

func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugins.RuleAction, eventData *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
	availableActionsLock.RLock()
	defer availableActionsLock.RUnlock()

	a, err := getActorByName(ra.Type)
	if err != nil {
		return false, fmt.Errorf("getting actor: %w", err)
	}

	logger := log.WithField("actor", a.Name())

	if a.IsAsync() {
		go func() {
			if _, err := a.Execute(c, m, rule, eventData, ra.Attributes); err != nil {
				logger.WithError(err).Error("Error in async actor")
			}
		}()
		return preventCooldown, nil
	}

	apc, err := a.Execute(c, m, rule, eventData, ra.Attributes)
	if err != nil {
		return apc, fmt.Errorf("execute action: %w", err)
	}

	return apc, nil
}

func handleMessage(c *irc.Client, m *irc.Message, evt *string, eventData *fieldcollection.FieldCollection) {
	// Send events to registered handlers
	if evt != nil {
		go notifyEventHandlers(*evt, eventData)
	}

	matchingRules := config.GetMatchingRules(m, evt, eventData)
	for i := range matchingRules {
		go handleMessageRuleExecution(c, m, matchingRules[i], eventData)
	}
}

func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection) {
	lockKey := r.CooldownLockKey(m, eventData)
	locker.LockByKey(lockKey)
	defer locker.UnlockByKey(lockKey)

	if !r.CanExecuteCooldowns(m, timerService, eventData) {
		return
	}

	var (
		ruleEventData   = fieldcollection.NewFieldCollection()
		executionError  bool
		preventCooldown bool
	)

	if eventData != nil {
		ruleEventData.SetFromData(eventData.Data())
	}

ActionsLoop:
	for _, a := range r.Actions {
		apc, err := triggerAction(c, m, r, a, ruleEventData)
		switch {
		case err == nil:
			// Rule execution did not cause an error, we store the
			// cooldown modifier and continue

			preventCooldown = preventCooldown || apc
			continue ActionsLoop

		case errors.Is(err, plugins.ErrStopRuleExecution):
			// Action has asked to stop executing this rule so we store
			// the cooldown modifier and stop executing the actions stack
			// Action experienced an error: We don't store the cooldown
			// state of this action and stop executing the actions stack
			// for this rule

			preventCooldown = preventCooldown || apc
			break ActionsLoop

		default:
			// Break execution for this rule when one action fails
			// Lock command

			executionError = true
			log.WithError(err).Error("Unable to trigger action")
			break ActionsLoop
		}
	}

	if !preventCooldown && !executionError {
		r.SetCooldown(timerService, m, eventData)
	}
}

func handleTypedMessage(c *irc.Client, m *irc.Message, evt event.Event) {
	var evtType *string
	if evt != nil {
		evtType = evt.Event()
	}

	handleMessage(c, m, evtType, event.ToFieldCollection(evt))
}