[core] Switch to go_helpers FieldCollection

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2024-04-03 21:00:28 +02:00
parent 262742603c
commit 30482591a7
Signed by: luzifer
SSH Key Fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
65 changed files with 548 additions and 974 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -49,7 +50,7 @@ func init() {
type ActorScript struct{} type ActorScript struct{}
// Execute implements actor interface // 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") command, err := attrs.StringSlice("command")
if err != nil { if err != nil {
return false, errors.Wrap(err, "getting command") return false, errors.Wrap(err, "getting command")
@ -130,7 +131,7 @@ func (ActorScript) IsAsync() bool { return false }
func (ActorScript) Name() string { return "script" } func (ActorScript) Name() string { return "script" }
// Validate implements actor interface // 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") cmd, err := attrs.StringSlice("command")
if err != nil || len(cmd) == 0 { if err != nil || len(cmd) == 0 {
return errors.New("command must be slice of strings with length > 0") return errors.New("command must be slice of strings with length > 0")

View File

@ -7,6 +7,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -41,7 +42,7 @@ func registerAction(name string, acf plugins.ActorCreationFunc) {
availableActions[name] = acf 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() availableActionsLock.RLock()
defer availableActionsLock.RUnlock() 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") 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 // Send events to registered handlers
if event != nil { if event != nil {
go notifyEventHandlers(*event, eventData) 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 ( var (
ruleEventData = plugins.NewFieldCollection() ruleEventData = fieldcollection.NewFieldCollection()
preventCooldown bool preventCooldown bool
) )

View File

@ -13,8 +13,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "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) 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 return true
} }
fields := plugins.NewFieldCollection() fields := fieldcollection.NewFieldCollection()
fields.Set("channel", a.Channel) fields.Set("channel", a.Channel)
res, err := formatMessage(*a.DisableOnTemplate, nil, nil, fields) res, err := formatMessage(*a.DisableOnTemplate, nil, nil, fields)

View File

@ -19,6 +19,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -303,7 +304,7 @@ func (c *configFile) CloseRawMessageWriter() (err error) {
return nil 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() configLock.RLock()
defer configLock.RUnlock() defer configLock.RUnlock()

View File

@ -5,6 +5,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "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() registeredEventHandlersLock.Lock()
defer registeredEventHandlersLock.Unlock() defer registeredEventHandlersLock.Unlock()

View File

@ -11,6 +11,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/go_helpers/v2/str"
korvike "github.com/Luzifer/korvike/functions" korvike "github.com/Luzifer/korvike/functions"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
@ -37,7 +38,7 @@ func newTemplateFuncProvider() *templateFuncProvider {
return out 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() t.lock.RLock()
defer t.lock.RUnlock() defer t.lock.RUnlock()

View File

@ -6,12 +6,13 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
func init() { 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) { return func(arg int) (string, error) {
msgParts := strings.Split(m.Trailing(), " ") msgParts := strings.Split(m.Trailing(), " ")
if len(msgParts) <= arg { 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 { return func(badge string) bool {
badges := twitch.ParseBadgeLevels(m) badges := twitch.ParseBadgeLevels(m)
return badges.Has(badge) 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) { return func(idx int, fallback ...string) (string, error) {
fields := r.GetMatchMessage().FindStringSubmatch(m.Trailing()) fields := r.GetMatchMessage().FindStringSubmatch(m.Trailing())
if len(fields) <= idx { 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] } return func(tag string) string { return m.Tags[tag] }
}, plugins.TemplateFuncDocumentation{ }, plugins.TemplateFuncDocumentation{
Description: "Takes the message sent to the channel, returns the value of the tag specified", Description: "Takes the message sent to the channel, returns the value of the tag specified",

12
go.mod
View File

@ -4,19 +4,19 @@ go 1.21
require ( require (
github.com/Luzifer/go-openssl/v4 v4.2.2 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/korvike/functions v0.11.0
github.com/Luzifer/rconfig/v2 v2.5.0 github.com/Luzifer/rconfig/v2 v2.5.0
github.com/Masterminds/sprig/v3 v3.2.3 github.com/Masterminds/sprig/v3 v3.2.3
github.com/getsentry/sentry-go v0.27.0 github.com/getsentry/sentry-go v0.27.0
github.com/glebarez/sqlite v1.11.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/go-sql-driver/mysql v1.8.1
github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/gofrs/uuid/v3 v3.1.2 github.com/gofrs/uuid/v3 v3.1.2
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.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/mitchellh/hashstructure/v2 v2.0.2
github.com/orandin/sentrus v1.0.0 github.com/orandin/sentrus v1.0.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
@ -31,7 +31,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.6 gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.7 gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.8 gorm.io/gorm v1.25.9
) )
require ( require (
@ -87,7 +87,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.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/shopspring/decimal v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
@ -105,6 +105,6 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.49.0 // indirect modernc.org/libc v1.49.0 // indirect
modernc.org/mathutil v1.6.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 modernc.org/sqlite v1.29.5 // indirect
) )

28
go.sum
View File

@ -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/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 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc=
github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc= 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.24.0 h1:abACOhsn6a6c6X22jq42mZM1wuOM0Ihfa6yzssrjrOg=
github.com/Luzifer/go_helpers/v2 v2.23.0/go.mod h1:BSGkJ/dxqs7AxsfZt8zjJb4R6YB5dONS+/ad7foLUrk= 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 h1:2hr3nnt9hy8Esu1W3h50+RggcLRXvrw92kVQLvxzd2Q=
github.com/Luzifer/korvike/functions v0.11.0/go.mod h1:osumwH64mWgbwZIfE7rE0BB7Y5HXxrzyO4JfO7fhduU= github.com/Luzifer/korvike/functions v0.11.0/go.mod h1:osumwH64mWgbwZIfE7rE0BB7Y5HXxrzyO4JfO7fhduU=
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok= 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/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 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= 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.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= 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 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 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= 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-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 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-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.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
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/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 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-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= 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.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 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/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.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI=
github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s= 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 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 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/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 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 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.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 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.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 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 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 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= 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.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/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= 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/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 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= 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 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=

View File

@ -11,6 +11,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -87,7 +89,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
type actor struct{} 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 }("") ptrStringEmpty := func(v string) *string { return &v }("")
reason, err := formatMessage(attrs.MustString("reason", ptrStringEmpty), m, r, eventData) 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
reasonTemplate, err := attrs.String("reason") if err = attrs.ValidateSchema(
if err != nil || reasonTemplate == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("reason must be non-empty string") fieldcollection.MustHaveNoUnknowFields,
} helpers.SchemaValidateTemplateField(tplValidator, "reason"),
); err != nil {
if err = tplValidator(reasonTemplate); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrap(err, "validating reason template")
} }
return nil return nil

View File

@ -9,6 +9,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -19,9 +21,6 @@ var (
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
hasPerm plugins.ChannelPermissionCheckFunc hasPerm plugins.ChannelPermissionCheckFunc
tcGetter func(string) (*twitch.Client, error) 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 // Register provides the plugins.RegisterFunc
@ -71,7 +70,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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) channel := plugins.DeriveChannel(m, eventData)
if channel, err = formatMessage(attrs.MustString("channel", &channel), m, r, eventData); err != nil { if channel, err = formatMessage(attrs.MustString("channel", &channel), m, r, eventData); err != nil {
return false, errors.Wrap(err, "parsing channel") 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) 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 { if err != nil {
return false, errors.Wrap(err, "creating clip") 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) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
for _, field := range []string{"channel", "creator"} { if err = attrs.ValidateSchema(
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.Wrapf(err, "validating %s template", field) 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 return nil

View File

@ -9,6 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/internal/actors/linkdetector"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
@ -40,7 +41,7 @@ func Register(args plugins.RegistrationArguments) error {
type Actor struct{} type Actor struct{}
// Execute implements the actor interface // 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") { if eventData.HasAll("clips") {
// We already detected clips, lets not do it again // We already detected clips, lets not do it again
return false, nil return false, nil
@ -82,4 +83,6 @@ func (Actor) IsAsync() bool { return false }
func (Actor) Name() string { return actorName } func (Actor) Name() string { return actorName }
// Validate implements the actor interface // 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
}

View File

@ -3,6 +3,7 @@ package commercial
import ( import (
"context" "context"
"fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -10,6 +11,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -61,7 +64,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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 }("") ptrStringEmpty := func(v string) *string { return &v }("")
durationStr, err := formatMessage(attrs.MustString("duration", ptrStringEmpty), m, r, eventData) 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
durationTemplate, err := attrs.String("duration") if err = attrs.ValidateSchema(
if err != nil || durationTemplate == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "duration", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("duration must be non-empty string") helpers.SchemaValidateTemplateField(tplValidator, "duration"),
} ); err != nil {
return fmt.Errorf("validating attributes: %w", err)
if err = tplValidator(durationTemplate); err != nil {
return errors.Wrap(err, "validating duration template")
} }
return nil return nil

View File

@ -13,6 +13,8 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gorm.io/gorm" "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/database"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -20,8 +22,6 @@ import (
var ( var (
db database.Connector db database.Connector
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
ptrStringEmpty = func(s string) *string { return &s }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -135,7 +135,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
return fmt.Errorf("registering API route: %w", err) 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) { return func(name string) (string, error) {
channel, err := fields.String("channel") channel, err := fields.String("channel")
if err != nil { if err != nil {
@ -212,13 +212,13 @@ func Register(args plugins.RegistrationArguments) (err error) {
type actorCounter struct{} 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) counterName, err := formatMessage(attrs.MustString("counter", nil), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "preparing response") 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) parseValue, err := formatMessage(counterSet, m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "execute counter value template") 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 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) parseStep, err := formatMessage(s, m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "execute counter step template") 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) IsAsync() bool { return false }
func (actorCounter) Name() string { return "counter" } func (actorCounter) Name() string { return "counter" }
func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if cn, err := attrs.String("counter"); err != nil || cn == "" { if err = attrs.ValidateSchema(
return errors.New("counter name must be non-empty string") 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}),
for _, field := range []string{"counter", "counter_step", "counter_set"} { helpers.SchemaValidateTemplateField(tplValidator, "counter", "counter_step", "counter_set"),
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { ); err != nil {
return errors.Wrapf(err, "validating %s template", field) return fmt.Errorf("validating attributes: %w", err)
}
} }
return nil return nil

View File

@ -7,6 +7,8 @@ import (
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -48,11 +50,10 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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 ( var (
ptrZeroDuration = func(v time.Duration) *time.Duration { return &v }(0) delay = attrs.MustDuration("delay", helpers.Ptr(time.Duration(0)))
delay = attrs.MustDuration("delay", ptrZeroDuration) jitter = attrs.MustDuration("jitter", helpers.Ptr(time.Duration(0)))
jitter = attrs.MustDuration("jitter", ptrZeroDuration)
) )
if delay == 0 && jitter == 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } 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 return nil
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -32,7 +33,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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"] msgID, ok := m.Tags["id"]
if !ok || msgID == "" { if !ok || msgID == "" {
return false, nil 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } 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 return nil
} }

View File

@ -4,10 +4,13 @@ package eventmod
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -44,7 +47,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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 }("") ptrStringEmpty := func(v string) *string { return &v }("")
fd, err := formatMessage(attrs.MustString("fields", ptrStringEmpty), m, r, eventData) 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
fieldsTemplate, err := attrs.String("fields") if err = attrs.ValidateSchema(
if err != nil || fieldsTemplate == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "fields", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("fields must be non-empty string") helpers.SchemaValidateTemplateField(tplValidator, "fields"),
} ); err != nil {
return fmt.Errorf("validating attributes: %w", err)
if err = tplValidator(fieldsTemplate); err != nil {
return errors.Wrap(err, "validating fields template")
} }
return nil return nil

View File

@ -5,6 +5,7 @@ package filesay
import ( import (
"bufio" "bufio"
"context" "context"
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
@ -13,6 +14,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -57,7 +60,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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 }("") ptrStringEmpty := func(v string) *string { return &v }("")
source, err := formatMessage(attrs.MustString("source", ptrStringEmpty), m, r, eventData) 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) IsAsync() bool { return true }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) error { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
sourceTpl, err := attrs.String("source") if err = attrs.ValidateSchema(
if err != nil || sourceTpl == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "source", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("source is expected to be non-empty string") helpers.SchemaValidateTemplateField(tplValidator, "source"),
} ); err != nil {
return fmt.Errorf("validating attributes: %w", err)
if err = tplValidator(sourceTpl); err != nil {
return errors.Wrap(err, "validating source template")
} }
return nil return nil

View File

@ -5,14 +5,14 @@ package linkdetector
import ( import (
"gopkg.in/irc.v4" "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/internal/linkcheck"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
const actorName = "linkdetector" const actorName = "linkdetector"
var ptrFalse = func(v bool) *bool { return &v }(false)
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
func Register(args plugins.RegistrationArguments) error { func Register(args plugins.RegistrationArguments) error {
args.RegisterActor(actorName, func() plugins.Actor { return &Actor{} }) args.RegisterActor(actorName, func() plugins.Actor { return &Actor{} })
@ -42,13 +42,13 @@ func Register(args plugins.RegistrationArguments) error {
type Actor struct{} type Actor struct{}
// Execute implements the actor interface // 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") { if eventData.HasAll("links") {
// We already detected links, lets not do it again // We already detected links, lets not do it again
return false, nil return false, nil
} }
if attrs.MustBool("heuristic", ptrFalse) { if attrs.MustBool("heuristic", helpers.Ptr(false)) {
eventData.Set("links", linkcheck.New().HeuristicScanForLinks(m.Trailing())) eventData.Set("links", linkcheck.New().HeuristicScanForLinks(m.Trailing()))
} else { } else {
eventData.Set("links", linkcheck.New().ScanForLinks(m.Trailing())) eventData.Set("links", linkcheck.New().ScanForLinks(m.Trailing()))
@ -64,4 +64,6 @@ func (Actor) IsAsync() bool { return false }
func (Actor) Name() string { return actorName } func (Actor) Name() string { return actorName }
// Validate implements the actor interface // 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
}

View File

@ -4,6 +4,7 @@ package linkprotect
import ( import (
"context" "context"
"fmt"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -11,7 +12,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/actors/clipdetector"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -21,8 +24,6 @@ const actorName = "linkprotect"
var ( var (
botTwitchClient *twitch.Client botTwitchClient *twitch.Client
clipLink = regexp.MustCompile(`.*(?:clips\.twitch\.tv|www\.twitch\.tv/[^/]*/clip)/.*`) 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 // Register provides the plugins.RegisterFunc
@ -127,7 +128,7 @@ const (
) )
//nolint:gocyclo // Minimum over the limit, makes no sense to split //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 // 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 { if preventCooldown, err = (clipdetector.Actor{}).Execute(c, m, r, eventData, attrs); err != nil {
return preventCooldown, errors.Wrap(err, "detecting links / clips") 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 len(links) == 0 {
// If there are no links there is nothing to protect and there // If there are no links there is nothing to protect and there
// are also no clips as they are parsed from the links // 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, plugins.ErrStopRuleExecution
} }
return false, nil return false, nil
} }
clipsInterface, err := eventData.Any("clips") clipsInterface, err := eventData.Get("clips")
if err != nil { if err != nil {
return preventCooldown, errors.Wrap(err, "getting clips from event") 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 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, plugins.ErrStopRuleExecution
} }
return false, nil return false, nil
} }
// That message misbehaved so we need to punish them // 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": case "ban":
if err = botTwitchClient.BanUser( if err = botTwitchClient.BanUser(
context.Background(), context.Background(),
plugins.DeriveChannel(m, eventData), plugins.DeriveChannel(m, eventData),
strings.TrimLeft(plugins.DeriveUser(m, eventData), "@"), strings.TrimLeft(plugins.DeriveUser(m, eventData), "@"),
0, 0,
attrs.MustString("reason", ptrStringEmpty), attrs.MustString("reason", helpers.Ptr("")),
); err != nil { ); err != nil {
return false, errors.Wrap(err, "executing user ban") 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), plugins.DeriveChannel(m, eventData),
strings.TrimLeft(plugins.DeriveUser(m, eventData), "@"), strings.TrimLeft(plugins.DeriveUser(m, eventData), "@"),
to, to,
attrs.MustString("reason", ptrStringEmpty), attrs.MustString("reason", helpers.Ptr("")),
); err != nil { ); err != nil {
return false, errors.Wrap(err, "executing user ban") 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 return false, plugins.ErrStopRuleExecution
} }
@ -218,41 +219,42 @@ func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) error { func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("action"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("action must be non-empty string") 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 v, err := attrs.String("reason"); err != nil || v == "" { if len(attrs.MustStringSlice("allowed_links", helpers.Ptr([]string{})))+
return errors.New("reason must be non-empty 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 {
if len(attrs.MustStringSlice("allowed_links"))+ return errors.New("no conditions are provided")
len(attrs.MustStringSlice("disallowed_links"))+ }
len(attrs.MustStringSlice("allowed_clip_channels"))+ return nil
len(attrs.MustStringSlice("disallowed_clip_channels")) == 0 { },
return errors.New("no conditions are provided") ); err != nil {
return fmt.Errorf("validating attributes: %w", err)
} }
return nil return nil
} }
func (a actor) check(links []string, clips []twitch.ClipInfo, attrs *plugins.FieldCollection) (v verdict) { func (a actor) check(links []string, clips []twitch.ClipInfo, attrs *fieldcollection.FieldCollection) (v verdict) {
hasClipDefinition := len(attrs.MustStringSlice("allowed_clip_channels"))+len(attrs.MustStringSlice("disallowed_clip_channels")) > 0 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 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 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 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 return verdictMisbehave
} }

View File

@ -2,18 +2,19 @@
package log package log
import ( import (
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/sirupsen/logrus" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
var ( var formatMessage plugins.MsgFormatter
formatMessage plugins.MsgFormatter
ptrStringEmpty = func(v string) *string { return &v }("")
)
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
func Register(args plugins.RegistrationArguments) error { func Register(args plugins.RegistrationArguments) error {
@ -44,8 +45,8 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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) {
message, err := formatMessage(attrs.MustString("message", ptrStringEmpty), m, r, eventData) message, err := formatMessage(attrs.MustString("message", helpers.Ptr("")), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "executing message template") 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) IsAsync() bool { return true }
func (actor) Name() string { return "log" } func (actor) Name() string { return "log" }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("message"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("message must be non-empty string") fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "message", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
} helpers.SchemaValidateTemplateField(tplValidator, "message"),
); err != nil {
if err = tplValidator(attrs.MustString("message", ptrStringEmpty)); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrap(err, "validating message template")
} }
return nil return nil

View File

@ -20,12 +20,7 @@ const (
postTimeout = 5 * time.Second postTimeout = 5 * time.Second
) )
var ( var formatMessage plugins.MsgFormatter
formatMessage plugins.MsgFormatter
ptrBoolFalse = func(v bool) *bool { return &v }(false)
ptrStringEmpty = func(s string) *string { return &s }("")
)
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
func Register(args plugins.RegistrationArguments) error { func Register(args plugins.RegistrationArguments) error {

View File

@ -7,6 +7,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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" "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 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") 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") 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") 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 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) IsAsync() bool { return false }
func (discordActor) Name() string { return "discordhook" } 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 { if err = d.ValidateRequireNonEmpty(attrs, "hook_url"); err != nil {
return err //nolint:wrapcheck return err //nolint:wrapcheck
} }
@ -89,7 +91,7 @@ func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs
return err //nolint:wrapcheck 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 // We're not validating the rest if embeds are disabled but in
// this case the content is mandatory // this case the content is mandatory
return d.ValidateRequireNonEmpty(attrs, "content") //nolint:wrapcheck 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 //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) { func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (err error) {
if !attrs.MustBool("add_embed", ptrBoolFalse) { if !attrs.MustBool("add_embed", helpers.Ptr(false)) {
// No embed? No problem! // No embed? No problem!
return nil return nil
} }
@ -122,45 +124,45 @@ func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins
sv string 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") 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") 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") 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") return errors.Wrap(err, "parsing embed_image")
} else if sv != "" { } else if sv != "" {
embed.Image = &discordPayloadEmbedImage{URL: 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") return errors.Wrap(err, "parsing embed_thumbnail")
} else if sv != "" { } else if sv != "" {
embed.Thumbnail = &discordPayloadEmbedImage{URL: 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") return errors.Wrap(err, "parsing embed_author_name")
} else if sv != "" { } else if sv != "" {
embed.Author = &discordPayloadEmbedAuthor{Name: 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") 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") 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") return errors.Wrap(err, "parsing embed_fields")
} else if sv != "" { } else if sv != "" {
var flds []discordPayloadEmbedField var flds []discordPayloadEmbedField

View File

@ -7,6 +7,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -14,14 +16,14 @@ type slackCompatibleActor struct {
plugins.ActorKit 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) text, err := formatMessage(attrs.MustString("text", nil), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "parsing text") return false, errors.Wrap(err, "parsing text")
} }
return sendPayload( return sendPayload(
s.fixHookURL(attrs.MustString("hook_url", ptrStringEmpty)), s.fixHookURL(attrs.MustString("hook_url", helpers.Ptr(""))),
map[string]string{ map[string]string{
"text": text, "text": text,
}, },
@ -33,7 +35,7 @@ func (slackCompatibleActor) IsAsync() bool { return false }
func (slackCompatibleActor) Name() string { return "slackhook" } 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 { if err = s.ValidateRequireNonEmpty(attrs, "hook_url", "text"); err != nil {
return err //nolint:wrapcheck return err //nolint:wrapcheck
} }

View File

@ -4,11 +4,14 @@ package modchannel
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -18,8 +21,6 @@ const actorName = "modchannel"
var ( var (
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
tcGetter func(string) (*twitch.Client, error) tcGetter func(string) (*twitch.Client, error)
ptrStringEmpty = func(s string) *string { return &s }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -70,10 +71,10 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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 ( var (
game = attrs.MustString("game", ptrStringEmpty) game = attrs.MustString("game", helpers.Ptr(""))
title = attrs.MustString("title", ptrStringEmpty) title = attrs.MustString("title", helpers.Ptr(""))
) )
if game == "" && title == "" { 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("channel"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("channel must be non-empty string") 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}),
for _, field := range []string{"channel", "game", "title"} { helpers.SchemaValidateTemplateField(tplValidator, "channel", "game", "title"),
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { ); err != nil {
return errors.Wrapf(err, "validating %s template", field) return fmt.Errorf("validating attributes: %w", err)
}
} }
return nil return nil

View File

@ -4,6 +4,7 @@
package nuke package nuke
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
@ -13,7 +14,9 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -29,10 +32,6 @@ var (
messageStore = map[string][]*storedMessage{} messageStore = map[string][]*storedMessage{}
messageStoreLock sync.RWMutex 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 // 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) rawMatch, err := formatMessage(attrs.MustString("match", nil), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "formatting match") return false, errors.Wrap(err, "formatting match")
} }
match := regexp.MustCompile(rawMatch) 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 { if err != nil {
return false, errors.Wrap(err, "formatting scan duration") 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 action actionFn
actionName string 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 { if err != nil {
return false, errors.Wrap(err, "formatting action") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("match"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("match must be non-empty string") 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}),
for _, field := range []string{"scan", "action", "match"} { helpers.SchemaValidateTemplateField(tplValidator, "scan", "action", "match"),
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { ); err != nil {
return errors.Wrapf(err, "validating %s template", field) return fmt.Errorf("validating attributes: %w", err)
}
} }
return nil return nil

View File

@ -4,6 +4,7 @@ package punish
import ( import (
"context" "context"
"fmt"
"math" "math"
"strings" "strings"
"time" "time"
@ -12,6 +13,8 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gorm.io/gorm" "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/database"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
@ -25,11 +28,9 @@ const (
) )
var ( var (
botTwitchClient *twitch.Client botTwitchClient *twitch.Client
db database.Connector db database.Connector
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
ptrDefaultCooldown = func(v time.Duration) *time.Duration { return &v }(oneWeek)
ptrStringEmpty = func(v string) *string { return &v }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -146,12 +147,12 @@ type (
// Punish // 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 ( var (
cooldown = attrs.MustDuration("cooldown", ptrDefaultCooldown) cooldown = attrs.MustDuration("cooldown", helpers.Ptr(oneWeek))
reason = attrs.MustString("reason", ptrStringEmpty) reason = attrs.MustString("reason", helpers.Ptr(""))
user = attrs.MustString("user", nil) user = attrs.MustString("user", nil)
uuid = attrs.MustString("uuid", ptrStringEmpty) uuid = attrs.MustString("uuid", helpers.Ptr(""))
) )
levels, err := attrs.StringSlice("levels") 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) IsAsync() bool { return false }
func (actorPunish) Name() string { return actorNamePunish } func (actorPunish) Name() string { return actorNamePunish }
func (actorPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actorPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("user"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("user must be non-empty string") 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}),
if v, err := attrs.StringSlice("levels"); err != nil || len(v) == 0 { fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("levels must be slice of strings with length > 0") fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "uuid", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
} helpers.SchemaValidateTemplateField(tplValidator, "user"),
); err != nil {
if err = tplValidator(attrs.MustString("user", ptrStringEmpty)); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrap(err, "validating user template")
} }
return nil return nil
@ -243,10 +243,10 @@ func (actorPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *p
// Reset // 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 ( var (
user = attrs.MustString("user", nil) 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 { 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) IsAsync() bool { return false }
func (actorResetPunish) Name() string { return actorNameResetPunish } func (actorResetPunish) Name() string { return actorNameResetPunish }
func (actorResetPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actorResetPunish) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("user"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("user must be non-empty string") 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"),
if err = tplValidator(attrs.MustString("user", ptrStringEmpty)); err != nil { ); err != nil {
return errors.Wrap(err, "validating user template") return fmt.Errorf("validating attributes: %w", err)
} }
return nil return nil

View File

@ -10,6 +10,8 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gorm.io/gorm" "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/database"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -23,9 +25,9 @@ var (
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
send plugins.SendMessageFunc send plugins.SendMessageFunc
ptrStringEmpty = func(v string) *string { return &v }("") // ptrStringEmpty = func(v string) *string { return &v }("")
ptrStringOutFormat = func(v string) *string { return &v }("Quote #{{ .index }}: {{ .quote }}") // ptrStringOutFormat = func(v string) *string { return &v }("Quote #{{ .index }}: {{ .quote }}")
ptrStringZero = func(v string) *string { return &v }("0") // ptrStringZero = func(v string) *string { return &v }("0")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -93,7 +95,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
return fmt.Errorf("registering API: %w", err) 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 func() (int, error) {
return getMaxQuoteIdx(db, plugins.DeriveChannel(m, nil)) return getMaxQuoteIdx(db, plugins.DeriveChannel(m, nil))
} }
@ -113,11 +115,11 @@ type (
actor struct{} 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 ( var (
action = attrs.MustString("action", ptrStringEmpty) action = attrs.MustString("action", helpers.Ptr(""))
indexStr = attrs.MustString("index", ptrStringZero) indexStr = attrs.MustString("index", helpers.Ptr("0"))
quote = attrs.MustString("quote", ptrStringEmpty) quote = attrs.MustString("quote", helpers.Ptr(""))
) )
if indexStr == "" { if indexStr == "" {
@ -166,7 +168,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
fields.Set("index", idx) fields.Set("index", idx)
fields.Set("quote", quote) 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) msg, err := formatMessage(format, m, r, fields)
if err != nil { if err != nil {
return false, errors.Wrap(err, "formatting output message") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
action := attrs.MustString("action", ptrStringEmpty) 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 { switch action {
case "add": case "add":
if v, err := attrs.String("quote"); err != nil || v == "" { 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": case "del":
if v, err := attrs.String("index"); err != nil || v == "" { 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": case "get":
// No requirements // No requirements
default: default:
return errors.New("action must be one of add, del or get") return fmt.Errorf("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 nil return nil

View File

@ -2,9 +2,13 @@
package raw package raw
import ( import (
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -13,8 +17,6 @@ const actorName = "raw"
var ( var (
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
send plugins.SendMessageFunc send plugins.SendMessageFunc
ptrStringEmpty = func(s string) *string { return &s }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -47,7 +49,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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) rawMsg, err := formatMessage(attrs.MustString("message", nil), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "preparing raw message") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("message"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("message must be non-empty string") fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "message", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
} helpers.SchemaValidateTemplateField(tplValidator, "message"),
); err != nil {
if err = tplValidator(attrs.MustString("message", ptrStringEmpty)); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrap(err, "validating message template")
} }
return nil return nil

View File

@ -12,6 +12,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -20,9 +22,6 @@ const actorName = "respond"
var ( var (
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
send plugins.SendMessageFunc send plugins.SendMessageFunc
ptrBoolFalse = func(v bool) *bool { return &v }(false)
ptrStringEmpty = func(s string) *string { return &s }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -102,7 +101,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
type actor struct{} 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) msg, err := formatMessage(attrs.MustString("message", nil), m, r, eventData)
if err != nil { if err != nil {
if !attrs.CanString("fallback") || attrs.MustString("fallback", 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"] id, ok := m.Tags["id"]
if ok { if ok {
if ircMessage.Tags == nil { 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("message"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("message must be non-empty string") 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}),
for _, field := range []string{"message", "fallback"} { fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "to_channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { helpers.SchemaValidateTemplateField(tplValidator, "message", "fallback"),
return errors.Wrapf(err, "validating %s template", field) ); err != nil {
} return fmt.Errorf("validating attributes: %w", err)
} }
return nil return nil

View File

@ -4,10 +4,13 @@ package shield
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -45,14 +48,12 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} type actor struct{}
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) {
ptrBoolFalse := func(v bool) *bool { return &v }(false)
return false, errors.Wrap( return false, errors.Wrap(
botTwitchClient.UpdateShieldMode( botTwitchClient.UpdateShieldMode(
context.Background(), context.Background(),
plugins.DeriveChannel(m, eventData), plugins.DeriveChannel(m, eventData),
attrs.MustBool("enable", ptrBoolFalse), attrs.MustBool("enable", helpers.Ptr(false)),
), ),
"configuring shield mode", "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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(_ plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if _, err = attrs.Bool("enable"); err != nil { if err = attrs.ValidateSchema(
return errors.New("enable must be boolean") fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "enable", Type: fieldcollection.SchemaFieldTypeBool}),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
} }
return nil return nil

View File

@ -4,11 +4,14 @@ package shoutout
import ( import (
"context" "context"
"fmt"
"regexp" "regexp"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -55,7 +58,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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) user, err := formatMessage(attrs.MustString("user", ptrStringEmpty), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "executing user template") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("user"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("user must be non-empty string") fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "user", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
} helpers.SchemaValidateTemplateField(tplValidator, "user"),
); err != nil {
if err = tplValidator(attrs.MustString("user", ptrStringEmpty)); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrap(err, "validating user template")
} }
return nil return nil

View File

@ -3,9 +3,13 @@
package stopexec package stopexec
import ( import (
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -42,10 +46,8 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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 }("") when, err := formatMessage(attrs.MustString("when", helpers.Ptr("")), m, r, eventData)
when, err := formatMessage(attrs.MustString("when", ptrStringEmpty), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "executing when template") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
whenTemplate, err := attrs.String("when") if err = attrs.ValidateSchema(
if err != nil || whenTemplate == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "when", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("when must be non-empty string") helpers.SchemaValidateTemplateField(tplValidator, "when"),
} ); err != nil {
return fmt.Errorf("validating attributes: %w", err)
if err = tplValidator(whenTemplate); err != nil {
return errors.Wrap(err, "validating when template")
} }
return nil return nil

View File

@ -3,6 +3,7 @@ package timeout
import ( import (
"context" "context"
"fmt"
"regexp" "regexp"
"strconv" "strconv"
"time" "time"
@ -10,6 +11,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -65,7 +68,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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) reason, err := formatMessage(attrs.MustString("reason", ptrStringEmpty), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "executing reason template") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.Duration("duration"); err != nil || v < time.Second { if err = attrs.ValidateSchema(
return errors.New("duration must be of type duration greater or equal one second") 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 == "" { if attrs.MustDuration("duration", nil) < time.Second {
return errors.New("reason must be non-empty string") return errors.New("duration must be greater or equal one second")
}
if err = tplValidator(attrs.MustString("reason", ptrStringEmpty)); err != nil {
return errors.Wrap(err, "validating reason template")
} }
return nil return nil

View File

@ -11,6 +11,8 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gorm.io/gorm" "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/database"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -144,7 +146,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
type actorSetVariable struct{} 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) varName, err := formatMessage(attrs.MustString("variable", nil), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "preparing variable name") 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) IsAsync() bool { return false }
func (actorSetVariable) Name() string { return "setvariable" } func (actorSetVariable) Name() string { return "setvariable" }
func (actorSetVariable) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actorSetVariable) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("variable"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("variable name must be non-empty string") 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}),
for _, field := range []string{"set", "variable"} { helpers.SchemaValidateTemplateField(tplValidator, "set", "variable"),
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { ); err != nil {
return errors.Wrapf(err, "validating %s template", field) return fmt.Errorf("validating attributes: %w", err)
}
} }
return nil return nil

View File

@ -3,11 +3,14 @@ package vip
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -16,8 +19,6 @@ var (
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
permCheckFn plugins.ChannelPermissionCheckFunc permCheckFn plugins.ChannelPermissionCheckFunc
tcGetter func(string) (*twitch.Client, error) tcGetter func(string) (*twitch.Client, error)
ptrStringEmpty = func(s string) *string { return &s }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -98,21 +99,19 @@ type (
) )
func (actor) IsAsync() bool { return false } func (actor) IsAsync() bool { return false }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
for _, field := range []string{"channel", "user"} { if err = attrs.ValidateSchema(
if v, err := attrs.String(field); err != nil || v == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.Errorf("%s must be non-empty string", field) fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "user", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
} helpers.SchemaValidateTemplateField(tplValidator, "channel", "user"),
); err != nil {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrapf(err, "validating %s template", field)
}
} }
return nil 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 { if channel, err = formatMessage(attrs.MustString("channel", nil), m, r, eventData); err != nil {
return "", "", errors.Wrap(err, "parsing channel") 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 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) channel, user, err := u.getParams(m, r, eventData, attrs)
if err != nil { if err != nil {
return false, errors.Wrap(err, "getting parameters") 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 (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) channel, user, err := v.getParams(m, r, eventData, attrs)
if err != nil { if err != nil {
return false, errors.Wrap(err, "getting parameters") return false, errors.Wrap(err, "getting parameters")

View File

@ -3,10 +3,13 @@ package whisper
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -16,8 +19,6 @@ const actorName = "whisper"
var ( var (
botTwitchClient *twitch.Client botTwitchClient *twitch.Client
formatMessage plugins.MsgFormatter formatMessage plugins.MsgFormatter
ptrStringEmpty = func(s string) *string { return &s }("")
) )
// Register provides the plugins.RegisterFunc // Register provides the plugins.RegisterFunc
@ -28,7 +29,7 @@ func Register(args plugins.RegistrationArguments) error {
args.RegisterActor(actorName, func() plugins.Actor { return &actor{} }) args.RegisterActor(actorName, func() plugins.Actor { return &actor{} })
args.RegisterActorDocumentation(plugins.ActionDocumentation{ args.RegisterActorDocumentation(plugins.ActionDocumentation{
Description: "Send a whisper (requires a verified bot!)", Description: "Send a whisper",
Name: "Send Whisper", Name: "Send Whisper",
Type: "whisper", Type: "whisper",
@ -59,7 +60,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{} 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) to, err := formatMessage(attrs.MustString("to", nil), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "preparing whisper receiver") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("to"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("to must be non-empty string") 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"),
if v, err := attrs.String("message"); err != nil || v == "" { ); err != nil {
return errors.New("message must be non-empty string") return fmt.Errorf("validating attributes: %w", err)
}
for _, field := range []string{"message", "to"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
} }
return nil return nil

View File

@ -1,17 +1,20 @@
package customevent package customevent
import ( import (
"fmt"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "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" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
type actor struct{} 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) fd, err := formatMessage(attrs.MustString("fields", ptrStringEmpty), m, r, eventData)
if err != nil { if err != nil {
return false, errors.Wrap(err, "executing fields template") 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) IsAsync() bool { return false }
func (actor) Name() string { return actorName } func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if v, err := attrs.String("fields"); err != nil || v == "" { if err = attrs.ValidateSchema(
return errors.New("fields is expected to be non-empty string") 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"),
for _, field := range []string{"fields", "schedule_in"} { ); err != nil {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil { return fmt.Errorf("validating attributes: %w", err)
return errors.Wrapf(err, "validating %s template", field)
}
} }
return nil return nil

View File

@ -14,6 +14,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm" "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/database"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -130,14 +131,14 @@ func handleCreateEvent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent) 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) payload := make(map[string]any)
if err := json.NewDecoder(fieldData).Decode(&payload); err != nil { if err := json.NewDecoder(fieldData).Decode(&payload); err != nil {
return nil, errors.Wrap(err, "parsing event payload") return nil, errors.Wrap(err, "parsing event payload")
} }
fields := plugins.FieldCollectionFromData(payload) fields := fieldcollection.FieldCollectionFromData(payload)
fields.Set("channel", "#"+strings.TrimLeft(channel, "#")) fields.Set("channel", "#"+strings.TrimLeft(channel, "#"))
return fields, nil return fields, nil

View File

@ -9,9 +9,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
) )
const cleanupTimeout = 15 * time.Minute 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) fieldBuf := new(bytes.Buffer)
if err := json.NewEncoder(fieldBuf).Encode(fields); err != nil { if err := json.NewEncoder(fieldBuf).Encode(fields); err != nil {
return errors.Wrap(err, "marshalling fields") return errors.Wrap(err, "marshalling fields")

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -87,7 +88,7 @@ func handleKoFiPost(w http.ResponseWriter, r *http.Request) {
return return
} }
fields := plugins.NewFieldCollection() fields := fieldcollection.NewFieldCollection()
fields.Set("channel", "#"+strings.TrimLeft(channel, "#")) fields.Set("channel", "#"+strings.TrimLeft(channel, "#"))
switch payload.Type { switch payload.Type {

View File

@ -10,9 +10,9 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/backoff" "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/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
) )
type ( type (
@ -86,7 +86,7 @@ func getEventByID(db database.Connector, eventID uint64) (socketMessage, error)
} }
func (o overlaysEvent) ToSocketMessage() (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 { if err := json.NewDecoder(strings.NewReader(o.Fields)).Decode(fields); err != nil {
return socketMessage{}, errors.Wrap(err, "decoding fields") return socketMessage{}, errors.Wrap(err, "decoding fields")
} }

View File

@ -7,8 +7,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
) )
func TestEventDatabaseRoundtrip(t *testing.T) { func TestEventDatabaseRoundtrip(t *testing.T) {
@ -30,7 +30,7 @@ func TestEventDatabaseRoundtrip(t *testing.T) {
IsLive: true, IsLive: true,
Time: tEvent2, Time: tEvent2,
Type: "event 2", 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.Equal(t, uint64(1), evtID)
assert.NoError(t, err, "adding second event") assert.NoError(t, err, "adding second event")
@ -39,7 +39,7 @@ func TestEventDatabaseRoundtrip(t *testing.T) {
IsLive: true, IsLive: true,
Time: tEvent1, Time: tEvent1,
Type: "event 1", 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.Equal(t, uint64(2), evtID)
assert.NoError(t, err, "adding first event") assert.NoError(t, err, "adding first event")
@ -48,7 +48,7 @@ func TestEventDatabaseRoundtrip(t *testing.T) {
IsLive: true, IsLive: true,
Time: tEvent1, Time: tEvent1,
Type: "event", 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.Equal(t, uint64(3), evtID)
assert.NoError(t, err, "adding other channel event") assert.NoError(t, err, "adding other channel event")
@ -66,6 +66,6 @@ func TestEventDatabaseRoundtrip(t *testing.T) {
IsLive: false, IsLive: false,
Time: tEvent1, Time: tEvent1,
Type: "event 1", Type: "event 1",
Fields: plugins.FieldCollectionFromData(map[string]any{"foo": "bar"}), Fields: fieldcollection.FieldCollectionFromData(map[string]any{"foo": "bar"}),
}, evt) }, evt)
} }

View File

@ -21,6 +21,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
@ -41,12 +42,12 @@ type (
// socketMessage represents the message overlay sockets will receive // socketMessage represents the message overlay sockets will receive
socketMessage struct { socketMessage struct {
EventID uint64 `json:"event_id"` EventID uint64 `json:"event_id"`
IsLive bool `json:"is_live"` IsLive bool `json:"is_live"`
Reason sendReason `json:"reason"` Reason sendReason `json:"reason"`
Time time.Time `json:"time"` Time time.Time `json:"time"`
Type string `json:"type"` Type string `json:"type"`
Fields *plugins.FieldCollection `json:"fields"` Fields *fieldcollection.FieldCollection `json:"fields"`
} }
) )
@ -180,7 +181,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
return fmt.Errorf("registering API route: %w", err) 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() subscribersLock.RLock()
defer subscribersLock.RUnlock() defer subscribersLock.RUnlock()

View File

@ -1,8 +1,10 @@
package raffle package raffle
import ( import (
"fmt"
"time" "time"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -17,7 +19,7 @@ var ptrStrEmpty = ptrStr("")
func ptrStr(v string) *string { return &v } 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) == "" { if m != nil || evtData.MustString("reward_id", ptrStrEmpty) == "" {
return false, errors.New("enter-raffle actor is only supposed to act on channelpoint redeems") 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(), EnteredAt: time.Now().UTC(),
} }
raffleEventFields := plugins.FieldCollectionFromData(map[string]any{ raffleEventFields := fieldcollection.FieldCollectionFromData(map[string]any{
"user_id": re.UserID, "user_id": re.UserID,
"user": re.UserLogin, "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) IsAsync() bool { return false }
func (enterRaffleActor) Name() string { return "enter-raffle" } func (enterRaffleActor) Name() string { return "enter-raffle" }
func (enterRaffleActor) Validate(_ plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) { func (enterRaffleActor) Validate(_ plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
keyword, err := attrs.String("keyword") if err = attrs.ValidateSchema(
if err != nil || keyword == "" { fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "keyword", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
return errors.New("keyword must be non-empty string") ); err != nil {
return fmt.Errorf("validating attributes: %w", err)
} }
return nil return nil

View File

@ -9,9 +9,9 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database" "github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
) )
const ( 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.speakUp[strings.Join([]string{r.Channel, winner.UserLogin}, ":")] = &speakUpWait{RaffleEntryID: winner.ID, Until: speakUpUntil}
d.lock.Unlock() d.lock.Unlock()
fields := plugins.FieldCollectionFromData(map[string]any{ fields := fieldcollection.FieldCollectionFromData(map[string]any{
"user_id": winner.UserID, "user_id": winner.UserID,
"user": winner.UserLogin, "user": winner.UserLogin,
"winner": winner, "winner": winner,
@ -636,9 +636,9 @@ func (d *dbClient) Update(r raffle) error {
// SendEvent processes the text template and sends the message if // SendEvent processes the text template and sends the message if
// enabled given through the event // 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 { if fields == nil {
fields = plugins.NewFieldCollection() fields = fieldcollection.NewFieldCollection()
} }
fields.Set("raffle", r) // Make raffle available to templating fields.Set("raffle", r) // Make raffle available to templating

View File

@ -9,6 +9,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -95,7 +96,7 @@ func handleRaffleEntry(m *irc.Message, channel, user string) error {
re.UserDisplayName = re.UserLogin re.UserDisplayName = re.UserLogin
} }
raffleEventFields := plugins.FieldCollectionFromData(map[string]any{ raffleEventFields := fieldcollection.FieldCollectionFromData(map[string]any{
"user_id": m.Tags["user-id"], "user_id": m.Tags["user-id"],
"user": user, "user": user,
}) })

4
internal/helpers/ptr.go Normal file
View File

@ -0,0 +1,4 @@
package helpers
// Ptr creates a pointer to any given type
func Ptr[T any](v T) *T { return &v }

View File

@ -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
}
}

View File

@ -3,6 +3,7 @@
package userstate package userstate
import ( import (
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
@ -16,7 +17,7 @@ func Register(args plugins.RegistrationArguments) error {
return errors.Wrap(err, "registering raw message handler") 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 { return func(badge string) bool {
state := userState.Get(plugins.DeriveChannel(m, fields)) state := userState.Get(plugins.DeriveChannel(m, fields))
if state == nil { if state == nil {

15
irc.go
View File

@ -13,6 +13,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "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/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -226,7 +227,7 @@ func (i ircHandler) handleClearChat(m *irc.Message) {
var ( var (
evt *string evt *string
fields = plugins.NewFieldCollection() fields = fieldcollection.NewFieldCollection()
) )
fields.Set(eventFieldChannel, i.getChannel(m)) // Compatibility to plugins.DeriveChannel 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) { 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 eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel
"message_id": m.Tags["target-msg-id"], "message_id": m.Tags["target-msg-id"],
"target_name": m.Tags["login"], "target_name": m.Tags["login"],
@ -270,7 +271,7 @@ func (i ircHandler) handleClearMessage(m *irc.Message) {
} }
func (i ircHandler) handleJoin(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 eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel
eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser 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) { 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 eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel
eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser
}) })
@ -299,7 +300,7 @@ func (i ircHandler) handlePermit(m *irc.Message) {
username := msgParts[1] username := msgParts[1]
fields := plugins.FieldCollectionFromData(map[string]interface{}{ fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{
eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel
eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser eventFieldUserName: m.User, // Compatibility to plugins.DeriveUser
eventFieldUserID: m.Tags["user-id"], 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 { if bits := i.tagToNumeric(m, "bits", 0); bits > 0 {
fields := plugins.FieldCollectionFromData(map[string]interface{}{ fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{
"bits": bits, "bits": bits,
eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel
"message": m.Trailing(), "message": m.Trailing(),
@ -380,7 +381,7 @@ func (i ircHandler) handleTwitchUsernotice(m *irc.Message) {
"trailing": m.Trailing(), "trailing": m.Trailing(),
}).Trace("IRC USERNOTICE event") }).Trace("IRC USERNOTICE event")
evtData := plugins.FieldCollectionFromData(map[string]any{ evtData := fieldcollection.FieldCollectionFromData(map[string]any{
eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel eventFieldChannel: i.getChannel(m), // Compatibility to plugins.DeriveChannel
eventFieldUserName: m.Tags["login"], // Compatibility to plugins.DeriveUser eventFieldUserName: m.Tags["login"], // Compatibility to plugins.DeriveUser
eventFieldUserID: m.Tags["user-id"], eventFieldUserID: m.Tags["user-id"],

View File

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -19,7 +20,7 @@ var (
stripNewline = regexp.MustCompile(`(?m)\s*\n\s*`) 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, formatMessageFieldChannel,
formatMessageFieldMessage, formatMessageFieldMessage,
formatMessageFieldUserID, formatMessageFieldUserID,
@ -27,8 +28,8 @@ var (
} }
) )
func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) (string, error) { func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *fieldcollection.FieldCollection) (string, error) {
compiledFields := plugins.NewFieldCollection() compiledFields := fieldcollection.NewFieldCollection()
if config != nil { if config != nil {
configLock.RLock() 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") 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)) 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 { if m == nil {
return return
} }
@ -73,7 +74,7 @@ func formatMessageFieldMessage(compiledFields *plugins.FieldCollection, m *irc.M
compiledFields.Set("msg", 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 { if m == nil {
return 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)) compiledFields.Set("username", plugins.DeriveUser(m, fields))
} }
@ -93,7 +94,7 @@ func validateTemplate(tplString string) error {
_, err := template. _, err := template.
New(tplString). New(tplString).
Funcs(tplFuncs.GetFuncMap(nil, nil, plugins.NewFieldCollection())). Funcs(tplFuncs.GetFuncMap(nil, nil, fieldcollection.NewFieldCollection())).
Parse(tplString) Parse(tplString)
return errors.Wrap(err, "parsing template") return errors.Wrap(err, "parsing template")
} }

View File

@ -3,6 +3,7 @@ package plugins
import ( import (
"reflect" "reflect"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -16,9 +17,9 @@ type (
// (not returning ErrValueNotSet) and does not contain zero value // (not returning ErrValueNotSet) and does not contain zero value
// recognized by reflect (to just check whether the field is set // recognized by reflect (to just check whether the field is set
// but allow zero values use HasAll on the FieldCollection) // 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 { for _, field := range fields {
v, err := attrs.Any(field) v, err := attrs.Get(field)
if err != nil { if err != nil {
return errors.Wrapf(err, "getting field %s", field) 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 // ValidateRequireValidTemplate checks whether fields are gettable
// as strings and do have a template which validates (this does not // as strings and do have a template which validates (this does not
// check for empty strings as an empty template is indeed valid) // 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 { for _, field := range fields {
v, err := attrs.String(field) v, err := attrs.String(field)
if err != nil { if err != nil {
@ -52,11 +53,11 @@ func (ActorKit) ValidateRequireValidTemplate(tplValidator TemplateValidatorFunc,
// ValidateRequireValidTemplateIfSet checks whether the field is // ValidateRequireValidTemplateIfSet checks whether the field is
// either not set or a valid template (this does not // either not set or a valid template (this does not
// check for empty strings as an empty template is indeed valid) // 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 { for _, field := range fields {
v, err := attrs.String(field) v, err := attrs.String(field)
if err != nil { if err != nil {
if errors.Is(err, ErrValueNotSet) { if errors.Is(err, fieldcollection.ErrValueNotSet) {
continue continue
} }
return errors.Wrapf(err, "getting string field %s", field) return errors.Wrapf(err, "getting string field %s", field)

View File

@ -4,11 +4,12 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestValidateRequireNonEmpty(t *testing.T) { func TestValidateRequireNonEmpty(t *testing.T) {
attrs := FieldCollectionFromData(map[string]any{ attrs := fieldcollection.FieldCollectionFromData(map[string]any{
"str": "", "str": "",
"str_v": "valid", "str_v": "valid",
"int": 0, "int": 0,

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -4,12 +4,13 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
) )
// DeriveChannel takes an irc.Message and a FieldCollection and tries // DeriveChannel takes an irc.Message and a FieldCollection and tries
// to extract from them the channel the event / message has taken place // 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], "#") { if m != nil && len(m.Params) > 0 && strings.HasPrefix(m.Params[0], "#") {
return 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 // DeriveUser takes an irc.Message and a FieldCollection and tries
// to extract from them the user causing the event / message // 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 != "" { if m != nil && m.User != "" {
return m.User return m.User
} }

View File

@ -7,6 +7,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gorm.io/gorm" "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/database"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" "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 defines an interface to implement in the plugin for actors
Actor interface { Actor interface {
// Execute will be called after the config was read into the Actor // Execute will be called after the config was read into the Actor
Execute(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 // IsAsync may return true if the Execute function is to be executed
// in a Go routine as of long runtime. Normally it should return false // in a Go routine as of long runtime. Normally it should return false
// except in very specific cases // except in very specific cases
@ -26,7 +27,7 @@ type (
// Validate will be called to validate the loaded configuration. It should // Validate will be called to validate the loaded configuration. It should
// return an error if required keys are missing from the AttributeStore // return an error if required keys are missing from the AttributeStore
// or if keys contain broken configs // 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 // 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 // EventHandlerFunc defines the type of function required to listen
// for events // 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 // EventHandlerRegisterFunc is passed from the bot to the
// plugins RegisterFunc to register a new event handler function // plugins RegisterFunc to register a new event handler function
// which is then fed with all events occurring in the bot // which is then fed with all events occurring in the bot
@ -76,12 +77,12 @@ type (
// ModuleConfigGetterFunc is passed from the bot to the // ModuleConfigGetterFunc is passed from the bot to the
// plugins RegisterFunc to fetch module generic or channel specific // plugins RegisterFunc to fetch module generic or channel specific
// configuration from the module configuration // 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 // MsgFormatter is passed from the bot to the
// plugins RegisterFunc to format messages using all registered and // plugins RegisterFunc to format messages using all registered and
// available template functions // 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 // MsgModificationFunc can be used to modify messages between the
// plugins generating them and the bot sending them to the Twitch // 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 // TemplateFuncGetter is the type of function to implement in the
// plugin to create a new template function on request of the bot // 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 // TemplateFuncRegister is passed from the bot to the
// plugins RegisterFunc to register a new TemplateFuncGetter // plugins RegisterFunc to register a new TemplateFuncGetter
TemplateFuncRegister func(name string, fg TemplateFuncGetter, doc ...TemplateFuncDocumentation) 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 // requiring access to the irc.Message, Rule or FieldCollection to
// satisfy the TemplateFuncGetter interface // satisfy the TemplateFuncGetter interface
func GenericTemplateFunctionGetter(f any) TemplateFuncGetter { func GenericTemplateFunctionGetter(f any) TemplateFuncGetter {
return func(*irc.Message, *Rule, *FieldCollection) any { return f } return func(*irc.Message, *Rule, *fieldcollection.FieldCollection) any { return f }
} }

View File

@ -1,6 +1,10 @@
package plugins package plugins
import "strings" import (
"strings"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
)
// DefaultConfigName is the name the default configuration must have // DefaultConfigName is the name the default configuration must have
// when defined // when defined
@ -9,16 +13,16 @@ const DefaultConfigName = "default"
type ( type (
// ModuleConfig represents a mapping of configurations per channel // ModuleConfig represents a mapping of configurations per channel
// and module // and module
ModuleConfig map[string]map[string]*FieldCollection ModuleConfig map[string]map[string]*fieldcollection.FieldCollection
) )
// GetChannelConfig reads the channel specific configuration for the // GetChannelConfig reads the channel specific configuration for the
// given module. This is created by taking an empty FieldCollection, // given module. This is created by taking an empty FieldCollection,
// merging in the default configuration and finally overwriting all // merging in the default configuration and finally overwriting all
// existing channel configurations. // existing channel configurations.
func (m ModuleConfig) GetChannelConfig(module, channel string) *FieldCollection { func (m ModuleConfig) GetChannelConfig(module, channel string) *fieldcollection.FieldCollection {
channel = strings.TrimLeft(channel, "#@") channel = strings.TrimLeft(channel, "#@")
composed := NewFieldCollection() composed := fieldcollection.NewFieldCollection()
for _, i := range []string{DefaultConfigName, channel} { for _, i := range []string{DefaultConfigName, channel} {
f := m[module][i] f := m[module][i]

View File

@ -3,6 +3,7 @@ package plugins
import ( import (
"testing" "testing"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -10,12 +11,12 @@ import (
func TestModuleConfigGet(t *testing.T) { func TestModuleConfigGet(t *testing.T) {
strPtrEmpty := func(v string) *string { return &v }("") strPtrEmpty := func(v string) *string { return &v }("")
m := ModuleConfig{ m := ModuleConfig{
"test": map[string]*FieldCollection{ "test": map[string]*fieldcollection.FieldCollection{
DefaultConfigName: FieldCollectionFromData(map[string]any{ DefaultConfigName: fieldcollection.FieldCollectionFromData(map[string]any{
"setindefault": DefaultConfigName, "setindefault": DefaultConfigName,
"setinboth": DefaultConfigName, "setinboth": DefaultConfigName,
}), }),
"test": FieldCollectionFromData(map[string]any{ "test": fieldcollection.FieldCollectionFromData(map[string]any{
"setinchannel": "channel", "setinchannel": "channel",
"setinboth": "channel", "setinboth": "channel",
}), }),

View File

@ -17,6 +17,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" "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 represents an action to be executed when running a Rule
RuleAction struct { RuleAction struct {
Type string `json:"type" yaml:"type,omitempty"` Type string `json:"type" yaml:"type,omitempty"`
Attributes *FieldCollection `json:"attributes" yaml:"attributes,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 // 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.msgFormatter = msgFormatter
r.timerStore = timerStore r.timerStore = timerStore
r.twitchClient = twitchClient 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.allowExecuteDisable,
r.allowExecuteChannelWhitelist, r.allowExecuteChannelWhitelist,
r.allowExecuteUserWhitelist, r.allowExecuteUserWhitelist,
@ -144,7 +145,7 @@ func (r *Rule) GetMatchMessage() *regexp.Regexp {
// SetCooldown uses the given TimerStore to set the cooldowns for the // SetCooldown uses the given TimerStore to set the cooldowns for the
// Rule after execution // 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 var err error
if r.Cooldown != nil { if r.Cooldown != nil {
@ -250,7 +251,7 @@ func (r Rule) Validate(tplValidate TemplateValidatorFunc) error {
return nil 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 { for _, b := range r.DisableOn {
if badges.Has(b) { if badges.Has(b) {
logger.Tracef("Non-Match: Disable-Badge %s", b) logger.Tracef("Non-Match: Disable-Badge %s", b)
@ -261,7 +262,7 @@ func (r *Rule) allowExecuteBadgeBlacklist(logger *logrus.Entry, _ *irc.Message,
return true 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 { if len(r.EnableOn) == 0 {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -276,7 +277,7 @@ func (r *Rule) allowExecuteBadgeWhitelist(_ *logrus.Entry, _ *irc.Message, _ *st
return false 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) == "" { if r.ChannelCooldown == nil || DeriveChannel(m, evtData) == "" {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -301,7 +302,7 @@ func (r *Rule) allowExecuteChannelCooldown(logger *logrus.Entry, m *irc.Message,
return false 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 { if len(r.MatchChannels) == 0 {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -315,7 +316,7 @@ func (r *Rule) allowExecuteChannelWhitelist(logger *logrus.Entry, m *irc.Message
return true 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 { if r.Disable == nil {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -329,7 +330,7 @@ func (r *Rule) allowExecuteDisable(logger *logrus.Entry, _ *irc.Message, _ *stri
return true 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) == "" { if r.DisableOnOffline == nil || !*r.DisableOnOffline || DeriveChannel(m, evtData) == "" {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -348,7 +349,7 @@ func (r *Rule) allowExecuteDisableOnOffline(logger *logrus.Entry, m *irc.Message
return true 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)) hasPermit, err := r.timerStore.HasPermit(DeriveChannel(m, evtData), DeriveUser(m, evtData))
if err != nil { if err != nil {
logger.WithError(err).Error("checking permit") logger.WithError(err).Error("checking permit")
@ -363,7 +364,7 @@ func (r *Rule) allowExecuteDisableOnPermit(logger *logrus.Entry, m *irc.Message,
return true 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 == "" { if r.DisableOnTemplate == nil || *r.DisableOnTemplate == "" {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -384,7 +385,7 @@ func (r *Rule) allowExecuteDisableOnTemplate(logger *logrus.Entry, m *irc.Messag
return true 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 // 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 // event to match. We now need to ensure this match is valid for
// the current execution: // the current execution:
@ -430,7 +431,7 @@ func (r *Rule) allowExecuteEventMatch(logger *logrus.Entry, _ *irc.Message, even
return false 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 { if len(r.DisableOnMatchMessages) == 0 {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -459,7 +460,7 @@ func (r *Rule) allowExecuteMessageMatcherBlacklist(logger *logrus.Entry, m *irc.
return true 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 { if r.MatchMessage == nil {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -484,7 +485,7 @@ func (r *Rule) allowExecuteMessageMatcherWhitelist(logger *logrus.Entry, m *irc.
return true 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 { if r.Cooldown == nil {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -509,7 +510,7 @@ func (r *Rule) allowExecuteRuleCooldown(logger *logrus.Entry, _ *irc.Message, _
return false 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 { if r.UserCooldown == nil {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true
@ -534,7 +535,7 @@ func (r *Rule) allowExecuteUserCooldown(logger *logrus.Entry, m *irc.Message, _
return false 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 { if len(r.MatchUsers) == 0 {
// No match criteria set, does not speak against matching // No match criteria set, does not speak against matching
return true return true

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/irc.v4" "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/pkg/twitch"
) )
@ -141,7 +142,7 @@ func TestAllowExecuteDisableOnTemplate(t *testing.T) {
} { } {
// We don't test the message formatter here but only the disable functionality // We don't test the message formatter here but only the disable functionality
// so we fake the result of the evaluation // 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 return msg, nil
} }

View File

@ -10,6 +10,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/backoff" "github.com/Luzifer/go_helpers/v2/backoff"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/internal/actors/announce" "github.com/Luzifer/twitch-bot/v3/internal/actors/announce"
"github.com/Luzifer/twitch-bot/v3/internal/actors/ban" "github.com/Luzifer/twitch-bot/v3/internal/actors/ban"
@ -181,12 +182,12 @@ func getRegistrationArguments() plugins.RegistrationArguments {
SendMessage: sendMessage, SendMessage: sendMessage,
ValidateToken: authService.ValidateTokenFor, 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) handleMessage(ircHdl.Client(), nil, &evt, eventData)
return nil return nil
}, },
GetModuleConfigForChannel: func(module, channel string) *plugins.FieldCollection { GetModuleConfigForChannel: func(module, channel string) *fieldcollection.FieldCollection {
return config.ModuleConfig.GetChannelConfig(module, channel) return config.ModuleConfig.GetChannelConfig(module, channel)
}, },

View File

@ -12,6 +12,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins" "github.com/Luzifer/twitch-bot/v3/plugins"
) )
@ -88,7 +89,7 @@ func generateTplDocsRender(e *plugins.TemplateFuncDocumentationExample) (string,
rule.MatchMessage = &e.MatchMessage 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, "testDuration": 5*time.Hour + 33*time.Minute + 12*time.Second,
})) }))
} }

View File

@ -8,10 +8,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" 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/helpers"
"github.com/Luzifer/twitch-bot/v3/internal/service/access" "github.com/Luzifer/twitch-bot/v3/internal/service/access"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch" "github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
) )
type ( type (
@ -241,7 +241,7 @@ func (*twitchWatcher) handleEventSubChannelAdBreakBegin(m json.RawMessage) error
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]any{ fields := fieldcollection.FieldCollectionFromData(map[string]any{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"duration": payload.Duration, "duration": payload.Duration,
"is_automatic": payload.IsAutomatic, "is_automatic": payload.IsAutomatic,
@ -260,7 +260,7 @@ func (*twitchWatcher) handleEventSubChannelFollow(m json.RawMessage) error {
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]interface{}{ fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"followed_at": payload.FollowedAt, "followed_at": payload.FollowedAt,
"user_id": payload.UserID, "user_id": payload.UserID,
@ -279,7 +279,7 @@ func (*twitchWatcher) handleEventSubChannelPointCustomRewardRedemptionAdd(m json
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]interface{}{ fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"reward_cost": payload.Reward.Cost, "reward_cost": payload.Reward.Cost,
"reward_id": payload.Reward.ID, "reward_id": payload.Reward.ID,
@ -302,7 +302,7 @@ func (*twitchWatcher) handleEventSubChannelOutboundRaid(m json.RawMessage) error
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]interface{}{ fields := fieldcollection.FieldCollectionFromData(map[string]interface{}{
"channel": "#" + payload.FromBroadcasterUserLogin, "channel": "#" + payload.FromBroadcasterUserLogin,
"to_id": payload.ToBroadcasterUserID, "to_id": payload.ToBroadcasterUserID,
"to": payload.ToBroadcasterUserLogin, "to": payload.ToBroadcasterUserLogin,
@ -333,7 +333,7 @@ func (*twitchWatcher) handleEventSubChannelPollChange(event *string) func(json.R
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]any{ fields := fieldcollection.FieldCollectionFromData(map[string]any{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"hasChannelPointVoting": payload.ChannelPointsVoting.IsEnabled, "hasChannelPointVoting": payload.ChannelPointsVoting.IsEnabled,
"title": payload.Title, "title": payload.Title,
@ -370,7 +370,7 @@ func (*twitchWatcher) handleEventSubHypetrainEvent(eventType *string) func(json.
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]any{ fields := fieldcollection.FieldCollectionFromData(map[string]any{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"level": payload.Level, "level": payload.Level,
}) })
@ -394,7 +394,7 @@ func (*twitchWatcher) handleEventSubShoutoutCreated(m json.RawMessage) error {
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]any{ fields := fieldcollection.FieldCollectionFromData(map[string]any{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"to_id": payload.ToBroadcasterUserID, "to_id": payload.ToBroadcasterUserID,
"to": payload.ToBroadcasterUserLogin, "to": payload.ToBroadcasterUserLogin,
@ -413,7 +413,7 @@ func (*twitchWatcher) handleEventSubShoutoutReceived(m json.RawMessage) error {
return errors.Wrap(err, "unmarshalling event") return errors.Wrap(err, "unmarshalling event")
} }
fields := plugins.FieldCollectionFromData(map[string]any{ fields := fieldcollection.FieldCollectionFromData(map[string]any{
"channel": "#" + payload.BroadcasterUserLogin, "channel": "#" + payload.BroadcasterUserLogin,
"from_id": payload.FromBroadcasterUserID, "from_id": payload.FromBroadcasterUserID,
"from": payload.FromBroadcasterUserLogin, "from": payload.FromBroadcasterUserLogin,
@ -570,7 +570,7 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o
"channel": channel, "channel": channel,
"category": *category, "category": *category,
}).Info("Category updated") }).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, "channel": "#" + channel,
"category": *category, "category": *category,
})) }))
@ -582,7 +582,7 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o
"channel": channel, "channel": channel,
"title": *title, "title": *title,
}).Info("Title updated") }).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, "channel": "#" + channel,
"title": *title, "title": *title,
})) }))
@ -600,7 +600,7 @@ func (t *twitchWatcher) triggerUpdate(channel string, title, category *string, o
evt = eventTypeTwitchStreamOffline 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, "channel": "#" + channel,
})) }))
} }