From 30482591a7ec045bebfa6a1b5c39e99376d672cd Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Wed, 3 Apr 2024 21:00:28 +0200 Subject: [PATCH] [core] Switch to go_helpers FieldCollection Signed-off-by: Knut Ahlers --- action_script.go | 5 +- actions.go | 9 +- automessage.go | 4 +- config.go | 3 +- events.go | 3 +- functions.go | 3 +- functions_irc.go | 9 +- go.mod | 12 +- go.sum | 28 +- internal/actors/ban/actor.go | 19 +- internal/actors/clip/actor.go | 21 +- internal/actors/clipdetector/actor.go | 7 +- internal/actors/commercial/actor.go | 19 +- internal/actors/counter/actor.go | 29 +- internal/actors/delay/actor.go | 11 +- internal/actors/delete/actor.go | 5 +- internal/actors/eventmod/actor.go | 19 +- internal/actors/filesay/actor.go | 19 +- internal/actors/linkdetector/actor.go | 12 +- internal/actors/linkprotect/actor.go | 62 +-- internal/actors/log/actor.go | 26 +- internal/actors/messagehook/actor.go | 7 +- internal/actors/messagehook/discord.go | 38 +- internal/actors/messagehook/slack.go | 8 +- internal/actors/modchannel/actor.go | 28 +- internal/actors/nuke/actor.go | 30 +- internal/actors/punish/actor.go | 58 +-- internal/actors/quotedb/actor.go | 46 +- internal/actors/raw/actor.go | 21 +- internal/actors/respond/actor.go | 27 +- internal/actors/shield/actor.go | 17 +- internal/actors/shoutout/actor.go | 18 +- internal/actors/stopexec/actor.go | 24 +- internal/actors/timeout/actor.go | 23 +- internal/actors/variables/actor.go | 21 +- internal/actors/vip/vip.go | 27 +- internal/actors/whisper/actor.go | 29 +- internal/apimodules/customevent/actor.go | 21 +- .../apimodules/customevent/customevent.go | 5 +- internal/apimodules/customevent/database.go | 4 +- internal/apimodules/kofi/kofi.go | 3 +- internal/apimodules/overlays/database.go | 4 +- internal/apimodules/overlays/database_test.go | 10 +- internal/apimodules/overlays/overlays.go | 15 +- internal/apimodules/raffle/actor.go | 15 +- internal/apimodules/raffle/database.go | 8 +- internal/apimodules/raffle/irc.go | 3 +- internal/helpers/ptr.go | 4 + internal/helpers/validateHelper.go | 21 + internal/template/userstate/actor.go | 3 +- irc.go | 15 +- msgformatter.go | 17 +- plugins/actorkit.go | 11 +- plugins/actorkit_test.go | 3 +- plugins/fieldcollection.go | 407 ------------------ plugins/fieldcollection_test.go | 93 ---- plugins/helpers.go | 5 +- plugins/interface.go | 15 +- plugins/moduleConfig.go | 12 +- plugins/moduleConfig_test.go | 7 +- plugins/rule.go | 39 +- plugins/rule_test.go | 3 +- plugins_core.go | 5 +- tplDocs.go | 3 +- twitchWatcher.go | 24 +- 65 files changed, 548 insertions(+), 974 deletions(-) create mode 100644 internal/helpers/ptr.go create mode 100644 internal/helpers/validateHelper.go delete mode 100644 plugins/fieldcollection.go delete mode 100644 plugins/fieldcollection_test.go diff --git a/action_script.go b/action_script.go index 4a34dc7..e22928d 100644 --- a/action_script.go +++ b/action_script.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -49,7 +50,7 @@ func init() { type ActorScript struct{} // Execute implements actor interface -func (ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { command, err := attrs.StringSlice("command") if err != nil { return false, errors.Wrap(err, "getting command") @@ -130,7 +131,7 @@ func (ActorScript) IsAsync() bool { return false } func (ActorScript) Name() string { return "script" } // Validate implements actor interface -func (ActorScript) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { +func (ActorScript) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { cmd, err := attrs.StringSlice("command") if err != nil || len(cmd) == 0 { return errors.New("command must be slice of strings with length > 0") diff --git a/actions.go b/actions.go index dce0ba1..e28d9d3 100644 --- a/actions.go +++ b/actions.go @@ -7,6 +7,7 @@ import ( log "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -41,7 +42,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 *fieldcollection.FieldCollection) (preventCooldown bool, err error) { availableActionsLock.RLock() defer availableActionsLock.RUnlock() @@ -65,7 +66,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 *fieldcollection.FieldCollection) { // Send events to registered handlers if event != nil { go notifyEventHandlers(*event, eventData) @@ -77,9 +78,9 @@ func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *plug } } -func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection) { +func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection) { var ( - ruleEventData = plugins.NewFieldCollection() + ruleEventData = fieldcollection.NewFieldCollection() preventCooldown bool ) diff --git a/automessage.go b/automessage.go index 80b4f3f..e4f0acb 100644 --- a/automessage.go +++ b/automessage.go @@ -13,8 +13,8 @@ import ( log "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" - "github.com/Luzifer/twitch-bot/v3/plugins" ) var cronParser = cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) @@ -174,7 +174,7 @@ func (a *autoMessage) allowExecuteDisableOnTemplate() bool { return true } - fields := plugins.NewFieldCollection() + fields := fieldcollection.NewFieldCollection() fields.Set("channel", a.Channel) res, err := formatMessage(*a.DisableOnTemplate, nil, nil, fields) diff --git a/config.go b/config.go index 51dce7d..a449161 100644 --- a/config.go +++ b/config.go @@ -19,6 +19,7 @@ import ( "gopkg.in/irc.v4" "gopkg.in/yaml.v3" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -303,7 +304,7 @@ func (c *configFile) CloseRawMessageWriter() (err error) { return nil } -func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData *plugins.FieldCollection) []*plugins.Rule { +func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData *fieldcollection.FieldCollection) []*plugins.Rule { configLock.RLock() defer configLock.RUnlock() diff --git a/events.go b/events.go index 2282b30..f876baa 100644 --- a/events.go +++ b/events.go @@ -5,6 +5,7 @@ import ( log "github.com/sirupsen/logrus" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -93,7 +94,7 @@ var ( } ) -func notifyEventHandlers(event string, eventData *plugins.FieldCollection) { +func notifyEventHandlers(event string, eventData *fieldcollection.FieldCollection) { registeredEventHandlersLock.Lock() defer registeredEventHandlersLock.Unlock() diff --git a/functions.go b/functions.go index 4f10696..67a0f90 100644 --- a/functions.go +++ b/functions.go @@ -11,6 +11,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" korvike "github.com/Luzifer/korvike/functions" "github.com/Luzifer/twitch-bot/v3/plugins" @@ -37,7 +38,7 @@ func newTemplateFuncProvider() *templateFuncProvider { return out } -func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) template.FuncMap { +func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, fields *fieldcollection.FieldCollection) template.FuncMap { t.lock.RLock() defer t.lock.RUnlock() diff --git a/functions_irc.go b/functions_irc.go index 98d845b..fefda52 100644 --- a/functions_irc.go +++ b/functions_irc.go @@ -6,12 +6,13 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) func init() { - tplFuncs.Register("arg", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} { + tplFuncs.Register("arg", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} { return func(arg int) (string, error) { msgParts := strings.Split(m.Trailing(), " ") if len(msgParts) <= arg { @@ -30,7 +31,7 @@ func init() { }, }) - tplFuncs.Register("chatterHasBadge", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} { + tplFuncs.Register("chatterHasBadge", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} { return func(badge string) bool { badges := twitch.ParseBadgeLevels(m) return badges.Has(badge) @@ -57,7 +58,7 @@ func init() { }, ) - tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, _ *plugins.FieldCollection) interface{} { + tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} { return func(idx int, fallback ...string) (string, error) { fields := r.GetMatchMessage().FindStringSubmatch(m.Trailing()) if len(fields) <= idx { @@ -94,7 +95,7 @@ func init() { }, ) - tplFuncs.Register("tag", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} { + tplFuncs.Register("tag", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} { return func(tag string) string { return m.Tags[tag] } }, plugins.TemplateFuncDocumentation{ Description: "Takes the message sent to the channel, returns the value of the tag specified", diff --git a/go.mod b/go.mod index 3a3ba27..17c22e1 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,19 @@ go 1.21 require ( github.com/Luzifer/go-openssl/v4 v4.2.2 - github.com/Luzifer/go_helpers/v2 v2.23.0 + github.com/Luzifer/go_helpers/v2 v2.24.0 github.com/Luzifer/korvike/functions v0.11.0 github.com/Luzifer/rconfig/v2 v2.5.0 github.com/Masterminds/sprig/v3 v3.2.3 github.com/getsentry/sentry-go v0.27.0 github.com/glebarez/sqlite v1.11.0 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.12.0 github.com/go-sql-driver/mysql v1.8.1 github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid/v3 v3.1.2 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 - github.com/itchyny/gojq v0.12.14 + github.com/itchyny/gojq v0.12.15 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/orandin/sentrus v1.0.0 github.com/pkg/errors v0.9.1 @@ -31,7 +31,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.6 gorm.io/driver/postgres v1.5.7 - gorm.io/gorm v1.25.8 + gorm.io/gorm v1.25.9 ) require ( @@ -87,7 +87,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -105,6 +105,6 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect modernc.org/libc v1.49.0 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect + modernc.org/memory v1.8.0 // indirect modernc.org/sqlite v1.29.5 // indirect ) diff --git a/go.sum b/go.sum index 4e0c947..cf71791 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Luzifer/go-openssl/v4 v4.2.2 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc= github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc= -github.com/Luzifer/go_helpers/v2 v2.23.0 h1:VowDwOCl6nOt+GVqKUX/do6a94pEeqNTRHb29MsoGX4= -github.com/Luzifer/go_helpers/v2 v2.23.0/go.mod h1:BSGkJ/dxqs7AxsfZt8zjJb4R6YB5dONS+/ad7foLUrk= +github.com/Luzifer/go_helpers/v2 v2.24.0 h1:abACOhsn6a6c6X22jq42mZM1wuOM0Ihfa6yzssrjrOg= +github.com/Luzifer/go_helpers/v2 v2.24.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc= github.com/Luzifer/korvike/functions v0.11.0 h1:2hr3nnt9hy8Esu1W3h50+RggcLRXvrw92kVQLvxzd2Q= github.com/Luzifer/korvike/functions v0.11.0/go.mod h1:osumwH64mWgbwZIfE7rE0BB7Y5HXxrzyO4JfO7fhduU= github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok= @@ -62,8 +62,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -72,8 +72,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= @@ -161,8 +161,8 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/itchyny/gojq v0.12.14 h1:6k8vVtsrhQSYgSGg827AD+PVVaB1NLXEdX+dda2oZCc= -github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s= +github.com/itchyny/gojq v0.12.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI= +github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -245,8 +245,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -422,8 +422,8 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= -gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= +gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/cc/v4 v4.19.5 h1:QlsZyQ1zf78DGeqnQ9ILi9hXyMdoC5e1qoGNUyBjHQw= @@ -438,8 +438,8 @@ modernc.org/libc v1.49.0 h1:/kkNBuCXvlTbOGwrQdgR67eK1Y9+kR+fhdBd89C64VM= modernc.org/libc v1.49.0/go.mod h1:DNz0lgQgT6FPIPm8rHtjFj0FL5/YOr/NYFXWYBcSxMw= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= diff --git a/internal/actors/ban/actor.go b/internal/actors/ban/actor.go index 5f7f8bd..918be39 100644 --- a/internal/actors/ban/actor.go +++ b/internal/actors/ban/actor.go @@ -11,6 +11,8 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -87,7 +89,7 @@ func Register(args plugins.RegistrationArguments) (err error) { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { ptrStringEmpty := func(v string) *string { return &v }("") reason, err := formatMessage(attrs.MustString("reason", ptrStringEmpty), m, r, eventData) @@ -110,14 +112,13 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - reasonTemplate, err := attrs.String("reason") - if err != nil || reasonTemplate == "" { - return errors.New("reason must be non-empty string") - } - - if err = tplValidator(reasonTemplate); err != nil { - return errors.Wrap(err, "validating reason template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.MustHaveNoUnknowFields, + helpers.SchemaValidateTemplateField(tplValidator, "reason"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/clip/actor.go b/internal/actors/clip/actor.go index af55261..a1490f1 100644 --- a/internal/actors/clip/actor.go +++ b/internal/actors/clip/actor.go @@ -9,6 +9,8 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -19,9 +21,6 @@ var ( formatMessage plugins.MsgFormatter hasPerm plugins.ChannelPermissionCheckFunc tcGetter func(string) (*twitch.Client, error) - - ptrBoolFalse = func(v bool) *bool { return &v }(false) - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -71,7 +70,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { channel := plugins.DeriveChannel(m, eventData) if channel, err = formatMessage(attrs.MustString("channel", &channel), m, r, eventData); err != nil { return false, errors.Wrap(err, "parsing channel") @@ -96,7 +95,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * return false, errors.Wrapf(err, "getting Twitch client for %q", creator) } - clipInfo, err := tc.CreateClip(context.TODO(), channel, attrs.MustBool("add_delay", ptrBoolFalse)) + clipInfo, err := tc.CreateClip(context.TODO(), channel, attrs.MustBool("add_delay", helpers.Ptr(false))) if err != nil { return false, errors.Wrap(err, "creating clip") } @@ -110,11 +109,13 @@ func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - for _, field := range []string{"channel", "creator"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "creator", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "channel", "creator"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/clipdetector/actor.go b/internal/actors/clipdetector/actor.go index ae03e87..10251ad 100644 --- a/internal/actors/clipdetector/actor.go +++ b/internal/actors/clipdetector/actor.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/internal/actors/linkdetector" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" @@ -40,7 +41,7 @@ func Register(args plugins.RegistrationArguments) error { type Actor struct{} // Execute implements the actor interface -func (Actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (Actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { if eventData.HasAll("clips") { // We already detected clips, lets not do it again return false, nil @@ -82,4 +83,6 @@ func (Actor) IsAsync() bool { return false } func (Actor) Name() string { return actorName } // Validate implements the actor interface -func (Actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) error { return nil } +func (Actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) error { + return nil +} diff --git a/internal/actors/commercial/actor.go b/internal/actors/commercial/actor.go index 10aa2f8..ad6f797 100644 --- a/internal/actors/commercial/actor.go +++ b/internal/actors/commercial/actor.go @@ -3,6 +3,7 @@ package commercial import ( "context" + "fmt" "regexp" "strconv" "strings" @@ -10,6 +11,8 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -61,7 +64,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { ptrStringEmpty := func(v string) *string { return &v }("") durationStr, err := formatMessage(attrs.MustString("duration", ptrStringEmpty), m, r, eventData) @@ -75,14 +78,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - durationTemplate, err := attrs.String("duration") - if err != nil || durationTemplate == "" { - return errors.New("duration must be non-empty string") - } - - if err = tplValidator(durationTemplate); err != nil { - return errors.Wrap(err, "validating duration template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "duration", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "duration"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/counter/actor.go b/internal/actors/counter/actor.go index eb06c05..5c4e832 100644 --- a/internal/actors/counter/actor.go +++ b/internal/actors/counter/actor.go @@ -13,6 +13,8 @@ import ( "gopkg.in/irc.v4" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -20,8 +22,6 @@ import ( var ( db database.Connector formatMessage plugins.MsgFormatter - - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -135,7 +135,7 @@ func Register(args plugins.RegistrationArguments) (err error) { return fmt.Errorf("registering API route: %w", err) } - args.RegisterTemplateFunction("channelCounter", func(_ *irc.Message, _ *plugins.Rule, fields *plugins.FieldCollection) interface{} { + args.RegisterTemplateFunction("channelCounter", func(_ *irc.Message, _ *plugins.Rule, fields *fieldcollection.FieldCollection) interface{} { return func(name string) (string, error) { channel, err := fields.String("channel") if err != nil { @@ -212,13 +212,13 @@ func Register(args plugins.RegistrationArguments) (err error) { type actorCounter struct{} -func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.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") } - if counterSet := attrs.MustString("counter_set", ptrStringEmpty); counterSet != "" { + if counterSet := attrs.MustString("counter_set", helpers.Ptr("")); counterSet != "" { parseValue, err := formatMessage(counterSet, m, r, eventData) if err != nil { return false, errors.Wrap(err, "execute counter value template") @@ -236,7 +236,7 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even } var counterStep int64 = 1 - if s := attrs.MustString("counter_step", ptrStringEmpty); s != "" { + if s := attrs.MustString("counter_step", helpers.Ptr("")); s != "" { parseStep, err := formatMessage(s, m, r, eventData) if err != nil { return false, errors.Wrap(err, "execute counter step template") @@ -257,15 +257,14 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even func (actorCounter) IsAsync() bool { return false } func (actorCounter) Name() string { return "counter" } -func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if cn, err := attrs.String("counter"); err != nil || cn == "" { - return errors.New("counter name must be non-empty string") - } - - for _, field := range []string{"counter", "counter_step", "counter_set"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "counter", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "counter_step", Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "counter_set", Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "counter", "counter_step", "counter_set"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/delay/actor.go b/internal/actors/delay/actor.go index 827495d..5fdea17 100644 --- a/internal/actors/delay/actor.go +++ b/internal/actors/delay/actor.go @@ -7,6 +7,8 @@ import ( "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -48,11 +50,10 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { var ( - ptrZeroDuration = func(v time.Duration) *time.Duration { return &v }(0) - delay = attrs.MustDuration("delay", ptrZeroDuration) - jitter = attrs.MustDuration("jitter", ptrZeroDuration) + delay = attrs.MustDuration("delay", helpers.Ptr(time.Duration(0))) + jitter = attrs.MustDuration("jitter", helpers.Ptr(time.Duration(0))) ) if delay == 0 && jitter == 0 { @@ -71,6 +72,6 @@ func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugins. func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) { +func (actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) (err error) { return nil } diff --git a/internal/actors/delete/actor.go b/internal/actors/delete/actor.go index f41d33d..5052fe1 100644 --- a/internal/actors/delete/actor.go +++ b/internal/actors/delete/actor.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -32,7 +33,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, _ *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *fieldcollection.FieldCollection, _ *fieldcollection.FieldCollection) (preventCooldown bool, err error) { msgID, ok := m.Tags["id"] if !ok || msgID == "" { return false, nil @@ -51,6 +52,6 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) { +func (actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) (err error) { return nil } diff --git a/internal/actors/eventmod/actor.go b/internal/actors/eventmod/actor.go index b0c2990..ddea0c3 100644 --- a/internal/actors/eventmod/actor.go +++ b/internal/actors/eventmod/actor.go @@ -4,10 +4,13 @@ package eventmod import ( "encoding/json" + "fmt" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -44,7 +47,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { ptrStringEmpty := func(v string) *string { return &v }("") fd, err := formatMessage(attrs.MustString("fields", ptrStringEmpty), m, r, eventData) @@ -69,14 +72,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - fieldsTemplate, err := attrs.String("fields") - if err != nil || fieldsTemplate == "" { - return errors.New("fields must be non-empty string") - } - - if err = tplValidator(fieldsTemplate); err != nil { - return errors.Wrap(err, "validating fields template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "fields", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "fields"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/filesay/actor.go b/internal/actors/filesay/actor.go index 734447e..2b456b9 100644 --- a/internal/actors/filesay/actor.go +++ b/internal/actors/filesay/actor.go @@ -5,6 +5,7 @@ package filesay import ( "bufio" "context" + "fmt" "net/http" "net/url" "time" @@ -13,6 +14,8 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -57,7 +60,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { ptrStringEmpty := func(v string) *string { return &v }("") source, err := formatMessage(attrs.MustString("source", ptrStringEmpty), m, r, eventData) @@ -114,14 +117,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return true } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) error { - sourceTpl, err := attrs.String("source") - if err != nil || sourceTpl == "" { - return errors.New("source is expected to be non-empty string") - } - - if err = tplValidator(sourceTpl); err != nil { - return errors.Wrap(err, "validating source template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "source", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "source"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/linkdetector/actor.go b/internal/actors/linkdetector/actor.go index 686d638..150e167 100644 --- a/internal/actors/linkdetector/actor.go +++ b/internal/actors/linkdetector/actor.go @@ -5,14 +5,14 @@ package linkdetector import ( "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/internal/linkcheck" "github.com/Luzifer/twitch-bot/v3/plugins" ) const actorName = "linkdetector" -var ptrFalse = func(v bool) *bool { return &v }(false) - // Register provides the plugins.RegisterFunc func Register(args plugins.RegistrationArguments) error { args.RegisterActor(actorName, func() plugins.Actor { return &Actor{} }) @@ -42,13 +42,13 @@ func Register(args plugins.RegistrationArguments) error { type Actor struct{} // Execute implements the actor interface -func (Actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (Actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { if eventData.HasAll("links") { // We already detected links, lets not do it again return false, nil } - if attrs.MustBool("heuristic", ptrFalse) { + if attrs.MustBool("heuristic", helpers.Ptr(false)) { eventData.Set("links", linkcheck.New().HeuristicScanForLinks(m.Trailing())) } else { eventData.Set("links", linkcheck.New().ScanForLinks(m.Trailing())) @@ -64,4 +64,6 @@ func (Actor) IsAsync() bool { return false } func (Actor) Name() string { return actorName } // Validate implements the actor interface -func (Actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) error { return nil } +func (Actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) error { + return nil +} diff --git a/internal/actors/linkprotect/actor.go b/internal/actors/linkprotect/actor.go index b19ab79..bf6f596 100644 --- a/internal/actors/linkprotect/actor.go +++ b/internal/actors/linkprotect/actor.go @@ -4,6 +4,7 @@ package linkprotect import ( "context" + "fmt" "regexp" "strings" "time" @@ -11,7 +12,9 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/internal/actors/clipdetector" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -21,8 +24,6 @@ const actorName = "linkprotect" var ( botTwitchClient *twitch.Client clipLink = regexp.MustCompile(`.*(?:clips\.twitch\.tv|www\.twitch\.tv/[^/]*/clip)/.*`) - ptrBoolFalse = func(v bool) *bool { return &v }(false) - ptrStringEmpty = func(v string) *string { return &v }("") ) // Register provides the plugins.RegisterFunc @@ -127,7 +128,7 @@ const ( ) //nolint:gocyclo // Minimum over the limit, makes no sense to split -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 *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { // In case the clip detector did not run before, lets run it now if preventCooldown, err = (clipdetector.Actor{}).Execute(c, m, r, eventData, attrs); err != nil { return preventCooldown, errors.Wrap(err, "detecting links / clips") @@ -141,13 +142,13 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData if len(links) == 0 { // If there are no links there is nothing to protect and there // are also no clips as they are parsed from the links - if attrs.MustBool("stop_on_no_action", ptrBoolFalse) { + if attrs.MustBool("stop_on_no_action", helpers.Ptr(false)) { return false, plugins.ErrStopRuleExecution } return false, nil } - clipsInterface, err := eventData.Any("clips") + clipsInterface, err := eventData.Get("clips") if err != nil { return preventCooldown, errors.Wrap(err, "getting clips from event") } @@ -157,21 +158,21 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData } if a.check(links, clips, attrs) == verdictAllFine { - if attrs.MustBool("stop_on_no_action", ptrBoolFalse) { + if attrs.MustBool("stop_on_no_action", helpers.Ptr(false)) { return false, plugins.ErrStopRuleExecution } return false, nil } // That message misbehaved so we need to punish them - switch lt := attrs.MustString("action", ptrStringEmpty); lt { + switch lt := attrs.MustString("action", helpers.Ptr("")); lt { case "ban": if err = botTwitchClient.BanUser( context.Background(), plugins.DeriveChannel(m, eventData), strings.TrimLeft(plugins.DeriveUser(m, eventData), "@"), 0, - attrs.MustString("reason", ptrStringEmpty), + attrs.MustString("reason", helpers.Ptr("")), ); err != nil { return false, errors.Wrap(err, "executing user ban") } @@ -201,13 +202,13 @@ func (a actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData plugins.DeriveChannel(m, eventData), strings.TrimLeft(plugins.DeriveUser(m, eventData), "@"), to, - attrs.MustString("reason", ptrStringEmpty), + attrs.MustString("reason", helpers.Ptr("")), ); err != nil { return false, errors.Wrap(err, "executing user ban") } } - if attrs.MustBool("stop_on_action", ptrBoolFalse) { + if attrs.MustBool("stop_on_action", helpers.Ptr(false)) { return false, plugins.ErrStopRuleExecution } @@ -218,41 +219,42 @@ func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) error { - if v, err := attrs.String("action"); err != nil || v == "" { - return errors.New("action must be non-empty string") - } - - if v, err := attrs.String("reason"); err != nil || v == "" { - return errors.New("reason must be non-empty string") - } - - if len(attrs.MustStringSlice("allowed_links"))+ - len(attrs.MustStringSlice("disallowed_links"))+ - len(attrs.MustStringSlice("allowed_clip_channels"))+ - len(attrs.MustStringSlice("disallowed_clip_channels")) == 0 { - return errors.New("no conditions are provided") +func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "action", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + func(attrs, _ *fieldcollection.FieldCollection) error { + if len(attrs.MustStringSlice("allowed_links", helpers.Ptr([]string{})))+ + len(attrs.MustStringSlice("disallowed_links", helpers.Ptr([]string{})))+ + len(attrs.MustStringSlice("allowed_clip_channels", helpers.Ptr([]string{})))+ + len(attrs.MustStringSlice("disallowed_clip_channels", helpers.Ptr([]string{}))) == 0 { + return errors.New("no conditions are provided") + } + return nil + }, + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil } -func (a actor) check(links []string, clips []twitch.ClipInfo, attrs *plugins.FieldCollection) (v verdict) { - hasClipDefinition := len(attrs.MustStringSlice("allowed_clip_channels"))+len(attrs.MustStringSlice("disallowed_clip_channels")) > 0 +func (a actor) check(links []string, clips []twitch.ClipInfo, attrs *fieldcollection.FieldCollection) (v verdict) { + hasClipDefinition := len(attrs.MustStringSlice("allowed_clip_channels", helpers.Ptr([]string{})))+len(attrs.MustStringSlice("disallowed_clip_channels", helpers.Ptr([]string{}))) > 0 - if v = a.checkLinkDenied(attrs.MustStringSlice("disallowed_links"), links, hasClipDefinition); v == verdictMisbehave { + if v = a.checkLinkDenied(attrs.MustStringSlice("disallowed_links", helpers.Ptr([]string{})), links, hasClipDefinition); v == verdictMisbehave { return verdictMisbehave } - if v = a.checkAllLinksAllowed(attrs.MustStringSlice("allowed_links"), links, hasClipDefinition); v == verdictMisbehave { + if v = a.checkAllLinksAllowed(attrs.MustStringSlice("allowed_links", helpers.Ptr([]string{})), links, hasClipDefinition); v == verdictMisbehave { return verdictMisbehave } - if v = a.checkClipChannelDenied(attrs.MustStringSlice("disallowed_clip_channels"), clips); v == verdictMisbehave { + if v = a.checkClipChannelDenied(attrs.MustStringSlice("disallowed_clip_channels", helpers.Ptr([]string{})), clips); v == verdictMisbehave { return verdictMisbehave } - if v = a.checkAllClipChannelsAllowed(attrs.MustStringSlice("allowed_clip_channels"), clips); v == verdictMisbehave { + if v = a.checkAllClipChannelsAllowed(attrs.MustStringSlice("allowed_clip_channels", helpers.Ptr([]string{})), clips); v == verdictMisbehave { return verdictMisbehave } diff --git a/internal/actors/log/actor.go b/internal/actors/log/actor.go index af62c93..45939c5 100644 --- a/internal/actors/log/actor.go +++ b/internal/actors/log/actor.go @@ -2,18 +2,19 @@ package log import ( + "fmt" + "github.com/pkg/errors" "gopkg.in/irc.v4" "github.com/sirupsen/logrus" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) -var ( - formatMessage plugins.MsgFormatter - ptrStringEmpty = func(v string) *string { return &v }("") -) +var formatMessage plugins.MsgFormatter // Register provides the plugins.RegisterFunc func Register(args plugins.RegistrationArguments) error { @@ -44,8 +45,8 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { - message, err := formatMessage(attrs.MustString("message", ptrStringEmpty), m, r, eventData) +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { + message, err := formatMessage(attrs.MustString("message", helpers.Ptr("")), m, r, eventData) if err != nil { return false, errors.Wrap(err, "executing message template") } @@ -61,13 +62,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return true } func (actor) Name() string { return "log" } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("message"); err != nil || v == "" { - return errors.New("message must be non-empty string") - } - - if err = tplValidator(attrs.MustString("message", ptrStringEmpty)); err != nil { - return errors.Wrap(err, "validating message template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "message", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "message"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/messagehook/actor.go b/internal/actors/messagehook/actor.go index 627503f..29eea62 100644 --- a/internal/actors/messagehook/actor.go +++ b/internal/actors/messagehook/actor.go @@ -20,12 +20,7 @@ const ( postTimeout = 5 * time.Second ) -var ( - formatMessage plugins.MsgFormatter - - ptrBoolFalse = func(v bool) *bool { return &v }(false) - ptrStringEmpty = func(s string) *string { return &s }("") -) +var formatMessage plugins.MsgFormatter // Register provides the plugins.RegisterFunc func Register(args plugins.RegistrationArguments) error { diff --git a/internal/actors/messagehook/discord.go b/internal/actors/messagehook/discord.go index f80eb06..c39bfa2 100644 --- a/internal/actors/messagehook/discord.go +++ b/internal/actors/messagehook/discord.go @@ -7,6 +7,8 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -50,18 +52,18 @@ type ( } ) -func (d discordActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (d discordActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { var payload discordPayload - if payload.Content, err = formatMessage(attrs.MustString("content", ptrStringEmpty), m, r, eventData); err != nil { + if payload.Content, err = formatMessage(attrs.MustString("content", helpers.Ptr("")), m, r, eventData); err != nil { return false, errors.Wrap(err, "parsing content") } - if payload.Username, err = formatMessage(attrs.MustString("username", ptrStringEmpty), m, r, eventData); err != nil { + if payload.Username, err = formatMessage(attrs.MustString("username", helpers.Ptr("")), m, r, eventData); err != nil { return false, errors.Wrap(err, "parsing username") } - if payload.AvatarURL, err = formatMessage(attrs.MustString("avatar_url", ptrStringEmpty), m, r, eventData); err != nil { + if payload.AvatarURL, err = formatMessage(attrs.MustString("avatar_url", helpers.Ptr("")), m, r, eventData); err != nil { return false, errors.Wrap(err, "parsing avatar_url") } @@ -69,14 +71,14 @@ func (d discordActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, ev return false, err } - return sendPayload(attrs.MustString("hook_url", ptrStringEmpty), payload, http.StatusNoContent) + return sendPayload(attrs.MustString("hook_url", helpers.Ptr("")), payload, http.StatusNoContent) } func (discordActor) IsAsync() bool { return false } func (discordActor) Name() string { return "discordhook" } -func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { +func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { if err = d.ValidateRequireNonEmpty(attrs, "hook_url"); err != nil { return err //nolint:wrapcheck } @@ -89,7 +91,7 @@ func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs return err //nolint:wrapcheck } - if !attrs.MustBool("add_embed", ptrBoolFalse) { + if !attrs.MustBool("add_embed", helpers.Ptr(false)) { // We're not validating the rest if embeds are disabled but in // this case the content is mandatory return d.ValidateRequireNonEmpty(attrs, "content") //nolint:wrapcheck @@ -111,8 +113,8 @@ func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs } //nolint:gocyclo // It's complex but just a bunch of converters -func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (err error) { - if !attrs.MustBool("add_embed", ptrBoolFalse) { +func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (err error) { + if !attrs.MustBool("add_embed", helpers.Ptr(false)) { // No embed? No problem! return nil } @@ -122,45 +124,45 @@ func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins sv string ) - if embed.Title, err = formatMessage(attrs.MustString("embed_title", ptrStringEmpty), m, r, eventData); err != nil { + if embed.Title, err = formatMessage(attrs.MustString("embed_title", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_title") } - if embed.Description, err = formatMessage(attrs.MustString("embed_description", ptrStringEmpty), m, r, eventData); err != nil { + if embed.Description, err = formatMessage(attrs.MustString("embed_description", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_description") } - if embed.URL, err = formatMessage(attrs.MustString("embed_url", ptrStringEmpty), m, r, eventData); err != nil { + if embed.URL, err = formatMessage(attrs.MustString("embed_url", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_url") } - if sv, err = formatMessage(attrs.MustString("embed_image", ptrStringEmpty), m, r, eventData); err != nil { + if sv, err = formatMessage(attrs.MustString("embed_image", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_image") } else if sv != "" { embed.Image = &discordPayloadEmbedImage{URL: sv} } - if sv, err = formatMessage(attrs.MustString("embed_thumbnail", ptrStringEmpty), m, r, eventData); err != nil { + if sv, err = formatMessage(attrs.MustString("embed_thumbnail", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_thumbnail") } else if sv != "" { embed.Thumbnail = &discordPayloadEmbedImage{URL: sv} } - if sv, err = formatMessage(attrs.MustString("embed_author_name", ptrStringEmpty), m, r, eventData); err != nil { + if sv, err = formatMessage(attrs.MustString("embed_author_name", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_author_name") } else if sv != "" { embed.Author = &discordPayloadEmbedAuthor{Name: sv} - if embed.Author.URL, err = formatMessage(attrs.MustString("embed_author_url", ptrStringEmpty), m, r, eventData); err != nil { + if embed.Author.URL, err = formatMessage(attrs.MustString("embed_author_url", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_author_url") } - if embed.Author.IconURL, err = formatMessage(attrs.MustString("embed_author_icon_url", ptrStringEmpty), m, r, eventData); err != nil { + if embed.Author.IconURL, err = formatMessage(attrs.MustString("embed_author_icon_url", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_author_icon_url") } } - if sv, err = formatMessage(attrs.MustString("embed_fields", ptrStringEmpty), m, r, eventData); err != nil { + if sv, err = formatMessage(attrs.MustString("embed_fields", helpers.Ptr("")), m, r, eventData); err != nil { return errors.Wrap(err, "parsing embed_fields") } else if sv != "" { var flds []discordPayloadEmbedField diff --git a/internal/actors/messagehook/slack.go b/internal/actors/messagehook/slack.go index 9bab6b1..a214265 100644 --- a/internal/actors/messagehook/slack.go +++ b/internal/actors/messagehook/slack.go @@ -7,6 +7,8 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -14,14 +16,14 @@ type slackCompatibleActor struct { plugins.ActorKit } -func (s slackCompatibleActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (s slackCompatibleActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { text, err := formatMessage(attrs.MustString("text", nil), m, r, eventData) if err != nil { return false, errors.Wrap(err, "parsing text") } return sendPayload( - s.fixHookURL(attrs.MustString("hook_url", ptrStringEmpty)), + s.fixHookURL(attrs.MustString("hook_url", helpers.Ptr(""))), map[string]string{ "text": text, }, @@ -33,7 +35,7 @@ func (slackCompatibleActor) IsAsync() bool { return false } func (slackCompatibleActor) Name() string { return "slackhook" } -func (s slackCompatibleActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { +func (s slackCompatibleActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { if err = s.ValidateRequireNonEmpty(attrs, "hook_url", "text"); err != nil { return err //nolint:wrapcheck } diff --git a/internal/actors/modchannel/actor.go b/internal/actors/modchannel/actor.go index b1564aa..8d0304f 100644 --- a/internal/actors/modchannel/actor.go +++ b/internal/actors/modchannel/actor.go @@ -4,11 +4,14 @@ package modchannel import ( "context" + "fmt" "strings" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -18,8 +21,6 @@ const actorName = "modchannel" var ( formatMessage plugins.MsgFormatter tcGetter func(string) (*twitch.Client, error) - - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -70,10 +71,10 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { var ( - game = attrs.MustString("game", ptrStringEmpty) - title = attrs.MustString("title", ptrStringEmpty) + game = attrs.MustString("game", helpers.Ptr("")) + title = attrs.MustString("title", helpers.Ptr("")) ) if game == "" && title == "" { @@ -119,15 +120,14 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("channel"); err != nil || v == "" { - return errors.New("channel must be non-empty string") - } - - for _, field := range []string{"channel", "game", "title"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "game", Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "title", Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "channel", "game", "title"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/nuke/actor.go b/internal/actors/nuke/actor.go index 0b262ba..bf44df8 100644 --- a/internal/actors/nuke/actor.go +++ b/internal/actors/nuke/actor.go @@ -4,6 +4,7 @@ package nuke import ( + "fmt" "regexp" "strings" "sync" @@ -13,7 +14,9 @@ import ( log "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -29,10 +32,6 @@ var ( messageStore = map[string][]*storedMessage{} messageStoreLock sync.RWMutex - - ptrStringDelete = func(v string) *string { return &v }("delete") - ptrStringEmpty = func(s string) *string { return &s }("") - ptrString10m = func(v string) *string { return &v }("10m") ) // Register provides the plugins.RegisterFunc @@ -150,14 +149,14 @@ type ( } ) -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.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") } match := regexp.MustCompile(rawMatch) - rawScan, err := formatMessage(attrs.MustString("scan", ptrString10m), m, r, eventData) + rawScan, err := formatMessage(attrs.MustString("scan", helpers.Ptr("10m")), m, r, eventData) if err != nil { return false, errors.Wrap(err, "formatting scan duration") } @@ -171,7 +170,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * action actionFn actionName string ) - rawAction, err := formatMessage(attrs.MustString("action", ptrStringDelete), m, r, eventData) + rawAction, err := formatMessage(attrs.MustString("action", helpers.Ptr("delete")), m, r, eventData) if err != nil { return false, errors.Wrap(err, "formatting action") } @@ -235,15 +234,14 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("match"); err != nil || v == "" { - return errors.New("match must be non-empty string") - } - - for _, field := range []string{"scan", "action", "match"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "match", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "action", Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "scan", Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "scan", "action", "match"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/punish/actor.go b/internal/actors/punish/actor.go index 6b27e15..eacb894 100644 --- a/internal/actors/punish/actor.go +++ b/internal/actors/punish/actor.go @@ -4,6 +4,7 @@ package punish import ( "context" + "fmt" "math" "strings" "time" @@ -12,6 +13,8 @@ import ( "gopkg.in/irc.v4" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" @@ -25,11 +28,9 @@ const ( ) var ( - botTwitchClient *twitch.Client - db database.Connector - formatMessage plugins.MsgFormatter - ptrDefaultCooldown = func(v time.Duration) *time.Duration { return &v }(oneWeek) - ptrStringEmpty = func(v string) *string { return &v }("") + botTwitchClient *twitch.Client + db database.Connector + formatMessage plugins.MsgFormatter ) // Register provides the plugins.RegisterFunc @@ -146,12 +147,12 @@ type ( // Punish -func (actorPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actorPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { var ( - cooldown = attrs.MustDuration("cooldown", ptrDefaultCooldown) - reason = attrs.MustString("reason", ptrStringEmpty) + cooldown = attrs.MustDuration("cooldown", helpers.Ptr(oneWeek)) + reason = attrs.MustString("reason", helpers.Ptr("")) user = attrs.MustString("user", nil) - uuid = attrs.MustString("uuid", ptrStringEmpty) + uuid = attrs.MustString("uuid", helpers.Ptr("")) ) levels, err := attrs.StringSlice("levels") @@ -225,17 +226,16 @@ func (actorPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, event func (actorPunish) IsAsync() bool { return false } func (actorPunish) Name() string { return actorNamePunish } -func (actorPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("user"); err != nil || v == "" { - return errors.New("user must be non-empty string") - } - - if v, err := attrs.StringSlice("levels"); err != nil || len(v) == 0 { - return errors.New("levels must be slice of strings with length > 0") - } - - if err = tplValidator(attrs.MustString("user", ptrStringEmpty)); err != nil { - return errors.Wrap(err, "validating user template") +func (actorPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "levels", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeStringSlice}), + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "user", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "cooldown", Type: fieldcollection.SchemaFieldTypeDuration}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "uuid", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "user"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil @@ -243,10 +243,10 @@ func (actorPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *p // Reset -func (actorResetPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actorResetPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { var ( user = attrs.MustString("user", nil) - uuid = attrs.MustString("uuid", ptrStringEmpty) + uuid = attrs.MustString("uuid", helpers.Ptr("")) ) if user, err = formatMessage(user, m, r, eventData); err != nil { @@ -262,13 +262,13 @@ func (actorResetPunish) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, func (actorResetPunish) IsAsync() bool { return false } func (actorResetPunish) Name() string { return actorNameResetPunish } -func (actorResetPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("user"); err != nil || v == "" { - return errors.New("user must be non-empty string") - } - - if err = tplValidator(attrs.MustString("user", ptrStringEmpty)); err != nil { - return errors.Wrap(err, "validating user template") +func (actorResetPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "user", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "uuid", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "user"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/quotedb/actor.go b/internal/actors/quotedb/actor.go index 2ffbb2b..2adc75c 100644 --- a/internal/actors/quotedb/actor.go +++ b/internal/actors/quotedb/actor.go @@ -10,6 +10,8 @@ import ( "gopkg.in/irc.v4" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -23,9 +25,9 @@ var ( formatMessage plugins.MsgFormatter send plugins.SendMessageFunc - ptrStringEmpty = func(v string) *string { return &v }("") - ptrStringOutFormat = func(v string) *string { return &v }("Quote #{{ .index }}: {{ .quote }}") - ptrStringZero = func(v string) *string { return &v }("0") + // ptrStringEmpty = func(v string) *string { return &v }("") + // ptrStringOutFormat = func(v string) *string { return &v }("Quote #{{ .index }}: {{ .quote }}") + // ptrStringZero = func(v string) *string { return &v }("0") ) // Register provides the plugins.RegisterFunc @@ -93,7 +95,7 @@ func Register(args plugins.RegistrationArguments) (err error) { return fmt.Errorf("registering API: %w", err) } - args.RegisterTemplateFunction("lastQuoteIndex", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} { + args.RegisterTemplateFunction("lastQuoteIndex", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} { return func() (int, error) { return getMaxQuoteIdx(db, plugins.DeriveChannel(m, nil)) } @@ -113,11 +115,11 @@ type ( actor struct{} ) -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { var ( - action = attrs.MustString("action", ptrStringEmpty) - indexStr = attrs.MustString("index", ptrStringZero) - quote = attrs.MustString("quote", ptrStringEmpty) + action = attrs.MustString("action", helpers.Ptr("")) + indexStr = attrs.MustString("index", helpers.Ptr("0")) + quote = attrs.MustString("quote", helpers.Ptr("")) ) if indexStr == "" { @@ -166,7 +168,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * fields.Set("index", idx) fields.Set("quote", quote) - format := attrs.MustString("format", ptrStringOutFormat) + format := attrs.MustString("format", helpers.Ptr("Quote #{{ .index }}: {{ .quote }}")) msg, err := formatMessage(format, m, r, fields) if err != nil { return false, errors.Wrap(err, "formatting output message") @@ -190,31 +192,35 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - action := attrs.MustString("action", ptrStringEmpty) +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "action", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "quote", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "index", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "format", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "index", "quote", "format"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) + } + + action := attrs.MustString("action", helpers.Ptr("")) switch action { case "add": if v, err := attrs.String("quote"); err != nil || v == "" { - return errors.New("quote must be non-empty string for action add") + return fmt.Errorf("quote must be non-empty string for action add") } case "del": if v, err := attrs.String("index"); err != nil || v == "" { - return errors.New("index must be non-empty string for adction del") + return fmt.Errorf("index must be non-empty string for adction del") } case "get": // No requirements default: - return errors.New("action must be one of add, del or get") - } - - for _, field := range []string{"index", "quote", "format"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } + return fmt.Errorf("action must be one of add, del or get") } return nil diff --git a/internal/actors/raw/actor.go b/internal/actors/raw/actor.go index 1fb3c75..d6e3b74 100644 --- a/internal/actors/raw/actor.go +++ b/internal/actors/raw/actor.go @@ -2,9 +2,13 @@ package raw import ( + "fmt" + "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -13,8 +17,6 @@ const actorName = "raw" var ( formatMessage plugins.MsgFormatter send plugins.SendMessageFunc - - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -47,7 +49,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.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") @@ -67,13 +69,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("message"); err != nil || v == "" { - return errors.New("message must be non-empty string") - } - - if err = tplValidator(attrs.MustString("message", ptrStringEmpty)); err != nil { - return errors.Wrap(err, "validating message template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "message", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "message"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/respond/actor.go b/internal/actors/respond/actor.go index a337260..de381ea 100644 --- a/internal/actors/respond/actor.go +++ b/internal/actors/respond/actor.go @@ -12,6 +12,8 @@ import ( log "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -20,9 +22,6 @@ const actorName = "respond" var ( formatMessage plugins.MsgFormatter send plugins.SendMessageFunc - - ptrBoolFalse = func(v bool) *bool { return &v }(false) - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -102,7 +101,7 @@ func Register(args plugins.RegistrationArguments) (err error) { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.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) == "" { @@ -127,7 +126,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * }, } - if attrs.MustBool("as_reply", ptrBoolFalse) { + if attrs.MustBool("as_reply", helpers.Ptr(false)) { id, ok := m.Tags["id"] if ok { if ircMessage.Tags == nil { @@ -146,15 +145,15 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("message"); err != nil || v == "" { - return errors.New("message must be non-empty string") - } - - for _, field := range []string{"message", "fallback"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "message", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "fallback", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "as_reply", Type: fieldcollection.SchemaFieldTypeBool}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "to_channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "message", "fallback"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/shield/actor.go b/internal/actors/shield/actor.go index 0ce8823..6fd197e 100644 --- a/internal/actors/shield/actor.go +++ b/internal/actors/shield/actor.go @@ -4,10 +4,13 @@ package shield import ( "context" + "fmt" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -45,14 +48,12 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { - ptrBoolFalse := func(v bool) *bool { return &v }(false) - +func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { return false, errors.Wrap( botTwitchClient.UpdateShieldMode( context.Background(), plugins.DeriveChannel(m, eventData), - attrs.MustBool("enable", ptrBoolFalse), + attrs.MustBool("enable", helpers.Ptr(false)), ), "configuring shield mode", ) @@ -61,9 +62,11 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if _, err = attrs.Bool("enable"); err != nil { - return errors.New("enable must be boolean") +func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "enable", Type: fieldcollection.SchemaFieldTypeBool}), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/shoutout/actor.go b/internal/actors/shoutout/actor.go index 45516c9..612e51b 100644 --- a/internal/actors/shoutout/actor.go +++ b/internal/actors/shoutout/actor.go @@ -4,11 +4,14 @@ package shoutout import ( "context" + "fmt" "regexp" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -55,7 +58,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { user, err := formatMessage(attrs.MustString("user", ptrStringEmpty), m, r, eventData) if err != nil { return false, errors.Wrap(err, "executing user template") @@ -74,13 +77,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("user"); err != nil || v == "" { - return errors.New("user must be non-empty string") - } - - if err = tplValidator(attrs.MustString("user", ptrStringEmpty)); err != nil { - return errors.Wrap(err, "validating user template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "user", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "user"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/stopexec/actor.go b/internal/actors/stopexec/actor.go index 313c73a..d692b51 100644 --- a/internal/actors/stopexec/actor.go +++ b/internal/actors/stopexec/actor.go @@ -3,9 +3,13 @@ package stopexec import ( + "fmt" + "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -42,10 +46,8 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *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 }("") - - when, err := formatMessage(attrs.MustString("when", ptrStringEmpty), m, r, eventData) +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { + when, err := formatMessage(attrs.MustString("when", helpers.Ptr("")), m, r, eventData) if err != nil { return false, errors.Wrap(err, "executing when template") } @@ -60,14 +62,12 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - whenTemplate, err := attrs.String("when") - if err != nil || whenTemplate == "" { - return errors.New("when must be non-empty string") - } - - if err = tplValidator(whenTemplate); err != nil { - return errors.Wrap(err, "validating when template") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "when", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "when"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/timeout/actor.go b/internal/actors/timeout/actor.go index 52d7bba..0c3c985 100644 --- a/internal/actors/timeout/actor.go +++ b/internal/actors/timeout/actor.go @@ -3,6 +3,7 @@ package timeout import ( "context" + "fmt" "regexp" "strconv" "time" @@ -10,6 +11,8 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -65,7 +68,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { reason, err := formatMessage(attrs.MustString("reason", ptrStringEmpty), m, r, eventData) if err != nil { return false, errors.Wrap(err, "executing reason template") @@ -86,17 +89,17 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, 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") +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "duration", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeDuration}), + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "reason"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } - if v, err := attrs.String("reason"); err != nil || v == "" { - return errors.New("reason must be non-empty string") - } - - if err = tplValidator(attrs.MustString("reason", ptrStringEmpty)); err != nil { - return errors.Wrap(err, "validating reason template") + if attrs.MustDuration("duration", nil) < time.Second { + return errors.New("duration must be greater or equal one second") } return nil diff --git a/internal/actors/variables/actor.go b/internal/actors/variables/actor.go index 8b9a4f1..a188c93 100644 --- a/internal/actors/variables/actor.go +++ b/internal/actors/variables/actor.go @@ -11,6 +11,8 @@ import ( "gopkg.in/irc.v4" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -144,7 +146,7 @@ func Register(args plugins.RegistrationArguments) (err error) { type actorSetVariable struct{} -func (actorSetVariable) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actorSetVariable) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.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") @@ -171,15 +173,14 @@ func (actorSetVariable) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, func (actorSetVariable) IsAsync() bool { return false } func (actorSetVariable) Name() string { return "setvariable" } -func (actorSetVariable) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("variable"); err != nil || v == "" { - return errors.New("variable name must be non-empty string") - } - - for _, field := range []string{"set", "variable"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actorSetVariable) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "variable", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "clear", Type: fieldcollection.SchemaFieldTypeBool}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "set", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "set", "variable"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/actors/vip/vip.go b/internal/actors/vip/vip.go index 19c6106..17e64e4 100644 --- a/internal/actors/vip/vip.go +++ b/internal/actors/vip/vip.go @@ -3,11 +3,14 @@ package vip import ( "context" + "fmt" "strings" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -16,8 +19,6 @@ var ( formatMessage plugins.MsgFormatter permCheckFn plugins.ChannelPermissionCheckFunc tcGetter func(string) (*twitch.Client, error) - - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -98,21 +99,19 @@ type ( ) func (actor) IsAsync() bool { return false } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - for _, field := range []string{"channel", "user"} { - if v, err := attrs.String(field); err != nil || v == "" { - return errors.Errorf("%s must be non-empty string", field) - } - - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "user", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "channel", "user"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil } -func (actor) getParams(m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (channel, user string, err error) { +func (actor) getParams(m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (channel, user string, err error) { if channel, err = formatMessage(attrs.MustString("channel", nil), m, r, eventData); err != nil { return "", "", errors.Wrap(err, "parsing channel") } @@ -124,7 +123,7 @@ func (actor) getParams(m *irc.Message, r *plugins.Rule, eventData *plugins.Field return strings.TrimLeft(channel, "#"), user, nil } -func (u unvipActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (u unvipActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { channel, user, err := u.getParams(m, r, eventData, attrs) if err != nil { return false, errors.Wrap(err, "getting parameters") @@ -140,7 +139,7 @@ func (u unvipActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even func (unvipActor) Name() string { return "unvip" } -func (v vipActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (v vipActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { channel, user, err := v.getParams(m, r, eventData, attrs) if err != nil { return false, errors.Wrap(err, "getting parameters") diff --git a/internal/actors/whisper/actor.go b/internal/actors/whisper/actor.go index 5cd56c0..48bee1c 100644 --- a/internal/actors/whisper/actor.go +++ b/internal/actors/whisper/actor.go @@ -3,10 +3,13 @@ package whisper import ( "context" + "fmt" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -16,8 +19,6 @@ const actorName = "whisper" var ( botTwitchClient *twitch.Client formatMessage plugins.MsgFormatter - - ptrStringEmpty = func(s string) *string { return &s }("") ) // Register provides the plugins.RegisterFunc @@ -28,7 +29,7 @@ func Register(args plugins.RegistrationArguments) error { args.RegisterActor(actorName, func() plugins.Actor { return &actor{} }) args.RegisterActorDocumentation(plugins.ActionDocumentation{ - Description: "Send a whisper (requires a verified bot!)", + Description: "Send a whisper", Name: "Send Whisper", Type: "whisper", @@ -59,7 +60,7 @@ func Register(args plugins.RegistrationArguments) error { type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.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") @@ -79,19 +80,13 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("to"); err != nil || v == "" { - return errors.New("to must be non-empty string") - } - - if v, err := attrs.String("message"); err != nil || v == "" { - return errors.New("message must be non-empty string") - } - - for _, field := range []string{"message", "to"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "message", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "to", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "message", "to"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/apimodules/customevent/actor.go b/internal/apimodules/customevent/actor.go index b272ede..a09b55b 100644 --- a/internal/apimodules/customevent/actor.go +++ b/internal/apimodules/customevent/actor.go @@ -1,17 +1,20 @@ package customevent import ( + "fmt" "strings" "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" + "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/plugins" ) type actor struct{} -func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { fd, err := formatMessage(attrs.MustString("fields", ptrStringEmpty), m, r, eventData) if err != nil { return false, errors.Wrap(err, "executing fields template") @@ -35,15 +38,13 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData * func (actor) IsAsync() bool { return false } func (actor) Name() string { return actorName } -func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - if v, err := attrs.String("fields"); err != nil || v == "" { - return errors.New("fields is expected to be non-empty string") - } - - for _, field := range []string{"fields", "schedule_in"} { - if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { - return errors.Wrapf(err, "validating %s template", field) - } +func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "fields", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "schedule_in", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + helpers.SchemaValidateTemplateField(tplValidator, "fields", "schedule_in"), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/apimodules/customevent/customevent.go b/internal/apimodules/customevent/customevent.go index 14c9a7d..cc8d236 100644 --- a/internal/apimodules/customevent/customevent.go +++ b/internal/apimodules/customevent/customevent.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -130,14 +131,14 @@ func handleCreateEvent(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -func parseEvent(channel string, fieldData io.Reader) (*plugins.FieldCollection, error) { +func parseEvent(channel string, fieldData io.Reader) (*fieldcollection.FieldCollection, error) { payload := make(map[string]any) if err := json.NewDecoder(fieldData).Decode(&payload); err != nil { return nil, errors.Wrap(err, "parsing event payload") } - fields := plugins.FieldCollectionFromData(payload) + fields := fieldcollection.FieldCollectionFromData(payload) fields.Set("channel", "#"+strings.TrimLeft(channel, "#")) return fields, nil diff --git a/internal/apimodules/customevent/database.go b/internal/apimodules/customevent/database.go index 02bd3e4..39c038e 100644 --- a/internal/apimodules/customevent/database.go +++ b/internal/apimodules/customevent/database.go @@ -9,9 +9,9 @@ import ( "github.com/pkg/errors" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" - "github.com/Luzifer/twitch-bot/v3/plugins" ) const cleanupTimeout = 15 * time.Minute @@ -48,7 +48,7 @@ func getFutureEvents(db database.Connector) (out []storedCustomEvent, err error) ) } -func storeEvent(db database.Connector, scheduleAt time.Time, channel string, fields *plugins.FieldCollection) error { +func storeEvent(db database.Connector, scheduleAt time.Time, channel string, fields *fieldcollection.FieldCollection) error { fieldBuf := new(bytes.Buffer) if err := json.NewEncoder(fieldBuf).Encode(fields); err != nil { return errors.Wrap(err, "marshalling fields") diff --git a/internal/apimodules/kofi/kofi.go b/internal/apimodules/kofi/kofi.go index 76f8b3e..d05a917 100644 --- a/internal/apimodules/kofi/kofi.go +++ b/internal/apimodules/kofi/kofi.go @@ -9,6 +9,7 @@ import ( "net/http" "strings" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" "github.com/gorilla/mux" "github.com/sirupsen/logrus" @@ -87,7 +88,7 @@ func handleKoFiPost(w http.ResponseWriter, r *http.Request) { return } - fields := plugins.NewFieldCollection() + fields := fieldcollection.NewFieldCollection() fields.Set("channel", "#"+strings.TrimLeft(channel, "#")) switch payload.Type { diff --git a/internal/apimodules/overlays/database.go b/internal/apimodules/overlays/database.go index 7dba886..5fa828a 100644 --- a/internal/apimodules/overlays/database.go +++ b/internal/apimodules/overlays/database.go @@ -10,9 +10,9 @@ import ( "gorm.io/gorm" "github.com/Luzifer/go_helpers/v2/backoff" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" - "github.com/Luzifer/twitch-bot/v3/plugins" ) type ( @@ -86,7 +86,7 @@ func getEventByID(db database.Connector, eventID uint64) (socketMessage, error) } func (o overlaysEvent) ToSocketMessage() (socketMessage, error) { - fields := new(plugins.FieldCollection) + fields := new(fieldcollection.FieldCollection) if err := json.NewDecoder(strings.NewReader(o.Fields)).Decode(fields); err != nil { return socketMessage{}, errors.Wrap(err, "decoding fields") } diff --git a/internal/apimodules/overlays/database_test.go b/internal/apimodules/overlays/database_test.go index ed4cf8b..879d5bf 100644 --- a/internal/apimodules/overlays/database_test.go +++ b/internal/apimodules/overlays/database_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/database" - "github.com/Luzifer/twitch-bot/v3/plugins" ) func TestEventDatabaseRoundtrip(t *testing.T) { @@ -30,7 +30,7 @@ func TestEventDatabaseRoundtrip(t *testing.T) { IsLive: true, Time: tEvent2, Type: "event 2", - Fields: plugins.FieldCollectionFromData(map[string]any{"foo": "bar"}), + Fields: fieldcollection.FieldCollectionFromData(map[string]any{"foo": "bar"}), }) assert.Equal(t, uint64(1), evtID) assert.NoError(t, err, "adding second event") @@ -39,7 +39,7 @@ func TestEventDatabaseRoundtrip(t *testing.T) { IsLive: true, Time: tEvent1, Type: "event 1", - Fields: plugins.FieldCollectionFromData(map[string]any{"foo": "bar"}), + Fields: fieldcollection.FieldCollectionFromData(map[string]any{"foo": "bar"}), }) assert.Equal(t, uint64(2), evtID) assert.NoError(t, err, "adding first event") @@ -48,7 +48,7 @@ func TestEventDatabaseRoundtrip(t *testing.T) { IsLive: true, Time: tEvent1, Type: "event", - Fields: plugins.FieldCollectionFromData(map[string]any{"foo": "bar"}), + Fields: fieldcollection.FieldCollectionFromData(map[string]any{"foo": "bar"}), }) assert.Equal(t, uint64(3), evtID) assert.NoError(t, err, "adding other channel event") @@ -66,6 +66,6 @@ func TestEventDatabaseRoundtrip(t *testing.T) { IsLive: false, Time: tEvent1, Type: "event 1", - Fields: plugins.FieldCollectionFromData(map[string]any{"foo": "bar"}), + Fields: fieldcollection.FieldCollectionFromData(map[string]any{"foo": "bar"}), }, evt) } diff --git a/internal/apimodules/overlays/overlays.go b/internal/apimodules/overlays/overlays.go index 423a79b..557d4e8 100644 --- a/internal/apimodules/overlays/overlays.go +++ b/internal/apimodules/overlays/overlays.go @@ -21,6 +21,7 @@ import ( log "github.com/sirupsen/logrus" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/plugins" @@ -41,12 +42,12 @@ type ( // socketMessage represents the message overlay sockets will receive socketMessage struct { - EventID uint64 `json:"event_id"` - IsLive bool `json:"is_live"` - Reason sendReason `json:"reason"` - Time time.Time `json:"time"` - Type string `json:"type"` - Fields *plugins.FieldCollection `json:"fields"` + EventID uint64 `json:"event_id"` + IsLive bool `json:"is_live"` + Reason sendReason `json:"reason"` + Time time.Time `json:"time"` + Type string `json:"type"` + Fields *fieldcollection.FieldCollection `json:"fields"` } ) @@ -180,7 +181,7 @@ func Register(args plugins.RegistrationArguments) (err error) { return fmt.Errorf("registering API route: %w", err) } - if err = args.RegisterEventHandler(func(event string, eventData *plugins.FieldCollection) (err error) { + if err = args.RegisterEventHandler(func(event string, eventData *fieldcollection.FieldCollection) (err error) { subscribersLock.RLock() defer subscribersLock.RUnlock() diff --git a/internal/apimodules/raffle/actor.go b/internal/apimodules/raffle/actor.go index 872c032..3bd5af4 100644 --- a/internal/apimodules/raffle/actor.go +++ b/internal/apimodules/raffle/actor.go @@ -1,8 +1,10 @@ package raffle import ( + "fmt" "time" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -17,7 +19,7 @@ var ptrStrEmpty = ptrStr("") func ptrStr(v string) *string { return &v } -func (enterRaffleActor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, evtData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) { +func (enterRaffleActor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, evtData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) { if m != nil || evtData.MustString("reward_id", ptrStrEmpty) == "" { return false, errors.New("enter-raffle actor is only supposed to act on channelpoint redeems") } @@ -43,7 +45,7 @@ func (enterRaffleActor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, EnteredAt: time.Now().UTC(), } - raffleEventFields := plugins.FieldCollectionFromData(map[string]any{ + raffleEventFields := fieldcollection.FieldCollectionFromData(map[string]any{ "user_id": re.UserID, "user": re.UserLogin, }) @@ -70,10 +72,11 @@ func (enterRaffleActor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, func (enterRaffleActor) IsAsync() bool { return false } func (enterRaffleActor) Name() string { return "enter-raffle" } -func (enterRaffleActor) Validate(_ plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { - keyword, err := attrs.String("keyword") - if err != nil || keyword == "" { - return errors.New("keyword must be non-empty string") +func (enterRaffleActor) Validate(_ plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) { + if err = attrs.ValidateSchema( + fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "keyword", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}), + ); err != nil { + return fmt.Errorf("validating attributes: %w", err) } return nil diff --git a/internal/apimodules/raffle/database.go b/internal/apimodules/raffle/database.go index 48c0700..c94fc5b 100644 --- a/internal/apimodules/raffle/database.go +++ b/internal/apimodules/raffle/database.go @@ -9,9 +9,9 @@ import ( "gopkg.in/irc.v4" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" - "github.com/Luzifer/twitch-bot/v3/plugins" ) const ( @@ -374,7 +374,7 @@ func (d *dbClient) PickWinner(raffleID uint64) error { d.speakUp[strings.Join([]string{r.Channel, winner.UserLogin}, ":")] = &speakUpWait{RaffleEntryID: winner.ID, Until: speakUpUntil} d.lock.Unlock() - fields := plugins.FieldCollectionFromData(map[string]any{ + fields := fieldcollection.FieldCollectionFromData(map[string]any{ "user_id": winner.UserID, "user": winner.UserLogin, "winner": winner, @@ -636,9 +636,9 @@ func (d *dbClient) Update(r raffle) error { // SendEvent processes the text template and sends the message if // enabled given through the event -func (r raffle) SendEvent(evt raffleMessageEvent, fields *plugins.FieldCollection) (err error) { +func (r raffle) SendEvent(evt raffleMessageEvent, fields *fieldcollection.FieldCollection) (err error) { if fields == nil { - fields = plugins.NewFieldCollection() + fields = fieldcollection.NewFieldCollection() } fields.Set("raffle", r) // Make raffle available to templating diff --git a/internal/apimodules/raffle/irc.go b/internal/apimodules/raffle/irc.go index 30cb0a2..14adff0 100644 --- a/internal/apimodules/raffle/irc.go +++ b/internal/apimodules/raffle/irc.go @@ -9,6 +9,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -95,7 +96,7 @@ func handleRaffleEntry(m *irc.Message, channel, user string) error { re.UserDisplayName = re.UserLogin } - raffleEventFields := plugins.FieldCollectionFromData(map[string]any{ + raffleEventFields := fieldcollection.FieldCollectionFromData(map[string]any{ "user_id": m.Tags["user-id"], "user": user, }) diff --git a/internal/helpers/ptr.go b/internal/helpers/ptr.go new file mode 100644 index 0000000..8f818be --- /dev/null +++ b/internal/helpers/ptr.go @@ -0,0 +1,4 @@ +package helpers + +// Ptr creates a pointer to any given type +func Ptr[T any](v T) *T { return &v } diff --git a/internal/helpers/validateHelper.go b/internal/helpers/validateHelper.go new file mode 100644 index 0000000..f8104c3 --- /dev/null +++ b/internal/helpers/validateHelper.go @@ -0,0 +1,21 @@ +package helpers + +import ( + "fmt" + + "github.com/Luzifer/go_helpers/v2/fieldcollection" +) + +// SchemaValidateTemplateField contains a ValidateOpt for the +// fieldcollection schema validator to validate template fields +func SchemaValidateTemplateField(tplValidator func(string) error, fields ...string) fieldcollection.ValidateOpt { + return func(f, _ *fieldcollection.FieldCollection) (err error) { + for _, field := range fields { + if err = tplValidator(f.MustString(field, Ptr(""))); err != nil { + return fmt.Errorf("validating %s: %w", field, err) + } + } + + return nil + } +} diff --git a/internal/template/userstate/actor.go b/internal/template/userstate/actor.go index 240ca85..d28b796 100644 --- a/internal/template/userstate/actor.go +++ b/internal/template/userstate/actor.go @@ -3,6 +3,7 @@ package userstate import ( + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" "github.com/pkg/errors" "gopkg.in/irc.v4" @@ -16,7 +17,7 @@ func Register(args plugins.RegistrationArguments) error { return errors.Wrap(err, "registering raw message handler") } - args.RegisterTemplateFunction("botHasBadge", func(m *irc.Message, _ *plugins.Rule, fields *plugins.FieldCollection) interface{} { + args.RegisterTemplateFunction("botHasBadge", func(m *irc.Message, _ *plugins.Rule, fields *fieldcollection.FieldCollection) interface{} { return func(badge string) bool { state := userState.Get(plugins.DeriveChannel(m, fields)) if state == nil { diff --git a/irc.go b/irc.go index 02a8908..e69f644 100644 --- a/irc.go +++ b/irc.go @@ -13,6 +13,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -226,7 +227,7 @@ func (i ircHandler) handleClearChat(m *irc.Message) { var ( evt *string - fields = plugins.NewFieldCollection() + fields = fieldcollection.NewFieldCollection() ) fields.Set(eventFieldChannel, i.getChannel(m)) // Compatibility to plugins.DeriveChannel @@ -258,7 +259,7 @@ func (i ircHandler) handleClearChat(m *irc.Message) { } func (i ircHandler) handleClearMessage(m *irc.Message) { - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel "message_id": m.Tags["target-msg-id"], "target_name": m.Tags["login"], @@ -270,7 +271,7 @@ func (i ircHandler) handleClearMessage(m *irc.Message) { } func (i ircHandler) handleJoin(m *irc.Message) { - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser }) @@ -278,7 +279,7 @@ func (i ircHandler) handleJoin(m *irc.Message) { } func (i ircHandler) handlePart(m *irc.Message) { - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser }) @@ -299,7 +300,7 @@ func (i ircHandler) handlePermit(m *irc.Message) { username := msgParts[1] - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser eventFieldUserID: m.Tags["user-id"], @@ -356,7 +357,7 @@ func (i ircHandler) handleTwitchPrivmsg(m *irc.Message) { } if bits := i.tagToNumeric(m, "bits", 0); bits > 0 { - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ "bits": bits, eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel "message": m.Trailing(), @@ -380,7 +381,7 @@ func (i ircHandler) handleTwitchUsernotice(m *irc.Message) { "trailing": m.Trailing(), }).Trace("IRC USERNOTICE event") - evtData := plugins.FieldCollectionFromData(map[string]any{ + evtData := fieldcollection.FieldCollectionFromData(map[string]any{ eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldUserName: m.Tags["login"], // Compatibility to plugins.DeriveUser eventFieldUserID: m.Tags["user-id"], diff --git a/msgformatter.go b/msgformatter.go index c25d3fc..cbe3324 100644 --- a/msgformatter.go +++ b/msgformatter.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -19,7 +20,7 @@ var ( stripNewline = regexp.MustCompile(`(?m)\s*\n\s*`) - formatMessageFieldSetters = []func(compiledFields *plugins.FieldCollection, m *irc.Message, fields *plugins.FieldCollection){ + formatMessageFieldSetters = []func(compiledFields *fieldcollection.FieldCollection, m *irc.Message, fields *fieldcollection.FieldCollection){ formatMessageFieldChannel, formatMessageFieldMessage, formatMessageFieldUserID, @@ -27,8 +28,8 @@ var ( } ) -func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) (string, error) { - compiledFields := plugins.NewFieldCollection() +func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *fieldcollection.FieldCollection) (string, error) { + compiledFields := fieldcollection.NewFieldCollection() if config != nil { configLock.RLock() @@ -61,11 +62,11 @@ func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *pl return strings.TrimSpace(buf.String()), errors.Wrap(err, "execute template") } -func formatMessageFieldChannel(compiledFields *plugins.FieldCollection, m *irc.Message, fields *plugins.FieldCollection) { +func formatMessageFieldChannel(compiledFields *fieldcollection.FieldCollection, m *irc.Message, fields *fieldcollection.FieldCollection) { compiledFields.Set(eventFieldChannel, plugins.DeriveChannel(m, fields)) } -func formatMessageFieldMessage(compiledFields *plugins.FieldCollection, m *irc.Message, _ *plugins.FieldCollection) { +func formatMessageFieldMessage(compiledFields *fieldcollection.FieldCollection, m *irc.Message, _ *fieldcollection.FieldCollection) { if m == nil { return } @@ -73,7 +74,7 @@ func formatMessageFieldMessage(compiledFields *plugins.FieldCollection, m *irc.M compiledFields.Set("msg", m) } -func formatMessageFieldUserID(compiledFields *plugins.FieldCollection, m *irc.Message, _ *plugins.FieldCollection) { +func formatMessageFieldUserID(compiledFields *fieldcollection.FieldCollection, m *irc.Message, _ *fieldcollection.FieldCollection) { if m == nil { return } @@ -83,7 +84,7 @@ func formatMessageFieldUserID(compiledFields *plugins.FieldCollection, m *irc.Me } } -func formatMessageFieldUsername(compiledFields *plugins.FieldCollection, m *irc.Message, fields *plugins.FieldCollection) { +func formatMessageFieldUsername(compiledFields *fieldcollection.FieldCollection, m *irc.Message, fields *fieldcollection.FieldCollection) { compiledFields.Set("username", plugins.DeriveUser(m, fields)) } @@ -93,7 +94,7 @@ func validateTemplate(tplString string) error { _, err := template. New(tplString). - Funcs(tplFuncs.GetFuncMap(nil, nil, plugins.NewFieldCollection())). + Funcs(tplFuncs.GetFuncMap(nil, nil, fieldcollection.NewFieldCollection())). Parse(tplString) return errors.Wrap(err, "parsing template") } diff --git a/plugins/actorkit.go b/plugins/actorkit.go index 3923235..b8a9b1e 100644 --- a/plugins/actorkit.go +++ b/plugins/actorkit.go @@ -3,6 +3,7 @@ package plugins import ( "reflect" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/pkg/errors" ) @@ -16,9 +17,9 @@ type ( // (not returning ErrValueNotSet) and does not contain zero value // recognized by reflect (to just check whether the field is set // but allow zero values use HasAll on the FieldCollection) -func (ActorKit) ValidateRequireNonEmpty(attrs *FieldCollection, fields ...string) error { +func (ActorKit) ValidateRequireNonEmpty(attrs *fieldcollection.FieldCollection, fields ...string) error { for _, field := range fields { - v, err := attrs.Any(field) + v, err := attrs.Get(field) if err != nil { return errors.Wrapf(err, "getting field %s", field) } @@ -34,7 +35,7 @@ func (ActorKit) ValidateRequireNonEmpty(attrs *FieldCollection, fields ...string // ValidateRequireValidTemplate checks whether fields are gettable // as strings and do have a template which validates (this does not // check for empty strings as an empty template is indeed valid) -func (ActorKit) ValidateRequireValidTemplate(tplValidator TemplateValidatorFunc, attrs *FieldCollection, fields ...string) error { +func (ActorKit) ValidateRequireValidTemplate(tplValidator TemplateValidatorFunc, attrs *fieldcollection.FieldCollection, fields ...string) error { for _, field := range fields { v, err := attrs.String(field) if err != nil { @@ -52,11 +53,11 @@ func (ActorKit) ValidateRequireValidTemplate(tplValidator TemplateValidatorFunc, // ValidateRequireValidTemplateIfSet checks whether the field is // either not set or a valid template (this does not // check for empty strings as an empty template is indeed valid) -func (ActorKit) ValidateRequireValidTemplateIfSet(tplValidator TemplateValidatorFunc, attrs *FieldCollection, fields ...string) error { +func (ActorKit) ValidateRequireValidTemplateIfSet(tplValidator TemplateValidatorFunc, attrs *fieldcollection.FieldCollection, fields ...string) error { for _, field := range fields { v, err := attrs.String(field) if err != nil { - if errors.Is(err, ErrValueNotSet) { + if errors.Is(err, fieldcollection.ErrValueNotSet) { continue } return errors.Wrapf(err, "getting string field %s", field) diff --git a/plugins/actorkit_test.go b/plugins/actorkit_test.go index 7132de6..5fc03ad 100644 --- a/plugins/actorkit_test.go +++ b/plugins/actorkit_test.go @@ -4,11 +4,12 @@ import ( "strings" "testing" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/stretchr/testify/assert" ) func TestValidateRequireNonEmpty(t *testing.T) { - attrs := FieldCollectionFromData(map[string]any{ + attrs := fieldcollection.FieldCollectionFromData(map[string]any{ "str": "", "str_v": "valid", "int": 0, diff --git a/plugins/fieldcollection.go b/plugins/fieldcollection.go deleted file mode 100644 index 8fbd0cb..0000000 --- a/plugins/fieldcollection.go +++ /dev/null @@ -1,407 +0,0 @@ -package plugins - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "sync" - "time" - - "github.com/pkg/errors" -) - -var ( - // ErrValueNotSet is used to notify the value is not available in the FieldCollection - ErrValueNotSet = errors.New("specified value not found") - // ErrValueMismatch is used to notify the value does not match the requested type - ErrValueMismatch = errors.New("specified value has different format") -) - -// FieldCollection holds an map[string]any with conversion functions attached -type FieldCollection struct { - data map[string]any - lock sync.RWMutex -} - -// NewFieldCollection creates a new FieldCollection with empty data store -func NewFieldCollection() *FieldCollection { - return &FieldCollection{data: make(map[string]any)} -} - -// FieldCollectionFromData is a wrapper around NewFieldCollection and SetFromData -func FieldCollectionFromData(data map[string]any) *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 -} - -// 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 -} - -// 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 -} - -// 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 -} - -// 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]any { - if f == nil { - return nil - } - - f.lock.RLock() - defer f.lock.RUnlock() - - out := make(map[string]any) - 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.data[k]; !ok { - missing = append(missing, k) - } - } - - if len(missing) > 0 { - return errors.Errorf("missing key(s) %s", strings.Join(missing, ", ")) - } - - return nil -} - -// 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 -} - -// 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 { - return *defVal - } - panic(err) - } - return v -} - -// 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 { - return *defVal - } - panic(err) - } - return v -} - -// 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 { - return *defVal - } - panic(err) - } - return v -} - -// 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 { - return *defVal - } - panic(err) - } - return v -} - -// MustStringSlice is a wrapper around StringSlice and returns nil in case name is not set -func (f *FieldCollection) MustStringSlice(name string) []string { - v, err := f.StringSlice(name) - if err != nil { - return nil - } - return v -} - -// Any tries to read key name as any-type (interface) -func (f *FieldCollection) Any(name string) (any, 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 - } - - return v, nil -} - -// 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 - } - - switch v := v.(type) { - case bool: - return v, nil - case string: - bv, err := strconv.ParseBool(v) - return bv, errors.Wrap(err, "parsing string to bool") - } - - return false, ErrValueMismatch -} - -// 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") - } - - d, err := time.ParseDuration(v) - return d, errors.Wrap(err, "parsing value") -} - -// 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 - } - - switch v := v.(type) { - case float64: - return int64(v), nil - case int: - return int64(v), nil - case int16: - return int64(v), nil - case int32: - return int64(v), nil - case int64: - return v, nil - } - - return 0, ErrValueMismatch -} - -// Set sets a single key to specified value -func (f *FieldCollection) Set(key string, value any) { - if f == nil { - f = NewFieldCollection() - } - - f.lock.Lock() - defer f.lock.Unlock() - - if f.data == nil { - f.data = make(map[string]any) - } - - f.data[key] = value -} - -// SetFromData takes a map of data and copies all data into the FieldCollection -func (f *FieldCollection) SetFromData(data map[string]any) { - if f == nil { - f = NewFieldCollection() - } - - f.lock.Lock() - defer f.lock.Unlock() - - if f.data == nil { - f.data = make(map[string]any) - } - - 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 - } - - if sv, ok := v.(string); ok { - return sv, nil - } - - if iv, ok := v.(fmt.Stringer); ok { - return iv.String(), nil - } - - return fmt.Sprintf("%v", v), nil -} - -// 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 - } - - switch v := v.(type) { - case []string: - return v, nil - - case []any: - var out []string - - for _, iv := range v { - sv, ok := iv.(string) - if !ok { - return nil, errors.New("value in slice was not string") - } - out = append(out, sv) - } - - return out, nil - } - - return nil, ErrValueMismatch -} - -// Implement JSON marshalling to plain underlying map[string]any - -// MarshalJSON implements the json.Marshaller interface -func (f *FieldCollection) MarshalJSON() ([]byte, error) { - if f == nil || f.data == nil { - return []byte("{}"), nil - } - - f.lock.RLock() - defer f.lock.RUnlock() - - data, err := json.Marshal(f.data) - if err != nil { - return nil, fmt.Errorf("marshalling data to json: %w", err) - } - - return data, nil -} - -// UnmarshalJSON implements the json.Unmarshaller interface -func (f *FieldCollection) UnmarshalJSON(raw []byte) error { - data := make(map[string]any) - 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]any - -// MarshalYAML implements the yaml.Marshaller interface -func (f *FieldCollection) MarshalYAML() (any, error) { - return f.Data(), nil -} - -// UnmarshalYAML implements the yaml.Unmarshaller interface -func (f *FieldCollection) UnmarshalYAML(unmarshal func(any) error) error { - data := make(map[string]any) - 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 deleted file mode 100644 index 9d1688e..0000000 --- a/plugins/fieldcollection_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package plugins - -import ( - "bytes" - "encoding/json" - "strings" - "testing" - - "gopkg.in/yaml.v3" -) - -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(_ *testing.T) { - var f *FieldCollection - - f.Set("foo", "bar") - - f = nil - f.SetFromData(map[string]interface{}{"foo": "bar"}) -} - -func TestFieldCollectionNilClone(_ *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) - } - } -} - -func TestFieldCollectionIntToString(t *testing.T) { - val := 123 - fc := FieldCollectionFromData(map[string]interface{}{"test": val}) - - if !fc.CanString("test") { - t.Fatalf("cannot convert %T to string", val) - } - - if v := fc.MustString("test", nil); v != "123" { - t.Errorf("unexpected value: 123 != %s", v) - } -} diff --git a/plugins/helpers.go b/plugins/helpers.go index 4b52443..ab5093e 100644 --- a/plugins/helpers.go +++ b/plugins/helpers.go @@ -4,12 +4,13 @@ import ( "fmt" "strings" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "gopkg.in/irc.v4" ) // DeriveChannel takes an irc.Message and a FieldCollection and tries // to extract from them the channel the event / message has taken place -func DeriveChannel(m *irc.Message, evtData *FieldCollection) string { +func DeriveChannel(m *irc.Message, evtData *fieldcollection.FieldCollection) string { if m != nil && len(m.Params) > 0 && strings.HasPrefix(m.Params[0], "#") { return m.Params[0] } @@ -23,7 +24,7 @@ func DeriveChannel(m *irc.Message, evtData *FieldCollection) string { // DeriveUser takes an irc.Message and a FieldCollection and tries // to extract from them the user causing the event / message -func DeriveUser(m *irc.Message, evtData *FieldCollection) string { +func DeriveUser(m *irc.Message, evtData *fieldcollection.FieldCollection) string { if m != nil && m.User != "" { return m.User } diff --git a/plugins/interface.go b/plugins/interface.go index b01e8f2..a4f960d 100644 --- a/plugins/interface.go +++ b/plugins/interface.go @@ -7,6 +7,7 @@ import ( "gopkg.in/irc.v4" "gorm.io/gorm" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" ) @@ -15,7 +16,7 @@ type ( // Actor defines an interface to implement in the plugin for actors 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.FieldCollection, attrs *fieldcollection.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 @@ -26,7 +27,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(TemplateValidatorFunc, *FieldCollection) error + Validate(TemplateValidatorFunc, *fieldcollection.FieldCollection) error } // ActorCreationFunc is a function to return a new instance of the @@ -62,7 +63,7 @@ type ( // EventHandlerFunc defines the type of function required to listen // for events - EventHandlerFunc func(evt string, eventData *FieldCollection) error + EventHandlerFunc func(evt string, eventData *fieldcollection.FieldCollection) error // EventHandlerRegisterFunc is passed from the bot to the // plugins RegisterFunc to register a new event handler function // which is then fed with all events occurring in the bot @@ -76,12 +77,12 @@ type ( // ModuleConfigGetterFunc is passed from the bot to the // plugins RegisterFunc to fetch module generic or channel specific // configuration from the module configuration - ModuleConfigGetterFunc func(module, channel string) *FieldCollection + ModuleConfigGetterFunc func(module, channel string) *fieldcollection.FieldCollection // MsgFormatter is passed from the bot to the // plugins RegisterFunc to format messages using all registered and // available template functions - MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields *FieldCollection) (string, error) + MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields *fieldcollection.FieldCollection) (string, error) // MsgModificationFunc can be used to modify messages between the // plugins generating them and the bot sending them to the Twitch @@ -160,7 +161,7 @@ type ( // TemplateFuncGetter is the type of function to implement in the // plugin to create a new template function on request of the bot - TemplateFuncGetter func(*irc.Message, *Rule, *FieldCollection) any + TemplateFuncGetter func(*irc.Message, *Rule, *fieldcollection.FieldCollection) any // TemplateFuncRegister is passed from the bot to the // plugins RegisterFunc to register a new TemplateFuncGetter TemplateFuncRegister func(name string, fg TemplateFuncGetter, doc ...TemplateFuncDocumentation) @@ -184,5 +185,5 @@ var ErrSkipSendingMessage = errors.New("skip sending message") // requiring access to the irc.Message, Rule or FieldCollection to // satisfy the TemplateFuncGetter interface func GenericTemplateFunctionGetter(f any) TemplateFuncGetter { - return func(*irc.Message, *Rule, *FieldCollection) any { return f } + return func(*irc.Message, *Rule, *fieldcollection.FieldCollection) any { return f } } diff --git a/plugins/moduleConfig.go b/plugins/moduleConfig.go index 3afec4c..8ff9f17 100644 --- a/plugins/moduleConfig.go +++ b/plugins/moduleConfig.go @@ -1,6 +1,10 @@ package plugins -import "strings" +import ( + "strings" + + "github.com/Luzifer/go_helpers/v2/fieldcollection" +) // DefaultConfigName is the name the default configuration must have // when defined @@ -9,16 +13,16 @@ const DefaultConfigName = "default" type ( // ModuleConfig represents a mapping of configurations per channel // and module - ModuleConfig map[string]map[string]*FieldCollection + ModuleConfig map[string]map[string]*fieldcollection.FieldCollection ) // GetChannelConfig reads the channel specific configuration for the // given module. This is created by taking an empty FieldCollection, // merging in the default configuration and finally overwriting all // existing channel configurations. -func (m ModuleConfig) GetChannelConfig(module, channel string) *FieldCollection { +func (m ModuleConfig) GetChannelConfig(module, channel string) *fieldcollection.FieldCollection { channel = strings.TrimLeft(channel, "#@") - composed := NewFieldCollection() + composed := fieldcollection.NewFieldCollection() for _, i := range []string{DefaultConfigName, channel} { f := m[module][i] diff --git a/plugins/moduleConfig_test.go b/plugins/moduleConfig_test.go index 0e02d69..c401b75 100644 --- a/plugins/moduleConfig_test.go +++ b/plugins/moduleConfig_test.go @@ -3,6 +3,7 @@ package plugins import ( "testing" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -10,12 +11,12 @@ import ( func TestModuleConfigGet(t *testing.T) { strPtrEmpty := func(v string) *string { return &v }("") m := ModuleConfig{ - "test": map[string]*FieldCollection{ - DefaultConfigName: FieldCollectionFromData(map[string]any{ + "test": map[string]*fieldcollection.FieldCollection{ + DefaultConfigName: fieldcollection.FieldCollectionFromData(map[string]any{ "setindefault": DefaultConfigName, "setinboth": DefaultConfigName, }), - "test": FieldCollectionFromData(map[string]any{ + "test": fieldcollection.FieldCollectionFromData(map[string]any{ "setinchannel": "channel", "setinboth": "channel", }), diff --git a/plugins/rule.go b/plugins/rule.go index c2e261b..267a365 100644 --- a/plugins/rule.go +++ b/plugins/rule.go @@ -17,6 +17,7 @@ import ( "gopkg.in/irc.v4" "gopkg.in/yaml.v3" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" ) @@ -73,8 +74,8 @@ type ( // RuleAction represents an action to be executed when running a Rule 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.FieldCollection `json:"attributes" yaml:"attributes,omitempty"` } ) @@ -89,7 +90,7 @@ func (r Rule) MatcherID() string { } // Matches checks whether the Rule should be executed for the given parameters -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.FieldCollection) bool { r.msgFormatter = msgFormatter r.timerStore = timerStore r.twitchClient = twitchClient @@ -102,7 +103,7 @@ func (r *Rule) Matches(m *irc.Message, event *string, timerStore TimerStore, msg }) ) - for _, matcher := range []func(*logrus.Entry, *irc.Message, *string, twitch.BadgeCollection, *FieldCollection) bool{ + for _, matcher := range []func(*logrus.Entry, *irc.Message, *string, twitch.BadgeCollection, *fieldcollection.FieldCollection) bool{ r.allowExecuteDisable, r.allowExecuteChannelWhitelist, r.allowExecuteUserWhitelist, @@ -144,7 +145,7 @@ func (r *Rule) GetMatchMessage() *regexp.Regexp { // SetCooldown uses the given TimerStore to set the cooldowns for the // Rule after execution -func (r *Rule) SetCooldown(timerStore TimerStore, m *irc.Message, evtData *FieldCollection) { +func (r *Rule) SetCooldown(timerStore TimerStore, m *irc.Message, evtData *fieldcollection.FieldCollection) { var err error if r.Cooldown != nil { @@ -250,7 +251,7 @@ func (r Rule) Validate(tplValidate TemplateValidatorFunc) error { return nil } -func (r *Rule) allowExecuteBadgeBlacklist(logger *logrus.Entry, _ *irc.Message, _ *string, badges twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteBadgeBlacklist(logger *logrus.Entry, _ *irc.Message, _ *string, badges twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { for _, b := range r.DisableOn { if badges.Has(b) { logger.Tracef("Non-Match: Disable-Badge %s", b) @@ -261,7 +262,7 @@ func (r *Rule) allowExecuteBadgeBlacklist(logger *logrus.Entry, _ *irc.Message, return true } -func (r *Rule) allowExecuteBadgeWhitelist(_ *logrus.Entry, _ *irc.Message, _ *string, badges twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteBadgeWhitelist(_ *logrus.Entry, _ *irc.Message, _ *string, badges twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { if len(r.EnableOn) == 0 { // No match criteria set, does not speak against matching return true @@ -276,7 +277,7 @@ func (r *Rule) allowExecuteBadgeWhitelist(_ *logrus.Entry, _ *irc.Message, _ *st return false } -func (r *Rule) allowExecuteChannelCooldown(logger *logrus.Entry, m *irc.Message, _ *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteChannelCooldown(logger *logrus.Entry, m *irc.Message, _ *string, badges twitch.BadgeCollection, evtData *fieldcollection.FieldCollection) bool { if r.ChannelCooldown == nil || DeriveChannel(m, evtData) == "" { // No match criteria set, does not speak against matching return true @@ -301,7 +302,7 @@ func (r *Rule) allowExecuteChannelCooldown(logger *logrus.Entry, m *irc.Message, return false } -func (r *Rule) allowExecuteChannelWhitelist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteChannelWhitelist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *fieldcollection.FieldCollection) bool { if len(r.MatchChannels) == 0 { // No match criteria set, does not speak against matching return true @@ -315,7 +316,7 @@ func (r *Rule) allowExecuteChannelWhitelist(logger *logrus.Entry, m *irc.Message return true } -func (r *Rule) allowExecuteDisable(logger *logrus.Entry, _ *irc.Message, _ *string, _ twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteDisable(logger *logrus.Entry, _ *irc.Message, _ *string, _ twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { if r.Disable == nil { // No match criteria set, does not speak against matching return true @@ -329,7 +330,7 @@ func (r *Rule) allowExecuteDisable(logger *logrus.Entry, _ *irc.Message, _ *stri return true } -func (r *Rule) allowExecuteDisableOnOffline(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteDisableOnOffline(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *fieldcollection.FieldCollection) bool { if r.DisableOnOffline == nil || !*r.DisableOnOffline || DeriveChannel(m, evtData) == "" { // No match criteria set, does not speak against matching return true @@ -348,7 +349,7 @@ func (r *Rule) allowExecuteDisableOnOffline(logger *logrus.Entry, m *irc.Message return true } -func (r *Rule) allowExecuteDisableOnPermit(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteDisableOnPermit(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *fieldcollection.FieldCollection) bool { hasPermit, err := r.timerStore.HasPermit(DeriveChannel(m, evtData), DeriveUser(m, evtData)) if err != nil { logger.WithError(err).Error("checking permit") @@ -363,7 +364,7 @@ func (r *Rule) allowExecuteDisableOnPermit(logger *logrus.Entry, m *irc.Message, return true } -func (r *Rule) allowExecuteDisableOnTemplate(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteDisableOnTemplate(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *fieldcollection.FieldCollection) bool { if r.DisableOnTemplate == nil || *r.DisableOnTemplate == "" { // No match criteria set, does not speak against matching return true @@ -384,7 +385,7 @@ func (r *Rule) allowExecuteDisableOnTemplate(logger *logrus.Entry, m *irc.Messag return true } -func (r *Rule) allowExecuteEventMatch(logger *logrus.Entry, _ *irc.Message, event *string, _ twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteEventMatch(logger *logrus.Entry, _ *irc.Message, event *string, _ twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { // The user defines either no event to match or they define an // event to match. We now need to ensure this match is valid for // the current execution: @@ -430,7 +431,7 @@ func (r *Rule) allowExecuteEventMatch(logger *logrus.Entry, _ *irc.Message, even return false } -func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { if len(r.DisableOnMatchMessages) == 0 { // No match criteria set, does not speak against matching return true @@ -459,7 +460,7 @@ func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *logrus.Entry, m *irc. return true } -func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { if r.MatchMessage == nil { // No match criteria set, does not speak against matching return true @@ -484,7 +485,7 @@ func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *logrus.Entry, m *irc. return true } -func (r *Rule) allowExecuteRuleCooldown(logger *logrus.Entry, _ *irc.Message, _ *string, badges twitch.BadgeCollection, _ *FieldCollection) bool { +func (r *Rule) allowExecuteRuleCooldown(logger *logrus.Entry, _ *irc.Message, _ *string, badges twitch.BadgeCollection, _ *fieldcollection.FieldCollection) bool { if r.Cooldown == nil { // No match criteria set, does not speak against matching return true @@ -509,7 +510,7 @@ func (r *Rule) allowExecuteRuleCooldown(logger *logrus.Entry, _ *irc.Message, _ return false } -func (r *Rule) allowExecuteUserCooldown(logger *logrus.Entry, m *irc.Message, _ *string, badges twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteUserCooldown(logger *logrus.Entry, m *irc.Message, _ *string, badges twitch.BadgeCollection, evtData *fieldcollection.FieldCollection) bool { if r.UserCooldown == nil { // No match criteria set, does not speak against matching return true @@ -534,7 +535,7 @@ func (r *Rule) allowExecuteUserCooldown(logger *logrus.Entry, m *irc.Message, _ return false } -func (r *Rule) allowExecuteUserWhitelist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *FieldCollection) bool { +func (r *Rule) allowExecuteUserWhitelist(logger *logrus.Entry, m *irc.Message, _ *string, _ twitch.BadgeCollection, evtData *fieldcollection.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 5bb6e1b..ee6d94f 100644 --- a/plugins/rule_test.go +++ b/plugins/rule_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" ) @@ -141,7 +142,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(string, *irc.Message, *Rule, *FieldCollection) (string, error) { + r.msgFormatter = func(string, *irc.Message, *Rule, *fieldcollection.FieldCollection) (string, error) { return msg, nil } diff --git a/plugins_core.go b/plugins_core.go index 7c66470..91fa1b7 100644 --- a/plugins_core.go +++ b/plugins_core.go @@ -10,6 +10,7 @@ import ( "gopkg.in/irc.v4" "github.com/Luzifer/go_helpers/v2/backoff" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/twitch-bot/v3/internal/actors/announce" "github.com/Luzifer/twitch-bot/v3/internal/actors/ban" @@ -181,12 +182,12 @@ func getRegistrationArguments() plugins.RegistrationArguments { SendMessage: sendMessage, ValidateToken: authService.ValidateTokenFor, - CreateEvent: func(evt string, eventData *plugins.FieldCollection) error { + CreateEvent: func(evt string, eventData *fieldcollection.FieldCollection) error { handleMessage(ircHdl.Client(), nil, &evt, eventData) return nil }, - GetModuleConfigForChannel: func(module, channel string) *plugins.FieldCollection { + GetModuleConfigForChannel: func(module, channel string) *fieldcollection.FieldCollection { return config.ModuleConfig.GetChannelConfig(module, channel) }, diff --git a/tplDocs.go b/tplDocs.go index 35e2a7c..5354cb3 100644 --- a/tplDocs.go +++ b/tplDocs.go @@ -12,6 +12,7 @@ import ( "github.com/sirupsen/logrus" "gopkg.in/irc.v4" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/plugins" ) @@ -88,7 +89,7 @@ func generateTplDocsRender(e *plugins.TemplateFuncDocumentationExample) (string, rule.MatchMessage = &e.MatchMessage } - return formatMessage(e.Template, msg, rule, plugins.FieldCollectionFromData(map[string]any{ + return formatMessage(e.Template, msg, rule, fieldcollection.FieldCollectionFromData(map[string]any{ "testDuration": 5*time.Hour + 33*time.Minute + 12*time.Second, })) } diff --git a/twitchWatcher.go b/twitchWatcher.go index 44ca537..fb88c63 100644 --- a/twitchWatcher.go +++ b/twitchWatcher.go @@ -8,10 +8,10 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" + "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/internal/service/access" "github.com/Luzifer/twitch-bot/v3/pkg/twitch" - "github.com/Luzifer/twitch-bot/v3/plugins" ) type ( @@ -241,7 +241,7 @@ func (*twitchWatcher) handleEventSubChannelAdBreakBegin(m json.RawMessage) error return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]any{ + fields := fieldcollection.FieldCollectionFromData(map[string]any{ "channel": "#" + payload.BroadcasterUserLogin, "duration": payload.Duration, "is_automatic": payload.IsAutomatic, @@ -260,7 +260,7 @@ func (*twitchWatcher) handleEventSubChannelFollow(m json.RawMessage) error { return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ "channel": "#" + payload.BroadcasterUserLogin, "followed_at": payload.FollowedAt, "user_id": payload.UserID, @@ -279,7 +279,7 @@ func (*twitchWatcher) handleEventSubChannelPointCustomRewardRedemptionAdd(m json return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ "channel": "#" + payload.BroadcasterUserLogin, "reward_cost": payload.Reward.Cost, "reward_id": payload.Reward.ID, @@ -302,7 +302,7 @@ func (*twitchWatcher) handleEventSubChannelOutboundRaid(m json.RawMessage) error return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]interface{}{ + fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{ "channel": "#" + payload.FromBroadcasterUserLogin, "to_id": payload.ToBroadcasterUserID, "to": payload.ToBroadcasterUserLogin, @@ -333,7 +333,7 @@ func (*twitchWatcher) handleEventSubChannelPollChange(event *string) func(json.R return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]any{ + fields := fieldcollection.FieldCollectionFromData(map[string]any{ "channel": "#" + payload.BroadcasterUserLogin, "hasChannelPointVoting": payload.ChannelPointsVoting.IsEnabled, "title": payload.Title, @@ -370,7 +370,7 @@ func (*twitchWatcher) handleEventSubHypetrainEvent(eventType *string) func(json. return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]any{ + fields := fieldcollection.FieldCollectionFromData(map[string]any{ "channel": "#" + payload.BroadcasterUserLogin, "level": payload.Level, }) @@ -394,7 +394,7 @@ func (*twitchWatcher) handleEventSubShoutoutCreated(m json.RawMessage) error { return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]any{ + fields := fieldcollection.FieldCollectionFromData(map[string]any{ "channel": "#" + payload.BroadcasterUserLogin, "to_id": payload.ToBroadcasterUserID, "to": payload.ToBroadcasterUserLogin, @@ -413,7 +413,7 @@ func (*twitchWatcher) handleEventSubShoutoutReceived(m json.RawMessage) error { return errors.Wrap(err, "unmarshalling event") } - fields := plugins.FieldCollectionFromData(map[string]any{ + fields := fieldcollection.FieldCollectionFromData(map[string]any{ "channel": "#" + payload.BroadcasterUserLogin, "from_id": payload.FromBroadcasterUserID, "from": payload.FromBroadcasterUserLogin, @@ -570,7 +570,7 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o "channel": channel, "category": *category, }).Info("Category updated") - go handleMessage(ircHdl.Client(), nil, eventTypeTwitchCategoryUpdate, plugins.FieldCollectionFromData(map[string]interface{}{ + go handleMessage(ircHdl.Client(), nil, eventTypeTwitchCategoryUpdate, fieldcollection.FieldCollectionFromData(map[string]interface{}{ "channel": "#" + channel, "category": *category, })) @@ -582,7 +582,7 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o "channel": channel, "title": *title, }).Info("Title updated") - go handleMessage(ircHdl.Client(), nil, eventTypeTwitchTitleUpdate, plugins.FieldCollectionFromData(map[string]interface{}{ + go handleMessage(ircHdl.Client(), nil, eventTypeTwitchTitleUpdate, fieldcollection.FieldCollectionFromData(map[string]interface{}{ "channel": "#" + channel, "title": *title, })) @@ -600,7 +600,7 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o evt = eventTypeTwitchStreamOffline } - go handleMessage(ircHdl.Client(), nil, evt, plugins.FieldCollectionFromData(map[string]interface{}{ + go handleMessage(ircHdl.Client(), nil, evt, fieldcollection.FieldCollectionFromData(map[string]interface{}{ "channel": "#" + channel, })) }