From 8ba6d2ef08423e0a4b23d515fd1ec80e1d332c98 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Thu, 11 Nov 2021 14:59:08 +0100 Subject: [PATCH] [core] BREAKING: Allow actors to set fields those after them (#11) Signed-off-by: Knut Ahlers --- action_counter.go | 4 +- action_script.go | 4 +- action_setvar.go | 4 +- actions.go | 4 +- automessage.go | 8 +- config.go | 2 +- functions.go | 2 +- functions_counter.go | 8 +- functions_irc.go | 12 +- internal/actors/ban/actor.go | 4 +- internal/actors/delay/actor.go | 4 +- internal/actors/delete/actor.go | 4 +- internal/actors/modchannel/actor.go | 4 +- internal/actors/nuke/actor.go | 4 +- internal/actors/punish/actor.go | 8 +- internal/actors/quotedb/actor.go | 15 +- internal/actors/raw/actor.go | 4 +- internal/actors/respond/actor.go | 4 +- internal/actors/timeout/actor.go | 4 +- internal/actors/whisper/actor.go | 4 +- irc.go | 60 ++++---- msgformatter.go | 22 ++- plugins/fieldcollection.go | 225 ++++++++++++++++++++++++---- plugins/fieldcollection_test.go | 80 ++++++++++ plugins/helpers.go | 4 +- plugins/interface.go | 10 +- plugins/rule.go | 38 ++--- plugins/rule_test.go | 2 +- twitchWatcher.go | 12 +- 29 files changed, 402 insertions(+), 158 deletions(-) create mode 100644 plugins/fieldcollection_test.go diff --git a/action_counter.go b/action_counter.go index 49db9ab..97c33da 100644 --- a/action_counter.go +++ b/action_counter.go @@ -107,7 +107,7 @@ func init() { type ActorCounter struct{} -func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { counterName, err := formatMessage(attrs.MustString("counter", nil), m, r, eventData) if err != nil { return false, errors.Wrap(err, "preparing response") @@ -144,7 +144,7 @@ func (a ActorCounter) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, ev func (a ActorCounter) IsAsync() bool { return false } func (a ActorCounter) Name() string { return "counter" } -func (a ActorCounter) Validate(attrs plugins.FieldCollection) (err error) { +func (a ActorCounter) Validate(attrs *plugins.FieldCollection) (err error) { if cn, err := attrs.String("counter"); err != nil || cn == "" { return errors.New("counter name must be non-empty string") } diff --git a/action_script.go b/action_script.go index 3f3eba4..02ee5cb 100644 --- a/action_script.go +++ b/action_script.go @@ -46,7 +46,7 @@ func init() { type ActorScript struct{} -func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { command, err := attrs.StringSlice("command") if err != nil { return false, errors.Wrap(err, "getting command") @@ -123,7 +123,7 @@ func (a ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eve func (a ActorScript) IsAsync() bool { return false } func (a ActorScript) Name() string { return "script" } -func (a ActorScript) Validate(attrs plugins.FieldCollection) (err error) { +func (a ActorScript) Validate(attrs *plugins.FieldCollection) (err error) { if cmd, err := attrs.StringSlice("command"); err != nil || len(cmd) == 0 { return errors.New("command must be slice of strings with length > 0") } diff --git a/action_setvar.go b/action_setvar.go index f7fbfba..2da85bc 100644 --- a/action_setvar.go +++ b/action_setvar.go @@ -92,7 +92,7 @@ func init() { type ActorSetVariable struct{} -func (a ActorSetVariable) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a ActorSetVariable) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { varName, err := formatMessage(attrs.MustString("variable", nil), m, r, eventData) if err != nil { return false, errors.Wrap(err, "preparing variable name") @@ -119,7 +119,7 @@ func (a ActorSetVariable) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule func (a ActorSetVariable) IsAsync() bool { return false } func (a ActorSetVariable) Name() string { return "setvariable" } -func (a ActorSetVariable) Validate(attrs plugins.FieldCollection) (err error) { +func (a ActorSetVariable) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("variable"); err != nil || v == "" { return errors.New("variable name must be non-empty string") } diff --git a/actions.go b/actions.go index f5410cc..dc98b3b 100644 --- a/actions.go +++ b/actions.go @@ -40,7 +40,7 @@ func registerAction(name string, acf plugins.ActorCreationFunc) { availableActions[name] = acf } -func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugins.RuleAction, eventData plugins.FieldCollection) (preventCooldown bool, err error) { +func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugins.RuleAction, eventData *plugins.FieldCollection) (preventCooldown bool, err error) { availableActionsLock.RLock() defer availableActionsLock.RUnlock() @@ -64,7 +64,7 @@ func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugin return apc, errors.Wrap(err, "execute action") } -func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData plugins.FieldCollection) { +func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *plugins.FieldCollection) { for _, r := range config.GetMatchingRules(m, event, eventData) { var preventCooldown bool diff --git a/automessage.go b/automessage.go index ac15ceb..c94ad69 100644 --- a/automessage.go +++ b/automessage.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Luzifer/go_helpers/v2/str" + "github.com/Luzifer/twitch-bot/plugins" "github.com/go-irc/irc" "github.com/mitchellh/hashstructure/v2" "github.com/pkg/errors" @@ -163,9 +164,10 @@ func (a *autoMessage) allowExecuteDisableOnTemplate() bool { return true } - res, err := formatMessage(*a.DisableOnTemplate, nil, nil, map[string]interface{}{ - "channel": a.Channel, - }) + fields := plugins.NewFieldCollection() + fields.Set("channel", a.Channel) + + res, err := formatMessage(*a.DisableOnTemplate, nil, nil, fields) if err != nil { log.WithError(err).Error("Error in auto-message disable template") // Caused an error, forbid execution diff --git a/config.go b/config.go index ba5d877..01ed3b0 100644 --- a/config.go +++ b/config.go @@ -250,7 +250,7 @@ func (c *configFile) CloseRawMessageWriter() error { return c.rawLogWriter.Close() } -func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData map[string]interface{}) []*plugins.Rule { +func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData *plugins.FieldCollection) []*plugins.Rule { configLock.RLock() defer configLock.RUnlock() diff --git a/functions.go b/functions.go index 0a08ea7..4aa16c8 100644 --- a/functions.go +++ b/functions.go @@ -29,7 +29,7 @@ func newTemplateFuncProvider() *templateFuncProvider { return out } -func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, fields map[string]interface{}) template.FuncMap { +func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) template.FuncMap { t.lock.RLock() defer t.lock.RUnlock() diff --git a/functions_counter.go b/functions_counter.go index e1ca64d..a4477bd 100644 --- a/functions_counter.go +++ b/functions_counter.go @@ -9,11 +9,11 @@ import ( ) func init() { - tplFuncs.Register("channelCounter", func(m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) interface{} { + tplFuncs.Register("channelCounter", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} { return func(name string) (string, error) { - channel, ok := fields["channel"].(string) - if !ok { - return "", errors.New("channel not available") + channel, err := fields.String("channel") + if err != nil { + return "", errors.Wrap(err, "channel not available") } return strings.Join([]string{channel, name}, ":"), nil diff --git a/functions_irc.go b/functions_irc.go index d2056ef..089e8f3 100644 --- a/functions_irc.go +++ b/functions_irc.go @@ -10,7 +10,7 @@ import ( ) func init() { - tplFuncs.Register("arg", func(m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) interface{} { + tplFuncs.Register("arg", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} { return func(arg int) (string, error) { msgParts := strings.Split(m.Trailing(), " ") if len(msgParts) <= arg { @@ -21,10 +21,10 @@ func init() { } }) - tplFuncs.Register("botHasBadge", func(m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) interface{} { + tplFuncs.Register("botHasBadge", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} { return func(badge string) bool { - channel, ok := fields["channel"].(string) - if !ok { + channel, err := fields.String("channel") + if err != nil { log.Trace("Fields for botHasBadge function had no channel") return false } @@ -40,7 +40,7 @@ func init() { tplFuncs.Register("fixUsername", plugins.GenericTemplateFunctionGetter(func(username string) string { return strings.TrimLeft(username, "@#") })) - tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) interface{} { + tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} { return func(idx int, fallback ...string) (string, error) { fields := r.GetMatchMessage().FindStringSubmatch(m.Trailing()) if len(fields) <= idx { @@ -55,7 +55,7 @@ func init() { } }) - tplFuncs.Register("tag", func(m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) interface{} { + tplFuncs.Register("tag", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} { return func(tag string) string { s, _ := m.GetTag(tag) return s diff --git a/internal/actors/ban/actor.go b/internal/actors/ban/actor.go index d3cbdfc..755ae77 100644 --- a/internal/actors/ban/actor.go +++ b/internal/actors/ban/actor.go @@ -36,7 +36,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { ptrStringEmpty := func(v string) *string { return &v }("") cmd := []string{ @@ -63,4 +63,4 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { return nil } +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { return nil } diff --git a/internal/actors/delay/actor.go b/internal/actors/delay/actor.go index 9726681..71d6a3a 100644 --- a/internal/actors/delay/actor.go +++ b/internal/actors/delay/actor.go @@ -45,7 +45,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { var ( ptrZeroDuration = func(v time.Duration) *time.Duration { return &v }(0) delay = attrs.MustDuration("delay", ptrZeroDuration) @@ -68,4 +68,4 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { return nil } +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { return nil } diff --git a/internal/actors/delete/actor.go b/internal/actors/delete/actor.go index cd17003..ce777a9 100644 --- a/internal/actors/delete/actor.go +++ b/internal/actors/delete/actor.go @@ -24,7 +24,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { msgID, ok := m.Tags.GetTag("id") if !ok || msgID == "" { return false, nil @@ -45,4 +45,4 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { return nil } +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { return nil } diff --git a/internal/actors/modchannel/actor.go b/internal/actors/modchannel/actor.go index f929741..21c6b57 100644 --- a/internal/actors/modchannel/actor.go +++ b/internal/actors/modchannel/actor.go @@ -64,7 +64,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { var ( ptrStringEmpty = func(v string) *string { return &v }("") game = attrs.MustString("game", ptrStringEmpty) @@ -109,7 +109,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("channel"); err != nil || v == "" { return errors.New("channel must be non-empty string") } diff --git a/internal/actors/nuke/actor.go b/internal/actors/nuke/actor.go index 5e4fcca..05276ad 100644 --- a/internal/actors/nuke/actor.go +++ b/internal/actors/nuke/actor.go @@ -143,7 +143,7 @@ type ( } ) -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { rawMatch, err := formatMessage(attrs.MustString("match", nil), m, r, eventData) if err != nil { return false, errors.Wrap(err, "formatting match") @@ -229,7 +229,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("match"); err != nil || v == "" { return errors.New("match must be non-empty string") } diff --git a/internal/actors/punish/actor.go b/internal/actors/punish/actor.go index 7e59dc7..1e3c997 100644 --- a/internal/actors/punish/actor.go +++ b/internal/actors/punish/actor.go @@ -142,7 +142,7 @@ type ( // Punish -func (a actorPunish) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actorPunish) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { var ( cooldown = attrs.MustDuration("cooldown", ptrDefaultCooldown) reason = attrs.MustString("reason", ptrStringEmpty) @@ -214,7 +214,7 @@ func (a actorPunish) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eve func (a actorPunish) IsAsync() bool { return false } func (a actorPunish) Name() string { return actorNamePunish } -func (a actorPunish) Validate(attrs plugins.FieldCollection) (err error) { +func (a actorPunish) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("user"); err != nil || v == "" { return errors.New("user must be non-empty string") } @@ -228,7 +228,7 @@ func (a actorPunish) Validate(attrs plugins.FieldCollection) (err error) { // Reset -func (a actorResetPunish) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actorResetPunish) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { var ( user = attrs.MustString("user", nil) uuid = attrs.MustString("uuid", ptrStringEmpty) @@ -249,7 +249,7 @@ func (a actorResetPunish) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule func (a actorResetPunish) IsAsync() bool { return false } func (a actorResetPunish) Name() string { return actorNameResetPunish } -func (a actorResetPunish) Validate(attrs plugins.FieldCollection) (err error) { +func (a actorResetPunish) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("user"); err != nil || v == "" { return errors.New("user must be non-empty string") } diff --git a/internal/actors/quotedb/actor.go b/internal/actors/quotedb/actor.go index eeccf67..df081f1 100644 --- a/internal/actors/quotedb/actor.go +++ b/internal/actors/quotedb/actor.go @@ -79,7 +79,7 @@ func Register(args plugins.RegistrationArguments) error { registerAPI(args.RegisterAPIRoute) - args.RegisterTemplateFunction("lastQuoteIndex", func(m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) interface{} { + args.RegisterTemplateFunction("lastQuoteIndex", func(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) interface{} { return func() int { return storedObject.GetMaxQuoteIdx(plugins.DeriveChannel(m, nil)) } @@ -101,7 +101,7 @@ type ( } ) -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { var ( action = attrs.MustString("action", ptrStringEmpty) indexStr = attrs.MustString("index", ptrStringZero) @@ -149,12 +149,9 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData return false, nil } - fields := make(plugins.FieldCollection) - for k, v := range eventData { - fields[k] = v - } - fields["index"] = idx - fields["quote"] = quote + fields := eventData.Clone() + fields.Set("index", idx) + fields.Set("quote", quote) format := attrs.MustString("format", ptrStringOutFormat) msg, err := formatMessage(format, m, r, fields) @@ -180,7 +177,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { action := attrs.MustString("action", ptrStringEmpty) switch action { diff --git a/internal/actors/raw/actor.go b/internal/actors/raw/actor.go index 0d80e20..1969be3 100644 --- a/internal/actors/raw/actor.go +++ b/internal/actors/raw/actor.go @@ -38,7 +38,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { rawMsg, err := formatMessage(attrs.MustString("message", nil), m, r, eventData) if err != nil { return false, errors.Wrap(err, "preparing raw message") @@ -58,7 +58,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("message"); err != nil || v == "" { return errors.New("message must be non-empty string") } diff --git a/internal/actors/respond/actor.go b/internal/actors/respond/actor.go index 56591db..937432b 100644 --- a/internal/actors/respond/actor.go +++ b/internal/actors/respond/actor.go @@ -74,7 +74,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { msg, err := formatMessage(attrs.MustString("message", nil), m, r, eventData) if err != nil { if !attrs.CanString("fallback") || attrs.MustString("fallback", nil) == "" { @@ -118,7 +118,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("message"); err != nil || v == "" { return errors.New("message must be non-empty string") } diff --git a/internal/actors/timeout/actor.go b/internal/actors/timeout/actor.go index 495820c..dc36da5 100644 --- a/internal/actors/timeout/actor.go +++ b/internal/actors/timeout/actor.go @@ -37,7 +37,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { return false, errors.Wrap( c.WriteMessage(&irc.Message{ Command: "PRIVMSG", @@ -53,7 +53,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.Duration("duration"); err != nil || v < time.Second { return errors.New("duration must be of type duration greater or equal one second") } diff --git a/internal/actors/whisper/actor.go b/internal/actors/whisper/actor.go index d4ef1ad..dfecfca 100644 --- a/internal/actors/whisper/actor.go +++ b/internal/actors/whisper/actor.go @@ -49,7 +49,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.FieldCollection, attrs plugins.FieldCollection) (preventCooldown bool, err error) { +func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { to, err := formatMessage(attrs.MustString("to", nil), m, r, eventData) if err != nil { return false, errors.Wrap(err, "preparing whisper receiver") @@ -77,7 +77,7 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData func (a actor) IsAsync() bool { return false } func (a actor) Name() string { return actorName } -func (a actor) Validate(attrs plugins.FieldCollection) (err error) { +func (a actor) Validate(attrs *plugins.FieldCollection) (err error) { if v, err := attrs.String("to"); err != nil || v == "" { return errors.New("to must be non-empty string") } diff --git a/irc.go b/irc.go index 50e04c1..0fa0b90 100644 --- a/irc.go +++ b/irc.go @@ -198,32 +198,32 @@ func (i ircHandler) handleClearChat(m *irc.Message) { var ( evt *string - fields = plugins.FieldCollection{ - "channel": i.getChannel(m), // Compatibility to plugins.DeriveChannel - } + fields = plugins.NewFieldCollection() ) + fields.Set("channel", i.getChannel(m)) // Compatibility to plugins.DeriveChannel + switch { case secondsErr == nil && hasTargetUserID: // User & Duration = Timeout evt = eventTypeTimeout - fields["duration"] = time.Duration(seconds) * time.Second - fields["seconds"] = seconds - fields["target_id"] = targetUserID - fields["target_name"] = m.Trailing() - log.WithFields(log.Fields(fields)).Info("User was timed out") + fields.Set("duration", time.Duration(seconds)*time.Second) + fields.Set("seconds", seconds) + fields.Set("target_id", targetUserID) + fields.Set("target_name", m.Trailing()) + log.WithFields(log.Fields(fields.Data())).Info("User was timed out") case hasTargetUserID: // User w/o Duration = Ban evt = eventTypeBan - fields["target_id"] = targetUserID - fields["target_name"] = m.Trailing() - log.WithFields(log.Fields(fields)).Info("User was banned") + fields.Set("target_id", targetUserID) + fields.Set("target_name", m.Trailing()) + log.WithFields(log.Fields(fields.Data())).Info("User was banned") default: // No User = /clear evt = eventTypeClearChat - log.WithFields(log.Fields(fields)).Info("Chat was cleared") + log.WithFields(log.Fields(fields.Data())).Info("Chat was cleared") } go handleMessage(i.c, m, evt, fields) @@ -254,7 +254,7 @@ func (i ircHandler) handlePermit(m *irc.Message) { log.WithField("user", username).Debug("Added permit") timerStore.AddPermit(m.Params[0], username) - go handleMessage(i.c, m, eventTypePermit, plugins.FieldCollection{"username": username}) + go handleMessage(i.c, m, eventTypePermit, plugins.FieldCollectionFromData(map[string]interface{}{"username": username})) } func (i ircHandler) handleTwitchNotice(m *irc.Message) { @@ -301,9 +301,9 @@ func (i ircHandler) handleTwitchPrivmsg(m *irc.Message) { } if bits, err := strconv.ParseInt(string(m.Tags["bits"]), 10, 64); err == nil { - go handleMessage(i.c, m, eventTypeBits, plugins.FieldCollection{ + go handleMessage(i.c, m, eventTypeBits, plugins.FieldCollectionFromData(map[string]interface{}{ "bits": bits, - }) + })) } go handleMessage(i.c, m, nil, nil) @@ -322,61 +322,61 @@ func (i ircHandler) handleTwitchUsernotice(m *irc.Message) { log.WithField("msg", m).Warn("Received usernotice without msg-id") case "raid": - evtData := plugins.FieldCollection{ + evtData := plugins.FieldCollectionFromData(map[string]interface{}{ "channel": i.getChannel(m), // Compatibility to plugins.DeriveChannel "from": m.Tags["login"], "user": m.Tags["login"], // Compatibility to plugins.DeriveUser "viewercount": m.Tags["msg-param-viewerCount"], - } - log.WithFields(log.Fields(evtData)).Info("Incoming raid") + }) + log.WithFields(log.Fields(evtData.Data())).Info("Incoming raid") go handleMessage(i.c, m, eventTypeRaid, evtData) case "resub": - evtData := plugins.FieldCollection{ + evtData := plugins.FieldCollectionFromData(map[string]interface{}{ "channel": i.getChannel(m), // Compatibility to plugins.DeriveChannel "from": m.Tags["login"], "subscribed_months": m.Tags["msg-param-cumulative-months"], "plan": m.Tags["msg-param-sub-plan"], "user": m.Tags["login"], // Compatibility to plugins.DeriveUser - } - log.WithFields(log.Fields(evtData)).Info("User re-subscribed") + }) + log.WithFields(log.Fields(evtData.Data())).Info("User re-subscribed") go handleMessage(i.c, m, eventTypeResub, evtData) case "sub": - evtData := plugins.FieldCollection{ + evtData := plugins.FieldCollectionFromData(map[string]interface{}{ "channel": i.getChannel(m), // Compatibility to plugins.DeriveChannel "from": m.Tags["login"], "plan": m.Tags["msg-param-sub-plan"], "user": m.Tags["login"], // Compatibility to plugins.DeriveUser - } - log.WithFields(log.Fields(evtData)).Info("User subscribed") + }) + log.WithFields(log.Fields(evtData.Data())).Info("User subscribed") go handleMessage(i.c, m, eventTypeSub, evtData) case "subgift", "anonsubgift": - evtData := plugins.FieldCollection{ + evtData := plugins.FieldCollectionFromData(map[string]interface{}{ "channel": i.getChannel(m), // Compatibility to plugins.DeriveChannel "from": m.Tags["login"], "gifted_months": m.Tags["msg-param-gift-months"], "plan": m.Tags["msg-param-sub-plan"], "to": m.Tags["msg-param-recipient-user-name"], "user": m.Tags["login"], // Compatibility to plugins.DeriveUser - } - log.WithFields(log.Fields(evtData)).Info("User gifted a sub") + }) + log.WithFields(log.Fields(evtData.Data())).Info("User gifted a sub") go handleMessage(i.c, m, eventTypeSubgift, evtData) case "submysterygift": - evtData := plugins.FieldCollection{ + evtData := plugins.FieldCollectionFromData(map[string]interface{}{ "channel": i.getChannel(m), // Compatibility to plugins.DeriveChannel "from": m.Tags["login"], "number": m.Tags["msg-param-mass-gift-count"], "plan": m.Tags["msg-param-sub-plan"], "user": m.Tags["login"], // Compatibility to plugins.DeriveUser - } - log.WithFields(log.Fields(evtData)).Info("User gifted subs to the community") + }) + log.WithFields(log.Fields(evtData.Data())).Info("User gifted subs to the community") go handleMessage(i.c, m, eventTypeSubmysterygift, evtData) diff --git a/msgformatter.go b/msgformatter.go index 7f2564a..72c7bc5 100644 --- a/msgformatter.go +++ b/msgformatter.go @@ -13,27 +13,23 @@ import ( // Compile-time assertion var _ plugins.MsgFormatter = formatMessage -func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields plugins.FieldCollection) (string, error) { - compiledFields := map[string]interface{}{} +func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) (string, error) { + compiledFields := plugins.NewFieldCollection() if config != nil { configLock.RLock() - for k, v := range config.Variables { - compiledFields[k] = v - } - compiledFields["permitTimeout"] = int64(config.PermitTimeout / time.Second) + compiledFields.SetFromData(config.Variables) + compiledFields.Set("permitTimeout", int64(config.PermitTimeout/time.Second)) configLock.RUnlock() } - for k, v := range fields { - compiledFields[k] = v - } + compiledFields.SetFromData(fields.Data()) if m != nil { - compiledFields["msg"] = m + compiledFields.Set("msg", m) } - compiledFields["username"] = plugins.DeriveUser(m, fields) - compiledFields["channel"] = plugins.DeriveChannel(m, fields) + compiledFields.Set("username", plugins.DeriveUser(m, fields)) + compiledFields.Set("channel", plugins.DeriveChannel(m, fields)) // Parse and execute template tpl, err := template. @@ -45,7 +41,7 @@ func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields plu } buf := new(bytes.Buffer) - err = tpl.Execute(buf, compiledFields) + err = tpl.Execute(buf, compiledFields.Data()) return buf.String(), errors.Wrap(err, "execute template") } diff --git a/plugins/fieldcollection.go b/plugins/fieldcollection.go index 853663f..023ab32 100644 --- a/plugins/fieldcollection.go +++ b/plugins/fieldcollection.go @@ -1,9 +1,11 @@ package plugins import ( + "encoding/json" "fmt" "strconv" "strings" + "sync" "time" "github.com/pkg/errors" @@ -14,33 +16,88 @@ var ( ErrValueMismatch = errors.New("specified value has different format") ) -type FieldCollection map[string]interface{} +type FieldCollection struct { + data map[string]interface{} + lock sync.RWMutex +} -func (f FieldCollection) CanBool(name string) bool { +// NewFieldCollection creates a new FieldCollection with empty data store +func NewFieldCollection() *FieldCollection { + return &FieldCollection{data: make(map[string]interface{})} +} + +// FieldCollectionFromData is a wrapper around NewFieldCollection and SetFromData +func FieldCollectionFromData(data map[string]interface{}) *FieldCollection { + o := NewFieldCollection() + o.SetFromData(data) + return o +} + +// CanBool tries to read key name as bool and checks whether error is nil +func (f *FieldCollection) CanBool(name string) bool { _, err := f.Bool(name) return err == nil } -func (f FieldCollection) CanDuration(name string) bool { +// CanDuration tries to read key name as time.Duration and checks whether error is nil +func (f *FieldCollection) CanDuration(name string) bool { _, err := f.Duration(name) return err == nil } -func (f FieldCollection) CanInt64(name string) bool { +// CanInt64 tries to read key name as int64 and checks whether error is nil +func (f *FieldCollection) CanInt64(name string) bool { _, err := f.Int64(name) return err == nil } -func (f FieldCollection) CanString(name string) bool { +// CanString tries to read key name as string and checks whether error is nil +func (f *FieldCollection) CanString(name string) bool { _, err := f.String(name) return err == nil } -func (f FieldCollection) Expect(keys ...string) error { +// Clone is a wrapper around n.SetFromData(o.Data()) +func (f *FieldCollection) Clone() *FieldCollection { + out := new(FieldCollection) + out.SetFromData(f.Data()) + return out +} + +// Data creates a map-copy of the data stored inside the FieldCollection +func (f *FieldCollection) Data() map[string]interface{} { + if f == nil { + return nil + } + + f.lock.RLock() + defer f.lock.RUnlock() + + out := make(map[string]interface{}) + for k := range f.data { + out[k] = f.data[k] + } + + return out +} + +// Expect takes a list of keys and returns an error with all non-found names +func (f *FieldCollection) Expect(keys ...string) error { + if len(keys) == 0 { + return nil + } + + if f == nil || f.data == nil { + return errors.New("uninitialized field collection") + } + + f.lock.RLock() + defer f.lock.RUnlock() + var missing []string for _, k := range keys { - if _, ok := f[k]; !ok { + if _, ok := f.data[k]; !ok { missing = append(missing, k) } } @@ -52,17 +109,13 @@ func (f FieldCollection) Expect(keys ...string) error { return nil } -func (f FieldCollection) HasAll(keys ...string) bool { - for _, k := range keys { - if _, ok := f[k]; !ok { - return false - } - } - - return true +// HasAll takes a list of keys and returns whether all of them exist inside the FieldCollection +func (f *FieldCollection) HasAll(keys ...string) bool { + return f.Expect(keys...) == nil } -func (f FieldCollection) MustBool(name string, defVal *bool) bool { +// MustBool is a wrapper around Bool and panics if an error was returned +func (f *FieldCollection) MustBool(name string, defVal *bool) bool { v, err := f.Bool(name) if err != nil { if defVal != nil { @@ -73,7 +126,8 @@ func (f FieldCollection) MustBool(name string, defVal *bool) bool { return v } -func (f FieldCollection) MustDuration(name string, defVal *time.Duration) time.Duration { +// MustDuration is a wrapper around Duration and panics if an error was returned +func (f *FieldCollection) MustDuration(name string, defVal *time.Duration) time.Duration { v, err := f.Duration(name) if err != nil { if defVal != nil { @@ -84,7 +138,8 @@ func (f FieldCollection) MustDuration(name string, defVal *time.Duration) time.D return v } -func (f FieldCollection) MustInt64(name string, defVal *int64) int64 { +// MustInt64 is a wrapper around Int64 and panics if an error was returned +func (f *FieldCollection) MustInt64(name string, defVal *int64) int64 { v, err := f.Int64(name) if err != nil { if defVal != nil { @@ -95,7 +150,8 @@ func (f FieldCollection) MustInt64(name string, defVal *int64) int64 { return v } -func (f FieldCollection) MustString(name string, defVal *string) string { +// MustString is a wrapper around String and panics if an error was returned +func (f *FieldCollection) MustString(name string, defVal *string) string { v, err := f.String(name) if err != nil { if defVal != nil { @@ -106,8 +162,16 @@ func (f FieldCollection) MustString(name string, defVal *string) string { return v } -func (f FieldCollection) Bool(name string) (bool, error) { - v, ok := f[name] +// Bool tries to read key name as bool +func (f *FieldCollection) Bool(name string) (bool, error) { + if f == nil || f.data == nil { + return false, errors.New("uninitialized field collection") + } + + f.lock.RLock() + defer f.lock.RUnlock() + + v, ok := f.data[name] if !ok { return false, ErrValueNotSet } @@ -123,7 +187,15 @@ func (f FieldCollection) Bool(name string) (bool, error) { return false, ErrValueMismatch } -func (f FieldCollection) Duration(name string) (time.Duration, error) { +// Duration tries to read key name as time.Duration +func (f *FieldCollection) Duration(name string) (time.Duration, error) { + if f == nil || f.data == nil { + return 0, errors.New("uninitialized field collection") + } + + f.lock.RLock() + defer f.lock.RUnlock() + v, err := f.String(name) if err != nil { return 0, errors.Wrap(err, "getting string value") @@ -133,8 +205,16 @@ func (f FieldCollection) Duration(name string) (time.Duration, error) { return d, errors.Wrap(err, "parsing value") } -func (f FieldCollection) Int64(name string) (int64, error) { - v, ok := f[name] +// Int64 tries to read key name as int64 +func (f *FieldCollection) Int64(name string) (int64, error) { + if f == nil || f.data == nil { + return 0, errors.New("uninitialized field collection") + } + + f.lock.RLock() + defer f.lock.RUnlock() + + v, ok := f.data[name] if !ok { return 0, ErrValueNotSet } @@ -153,8 +233,50 @@ func (f FieldCollection) Int64(name string) (int64, error) { return 0, ErrValueMismatch } -func (f FieldCollection) String(name string) (string, error) { - v, ok := f[name] +// Set sets a single key to specified value +func (f *FieldCollection) Set(key string, value interface{}) { + if f == nil { + f = NewFieldCollection() + } + + f.lock.Lock() + defer f.lock.Unlock() + + if f.data == nil { + f.data = make(map[string]interface{}) + } + + f.data[key] = value +} + +// SetFromData takes a map of data and copies all data into the FieldCollection +func (f *FieldCollection) SetFromData(data map[string]interface{}) { + if f == nil { + f = NewFieldCollection() + } + + f.lock.Lock() + defer f.lock.Unlock() + + if f.data == nil { + f.data = make(map[string]interface{}) + } + + for key, value := range data { + f.data[key] = value + } +} + +// String tries to read key name as string +func (f *FieldCollection) String(name string) (string, error) { + if f == nil || f.data == nil { + return "", errors.New("uninitialized field collection") + } + + f.lock.RLock() + defer f.lock.RUnlock() + + v, ok := f.data[name] if !ok { return "", ErrValueNotSet } @@ -170,8 +292,16 @@ func (f FieldCollection) String(name string) (string, error) { return "", ErrValueMismatch } -func (f FieldCollection) StringSlice(name string) ([]string, error) { - v, ok := f[name] +// StringSlice tries to read key name as []string +func (f *FieldCollection) StringSlice(name string) ([]string, error) { + if f == nil || f.data == nil { + return nil, errors.New("uninitialized field collection") + } + + f.lock.RLock() + defer f.lock.RUnlock() + + v, ok := f.data[name] if !ok { return nil, ErrValueNotSet } @@ -196,3 +326,42 @@ func (f FieldCollection) StringSlice(name string) ([]string, error) { return nil, ErrValueMismatch } + +// Implement JSON marshalling to plain underlying map[string]interface{} + +func (f *FieldCollection) MarshalJSON() ([]byte, error) { + if f == nil || f.data == nil { + return []byte("{}"), nil + } + + f.lock.RLock() + defer f.lock.RUnlock() + + return json.Marshal(f.data) +} + +func (f *FieldCollection) UnmarshalJSON(raw []byte) error { + data := make(map[string]interface{}) + if err := json.Unmarshal(raw, &data); err != nil { + return errors.Wrap(err, "unmarshalling from JSON") + } + + f.SetFromData(data) + return nil +} + +// Implement YAML marshalling to plain underlying map[string]interface{} + +func (f *FieldCollection) MarshalYAML() (interface{}, error) { + return f.Data(), nil +} + +func (f *FieldCollection) UnmarshalYAML(unmarshal func(interface{}) error) error { + data := make(map[string]interface{}) + if err := unmarshal(&data); err != nil { + return errors.Wrap(err, "unmarshalling from YAML") + } + + f.SetFromData(data) + return nil +} diff --git a/plugins/fieldcollection_test.go b/plugins/fieldcollection_test.go new file mode 100644 index 0000000..6331cd1 --- /dev/null +++ b/plugins/fieldcollection_test.go @@ -0,0 +1,80 @@ +package plugins + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + "gopkg.in/yaml.v2" +) + +func TestFieldCollectionJSONMarshal(t *testing.T) { + var ( + buf = new(bytes.Buffer) + raw = `{"key1":"test1","key2":"test2"}` + f = NewFieldCollection() + ) + + if err := json.NewDecoder(strings.NewReader(raw)).Decode(f); err != nil { + t.Fatalf("Unable to unmarshal: %s", err) + } + + if err := json.NewEncoder(buf).Encode(f); err != nil { + t.Fatalf("Unable to marshal: %s", err) + } + + if raw != strings.TrimSpace(buf.String()) { + t.Errorf("Marshalled JSON does not match expectation: res=%s exp=%s", buf.String(), raw) + } +} + +func TestFieldCollectionYAMLMarshal(t *testing.T) { + var ( + buf = new(bytes.Buffer) + raw = "key1: test1\nkey2: test2" + f = NewFieldCollection() + ) + + if err := yaml.NewDecoder(strings.NewReader(raw)).Decode(f); err != nil { + t.Fatalf("Unable to unmarshal: %s", err) + } + + if err := yaml.NewEncoder(buf).Encode(f); err != nil { + t.Fatalf("Unable to marshal: %s", err) + } + + if raw != strings.TrimSpace(buf.String()) { + t.Errorf("Marshalled YAML does not match expectation: res=%s exp=%s", buf.String(), raw) + } +} + +func TestFieldCollectionNilModify(t *testing.T) { + var f *FieldCollection + + f.Set("foo", "bar") + + f = nil + f.SetFromData(map[string]interface{}{"foo": "bar"}) +} + +func TestFieldCollectionNilClone(t *testing.T) { + var f *FieldCollection + + f.Clone() +} + +func TestFieldCollectionNilDataGet(t *testing.T) { + var f *FieldCollection + + for name, fn := range map[string]func(name string) bool{ + "bool": f.CanBool, + "duration": f.CanDuration, + "int64": f.CanInt64, + "string": f.CanString, + } { + if fn("foo") { + t.Errorf("%s key is available", name) + } + } +} diff --git a/plugins/helpers.go b/plugins/helpers.go index b6552b4..90766f6 100644 --- a/plugins/helpers.go +++ b/plugins/helpers.go @@ -7,7 +7,7 @@ import ( "github.com/go-irc/irc" ) -func DeriveChannel(m *irc.Message, evtData FieldCollection) string { +func DeriveChannel(m *irc.Message, evtData *FieldCollection) string { if m != nil && len(m.Params) > 0 && strings.HasPrefix(m.Params[0], "#") { return m.Params[0] } @@ -19,7 +19,7 @@ func DeriveChannel(m *irc.Message, evtData FieldCollection) string { return "" } -func DeriveUser(m *irc.Message, evtData FieldCollection) string { +func DeriveUser(m *irc.Message, evtData *FieldCollection) string { if m != nil && m.User != "" { return m.User } diff --git a/plugins/interface.go b/plugins/interface.go index 6e6ada1..aa01d0a 100644 --- a/plugins/interface.go +++ b/plugins/interface.go @@ -10,7 +10,7 @@ import ( type ( Actor interface { // Execute will be called after the config was read into the Actor - Execute(c *irc.Client, m *irc.Message, r *Rule, evtData FieldCollection, attrs FieldCollection) (preventCooldown bool, err error) + Execute(c *irc.Client, m *irc.Message, r *Rule, evtData *FieldCollection, attrs *FieldCollection) (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 @@ -21,7 +21,7 @@ type ( // Validate will be called to validate the loaded configuration. It should // return an error if required keys are missing from the AttributeStore // or if keys contain broken configs - Validate(FieldCollection) error + Validate(*FieldCollection) error } ActorCreationFunc func() Actor @@ -34,7 +34,7 @@ type ( LoggerCreationFunc func(moduleName string) *log.Entry - MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields FieldCollection) (string, error) + MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields *FieldCollection) (string, error) RawMessageHandlerFunc func(m *irc.Message) error RawMessageHandlerRegisterFunc func(RawMessageHandlerFunc) error @@ -83,10 +83,10 @@ type ( UnmarshalStoredObject([]byte) error } - TemplateFuncGetter func(*irc.Message, *Rule, FieldCollection) interface{} + TemplateFuncGetter func(*irc.Message, *Rule, *FieldCollection) interface{} TemplateFuncRegister func(name string, fg TemplateFuncGetter) ) func GenericTemplateFunctionGetter(f interface{}) TemplateFuncGetter { - return func(*irc.Message, *Rule, FieldCollection) interface{} { return f } + return func(*irc.Message, *Rule, *FieldCollection) interface{} { return f } } diff --git a/plugins/rule.go b/plugins/rule.go index f04d3da..05b2626 100644 --- a/plugins/rule.go +++ b/plugins/rule.go @@ -49,8 +49,8 @@ type ( } RuleAction struct { - Type string `json:"type" yaml:"type,omitempty"` - Attributes FieldCollection `json:"attributes" yaml:"attributes,omitempty"` + Type string `json:"type" yaml:"type,omitempty"` + Attributes *FieldCollection `json:"attributes" yaml:"attributes,omitempty"` } ) @@ -66,7 +66,7 @@ func (r Rule) MatcherID() string { return fmt.Sprintf("hashstructure:%x", h) } -func (r *Rule) Matches(m *irc.Message, event *string, timerStore TimerStore, msgFormatter MsgFormatter, twitchClient *twitch.Client, eventData FieldCollection) bool { +func (r *Rule) Matches(m *irc.Message, event *string, timerStore TimerStore, msgFormatter MsgFormatter, twitchClient *twitch.Client, eventData *FieldCollection) bool { r.msgFormatter = msgFormatter r.timerStore = timerStore r.twitchClient = twitchClient @@ -79,7 +79,7 @@ func (r *Rule) Matches(m *irc.Message, event *string, timerStore TimerStore, msg }) ) - for _, matcher := range []func(*log.Entry, *irc.Message, *string, twitch.BadgeCollection, FieldCollection) bool{ + for _, matcher := range []func(*log.Entry, *irc.Message, *string, twitch.BadgeCollection, *FieldCollection) bool{ r.allowExecuteDisable, r.allowExecuteChannelWhitelist, r.allowExecuteUserWhitelist, @@ -117,7 +117,7 @@ func (r *Rule) GetMatchMessage() *regexp.Regexp { return r.matchMessage } -func (r *Rule) SetCooldown(timerStore TimerStore, m *irc.Message, evtData FieldCollection) { +func (r *Rule) SetCooldown(timerStore TimerStore, m *irc.Message, evtData *FieldCollection) { if r.Cooldown != nil { timerStore.AddCooldown(TimerTypeCooldown, "", r.MatcherID(), time.Now().Add(*r.Cooldown)) } @@ -131,7 +131,7 @@ func (r *Rule) SetCooldown(timerStore TimerStore, m *irc.Message, evtData FieldC } } -func (r *Rule) allowExecuteBadgeBlacklist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteBadgeBlacklist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { for _, b := range r.DisableOn { if badges.Has(b) { logger.Tracef("Non-Match: Disable-Badge %s", b) @@ -142,7 +142,7 @@ func (r *Rule) allowExecuteBadgeBlacklist(logger *log.Entry, m *irc.Message, eve return true } -func (r *Rule) allowExecuteBadgeWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteBadgeWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if len(r.EnableOn) == 0 { // No match criteria set, does not speak against matching return true @@ -157,7 +157,7 @@ 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 twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteChannelCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.ChannelCooldown == nil || DeriveChannel(m, evtData) == "" { // No match criteria set, does not speak against matching return true @@ -176,7 +176,7 @@ func (r *Rule) allowExecuteChannelCooldown(logger *log.Entry, m *irc.Message, ev return false } -func (r *Rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if len(r.MatchChannels) == 0 { // No match criteria set, does not speak against matching return true @@ -190,7 +190,7 @@ func (r *Rule) allowExecuteChannelWhitelist(logger *log.Entry, m *irc.Message, e return true } -func (r *Rule) allowExecuteDisable(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteDisable(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.Disable == nil { // No match criteria set, does not speak against matching return true @@ -204,7 +204,7 @@ func (r *Rule) allowExecuteDisable(logger *log.Entry, m *irc.Message, event *str return true } -func (r *Rule) allowExecuteDisableOnOffline(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteDisableOnOffline(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.DisableOnOffline == nil || !*r.DisableOnOffline || DeriveChannel(m, evtData) == "" { // No match criteria set, does not speak against matching return true @@ -223,7 +223,7 @@ func (r *Rule) allowExecuteDisableOnOffline(logger *log.Entry, m *irc.Message, e return true } -func (r *Rule) allowExecuteDisableOnPermit(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteDisableOnPermit(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.DisableOnPermit != nil && *r.DisableOnPermit && DeriveChannel(m, evtData) != "" && r.timerStore.HasPermit(DeriveChannel(m, evtData), DeriveUser(m, evtData)) { logger.Trace("Non-Match: Permit") return false @@ -232,7 +232,7 @@ func (r *Rule) allowExecuteDisableOnPermit(logger *log.Entry, m *irc.Message, ev return true } -func (r *Rule) allowExecuteDisableOnTemplate(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteDisableOnTemplate(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.DisableOnTemplate == nil || *r.DisableOnTemplate == "" { // No match criteria set, does not speak against matching return true @@ -253,7 +253,7 @@ func (r *Rule) allowExecuteDisableOnTemplate(logger *log.Entry, m *irc.Message, return true } -func (r *Rule) allowExecuteEventWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteEventWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.MatchEvent == nil || *r.MatchEvent == "" { // No match criteria set, does not speak against matching return true @@ -267,7 +267,7 @@ func (r *Rule) allowExecuteEventWhitelist(logger *log.Entry, m *irc.Message, eve return true } -func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if len(r.DisableOnMatchMessages) == 0 { // No match criteria set, does not speak against matching return true @@ -296,7 +296,7 @@ func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *log.Entry, m *irc.Mes return true } -func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.MatchMessage == nil { // No match criteria set, does not speak against matching return true @@ -321,7 +321,7 @@ 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 twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteRuleCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.Cooldown == nil { // No match criteria set, does not speak against matching return true @@ -340,7 +340,7 @@ func (r *Rule) allowExecuteRuleCooldown(logger *log.Entry, m *irc.Message, event return false } -func (r *Rule) allowExecuteUserCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteUserCooldown(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if r.UserCooldown == nil { // No match criteria set, does not speak against matching return true @@ -359,7 +359,7 @@ func (r *Rule) allowExecuteUserCooldown(logger *log.Entry, m *irc.Message, event return false } -func (r *Rule) allowExecuteUserWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData FieldCollection) bool { +func (r *Rule) allowExecuteUserWhitelist(logger *log.Entry, m *irc.Message, event *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { if len(r.MatchUsers) == 0 { // No match criteria set, does not speak against matching return true diff --git a/plugins/rule_test.go b/plugins/rule_test.go index 721334a..f485296 100644 --- a/plugins/rule_test.go +++ b/plugins/rule_test.go @@ -139,7 +139,7 @@ func TestAllowExecuteDisableOnTemplate(t *testing.T) { } { // We don't test the message formatter here but only the disable functionality // so we fake the result of the evaluation - r.msgFormatter = func(tplString string, m *irc.Message, r *Rule, fields FieldCollection) (string, error) { + r.msgFormatter = func(tplString string, m *irc.Message, r *Rule, fields *FieldCollection) (string, error) { return msg, nil } diff --git a/twitchWatcher.go b/twitchWatcher.go index 04ad558..28dbb35 100644 --- a/twitchWatcher.go +++ b/twitchWatcher.go @@ -196,10 +196,10 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o "channel": channel, "category": *category, }).Debug("Twitch metadata changed") - go handleMessage(ircHdl.Client(), nil, eventTypeTwitchCategoryUpdate, plugins.FieldCollection{ + go handleMessage(ircHdl.Client(), nil, eventTypeTwitchCategoryUpdate, plugins.FieldCollectionFromData(map[string]interface{}{ "channel": channel, "category": *category, - }) + })) } if title != nil && t.ChannelStatus[channel].Title != *title { @@ -208,10 +208,10 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o "channel": channel, "title": *title, }).Debug("Twitch metadata changed") - go handleMessage(ircHdl.Client(), nil, eventTypeTwitchTitleUpdate, plugins.FieldCollection{ + go handleMessage(ircHdl.Client(), nil, eventTypeTwitchTitleUpdate, plugins.FieldCollectionFromData(map[string]interface{}{ "channel": channel, "title": *title, - }) + })) } if online != nil && t.ChannelStatus[channel].IsLive != *online { @@ -226,8 +226,8 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o evt = eventTypeTwitchStreamOffline } - go handleMessage(ircHdl.Client(), nil, evt, plugins.FieldCollection{ + go handleMessage(ircHdl.Client(), nil, evt, plugins.FieldCollectionFromData(map[string]interface{}{ "channel": channel, - }) + })) } }