[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"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -49,7 +50,7 @@ func init() {
type ActorScript struct{}
// Execute implements actor interface
func (ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (ActorScript) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
command, err := attrs.StringSlice("command")
if err != nil {
return false, errors.Wrap(err, "getting command")
@ -130,7 +131,7 @@ func (ActorScript) IsAsync() bool { return false }
func (ActorScript) Name() string { return "script" }
// Validate implements actor interface
func (ActorScript) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
func (ActorScript) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
cmd, err := attrs.StringSlice("command")
if err != nil || len(cmd) == 0 {
return errors.New("command must be slice of strings with length > 0")

View File

@ -7,6 +7,7 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -41,7 +42,7 @@ func registerAction(name string, acf plugins.ActorCreationFunc) {
availableActions[name] = acf
}
func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugins.RuleAction, eventData *plugins.FieldCollection) (preventCooldown bool, err error) {
func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugins.RuleAction, eventData *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
availableActionsLock.RLock()
defer availableActionsLock.RUnlock()
@ -65,7 +66,7 @@ func triggerAction(c *irc.Client, m *irc.Message, rule *plugins.Rule, ra *plugin
return apc, errors.Wrap(err, "execute action")
}
func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *plugins.FieldCollection) {
func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *fieldcollection.FieldCollection) {
// Send events to registered handlers
if event != nil {
go notifyEventHandlers(*event, eventData)
@ -77,9 +78,9 @@ func handleMessage(c *irc.Client, m *irc.Message, event *string, eventData *plug
}
}
func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection) {
func handleMessageRuleExecution(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection) {
var (
ruleEventData = plugins.NewFieldCollection()
ruleEventData = fieldcollection.NewFieldCollection()
preventCooldown bool
)

View File

@ -13,8 +13,8 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
var cronParser = cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
@ -174,7 +174,7 @@ func (a *autoMessage) allowExecuteDisableOnTemplate() bool {
return true
}
fields := plugins.NewFieldCollection()
fields := fieldcollection.NewFieldCollection()
fields.Set("channel", a.Channel)
res, err := formatMessage(*a.DisableOnTemplate, nil, nil, fields)

View File

@ -19,6 +19,7 @@ import (
"gopkg.in/irc.v4"
"gopkg.in/yaml.v3"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -303,7 +304,7 @@ func (c *configFile) CloseRawMessageWriter() (err error) {
return nil
}
func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData *plugins.FieldCollection) []*plugins.Rule {
func (c configFile) GetMatchingRules(m *irc.Message, event *string, eventData *fieldcollection.FieldCollection) []*plugins.Rule {
configLock.RLock()
defer configLock.RUnlock()

View File

@ -5,6 +5,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -93,7 +94,7 @@ var (
}
)
func notifyEventHandlers(event string, eventData *plugins.FieldCollection) {
func notifyEventHandlers(event string, eventData *fieldcollection.FieldCollection) {
registeredEventHandlersLock.Lock()
defer registeredEventHandlersLock.Unlock()

View File

@ -11,6 +11,7 @@ import (
"github.com/sirupsen/logrus"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str"
korvike "github.com/Luzifer/korvike/functions"
"github.com/Luzifer/twitch-bot/v3/plugins"
@ -37,7 +38,7 @@ func newTemplateFuncProvider() *templateFuncProvider {
return out
}
func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) template.FuncMap {
func (t *templateFuncProvider) GetFuncMap(m *irc.Message, r *plugins.Rule, fields *fieldcollection.FieldCollection) template.FuncMap {
t.lock.RLock()
defer t.lock.RUnlock()

View File

@ -6,12 +6,13 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
func init() {
tplFuncs.Register("arg", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} {
tplFuncs.Register("arg", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} {
return func(arg int) (string, error) {
msgParts := strings.Split(m.Trailing(), " ")
if len(msgParts) <= arg {
@ -30,7 +31,7 @@ func init() {
},
})
tplFuncs.Register("chatterHasBadge", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} {
tplFuncs.Register("chatterHasBadge", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} {
return func(badge string) bool {
badges := twitch.ParseBadgeLevels(m)
return badges.Has(badge)
@ -57,7 +58,7 @@ func init() {
},
)
tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, _ *plugins.FieldCollection) interface{} {
tplFuncs.Register("group", func(m *irc.Message, r *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} {
return func(idx int, fallback ...string) (string, error) {
fields := r.GetMatchMessage().FindStringSubmatch(m.Trailing())
if len(fields) <= idx {
@ -94,7 +95,7 @@ func init() {
},
)
tplFuncs.Register("tag", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} {
tplFuncs.Register("tag", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} {
return func(tag string) string { return m.Tags[tag] }
}, plugins.TemplateFuncDocumentation{
Description: "Takes the message sent to the channel, returns the value of the tag specified",

12
go.mod
View File

@ -4,19 +4,19 @@ go 1.21
require (
github.com/Luzifer/go-openssl/v4 v4.2.2
github.com/Luzifer/go_helpers/v2 v2.23.0
github.com/Luzifer/go_helpers/v2 v2.24.0
github.com/Luzifer/korvike/functions v0.11.0
github.com/Luzifer/rconfig/v2 v2.5.0
github.com/Masterminds/sprig/v3 v3.2.3
github.com/getsentry/sentry-go v0.27.0
github.com/glebarez/sqlite v1.11.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-git/go-git/v5 v5.12.0
github.com/go-sql-driver/mysql v1.8.1
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gofrs/uuid/v3 v3.1.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.1
github.com/itchyny/gojq v0.12.14
github.com/itchyny/gojq v0.12.15
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/orandin/sentrus v1.0.0
github.com/pkg/errors v0.9.1
@ -31,7 +31,7 @@ require (
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.8
gorm.io/gorm v1.25.9
)
require (
@ -87,7 +87,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cast v1.6.0 // indirect
@ -105,6 +105,6 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.49.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.29.5 // indirect
)

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/Luzifer/go-openssl/v4 v4.2.2 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc=
github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc=
github.com/Luzifer/go_helpers/v2 v2.23.0 h1:VowDwOCl6nOt+GVqKUX/do6a94pEeqNTRHb29MsoGX4=
github.com/Luzifer/go_helpers/v2 v2.23.0/go.mod h1:BSGkJ/dxqs7AxsfZt8zjJb4R6YB5dONS+/ad7foLUrk=
github.com/Luzifer/go_helpers/v2 v2.24.0 h1:abACOhsn6a6c6X22jq42mZM1wuOM0Ihfa6yzssrjrOg=
github.com/Luzifer/go_helpers/v2 v2.24.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc=
github.com/Luzifer/korvike/functions v0.11.0 h1:2hr3nnt9hy8Esu1W3h50+RggcLRXvrw92kVQLvxzd2Q=
github.com/Luzifer/korvike/functions v0.11.0/go.mod h1:osumwH64mWgbwZIfE7rE0BB7Y5HXxrzyO4JfO7fhduU=
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok=
@ -62,8 +62,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@ -72,8 +72,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
@ -161,8 +161,8 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/itchyny/gojq v0.12.14 h1:6k8vVtsrhQSYgSGg827AD+PVVaB1NLXEdX+dda2oZCc=
github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s=
github.com/itchyny/gojq v0.12.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI=
github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10=
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@ -245,8 +245,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -422,8 +422,8 @@ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo=
gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/cc/v4 v4.19.5 h1:QlsZyQ1zf78DGeqnQ9ILi9hXyMdoC5e1qoGNUyBjHQw=
@ -438,8 +438,8 @@ modernc.org/libc v1.49.0 h1:/kkNBuCXvlTbOGwrQdgR67eK1Y9+kR+fhdBd89C64VM=
modernc.org/libc v1.49.0/go.mod h1:DNz0lgQgT6FPIPm8rHtjFj0FL5/YOr/NYFXWYBcSxMw=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=

View File

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

View File

@ -9,6 +9,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -19,9 +21,6 @@ var (
formatMessage plugins.MsgFormatter
hasPerm plugins.ChannelPermissionCheckFunc
tcGetter func(string) (*twitch.Client, error)
ptrBoolFalse = func(v bool) *bool { return &v }(false)
ptrStringEmpty = func(s string) *string { return &s }("")
)
// Register provides the plugins.RegisterFunc
@ -71,7 +70,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{}
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
channel := plugins.DeriveChannel(m, eventData)
if channel, err = formatMessage(attrs.MustString("channel", &channel), m, r, eventData); err != nil {
return false, errors.Wrap(err, "parsing channel")
@ -96,7 +95,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
return false, errors.Wrapf(err, "getting Twitch client for %q", creator)
}
clipInfo, err := tc.CreateClip(context.TODO(), channel, attrs.MustBool("add_delay", ptrBoolFalse))
clipInfo, err := tc.CreateClip(context.TODO(), channel, attrs.MustBool("add_delay", helpers.Ptr(false)))
if err != nil {
return false, errors.Wrap(err, "creating clip")
}
@ -110,11 +109,13 @@ func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
for _, field := range []string{"channel", "creator"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "channel", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "creator", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "channel", "creator"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
return nil

View File

@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/actors/linkdetector"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
@ -40,7 +41,7 @@ func Register(args plugins.RegistrationArguments) error {
type Actor struct{}
// Execute implements the actor interface
func (Actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (Actor) Execute(c *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
if eventData.HasAll("clips") {
// We already detected clips, lets not do it again
return false, nil
@ -82,4 +83,6 @@ func (Actor) IsAsync() bool { return false }
func (Actor) Name() string { return actorName }
// Validate implements the actor interface
func (Actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) error { return nil }
func (Actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) error {
return nil
}

View File

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

View File

@ -13,6 +13,8 @@ import (
"gopkg.in/irc.v4"
"gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -20,8 +22,6 @@ import (
var (
db database.Connector
formatMessage plugins.MsgFormatter
ptrStringEmpty = func(s string) *string { return &s }("")
)
// Register provides the plugins.RegisterFunc
@ -135,7 +135,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
return fmt.Errorf("registering API route: %w", err)
}
args.RegisterTemplateFunction("channelCounter", func(_ *irc.Message, _ *plugins.Rule, fields *plugins.FieldCollection) interface{} {
args.RegisterTemplateFunction("channelCounter", func(_ *irc.Message, _ *plugins.Rule, fields *fieldcollection.FieldCollection) interface{} {
return func(name string) (string, error) {
channel, err := fields.String("channel")
if err != nil {
@ -212,13 +212,13 @@ func Register(args plugins.RegistrationArguments) (err error) {
type actorCounter struct{}
func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
counterName, err := formatMessage(attrs.MustString("counter", nil), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "preparing response")
}
if counterSet := attrs.MustString("counter_set", ptrStringEmpty); counterSet != "" {
if counterSet := attrs.MustString("counter_set", helpers.Ptr("")); counterSet != "" {
parseValue, err := formatMessage(counterSet, m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "execute counter value template")
@ -236,7 +236,7 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even
}
var counterStep int64 = 1
if s := attrs.MustString("counter_step", ptrStringEmpty); s != "" {
if s := attrs.MustString("counter_step", helpers.Ptr("")); s != "" {
parseStep, err := formatMessage(s, m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "execute counter step template")
@ -257,15 +257,14 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even
func (actorCounter) IsAsync() bool { return false }
func (actorCounter) Name() string { return "counter" }
func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
if cn, err := attrs.String("counter"); err != nil || cn == "" {
return errors.New("counter name must be non-empty string")
}
for _, field := range []string{"counter", "counter_step", "counter_set"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
func (actorCounter) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "counter", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "counter_step", Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "counter_set", Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "counter", "counter_step", "counter_set"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
return nil

View File

@ -7,6 +7,8 @@ import (
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -48,11 +50,10 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{}
func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
var (
ptrZeroDuration = func(v time.Duration) *time.Duration { return &v }(0)
delay = attrs.MustDuration("delay", ptrZeroDuration)
jitter = attrs.MustDuration("jitter", ptrZeroDuration)
delay = attrs.MustDuration("delay", helpers.Ptr(time.Duration(0)))
jitter = attrs.MustDuration("jitter", helpers.Ptr(time.Duration(0)))
)
if delay == 0 && jitter == 0 {
@ -71,6 +72,6 @@ func (actor) Execute(_ *irc.Client, _ *irc.Message, _ *plugins.Rule, _ *plugins.
func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) {
func (actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) (err error) {
return nil
}

View File

@ -7,6 +7,7 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -32,7 +33,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{}
func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *plugins.FieldCollection, _ *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *fieldcollection.FieldCollection, _ *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
msgID, ok := m.Tags["id"]
if !ok || msgID == "" {
return false, nil
@ -51,6 +52,6 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, _ *plugins.Rule, eventData *
func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(plugins.TemplateValidatorFunc, *plugins.FieldCollection) (err error) {
func (actor) Validate(plugins.TemplateValidatorFunc, *fieldcollection.FieldCollection) (err error) {
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -50,18 +52,18 @@ type (
}
)
func (d discordActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (d discordActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
var payload discordPayload
if payload.Content, err = formatMessage(attrs.MustString("content", ptrStringEmpty), m, r, eventData); err != nil {
if payload.Content, err = formatMessage(attrs.MustString("content", helpers.Ptr("")), m, r, eventData); err != nil {
return false, errors.Wrap(err, "parsing content")
}
if payload.Username, err = formatMessage(attrs.MustString("username", ptrStringEmpty), m, r, eventData); err != nil {
if payload.Username, err = formatMessage(attrs.MustString("username", helpers.Ptr("")), m, r, eventData); err != nil {
return false, errors.Wrap(err, "parsing username")
}
if payload.AvatarURL, err = formatMessage(attrs.MustString("avatar_url", ptrStringEmpty), m, r, eventData); err != nil {
if payload.AvatarURL, err = formatMessage(attrs.MustString("avatar_url", helpers.Ptr("")), m, r, eventData); err != nil {
return false, errors.Wrap(err, "parsing avatar_url")
}
@ -69,14 +71,14 @@ func (d discordActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, ev
return false, err
}
return sendPayload(attrs.MustString("hook_url", ptrStringEmpty), payload, http.StatusNoContent)
return sendPayload(attrs.MustString("hook_url", helpers.Ptr("")), payload, http.StatusNoContent)
}
func (discordActor) IsAsync() bool { return false }
func (discordActor) Name() string { return "discordhook" }
func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = d.ValidateRequireNonEmpty(attrs, "hook_url"); err != nil {
return err //nolint:wrapcheck
}
@ -89,7 +91,7 @@ func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs
return err //nolint:wrapcheck
}
if !attrs.MustBool("add_embed", ptrBoolFalse) {
if !attrs.MustBool("add_embed", helpers.Ptr(false)) {
// We're not validating the rest if embeds are disabled but in
// this case the content is mandatory
return d.ValidateRequireNonEmpty(attrs, "content") //nolint:wrapcheck
@ -111,8 +113,8 @@ func (d discordActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs
}
//nolint:gocyclo // It's complex but just a bunch of converters
func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (err error) {
if !attrs.MustBool("add_embed", ptrBoolFalse) {
func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (err error) {
if !attrs.MustBool("add_embed", helpers.Ptr(false)) {
// No embed? No problem!
return nil
}
@ -122,45 +124,45 @@ func (discordActor) addEmbed(payload *discordPayload, m *irc.Message, r *plugins
sv string
)
if embed.Title, err = formatMessage(attrs.MustString("embed_title", ptrStringEmpty), m, r, eventData); err != nil {
if embed.Title, err = formatMessage(attrs.MustString("embed_title", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_title")
}
if embed.Description, err = formatMessage(attrs.MustString("embed_description", ptrStringEmpty), m, r, eventData); err != nil {
if embed.Description, err = formatMessage(attrs.MustString("embed_description", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_description")
}
if embed.URL, err = formatMessage(attrs.MustString("embed_url", ptrStringEmpty), m, r, eventData); err != nil {
if embed.URL, err = formatMessage(attrs.MustString("embed_url", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_url")
}
if sv, err = formatMessage(attrs.MustString("embed_image", ptrStringEmpty), m, r, eventData); err != nil {
if sv, err = formatMessage(attrs.MustString("embed_image", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_image")
} else if sv != "" {
embed.Image = &discordPayloadEmbedImage{URL: sv}
}
if sv, err = formatMessage(attrs.MustString("embed_thumbnail", ptrStringEmpty), m, r, eventData); err != nil {
if sv, err = formatMessage(attrs.MustString("embed_thumbnail", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_thumbnail")
} else if sv != "" {
embed.Thumbnail = &discordPayloadEmbedImage{URL: sv}
}
if sv, err = formatMessage(attrs.MustString("embed_author_name", ptrStringEmpty), m, r, eventData); err != nil {
if sv, err = formatMessage(attrs.MustString("embed_author_name", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_author_name")
} else if sv != "" {
embed.Author = &discordPayloadEmbedAuthor{Name: sv}
if embed.Author.URL, err = formatMessage(attrs.MustString("embed_author_url", ptrStringEmpty), m, r, eventData); err != nil {
if embed.Author.URL, err = formatMessage(attrs.MustString("embed_author_url", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_author_url")
}
if embed.Author.IconURL, err = formatMessage(attrs.MustString("embed_author_icon_url", ptrStringEmpty), m, r, eventData); err != nil {
if embed.Author.IconURL, err = formatMessage(attrs.MustString("embed_author_icon_url", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_author_icon_url")
}
}
if sv, err = formatMessage(attrs.MustString("embed_fields", ptrStringEmpty), m, r, eventData); err != nil {
if sv, err = formatMessage(attrs.MustString("embed_fields", helpers.Ptr("")), m, r, eventData); err != nil {
return errors.Wrap(err, "parsing embed_fields")
} else if sv != "" {
var flds []discordPayloadEmbedField

View File

@ -7,6 +7,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -14,14 +16,14 @@ type slackCompatibleActor struct {
plugins.ActorKit
}
func (s slackCompatibleActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (s slackCompatibleActor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
text, err := formatMessage(attrs.MustString("text", nil), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "parsing text")
}
return sendPayload(
s.fixHookURL(attrs.MustString("hook_url", ptrStringEmpty)),
s.fixHookURL(attrs.MustString("hook_url", helpers.Ptr(""))),
map[string]string{
"text": text,
},
@ -33,7 +35,7 @@ func (slackCompatibleActor) IsAsync() bool { return false }
func (slackCompatibleActor) Name() string { return "slackhook" }
func (s slackCompatibleActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
func (s slackCompatibleActor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = s.ValidateRequireNonEmpty(attrs, "hook_url", "text"); err != nil {
return err //nolint:wrapcheck
}

View File

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

View File

@ -4,6 +4,7 @@
package nuke
import (
"fmt"
"regexp"
"strings"
"sync"
@ -13,7 +14,9 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -29,10 +32,6 @@ var (
messageStore = map[string][]*storedMessage{}
messageStoreLock sync.RWMutex
ptrStringDelete = func(v string) *string { return &v }("delete")
ptrStringEmpty = func(s string) *string { return &s }("")
ptrString10m = func(v string) *string { return &v }("10m")
)
// Register provides the plugins.RegisterFunc
@ -150,14 +149,14 @@ type (
}
)
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
rawMatch, err := formatMessage(attrs.MustString("match", nil), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "formatting match")
}
match := regexp.MustCompile(rawMatch)
rawScan, err := formatMessage(attrs.MustString("scan", ptrString10m), m, r, eventData)
rawScan, err := formatMessage(attrs.MustString("scan", helpers.Ptr("10m")), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "formatting scan duration")
}
@ -171,7 +170,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
action actionFn
actionName string
)
rawAction, err := formatMessage(attrs.MustString("action", ptrStringDelete), m, r, eventData)
rawAction, err := formatMessage(attrs.MustString("action", helpers.Ptr("delete")), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "formatting action")
}
@ -235,15 +234,14 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
if v, err := attrs.String("match"); err != nil || v == "" {
return errors.New("match must be non-empty string")
}
for _, field := range []string{"scan", "action", "match"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "match", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "action", Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "scan", Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "scan", "action", "match"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
return nil

View File

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

View File

@ -10,6 +10,8 @@ import (
"gopkg.in/irc.v4"
"gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -23,9 +25,9 @@ var (
formatMessage plugins.MsgFormatter
send plugins.SendMessageFunc
ptrStringEmpty = func(v string) *string { return &v }("")
ptrStringOutFormat = func(v string) *string { return &v }("Quote #{{ .index }}: {{ .quote }}")
ptrStringZero = func(v string) *string { return &v }("0")
// ptrStringEmpty = func(v string) *string { return &v }("")
// ptrStringOutFormat = func(v string) *string { return &v }("Quote #{{ .index }}: {{ .quote }}")
// ptrStringZero = func(v string) *string { return &v }("0")
)
// Register provides the plugins.RegisterFunc
@ -93,7 +95,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
return fmt.Errorf("registering API: %w", err)
}
args.RegisterTemplateFunction("lastQuoteIndex", func(m *irc.Message, _ *plugins.Rule, _ *plugins.FieldCollection) interface{} {
args.RegisterTemplateFunction("lastQuoteIndex", func(m *irc.Message, _ *plugins.Rule, _ *fieldcollection.FieldCollection) interface{} {
return func() (int, error) {
return getMaxQuoteIdx(db, plugins.DeriveChannel(m, nil))
}
@ -113,11 +115,11 @@ type (
actor struct{}
)
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
var (
action = attrs.MustString("action", ptrStringEmpty)
indexStr = attrs.MustString("index", ptrStringZero)
quote = attrs.MustString("quote", ptrStringEmpty)
action = attrs.MustString("action", helpers.Ptr(""))
indexStr = attrs.MustString("index", helpers.Ptr("0"))
quote = attrs.MustString("quote", helpers.Ptr(""))
)
if indexStr == "" {
@ -166,7 +168,7 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
fields.Set("index", idx)
fields.Set("quote", quote)
format := attrs.MustString("format", ptrStringOutFormat)
format := attrs.MustString("format", helpers.Ptr("Quote #{{ .index }}: {{ .quote }}"))
msg, err := formatMessage(format, m, r, fields)
if err != nil {
return false, errors.Wrap(err, "formatting output message")
@ -190,31 +192,35 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
action := attrs.MustString("action", ptrStringEmpty)
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "action", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "quote", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "index", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "format", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "index", "quote", "format"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
action := attrs.MustString("action", helpers.Ptr(""))
switch action {
case "add":
if v, err := attrs.String("quote"); err != nil || v == "" {
return errors.New("quote must be non-empty string for action add")
return fmt.Errorf("quote must be non-empty string for action add")
}
case "del":
if v, err := attrs.String("index"); err != nil || v == "" {
return errors.New("index must be non-empty string for adction del")
return fmt.Errorf("index must be non-empty string for adction del")
}
case "get":
// No requirements
default:
return errors.New("action must be one of add, del or get")
}
for _, field := range []string{"index", "quote", "format"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
return fmt.Errorf("action must be one of add, del or get")
}
return nil

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package timeout
import (
"context"
"fmt"
"regexp"
"strconv"
"time"
@ -10,6 +11,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -65,7 +68,7 @@ func Register(args plugins.RegistrationArguments) error {
type actor struct{}
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
reason, err := formatMessage(attrs.MustString("reason", ptrStringEmpty), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "executing reason template")
@ -86,17 +89,17 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
if v, err := attrs.Duration("duration"); err != nil || v < time.Second {
return errors.New("duration must be of type duration greater or equal one second")
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "duration", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeDuration}),
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "reason", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "reason"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
if v, err := attrs.String("reason"); err != nil || v == "" {
return errors.New("reason must be non-empty string")
}
if err = tplValidator(attrs.MustString("reason", ptrStringEmpty)); err != nil {
return errors.Wrap(err, "validating reason template")
if attrs.MustDuration("duration", nil) < time.Second {
return errors.New("duration must be greater or equal one second")
}
return nil

View File

@ -11,6 +11,8 @@ import (
"gopkg.in/irc.v4"
"gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -144,7 +146,7 @@ func Register(args plugins.RegistrationArguments) (err error) {
type actorSetVariable struct{}
func (actorSetVariable) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actorSetVariable) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
varName, err := formatMessage(attrs.MustString("variable", nil), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "preparing variable name")
@ -171,15 +173,14 @@ func (actorSetVariable) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule,
func (actorSetVariable) IsAsync() bool { return false }
func (actorSetVariable) Name() string { return "setvariable" }
func (actorSetVariable) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
if v, err := attrs.String("variable"); err != nil || v == "" {
return errors.New("variable name must be non-empty string")
}
for _, field := range []string{"set", "variable"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
func (actorSetVariable) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "variable", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "clear", Type: fieldcollection.SchemaFieldTypeBool}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "set", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "set", "variable"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
return nil

View File

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

View File

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

View File

@ -1,17 +1,20 @@
package customevent
import (
"fmt"
"strings"
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
type actor struct{}
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *plugins.FieldCollection, attrs *plugins.FieldCollection) (preventCooldown bool, err error) {
func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error) {
fd, err := formatMessage(attrs.MustString("fields", ptrStringEmpty), m, r, eventData)
if err != nil {
return false, errors.Wrap(err, "executing fields template")
@ -35,15 +38,13 @@ func (actor) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, eventData *
func (actor) IsAsync() bool { return false }
func (actor) Name() string { return actorName }
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *plugins.FieldCollection) (err error) {
if v, err := attrs.String("fields"); err != nil || v == "" {
return errors.New("fields is expected to be non-empty string")
}
for _, field := range []string{"fields", "schedule_in"} {
if err = tplValidator(attrs.MustString(field, ptrStringEmpty)); err != nil {
return errors.Wrapf(err, "validating %s template", field)
}
func (actor) Validate(tplValidator plugins.TemplateValidatorFunc, attrs *fieldcollection.FieldCollection) (err error) {
if err = attrs.ValidateSchema(
fieldcollection.MustHaveField(fieldcollection.SchemaField{Name: "fields", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
fieldcollection.CanHaveField(fieldcollection.SchemaField{Name: "schedule_in", NonEmpty: true, Type: fieldcollection.SchemaFieldTypeString}),
helpers.SchemaValidateTemplateField(tplValidator, "fields", "schedule_in"),
); err != nil {
return fmt.Errorf("validating attributes: %w", err)
}
return nil

View File

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

View File

@ -9,9 +9,9 @@ import (
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/internal/helpers"
"github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
const cleanupTimeout = 15 * time.Minute
@ -48,7 +48,7 @@ func getFutureEvents(db database.Connector) (out []storedCustomEvent, err error)
)
}
func storeEvent(db database.Connector, scheduleAt time.Time, channel string, fields *plugins.FieldCollection) error {
func storeEvent(db database.Connector, scheduleAt time.Time, channel string, fields *fieldcollection.FieldCollection) error {
fieldBuf := new(bytes.Buffer)
if err := json.NewEncoder(fieldBuf).Encode(fields); err != nil {
return errors.Wrap(err, "marshalling fields")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

15
irc.go
View File

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

View File

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -19,7 +20,7 @@ var (
stripNewline = regexp.MustCompile(`(?m)\s*\n\s*`)
formatMessageFieldSetters = []func(compiledFields *plugins.FieldCollection, m *irc.Message, fields *plugins.FieldCollection){
formatMessageFieldSetters = []func(compiledFields *fieldcollection.FieldCollection, m *irc.Message, fields *fieldcollection.FieldCollection){
formatMessageFieldChannel,
formatMessageFieldMessage,
formatMessageFieldUserID,
@ -27,8 +28,8 @@ var (
}
)
func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *plugins.FieldCollection) (string, error) {
compiledFields := plugins.NewFieldCollection()
func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *fieldcollection.FieldCollection) (string, error) {
compiledFields := fieldcollection.NewFieldCollection()
if config != nil {
configLock.RLock()
@ -61,11 +62,11 @@ func formatMessage(tplString string, m *irc.Message, r *plugins.Rule, fields *pl
return strings.TrimSpace(buf.String()), errors.Wrap(err, "execute template")
}
func formatMessageFieldChannel(compiledFields *plugins.FieldCollection, m *irc.Message, fields *plugins.FieldCollection) {
func formatMessageFieldChannel(compiledFields *fieldcollection.FieldCollection, m *irc.Message, fields *fieldcollection.FieldCollection) {
compiledFields.Set(eventFieldChannel, plugins.DeriveChannel(m, fields))
}
func formatMessageFieldMessage(compiledFields *plugins.FieldCollection, m *irc.Message, _ *plugins.FieldCollection) {
func formatMessageFieldMessage(compiledFields *fieldcollection.FieldCollection, m *irc.Message, _ *fieldcollection.FieldCollection) {
if m == nil {
return
}
@ -73,7 +74,7 @@ func formatMessageFieldMessage(compiledFields *plugins.FieldCollection, m *irc.M
compiledFields.Set("msg", m)
}
func formatMessageFieldUserID(compiledFields *plugins.FieldCollection, m *irc.Message, _ *plugins.FieldCollection) {
func formatMessageFieldUserID(compiledFields *fieldcollection.FieldCollection, m *irc.Message, _ *fieldcollection.FieldCollection) {
if m == nil {
return
}
@ -83,7 +84,7 @@ func formatMessageFieldUserID(compiledFields *plugins.FieldCollection, m *irc.Me
}
}
func formatMessageFieldUsername(compiledFields *plugins.FieldCollection, m *irc.Message, fields *plugins.FieldCollection) {
func formatMessageFieldUsername(compiledFields *fieldcollection.FieldCollection, m *irc.Message, fields *fieldcollection.FieldCollection) {
compiledFields.Set("username", plugins.DeriveUser(m, fields))
}
@ -93,7 +94,7 @@ func validateTemplate(tplString string) error {
_, err := template.
New(tplString).
Funcs(tplFuncs.GetFuncMap(nil, nil, plugins.NewFieldCollection())).
Funcs(tplFuncs.GetFuncMap(nil, nil, fieldcollection.NewFieldCollection())).
Parse(tplString)
return errors.Wrap(err, "parsing template")
}

View File

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

View File

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

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

View File

@ -7,6 +7,7 @@ import (
"gopkg.in/irc.v4"
"gorm.io/gorm"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
)
@ -15,7 +16,7 @@ type (
// Actor defines an interface to implement in the plugin for actors
Actor interface {
// Execute will be called after the config was read into the Actor
Execute(c *irc.Client, m *irc.Message, r *Rule, evtData *FieldCollection, attrs *FieldCollection) (preventCooldown bool, err error)
Execute(c *irc.Client, m *irc.Message, r *Rule, evtData *fieldcollection.FieldCollection, attrs *fieldcollection.FieldCollection) (preventCooldown bool, err error)
// IsAsync may return true if the Execute function is to be executed
// in a Go routine as of long runtime. Normally it should return false
// except in very specific cases
@ -26,7 +27,7 @@ type (
// Validate will be called to validate the loaded configuration. It should
// return an error if required keys are missing from the AttributeStore
// or if keys contain broken configs
Validate(TemplateValidatorFunc, *FieldCollection) error
Validate(TemplateValidatorFunc, *fieldcollection.FieldCollection) error
}
// ActorCreationFunc is a function to return a new instance of the
@ -62,7 +63,7 @@ type (
// EventHandlerFunc defines the type of function required to listen
// for events
EventHandlerFunc func(evt string, eventData *FieldCollection) error
EventHandlerFunc func(evt string, eventData *fieldcollection.FieldCollection) error
// EventHandlerRegisterFunc is passed from the bot to the
// plugins RegisterFunc to register a new event handler function
// which is then fed with all events occurring in the bot
@ -76,12 +77,12 @@ type (
// ModuleConfigGetterFunc is passed from the bot to the
// plugins RegisterFunc to fetch module generic or channel specific
// configuration from the module configuration
ModuleConfigGetterFunc func(module, channel string) *FieldCollection
ModuleConfigGetterFunc func(module, channel string) *fieldcollection.FieldCollection
// MsgFormatter is passed from the bot to the
// plugins RegisterFunc to format messages using all registered and
// available template functions
MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields *FieldCollection) (string, error)
MsgFormatter func(tplString string, m *irc.Message, r *Rule, fields *fieldcollection.FieldCollection) (string, error)
// MsgModificationFunc can be used to modify messages between the
// plugins generating them and the bot sending them to the Twitch
@ -160,7 +161,7 @@ type (
// TemplateFuncGetter is the type of function to implement in the
// plugin to create a new template function on request of the bot
TemplateFuncGetter func(*irc.Message, *Rule, *FieldCollection) any
TemplateFuncGetter func(*irc.Message, *Rule, *fieldcollection.FieldCollection) any
// TemplateFuncRegister is passed from the bot to the
// plugins RegisterFunc to register a new TemplateFuncGetter
TemplateFuncRegister func(name string, fg TemplateFuncGetter, doc ...TemplateFuncDocumentation)
@ -184,5 +185,5 @@ var ErrSkipSendingMessage = errors.New("skip sending message")
// requiring access to the irc.Message, Rule or FieldCollection to
// satisfy the TemplateFuncGetter interface
func GenericTemplateFunctionGetter(f any) TemplateFuncGetter {
return func(*irc.Message, *Rule, *FieldCollection) any { return f }
return func(*irc.Message, *Rule, *fieldcollection.FieldCollection) any { return f }
}

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
)
@ -141,7 +142,7 @@ func TestAllowExecuteDisableOnTemplate(t *testing.T) {
} {
// We don't test the message formatter here but only the disable functionality
// so we fake the result of the evaluation
r.msgFormatter = func(string, *irc.Message, *Rule, *FieldCollection) (string, error) {
r.msgFormatter = func(string, *irc.Message, *Rule, *fieldcollection.FieldCollection) (string, error) {
return msg, nil
}

View File

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

View File

@ -12,6 +12,7 @@ import (
"github.com/sirupsen/logrus"
"gopkg.in/irc.v4"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
@ -88,7 +89,7 @@ func generateTplDocsRender(e *plugins.TemplateFuncDocumentationExample) (string,
rule.MatchMessage = &e.MatchMessage
}
return formatMessage(e.Template, msg, rule, plugins.FieldCollectionFromData(map[string]any{
return formatMessage(e.Template, msg, rule, fieldcollection.FieldCollectionFromData(map[string]any{
"testDuration": 5*time.Hour + 33*time.Minute + 12*time.Second,
}))
}

View File

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