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